Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

fomoweth/permit-utils

Repository files navigation

Permit Utils

Utility libraries and helpers for working with EIP-2612 and Permit2 signatures in Solidity and Foundry tests.

This repository provides building blocks for constructing permit structs, computing their EIP-712 hashes, and generating signatures for testing and integration with protocols that use ERC20 permits.


Overview

permit-utils
├── src
│ ├── EIP2612Permit.sol
│ ├── PermitSignatures.sol
│ ├── PermitUtils.sol
│ ├── StructBuilder.sol
│ └── TypeHashes.sol
└── test
 └── ...

EIP2612Permit

Defines the EIP2612Permit struct to represent EIP-2612 compliant permit parameters:

  • owner
  • spender
  • value
  • nonce
  • deadline

PermitSignatures

Helper library for signing permits using Foundry’s vm.sign cheatcode:

  • Supports EIP-2612 permits and Permit2 structs.
  • Standard (r, s, v) and compact (r, vs) signatures.
  • Produces ready-to-use bytes for contract calls.

StructBuilder

Utility for constructing Permit2 structs in memory:

  • Build PermitSingle, PermitBatch, PermitTransferFrom, and PermitBatchTransferFrom variants.
  • Simplifies test setup with builder-style APIs.

TypeHashes

Library for computing EIP-712 type hashes and struct hashes:

  • Precomputed constants for ERC20 and Permit2.
  • Hash helpers that mirror on-chain implementations.

Quick Reference Table

Struct TypeHash Hash Helper
EIP2612Permit (ERC20 Permit) ERC20_PERMIT_TYPEHASH / ERC20_PERMIT_DAI_TYPEHASH TypeHashes.hash(EIP2612Permit, isEIP2612)
PermitSingle (Permit2) PERMIT_SINGLE_TYPEHASH TypeHashes.hash(IAllowanceTransfer.PermitSingle)
PermitBatch (Permit2) PERMIT_BATCH_TYPEHASH TypeHashes.hash(IAllowanceTransfer.PermitBatch)
PermitTransferFrom (Permit2) PERMIT_TRANSFER_FROM_TYPEHASH TypeHashes.hash(ISignatureTransfer.PermitTransferFrom, spender)
PermitBatchTransferFrom (Permit2) PERMIT_BATCH_TRANSFER_FROM_TYPEHASH TypeHashes.hash(ISignatureTransfer.PermitBatchTransferFrom, spender)
PermitWitnessTransferFrom (Permit2) PERMIT_WITNESS_TRANSFER_FROM_TYPEHASH TypeHashes.hash(ISignatureTransfer.PermitTransferFrom, spender, witnessType, witness)
PermitBatchWitnessTransferFrom (Permit2) PERMIT_BATCH_WITNESS_TRANSFER_FROM_TYPEHASH TypeHashes.hash(ISignatureTransfer.PermitBatchTransferFrom, spender, witnessType, witness)

Permit2 Struct Hierarchy

Permit2 Ecosystem
├── AllowanceTransfer
│ ├── PermitSingle
│ │ ├── PermitDetails details
│ │ ├── address spender
│ │ └── uint256 sigDeadline
│ │
│ └── PermitBatch
│ ├── PermitDetails[] details
│ ├── address spender
│ └── uint256 sigDeadline
│
├── SignatureTransfer
│ ├── PermitTransferFrom
│ │ ├── TokenPermissions permitted
│ │ ├── uint256 nonce
│ │ └── uint256 deadline
│ │
│ ├── PermitBatchTransferFrom
│ │ ├── TokenPermissions[] permitted
│ │ ├── uint256 nonce
│ │ └── uint256 deadline
│ │
│ ├── PermitWitnessTransferFrom
│ │ ├── PermitTransferFrom (base)
│ │ ├── bytes witness
│ │ └── string witnessTypeString
│ │
│ └── PermitBatchWitnessTransferFrom
│ ├── PermitBatchTransferFrom (base)
│ ├── bytes witness
│ └── string witnessTypeString
│
└── Core Components
 ├── PermitDetails
 │ ├── address token
 │ ├── uint160 amount
 │ ├── uint48 expiration
 │ └── uint48 nonce
 │
 ├── TokenPermissions
 │ ├── address token
 │ └── uint256 amount
 │
 └── SignatureTransferDetails
 ├── address to
 └── uint256 requestedAmount
  • AllowanceTransfer: approval-style, focuses on allowances (PermitSingle, PermitBatch).
  • SignatureTransfer: transfer-style, focuses on moving tokens in a signed flow.
  • Witness variants: extend the base permits with arbitrary offchain "witness" data.

Installation

forge install fomoweth/permit-utils

Usage Examples

import {IERC20Permit} from "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol";
import {SignatureChecker} from "lib/openzeppelin-contracts/contracts/utils/cryptography/SignatureChecker.sol";
import {IPermit2, IAllowanceTransfer, ISignatureTransfer} from "lib/permit2/src/interfaces/IPermit2.sol";
import {EIP2612Permit, PermitSignatures, StructBuilder, TypeHashes} from "lib/permit-utils/PermitUtils.sol";
contract PermitTest is Test {
 using PermitSignatures for bytes;
 using StructBuilder for *;
 using TypeHashes for *;
 IPermit2 internal constant PERMIT2 = IPermit2(0x000000000022D473030F116dDEE9F6B43aC78BA3);
 address internal constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
 uint256 internal privateKey;
 address internal signer;
 function setUp() public {
 privateKey = ...;
 signer = vm.addr(privateKey);
 }
 function test_signEip2612Permit() public {
 EIP2612Permit memory permit = EIP2612Permit({
 owner: signer,
 spender: ...,
 value: 1 ether,
 nonce: 0,
 deadline: block.timestamp + 5 minutes
 });
 address token = ...;
 bytes32 domainSeparator = IERC20Permit(token).DOMAIN_SEPARATOR();
 (uint8 v, bytes32 r, bytes32 s) = PermitSignatures.sign(privateKey, domainSeparator, permit).parse();
 IERC20Permit(token).permit(permit.owner, permit.spender, permit.value, permit.deadline, v, r, s);
 }
 function test_signDaiPermit() public {
 EIP2612Permit memory permit = EIP2612Permit({
 owner: signer,
 spender: ...,
 value: 1 ether,
 nonce: 0,
 deadline: block.timestamp + 5 minutes
 });
 bytes32 domainSeparator = IERC20Permit(DAI).DOMAIN_SEPARATOR();
 bytes memory signature = PermitSignatures.sign(privateKey, domainSeparator, permit);
 bytes32 structHash = permit.hash(false); // `false` for DAI
 bytes32 messageHash = domainSeparator.hashTypedData(structHash);
 assertTrue(SignatureChecker.isValidSignatureNow(signer, messageHash, signature));
 }
 function test_signPermitSingle() public {
 address spender = ...;
 uint256 sigDeadline = ...;
 address token = ...;
 uint160 amount = ...;
 uint48 expiration = type(uint48).max;
 uint48 nonce = ...;
 IAllowanceTransfer.PermitDetails memory details =
 StructBuilder.asPermitDetails(token, amount, expiration, nonce);
 IAllowanceTransfer.PermitSingle memory permit = StructBuilder
 .init(spender, sigDeadline)
 .set(details)
 .validate();
 bytes32 domainSeparator = PERMIT2.DOMAIN_SEPARATOR();
 bytes memory signature = PermitSignatures.signCompact(privateKey, domainSeparator, permit);
 PERMIT2.permit(signer, permit, signature);
 }
 function test_signPermitBatch() public {
 address spender = ...;
 uint256 sigDeadline = ...;
 address[] memory tokens = ...;
 uint256[] memory amounts = ...;
 uint256[] memory expirations = ...;
 uint256[] memory nonces = ...;
 IAllowanceTransfer.PermitBatch memory permit = StructBuilder
 .init(spender, sigDeadline, tokens.length)
 .set(tokens, amounts, expirations, nonces)
 .validate();
 bytes32 domainSeparator = PERMIT2.DOMAIN_SEPARATOR();
 bytes memory signature = PermitSignatures.signCompact(privateKey, domainSeparator, permit);
 PERMIT2.permit(signer, permit, signature);
 }
 function test_signPermitTransfer() public {
 uint256 nonce = ...;
 uint256 deadline = ...;
 address token = ...;
 uint256 amount = ...;
 address recipient = ...;
 ISignatureTransfer.SignatureTransferDetails memory transferDetails =
 StructBuilder.asSignatureTransferDetails(recipient, amount);
 ISignatureTransfer.PermitTransferFrom memory permit = StructBuilder
 .init(nonce, deadline)
 .set(token, amount)
 .validate();
 bytes32 domainSeparator = PERMIT2.DOMAIN_SEPARATOR();
 bytes memory signature = PermitSignatures.signCompact(privateKey, domainSeparator, permit);
 PERMIT2.permitTransferFrom(permit, transferDetails, signer, signature);
 }
 function test_signPermitBatchTransfer() public {
 uint256 nonce = ...;
 uint256 deadline = ...;
 address[] memory tokens = ...;
 uint256[] memory amounts = ...;
 address[] memory recipients = ...;
 ISignatureTransfer.SignatureTransferDetails[] memory transferDetails =
 StructBuilder.asSignatureTransferDetails(recipients, amounts);
 ISignatureTransfer.TokenPermissions[] memory permitted =
 StructBuilder.asTokenPermissions(tokens, amounts);
 ISignatureTransfer.PermitBatchTransferFrom memory permit = StructBuilder
 .init(nonce, deadline, permitted.length)
 .set(permitted)
 .validate();
 bytes32 domainSeparator = PERMIT2.DOMAIN_SEPARATOR();
 bytes memory signature = PermitSignatures.signCompact(privateKey, domainSeparator, permit);
 PERMIT2.permitTransferFrom(permit, transferDetails, signer, signature);
 }
 struct MockWitness {
 uint256 value;
 address person;
 bool test;
 }
 string internal constant MOCK_WITNESS_TYPE = "MockWitness(uint256 value,address person,bool test)";
 bytes32 internal constant MOCK_WITNESS_TYPEHASH = keccak256(bytes(MOCK_WITNESS_TYPE));
 string internal constant MOCK_WITNESS_TYPESTRING =
 "MockWitness witness)MockWitness(uint256 value,address person,bool test)TokenPermissions(address token,uint256 amount)";
 function test_signPermitWitnessTransfer() public {
 MockWitness memory witnessData = ...;
 string memory witnessTypeString = TypeHashes.WITNESS_TYPESTRING(MOCK_WITNESS_TYPE);
 bytes32 witness = keccak256(abi.encode(MOCK_WITNESS_TYPEHASH, witnessData));
 assertEq(witnessTypeString, MOCK_WITNESS_TYPESTRING);
 uint256 nonce = ...;
 uint256 deadline = ...;
 address token = ...;
 uint256 amount = ...;
 address recipient = ...;
 ISignatureTransfer.SignatureTransferDetails memory transferDetails =
 StructBuilder.asSignatureTransferDetails(recipient, amount);
 ISignatureTransfer.PermitTransferFrom memory permit = StructBuilder
 .init(nonce, deadline)
 .set(token, amount)
 .validate();
 bytes32 domainSeparator = PERMIT2.DOMAIN_SEPARATOR();
 bytes memory signature = PermitSignatures.signCompact(privateKey, domainSeparator, permit);
 PERMIT2.permitWitnessTransferFrom(permit, transferDetails, signer, witness, witnessTypeString, signature);
 }
 function test_signPermitWitnessBatchTransfer() public {
 MockWitness memory witnessData = ...;
 string memory witnessTypeString = TypeHashes.WITNESS_TYPESTRING(MOCK_WITNESS_TYPE);
 bytes32 witness = keccak256(abi.encode(MOCK_WITNESS_TYPEHASH, witnessData));
 assertEq(witnessTypeString, MOCK_WITNESS_TYPESTRING);
 uint256 nonce = ...;
 uint256 deadline = ...;
 address[] memory tokens = ...;
 uint256[] memory amounts = ...;
 address[] memory recipients = ...;
 ISignatureTransfer.SignatureTransferDetails[] memory transferDetails =
 StructBuilder.asSignatureTransferDetails(recipients, amounts);
 ISignatureTransfer.PermitBatchTransferFrom memory permit = StructBuilder
 .init(nonce, deadline, permitted.length)
 .set(tokens, amounts)
 .validate();
 bytes32 domainSeparator = PERMIT2.DOMAIN_SEPARATOR();
 bytes memory signature = PermitSignatures.signCompact(privateKey, domainSeparator, permit);
 PERMIT2.permitWitnessTransferFrom(permit, transferDetails, signer, witness, witnessTypeString, signature);
 }
}

Motivation

Permit-based approvals (EIP-2612 and Permit2) are widely adopted in DeFi, but their signatures and struct encoding can be tricky to test. This repository provides a focused toolkit for: • Struct construction • Type hashing • EIP-712 digest computation • Signature generation

So developers can write robust unit and integration tests quickly.

References

About

EIP-2612 and Permit2 signature utilities for Foundry tests

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

Contributors

AltStyle によって変換されたページ (->オリジナル) /