// SPDX-License-Identifier: MITpragmasolidity ^0.8.17;import {IAllowanceTransfer} from"../interfaces/IAllowanceTransfer.sol";
libraryAllowance{
// note if the expiration passed is 0, then it the approval set to the block.timestampuint256privateconstant BLOCK_TIMESTAMP_EXPIRATION =0;
/// @notice Sets the allowed amount, expiry, and nonce of the spender's permissions on owner's token./// @dev Nonce is incremented./// @dev If the inputted expiration is 0, the stored expiration is set to block.timestampfunctionupdateAll(
IAllowanceTransfer.PackedAllowance storage allowed,
uint160 amount,
uint48 expiration,
uint48 nonce
) internal{
uint48 storedNonce;
unchecked {
storedNonce = nonce +1;
}
uint48 storedExpiration = expiration == BLOCK_TIMESTAMP_EXPIRATION ? uint48(block.timestamp) : expiration;
uint256 word = pack(amount, storedExpiration, storedNonce);
assembly {
sstore(allowed.slot, word)
}
}
/// @notice Sets the allowed amount and expiry of the spender's permissions on owner's token./// @dev Nonce does not need to be incremented.functionupdateAmountAndExpiration(
IAllowanceTransfer.PackedAllowance storage allowed,
uint160 amount,
uint48 expiration
) internal{
// If the inputted expiration is 0, the allowance only lasts the duration of the block.
allowed.expiration = expiration ==0 ? uint48(block.timestamp) : expiration;
allowed.amount = amount;
}
/// @notice Computes the packed slot of the amount, expiration, and nonce that make up PackedAllowancefunctionpack(uint160 amount, uint48 expiration, uint48 nonce) internalpurereturns (uint256 word) {
word = (uint256(nonce) <<208) |uint256(expiration) <<160| amount;
}
}
Contract Source Code
File 2 of 14: AllowanceTransfer.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.17;import {ERC20} from"solmate/src/tokens/ERC20.sol";
import {SafeTransferLib} from"solmate/src/utils/SafeTransferLib.sol";
import {PermitHash} from"./libraries/PermitHash.sol";
import {SignatureVerification} from"./libraries/SignatureVerification.sol";
import {EIP712} from"./EIP712.sol";
import {IAllowanceTransfer} from"../src/interfaces/IAllowanceTransfer.sol";
import {SignatureExpired, InvalidNonce} from"./PermitErrors.sol";
import {Allowance} from"./libraries/Allowance.sol";
contractAllowanceTransferisIAllowanceTransfer, EIP712{
usingSignatureVerificationforbytes;
usingSafeTransferLibforERC20;
usingPermitHashforPermitSingle;
usingPermitHashforPermitBatch;
usingAllowanceforPackedAllowance;
/// @notice Maps users to tokens to spender addresses and information about the approval on the token/// @dev Indexed in the order of token owner address, token address, spender address/// @dev The stored word saves the allowed amount, expiration on the allowance, and noncemapping(address=>mapping(address=>mapping(address=> PackedAllowance))) public allowance;
/// @inheritdoc IAllowanceTransferfunctionapprove(address token, address spender, uint160 amount, uint48 expiration) external{
PackedAllowance storage allowed = allowance[msg.sender][token][spender];
allowed.updateAmountAndExpiration(amount, expiration);
emit Approval(msg.sender, token, spender, amount, expiration);
}
/// @inheritdoc IAllowanceTransferfunctionpermit(address owner, PermitSingle memory permitSingle, bytescalldata signature) external{
if (block.timestamp> permitSingle.sigDeadline) revert SignatureExpired(permitSingle.sigDeadline);
// Verify the signer address from the signature.
signature.verify(_hashTypedData(permitSingle.hash()), owner);
_updateApproval(permitSingle.details, owner, permitSingle.spender);
}
/// @inheritdoc IAllowanceTransferfunctionpermit(address owner, PermitBatch memory permitBatch, bytescalldata signature) external{
if (block.timestamp> permitBatch.sigDeadline) revert SignatureExpired(permitBatch.sigDeadline);
// Verify the signer address from the signature.
signature.verify(_hashTypedData(permitBatch.hash()), owner);
address spender = permitBatch.spender;
unchecked {
uint256 length = permitBatch.details.length;
for (uint256 i =0; i < length; ++i) {
_updateApproval(permitBatch.details[i], owner, spender);
}
}
}
/// @inheritdoc IAllowanceTransferfunctiontransferFrom(addressfrom, address to, uint160 amount, address token) external{
_transfer(from, to, amount, token);
}
/// @inheritdoc IAllowanceTransferfunctiontransferFrom(AllowanceTransferDetails[] calldata transferDetails) external{
unchecked {
uint256 length = transferDetails.length;
for (uint256 i =0; i < length; ++i) {
AllowanceTransferDetails memory transferDetail = transferDetails[i];
_transfer(transferDetail.from, transferDetail.to, transferDetail.amount, transferDetail.token);
}
}
}
/// @notice Internal function for transferring tokens using stored allowances/// @dev Will fail if the allowed timeframe has passedfunction_transfer(addressfrom, address to, uint160 amount, address token) private{
PackedAllowance storage allowed = allowance[from][token][msg.sender];
if (block.timestamp> allowed.expiration) revert AllowanceExpired(allowed.expiration);
uint256 maxAmount = allowed.amount;
if (maxAmount !=type(uint160).max) {
if (amount > maxAmount) {
revert InsufficientAllowance(maxAmount);
} else {
unchecked {
allowed.amount =uint160(maxAmount) - amount;
}
}
}
// Transfer the tokens from the from address to the recipient.
ERC20(token).safeTransferFrom(from, to, amount);
}
/// @inheritdoc IAllowanceTransferfunctionlockdown(TokenSpenderPair[] calldata approvals) external{
address owner =msg.sender;
// Revoke allowances for each pair of spenders and tokens.unchecked {
uint256 length = approvals.length;
for (uint256 i =0; i < length; ++i) {
address token = approvals[i].token;
address spender = approvals[i].spender;
allowance[owner][token][spender].amount =0;
emit Lockdown(owner, token, spender);
}
}
}
/// @inheritdoc IAllowanceTransferfunctioninvalidateNonces(address token, address spender, uint48 newNonce) external{
uint48 oldNonce = allowance[msg.sender][token][spender].nonce;
if (newNonce <= oldNonce) revert InvalidNonce();
// Limit the amount of nonces that can be invalidated in one transaction.unchecked {
uint48 delta = newNonce - oldNonce;
if (delta >type(uint16).max) revert ExcessiveInvalidation();
}
allowance[msg.sender][token][spender].nonce = newNonce;
emit NonceInvalidation(msg.sender, token, spender, newNonce, oldNonce);
}
/// @notice Sets the new values for amount, expiration, and nonce./// @dev Will check that the signed nonce is equal to the current nonce and then incrememnt the nonce value by 1./// @dev Emits a Permit event.function_updateApproval(PermitDetails memory details, address owner, address spender) private{
uint48 nonce = details.nonce;
address token = details.token;
uint160 amount = details.amount;
uint48 expiration = details.expiration;
PackedAllowance storage allowed = allowance[owner][token][spender];
if (allowed.nonce != nonce) revert InvalidNonce();
allowed.updateAll(amount, expiration, nonce);
emit Permit(owner, token, spender, amount, expiration, nonce);
}
}
Contract Source Code
File 3 of 14: EIP712.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.17;import {IEIP712} from"./interfaces/IEIP712.sol";
/// @notice EIP712 helpers for permit2/// @dev Maintains cross-chain replay protection in the event of a fork/// @dev Reference: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.solcontractEIP712isIEIP712{
// 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.bytes32privateimmutable _CACHED_DOMAIN_SEPARATOR;
uint256privateimmutable _CACHED_CHAIN_ID;
bytes32privateconstant _HASHED_NAME =keccak256("Permit2");
bytes32privateconstant _TYPE_HASH =keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");
constructor() {
_CACHED_CHAIN_ID =block.chainid;
_CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME);
}
/// @notice Returns the domain separator for the current chain./// @dev Uses cached version if chainid and address are unchanged from construction.functionDOMAIN_SEPARATOR() publicviewoverridereturns (bytes32) {
returnblock.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) privateviewreturns (bytes32) {
returnkeccak256(abi.encode(typeHash, nameHash, block.chainid, address(this)));
}
/// @notice Creates an EIP-712 typed data hashfunction_hashTypedData(bytes32 dataHash) internalviewreturns (bytes32) {
returnkeccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), dataHash));
}
}
Contract Source Code
File 4 of 14: ERC20.sol
// SPDX-License-Identifier: AGPL-3.0-onlypragmasolidity >=0.8.0;/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation./// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.abstractcontractERC20{
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/eventTransfer(addressindexedfrom, addressindexed to, uint256 amount);
eventApproval(addressindexed owner, addressindexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/stringpublic name;
stringpublic symbol;
uint8publicimmutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/uint256public totalSupply;
mapping(address=>uint256) public balanceOf;
mapping(address=>mapping(address=>uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/uint256internalimmutable INITIAL_CHAIN_ID;
bytes32internalimmutable INITIAL_DOMAIN_SEPARATOR;
mapping(address=>uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/constructor(stringmemory _name,
stringmemory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID =block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/functionapprove(address spender, uint256 amount) publicvirtualreturns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
returntrue;
}
functiontransfer(address to, uint256 amount) publicvirtualreturns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user// balances can't exceed the max uint256 value.unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
returntrue;
}
functiontransferFrom(addressfrom,
address to,
uint256 amount
) publicvirtualreturns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.if (allowed !=type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user// balances can't exceed the max uint256 value.unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
returntrue;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/functionpermit(address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) publicvirtual{
require(deadline >=block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing// the owner's nonce which cannot realistically overflow.unchecked {
address recoveredAddress =ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress !=address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
functionDOMAIN_SEPARATOR() publicviewvirtualreturns (bytes32) {
returnblock.chainid== INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
functioncomputeDomainSeparator() internalviewvirtualreturns (bytes32) {
returnkeccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/function_mint(address to, uint256 amount) internalvirtual{
totalSupply += amount;
// Cannot overflow because the sum of all user// balances can't exceed the max uint256 value.unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function_burn(addressfrom, uint256 amount) internalvirtual{
balanceOf[from] -= amount;
// Cannot underflow because a user's balance// will never be larger than the total supply.unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
Contract Source Code
File 5 of 14: IAllowanceTransfer.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.17;import {IEIP712} from"./IEIP712.sol";
/// @title AllowanceTransfer/// @notice Handles ERC20 token permissions through signature based allowance setting and ERC20 token transfers by checking allowed amounts/// @dev Requires user's token approval on the Permit2 contractinterfaceIAllowanceTransferisIEIP712{
/// @notice Thrown when an allowance on a token has expired./// @param deadline The timestamp at which the allowed amount is no longer validerrorAllowanceExpired(uint256 deadline);
/// @notice Thrown when an allowance on a token has been depleted./// @param amount The maximum amount allowederrorInsufficientAllowance(uint256 amount);
/// @notice Thrown when too many nonces are invalidated.errorExcessiveInvalidation();
/// @notice Emits an event when the owner successfully invalidates an ordered nonce.eventNonceInvalidation(addressindexed owner, addressindexed token, addressindexed spender, uint48 newNonce, uint48 oldNonce
);
/// @notice Emits an event when the owner successfully sets permissions on a token for the spender.eventApproval(addressindexed owner, addressindexed token, addressindexed spender, uint160 amount, uint48 expiration
);
/// @notice Emits an event when the owner successfully sets permissions using a permit signature on a token for the spender.eventPermit(addressindexed owner,
addressindexed token,
addressindexed spender,
uint160 amount,
uint48 expiration,
uint48 nonce
);
/// @notice Emits an event when the owner sets the allowance back to 0 with the lockdown function.eventLockdown(addressindexed owner, address token, address spender);
/// @notice The permit data for a tokenstructPermitDetails {
// ERC20 token addressaddress token;
// the maximum amount allowed to spenduint160 amount;
// timestamp at which a spender's token allowances become invaliduint48 expiration;
// an incrementing value indexed per owner,token,and spender for each signatureuint48 nonce;
}
/// @notice The permit message signed for a single token allowncestructPermitSingle {
// the permit data for a single token alownce
PermitDetails details;
// address permissioned on the allowed tokensaddress spender;
// deadline on the permit signatureuint256 sigDeadline;
}
/// @notice The permit message signed for multiple token allowancesstructPermitBatch {
// the permit data for multiple token allowances
PermitDetails[] details;
// address permissioned on the allowed tokensaddress spender;
// deadline on the permit signatureuint256 sigDeadline;
}
/// @notice The saved permissions/// @dev This info is saved per owner, per token, per spender and all signed over in the permit message/// @dev Setting amount to type(uint160).max sets an unlimited approvalstructPackedAllowance {
// amount alloweduint160 amount;
// permission expiryuint48 expiration;
// an incrementing value indexed per owner,token,and spender for each signatureuint48 nonce;
}
/// @notice A token spender pair.structTokenSpenderPair {
// the token the spender is approvedaddress token;
// the spender addressaddress spender;
}
/// @notice Details for a token transfer.structAllowanceTransferDetails {
// the owner of the tokenaddressfrom;
// the recipient of the tokenaddress to;
// the amount of the tokenuint160 amount;
// the token to be transferredaddress token;
}
/// @notice A mapping from owner address to token address to spender address to PackedAllowance struct, which contains details and conditions of the approval./// @notice The mapping is indexed in the above order see: allowance[ownerAddress][tokenAddress][spenderAddress]/// @dev The packed slot holds the allowed amount, expiration at which the allowed amount is no longer valid, and current nonce thats updated on any signature based approvals.functionallowance(address user, address token, address spender)
externalviewreturns (uint160 amount, uint48 expiration, uint48 nonce);
/// @notice Approves the spender to use up to amount of the specified token up until the expiration/// @param token The token to approve/// @param spender The spender address to approve/// @param amount The approved amount of the token/// @param expiration The timestamp at which the approval is no longer valid/// @dev The packed allowance also holds a nonce, which will stay unchanged in approve/// @dev Setting amount to type(uint160).max sets an unlimited approvalfunctionapprove(address token, address spender, uint160 amount, uint48 expiration) external;
/// @notice Permit a spender to a given amount of the owners token via the owner's EIP-712 signature/// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce/// @param owner The owner of the tokens being approved/// @param permitSingle Data signed over by the owner specifying the terms of approval/// @param signature The owner's signature over the permit datafunctionpermit(address owner, PermitSingle memory permitSingle, bytescalldata signature) external;
/// @notice Permit a spender to the signed amounts of the owners tokens via the owner's EIP-712 signature/// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce/// @param owner The owner of the tokens being approved/// @param permitBatch Data signed over by the owner specifying the terms of approval/// @param signature The owner's signature over the permit datafunctionpermit(address owner, PermitBatch memory permitBatch, bytescalldata signature) external;
/// @notice Transfer approved tokens from one address to another/// @param from The address to transfer from/// @param to The address of the recipient/// @param amount The amount of the token to transfer/// @param token The token address to transfer/// @dev Requires the from address to have approved at least the desired amount/// of tokens to msg.sender.functiontransferFrom(addressfrom, address to, uint160 amount, address token) external;
/// @notice Transfer approved tokens in a batch/// @param transferDetails Array of owners, recipients, amounts, and tokens for the transfers/// @dev Requires the from addresses to have approved at least the desired amount/// of tokens to msg.sender.functiontransferFrom(AllowanceTransferDetails[] calldata transferDetails) external;
/// @notice Enables performing a "lockdown" of the sender's Permit2 identity/// by batch revoking approvals/// @param approvals Array of approvals to revoke.functionlockdown(TokenSpenderPair[] calldata approvals) external;
/// @notice Invalidate nonces for a given (token, spender) pair/// @param token The token to invalidate nonces for/// @param spender The spender to invalidate nonces for/// @param newNonce The new nonce to set. Invalidates all nonces less than it./// @dev Can't invalidate more than 2**16 nonces per transaction.functioninvalidateNonces(address token, address spender, uint48 newNonce) external;
}
// SPDX-License-Identifier: MITpragmasolidity ^0.8.17;interfaceIERC1271{
/// @dev Should return whether the signature provided is valid for the provided data/// @param hash Hash of the data to be signed/// @param signature Signature byte array associated with _data/// @return magicValue The bytes4 magic value 0x1626ba7efunctionisValidSignature(bytes32 hash, bytesmemory signature) externalviewreturns (bytes4 magicValue);
}
Contract Source Code
File 8 of 14: ISignatureTransfer.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.17;import {IEIP712} from"./IEIP712.sol";
/// @title SignatureTransfer/// @notice Handles ERC20 token transfers through signature based actions/// @dev Requires user's token approval on the Permit2 contractinterfaceISignatureTransferisIEIP712{
/// @notice Thrown when the requested amount for a transfer is larger than the permissioned amount/// @param maxAmount The maximum amount a spender can request to transfererrorInvalidAmount(uint256 maxAmount);
/// @notice Thrown when the number of tokens permissioned to a spender does not match the number of tokens being transferred/// @dev If the spender does not need to transfer the number of tokens permitted, the spender can request amount 0 to be transferrederrorLengthMismatch();
/// @notice Emits an event when the owner successfully invalidates an unordered nonce.eventUnorderedNonceInvalidation(addressindexed owner, uint256 word, uint256 mask);
/// @notice The token and amount details for a transfer signed in the permit transfer signaturestructTokenPermissions {
// ERC20 token addressaddress token;
// the maximum amount that can be spentuint256 amount;
}
/// @notice The signed permit message for a single token transferstructPermitTransferFrom {
TokenPermissions permitted;
// a unique value for every token owner's signature to prevent signature replaysuint256 nonce;
// deadline on the permit signatureuint256 deadline;
}
/// @notice Specifies the recipient address and amount for batched transfers./// @dev Recipients and amounts correspond to the index of the signed token permissions array./// @dev Reverts if the requested amount is greater than the permitted signed amount.structSignatureTransferDetails {
// recipient addressaddress to;
// spender requested amountuint256 requestedAmount;
}
/// @notice Used to reconstruct the signed permit message for multiple token transfers/// @dev Do not need to pass in spender address as it is required that it is msg.sender/// @dev Note that a user still signs over a spender addressstructPermitBatchTransferFrom {
// the tokens and corresponding amounts permitted for a transfer
TokenPermissions[] permitted;
// a unique value for every token owner's signature to prevent signature replaysuint256 nonce;
// deadline on the permit signatureuint256 deadline;
}
/// @notice A map from token owner address and a caller specified word index to a bitmap. Used to set bits in the bitmap to prevent against signature replay protection/// @dev Uses unordered nonces so that permit messages do not need to be spent in a certain order/// @dev The mapping is indexed first by the token owner, then by an index specified in the nonce/// @dev It returns a uint256 bitmap/// @dev The index, or wordPosition is capped at type(uint248).maxfunctionnonceBitmap(address, uint256) externalviewreturns (uint256);
/// @notice Transfers a token using a signed permit message/// @dev Reverts if the requested amount is greater than the permitted signed amount/// @param permit The permit data signed over by the owner/// @param owner The owner of the tokens to transfer/// @param transferDetails The spender's requested transfer details for the permitted token/// @param signature The signature to verifyfunctionpermitTransferFrom(
PermitTransferFrom memory permit,
SignatureTransferDetails calldata transferDetails,
address owner,
bytescalldata signature
) external;
/// @notice Transfers a token using a signed permit message/// @notice Includes extra data provided by the caller to verify signature over/// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition/// @dev Reverts if the requested amount is greater than the permitted signed amount/// @param permit The permit data signed over by the owner/// @param owner The owner of the tokens to transfer/// @param transferDetails The spender's requested transfer details for the permitted token/// @param witness Extra data to include when checking the user signature/// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash/// @param signature The signature to verifyfunctionpermitWitnessTransferFrom(
PermitTransferFrom memory permit,
SignatureTransferDetails calldata transferDetails,
address owner,
bytes32 witness,
stringcalldata witnessTypeString,
bytescalldata signature
) external;
/// @notice Transfers multiple tokens using a signed permit message/// @param permit The permit data signed over by the owner/// @param owner The owner of the tokens to transfer/// @param transferDetails Specifies the recipient and requested amount for the token transfer/// @param signature The signature to verifyfunctionpermitTransferFrom(
PermitBatchTransferFrom memory permit,
SignatureTransferDetails[] calldata transferDetails,
address owner,
bytescalldata signature
) external;
/// @notice Transfers multiple tokens using a signed permit message/// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition/// @notice Includes extra data provided by the caller to verify signature over/// @param permit The permit data signed over by the owner/// @param owner The owner of the tokens to transfer/// @param transferDetails Specifies the recipient and requested amount for the token transfer/// @param witness Extra data to include when checking the user signature/// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash/// @param signature The signature to verifyfunctionpermitWitnessTransferFrom(
PermitBatchTransferFrom memory permit,
SignatureTransferDetails[] calldata transferDetails,
address owner,
bytes32 witness,
stringcalldata witnessTypeString,
bytescalldata signature
) external;
/// @notice Invalidates the bits specified in mask for the bitmap at the word position/// @dev The wordPos is maxed at type(uint248).max/// @param wordPos A number to index the nonceBitmap at/// @param mask A bitmap masked against msg.sender's current bitmap at the word positionfunctioninvalidateUnorderedNonces(uint256 wordPos, uint256 mask) external;
}
Contract Source Code
File 9 of 14: Permit2.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.17;import {SignatureTransfer} from"./SignatureTransfer.sol";
import {AllowanceTransfer} from"./AllowanceTransfer.sol";
/// @notice Permit2 handles signature-based transfers in SignatureTransfer and allowance-based transfers in AllowanceTransfer./// @dev Users must approve Permit2 before calling any of the transfer functions.contractPermit2isSignatureTransfer, AllowanceTransfer{
// Permit2 unifies the two contracts so users have maximal flexibility with their approval.
}
Contract Source Code
File 10 of 14: PermitErrors.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.17;/// @notice Shared errors between signature based transfers and allowance based transfers./// @notice Thrown when validating an inputted signature that is stale/// @param signatureDeadline The timestamp at which a signature is no longer validerrorSignatureExpired(uint256 signatureDeadline);
/// @notice Thrown when validating that the inputted nonce has not been usederrorInvalidNonce();
// SPDX-License-Identifier: AGPL-3.0-onlypragmasolidity >=0.8.0;import {ERC20} from"../tokens/ERC20.sol";
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values./// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer./// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.librarySafeTransferLib{
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/functionsafeTransferETH(address to, uint256 amount) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
// Transfer the ETH and store if it succeeded or not.
success :=call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/functionsafeTransferFrom(
ERC20 token,
addressfrom,
address to,
uint256 amount
) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
// Get a pointer to some free memory.let freeMemoryPointer :=mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument.mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument.mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument.
success :=and(
// Set success to whether the call reverted, if not we check it either// returned exactly 1 (can't just be non-zero data), or had no return data.or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.// Counterintuitively, this call must be positioned second to the or() call in the// surrounding and() call or else returndatasize() will be zero during the computation.call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
functionsafeTransfer(
ERC20 token,
address to,
uint256 amount
) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
// Get a pointer to some free memory.let freeMemoryPointer :=mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success :=and(
// Set success to whether the call reverted, if not we check it either// returned exactly 1 (can't just be non-zero data), or had no return data.or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.// Counterintuitively, this call must be positioned second to the or() call in the// surrounding and() call or else returndatasize() will be zero during the computation.call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
functionsafeApprove(
ERC20 token,
address to,
uint256 amount
) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
// Get a pointer to some free memory.let freeMemoryPointer :=mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success :=and(
// Set success to whether the call reverted, if not we check it either// returned exactly 1 (can't just be non-zero data), or had no return data.or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.// Counterintuitively, this call must be positioned second to the or() call in the// surrounding and() call or else returndatasize() will be zero during the computation.call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}
Contract Source Code
File 13 of 14: SignatureTransfer.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.17;import {ISignatureTransfer} from"./interfaces/ISignatureTransfer.sol";
import {SignatureExpired, InvalidNonce} from"./PermitErrors.sol";
import {ERC20} from"solmate/src/tokens/ERC20.sol";
import {SafeTransferLib} from"solmate/src/utils/SafeTransferLib.sol";
import {SignatureVerification} from"./libraries/SignatureVerification.sol";
import {PermitHash} from"./libraries/PermitHash.sol";
import {EIP712} from"./EIP712.sol";
contractSignatureTransferisISignatureTransfer, EIP712{
usingSignatureVerificationforbytes;
usingSafeTransferLibforERC20;
usingPermitHashforPermitTransferFrom;
usingPermitHashforPermitBatchTransferFrom;
/// @inheritdoc ISignatureTransfermapping(address=>mapping(uint256=>uint256)) public nonceBitmap;
/// @inheritdoc ISignatureTransferfunctionpermitTransferFrom(
PermitTransferFrom memory permit,
SignatureTransferDetails calldata transferDetails,
address owner,
bytescalldata signature
) external{
_permitTransferFrom(permit, transferDetails, owner, permit.hash(), signature);
}
/// @inheritdoc ISignatureTransferfunctionpermitWitnessTransferFrom(
PermitTransferFrom memory permit,
SignatureTransferDetails calldata transferDetails,
address owner,
bytes32 witness,
stringcalldata witnessTypeString,
bytescalldata signature
) external{
_permitTransferFrom(
permit, transferDetails, owner, permit.hashWithWitness(witness, witnessTypeString), signature
);
}
/// @notice Transfers a token using a signed permit message./// @param permit The permit data signed over by the owner/// @param dataHash The EIP-712 hash of permit data to include when checking signature/// @param owner The owner of the tokens to transfer/// @param transferDetails The spender's requested transfer details for the permitted token/// @param signature The signature to verifyfunction_permitTransferFrom(
PermitTransferFrom memory permit,
SignatureTransferDetails calldata transferDetails,
address owner,
bytes32 dataHash,
bytescalldata signature
) private{
uint256 requestedAmount = transferDetails.requestedAmount;
if (block.timestamp> permit.deadline) revert SignatureExpired(permit.deadline);
if (requestedAmount > permit.permitted.amount) revert InvalidAmount(permit.permitted.amount);
_useUnorderedNonce(owner, permit.nonce);
signature.verify(_hashTypedData(dataHash), owner);
ERC20(permit.permitted.token).safeTransferFrom(owner, transferDetails.to, requestedAmount);
}
/// @inheritdoc ISignatureTransferfunctionpermitTransferFrom(
PermitBatchTransferFrom memory permit,
SignatureTransferDetails[] calldata transferDetails,
address owner,
bytescalldata signature
) external{
_permitTransferFrom(permit, transferDetails, owner, permit.hash(), signature);
}
/// @inheritdoc ISignatureTransferfunctionpermitWitnessTransferFrom(
PermitBatchTransferFrom memory permit,
SignatureTransferDetails[] calldata transferDetails,
address owner,
bytes32 witness,
stringcalldata witnessTypeString,
bytescalldata signature
) external{
_permitTransferFrom(
permit, transferDetails, owner, permit.hashWithWitness(witness, witnessTypeString), signature
);
}
/// @notice Transfers tokens using a signed permit messages/// @param permit The permit data signed over by the owner/// @param dataHash The EIP-712 hash of permit data to include when checking signature/// @param owner The owner of the tokens to transfer/// @param signature The signature to verifyfunction_permitTransferFrom(
PermitBatchTransferFrom memory permit,
SignatureTransferDetails[] calldata transferDetails,
address owner,
bytes32 dataHash,
bytescalldata signature
) private{
uint256 numPermitted = permit.permitted.length;
if (block.timestamp> permit.deadline) revert SignatureExpired(permit.deadline);
if (numPermitted != transferDetails.length) revert LengthMismatch();
_useUnorderedNonce(owner, permit.nonce);
signature.verify(_hashTypedData(dataHash), owner);
unchecked {
for (uint256 i =0; i < numPermitted; ++i) {
TokenPermissions memory permitted = permit.permitted[i];
uint256 requestedAmount = transferDetails[i].requestedAmount;
if (requestedAmount > permitted.amount) revert InvalidAmount(permitted.amount);
if (requestedAmount !=0) {
// allow spender to specify which of the permitted tokens should be transferred
ERC20(permitted.token).safeTransferFrom(owner, transferDetails[i].to, requestedAmount);
}
}
}
}
/// @inheritdoc ISignatureTransferfunctioninvalidateUnorderedNonces(uint256 wordPos, uint256 mask) external{
nonceBitmap[msg.sender][wordPos] |= mask;
emit UnorderedNonceInvalidation(msg.sender, wordPos, mask);
}
/// @notice Returns the index of the bitmap and the bit position within the bitmap. Used for unordered nonces/// @param nonce The nonce to get the associated word and bit positions/// @return wordPos The word position or index into the nonceBitmap/// @return bitPos The bit position/// @dev The first 248 bits of the nonce value is the index of the desired bitmap/// @dev The last 8 bits of the nonce value is the position of the bit in the bitmapfunctionbitmapPositions(uint256 nonce) privatepurereturns (uint256 wordPos, uint256 bitPos) {
wordPos =uint248(nonce >>8);
bitPos =uint8(nonce);
}
/// @notice Checks whether a nonce is taken and sets the bit at the bit position in the bitmap at the word position/// @param from The address to use the nonce at/// @param nonce The nonce to spendfunction_useUnorderedNonce(addressfrom, uint256 nonce) internal{
(uint256 wordPos, uint256 bitPos) = bitmapPositions(nonce);
uint256 bit =1<< bitPos;
uint256 flipped = nonceBitmap[from][wordPos] ^= bit;
if (flipped & bit ==0) revert InvalidNonce();
}
}
Contract Source Code
File 14 of 14: SignatureVerification.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.17;import {IERC1271} from"../interfaces/IERC1271.sol";
librarySignatureVerification{
/// @notice Thrown when the passed in signature is not a valid lengtherrorInvalidSignatureLength();
/// @notice Thrown when the recovered signer is equal to the zero addresserrorInvalidSignature();
/// @notice Thrown when the recovered signer does not equal the claimedSignererrorInvalidSigner();
/// @notice Thrown when the recovered contract signature is incorrecterrorInvalidContractSignature();
bytes32constant UPPER_BIT_MASK = (0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
functionverify(bytescalldata signature, bytes32 hash, address claimedSigner) internalview{
bytes32 r;
bytes32 s;
uint8 v;
if (claimedSigner.code.length==0) {
if (signature.length==65) {
(r, s) =abi.decode(signature, (bytes32, bytes32));
v =uint8(signature[64]);
} elseif (signature.length==64) {
// EIP-2098bytes32 vs;
(r, vs) =abi.decode(signature, (bytes32, bytes32));
s = vs & UPPER_BIT_MASK;
v =uint8(uint256(vs >>255)) +27;
} else {
revert InvalidSignatureLength();
}
address signer =ecrecover(hash, v, r, s);
if (signer ==address(0)) revert InvalidSignature();
if (signer != claimedSigner) revert InvalidSigner();
} else {
bytes4 magicValue = IERC1271(claimedSigner).isValidSignature(hash, signature);
if (magicValue != IERC1271.isValidSignature.selector) revert InvalidContractSignature();
}
}
}