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

test: permit2 flows #67

Merged
merged 10 commits into from
Jul 19, 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
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