Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: migrate GPv2Trade extractOrder tests to Foundry #199

Merged
merged 2 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
99 changes: 99 additions & 0 deletions test/GPv2Trade/ExtractOrder.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// 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 {
assertEq(abi.encode(lhs), abi.encode(rhs));
}

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();
}
}
Loading