@perfect-abstractions/compose/token/ERC20/Permit/ERC20PermitMod.solHelper functions for verifying EIP-2612 permits and accessing permit storage
permit performs the full EIP-2612 flow: deadline check, signature recovery, allowance write, nonce bump.Approval itself, so your facet must not emit it again.Use helper functions from Compose using your own custom facets. See Facets & Modules for more information.
This module touches three separate slots: the allowance it writes, the token name it hashes into the EIP-712 domain, and the nonce it consumes.
ERC20_METADATA_STORAGE_POSITIONbytes32keccak256("erc20.metadata"))ERC20_STORAGE_POSITIONbytes32keccak256("erc20"))STORAGE_POSITIONbytes32keccak256("nonces"))Only the name field is declared here, because the domain separator needs nothing else.
This is a layout-compatible prefix of the metadata struct, not a different slot. name is the first field either way, so this module reads exactly the same string ERC20MetadataMod writes. The symbol and decimals fields are simply not declared, because permit never reads them.
The nonce slot is keccak256("nonces"), a bare identifier rather than something like erc20.nonces. Any other contract in the same diamond that picks the string "nonces" for its own storage will land on this slot and corrupt permit nonces. Nothing else in Compose uses it today.
Returns a pointer to the one-field ERC20MetadataStorage struct, used to read the token name for the domain separator.
Returns:
sERC20MetadataStorage storagename only.Returns a pointer to the ERC20Storage struct, where allowances live.
Returns:
sERC20Storage storageReturns a pointer to the NoncesStorage struct.
Read an owner's nonce with getPermitStorage().nonces[_owner]. This module has no nonces() view of its own, unlike ERC20PermitFacet.
Returns:
sNoncesStorage storageReturns the EIP-712 domain separator that signatures must be built against.
It is computed on every call rather than cached, from the token name in metadata storage, the hardcoded version string "1", the current block.chainid, and the address of the contract executing the code.
Returns:
-bytes32A file-level free function has no this, so the module cannot write address(this) the way the facet does. It reads the executing address with a one-line assembly block instead. Under delegatecall both forms resolve to the same value, the diamond's address, so the module and the facet produce identical domain separators.
Verifies an EIP-2612 signature and sets allowance[_owner][_spender] to _value, then increments the owner's nonce and emits Approval.
The whole flow lives here. Your facet supplies the external entrypoint and nothing else, and it must not emit Approval a second time.
Parameters:
_owneraddress_spenderaddressaddress(0)._valueuint256_deadlineuint256block.timestamp equals the deadline is still accepted._vuint8_rbytes32r value of the signature._sbytes32s value of the signature.Reverts:
ERC20InvalidSpendererror_spender is address(0). Checked before anything else.ERC2612InvalidSignatureerror_owner, or recovery failed. All three cases share this one error.Emitted by this module on every successful permit. Your facet should not emit it again, or the allowance change will appear twice in the logs.
_owneraddress_spenderaddress_valueuint256Thrown for every signature failure: an expired deadline, a signer that does not match _owner, or a recovery that returned address(0). One error covers all three, so a caller cannot tell them apart without checking the deadline separately.
_owneraddress_spenderaddress_valueuint256_deadlineuint256_vuint8_rbytes32r value of the rejected signature._sbytes32s value of the rejected signature.ERC20MetadataMod.setMetadata during deployment, before anyone signs a permit. Changing the name later invalidates every signature already in circulation.DOMAIN_SEPARATOR and a nonce getter from your facet as well. Wallets and signing libraries expect to read both on-chain, and EIP-2612 requires a nonces(address) view that this module does not provide.Approval from your wrapper. This module already emits it.ERC20PermitFacet instead. It exports nonces, DOMAIN_SEPARATOR and permit ready to install.Import the module under a namespace and call it from your facet:
The allowance is written to ERC20Storage at keccak256("erc20"), the same slot ERC20DataFacet reads and ERC20TransferFacet spends, so a permit and a plain approve are interchangeable from every other contract's point of view.
This module writes the allowance directly rather than calling ERC20ApproveMod. It has to: that helper always acts for msg.sender, and permit must act for the signer instead.