diff --git a/test/forkMainnet/ConditionalSwap/Fill.t.sol b/test/forkMainnet/ConditionalSwap/Fill.t.sol new file mode 100644 index 00000000..ecaf2777 --- /dev/null +++ b/test/forkMainnet/ConditionalSwap/Fill.t.sol @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { IConditionalSwap } from "contracts/interfaces/IConditionalSwap.sol"; +import { ConditionalOrderSwapTest } from "test/forkMainnet/ConditionalSwap/Setup.t.sol"; +import { BalanceSnapshot, Snapshot } from "test/utils/BalanceSnapshot.sol"; + +contract ConFillTest is ConditionalOrderSwapTest { + using BalanceSnapshot for Snapshot; + + function setUp() public override { + super.setUp(); + } + + function testBestBuyOrder() 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 }); + Snapshot memory makerMakerToken = BalanceSnapshot.take({ owner: maker, token: defaultOrder.makerToken }); + Snapshot memory recTakerToken = BalanceSnapshot.take({ owner: recipient, token: defaultOrder.takerToken }); + Snapshot memory recMakerToken = BalanceSnapshot.take({ owner: recipient, token: defaultOrder.makerToken }); + + // craft the `flagAndPeriod` of the defaultOrder for BestBuy case + uint256 flags = 1 << 253; + defaultOrder.flagsAndPeriod = flags; + + defaultTakerSig = signConOrder(takerPrivateKey, defaultOrder, address(conditionalSwap)); + + vm.startPrank(maker); + conditionalSwap.fillConOrder(defaultOrder, defaultTakerSig, defaultOrder.takerTokenAmount, defaultOrder.makerTokenAmount, defaultSettlementData); + vm.stopPrank(); + + takerTakerToken.assertChange(-int256(defaultOrder.takerTokenAmount)); + takerMakerToken.assertChange(int256(0)); + makerTakerToken.assertChange(int256(defaultOrder.takerTokenAmount)); + makerMakerToken.assertChange(-int256(defaultOrder.makerTokenAmount)); + recTakerToken.assertChange(int256(0)); + recMakerToken.assertChange(int256(defaultOrder.makerTokenAmount)); + } + + function testRepayment() 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 }); + Snapshot memory makerMakerToken = BalanceSnapshot.take({ owner: maker, token: defaultOrder.makerToken }); + Snapshot memory recTakerToken = BalanceSnapshot.take({ owner: recipient, token: defaultOrder.takerToken }); + Snapshot memory recMakerToken = BalanceSnapshot.take({ owner: recipient, token: defaultOrder.makerToken }); + + // craft the `flagAndPeriod` of the defaultOrder for BestBuy case + uint256 flags = 7 << 253; + uint256 period = 12 hours; + uint256 numberOfCycles = (defaultExpiry - block.timestamp) / period; + defaultOrder.flagsAndPeriod = flags | period; + + defaultTakerSig = signConOrder(takerPrivateKey, defaultOrder, address(conditionalSwap)); + vm.startPrank(maker); + for (uint256 i; i < numberOfCycles; ++i) { + conditionalSwap.fillConOrder(defaultOrder, defaultTakerSig, defaultOrder.takerTokenAmount, defaultOrder.makerTokenAmount, defaultSettlementData); + vm.warp(block.timestamp + period); + } + vm.stopPrank(); + + takerTakerToken.assertChange(-int256(defaultOrder.takerTokenAmount) * int256(numberOfCycles)); + takerMakerToken.assertChange(int256(0)); + makerTakerToken.assertChange(int256(defaultOrder.takerTokenAmount) * int256(numberOfCycles)); + makerMakerToken.assertChange(-int256(defaultOrder.makerTokenAmount) * int256(numberOfCycles)); + recTakerToken.assertChange(int256(0)); + 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); + + vm.expectRevert(IConditionalSwap.ExpiredOrder.selector); + vm.startPrank(maker); + conditionalSwap.fillConOrder(defaultOrder, defaultTakerSig, defaultOrder.takerTokenAmount, defaultOrder.makerTokenAmount, defaultSettlementData); + vm.stopPrank(); + } + + function testCannotFillOrderByInvalidOderMaker() public { + address invalidOrderMaker = makeAddr("invalidOrderMaker"); + + vm.expectRevert(IConditionalSwap.NotOrderMaker.selector); + vm.startPrank(invalidOrderMaker); + conditionalSwap.fillConOrder(defaultOrder, defaultTakerSig, defaultOrder.takerTokenAmount, defaultOrder.makerTokenAmount, defaultSettlementData); + vm.stopPrank(); + } + + function testCannotFillOrderWithZeroTakerTokenAmount() public { + vm.expectRevert(IConditionalSwap.ZeroTokenAmount.selector); + vm.startPrank(maker); + conditionalSwap.fillConOrder(defaultOrder, defaultTakerSig, 0, defaultOrder.makerTokenAmount, defaultSettlementData); + vm.stopPrank(); + } + + function testCannotFillOrderWithInvalidTotalTakerTokenAmount() public { + // craft the `flagAndPeriod` of the defaultOrder for BestBuy case + uint256 flags = 1 << 253; + defaultOrder.flagsAndPeriod = flags; + + defaultTakerSig = signConOrder(takerPrivateKey, defaultOrder, address(conditionalSwap)); + + vm.startPrank(maker); + // the first fill with full takerTokenAmount + conditionalSwap.fillConOrder(defaultOrder, defaultTakerSig, defaultOrder.takerTokenAmount, defaultOrder.makerTokenAmount, defaultSettlementData); + + vm.expectRevert(IConditionalSwap.InvalidTakingAmount.selector); + // The second fill with 1 takerTokenAmount would exceed the total cap this time. + conditionalSwap.fillConOrder(defaultOrder, defaultTakerSig, 1, defaultOrder.makerTokenAmount, defaultSettlementData); + vm.stopPrank(); + } + + function testCannotFillOrderWithInvalidSingleTakerTokenAmount() public { + // craft the `flagAndPeriod` of the defaultOrder for BestBuy case + uint256 flags = 7 << 253; + uint256 period = 12 hours; + defaultOrder.flagsAndPeriod = flags | period; + + defaultTakerSig = signConOrder(takerPrivateKey, defaultOrder, address(conditionalSwap)); + + vm.expectRevert(IConditionalSwap.InvalidTakingAmount.selector); + vm.startPrank(maker); + conditionalSwap.fillConOrder(defaultOrder, defaultTakerSig, defaultOrder.takerTokenAmount + 1, defaultOrder.makerTokenAmount, defaultSettlementData); + vm.stopPrank(); + } + + function testCannotFillOrderWithInvalidZeroRecipient() public { + defaultOrder.recipient = payable(address(0)); + + vm.expectRevert(IConditionalSwap.InvalidRecipient.selector); + vm.startPrank(maker); + conditionalSwap.fillConOrder(defaultOrder, defaultTakerSig, defaultOrder.takerTokenAmount, defaultOrder.makerTokenAmount, defaultSettlementData); + vm.stopPrank(); + } + + function testCannotFillOrderWithIncorrectSignature() public { + uint256 randomPrivateKey = 1234; + bytes memory randomEOASig = signConOrder(randomPrivateKey, defaultOrder, address(conditionalSwap)); + + vm.expectRevert(IConditionalSwap.InvalidSignature.selector); + vm.startPrank(maker); + conditionalSwap.fillConOrder(defaultOrder, randomEOASig, defaultOrder.takerTokenAmount, defaultOrder.makerTokenAmount, defaultSettlementData); + vm.stopPrank(); + } + + function testCannotFillOrderWithinSamePeriod() public { + // craft the `flagAndPeriod` of the defaultOrder for BestBuy case + uint256 flags = 7 << 253; + uint256 period = 12 hours; + defaultOrder.flagsAndPeriod = flags | period; + + defaultTakerSig = signConOrder(takerPrivateKey, defaultOrder, address(conditionalSwap)); + vm.startPrank(maker); + conditionalSwap.fillConOrder(defaultOrder, defaultTakerSig, defaultOrder.takerTokenAmount, defaultOrder.makerTokenAmount, defaultSettlementData); + vm.warp(block.timestamp + 1); + vm.expectRevert(IConditionalSwap.InsufficientTimePassed.selector); + conditionalSwap.fillConOrder(defaultOrder, defaultTakerSig, defaultOrder.takerTokenAmount, defaultOrder.makerTokenAmount, defaultSettlementData); + vm.stopPrank(); + } +} diff --git a/test/forkMainnet/ConditionalSwap/Setup.t.sol b/test/forkMainnet/ConditionalSwap/Setup.t.sol new file mode 100644 index 00000000..ab94a083 --- /dev/null +++ b/test/forkMainnet/ConditionalSwap/Setup.t.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +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 { Tokens } from "test/utils/Tokens.sol"; +import { BalanceUtil } from "test/utils/BalanceUtil.sol"; +import { SigHelper } from "test/utils/SigHelper.sol"; +import { computeContractAddress } from "test/utils/Addresses.sol"; +import { Permit2Helper } from "test/utils/Permit2Helper.sol"; + +contract ConditionalOrderSwapTest is Test, Tokens, BalanceUtil, Permit2Helper, SigHelper { + // role + address public conditionalOrderOwner = makeAddr("conditionalOrderOwner"); + address public allowanceTargetOwner = makeAddr("allowanceTargetOwner"); + uint256 public takerPrivateKey = uint256(1); + uint256 public makerPrivateKey = uint256(2); + address public taker = vm.addr(takerPrivateKey); + address payable public maker = payable(vm.addr(makerPrivateKey)); + address payable public recipient = payable(makeAddr("recipient")); + + uint256 public defaultExpiry = block.timestamp + 1 days; + uint256 public defaultSalt = 1234; + bytes public defaultTakerPermit; + bytes public defaultTakerSig; + bytes public defaultSettlementData; + + ConditionalSwap conditionalSwap; + AllowanceTarget allowanceTarget; + ConOrder defaultOrder; + + function setUp() public virtual { + // deploy allowance target + address[] memory trusted = new address[](1); + // pre-compute ConditionalOrderSwap address since the whitelist of allowance target is immutable + // NOTE: this assumes LimitOrderSwap is deployed right next to Allowance Target + trusted[0] = computeContractAddress(address(this), uint8(vm.getNonce(address(this)) + 1)); + + allowanceTarget = new AllowanceTarget(allowanceTargetOwner, trusted); + conditionalSwap = new ConditionalSwap(conditionalOrderOwner, UNISWAP_PERMIT2_ADDRESS, address(allowanceTarget)); + + deal(maker, 100 ether); + deal(taker, 100 ether); + setTokenBalanceAndApprove(maker, address(conditionalSwap), tokens, 100000); + setTokenBalanceAndApprove(taker, address(conditionalSwap), tokens, 100000); + + defaultTakerPermit = hex"01"; + defaultSettlementData = hex"00"; + + defaultOrder = ConOrder({ + taker: taker, + maker: maker, + recipient: recipient, + takerToken: USDT_ADDRESS, + takerTokenAmount: 10 * 1e6, + makerToken: DAI_ADDRESS, + makerTokenAmount: 10 ether, + takerTokenPermit: defaultTakerPermit, + flagsAndPeriod: 0, + expiry: defaultExpiry, + salt: defaultSalt + }); + } +}