Skip to content

Commit

Permalink
chore: update twap
Browse files Browse the repository at this point in the history
  • Loading branch information
mfw78 committed Sep 25, 2023
1 parent 01892f4 commit 6d45130
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 25 deletions.
17 changes: 15 additions & 2 deletions src/types/twap/TWAP.sol
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,22 @@ contract TWAP is BaseConditionalOrder {

order = TWAPOrder.orderFor(twap);

/// @dev Revert if the order is outside the TWAP bundle's span.
/// @dev As the `TWAPOrder.orderFor` function will revert if the TWAP has not started
/// or if the TWAP has finished, the _only_ time now that `block.timestamp` can be
/// greater than `order.validTo` is if the order is outside the TWAP bundle's span.
if (!(block.timestamp <= order.validTo)) {
revert IConditionalOrder.OrderNotValid(NOT_WITHIN_SPAN);
// Handle the case where this is the last part
uint256 currentPart = ((block.timestamp - twap.t0) / twap.t) + 1;

if (currentPart == twap.n) {
// This is the last part, and the order is outside the span. The watch tower should
// delete the order.
revert IConditionalOrder.PollNever(NOT_WITHIN_SPAN);
} else {
// This is not the last part, so the watch tower should try again at the start of
// the next part.
revert IConditionalOrder.PollTryAtEpoch(twap.t0 + (currentPart * twap.t), NOT_WITHIN_SPAN);
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/types/twap/libraries/TWAPOrderMathLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ library TWAPOrderMathLib {

unchecked {
/// @dev Order is not valid before the start (order commences at `t0`).
if (!(startTime <= block.timestamp)) revert IConditionalOrder.OrderNotValid(BEFORE_TWAP_START);
if (!(startTime <= block.timestamp)) revert IConditionalOrder.PollTryAtEpoch(startTime, BEFORE_TWAP_START);

/**
* @dev Order is expired after the last part (`n` parts, running at `t` time length).
Expand All @@ -51,7 +51,7 @@ library TWAPOrderMathLib {
* `type(uint32).max` so the sum of `startTime + (numParts * frequency)` is ≈ 2⁵⁵.
*/
if (!(block.timestamp < startTime + (numParts * frequency))) {
revert IConditionalOrder.OrderNotValid(AFTER_TWAP_FINISH);
revert IConditionalOrder.PollNever(AFTER_TWAP_FINISH);
}

/**
Expand Down
66 changes: 45 additions & 21 deletions test/ComposableCoW.twap.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,100 +49,122 @@ contract ComposableCoWTwapTest is BaseComposableCoWTest {
/**
* @dev Revert when the sell token and buy token are the same
*/
function test_getTradeableOrder_RevertOnSameTokens() public {
function test_validateData_RevertOnSameTokens() public {
// Revert when the same token is used for both the buy and sell token
TWAPOrder.Data memory o = _twapTestBundle(block.timestamp);
o.sellToken = token0;
o.buyToken = token0;

vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.OrderNotValid.selector, INVALID_SAME_TOKEN));
twap.getTradeableOrder(address(0), address(0), bytes32(0), abi.encode(o), bytes(""));
twap.validateData(abi.encode(o));
}

/**
* @dev Revert when either the buy or sell token is address(0)
*/
function test_getTradeableOrder_RevertOnTokenZero() public {
function test_validateData_RevertOnTokenZero() public {
// Revert when either the buy or sell token is address(0)
TWAPOrder.Data memory o = _twapTestBundle(block.timestamp);
o.sellToken = IERC20(address(0));

vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.OrderNotValid.selector, INVALID_TOKEN));
twap.getTradeableOrder(address(0), address(0), bytes32(0), abi.encode(o), bytes(""));
twap.validateData(abi.encode(o));

o.sellToken = token0;
o.buyToken = IERC20(address(0));

vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.OrderNotValid.selector, INVALID_TOKEN));
twap.getTradeableOrder(address(0), address(0), bytes32(0), abi.encode(o), bytes(""));
twap.validateData(abi.encode(o));
}

/**
* @dev Revert when the sell amount is 0
*/
function test_getTradeableOrder_RevertOnZeroPartSellAmount() public {
function test_validateData_RevertOnZeroPartSellAmount() public {
// Revert when the sell amount is zero
TWAPOrder.Data memory o = _twapTestBundle(block.timestamp);
o.partSellAmount = 0;

vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.OrderNotValid.selector, INVALID_PART_SELL_AMOUNT));
twap.getTradeableOrder(address(0), address(0), bytes32(0), abi.encode(o), bytes(""));
twap.validateData(abi.encode(o));
}

/**
* @dev Revert when the min part limit is 0
*/
function test_getTradeableOrder_RevertOnZeroMinPartLimit() public {
function test_validateData_RevertOnZeroMinPartLimit() public {
// Revert when the limit is zero
TWAPOrder.Data memory o = _twapTestBundle(block.timestamp);
o.minPartLimit = 0;

vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.OrderNotValid.selector, INVALID_MIN_PART_LIMIT));
twap.validateData(abi.encode(o));
}

/**
* @dev Concrete revert test on Span if the last part.
*/
function test_getTradeableOrder_RevertOnOutsideOfSpanLastPart() public {
TWAPOrder.Data memory o = _twapTestBundle(block.timestamp);

vm.warp(block.timestamp + (FREQUENCY * (NUM_PARTS - 1)) + SPAN);
vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.PollNever.selector, NOT_WITHIN_SPAN));
twap.getTradeableOrder(address(0), address(0), bytes32(0), abi.encode(o), bytes(""));
}

/**
* @dev Concrete revert test on Span if not the last part.
*/
function test_getTradeableOrder_RevertOnOutsideOfSpanNotLastPart() public {
TWAPOrder.Data memory o = _twapTestBundle(block.timestamp);

vm.warp(block.timestamp + (FREQUENCY * (NUM_PARTS - 2)) + SPAN);
vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.PollTryAtEpoch.selector, o.t0 + (FREQUENCY * (NUM_PARTS - 1)), NOT_WITHIN_SPAN));
twap.getTradeableOrder(address(0), address(0), bytes32(0), abi.encode(o), bytes(""));
}

/**
* @dev Fuzz test revert on invalid start time
*/
function test_getTradeableOrder_FuzzRevertOnInvalidStartTime(uint256 startTime) public {
function test_validateData_FuzzRevertOnInvalidStartTime(uint256 startTime) public {
vm.assume(startTime >= type(uint32).max);
// Revert when the start time exceeds or equals the max uint32
TWAPOrder.Data memory o = _twapTestBundle(startTime);
o.t0 = startTime;

vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.OrderNotValid.selector, INVALID_START_TIME));
twap.getTradeableOrder(address(0), address(0), bytes32(0), abi.encode(o), bytes(""));
twap.validateData(abi.encode(o));
}

/**
* @dev Fuzz test revert on invalid numParts
*/
function test_getTradeableOrder_FuzzRevertOnInvalidNumParts(uint256 numParts) public {
function test_validateData_FuzzRevertOnInvalidNumParts(uint256 numParts) public {
vm.assume(numParts < 2 || numParts > type(uint32).max);
// Revert if not an actual TWAP (ie. numParts < 2)
TWAPOrder.Data memory o = _twapTestBundle(block.timestamp);
o.n = numParts;

vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.OrderNotValid.selector, INVALID_NUM_PARTS));
twap.getTradeableOrder(address(0), address(0), bytes32(0), abi.encode(o), bytes(""));
twap.validateData(abi.encode(o));
}

/**
* @dev Fuzz test revert on invalid frequency
*/
function test_getTradeableOrder_FuzzRevertOnInvalidFrequency(uint256 frequency) public {
function test_validateData_FuzzRevertOnInvalidFrequency(uint256 frequency) public {
vm.assume(frequency < 1 || frequency > 365 days);
TWAPOrder.Data memory o = _twapTestBundle(block.timestamp);
o.t = frequency;

vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.OrderNotValid.selector, INVALID_FREQUENCY));
twap.getTradeableOrder(address(0), address(0), bytes32(0), abi.encode(o), bytes(""));
twap.validateData(abi.encode(o));
}

/**
* @dev Fuzz test revert on invalid span
*/
function test_getTradeableOrder_FuzzRevertOnInvalidSpan(uint256 frequency, uint256 span) public {
function test_validateData_FuzzRevertOnInvalidSpan(uint256 frequency, uint256 span) public {
vm.assume(frequency > 0 && frequency <= 365 days);
vm.assume(span > frequency);

Expand All @@ -151,7 +173,7 @@ contract ComposableCoWTwapTest is BaseComposableCoWTest {
o.span = span;

vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.OrderNotValid.selector, INVALID_SPAN));
twap.getTradeableOrder(address(0), address(0), bytes32(0), abi.encode(o), bytes(""));
twap.validateData(abi.encode(o));
}

/**
Expand All @@ -175,7 +197,7 @@ contract ComposableCoWTwapTest is BaseComposableCoWTest {
// Warp to current time
vm.warp(currentTime);

vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.OrderNotValid.selector, BEFORE_TWAP_START));
vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.PollTryAtEpoch.selector, startTime, BEFORE_TWAP_START));
twap.getTradeableOrder(address(0), address(0), bytes32(0), abi.encode(o), bytes(""));
}

Expand All @@ -201,7 +223,7 @@ contract ComposableCoWTwapTest is BaseComposableCoWTest {
// Warp to expiry
vm.warp(currentTime);

vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.OrderNotValid.selector, AFTER_TWAP_FINISH));
vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.PollNever.selector, AFTER_TWAP_FINISH));
twap.getTradeableOrder(address(0), address(0), bytes32(0), abi.encode(o), bytes(""));
}

Expand Down Expand Up @@ -229,7 +251,9 @@ contract ComposableCoWTwapTest is BaseComposableCoWTest {
// Warp to outside of the span
vm.warp(currentTime);

vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.OrderNotValid.selector, NOT_WITHIN_SPAN));
// Just check that it reverts, don't reproduce the whole logic for PollNever / PollAtEpoch
// do that in a concrete tests.
vm.expectRevert();
twap.getTradeableOrder(address(0), address(0), bytes32(0), abi.encode(o), bytes(""));
}

Expand Down Expand Up @@ -258,7 +282,7 @@ contract ComposableCoWTwapTest is BaseComposableCoWTest {
vm.warp(currentTime);

// The below should revert
vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.OrderNotValid.selector, BEFORE_TWAP_START));
vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.PollTryAtEpoch.selector, ctxBlockTimestamp, BEFORE_TWAP_START));
composableCow.getTradeableOrderWithSignature(address(safe1), params, bytes(""), new bytes32[](0));
}

Expand Down Expand Up @@ -287,7 +311,7 @@ contract ComposableCoWTwapTest is BaseComposableCoWTest {
vm.warp(currentTime);

// The below should revert
vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.OrderNotValid.selector, AFTER_TWAP_FINISH));
vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.PollNever.selector, AFTER_TWAP_FINISH));
composableCow.getTradeableOrderWithSignature(address(safe1), params, bytes(""), new bytes32[](0));
}

Expand Down

0 comments on commit 6d45130

Please sign in to comment.