Skip to content

Commit

Permalink
Merge pull request #67 from init4tech/anna/test-permit2
Browse files Browse the repository at this point in the history
test: permit2 flows
  • Loading branch information
prestwich authored Jul 19, 2024
2 parents 965266d + beca65a commit 9ce2d34
Show file tree
Hide file tree
Showing 4 changed files with 561 additions and 1 deletion.
7 changes: 7 additions & 0 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
OrderOriginPermit2Test:test_fillPermit2() (gas: 225289)
OrderOriginPermit2Test:test_fillPermit2_multi() (gas: 1019134)
OrderOriginPermit2Test:test_initiatePermit2() (gas: 235752)
OrderOriginPermit2Test:test_initiatePermit2_multi() (gas: 989274)
OrdersTest:test_fill_ERC20() (gas: 70537)
OrdersTest:test_fill_ETH() (gas: 68498)
OrdersTest:test_fill_both() (gas: 166773)
Expand All @@ -12,6 +16,8 @@ OrdersTest:test_orderExpired() (gas: 28106)
OrdersTest:test_sweepERC20() (gas: 60491)
OrdersTest:test_sweepETH() (gas: 82186)
OrdersTest:test_underflowETH() (gas: 63690)
PassagePermit2Test:test_disallowedEnterPermit2() (gas: 699630)
PassagePermit2Test:test_enterTokenPermit2() (gas: 145449)
PassageTest:test_configureEnter() (gas: 125771)
PassageTest:test_disallowedEnter() (gas: 56619)
PassageTest:test_enter() (gas: 25519)
Expand All @@ -23,6 +29,7 @@ PassageTest:test_onlyTokenAdmin() (gas: 16881)
PassageTest:test_receive() (gas: 21383)
PassageTest:test_setUp() (gas: 17011)
PassageTest:test_withdraw() (gas: 59188)
RollupPassagePermit2Test:test_exitTokenPermit2() (gas: 129402)
RollupPassageTest:test_exit() (gas: 22403)
RollupPassageTest:test_exitToken() (gas: 50232)
RollupPassageTest:test_fallback() (gas: 19949)
Expand Down
175 changes: 174 additions & 1 deletion test/Helpers.t.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import {Test, console2} from "forge-std/Test.sol";
import {Zenith} from "../src/Zenith.sol";
import {UsesPermit2} from "../src/permit2/UsesPermit2.sol";

import {Test, console2} from "forge-std/Test.sol";
import {ERC20} from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
import {ERC20Burnable} from "openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import {ISignatureTransfer} from "permit2/src/interfaces/ISignatureTransfer.sol";

contract TestERC20 is ERC20Burnable {
constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) {}
Expand All @@ -14,6 +17,176 @@ contract TestERC20 is ERC20Burnable {
}
}

contract Permit2Helpers is Test {
address permit2Contract;

/// @notice the address signing the Permit messages and its pk
uint256 ownerKey = 123;
address owner = vm.addr(ownerKey);

// permit consts
UsesPermit2.Witness witness;

bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
uint256 private immutable _CACHED_CHAIN_ID;

bytes32 private constant _HASHED_NAME = keccak256("Permit2");
bytes32 private constant _TYPE_HASH =
keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");

string public constant _PERMIT_TRANSFER_FROM_WITNESS_TYPEHASH_STUB =
"PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,";

bytes32 public constant _TOKEN_PERMISSIONS_TYPEHASH = keccak256("TokenPermissions(address token,uint256 amount)");

string public constant _PERMIT_BATCH_WITNESS_TRANSFER_FROM_TYPEHASH_STUB =
"PermitBatchWitnessTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline,";

// Cache the domain separator as an immutable value, but also store the chain id that it
// corresponds to, in order to invalidate the cached domain separator if the chain id changes.
constructor() {
_CACHED_CHAIN_ID = block.chainid;
_CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME);
}

function _setUpPermit2(address token, uint256 amount) internal {
vm.label(owner, "owner");

// setup permit2 contract
permit2Contract = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
vm.label(address(permit2Contract), "permit2");

// approve permit2
vm.prank(owner);
TestERC20(token).approve(address(permit2Contract), amount * 10000);
}

/// @notice given a Permit and a Witness, produce a signature from the `owner`
function signPermit(
uint256 signingKey,
address spender,
ISignatureTransfer.PermitTransferFrom memory permit,
UsesPermit2.Witness memory _witness
) internal view returns (bytes memory signature) {
bytes32 permit2Hash = hashWithWitness(spender, permit, _witness.witnessHash, _witness.witnessTypeString);
bytes32 signHash = _hashTypedData(permit2Hash);
uint8 v;
bytes32 r;
bytes32 s;
(v, r, s) = vm.sign(signingKey, signHash);
signature = abi.encodePacked(r, s, v);
}

// this function is private on permit2 contracts but need to port it here for test functionality
function hashWithWitness(
address spender,
ISignatureTransfer.PermitTransferFrom memory _permit,
bytes32 _witness,
string memory witnessTypeString
) internal pure returns (bytes32) {
bytes32 typeHash = keccak256(abi.encodePacked(_PERMIT_TRANSFER_FROM_WITNESS_TYPEHASH_STUB, witnessTypeString));

bytes32 tokenPermissionsHash = _hashTokenPermissions(_permit.permitted);
return keccak256(abi.encode(typeHash, tokenPermissionsHash, spender, _permit.nonce, _permit.deadline, _witness));
}

/// @notice given a Permit and a Witness, produce a signature from the `owner`
function signPermit(
uint256 signingKey,
address spender,
ISignatureTransfer.PermitBatchTransferFrom memory permit,
UsesPermit2.Witness memory _witness
) internal view returns (bytes memory signature) {
bytes32 permit2Hash = hashWithWitness(spender, permit, _witness.witnessHash, _witness.witnessTypeString);
bytes32 signHash = _hashTypedData(permit2Hash);
uint8 v;
bytes32 r;
bytes32 s;
(v, r, s) = vm.sign(signingKey, signHash);
signature = abi.encodePacked(r, s, v);
}

function hashWithWitness(
address spender,
ISignatureTransfer.PermitBatchTransferFrom memory permit,
bytes32 _witness,
string memory witnessTypeString
) internal pure returns (bytes32) {
bytes32 typeHash =
keccak256(abi.encodePacked(_PERMIT_BATCH_WITNESS_TRANSFER_FROM_TYPEHASH_STUB, witnessTypeString));

uint256 numPermitted = permit.permitted.length;
bytes32[] memory tokenPermissionHashes = new bytes32[](numPermitted);

for (uint256 i = 0; i < numPermitted; ++i) {
tokenPermissionHashes[i] = _hashTokenPermissions(permit.permitted[i]);
}

return keccak256(
abi.encode(
typeHash,
keccak256(abi.encodePacked(tokenPermissionHashes)),
spender,
permit.nonce,
permit.deadline,
_witness
)
);
}

// this function is private on permit2 contracts but need to port it here for test functionality
function _hashTokenPermissions(ISignatureTransfer.TokenPermissions memory _permitted)
private
pure
returns (bytes32)
{
return keccak256(abi.encode(_TOKEN_PERMISSIONS_TYPEHASH, _permitted));
}

/// @notice Returns the domain separator for the current chain.
/// @dev Uses cached version if chainid and address are unchanged from construction.
function DOMAIN_SEPARATOR() public view returns (bytes32) {
return block.chainid == _CACHED_CHAIN_ID
? _CACHED_DOMAIN_SEPARATOR
: _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME);
}

/// @notice Builds a domain separator using the current chainId and contract address.
function _buildDomainSeparator(bytes32 typeHash, bytes32 nameHash) private view returns (bytes32) {
return keccak256(abi.encode(typeHash, nameHash, block.chainid, permit2Contract));
}

/// @notice Creates an EIP-712 typed data hash
function _hashTypedData(bytes32 dataHash) internal view returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), dataHash));
}
}

/// HACK to use abi.encodeWithSelector
interface ISinglePermit {
/// @notice stubbed `permitWitnessTransferFrom` - does not check signature, nonce, or deadline
function permitWitnessTransferFrom(
ISignatureTransfer.PermitTransferFrom memory permit,
ISignatureTransfer.SignatureTransferDetails calldata transferDetails,
address owner,
bytes32, /*witness*/
string calldata, /*witnessTypeString*/
bytes calldata /*signature*/
) external;
}

/// HACK to use abi.encodeWithSelector
interface IBatchPermit {
function permitWitnessTransferFrom(
ISignatureTransfer.PermitBatchTransferFrom memory permit,
ISignatureTransfer.SignatureTransferDetails[] calldata transferDetails,
address owner,
bytes32, /*witness*/
string calldata, /*witnessTypeString*/
bytes calldata /*signature*/
) external;
}

contract HelpersTest is Test {
Zenith public target;

Expand Down
Loading

0 comments on commit 9ce2d34

Please sign in to comment.