forked from Vectorized/solady
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ ERC20P related to issue Vectorized#735
- Loading branch information
Showing
5 changed files
with
1,316 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.4; | ||
|
||
import {ERC20StorageLayout} from "./ERC20StorageLayout.sol"; | ||
|
||
/// @notice EIP-2612 implementation implementation. | ||
/// @author Solady: | ||
/// (https://github.com/vectorized/solady/blob/main/src/tokens/ERC20P/ERC20StorageLayout.sol) | ||
/// | ||
/// @dev Note: | ||
/// - The `permit` function uses the ecrecover precompile (0x1). | ||
abstract contract EIP2612 is ERC20StorageLayout { | ||
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | ||
/* CUSTOM ERRORS */ | ||
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | ||
|
||
/// @dev The permit is invalid. | ||
error InvalidPermit(); | ||
|
||
/// @dev The permit has expired. | ||
error PermitExpired(); | ||
|
||
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | ||
/* CONSTANTS */ | ||
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | ||
/// @dev The nonce slot of `owner` is given by: | ||
/// ``` | ||
/// mstore(0x0c, _NONCES_SLOT_SEED) | ||
/// mstore(0x00, owner) | ||
/// let nonceSlot := keccak256(0x0c, 0x20) | ||
/// ``` | ||
uint256 internal constant _NONCES_SLOT_SEED = 0x38377508; | ||
|
||
/// @dev `(_NONCES_SLOT_SEED << 16) | 0x1901`. | ||
uint256 private constant _NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX = | ||
0x383775081901; | ||
|
||
/// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. | ||
bytes32 private constant _DOMAIN_TYPEHASH = | ||
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; | ||
|
||
/// @dev `keccak256("1")`. | ||
bytes32 private constant _VERSION_HASH = | ||
0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6; | ||
|
||
/// @dev `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`. | ||
bytes32 private constant _PERMIT_TYPEHASH = | ||
0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; | ||
|
||
function name() public view virtual returns (string memory); | ||
|
||
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | ||
/* EIP-2612 */ | ||
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | ||
|
||
/// @dev For more performance, override to return the constant value | ||
/// of `keccak256(bytes(name()))` if `name()` will never change. | ||
function _constantNameHash() | ||
internal | ||
view | ||
virtual | ||
returns (bytes32 result) {} | ||
|
||
/// @dev Returns the current nonce for `owner`. | ||
/// This value is used to compute the signature for EIP-2612 permit. | ||
function nonces( | ||
address owner | ||
) public view virtual returns (uint256 result) { | ||
/// @solidity memory-safe-assembly | ||
assembly { | ||
// Compute the nonce slot and load its value. | ||
mstore(0x0c, _NONCES_SLOT_SEED) | ||
mstore(0x00, owner) | ||
result := sload(keccak256(0x0c, 0x20)) | ||
} | ||
} | ||
|
||
/// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`, | ||
/// authorized by a signed approval by `owner`. | ||
/// | ||
/// Emits a {Approval} event. | ||
function permit( | ||
address owner, | ||
address spender, | ||
uint256 value, | ||
uint256 deadline, | ||
uint8 v, | ||
bytes32 r, | ||
bytes32 s | ||
) public virtual { | ||
bytes32 nameHash = _constantNameHash(); | ||
// We simply calculate it on-the-fly to allow for cases where the `name` may change. | ||
if (nameHash == bytes32(0)) nameHash = keccak256(bytes(name())); | ||
/// @solidity memory-safe-assembly | ||
assembly { | ||
// Revert if the block timestamp is greater than `deadline`. | ||
if gt(timestamp(), deadline) { | ||
mstore(0x00, 0x1a15a3cc) // `PermitExpired()`. | ||
revert(0x1c, 0x04) | ||
} | ||
let m := mload(0x40) // Grab the free memory pointer. | ||
// Clean the upper 96 bits. | ||
owner := shr(96, shl(96, owner)) | ||
spender := shr(96, shl(96, spender)) | ||
// Compute the nonce slot and load its value. | ||
mstore(0x0e, _NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX) | ||
mstore(0x00, owner) | ||
let nonceSlot := keccak256(0x0c, 0x20) | ||
let nonceValue := sload(nonceSlot) | ||
// Prepare the domain separator. | ||
mstore(m, _DOMAIN_TYPEHASH) | ||
mstore(add(m, 0x20), nameHash) | ||
mstore(add(m, 0x40), _VERSION_HASH) | ||
mstore(add(m, 0x60), chainid()) | ||
mstore(add(m, 0x80), address()) | ||
mstore(0x2e, keccak256(m, 0xa0)) | ||
// Prepare the struct hash. | ||
mstore(m, _PERMIT_TYPEHASH) | ||
mstore(add(m, 0x20), owner) | ||
mstore(add(m, 0x40), spender) | ||
mstore(add(m, 0x60), value) | ||
mstore(add(m, 0x80), nonceValue) | ||
mstore(add(m, 0xa0), deadline) | ||
mstore(0x4e, keccak256(m, 0xc0)) | ||
// Prepare the ecrecover calldata. | ||
mstore(0x00, keccak256(0x2c, 0x42)) | ||
mstore(0x20, and(0xff, v)) | ||
mstore(0x40, r) | ||
mstore(0x60, s) | ||
let t := staticcall(gas(), 1, 0, 0x80, 0x20, 0x20) | ||
// If the ecrecover fails, the returndatasize will be 0x00, | ||
// `owner` will be checked if it equals the hash at 0x00, | ||
// which evaluates to false (i.e. 0), and we will revert. | ||
// If the ecrecover succeeds, the returndatasize will be 0x20, | ||
// `owner` will be compared against the returned address at 0x20. | ||
if iszero(eq(mload(returndatasize()), owner)) { | ||
mstore(0x00, 0xddafbaef) // `InvalidPermit()`. | ||
revert(0x1c, 0x04) | ||
} | ||
// Increment and store the updated nonce. | ||
sstore(nonceSlot, add(nonceValue, t)) // `t` is 1 if ecrecover succeeds. | ||
// Compute the allowance slot and store the value. | ||
// The `owner` is already at slot 0x20. | ||
mstore(0x40, or(shl(160, _ALLOWANCE_SLOT_SEED), spender)) | ||
sstore(keccak256(0x2c, 0x34), value) | ||
// Emit the {Approval} event. | ||
log3( | ||
add(m, 0x60), | ||
0x20, | ||
0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925, | ||
owner, | ||
spender | ||
) | ||
mstore(0x40, m) // Restore the free memory pointer. | ||
mstore(0x60, 0) // Restore the zero pointer. | ||
} | ||
} | ||
|
||
/// @dev Returns the EIP-712 domain separator for the EIP-2612 permit. | ||
function DOMAIN_SEPARATOR() public view virtual returns (bytes32 result) { | ||
bytes32 nameHash = _constantNameHash(); | ||
// We simply calculate it on-the-fly to allow for cases where the `name` may change. | ||
if (nameHash == bytes32(0)) nameHash = keccak256(bytes(name())); | ||
/// @solidity memory-safe-assembly | ||
assembly { | ||
let m := mload(0x40) // Grab the free memory pointer. | ||
mstore(m, _DOMAIN_TYPEHASH) | ||
mstore(add(m, 0x20), nameHash) | ||
mstore(add(m, 0x40), _VERSION_HASH) | ||
mstore(add(m, 0x60), chainid()) | ||
mstore(add(m, 0x80), address()) | ||
result := keccak256(m, 0xa0) | ||
} | ||
} | ||
} |
Oops, something went wrong.