Skip to content

Commit

Permalink
chore: migrate GPv2VaultRelayer.batchSwapWithFee test to Foundry _exc…
Browse files Browse the repository at this point in the history
…ept_ fee transfer tests (#175)

## Description

See title. Some related helper functions have been migrated to Solidity
as well.
All remaining tests for `batchSwapWithFee` are in their own `describe`
block and will be migrated to their own contract.

## Test Plan

CI.

## Related Issues

#124
  • Loading branch information
fedgiac authored Jul 22, 2024
1 parent 13795b4 commit 6acd04f
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 124 deletions.
121 changes: 1 addition & 120 deletions test/GPv2VaultRelayer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import { SwapKind, UserBalanceOpKind } from "./balancer";
import { OrderBalanceId } from "./encoding";

describe("GPv2VaultRelayer", () => {
const [deployer, creator, nonCreator, ...traders] =
waffle.provider.getWallets();
const [deployer, creator, ...traders] = waffle.provider.getWallets();

let vault: MockContract;
let vaultRelayer: Contract;
Expand Down Expand Up @@ -69,124 +68,6 @@ describe("GPv2VaultRelayer", () => {
},
];
};
const emptySwap = encodeSwapParams({});

it("should revert if not called by the creator", async () => {
await expect(
vaultRelayer.connect(nonCreator).batchSwapWithFee(...emptySwap),
).to.be.revertedWith("not creator");
});

for (const [name, kind] of Object.entries({
In: SwapKind.GIVEN_IN,
Out: SwapKind.GIVEN_OUT,
} as const)) {
describe(`Swap Given ${name}`, () => {
it(`performs swaps given ${name.toLowerCase()}`, async () => {
const swaps = [
{
poolId: `0x${"01".repeat(32)}`,
assetInIndex: 0,
assetOutIndex: 1,
amount: ethers.utils.parseEther("42.0"),
userData: "0x010203",
},
{
poolId: `0x${"02".repeat(32)}`,
assetInIndex: 1,
assetOutIndex: 2,
amount: ethers.utils.parseEther("1337.0"),
userData: "0xabcd",
},
];
const tokens = [
await waffle.deployMockContract(deployer, IERC20.abi),
await waffle.deployMockContract(deployer, IERC20.abi),
await waffle.deployMockContract(deployer, IERC20.abi),
];
const funds = {
sender: traders[0].address,
fromInternalBalance: false,
recipient: traders[1].address,
toInternalBalance: true,
};
const limits = [
ethers.utils.parseEther("42.0"),
ethers.constants.Zero,
ethers.utils.parseEther("1337.0").mul(-1),
];
const deadline = 0x01020304;
const feeTransfer = {
account: traders[0].address,
token: tokens[0].address,
amount: ethers.utils.parseEther("1.0"),
balance: OrderBalanceId.ERC20,
};

await vault.mock.batchSwap
.withArgs(
kind,
swaps,
tokens.map(({ address }) => address),
funds,
limits,
deadline,
)
.returns([]);
await tokens[0].mock.transferFrom.returns(true);

await expect(
vaultRelayer.batchSwapWithFee(
kind,
swaps,
tokens.map(({ address }) => address),
funds,
limits,
deadline,
feeTransfer,
),
).to.not.be.reverted;
});
});
}

it("returns the Vault swap token deltas", async () => {
const deltas = [
ethers.utils.parseEther("42.0"),
ethers.constants.Zero,
ethers.utils.parseEther("1337.0").mul(-1),
];

const token = await waffle.deployMockContract(deployer, IERC20.abi);
const feeTransfer = {
account: traders[0].address,
token: token.address,
amount: ethers.utils.parseEther("1.0"),
balance: OrderBalanceId.ERC20,
};

await vault.mock.batchSwap.returns(deltas);
await token.mock.transferFrom.returns(true);

expect(
await vaultRelayer.callStatic.batchSwapWithFee(
...encodeSwapParams({
kind: SwapKind.GIVEN_IN,
feeTransfer,
}),
),
).to.deep.equal(deltas);
});

it("reverts on failed Vault swap", async () => {
await vault.mock.batchSwap.revertsWithReason("test error");

await expect(
vaultRelayer.batchSwapWithFee(
...encodeSwapParams({ kind: SwapKind.GIVEN_OUT }),
),
).to.be.revertedWith("test error");
});

describe("Fee Transfer", () => {
it("should perform ERC20 transfer when not using direct ERC20 balance", async () => {
Expand Down
120 changes: 120 additions & 0 deletions test/GPv2VaultRelayer/BatchSwapWithFee.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
pragma solidity ^0.8.0;

import {IERC20, IVault, GPv2Transfer, GPv2Order} from "src/contracts/GPv2VaultRelayer.sol";

import {BatchSwapWithFeeHelper} from "./Helper.sol";

contract BatchSwapWithFee is BatchSwapWithFeeHelper {
function test_should_revert_if_not_called_by_the_creator() public {
vm.prank(makeAddr("not the creator"));
vm.expectRevert("GPv2: not creator");
batchSwapWithFee(vaultRelayer, defaultSwapWithFees());
}

function test_performs_swaps_given_in() public {
performs_swaps_for_swap_kind(IVault.SwapKind.GIVEN_IN);
}

function test_performs_swaps_given_out() public {
performs_swaps_for_swap_kind(IVault.SwapKind.GIVEN_OUT);
}

function performs_swaps_for_swap_kind(IVault.SwapKind kind) private {
address trader0 = makeAddr("trader 0");
address trader1 = makeAddr("trader 1");

IVault.BatchSwapStep[] memory swaps = new IVault.BatchSwapStep[](2);
swaps[0] = IVault.BatchSwapStep({
poolId: keccak256("pool id 1"),
assetInIndex: 0,
assetOutIndex: 1,
amount: 42 ether,
userData: hex"010203"
});
swaps[1] = IVault.BatchSwapStep({
poolId: keccak256("pool id 2"),
assetInIndex: 1,
assetOutIndex: 2,
amount: 1337 ether,
userData: hex"abcd"
});

IERC20[] memory tokens = new IERC20[](3);
tokens[0] = IERC20(makeAddr("token 0"));
tokens[1] = IERC20(makeAddr("token 1"));
tokens[2] = IERC20(makeAddr("token 2"));

IVault.FundManagement memory funds = IVault.FundManagement({
sender: trader0,
fromInternalBalance: false,
recipient: payable(trader1),
toInternalBalance: true
});

int256[] memory limits = new int256[](3);
limits[0] = 42 ether;
limits[1] = 0;
limits[2] = -1337 ether;

uint256 deadline = 0x01020304;

GPv2Transfer.Data memory feeTransfer =
GPv2Transfer.Data({account: trader0, token: tokens[0], amount: 1 ether, balance: GPv2Order.BALANCE_ERC20});

// end parameter setup

vm.mockCall(
address(vault),
abi.encodeCall(IVault.batchSwap, (kind, swaps, tokens, funds, limits, deadline)),
abi.encode(new int256[](0))
);
// Note: any transfer should return true.
vm.mockCall(address(tokens[0]), abi.encodePacked(IERC20.transferFrom.selector), abi.encode(true));

vm.prank(creator);
batchSwapWithFee(
vaultRelayer,
SwapWithFees({
kind: kind,
swaps: swaps,
tokens: tokens,
funds: funds,
limits: limits,
deadline: deadline,
feeTransfer: feeTransfer
})
);
}

function test_returns_the_vault_swap_token_delta() public {
address trader = makeAddr("trader");

int256[] memory deltas = new int256[](3);
deltas[0] = 42 ether;
deltas[1] = 0;
deltas[2] = -1337 ether;

IERC20 token = IERC20(makeAddr("token"));
GPv2Transfer.Data memory feeTransfer =
GPv2Transfer.Data({account: trader, token: token, amount: 1 ether, balance: GPv2Order.BALANCE_ERC20});

vm.mockCall(address(vault), abi.encodePacked(IVault.batchSwap.selector), abi.encode(deltas));
vm.mockCall(address(token), abi.encodePacked(IERC20.transferFrom.selector), abi.encode(true));

SwapWithFees memory swapWithFees = defaultSwapWithFees();
swapWithFees.kind = IVault.SwapKind.GIVEN_IN;
swapWithFees.feeTransfer = feeTransfer;

vm.prank(creator);
int256[] memory result = batchSwapWithFee(vaultRelayer, swapWithFees);
assertEq(result, deltas);
}

function test_reverts_on_failed_vault_swap() public {
vm.mockCallRevert(address(vault), abi.encodePacked(IVault.batchSwap.selector), "mock revert");
vm.prank(creator);
vm.expectRevert("mock revert");
batchSwapWithFee(vaultRelayer, defaultSwapWithFees());
}
}
47 changes: 46 additions & 1 deletion test/GPv2VaultRelayer/Helper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity ^0.8.0;

import {Test} from "forge-std/Test.sol";

import {GPv2VaultRelayer, IVault} from "src/contracts/GPv2VaultRelayer.sol";
import {GPv2VaultRelayer, IVault, IERC20, GPv2Order, GPv2Transfer} from "src/contracts/GPv2VaultRelayer.sol";

contract Helper is Test {
address payable internal creator = payable(makeAddr("GPv2VaultRelayer.Helper creator"));
Expand All @@ -20,3 +20,48 @@ contract Helper is Test {
vaultRelayer = new GPv2VaultRelayer(vault);
}
}

contract BatchSwapWithFeeHelper is Helper {
// All input parameters to `batchSwapWithFee`
struct SwapWithFees {
IVault.SwapKind kind;
IVault.BatchSwapStep[] swaps;
IERC20[] tokens;
IVault.FundManagement funds;
int256[] limits;
uint256 deadline;
GPv2Transfer.Data feeTransfer;
}

function defaultSwapWithFees() internal pure returns (SwapWithFees memory) {
return SwapWithFees({
kind: IVault.SwapKind.GIVEN_IN,
swaps: new IVault.BatchSwapStep[](0),
tokens: new IERC20[](0),
funds: IVault.FundManagement({
sender: address(0),
fromInternalBalance: true,
recipient: payable(address(0)),
toInternalBalance: true
}),
limits: new int256[](0),
deadline: 0,
feeTransfer: GPv2Transfer.Data({
account: address(0),
token: IERC20(address(0)),
amount: 0,
balance: GPv2Order.BALANCE_ERC20
})
});
}

// Wrapper function to call `batchSwapWithFee` with structured data
function batchSwapWithFee(GPv2VaultRelayer vaultRelayer, SwapWithFees memory swap)
internal
returns (int256[] memory)
{
return vaultRelayer.batchSwapWithFee(
swap.kind, swap.swaps, swap.tokens, swap.funds, swap.limits, swap.deadline, swap.feeTransfer
);
}
}
4 changes: 1 addition & 3 deletions test/GPv2VaultRelayer/TransferFromAccounts.t.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
// 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 {IERC20, IVault, GPv2Transfer, GPv2Order} from "src/contracts/GPv2VaultRelayer.sol";

import {Helper} from "./Helper.sol";

Expand Down

0 comments on commit 6acd04f

Please sign in to comment.