diff --git a/contracts/ConditionalSwap.sol b/contracts/ConditionalSwap.sol index 0ca3fb9b..e7fb05f2 100644 --- a/contracts/ConditionalSwap.sol +++ b/contracts/ConditionalSwap.sol @@ -82,29 +82,25 @@ contract ConditionalSwap is IConditionalSwap, Ownable, TokenCollector, EIP712 { bytes1 settlementType = settlementData[0]; bytes memory strategyData = settlementData[1:]; - uint256 returnedAmount; if (settlementType == 0x0) { // direct settlement type _collect(order.takerToken, order.taker, msg.sender, takerTokenAmount, order.takerTokenPermit); _collect(order.makerToken, msg.sender, order.recipient, makerTokenAmount, order.takerTokenPermit); } else if (settlementType == 0x01) { // strategy settlement type - (address strategy, bytes memory data) = abi.decode(strategyData, (address, bytes)); _collect(order.takerToken, order.taker, strategy, takerTokenAmount, order.takerTokenPermit); uint256 makerTokenBalanceBefore = order.makerToken.getBalance(address(this)); //@todo Create a separate strategy contract specifically for conditionalSwap IStrategy(strategy).executeStrategy(order.takerToken, order.makerToken, takerTokenAmount, data); - returnedAmount = order.makerToken.getBalance(address(this)) - makerTokenBalanceBefore; + uint256 returnedAmount = order.makerToken.getBalance(address(this)) - makerTokenBalanceBefore; - if (returnedAmount < minMakerTokenAmount) revert InsufficientOutput(); + if (returnedAmount < makerTokenAmount) revert InsufficientOutput(); order.makerToken.transferTo(order.recipient, returnedAmount); - } else { - revert(); // specific the error message1 - } + } else revert InvalidSettlementType(); - _emitConOrderFilled(order, orderHash, takerTokenAmount, returnedAmount); + _emitConOrderFilled(order, orderHash, takerTokenAmount, makerTokenAmount); } function _emitConOrderFilled(ConOrder calldata order, bytes32 orderHash, uint256 takerTokenSettleAmount, uint256 makerTokenSettleAmount) internal { diff --git a/contracts/interfaces/IConditionalSwap.sol b/contracts/interfaces/IConditionalSwap.sol index 8b668ee0..6cd3067d 100644 --- a/contracts/interfaces/IConditionalSwap.sol +++ b/contracts/interfaces/IConditionalSwap.sol @@ -4,18 +4,18 @@ pragma solidity 0.8.17; import { ConOrder } from "../libraries/ConditionalOrder.sol"; interface IConditionalSwap { - // error error ExpiredOrder(); error InsufficientTimePassed(); - error ZeroTokenAmount(); error InvalidSignature(); + error ZeroTokenAmount(); error InvalidTakingAmount(); error InvalidMakingAmount(); - error InvalidRecipient(); - error NotOrderMaker(); error InsufficientOutput(); + error NotOrderMaker(); + error InvalidRecipient(); + error InvalidSettlementType(); - // event + /// @notice Emitted when a conditional order is filled event ConditionalOrderFilled( bytes32 indexed orderHash, address indexed taker, diff --git a/test/forkMainnet/ConditionalSwap/Fill.t.sol b/test/forkMainnet/ConditionalSwap/Fill.t.sol index ecaf2777..be70cbdc 100644 --- a/test/forkMainnet/ConditionalSwap/Fill.t.sol +++ b/test/forkMainnet/ConditionalSwap/Fill.t.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.17; import { IConditionalSwap } from "contracts/interfaces/IConditionalSwap.sol"; +import { getConOrderHash } from "contracts/libraries/ConditionalOrder.sol"; import { ConditionalOrderSwapTest } from "test/forkMainnet/ConditionalSwap/Setup.t.sol"; import { BalanceSnapshot, Snapshot } from "test/utils/BalanceSnapshot.sol"; @@ -21,11 +22,23 @@ contract ConFillTest is ConditionalOrderSwapTest { Snapshot memory recMakerToken = BalanceSnapshot.take({ owner: recipient, token: defaultOrder.makerToken }); // craft the `flagAndPeriod` of the defaultOrder for BestBuy case - uint256 flags = 1 << 253; + uint256 flags = FLG_PARTIAL_FILL_MASK; defaultOrder.flagsAndPeriod = flags; defaultTakerSig = signConOrder(takerPrivateKey, defaultOrder, address(conditionalSwap)); + vm.expectEmit(true, true, true, true); + emit ConditionalOrderFilled( + getConOrderHash(defaultOrder), + defaultOrder.taker, + defaultOrder.maker, + defaultOrder.takerToken, + defaultOrder.takerTokenAmount, + defaultOrder.makerToken, + defaultOrder.makerTokenAmount, + recipient + ); + vm.startPrank(maker); conditionalSwap.fillConOrder(defaultOrder, defaultTakerSig, defaultOrder.takerTokenAmount, defaultOrder.makerTokenAmount, defaultSettlementData); vm.stopPrank(); @@ -38,7 +51,7 @@ contract ConFillTest is ConditionalOrderSwapTest { recMakerToken.assertChange(int256(defaultOrder.makerTokenAmount)); } - function testRepayment() external { + function testRepaymentOrDCAOrder() external { Snapshot memory takerTakerToken = BalanceSnapshot.take({ owner: taker, token: defaultOrder.takerToken }); Snapshot memory takerMakerToken = BalanceSnapshot.take({ owner: taker, token: defaultOrder.makerToken }); Snapshot memory makerTakerToken = BalanceSnapshot.take({ owner: maker, token: defaultOrder.takerToken }); @@ -47,12 +60,26 @@ contract ConFillTest is ConditionalOrderSwapTest { Snapshot memory recMakerToken = BalanceSnapshot.take({ owner: recipient, token: defaultOrder.makerToken }); // craft the `flagAndPeriod` of the defaultOrder for BestBuy case - uint256 flags = 7 << 253; + uint256 flags = FLG_SINGLE_AMOUNT_CAP_MASK | FLG_PERIODIC_MASK | FLG_PARTIAL_FILL_MASK; uint256 period = 12 hours; - uint256 numberOfCycles = (defaultExpiry - block.timestamp) / period; defaultOrder.flagsAndPeriod = flags | period; + uint256 numberOfCycles = (defaultExpiry - block.timestamp) / period; + defaultTakerSig = signConOrder(takerPrivateKey, defaultOrder, address(conditionalSwap)); + + vm.expectEmit(true, true, true, true); + emit ConditionalOrderFilled( + getConOrderHash(defaultOrder), + defaultOrder.taker, + defaultOrder.maker, + defaultOrder.takerToken, + defaultOrder.takerTokenAmount, + defaultOrder.makerToken, + defaultOrder.makerTokenAmount, + recipient + ); + vm.startPrank(maker); for (uint256 i; i < numberOfCycles; ++i) { conditionalSwap.fillConOrder(defaultOrder, defaultTakerSig, defaultOrder.takerTokenAmount, defaultOrder.makerTokenAmount, defaultSettlementData); @@ -68,14 +95,6 @@ contract ConFillTest is ConditionalOrderSwapTest { recMakerToken.assertChange(int256(defaultOrder.makerTokenAmount) * int256(numberOfCycles)); } - function testDCAOrder() public { - // craft the `flagAndPeriod` of the defaultOrder for BestBuy case - uint256 flags = 7 << 253; - uint256 period = 1 days; - - defaultOrder.flagsAndPeriod = flags | period; - } - function testCannotFillExpiredOrder() public { vm.warp(defaultOrder.expiry + 1); @@ -103,7 +122,7 @@ contract ConFillTest is ConditionalOrderSwapTest { function testCannotFillOrderWithInvalidTotalTakerTokenAmount() public { // craft the `flagAndPeriod` of the defaultOrder for BestBuy case - uint256 flags = 1 << 253; + uint256 flags = FLG_PARTIAL_FILL_MASK; defaultOrder.flagsAndPeriod = flags; defaultTakerSig = signConOrder(takerPrivateKey, defaultOrder, address(conditionalSwap)); @@ -120,7 +139,7 @@ contract ConFillTest is ConditionalOrderSwapTest { function testCannotFillOrderWithInvalidSingleTakerTokenAmount() public { // craft the `flagAndPeriod` of the defaultOrder for BestBuy case - uint256 flags = 7 << 253; + uint256 flags = FLG_SINGLE_AMOUNT_CAP_MASK | FLG_PERIODIC_MASK | FLG_PARTIAL_FILL_MASK; uint256 period = 12 hours; defaultOrder.flagsAndPeriod = flags | period; @@ -153,7 +172,7 @@ contract ConFillTest is ConditionalOrderSwapTest { function testCannotFillOrderWithinSamePeriod() public { // craft the `flagAndPeriod` of the defaultOrder for BestBuy case - uint256 flags = 7 << 253; + uint256 flags = FLG_SINGLE_AMOUNT_CAP_MASK | FLG_PERIODIC_MASK | FLG_PARTIAL_FILL_MASK; uint256 period = 12 hours; defaultOrder.flagsAndPeriod = flags | period; @@ -165,4 +184,24 @@ contract ConFillTest is ConditionalOrderSwapTest { conditionalSwap.fillConOrder(defaultOrder, defaultTakerSig, defaultOrder.takerTokenAmount, defaultOrder.makerTokenAmount, defaultSettlementData); vm.stopPrank(); } + + function testCannotFillOrderWithInvalidSettlementType() public { + bytes memory settlementData = hex"02"; + + defaultTakerSig = signConOrder(takerPrivateKey, defaultOrder, address(conditionalSwap)); + + vm.expectRevert(IConditionalSwap.InvalidSettlementType.selector); + vm.startPrank(maker); + conditionalSwap.fillConOrder(defaultOrder, defaultTakerSig, defaultOrder.takerTokenAmount, defaultOrder.makerTokenAmount, settlementData); + vm.stopPrank(); + } + + function testCannotFillOrderWithInvalidMakingAmount() public { + defaultTakerSig = signConOrder(takerPrivateKey, defaultOrder, address(conditionalSwap)); + + vm.expectRevert(IConditionalSwap.InvalidMakingAmount.selector); + vm.startPrank(maker); + conditionalSwap.fillConOrder(defaultOrder, defaultTakerSig, defaultOrder.takerTokenAmount, defaultOrder.makerTokenAmount - 1, defaultSettlementData); + vm.stopPrank(); + } } diff --git a/test/forkMainnet/ConditionalSwap/Setup.t.sol b/test/forkMainnet/ConditionalSwap/Setup.t.sol index ab94a083..7d7a9247 100644 --- a/test/forkMainnet/ConditionalSwap/Setup.t.sol +++ b/test/forkMainnet/ConditionalSwap/Setup.t.sol @@ -2,11 +2,9 @@ pragma solidity 0.8.17; import { Test } from "forge-std/Test.sol"; -import { IWETH } from "contracts/interfaces/IWETH.sol"; -import { IConditionalSwap } from "contracts/interfaces/IConditionalSwap.sol"; import { ConditionalSwap } from "contracts/ConditionalSwap.sol"; import { AllowanceTarget } from "contracts/AllowanceTarget.sol"; -import { ConOrder, getConOrderHash } from "contracts/libraries/ConditionalOrder.sol"; +import { ConOrder } from "contracts/libraries/ConditionalOrder.sol"; import { Tokens } from "test/utils/Tokens.sol"; import { BalanceUtil } from "test/utils/BalanceUtil.sol"; import { SigHelper } from "test/utils/SigHelper.sol"; @@ -14,6 +12,17 @@ import { computeContractAddress } from "test/utils/Addresses.sol"; import { Permit2Helper } from "test/utils/Permit2Helper.sol"; contract ConditionalOrderSwapTest is Test, Tokens, BalanceUtil, Permit2Helper, SigHelper { + event ConditionalOrderFilled( + bytes32 indexed orderHash, + address indexed taker, + address indexed maker, + address takerToken, + uint256 takerTokenFilledAmount, + address makerToken, + uint256 makerTokenSettleAmount, + address recipient + ); + // role address public conditionalOrderOwner = makeAddr("conditionalOrderOwner"); address public allowanceTargetOwner = makeAddr("allowanceTargetOwner"); @@ -29,6 +38,11 @@ contract ConditionalOrderSwapTest is Test, Tokens, BalanceUtil, Permit2Helper, S bytes public defaultTakerSig; bytes public defaultSettlementData; + // mask for triggering different business logic (e.g. BestBuy, Repayment, DCA) + uint256 public constant FLG_SINGLE_AMOUNT_CAP_MASK = 1 << 255; // ConOrder.amount is the cap of single execution, not total cap + uint256 public constant FLG_PERIODIC_MASK = 1 << 254; // ConOrder can be executed periodically + uint256 public constant FLG_PARTIAL_FILL_MASK = 1 << 253; // ConOrder can be fill partially + ConditionalSwap conditionalSwap; AllowanceTarget allowanceTarget; ConOrder defaultOrder;