ERC2612 extends ERC20 with permit() — a function that accepts an off-chain EIP-712 signature to set approvals without requiring a separate on-chain transaction from the token holder. This gasless approval mechanism has been widely adopted but introduces signature security risks that many developers underestimate.
A user signs a typed EIP-712 message off-chain containing: owner, spender, value, nonce, and deadline. The spender (or anyone) can then submit this signature on-chain via token.permit(owner, spender, value, deadline, v, r, s). The token contract verifies the signature and sets the allowance.
If a permit signature is reused after the nonce is incremented (e.g., through a contract that inadvertently resets nonces), the signature can be replayed. More commonly, applications that sign permits without including chain IDs are vulnerable to cross-chain replay — the same signature works on Ethereum mainnet and on a fork. See signature replay.
EIP-712 domain separator must include: contract address, chain ID, and version. Verify this in your permit implementation:
bytes32 public constant DOMAIN_SEPARATOR = keccak256(abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256(bytes("1")),
block.chainid, // must use chainid, not hardcoded value
address(this)
));
WETH's permit() function does nothing — it doesn't revert and doesn't set any allowance. Protocols that call weth.permit(...) and then immediately call weth.transferFrom() will fail on the transferFrom step because no allowance was granted.
This vulnerability contributed to the $130M+ Multichain incident. Always check that the allowance was actually set after calling permit, or use a try/catch and fallback to a standard approval flow:
try IERC20Permit(token).permit(owner, spender, value, deadline, v, r, s) {
// permit succeeded
} catch {
// permit failed — check if allowance was already sufficient
if (IERC20(token).allowance(owner, spender) < value) revert PermitFailed();
}
See the WETH token risk profile for the specific vulnerability details.
DAI implements a non-standard permit using allowed (boolean) instead of value, and a different expiry/nonce format from EIP-2612. Code written for standard ERC2612 permit will silently fail or revert when used with DAI. Always check the specific token's permit implementation. See the DAI token risk profile.
Since permit transactions are broadcast to the mempool, an attacker can observe a pending permit and front-run it with their own call using the signature. If the attacker's transaction executes first with a different spender address, the original transaction will fail with "invalid nonce." Design applications to handle permit failures gracefully.
SmartContract.us detects permit no-op vulnerabilities, cross-chain signature risks, and non-standard permit implementations. Analyze a contract →