Skip to content

Commit

Permalink
chore: migrate GPv2Trade extractOrder tests to Foundry
Browse files Browse the repository at this point in the history
  • Loading branch information
fedgiac committed Aug 8, 2024
1 parent 658b897 commit 0c08be9
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 97 deletions.
99 changes: 2 additions & 97 deletions test/GPv2Trade.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { expect } from "chai";
import { Contract, BigNumber } from "ethers";
import { ethers, waffle } from "hardhat";
import { Contract } from "ethers";
import { ethers } from "hardhat";

import {
OrderBalance,
OrderKind,
SettlementEncoder,
SigningScheme,
encodeOrderFlags,
encodeSigningScheme,
Expand All @@ -15,34 +14,9 @@ import {
OrderBalanceId,
decodeOrderKind,
decodeOrderBalance,
decodeOrder,
} from "./encoding";

function fillBytes(count: number, byte: number): string {
return ethers.utils.hexlify([...Array(count)].map(() => byte));
}

function fillUint(bits: number, byte: number): BigNumber {
return BigNumber.from(fillBytes(bits / 8, byte));
}

describe("GPv2Trade", () => {
const [, ...traders] = waffle.provider.getWallets();

const testDomain = { name: "test" };
const sampleOrder = {
sellToken: fillBytes(20, 0x01),
buyToken: fillBytes(20, 0x02),
receiver: fillBytes(20, 0x03),
sellAmount: ethers.utils.parseEther("42"),
buyAmount: ethers.utils.parseEther("13.37"),
validTo: 0xffffffff,
appData: ethers.constants.HashZero,
feeAmount: ethers.utils.parseEther("1.0"),
kind: OrderKind.SELL,
partiallyFillable: false,
};

let tradeLib: Contract;

beforeEach(async () => {
Expand All @@ -51,75 +25,6 @@ describe("GPv2Trade", () => {
tradeLib = await GPv2Trade.deploy();
});

describe("extractOrder", () => {
it("should round-trip encode order data", async () => {
// NOTE: Pay extra attention to use all bytes for each field, and that
// they all have different values to make sure the are correctly
// round-tripped.
const order = {
sellToken: fillBytes(20, 0x01),
buyToken: fillBytes(20, 0x02),
receiver: fillBytes(20, 0x03),
sellAmount: fillUint(256, 0x04),
buyAmount: fillUint(256, 0x05),
validTo: fillUint(32, 0x06).toNumber(),
appData: fillBytes(32, 0x07),
feeAmount: fillUint(256, 0x08),
kind: OrderKind.BUY,
partiallyFillable: true,
sellTokenBalance: OrderBalance.EXTERNAL,
buyTokenBalance: OrderBalance.INTERNAL,
};
const tradeExecution = {
executedAmount: fillUint(256, 0x09),
};

const encoder = new SettlementEncoder(testDomain);
await encoder.signEncodeTrade(
order,
traders[0],
SigningScheme.EIP712,
tradeExecution,
);

const encodedOrder = await tradeLib.extractOrderTest(
encoder.tokens,
encoder.trades[0],
);
expect(decodeOrder(encodedOrder)).to.deep.equal(order);
});

it("should revert for invalid sell token indices", async () => {
const encoder = new SettlementEncoder(testDomain);
await encoder.signEncodeTrade(
sampleOrder,
traders[1],
SigningScheme.EIP712,
);

const tokens = encoder.tokens.filter(
(token) => token !== sampleOrder.sellToken,
);
await expect(tradeLib.extractOrderTest(tokens, encoder.trades)).to.be
.reverted;
});

it("should revert for invalid buy token indices", async () => {
const encoder = new SettlementEncoder(testDomain);
await encoder.signEncodeTrade(
sampleOrder,
traders[1],
SigningScheme.EIP712,
);

const tokens = encoder.tokens.filter(
(token) => token !== sampleOrder.buyToken,
);
await expect(tradeLib.extractOrderTest(tokens, encoder.trades)).to.be
.reverted;
});
});

describe("extractFlags", () => {
it("should extract all supported order flags", async () => {
const flagVariants = [OrderKind.SELL, OrderKind.BUY].flatMap((kind) =>
Expand Down
115 changes: 115 additions & 0 deletions test/GPv2Trade/ExtractOrder.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
pragma solidity ^0.8;

import {GPv2Order, IERC20} from "src/contracts/libraries/GPv2Trade.sol";
import {GPv2Signing} from "src/contracts/mixins/GPv2Signing.sol";

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

import {Order as OrderLib} from "test/libraries/Order.sol";
import {SettlementEncoder} from "test/libraries/encoders/SettlementEncoder.sol";

contract ExtractOrder is Helper {
using SettlementEncoder for SettlementEncoder.State;

struct Fuzzed {
address sellToken;
address buyToken;
address receiver;
uint256 sellAmount;
uint256 buyAmount;
uint32 validTo;
bytes32 appData;
uint256 feeAmount;
uint256 executedAmount;
}

function sampleOrder() private returns (GPv2Order.Data memory order) {
order = GPv2Order.Data({
sellToken: IERC20(makeAddr("GPv2Trade.ExtractOrder sampleOrder sell token")),
buyToken: IERC20(makeAddr("GPv2Trade.ExtractOrder sampleOrder buy token")),
receiver: makeAddr("GPv2Trade.ExtractOrder sampleOrder receiver"),
sellAmount: 42 ether,
buyAmount: 13.37 ether,
validTo: type(uint32).max,
appData: bytes32(0),
feeAmount: 1 ether,
kind: GPv2Order.KIND_SELL,
partiallyFillable: false,
sellTokenBalance: GPv2Order.BALANCE_ERC20,
buyTokenBalance: GPv2Order.BALANCE_ERC20
});
}

function assertSameOrder(GPv2Order.Data memory lhs, GPv2Order.Data memory rhs) private pure {
uint256 assertionCount = 12;
assertEq(address(lhs.sellToken), address(rhs.sellToken));
assertEq(address(lhs.buyToken), address(rhs.buyToken));
assertEq(lhs.receiver, rhs.receiver);
assertEq(lhs.sellAmount, rhs.sellAmount);
assertEq(lhs.buyAmount, rhs.buyAmount);
assertEq(lhs.validTo, rhs.validTo);
assertEq(lhs.appData, rhs.appData);
assertEq(lhs.feeAmount, rhs.feeAmount);
assertEq(lhs.kind, rhs.kind);
assertEq(lhs.partiallyFillable, rhs.partiallyFillable);
assertEq(lhs.sellTokenBalance, rhs.sellTokenBalance);
assertEq(lhs.buyTokenBalance, rhs.buyTokenBalance);
// We want to make sure that if we add new fields to `GPv2Order.Data` we
// don't forget to add the corresponding assertion.
// This only works because all fields are _not_ of dynamic length.
require(abi.encode(lhs).length == assertionCount * 32, "bad test setup when comparing two orders");
}

function testFuzz_should_round_trip_encode_order_data(Fuzzed memory fuzzed) public {
OrderLib.Flags[] memory flags = OrderLib.ALL_FLAGS();

for (uint256 i = 0; i < flags.length; i++) {
GPv2Order.Data memory order = GPv2Order.Data({
sellToken: IERC20(fuzzed.sellToken),
buyToken: IERC20(fuzzed.buyToken),
receiver: fuzzed.receiver,
sellAmount: fuzzed.sellAmount,
buyAmount: fuzzed.buyAmount,
validTo: fuzzed.validTo,
appData: fuzzed.appData,
feeAmount: fuzzed.feeAmount,
kind: flags[i].kind,
partiallyFillable: flags[i].partiallyFillable,
sellTokenBalance: flags[i].sellTokenBalance,
buyTokenBalance: flags[i].buyTokenBalance
});

SettlementEncoder.State storage encoder = SettlementEncoder.makeSettlementEncoder();
encoder.signEncodeTrade(
vm, trader, order, domainSeparator, GPv2Signing.Scheme.Eip712, fuzzed.executedAmount
);
GPv2Order.Data memory extractedOrder = executor.extractOrderTest(encoder.tokens(), encoder.trades[0]);
assertSameOrder(order, extractedOrder);
}
}

function should_revert_for_invalid_token_indices(GPv2Order.Data memory order, IERC20[] memory tokens) internal {
SettlementEncoder.State storage encoder = SettlementEncoder.makeSettlementEncoder();
encoder.signEncodeTrade(vm, trader, order, domainSeparator, GPv2Signing.Scheme.Eip712, 0);
// TODO: once Foundry supports catching EVM errors, require that this
// reverts with "array out-of-bounds access".
// Track support at https://github.com/foundry-rs/foundry/issues/4012
vm.expectRevert();
executor.extractOrderTest(tokens, encoder.trades[0]);
}

function test_should_revert_for_invalid_sell_token_indices() public {
GPv2Order.Data memory order = sampleOrder();
IERC20[] memory tokens = new IERC20[](1);
tokens[0] = order.buyToken;
should_revert_for_invalid_token_indices(order, tokens);
}

function test_should_revert_for_invalid_buy_token_indices() public {
GPv2Order.Data memory order = sampleOrder();
IERC20[] memory tokens = new IERC20[](1);
tokens[0] = order.sellToken;
should_revert_for_invalid_token_indices(order, tokens);
}
}
23 changes: 23 additions & 0 deletions test/GPv2Trade/Helper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
pragma solidity ^0.8;

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

import {SettlementEncoder} from "test/libraries/encoders/SettlementEncoder.sol";
import {GPv2TradeTestInterface} from "test/src/GPv2TradeTestInterface.sol";

// TODO: move the content of `GPv2TradeTestInterface` here once all tests have been removed.
// solhint-disable-next-line no-empty-blocks
contract Harness is GPv2TradeTestInterface {}

contract Helper is Test {
Harness internal executor;
Vm.Wallet internal trader = vm.createWallet("GPv2Trade.Helper trader");
bytes32 internal domainSeparator = keccak256("GPv2Trade.Helper domain separator");
SettlementEncoder.State internal encoder;

function setUp() public {
executor = new Harness();
encoder = SettlementEncoder.makeSettlementEncoder();
}
}

0 comments on commit 0c08be9

Please sign in to comment.