Last active
November 10, 2023 21:04
-
-
Save z0r0z/b670a1808c05d6f0c8be6a585111371d to your computer and use it in GitHub Desktop.
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
// SPDX-License-Identifier: AGPL-3.0-only | |
pragma solidity ^0.8.19; | |
/// @notice Receiver mixin for ETH and safe-transferred ERC721 and ERC1155 tokens. | |
/// @author Solady (https://github.com/Vectorized/solady/blob/main/src/accounts/Receiver.sol) | |
/// | |
/// @dev Note: | |
/// - Handles all ERC721 and ERC1155 token safety callbacks. | |
/// - Collapses function table gas overhead and code size. | |
/// - Utilizes fallback so unknown calldata will pass on. | |
abstract contract Receiver { | |
/// @dev For receiving ETH. | |
receive() external payable virtual {} | |
/// @dev Fallback function with the `receiverFallback` modifier. | |
fallback() external payable virtual receiverFallback {} | |
/// @dev Modifier for the fallback function to handle token callbacks. | |
modifier receiverFallback() virtual { | |
/// @solidity memory-safe-assembly | |
assembly { | |
let s := shr(224, calldataload(0)) | |
// 0x150b7a02: `onERC721Received(address,address,uint256,bytes)`. | |
// 0xf23a6e61: `onERC1155Received(address,address,uint256,uint256,bytes)`. | |
// 0xbc197c81: `onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)`. | |
if or(eq(s, 0x150b7a02), or(eq(s, 0xf23a6e61), eq(s, 0xbc197c81))) { | |
mstore(0x20, s) // Store `msg.sig`. | |
return(0x3c, 0x20) // Return `msg.sig`. | |
} | |
} | |
_; | |
} | |
} | |
/// @notice Library for compressing and decompressing bytes. | |
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibZip.sol) | |
/// @author Calldata compression by clabby (https://github.com/clabby/op-kompressor) | |
/// @author FastLZ by ariya (https://github.com/ariya/FastLZ) | |
/// | |
/// @dev Note: | |
/// The accompanying solady.js library includes implementations of | |
/// FastLZ and calldata operations for convenience. | |
library LibZip { | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* CALLDATA OPERATIONS */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev To be called in the `receive` and `fallback` functions. | |
/// ``` | |
/// receive() external payable { LibZip.cdFallback(); } | |
/// fallback() external payable { LibZip.cdFallback(); } | |
/// ``` | |
/// For efficiency, this function will directly return the results, terminating the context. | |
/// If called internally, it must be called at the end of the function. | |
function cdFallback() internal { | |
assembly { | |
if iszero(calldatasize()) { return(calldatasize(), calldatasize()) } | |
let o := 0 | |
let f := not(3) // For negating the first 4 bytes. | |
for { let i := 0 } lt(i, calldatasize()) {} { | |
let c := byte(0, xor(add(i, f), calldataload(i))) | |
i := add(i, 1) | |
if iszero(c) { | |
let d := byte(0, xor(add(i, f), calldataload(i))) | |
i := add(i, 1) | |
// Fill with either 0xff or 0x00. | |
mstore(o, not(0)) | |
if iszero(gt(d, 0x7f)) { codecopy(o, codesize(), add(d, 1)) } | |
o := add(o, add(and(d, 0x7f), 1)) | |
continue | |
} | |
mstore8(o, c) | |
o := add(o, 1) | |
} | |
let success := delegatecall(gas(), address(), 0x00, o, codesize(), 0x00) | |
returndatacopy(0x00, 0x00, returndatasize()) | |
if iszero(success) { revert(0x00, returndatasize()) } | |
return(0x00, returndatasize()) | |
} | |
} | |
} | |
/// @notice Simple single owner authorization mixin. | |
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol) | |
/// | |
/// @dev Note: | |
/// This implementation does NOT auto-initialize the owner to `msg.sender`. | |
/// You MUST call the `_initializeOwner` in the constructor / initializer. | |
/// | |
/// While the ownable portion follows | |
/// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility, | |
/// the nomenclature for the 2-step ownership handover may be unique to this codebase. | |
abstract contract Ownable { | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* CUSTOM ERRORS */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev The caller is not authorized to call the function. | |
error Unauthorized(); | |
/// @dev The `newOwner` cannot be the zero address. | |
error NewOwnerIsZeroAddress(); | |
/// @dev The `pendingOwner` does not have a valid handover request. | |
error NoHandoverRequest(); | |
/// @dev Cannot double-initialize. | |
error AlreadyInitialized(); | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* EVENTS */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev The ownership is transferred from `oldOwner` to `newOwner`. | |
/// This event is intentionally kept the same as OpenZeppelin's Ownable to be | |
/// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173), | |
/// despite it not being as lightweight as a single argument event. | |
event OwnershipTransferred(address indexed oldOwner, address indexed newOwner); | |
/// @dev An ownership handover to `pendingOwner` has been requested. | |
event OwnershipHandoverRequested(address indexed pendingOwner); | |
/// @dev The ownership handover to `pendingOwner` has been canceled. | |
event OwnershipHandoverCanceled(address indexed pendingOwner); | |
/// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`. | |
uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE = | |
0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0; | |
/// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`. | |
uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE = | |
0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d; | |
/// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`. | |
uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE = | |
0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92; | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* STORAGE */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev The owner slot is given by: | |
/// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`. | |
/// It is intentionally chosen to be a high value | |
/// to avoid collision with lower slots. | |
/// The choice of manual storage layout is to enable compatibility | |
/// with both regular and upgradeable contracts. | |
bytes32 internal constant _OWNER_SLOT = | |
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927; | |
/// The ownership handover slot of `newOwner` is given by: | |
/// ``` | |
/// mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED)) | |
/// let handoverSlot := keccak256(0x00, 0x20) | |
/// ``` | |
/// It stores the expiry timestamp of the two-step ownership handover. | |
uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1; | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* INTERNAL FUNCTIONS */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev Override to return true to make `_initializeOwner` prevent double-initialization. | |
function _guardInitializeOwner() internal pure virtual returns (bool guard) {} | |
/// @dev Initializes the owner directly without authorization guard. | |
/// This function must be called upon initialization, | |
/// regardless of whether the contract is upgradeable or not. | |
/// This is to enable generalization to both regular and upgradeable contracts, | |
/// and to save gas in case the initial owner is not the caller. | |
/// For performance reasons, this function will not check if there | |
/// is an existing owner. | |
function _initializeOwner(address newOwner) internal virtual { | |
if (_guardInitializeOwner()) { | |
/// @solidity memory-safe-assembly | |
assembly { | |
let ownerSlot := _OWNER_SLOT | |
if sload(ownerSlot) { | |
mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`. | |
revert(0x1c, 0x04) | |
} | |
// Clean the upper 96 bits. | |
newOwner := shr(96, shl(96, newOwner)) | |
// Store the new value. | |
sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner)))) | |
// Emit the {OwnershipTransferred} event. | |
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner) | |
} | |
} else { | |
/// @solidity memory-safe-assembly | |
assembly { | |
// Clean the upper 96 bits. | |
newOwner := shr(96, shl(96, newOwner)) | |
// Store the new value. | |
sstore(_OWNER_SLOT, newOwner) | |
// Emit the {OwnershipTransferred} event. | |
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner) | |
} | |
} | |
} | |
/// @dev Sets the owner directly without authorization guard. | |
function _setOwner(address newOwner) internal virtual { | |
if (_guardInitializeOwner()) { | |
/// @solidity memory-safe-assembly | |
assembly { | |
let ownerSlot := _OWNER_SLOT | |
// Clean the upper 96 bits. | |
newOwner := shr(96, shl(96, newOwner)) | |
// Emit the {OwnershipTransferred} event. | |
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner) | |
// Store the new value. | |
sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner)))) | |
} | |
} else { | |
/// @solidity memory-safe-assembly | |
assembly { | |
let ownerSlot := _OWNER_SLOT | |
// Clean the upper 96 bits. | |
newOwner := shr(96, shl(96, newOwner)) | |
// Emit the {OwnershipTransferred} event. | |
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner) | |
// Store the new value. | |
sstore(ownerSlot, newOwner) | |
} | |
} | |
} | |
/// @dev Throws if the sender is not the owner. | |
function _checkOwner() internal view virtual { | |
/// @solidity memory-safe-assembly | |
assembly { | |
// If the caller is not the stored owner, revert. | |
if iszero(eq(caller(), sload(_OWNER_SLOT))) { | |
mstore(0x00, 0x82b42900) // `Unauthorized()`. | |
revert(0x1c, 0x04) | |
} | |
} | |
} | |
/// @dev Returns how long a two-step ownership handover is valid for in seconds. | |
/// Override to return a different value if needed. | |
/// Made internal to conserve bytecode. Wrap it in a public function if needed. | |
function _ownershipHandoverValidFor() internal view virtual returns (uint64) { | |
return 48 * 3600; | |
} | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* PUBLIC UPDATE FUNCTIONS */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev Allows the owner to transfer the ownership to `newOwner`. | |
function transferOwnership(address newOwner) public payable virtual onlyOwner { | |
/// @solidity memory-safe-assembly | |
assembly { | |
if iszero(shl(96, newOwner)) { | |
mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`. | |
revert(0x1c, 0x04) | |
} | |
} | |
_setOwner(newOwner); | |
} | |
/// @dev Allows the owner to renounce their ownership. | |
function renounceOwnership() public payable virtual onlyOwner { | |
_setOwner(address(0)); | |
} | |
/// @dev Request a two-step ownership handover to the caller. | |
/// The request will automatically expire in 48 hours (172800 seconds) by default. | |
function requestOwnershipHandover() public payable virtual { | |
unchecked { | |
uint256 expires = block.timestamp + _ownershipHandoverValidFor(); | |
/// @solidity memory-safe-assembly | |
assembly { | |
// Compute and set the handover slot to `expires`. | |
mstore(0x0c, _HANDOVER_SLOT_SEED) | |
mstore(0x00, caller()) | |
sstore(keccak256(0x0c, 0x20), expires) | |
// Emit the {OwnershipHandoverRequested} event. | |
log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller()) | |
} | |
} | |
} | |
/// @dev Cancels the two-step ownership handover to the caller, if any. | |
function cancelOwnershipHandover() public payable virtual { | |
/// @solidity memory-safe-assembly | |
assembly { | |
// Compute and set the handover slot to 0. | |
mstore(0x0c, _HANDOVER_SLOT_SEED) | |
mstore(0x00, caller()) | |
sstore(keccak256(0x0c, 0x20), 0) | |
// Emit the {OwnershipHandoverCanceled} event. | |
log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller()) | |
} | |
} | |
/// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`. | |
/// Reverts if there is no existing ownership handover requested by `pendingOwner`. | |
function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner { | |
/// @solidity memory-safe-assembly | |
assembly { | |
// Compute and set the handover slot to 0. | |
mstore(0x0c, _HANDOVER_SLOT_SEED) | |
mstore(0x00, pendingOwner) | |
let handoverSlot := keccak256(0x0c, 0x20) | |
// If the handover does not exist, or has expired. | |
if gt(timestamp(), sload(handoverSlot)) { | |
mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`. | |
revert(0x1c, 0x04) | |
} | |
// Set the handover slot to 0. | |
sstore(handoverSlot, 0) | |
} | |
_setOwner(pendingOwner); | |
} | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* PUBLIC READ FUNCTIONS */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev Returns the owner of the contract. | |
function owner() public view virtual returns (address result) { | |
/// @solidity memory-safe-assembly | |
assembly { | |
result := sload(_OWNER_SLOT) | |
} | |
} | |
/// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`. | |
function ownershipHandoverExpiresAt(address pendingOwner) | |
public | |
view | |
virtual | |
returns (uint256 result) | |
{ | |
/// @solidity memory-safe-assembly | |
assembly { | |
// Compute the handover slot. | |
mstore(0x0c, _HANDOVER_SLOT_SEED) | |
mstore(0x00, pendingOwner) | |
// Load the handover slot. | |
result := sload(keccak256(0x0c, 0x20)) | |
} | |
} | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* MODIFIERS */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev Marks a function as only callable by the owner. | |
modifier onlyOwner() virtual { | |
_checkOwner(); | |
_; | |
} | |
} | |
/// @notice UUPS proxy mixin. | |
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/UUPSUpgradeable.sol) | |
/// @author Modified from OpenZeppelin | |
/// (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/UUPSUpgradeable.sol) | |
/// | |
/// Note: | |
/// - This implementation is intended to be used with ERC1967 proxies. | |
/// See: `LibClone.deployERC1967` and related functions. | |
/// - This implementation is NOT compatible with legacy OpenZeppelin proxies | |
/// which do not store the implementation at `_ERC1967_IMPLEMENTATION_SLOT`. | |
abstract contract UUPSUpgradeable { | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* CUSTOM ERRORS */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev The upgrade failed. | |
error UpgradeFailed(); | |
/// @dev The call is from an unauthorized call context. | |
error UnauthorizedCallContext(); | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* IMMUTABLES */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev For checking if the context is a delegate call. | |
uint256 private immutable __self = uint256(uint160(address(this))); | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* EVENTS */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev Emitted when the proxy's implementation is upgraded. | |
event Upgraded(address indexed implementation); | |
/// @dev `keccak256(bytes("Upgraded(address)"))`. | |
uint256 private constant _UPGRADED_EVENT_SIGNATURE = | |
0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b; | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* STORAGE */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev The ERC-1967 storage slot for the implementation in the proxy. | |
/// `uint256(keccak256("eip1967.proxy.implementation")) - 1`. | |
bytes32 internal constant _ERC1967_IMPLEMENTATION_SLOT = | |
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* UUPS OPERATIONS */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev Please override this function to check if `msg.sender` is authorized | |
/// to upgrade the proxy to `newImplementation`, reverting if not. | |
/// ``` | |
/// function _authorizeUpgrade(address) internal override onlyOwner {} | |
/// ``` | |
function _authorizeUpgrade(address newImplementation) internal virtual; | |
/// @dev Returns the storage slot used by the implementation, | |
/// as specified in [ERC1822](https://eips.ethereum.org/EIPS/eip-1822). | |
/// | |
/// Note: The `notDelegated` modifier prevents accidental upgrades to | |
/// an implementation that is a proxy contract. | |
function proxiableUUID() public view virtual notDelegated returns (bytes32) { | |
// This function must always return `_ERC1967_IMPLEMENTATION_SLOT` to comply with ERC1967. | |
return _ERC1967_IMPLEMENTATION_SLOT; | |
} | |
/// @dev Upgrades the proxy's implementation to `newImplementation`. | |
/// Emits a {Upgraded} event. | |
/// | |
/// Note: The `onlyProxy` modifier prevents accidental calling on the implementation. | |
function upgradeTo(address newImplementation) public payable virtual onlyProxy { | |
_authorizeUpgrade(newImplementation); | |
/// @solidity memory-safe-assembly | |
assembly { | |
newImplementation := shr(96, shl(96, newImplementation)) // Clears upper 96 bits. | |
mstore(0x01, 0x52d1902d) // `proxiableUUID()`. | |
let s := _ERC1967_IMPLEMENTATION_SLOT | |
// Check if `newImplementation` implements `proxiableUUID` correctly. | |
if iszero(eq(mload(staticcall(gas(), newImplementation, 0x1d, 0x04, 0x01, 0x20)), s)) { | |
mstore(0x01, 0x55299b49) // `UpgradeFailed()`. | |
revert(0x1d, 0x04) | |
} | |
// Emit the {Upgraded} event. | |
log2(codesize(), 0x00, _UPGRADED_EVENT_SIGNATURE, newImplementation) | |
sstore(s, newImplementation) // Updates the implementation. | |
} | |
} | |
/// @dev Upgrades the proxy's implementation to `newImplementation`. | |
/// Emits a {Upgraded} event. | |
/// | |
/// Note: This function calls `upgradeTo` internally, | |
/// followed by a delegatecall to `newImplementation`. | |
function upgradeToAndCall(address newImplementation, bytes calldata data) | |
public | |
payable | |
virtual | |
{ | |
upgradeTo(newImplementation); | |
/// @solidity memory-safe-assembly | |
assembly { | |
// Forwards the `data` to `newImplementation` via delegatecall. | |
let m := mload(0x40) | |
calldatacopy(m, data.offset, data.length) | |
if iszero(delegatecall(gas(), newImplementation, m, data.length, codesize(), 0x00)) { | |
// Bubble up the revert if the call reverts. | |
returndatacopy(m, 0x00, returndatasize()) | |
revert(m, returndatasize()) | |
} | |
} | |
} | |
/// @dev Requires that the execution is performed through a proxy. | |
modifier onlyProxy() { | |
uint256 s = __self; | |
/// @solidity memory-safe-assembly | |
assembly { | |
// To enable use cases with an immutable default implementation in the bytecode, | |
// (see: ERC6551Proxy), we don't require that the proxy address must match the | |
// value stored in the implementation slot, which may not be initialized. | |
if eq(s, address()) { | |
mstore(0x00, 0x9f03a026) // `UnauthorizedCallContext()`. | |
revert(0x1c, 0x04) | |
} | |
} | |
_; | |
} | |
/// @dev Requires that the execution is NOT performed via delegatecall. | |
/// This is the opposite of `onlyProxy`. | |
modifier notDelegated() { | |
uint256 s = __self; | |
/// @solidity memory-safe-assembly | |
assembly { | |
if iszero(eq(s, address())) { | |
mstore(0x00, 0x9f03a026) // `UnauthorizedCallContext()`. | |
revert(0x1c, 0x04) | |
} | |
} | |
_; | |
} | |
} | |
/// @notice Contract for EIP-712 typed structured data hashing and signing. | |
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/EIP712.sol) | |
/// @author Modified from Solbase (https://github.com/Sol-DAO/solbase/blob/main/src/utils/EIP712.sol) | |
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol) | |
/// | |
/// @dev Note, this implementation: | |
/// - Uses `address(this)` for the `verifyingContract` field. | |
/// - Does NOT use the optional EIP-712 salt. | |
/// - Does NOT use any EIP-712 extensions. | |
/// This is for simplicity and to save gas. | |
/// If you need to customize, please fork / modify accordingly. | |
abstract contract EIP712 { | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* CONSTANTS AND IMMUTABLES */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. | |
bytes32 internal constant _DOMAIN_TYPEHASH = | |
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; | |
uint256 private immutable _cachedThis; | |
uint256 private immutable _cachedChainId; | |
bytes32 private immutable _cachedNameHash; | |
bytes32 private immutable _cachedVersionHash; | |
bytes32 private immutable _cachedDomainSeparator; | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* CONSTRUCTOR */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev Cache the hashes for cheaper runtime gas costs. | |
/// In the case of upgradeable contracts (i.e. proxies), | |
/// or if the chain id changes due to a hard fork, | |
/// the domain separator will be seamlessly calculated on-the-fly. | |
constructor() { | |
_cachedThis = uint256(uint160(address(this))); | |
_cachedChainId = block.chainid; | |
string memory name; | |
string memory version; | |
if (!_domainNameAndVersionMayChange()) (name, version) = _domainNameAndVersion(); | |
bytes32 nameHash = _domainNameAndVersionMayChange() ? bytes32(0) : keccak256(bytes(name)); | |
bytes32 versionHash = | |
_domainNameAndVersionMayChange() ? bytes32(0) : keccak256(bytes(version)); | |
_cachedNameHash = nameHash; | |
_cachedVersionHash = versionHash; | |
bytes32 separator; | |
if (!_domainNameAndVersionMayChange()) { | |
/// @solidity memory-safe-assembly | |
assembly { | |
let m := mload(0x40) // Load the free memory pointer. | |
mstore(m, _DOMAIN_TYPEHASH) | |
mstore(add(m, 0x20), nameHash) | |
mstore(add(m, 0x40), versionHash) | |
mstore(add(m, 0x60), chainid()) | |
mstore(add(m, 0x80), address()) | |
separator := keccak256(m, 0xa0) | |
} | |
} | |
_cachedDomainSeparator = separator; | |
} | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* FUNCTIONS TO OVERRIDE */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev Please override this function to return the domain name and version. | |
/// ``` | |
/// function _domainNameAndVersion() | |
/// internal | |
/// pure | |
/// virtual | |
/// returns (string memory name, string memory version) | |
/// { | |
/// name = "Solady"; | |
/// version = "1"; | |
/// } | |
/// ``` | |
/// | |
/// Note: If the returned result may change after the contract has been deployed, | |
/// you must override `_domainNameAndVersionMayChange()` to return true. | |
function _domainNameAndVersion() | |
internal | |
view | |
virtual | |
returns (string memory name, string memory version); | |
/// @dev Returns if `_domainNameAndVersion()` may change | |
/// after the contract has been deployed (i.e. after the constructor). | |
/// Default: false. | |
function _domainNameAndVersionMayChange() internal pure virtual returns (bool result) {} | |
/// @dev Returns the hash of the fully encoded EIP-712 message for this domain, | |
/// given `structHash`, as defined in | |
/// https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct. | |
/// | |
/// The hash can be used together with {ECDSA-recover} to obtain the signer of a message: | |
/// ``` | |
/// bytes32 digest = _hashTypedData(keccak256(abi.encode( | |
/// keccak256("Mail(address to,string contents)"), | |
/// mailTo, | |
/// keccak256(bytes(mailContents)) | |
/// ))); | |
/// address signer = ECDSA.recover(digest, signature); | |
/// ``` | |
function _hashTypedData(bytes32 structHash) internal view virtual returns (bytes32 digest) { | |
// We will use `digest` to store the domain separator to save a bit of gas. | |
if (_domainNameAndVersionMayChange()) { | |
digest = _buildDomainSeparator(); | |
} else { | |
digest = _cachedDomainSeparator; | |
if (_cachedDomainSeparatorInvalidated()) digest = _buildDomainSeparator(); | |
} | |
/// @solidity memory-safe-assembly | |
assembly { | |
// Compute the digest. | |
mstore(0x00, 0x1901000000000000) // Store "\x19\x01". | |
mstore(0x1a, digest) // Store the domain separator. | |
mstore(0x3a, structHash) // Store the struct hash. | |
digest := keccak256(0x18, 0x42) | |
// Restore the part of the free memory slot that was overwritten. | |
mstore(0x3a, 0) | |
} | |
} | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* EIP-5267 OPERATIONS */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev See: https://eips.ethereum.org/EIPS/eip-5267 | |
function eip712Domain() | |
public | |
view | |
virtual | |
returns ( | |
bytes1 fields, | |
string memory name, | |
string memory version, | |
uint256 chainId, | |
address verifyingContract, | |
bytes32 salt, | |
uint256[] memory extensions | |
) | |
{ | |
fields = hex"0f"; // `0b01111`. | |
(name, version) = _domainNameAndVersion(); | |
chainId = block.chainid; | |
verifyingContract = address(this); | |
salt = salt; // `bytes32(0)`. | |
extensions = extensions; // `new uint256[](0)`. | |
} | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* PRIVATE HELPERS */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev Returns the EIP-712 domain separator. | |
function _buildDomainSeparator() private view returns (bytes32 separator) { | |
// We will use `separator` to store the name hash to save a bit of gas. | |
bytes32 versionHash; | |
if (_domainNameAndVersionMayChange()) { | |
(string memory name, string memory version) = _domainNameAndVersion(); | |
separator = keccak256(bytes(name)); | |
versionHash = keccak256(bytes(version)); | |
} else { | |
separator = _cachedNameHash; | |
versionHash = _cachedVersionHash; | |
} | |
/// @solidity memory-safe-assembly | |
assembly { | |
let m := mload(0x40) // Load the free memory pointer. | |
mstore(m, _DOMAIN_TYPEHASH) | |
mstore(add(m, 0x20), separator) // Name hash. | |
mstore(add(m, 0x40), versionHash) | |
mstore(add(m, 0x60), chainid()) | |
mstore(add(m, 0x80), address()) | |
separator := keccak256(m, 0xa0) | |
} | |
} | |
/// @dev Returns if the cached domain separator has been invalidated. | |
function _cachedDomainSeparatorInvalidated() private view returns (bool result) { | |
uint256 cachedChainId = _cachedChainId; | |
uint256 cachedThis = _cachedThis; | |
/// @solidity memory-safe-assembly | |
assembly { | |
result := iszero(and(eq(chainid(), cachedChainId), eq(address(), cachedThis))) | |
} | |
} | |
} | |
/// @notice Signature verification helper that supports both ECDSA signatures from EOAs | |
/// and ERC1271 signatures from smart contract wallets like Argent and Gnosis safe. | |
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SignatureCheckerLib.sol) | |
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/SignatureChecker.sol) | |
/// | |
/// @dev Note: | |
/// - The signature checking functions use the ecrecover precompile (0x1). | |
/// - The `bytes memory signature` variants use the identity precompile (0x4) | |
/// to copy memory internally. | |
/// - Unlike ECDSA signatures, contract signatures are revocable. | |
/// - As of Solady version 0.0.134, all `bytes signature` variants accept both | |
/// regular 65-byte `(r, s, v)` and EIP-2098 `(r, vs)` short form signatures. | |
/// See: https://eips.ethereum.org/EIPS/eip-2098 | |
/// This is for calldata efficiency on smart accounts prevalent on L2s. | |
/// | |
/// WARNING! Do NOT use signatures as unique identifiers: | |
/// - Use a nonce in the digest to prevent replay attacks on the same contract. | |
/// - Use EIP-712 for the digest to prevent replay attacks across different chains and contracts. | |
/// EIP-712 also enables readable signing of typed data for better user safety. | |
/// This implementation does NOT check if a signature is non-malleable. | |
library SignatureCheckerLib { | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* SIGNATURE CHECKING OPERATIONS */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev Returns whether `signature` is valid for `signer` and `hash`. | |
/// If `signer` is a smart contract, the signature is validated with ERC1271. | |
/// Otherwise, the signature is validated with `ECDSA.recover`. | |
function isValidSignatureNowCalldata(address signer, bytes32 hash, bytes calldata signature) | |
internal | |
view | |
returns (bool isValid) | |
{ | |
/// @solidity memory-safe-assembly | |
assembly { | |
// Clean the upper 96 bits of `signer` in case they are dirty. | |
for { signer := shr(96, shl(96, signer)) } signer {} { | |
let m := mload(0x40) | |
mstore(0x00, hash) | |
if eq(signature.length, 64) { | |
let vs := calldataload(add(signature.offset, 0x20)) | |
mstore(0x20, add(shr(255, vs), 27)) // `v`. | |
mstore(0x40, calldataload(signature.offset)) // `r`. | |
mstore(0x60, shr(1, shl(1, vs))) // `s`. | |
let t := | |
staticcall( | |
gas(), // Amount of gas left for the transaction. | |
1, // Address of `ecrecover`. | |
0x00, // Start of input. | |
0x80, // Size of input. | |
0x01, // Start of output. | |
0x20 // Size of output. | |
) | |
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. | |
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { | |
isValid := 1 | |
mstore(0x60, 0) // Restore the zero slot. | |
mstore(0x40, m) // Restore the free memory pointer. | |
break | |
} | |
} | |
if eq(signature.length, 65) { | |
mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`. | |
calldatacopy(0x40, signature.offset, 0x40) // `r`, `s`. | |
let t := | |
staticcall( | |
gas(), // Amount of gas left for the transaction. | |
1, // Address of `ecrecover`. | |
0x00, // Start of input. | |
0x80, // Size of input. | |
0x01, // Start of output. | |
0x20 // Size of output. | |
) | |
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. | |
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { | |
isValid := 1 | |
mstore(0x60, 0) // Restore the zero slot. | |
mstore(0x40, m) // Restore the free memory pointer. | |
break | |
} | |
} | |
mstore(0x60, 0) // Restore the zero slot. | |
mstore(0x40, m) // Restore the free memory pointer. | |
let f := shl(224, 0x1626ba7e) | |
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. | |
mstore(add(m, 0x04), hash) | |
let d := add(m, 0x24) | |
mstore(d, 0x40) // The offset of the `signature` in the calldata. | |
mstore(add(m, 0x44), signature.length) | |
// Copy the `signature` over. | |
calldatacopy(add(m, 0x64), signature.offset, signature.length) | |
// forgefmt: disable-next-item | |
isValid := and( | |
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned). | |
eq(mload(d), f), | |
// Whether the staticcall does not revert. | |
// This must be placed at the end of the `and` clause, | |
// as the arguments are evaluated from right to left. | |
staticcall( | |
gas(), // Remaining gas. | |
signer, // The `signer` address. | |
m, // Offset of calldata in memory. | |
add(signature.length, 0x64), // Length of calldata in memory. | |
d, // Offset of returndata. | |
0x20 // Length of returndata to write. | |
) | |
) | |
break | |
} | |
} | |
} | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* HASHING OPERATIONS */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev Returns an Ethereum Signed Message, created from a `hash`. | |
/// This produces a hash corresponding to the one signed with the | |
/// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign) | |
/// JSON-RPC method as part of EIP-191. | |
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) { | |
/// @solidity memory-safe-assembly | |
assembly { | |
mstore(0x20, hash) // Store into scratch space for keccak256. | |
mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes. | |
result := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. | |
} | |
} | |
} | |
/// @notice ERC1271 mixin with nested EIP-712 approach. | |
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/accounts/ERC1271.sol) | |
abstract contract ERC1271 is EIP712 { | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* ERC1271 OPERATIONS */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev Returns the ERC1271 signer. | |
/// Override to return the signer `isValidSignature` checks against. | |
function _erc1271Signer() internal view virtual returns (address); | |
/// @dev Validates the signature with ERC1271 return, | |
/// so that this account can also be used as a signer. | |
/// | |
/// This implementation uses ECDSA recovery. It also uses a nested EIP-712 approach to | |
/// prevent signature replays when a single EOA owns multiple smart contract accounts, | |
/// while still enabling wallet UIs (e.g. Metamask) to show the EIP-712 values. | |
/// | |
/// For the nested EIP-712 workflow, the final hash will be: | |
/// ``` | |
/// keccak256(\x19\x01 || DOMAIN_SEP_A || | |
/// hashStruct(Parent({ | |
/// childHash: keccak256(\x19\x01 || DOMAIN_SEP_B || hashStruct(originalStruct)), | |
/// child: hashStruct(originalStruct) | |
/// })) | |
/// ) | |
/// ``` | |
/// where `||` denotes the concatenation operator for bytes. | |
/// The signature will be `r || s || v || PARENT_TYPEHASH || DOMAIN_SEP_B || child`. | |
/// | |
/// The `DOMAIN_SEP_B` and `child` will be used to verify if `childHash` is indeed correct. | |
/// | |
/// For the `personal_sign` workflow, the final hash will be: | |
/// ``` | |
/// keccak256(\x19\x01 || DOMAIN_SEP_A || | |
/// hashStruct(Parent({ | |
/// childHash: personalSign(someBytes) | |
/// })) | |
/// ) | |
/// ``` | |
/// where `||` denotes the concatenation operator for bytes. | |
/// The signature will be `r || s || v || PARENT_TYPEHASH`. | |
/// | |
/// For demo and typescript code, see: | |
/// - https://github.com/junomonster/nested-eip-712 | |
/// - https://github.com/frangio/eip712-wrapper-for-eip1271 | |
/// | |
/// Of course, if you are a wallet app maker and can update your app's UI at will, | |
/// you can choose a more minimalistic signature scheme like | |
/// `keccak256(abi.encode(address(this), hash))` instead of all these acrobatics. | |
/// All these are just for widespead out-of-the-box compatibility with other wallet apps. | |
/// | |
/// The `hash` parameter is the `childHash`. | |
function isValidSignature(bytes32 hash, bytes calldata signature) | |
public | |
view | |
virtual | |
returns (bytes4 result) | |
{ | |
/// @solidity memory-safe-assembly | |
assembly { | |
let m := mload(0x40) // Cache the free memory pointer. | |
let o := add(signature.offset, sub(signature.length, 0x60)) | |
calldatacopy(0x00, o, 0x60) // Copy the `DOMAIN_SEP_B` and child's structHash. | |
mstore(0x00, 0x1901) // Store the "\x19\x01" prefix, overwriting 0x00. | |
for {} 1 {} { | |
// Use the nested EIP-712 workflow if the reconstructed childHash matches, | |
// and the signature is at least 96 bytes long. | |
if iszero(or(xor(keccak256(0x1e, 0x42), hash), lt(signature.length, 0x60))) { | |
// Truncate the `signature.length` by 3 words (96 bytes). | |
signature.length := sub(signature.length, 0x60) | |
mstore(0x00, calldataload(o)) // Store the `PARENT_TYPEHASH`. | |
mstore(0x20, hash) // Store the `childHash`. | |
// The child's structHash is already at 0x40. | |
hash := keccak256(0x00, 0x60) // Compute the parent's structHash. | |
break | |
} | |
// Else, use the `personal_sign` workflow. | |
// Truncate the `signature.length` by 1 word (32 bytes), until zero. | |
signature.length := mul(gt(signature.length, 0x20), sub(signature.length, 0x20)) | |
// The `PARENT_TYPEHASH` is already at 0x40. | |
mstore(0x60, hash) // Store the `childHash`. | |
hash := keccak256(0x40, 0x40) // Compute the parent's structHash. | |
mstore(0x60, 0) // Restore the zero pointer. | |
break | |
} | |
mstore(0x40, m) // Restore the free memory pointer. | |
} | |
bool success = SignatureCheckerLib.isValidSignatureNowCalldata( | |
_erc1271Signer(), _hashTypedData(hash), signature | |
); | |
/// @solidity memory-safe-assembly | |
assembly { | |
// `success ? bytes4(keccak256("isValidSignature(bytes32,bytes)")) : 0xffffffff`. | |
result := shl(224, or(0x1626ba7e, sub(0, iszero(success)))) | |
} | |
} | |
} | |
/// @notice Simple ERC4337 account implementation. | |
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/accounts/ERC4337.sol) | |
/// @author Infinitism (https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/samples/SimpleAccount.sol) | |
/// | |
/// Recommended usage: | |
/// 1. Deploy the ERC4337 as an implementation contract, and verify it on Etherscan. | |
/// 2. Create a factory that uses `LibClone.deployERC1967` or | |
/// `LibClone.deployDeterministicERC1967` to clone the implementation. | |
/// See: `ERC4337Factory.sol`. | |
abstract contract ERC4337 is Ownable, UUPSUpgradeable, Receiver, ERC1271 { | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* STRUCTS */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev The ERC4337 user operation (userOp) struct. | |
struct UserOperation { | |
address sender; | |
uint256 nonce; | |
bytes initCode; | |
bytes callData; | |
uint256 callGasLimit; | |
uint256 verificationGasLimit; | |
uint256 preVerificationGas; | |
uint256 maxFeePerGas; | |
uint256 maxPriorityFeePerGas; | |
bytes paymasterAndData; | |
bytes signature; | |
} | |
/// @dev Call struct for the `executeBatch` function. | |
struct Call { | |
address target; | |
uint256 value; | |
bytes data; | |
} | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* INITIALIZER */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev Initializes the account with the owner. Can only be called once. | |
function initialize(address newOwner) public payable virtual { | |
_initializeOwner(newOwner); | |
} | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* ENTRY POINT */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev Returns the canonical ERC4337 EntryPoint contract. | |
/// Override this function to return a different EntryPoint. | |
function entryPoint() public view virtual returns (address) { | |
return 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789; | |
} | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* VALIDATION OPERATIONS */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev Validates the signature and nonce. | |
/// The EntryPoint will make the call to the recipient only if | |
/// this validation call returns successfully. | |
/// | |
/// Signature failure should be reported by returning 1 (see: `_validateSignature`). | |
/// This allows making a "simulation call" without a valid signature. | |
/// Other failures (e.g. nonce mismatch, or invalid signature format) | |
/// should still revert to signal failure. | |
function validateUserOp( | |
UserOperation calldata userOp, | |
bytes32 userOpHash, | |
uint256 missingAccountFunds | |
) | |
external | |
payable | |
virtual | |
onlyEntryPoint | |
payPrefund(missingAccountFunds) | |
returns (uint256 validationData) {} | |
/// @dev Validate `userOp.signature` for the `userOpHash`. | |
function _validateSignature(UserOperation calldata userOp, bytes32 userOpHash) | |
internal | |
virtual | |
returns (uint256 validationData) | |
{ | |
bool success = SignatureCheckerLib.isValidSignatureNowCalldata( | |
owner(), SignatureCheckerLib.toEthSignedMessageHash(userOpHash), userOp.signature | |
); | |
/// @solidity memory-safe-assembly | |
assembly { | |
// Returns 0 if the recovered address matches the owner. | |
// Else returns 1, which is equivalent to: | |
// `(success ? 0 : 1) | (uint256(validUntil) << 160) | (uint256(validAfter) << (160 + 48))` | |
// where `validUntil` is 0 (indefinite) and `validAfter` is 0. | |
validationData := iszero(success) | |
} | |
} | |
/// @dev Sends to the EntryPoint (i.e. `msg.sender`) the missing funds for this transaction. | |
/// Subclass MAY override this modifier for better funds management. | |
/// (e.g. send to the EntryPoint more than the minimum required, so that in future transactions | |
/// it will not be required to send again) | |
/// | |
/// `missingAccountFunds` is the minimum value this modifier should send the EntryPoint, | |
/// which MAY be zero, in case there is enough deposit, or the userOp has a paymaster. | |
modifier payPrefund(uint256 missingAccountFunds) virtual { | |
_; | |
/// @solidity memory-safe-assembly | |
assembly { | |
if missingAccountFunds { | |
// Ignore failure (it's EntryPoint's job to verify, not the account's). | |
pop(call(gas(), caller(), missingAccountFunds, codesize(), 0x00, codesize(), 0x00)) | |
} | |
} | |
} | |
/// @dev Requires that the caller is the EntryPoint. | |
modifier onlyEntryPoint() virtual { | |
if (msg.sender != entryPoint()) revert Unauthorized(); | |
_; | |
} | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* EXECUTION OPERATIONS */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev Execute a call from this account. | |
function execute(address target, uint256 value, bytes calldata data) | |
public | |
payable | |
virtual | |
onlyEntryPointOrOwner | |
returns (bytes memory result) | |
{ | |
/// @solidity memory-safe-assembly | |
assembly { | |
result := mload(0x40) | |
calldatacopy(result, data.offset, data.length) | |
if iszero(call(gas(), target, value, result, data.length, codesize(), 0x00)) { | |
// Bubble up the revert if the call reverts. | |
returndatacopy(result, 0x00, returndatasize()) | |
revert(result, returndatasize()) | |
} | |
mstore(result, returndatasize()) // Store the length. | |
let o := add(result, 0x20) | |
returndatacopy(o, 0x00, returndatasize()) // Copy the returndata. | |
mstore(0x40, add(o, returndatasize())) // Allocate the memory. | |
} | |
} | |
/// @dev Execute a sequence of calls from this account. | |
function executeBatch(Call[] calldata calls) | |
public | |
payable | |
virtual | |
onlyEntryPointOrOwner | |
returns (bytes[] memory results) | |
{ | |
/// @solidity memory-safe-assembly | |
assembly { | |
results := mload(0x40) | |
mstore(results, calls.length) | |
let r := add(0x20, results) | |
let m := add(r, shl(5, calls.length)) | |
calldatacopy(r, calls.offset, shl(5, calls.length)) | |
for { let end := m } iszero(eq(r, end)) { r := add(r, 0x20) } { | |
let e := add(calls.offset, mload(r)) | |
let o := add(e, calldataload(add(e, 0x40))) | |
calldatacopy(m, add(o, 0x20), calldataload(o)) | |
// forgefmt: disable-next-item | |
if iszero(call(gas(), calldataload(e), calldataload(add(e, 0x20)), | |
m, calldataload(o), codesize(), 0x00)) { | |
// Bubble up the revert if the call reverts. | |
returndatacopy(m, 0x00, returndatasize()) | |
revert(m, returndatasize()) | |
} | |
mstore(r, m) // Append `m` into `results`. | |
mstore(m, returndatasize()) // Store the length, | |
let p := add(m, 0x20) | |
returndatacopy(p, 0x00, returndatasize()) // and copy the returndata. | |
m := add(p, returndatasize()) // Advance `m`. | |
} | |
mstore(0x40, m) // Allocate the memory. | |
} | |
} | |
/// @dev Execute a delegatecall with `delegate` on this account. | |
function delegateExecute(address delegate, bytes calldata data) | |
public | |
payable | |
virtual | |
onlyEntryPointOrOwner | |
delegateExecuteGuard | |
returns (bytes memory result) | |
{ | |
/// @solidity memory-safe-assembly | |
assembly { | |
result := mload(0x40) | |
calldatacopy(result, data.offset, data.length) | |
// Forwards the `data` to `delegate` via delegatecall. | |
if iszero(delegatecall(gas(), delegate, result, data.length, codesize(), 0x00)) { | |
// Bubble up the revert if the call reverts. | |
returndatacopy(result, 0x00, returndatasize()) | |
revert(result, returndatasize()) | |
} | |
mstore(result, returndatasize()) // Store the length. | |
let o := add(result, 0x20) | |
returndatacopy(o, 0x00, returndatasize()) // Copy the returndata. | |
mstore(0x40, add(o, returndatasize())) // Allocate the memory. | |
} | |
} | |
/// @dev Ensures that the owner and implementation slots' values aren't changed. | |
/// You can override this modifier to ensure the sanctity of other storage slots too. | |
modifier delegateExecuteGuard() virtual { | |
bytes32 ownerSlotValue; | |
bytes32 implementationSlotValue; | |
/// @solidity memory-safe-assembly | |
assembly { | |
implementationSlotValue := sload(_ERC1967_IMPLEMENTATION_SLOT) | |
ownerSlotValue := sload(_OWNER_SLOT) | |
} | |
_; | |
/// @solidity memory-safe-assembly | |
assembly { | |
if iszero( | |
and( | |
eq(implementationSlotValue, sload(_ERC1967_IMPLEMENTATION_SLOT)), | |
eq(ownerSlotValue, sload(_OWNER_SLOT)) | |
) | |
) { revert(codesize(), 0x00) } | |
} | |
} | |
/// @dev Requires that the caller is the EntryPoint, the owner, or the account itself. | |
modifier onlyEntryPointOrOwner() virtual { | |
if (msg.sender != entryPoint()) _checkOwner(); | |
_; | |
} | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* DIRECT STORAGE OPERATIONS */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev Returns the raw storage value at `storageSlot`. | |
function storageLoad(bytes32 storageSlot) public view virtual returns (bytes32 result) { | |
/// @solidity memory-safe-assembly | |
assembly { | |
result := sload(storageSlot) | |
} | |
} | |
/// @dev Writes the raw storage value at `storageSlot`. | |
function storageStore(bytes32 storageSlot, bytes32 storageValue) | |
public | |
payable | |
virtual | |
onlyEntryPointOrOwner | |
storageStoreGuard(storageSlot) | |
{ | |
/// @solidity memory-safe-assembly | |
assembly { | |
sstore(storageSlot, storageValue) | |
} | |
} | |
/// @dev Ensures that the `storageSlot` is not prohibited for direct storage writes. | |
/// You can override this modifier to ensure the sanctity of other storage slots too. | |
modifier storageStoreGuard(bytes32 storageSlot) virtual { | |
/// @solidity memory-safe-assembly | |
assembly { | |
if or(eq(storageSlot, _OWNER_SLOT), eq(storageSlot, _ERC1967_IMPLEMENTATION_SLOT)) { | |
revert(codesize(), 0x00) | |
} | |
} | |
_; | |
} | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* DEPOSIT OPERATIONS */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev Returns the account's balance on the EntryPoint. | |
function getDeposit() public view virtual returns (uint256 result) { | |
address ep = entryPoint(); | |
/// @solidity memory-safe-assembly | |
assembly { | |
mstore(0x20, address()) // Store the `account` argument. | |
mstore(0x00, 0x70a08231) // `balanceOf(address)`. | |
result := | |
mul( // Returns 0 if the EntryPoint does not exist. | |
mload(0x20), | |
and( // The arguments of `and` are evaluated from right to left. | |
gt(returndatasize(), 0x1f), // At least 32 bytes returned. | |
staticcall(gas(), ep, 0x1c, 0x24, 0x20, 0x20) | |
) | |
) | |
} | |
} | |
/// @dev Deposit more funds for this account in the EntryPoint. | |
function addDeposit() public payable virtual { | |
address ep = entryPoint(); | |
/// @solidity memory-safe-assembly | |
assembly { | |
// The EntryPoint has balance accounting logic in the `receive()` function. | |
// forgefmt: disable-next-item | |
if iszero(mul(extcodesize(ep), call(gas(), ep, callvalue(), codesize(), 0x00, codesize(), 0x00))) { | |
revert(codesize(), 0x00) // For gas estimation. | |
} | |
} | |
} | |
/// @dev Withdraw ETH from the account's deposit on the EntryPoint. | |
function withdrawDepositTo(address to, uint256 amount) public payable virtual onlyOwner { | |
address ep = entryPoint(); | |
/// @solidity memory-safe-assembly | |
assembly { | |
mstore(0x14, to) // Store the `to` argument. | |
mstore(0x34, amount) // Store the `amount` argument. | |
mstore(0x00, 0x205c2878000000000000000000000000) // `withdrawTo(address,uint256)`. | |
if iszero(mul(extcodesize(ep), call(gas(), ep, 0, 0x10, 0x44, codesize(), 0x00))) { | |
returndatacopy(mload(0x40), 0x00, returndatasize()) | |
revert(mload(0x40), returndatasize()) | |
} | |
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten. | |
} | |
} | |
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | |
/* OVERRIDES */ | |
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | |
/// @dev Requires that the caller is the owner or the account itself. | |
/// This override affects the `onlyOwner` modifier. | |
function _checkOwner() internal view virtual override(Ownable) { | |
if (msg.sender != owner()) if (msg.sender != address(this)) revert Unauthorized(); | |
} | |
/// @dev To prevent double-initialization (reuses the owner storage slot for efficiency). | |
function _guardInitializeOwner() internal pure virtual override(Ownable) returns (bool) { | |
return true; | |
} | |
/// @dev Uses the `owner` as the ERC1271 signer. | |
function _erc1271Signer() internal view virtual override(ERC1271) returns (address) { | |
return owner(); | |
} | |
/// @dev To ensure that only the owner or the account itself can upgrade the implementation. | |
function _authorizeUpgrade(address) internal virtual override(UUPSUpgradeable) onlyOwner {} | |
/// @dev Handle token callbacks. If no token callback is triggered, | |
/// use `LibZip.cdFallback` for generalized calldata decompression. | |
/// If you don't need either, re-override this function. | |
fallback() external payable virtual override(Receiver) receiverFallback { | |
LibZip.cdFallback(); | |
} | |
} | |
/// @notice Simple extendable smart account implementation. | |
/// @author nani.eth (https://github.com/nanidao/account/blob/main/src/Account.sol) | |
contract Account is ERC4337 { | |
/// @dev Constructs | |
/// this implementation. | |
constructor() payable {} | |
/// @dev Returns domain name and | |
/// version of this implementation. | |
function _domainNameAndVersion() | |
internal | |
pure | |
virtual | |
override | |
returns (string memory, string memory) | |
{ | |
return ("NANI", "0.0.0"); | |
} | |
/// @dev Validates userOp | |
/// with auth logic flow. | |
function validateUserOp( | |
UserOperation calldata userOp, | |
bytes32 userOpHash, | |
uint256 missingAccountFunds | |
) | |
external | |
payable | |
virtual | |
override | |
onlyEntryPoint | |
payPrefund(missingAccountFunds) | |
returns (uint256 validationData) | |
{ | |
if (userOp.nonce < type(uint64).max) { | |
validationData = _validateSignature(userOp, userOpHash); | |
} else { | |
validationData = _validateUserOp(userOp, userOpHash, missingAccountFunds); | |
} | |
} | |
/// @dev Decodes `userOp.nonce` for a 'key'-stored authorizer account | |
/// that contains extended validation logic and auth for the `userOp`. | |
function _validateUserOp(UserOperation calldata, bytes32, uint256) | |
internal | |
virtual | |
returns (uint256 validationData) | |
{ | |
/// @solidity memory-safe-assembly | |
assembly { | |
calldatacopy(0x00, 0x00, calldatasize()) | |
if iszero( // Check authorizer in decoded `nonce` | |
call( // to forward calldata in `validateUserOp`. | |
gas(), | |
shr(96, /*authorizer*/ sload(shl(64, shr(64, /*nonce*/ calldataload(0x84))))), | |
0, | |
0x00, // Call. | |
calldatasize(), | |
0x00, // Return. | |
0x20 | |
) | |
) { | |
// Bubble up the revert if the call reverts. | |
returndatacopy(0x00, 0x00, returndatasize()) | |
revert(0x00, returndatasize()) | |
} | |
validationData := mload(0x00) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment