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 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
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();
}
}
73 changes: 73 additions & 0 deletions test/GPv2Signing/RecoverOrderFromTrade.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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 {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(
Order.Fuzzed memory params,
uint256 executedAmount
) public {
GPv2Order.Data memory order = Order.fuzz(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(Order.Fuzzed memory params) public {
GPv2Order.Data memory order = Order.fuzz(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);
}
}
34 changes: 34 additions & 0 deletions test/libraries/Order.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ library Order {
bool partiallyFillable;
}

/// All parameters needed to generated a valid fuzzed order.
struct Fuzzed {
address sellToken;
address buyToken;
address receiver;
uint256 sellAmount;
uint256 buyAmount;
uint32 validTo;
bytes32 appData;
uint256 feeAmount;
bytes32 flagsPick;
}

// I wish I could declare the following as constants and export them as part
// of the library. However, "Only constants of value type and byte array
// type are implemented." and "Library cannot have non-constant state
Expand Down Expand Up @@ -145,4 +158,25 @@ library Order {
orderUid = new bytes(GPv2Order.UID_LENGTH);
orderUid.packOrderUidParams(orderHash, owner, validTo);
}

function fuzz(Fuzzed 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