diff --git a/test/GPv2VaultRelayer.test.ts b/test/GPv2VaultRelayer.test.ts index d071db33..7cd3db6a 100644 --- a/test/GPv2VaultRelayer.test.ts +++ b/test/GPv2VaultRelayer.test.ts @@ -27,144 +27,6 @@ describe("GPv2VaultRelayer", () => { vaultRelayer = await GPv2VaultRelayer.deploy(vault.address); }); - describe("transferFromAccounts", () => { - it("should revert if not called by the creator", async () => { - await expect( - vaultRelayer.connect(nonCreator).transferFromAccounts([]), - ).to.be.revertedWith("not creator"); - }); - - it("should execute ERC20 and Vault transfers", async () => { - const tokens = [ - await waffle.deployMockContract(deployer, IERC20.abi), - await waffle.deployMockContract(deployer, IERC20.abi), - await waffle.deployMockContract(deployer, IERC20.abi), - ]; - - const amount = ethers.utils.parseEther("13.37"); - await tokens[0].mock.transferFrom - .withArgs(traders[0].address, creator.address, amount) - .returns(true); - await vault.mock.manageUserBalance - .withArgs([ - { - kind: UserBalanceOpKind.TRANSFER_EXTERNAL, - asset: tokens[1].address, - amount, - sender: traders[1].address, - recipient: creator.address, - }, - { - kind: UserBalanceOpKind.WITHDRAW_INTERNAL, - asset: tokens[2].address, - amount, - sender: traders[2].address, - recipient: creator.address, - }, - ]) - .returns(); - - await expect( - vaultRelayer.transferFromAccounts([ - { - account: traders[0].address, - token: tokens[0].address, - amount, - balance: OrderBalanceId.ERC20, - }, - { - account: traders[1].address, - token: tokens[1].address, - amount, - balance: OrderBalanceId.EXTERNAL, - }, - { - account: traders[2].address, - token: tokens[2].address, - amount, - balance: OrderBalanceId.INTERNAL, - }, - ]), - ).to.not.be.reverted; - }); - - it("should revert on failed ERC20 transfers", async () => { - const token = await waffle.deployMockContract(deployer, IERC20.abi); - - const amount = ethers.utils.parseEther("4.2"); - await token.mock.transferFrom - .withArgs(traders[0].address, creator.address, amount) - .revertsWithReason("test error"); - - await expect( - vaultRelayer.transferFromAccounts([ - { - account: traders[0].address, - token: token.address, - amount, - balance: OrderBalanceId.ERC20, - }, - ]), - ).to.be.revertedWith("test error"); - }); - - it("should revert on failed Vault transfers", async () => { - const token = await waffle.deployMockContract(deployer, IERC20.abi); - - const amount = ethers.utils.parseEther("4.2"); - await vault.mock.manageUserBalance - .withArgs([ - { - kind: UserBalanceOpKind.TRANSFER_EXTERNAL, - asset: token.address, - amount, - sender: traders[0].address, - recipient: creator.address, - }, - ]) - .revertsWithReason("test error"); - - await expect( - vaultRelayer.transferFromAccounts([ - { - account: traders[0].address, - token: token.address, - amount, - balance: OrderBalanceId.EXTERNAL, - }, - ]), - ).to.be.revertedWith("test error"); - }); - - it("should revert on failed Vault withdrawals", async () => { - const token = await waffle.deployMockContract(deployer, IERC20.abi); - - const amount = ethers.utils.parseEther("4.2"); - await vault.mock.manageUserBalance - .withArgs([ - { - kind: UserBalanceOpKind.WITHDRAW_INTERNAL, - asset: token.address, - amount, - sender: traders[0].address, - recipient: creator.address, - }, - ]) - .revertsWithReason("test error"); - - await expect( - vaultRelayer.transferFromAccounts([ - { - account: traders[0].address, - token: token.address, - amount, - balance: OrderBalanceId.INTERNAL, - }, - ]), - ).to.be.revertedWith("test error"); - }); - }); - describe("batchSwapWithFee", () => { interface BatchSwapWithFee { kind: SwapKind; diff --git a/test/GPv2VaultRelayer/Helper.sol b/test/GPv2VaultRelayer/Helper.sol new file mode 100644 index 00000000..0fb24fd8 --- /dev/null +++ b/test/GPv2VaultRelayer/Helper.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity ^0.8.0; + +import {Test} from "forge-std/Test.sol"; + +import {GPv2VaultRelayer, IVault} from "src/contracts/GPv2VaultRelayer.sol"; + +contract Helper is Test { + address payable internal creator = payable(makeAddr("GPv2VaultRelayer.Helper creator")); + IVault internal vault = IVault(makeAddr("GPv2VaultRelayer.Helper vault")); + GPv2VaultRelayer internal vaultRelayer; + + function setUp() public { + // Some calls check if the vault is a contract. `0xfe` is the designated + // invalid instruction: this way, calling the vault without a mock + // triggers a revert with `InvalidEFOpcode`. + vm.etch(address(vault), hex"fe"); + + vm.prank(creator); + vaultRelayer = new GPv2VaultRelayer(vault); + } +} diff --git a/test/GPv2VaultRelayer/TransferFromAccounts.t.sol b/test/GPv2VaultRelayer/TransferFromAccounts.t.sol new file mode 100644 index 00000000..8cbc5773 --- /dev/null +++ b/test/GPv2VaultRelayer/TransferFromAccounts.t.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity ^0.8.0; + +import { + GPv2VaultRelayer, GPv2Transfer, IERC20, IVault, GPv2Transfer, GPv2Order +} from "src/contracts/GPv2VaultRelayer.sol"; + +import {Helper} from "./Helper.sol"; + +contract TransferFromAccounts is Helper { + function test_should_revert_if_not_called_by_the_creator() public { + vm.prank(makeAddr("not the creator")); + vm.expectRevert("GPv2: not creator"); + vaultRelayer.transferFromAccounts(new GPv2Transfer.Data[](0)); + } + + function test_should_execute_ERC20_and_Vault_transfers() public { + IERC20 token0 = IERC20(makeAddr("token 0")); + IERC20 token1 = IERC20(makeAddr("token 1")); + IERC20 token2 = IERC20(makeAddr("token 2")); + address trader0 = makeAddr("trader 0"); + address trader1 = makeAddr("trader 1"); + address trader2 = makeAddr("trader 2"); + + uint256 amount = 13.37 ether; + vm.mockCall(address(token0), abi.encodeCall(IERC20.transferFrom, (trader0, creator, amount)), abi.encode(true)); + + IVault.UserBalanceOp[] memory vaultOps = new IVault.UserBalanceOp[](2); + vaultOps[0] = IVault.UserBalanceOp({ + kind: IVault.UserBalanceOpKind.TRANSFER_EXTERNAL, + asset: token1, + amount: amount, + sender: trader1, + recipient: creator + }); + vaultOps[1] = IVault.UserBalanceOp({ + kind: IVault.UserBalanceOpKind.WITHDRAW_INTERNAL, + asset: token2, + amount: amount, + sender: trader2, + recipient: creator + }); + vm.mockCall(address(vault), abi.encodeCall(IVault.manageUserBalance, (vaultOps)), hex""); + + GPv2Transfer.Data[] memory transfers = new GPv2Transfer.Data[](3); + transfers[0] = + GPv2Transfer.Data({account: trader0, token: token0, amount: amount, balance: GPv2Order.BALANCE_ERC20}); + transfers[1] = + GPv2Transfer.Data({account: trader1, token: token1, amount: amount, balance: GPv2Order.BALANCE_EXTERNAL}); + transfers[2] = + GPv2Transfer.Data({account: trader2, token: token2, amount: amount, balance: GPv2Order.BALANCE_INTERNAL}); + + vm.prank(creator); + vaultRelayer.transferFromAccounts(transfers); + } + + function test_reverts_on_failed_ERC20_transfer() public { + IERC20 token = IERC20(makeAddr("token")); + address trader = makeAddr("trader"); + + uint256 amount = 4.2 ether; + vm.mockCallRevert(address(token), abi.encodeCall(IERC20.transferFrom, (trader, creator, amount)), "mock revert"); + + GPv2Transfer.Data[] memory transfers = new GPv2Transfer.Data[](1); + transfers[0] = + GPv2Transfer.Data({account: trader, token: token, amount: amount, balance: GPv2Order.BALANCE_ERC20}); + + vm.prank(creator); + vm.expectRevert("mock revert"); + vaultRelayer.transferFromAccounts(transfers); + } + + function test_reverts_on_failed_vault_transfer() public { + IERC20 token = IERC20(makeAddr("token")); + address trader = makeAddr("trader"); + + uint256 amount = 4.2 ether; + IVault.UserBalanceOp[] memory vaultOps = new IVault.UserBalanceOp[](1); + vaultOps[0] = IVault.UserBalanceOp({ + kind: IVault.UserBalanceOpKind.TRANSFER_EXTERNAL, + asset: token, + amount: amount, + sender: trader, + recipient: creator + }); + vm.mockCallRevert(address(vault), abi.encodeCall(IVault.manageUserBalance, (vaultOps)), "mock revert"); + + GPv2Transfer.Data[] memory transfers = new GPv2Transfer.Data[](1); + transfers[0] = + GPv2Transfer.Data({account: trader, token: token, amount: amount, balance: GPv2Order.BALANCE_EXTERNAL}); + + vm.prank(creator); + vm.expectRevert("mock revert"); + vaultRelayer.transferFromAccounts(transfers); + } + + function test_reverts_on_failed_vault_withdrawal() public { + IERC20 token = IERC20(makeAddr("token")); + address trader = makeAddr("trader"); + + uint256 amount = 4.2 ether; + IVault.UserBalanceOp[] memory vaultOps = new IVault.UserBalanceOp[](1); + vaultOps[0] = IVault.UserBalanceOp({ + kind: IVault.UserBalanceOpKind.WITHDRAW_INTERNAL, + asset: token, + amount: amount, + sender: trader, + recipient: creator + }); + vm.mockCallRevert(address(vault), abi.encodeCall(IVault.manageUserBalance, (vaultOps)), "mock revert"); + + GPv2Transfer.Data[] memory transfers = new GPv2Transfer.Data[](1); + transfers[0] = + GPv2Transfer.Data({account: trader, token: token, amount: amount, balance: GPv2Order.BALANCE_INTERNAL}); + + vm.prank(creator); + vm.expectRevert("mock revert"); + vaultRelayer.transferFromAccounts(transfers); + } +}