diff --git a/test/GPv2Signing.test.ts b/test/GPv2Signing.test.ts index 87de48f8..b73d22a2 100644 --- a/test/GPv2Signing.test.ts +++ b/test/GPv2Signing.test.ts @@ -4,8 +4,6 @@ import { artifacts, ethers, waffle } from "hardhat"; import { EIP1271_MAGICVALUE, - OrderBalance, - OrderKind, SettlementEncoder, SigningScheme, TypedDataDomain, @@ -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(); @@ -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 diff --git a/test/GPv2Signing/Helper.sol b/test/GPv2Signing/Helper.sol index 98e07eac..d89c606e 100644 --- a/test/GPv2Signing/Helper.sol +++ b/test/GPv2Signing/Helper.sol @@ -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(); } } diff --git a/test/GPv2Signing/RecoverOrderFromTrade.t.sol b/test/GPv2Signing/RecoverOrderFromTrade.t.sol new file mode 100644 index 00000000..42cabf14 --- /dev/null +++ b/test/GPv2Signing/RecoverOrderFromTrade.t.sol @@ -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); + } +} diff --git a/test/libraries/Order.sol b/test/libraries/Order.sol index 1eedf725..393735f8 100644 --- a/test/libraries/Order.sol +++ b/test/libraries/Order.sol @@ -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 @@ -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 + }); + } }