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 set pre-signature tests to Foundry #203

Merged
merged 11 commits into from
Aug 15, 2024
5 changes: 0 additions & 5 deletions src/ts/sign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,6 @@ export const EIP1271_MAGICVALUE = ethers.utils.hexDataSlice(
4,
);

/**
* Marker value indicating a presignature is set.
*/
export const PRE_SIGNED = ethers.utils.id("GPv2Signing.Scheme.PreSign");

/**
* The signing scheme used to sign the order.
*/
Expand Down
66 changes: 0 additions & 66 deletions test/GPv2Signing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@ import {
EIP1271_MAGICVALUE,
OrderBalance,
OrderKind,
PRE_SIGNED,
SettlementEncoder,
SigningScheme,
TypedDataDomain,
computeOrderUid,
domain,
encodeEip1271SignatureData,
hashOrder,
packOrderUidParams,
signOrder,
} from "../src/ts";

Expand All @@ -38,70 +36,6 @@ describe("GPv2Signing", () => {
testDomain = domain(chainId, signing.address);
});

describe("domainSeparator", () => {
it("should have an EIP-712 domain separator", async () => {
expect(await signing.domainSeparator()).to.equal(
ethers.utils._TypedDataEncoder.hashDomain(testDomain),
);
});

it("should have a different replay protection for each deployment", async () => {
const GPv2Signing = await ethers.getContractFactory(
"GPv2SigningTestInterface",
);
const signing2 = await GPv2Signing.deploy();

expect(await signing.domainSeparator()).to.not.equal(
await signing2.domainSeparator(),
);
});
});

describe("setPreSignature", () => {
const [owner, nonOwner] = traders;
const orderUid = packOrderUidParams({
orderDigest: ethers.constants.HashZero,
owner: owner.address,
validTo: 0xffffffff,
});

it("should set the pre-signature", async () => {
await signing.connect(owner).setPreSignature(orderUid, true);
expect(await signing.preSignature(orderUid)).to.equal(PRE_SIGNED);
});

it("should unset the pre-signature", async () => {
await signing.connect(owner).setPreSignature(orderUid, true);
await signing.connect(owner).setPreSignature(orderUid, false);
expect(await signing.preSignature(orderUid)).to.equal(
ethers.constants.Zero,
);
});

it("should emit a PreSignature event", async () => {
await expect(signing.connect(owner).setPreSignature(orderUid, true))
.to.emit(signing, "PreSignature")
.withArgs(owner.address, orderUid, true);

await expect(signing.connect(owner).setPreSignature(orderUid, false))
.to.emit(signing, "PreSignature")
.withArgs(owner.address, orderUid, false);
});

it("should emit a PreSignature event even if storage doesn't change", async () => {
await signing.connect(owner).setPreSignature(orderUid, true);
await expect(signing.connect(owner).setPreSignature(orderUid, true))
.to.emit(signing, "PreSignature")
.withArgs(owner.address, orderUid, true);
});

it("should revert if the order owner is not the transaction sender", async () => {
await expect(
signing.connect(nonOwner).setPreSignature(orderUid, true),
).to.be.revertedWith("cannot presign order");
});
});

describe("recoverOrderFromTrade", () => {
it("should round-trip encode order data", async () => {
// NOTE: Pay extra attention to use all bytes for each field, and that
Expand Down
24 changes: 24 additions & 0 deletions test/GPv2Signing/DomainSeparator.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
pragma solidity ^0.8;

import {Harness, Helper} from "./Helper.sol";
import {Eip712} from "test/libraries/Eip712.sol";

contract DomainSeparator is Helper {
function test_TYPE_HASH_matches_the_EIP_712_order_type_hash() public view {
bytes32 expectedDomainSeparator = Eip712.hashStruct(
Eip712.EIP712Domain({
name: "Gnosis Protocol",
version: "v2",
chainId: block.chainid,
verifyingContract: address(executor)
})
);
assertEq(executor.domainSeparator(), expectedDomainSeparator);
}

function test_should_have_a_different_replay_protection_for_each_deployment() public {
Harness signing = new Harness();
assertNotEq(executor.domainSeparator(), signing.domainSeparator());
}
}
33 changes: 11 additions & 22 deletions test/GPv2Signing/Helper.sol
Original file line number Diff line number Diff line change
@@ -1,30 +1,19 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
pragma solidity ^0.8;

import {IERC20} from "src/contracts/interfaces/IERC20.sol";
import {GPv2Order} from "src/contracts/libraries/GPv2Order.sol";
import {GPv2Trade} from "src/contracts/libraries/GPv2Trade.sol";
import {GPv2Signing} from "src/contracts/mixins/GPv2Signing.sol";
import {Test} from "forge-std/Test.sol";

// solhint-disable func-name-mixedcase
contract Harness is GPv2Signing {
constructor(bytes32 _domainSeparator) {
domainSeparator = _domainSeparator;
}
import {GPv2SigningTestInterface} from "test/src/GPv2SigningTestInterface.sol";

function exposed_recoverOrderFromTrade(
RecoveredOrder memory recoveredOrder,
IERC20[] calldata tokens,
GPv2Trade.Data calldata trade
) external view {
recoverOrderFromTrade(recoveredOrder, tokens, trade);
}
// TODO: move the content of `GPv2SigningTestInterface` here once all tests have
// been removed.
// solhint-disable-next-line no-empty-blocks
contract Harness is GPv2SigningTestInterface {}

contract Helper is Test {
Harness internal executor;

function exposed_recoverOrderSigner(GPv2Order.Data memory order, Scheme signingScheme, bytes calldata signature)
external
view
returns (bytes32 orderDigest, address owner)
{
(orderDigest, owner) = recoverOrderSigner(order, signingScheme, signature);
function setUp() public {
executor = new Harness();
}
}
56 changes: 56 additions & 0 deletions test/GPv2Signing/SetPreSignature.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
pragma solidity ^0.8;

import {GPv2Signing} from "src/contracts/mixins/GPv2Signing.sol";

import {Helper} from "./Helper.sol";
import {Order} from "test/libraries/Order.sol";

contract SetPreSignature is Helper {
address private immutable owner = makeAddr("GPv2Signing.SetPreSignature owner");
bytes private orderUid =
Order.computeOrderUid(keccak256("GPv2Signing.SetPreSignature order hash"), owner, type(uint32).max);

// Same constant as in GPv2Signing.PRE_SIGNED.
uint256 private constant PRE_SIGNED = uint256(keccak256("GPv2Signing.Scheme.PreSign"));
fedgiac marked this conversation as resolved.
Show resolved Hide resolved
uint256 private constant NOT_SIGNED = 0;

function test_should_set_the_pre_signature() public {
vm.prank(owner);
executor.setPreSignature(orderUid, true);
assertEq(executor.preSignature(orderUid), PRE_SIGNED);
}

function test_should_unset_the_pre_signature() public {
vm.prank(owner);
executor.setPreSignature(orderUid, true);
vm.prank(owner);
executor.setPreSignature(orderUid, false);
assertEq(executor.preSignature(orderUid), NOT_SIGNED);
}

function test_should_emit_a_pre_signature_event() public {
vm.prank(owner);
vm.expectEmit(address(executor));
emit GPv2Signing.PreSignature(owner, orderUid, true);
executor.setPreSignature(orderUid, true);

vm.prank(owner);
vm.expectEmit(address(executor));
emit GPv2Signing.PreSignature(owner, orderUid, false);
executor.setPreSignature(orderUid, false);
}

function test_should_emit_a_PreSignature_event_even_if_storage_does_not_change() public {
emit GPv2Signing.PreSignature(owner, orderUid, true);
fedgiac marked this conversation as resolved.
Show resolved Hide resolved
vm.prank(owner);
vm.expectEmit(address(executor));
emit GPv2Signing.PreSignature(owner, orderUid, true);
executor.setPreSignature(orderUid, true);
}

function test_reverts_if_the_order_owner_is_not_the_transaction_sender() public {
vm.expectRevert("GPv2: cannot presign order");
executor.setPreSignature(orderUid, true);
}
}
42 changes: 42 additions & 0 deletions test/libraries/Eip712.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ pragma solidity ^0.8;
import {GPv2Order} from "src/contracts/libraries/GPv2Order.sol";

library Eip712 {
struct EIP712Domain {
string name;
string version;
uint256 chainId;
address verifyingContract;
}

// This is the struct representing an order that is signed by the user using
// EIP-712.
struct Order {
Expand All @@ -21,6 +28,25 @@ library Eip712 {
string buyTokenBalance;
}

// Ideally, this would be replaced by type(EIP712Domain).typehash.
// Progress tracking for this Solidity feature is here:
// https://github.com/ethereum/solidity/issues/14157
function EIP712DOMAIN_TYPE_HASH() internal pure returns (bytes32) {
return keccak256(
bytes(
string.concat(
// Should reflect the definition of the struct `EIP712Domain`.
"EIP712Domain(",
"string name,",
"string version,",
"uint256 chainId,",
"address verifyingContract",
")"
)
)
);
}

// Ideally, this would be replaced by type(Order).typehash.
// Progress tracking for this Solidity feature is here:
// https://github.com/ethereum/solidity/issues/14157
Expand Down Expand Up @@ -96,6 +122,22 @@ library Eip712 {
});
}

function hashStruct(EIP712Domain memory domain) internal pure returns (bytes32) {
// Ideally, this would be replaced by `domain.hashStruct()`.
// Progress tracking for this Solidity feature is here:
// https://github.com/ethereum/solidity/issues/14208
return keccak256(
// Note: dynamic types are hashed.
abi.encode(
EIP712DOMAIN_TYPE_HASH(),
keccak256(bytes(domain.name)),
keccak256(bytes(domain.version)),
domain.chainId,
domain.verifyingContract
)
);
}

function hashStruct(Order memory order) internal pure returns (bytes32) {
// Ideally, this would be replaced by `order.hashStruct()`.
// Progress tracking for this Solidity feature is here:
Expand Down
Loading