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 GPv2Signing recoverOrderFromTrade tests to Foundry #204

Merged
merged 16 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from 15 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
111 changes: 2 additions & 109 deletions test/GPv2Signing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { artifacts, ethers, waffle } from "hardhat";

import {
EIP1271_MAGICVALUE,
OrderBalance,
OrderKind,
SettlementEncoder,
SigningScheme,
TypedDataDomain,
Expand All @@ -16,8 +14,8 @@ import {
signOrder,
} from "../src/ts";

import { decodeOrder, encodeOrder } from "./encoding";
import { fillBytes, fillUint, SAMPLE_ORDER } from "./testHelpers";
import { encodeOrder } from "./encoding";
import { SAMPLE_ORDER } from "./testHelpers";

describe("GPv2Signing", () => {
const [deployer, ...traders] = waffle.provider.getWallets();
Expand All @@ -37,111 +35,6 @@ describe("GPv2Signing", () => {
});

describe("recoverOrderFromTrade", () => {
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 { data: encodedOrder } = await signing.recoverOrderFromTradeTest(
encoder.tokens,
encoder.trades[0],
);
expect(decodeOrder(encodedOrder)).to.deep.equal(order);
});

it("should compute order unique identifier", async () => {
const encoder = new SettlementEncoder(testDomain);
await encoder.signEncodeTrade(
SAMPLE_ORDER,
traders[0],
SigningScheme.EIP712,
);

const { uid: orderUid } = await signing.recoverOrderFromTradeTest(
encoder.tokens,
encoder.trades[0],
);
expect(orderUid).to.equal(
computeOrderUid(testDomain, SAMPLE_ORDER, traders[0].address),
);
});

it("should recover the owner for all signing schemes", async () => {
const artifact = await artifacts.readArtifact("EIP1271Verifier");
const verifier = await waffle.deployMockContract(deployer, artifact.abi);
await verifier.mock.isValidSignature.returns(EIP1271_MAGICVALUE);

const sampleOrderUid = computeOrderUid(
testDomain,
SAMPLE_ORDER,
traders[2].address,
);
await signing.connect(traders[2]).setPreSignature(sampleOrderUid, true);

const encoder = new SettlementEncoder(testDomain);
await encoder.signEncodeTrade(
SAMPLE_ORDER,
traders[0],
SigningScheme.EIP712,
);
await encoder.signEncodeTrade(
SAMPLE_ORDER,
traders[1],
SigningScheme.ETHSIGN,
);
encoder.encodeTrade(SAMPLE_ORDER, {
scheme: SigningScheme.EIP1271,
data: {
verifier: verifier.address,
signature: "0x",
},
});
encoder.encodeTrade(SAMPLE_ORDER, {
scheme: SigningScheme.PRESIGN,
data: traders[2].address,
});

const owners = [
traders[0].address,
traders[1].address,
verifier.address,
traders[2].address,
];

for (const [i, trade] of encoder.trades.entries()) {
const { owner } = await signing.recoverOrderFromTradeTest(
encoder.tokens,
trade,
);
expect(owner).to.equal(owners[i]);
}
});

describe("uid uniqueness", () => {
it("invalid EVM transaction encoding does not change order hash", async () => {
// The variables for an EVM transaction are encoded in multiples of 32
Expand Down
2 changes: 2 additions & 0 deletions test/GPv2Signing/Helper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ contract Harness is GPv2SigningTestInterface {}

contract Helper is Test {
Harness internal executor;
bytes32 internal domainSeparator;

function setUp() public {
executor = new Harness();
domainSeparator = executor.domainSeparator();
}
}
74 changes: 74 additions & 0 deletions test/GPv2Signing/RecoverOrderFromTrade.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
pragma solidity ^0.8;

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

import {EIP1271Verifier, GPv2EIP1271, GPv2Order, GPv2Signing} from "src/contracts/mixins/GPv2Signing.sol";

import {Helper} from "./Helper.sol";
import {Order} from "test/libraries/Order.sol";
import {OrderFuzz} from "test/libraries/OrderFuzz.sol";
import {Sign} from "test/libraries/Sign.sol";
import {SettlementEncoder} from "test/libraries/encoders/SettlementEncoder.sol";

contract RecoverOrderFromTrade is Helper {
using SettlementEncoder for SettlementEncoder.State;
using Sign for EIP1271Verifier;

Vm.Wallet private trader;

constructor() {
trader = vm.createWallet("GPv2Signing.RecoverOrderFromTrade: trader");
}

function test_should_round_trip_encode_order_data_and_unique_identifier(
OrderFuzz.Params memory params,
uint256 executedAmount
) public {
GPv2Order.Data memory order = OrderFuzz.order(params);

SettlementEncoder.State storage encoder = SettlementEncoder.makeSettlementEncoder();
encoder.signEncodeTrade(vm, trader, order, domainSeparator, GPv2Signing.Scheme.Eip712, executedAmount);

GPv2Signing.RecoveredOrder memory recovered =
executor.recoverOrderFromTradeTest(encoder.tokens(), encoder.trades[0]);
assertEq(abi.encode(recovered.data), abi.encode(order));
assertEq(recovered.uid, Order.computeOrderUid(order, domainSeparator, trader.addr));
}

function test_should_recover_the_order_for_all_signing_schemes(OrderFuzz.Params memory params) public {
GPv2Order.Data memory order = OrderFuzz.order(params);

address traderPreSign = makeAddr("trader pre-sign");
EIP1271Verifier traderEip1271 = EIP1271Verifier(makeAddr("eip1271 verifier"));
Vm.Wallet memory traderEip712 = vm.createWallet("trader eip712");
Vm.Wallet memory traderEthsign = vm.createWallet("trader ethsign");

bytes memory uidPreSign = Order.computeOrderUid(order, domainSeparator, traderPreSign);
vm.prank(traderPreSign);
executor.setPreSignature(uidPreSign, true);

vm.mockCallRevert(address(traderEip1271), hex"", "unexpected call to mock contract");
vm.mockCall(
address(traderEip1271),
abi.encodePacked(EIP1271Verifier.isValidSignature.selector),
abi.encode(GPv2EIP1271.MAGICVALUE)
);

SettlementEncoder.State storage encoder = SettlementEncoder.makeSettlementEncoder();
encoder.encodeTrade(order, Sign.preSign(traderPreSign), 0);
encoder.encodeTrade(order, Sign.sign(traderEip1271, hex""), 0);
encoder.signEncodeTrade(vm, traderEip712, order, domainSeparator, GPv2Signing.Scheme.Eip712, 0);
encoder.signEncodeTrade(vm, traderEthsign, order, domainSeparator, GPv2Signing.Scheme.EthSign, 0);

GPv2Signing.RecoveredOrder memory recovered;
recovered = executor.recoverOrderFromTradeTest(encoder.tokens(), encoder.trades[0]);
assertEq(recovered.owner, traderPreSign);
recovered = executor.recoverOrderFromTradeTest(encoder.tokens(), encoder.trades[1]);
assertEq(recovered.owner, address(traderEip1271));
recovered = executor.recoverOrderFromTradeTest(encoder.tokens(), encoder.trades[2]);
assertEq(recovered.owner, traderEip712.addr);
recovered = executor.recoverOrderFromTradeTest(encoder.tokens(), encoder.trades[3]);
assertEq(recovered.owner, traderEthsign.addr);
}
}
46 changes: 46 additions & 0 deletions test/libraries/OrderFuzz.sol
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a specific reason why this is not part of the Order test library? If not, I'd be a fan of including this in the Order test library - thinking of the event that downstream consumers want to fuzz orders as well (such as some kind of testing for programmatic orders) - having this all in one library to retrieve would be helpful.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It used to be that the library was an abstract contract to be inherited by the test (I was experimenting with Foundry's fixtures) but I came to the conclusion it was better to keep it a simple library. Agreed that merging into Order makes sense.

Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
pragma solidity ^0.8;

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

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

library OrderFuzz {
using GPv2Order for GPv2Order.Data;
using GPv2Order for bytes;
using GPv2Trade for uint256;

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

function order(Params memory params) internal pure returns (GPv2Order.Data memory) {
Order.Flags[] memory allFlags = Order.ALL_FLAGS();
// `flags` isn't exactly random, but for fuzzing purposes it should be
// more than enough.
Order.Flags memory flags = allFlags[uint256(params.flagsPick) % allFlags.length];
return GPv2Order.Data({
sellToken: IERC20(params.sellToken),
buyToken: IERC20(params.buyToken),
receiver: params.receiver,
sellAmount: params.sellAmount,
buyAmount: params.buyAmount,
validTo: params.validTo,
appData: params.appData,
feeAmount: params.feeAmount,
partiallyFillable: flags.partiallyFillable,
kind: flags.kind,
sellTokenBalance: flags.sellTokenBalance,
buyTokenBalance: flags.buyTokenBalance
});
}
}
Loading