diff --git a/packages/backend/discovery/_templates/facet/EthscriptionsSafeModule/shape/EthscriptionsSafeModule.sol b/packages/backend/discovery/_templates/facet/EthscriptionsSafeModule/shape/EthscriptionsSafeModule.sol new file mode 100644 index 00000000000..f211943e1e5 --- /dev/null +++ b/packages/backend/discovery/_templates/facet/EthscriptionsSafeModule/shape/EthscriptionsSafeModule.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Unknown +pragma solidity 0.8.18; + +contract EthscriptionsSafeModule { + address public constant ethscriptionsProxyAddress = 0xeEd444Fc821b866b002f30f502C53e88E15d5095; + + function createEthscription(address to, string calldata dataURI) external { + require( + GnosisSafe(msg.sender).execTransactionFromModule( + ethscriptionsProxyAddress, + 0, + abi.encodeWithSignature( + "createEthscription(address,string)", + to, + dataURI + ), + Enum.Operation.DelegateCall + ), + "execTransactionFromModule failed" + ); + } + + function transferEthscription(address to, bytes32 ethscriptionId) external { + require( + GnosisSafe(msg.sender).execTransactionFromModule( + ethscriptionsProxyAddress, + 0, + abi.encodeWithSignature( + "transferEthscription(address,bytes32)", + to, + ethscriptionId + ), + Enum.Operation.DelegateCall + ), + "execTransactionFromModule failed" + ); + } +} \ No newline at end of file diff --git a/packages/backend/discovery/_templates/facet/EthscriptionsSafeModule/template.jsonc b/packages/backend/discovery/_templates/facet/EthscriptionsSafeModule/template.jsonc new file mode 100644 index 00000000000..df6c2aeb35b --- /dev/null +++ b/packages/backend/discovery/_templates/facet/EthscriptionsSafeModule/template.jsonc @@ -0,0 +1,5 @@ +{ + "$schema": "../../../../../discovery/schemas/contract.v2.schema.json", + "displayName": "EthscriptionsSafeModule", + "description": "Module that allows the Safe to interact with Ethscriptions." +} diff --git a/packages/backend/discovery/_templates/facet/EthscriptionsSafeProxy/shape/EthscriptionsSafeProxy.sol b/packages/backend/discovery/_templates/facet/EthscriptionsSafeProxy/shape/EthscriptionsSafeProxy.sol new file mode 100644 index 00000000000..3b7809317f0 --- /dev/null +++ b/packages/backend/discovery/_templates/facet/EthscriptionsSafeProxy/shape/EthscriptionsSafeProxy.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Unknown +pragma solidity 0.8.18; + +contract EthscriptionsSafeProxy { + address internal immutable deployedAddress; + + event ethscriptions_protocol_CreateEthscription( + address indexed initialOwner, + string contentURI + ); + + event ethscriptions_protocol_TransferEthscription( + address indexed recipient, + bytes32 indexed ethscriptionId + ); + + constructor() { + deployedAddress = address(this); + } + + function createEthscription(address to, string calldata dataURI) external { + require(deployedAddress != address(this), "Only Delegate Call"); + emit ethscriptions_protocol_CreateEthscription(to, dataURI); + } + + function transferEthscription(address to, bytes32 ethscriptionId) external { + require(deployedAddress != address(this), "Only Delegate Call"); + emit ethscriptions_protocol_TransferEthscription(to, ethscriptionId); + } +} \ No newline at end of file diff --git a/packages/backend/discovery/_templates/facet/EthscriptionsSafeProxy/template.jsonc b/packages/backend/discovery/_templates/facet/EthscriptionsSafeProxy/template.jsonc new file mode 100644 index 00000000000..e4451a71e33 --- /dev/null +++ b/packages/backend/discovery/_templates/facet/EthscriptionsSafeProxy/template.jsonc @@ -0,0 +1,5 @@ +{ + "$schema": "../../../../../discovery/schemas/contract.v2.schema.json", + "displayName": "EthscriptionsSafeProxy", + "description": "Helper of the Safe Module that allows to send Ethscriptions transactions." +} diff --git a/packages/backend/discovery/_templates/facet/FacetEtherBridge/shape/FacetEtherBridgeV6.sol b/packages/backend/discovery/_templates/facet/FacetEtherBridge/shape/FacetEtherBridgeV6.sol new file mode 100644 index 00000000000..bdb3ee48cbc --- /dev/null +++ b/packages/backend/discovery/_templates/facet/FacetEtherBridge/shape/FacetEtherBridgeV6.sol @@ -0,0 +1,3728 @@ +// SPDX-License-Identifier: Unknown +pragma solidity 0.8.24; + +library LibRLP { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* STRUCTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev A pointer to a RLP item list in memory. + struct List { + // Do NOT modify the `_data` directly. + uint256 _data; + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CREATE ADDRESS PREDICTION */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the address where a contract will be stored if deployed via + /// `deployer` with `nonce` using the `CREATE` opcode. + /// For the specification of the Recursive Length Prefix (RLP) + /// encoding scheme, please refer to p. 19 of the Ethereum Yellow Paper + /// (https://ethereum.github.io/yellowpaper/paper.pdf) + /// and the Ethereum Wiki (https://eth.wiki/fundamentals/rlp). + /// + /// Based on the EIP-161 (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md) + /// specification, all contract accounts on the Ethereum mainnet are initiated with + /// `nonce = 1`. Thus, the first contract address created by another contract + /// is calculated with a non-zero nonce. + /// + /// The theoretical allowed limit, based on EIP-2681 + /// (https://eips.ethereum.org/EIPS/eip-2681), for an account nonce is 2**64-2. + /// + /// Caution! This function will NOT check that the nonce is within the theoretical range. + /// This is for performance, as exceeding the range is extremely impractical. + /// It is the user's responsibility to ensure that the nonce is valid + /// (e.g. no dirty bits after packing / unpacking). + /// + /// This is equivalent to: + /// `address(uint160(uint256(keccak256(LibRLP.p(deployer).p(nonce).encode()))))`. + /// + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function computeAddress(address deployer, uint256 nonce) + internal + pure + returns (address deployed) + { + /// @solidity memory-safe-assembly + assembly { + for {} 1 {} { + // The integer zero is treated as an empty byte string, + // and as a result it only has a length prefix, 0x80, + // computed via `0x80 + 0`. + + // A one-byte integer in the [0x00, 0x7f] range uses its + // own value as a length prefix, + // there is no additional `0x80 + length` prefix that precedes it. + if iszero(gt(nonce, 0x7f)) { + mstore(0x00, deployer) + // Using `mstore8` instead of `or` naturally cleans + // any dirty upper bits of `deployer`. + mstore8(0x0b, 0x94) + mstore8(0x0a, 0xd6) + // `shl` 7 is equivalent to multiplying by 0x80. + mstore8(0x20, or(shl(7, iszero(nonce)), nonce)) + deployed := keccak256(0x0a, 0x17) + break + } + let i := 8 + // Just use a loop to generalize all the way with minimal bytecode size. + for {} shr(i, nonce) { i := add(i, 8) } {} + // `shr` 3 is equivalent to dividing by 8. + i := shr(3, i) + // Store in descending slot sequence to overlap the values correctly. + mstore(i, nonce) + mstore(0x00, shl(8, deployer)) + mstore8(0x1f, add(0x80, i)) + mstore8(0x0a, 0x94) + mstore8(0x09, add(0xd6, i)) + deployed := keccak256(0x09, add(0x17, i)) + break + } + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* RLP ENCODING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // Note: + // - addresses are treated like byte strings of length 20, agnostic of leading zero bytes. + // - uint256s are converted to byte strings, stripped of leading zero bytes, and encoded. + // - bools are converted to uint256s (`b ? 1 : 0`), then encoded with the uint256. + // - For bytes1 to bytes32, you must manually convert them to bytes memory + // with `abi.encodePacked(x)` before encoding. + + /// @dev Returns a new empty list. + function p() internal pure returns (List memory result) {} + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(uint256 x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(address x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(bool x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(bytes memory x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(List memory x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, uint256 x) internal pure returns (List memory result) { + result._data = x << 48; + _updateTail(list, result); + /// @solidity memory-safe-assembly + assembly { + // If `x` is too big, we cannot pack it inline with the node. + // We'll have to allocate a new slot for `x` and store the pointer to it in the node. + if shr(208, x) { + let m := mload(0x40) + mstore(m, x) + mstore(0x40, add(m, 0x20)) + mstore(result, shl(40, or(1, shl(8, m)))) + } + } + result = list; + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, address x) internal pure returns (List memory result) { + /// @solidity memory-safe-assembly + assembly { + mstore(result, shl(40, or(4, shl(8, x)))) + } + _updateTail(list, result); + result = list; + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, bool x) internal pure returns (List memory result) { + /// @solidity memory-safe-assembly + assembly { + mstore(result, shl(48, iszero(iszero(x)))) + } + _updateTail(list, result); + result = list; + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, bytes memory x) internal pure returns (List memory result) { + /// @solidity memory-safe-assembly + assembly { + mstore(result, shl(40, or(2, shl(8, x)))) + } + _updateTail(list, result); + result = list; + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, List memory x) internal pure returns (List memory result) { + /// @solidity memory-safe-assembly + assembly { + mstore(result, shl(40, or(3, shl(8, x)))) + } + _updateTail(list, result); + result = list; + } + + /// @dev Returns the RLP encoding of `list`. + function encode(List memory list) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + function encodeUint(x_, o_) -> _o { + _o := add(o_, 1) + if iszero(gt(x_, 0x7f)) { + mstore8(o_, or(shl(7, iszero(x_)), x_)) // Copy `x_`. + leave + } + let r_ := shl(7, lt(0xffffffffffffffffffffffffffffffff, x_)) + r_ := or(r_, shl(6, lt(0xffffffffffffffff, shr(r_, x_)))) + r_ := or(r_, shl(5, lt(0xffffffff, shr(r_, x_)))) + r_ := or(r_, shl(4, lt(0xffff, shr(r_, x_)))) + r_ := or(shr(3, r_), lt(0xff, shr(r_, x_))) + mstore8(o_, add(r_, 0x81)) // Store the prefix. + mstore(0x00, x_) + mstore(_o, mload(xor(31, r_))) // Copy `x_`. + _o := add(add(1, r_), _o) + } + function encodeAddress(x_, o_) -> _o { + _o := add(o_, 0x15) + mstore(o_, shl(88, x_)) + mstore8(o_, 0x94) + } + function encodeBytes(x_, o_, c_) -> _o { + _o := add(o_, 1) + let n_ := mload(x_) + if iszero(gt(n_, 55)) { + let f_ := mload(add(0x20, x_)) + if iszero(and(eq(1, n_), lt(byte(0, f_), 0x80))) { + mstore8(o_, add(n_, c_)) // Store the prefix. + mstore(add(0x21, o_), mload(add(0x40, x_))) + mstore(_o, f_) + _o := add(n_, _o) + leave + } + mstore(o_, f_) // Copy `x_`. + leave + } + returndatacopy(returndatasize(), returndatasize(), shr(32, n_)) + let r_ := add(1, add(lt(0xff, n_), add(lt(0xffff, n_), lt(0xffffff, n_)))) + mstore(o_, shl(248, add(r_, add(c_, 55)))) // Store the prefix. + // Copy `x`. + let i_ := add(r_, _o) + _o := add(i_, n_) + for { let d_ := sub(add(0x20, x_), i_) } 1 {} { + mstore(i_, mload(add(d_, i_))) + i_ := add(i_, 0x20) + if iszero(lt(i_, _o)) { break } + } + mstore(o_, or(mload(o_), shl(sub(248, shl(3, r_)), n_))) // Store the prefix. + } + function encodeList(l_, o_) -> _o { + if iszero(mload(l_)) { + mstore8(o_, 0xc0) + _o := add(o_, 1) + leave + } + let j_ := add(o_, 0x20) + for { let h_ := l_ } 1 {} { + h_ := and(mload(h_), 0xffffffffff) + if iszero(h_) { break } + let t_ := byte(26, mload(h_)) + if iszero(gt(t_, 1)) { + if iszero(t_) { + j_ := encodeUint(shr(48, mload(h_)), j_) + continue + } + j_ := encodeUint(mload(shr(48, mload(h_))), j_) + continue + } + if eq(t_, 2) { + j_ := encodeBytes(shr(48, mload(h_)), j_, 0x80) + continue + } + if eq(t_, 3) { + j_ := encodeList(shr(48, mload(h_)), j_) + continue + } + j_ := encodeAddress(shr(48, mload(h_)), j_) + } + let n_ := sub(j_, add(o_, 0x20)) + if iszero(gt(n_, 55)) { + mstore8(o_, add(n_, 0xc0)) // Store the prefix. + mstore(add(0x01, o_), mload(add(0x20, o_))) + mstore(add(0x21, o_), mload(add(0x40, o_))) + _o := add(n_, add(0x01, o_)) + leave + } + mstore(o_, n_) + _o := encodeBytes(o_, o_, 0xc0) + } + result := mload(0x40) + let begin := add(result, 0x20) + let end := encodeList(list, begin) + mstore(result, sub(end, begin)) // Store the length of `result`. + mstore(end, 0) // Zeroize the slot after `result`. + mstore(0x40, add(end, 0x20)) // Allocate memory for `result`. + } + } + + /// @dev Returns the RLP encoding of `x`. + function encode(uint256 x) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + for {} 1 {} { + result := mload(0x40) + if iszero(gt(x, 0x7f)) { + mstore(result, 1) // Store the length of `result`. + mstore(add(result, 0x20), shl(248, or(shl(7, iszero(x)), x))) // Copy `x`. + mstore(0x40, add(result, 0x40)) // Allocate memory for `result`. + break + } + let r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x)) + r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x)))) + r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) + r := or(r, shl(4, lt(0xffff, shr(r, x)))) + r := add(2, or(shr(3, r), lt(0xff, shr(r, x)))) + mstore(add(r, result), x) // Copy `x`. + mstore(add(result, 1), add(r, 0x7f)) // Store the prefix. + mstore(result, r) // Store the length of `result`. + mstore(add(r, add(result, 0x20)), 0) // Zeroize the slot after `result`. + mstore(0x40, add(result, 0x60)) // Allocate memory for `result`. + break + } + } + } + + /// @dev Returns the RLP encoding of `x`. + function encode(address x) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + mstore(result, 0x15) + let o := add(0x20, result) + mstore(o, shl(88, x)) + mstore8(o, 0x94) + mstore(0x40, add(0x20, o)) + } + } + + /// @dev Returns the RLP encoding of `x`. + function encode(bool x) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + mstore(result, 1) + mstore(add(0x20, result), shl(add(0xf8, mul(7, iszero(x))), 0x01)) + mstore(0x40, add(0x40, result)) + } + } + + /// @dev Returns the RLP encoding of `x`. + function encode(bytes memory x) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := x + + for {} iszero(and(eq(1, mload(x)), lt(byte(0, mload(add(x, 0x20))), 0x80))) {} { + result := mload(0x40) + let n := mload(x) // Length of `x`. + if iszero(gt(n, 55)) { + mstore(0x40, add(result, 0x60)) + mstore(add(0x41, result), mload(add(0x40, x))) + mstore(add(0x21, result), mload(add(0x20, x))) + mstore(add(1, result), add(n, 0x80)) // Store the prefix. + mstore(result, add(1, n)) // Store the length of `result`. + mstore(add(add(result, 0x21), n), 0) // Zeroize the slot after `result`. + break + } + returndatacopy(returndatasize(), returndatasize(), shr(32, n)) + let r := add(2, add(lt(0xff, n), add(lt(0xffff, n), lt(0xffffff, n)))) + // Copy `x`. + let i := add(r, add(0x20, result)) + let end := add(i, n) + for { let d := sub(add(0x20, x), i) } 1 {} { + mstore(i, mload(add(d, i))) + i := add(i, 0x20) + if iszero(lt(i, end)) { break } + } + mstore(add(r, result), n) // Store the prefix. + mstore(add(1, result), add(r, 0xb6)) // Store the prefix. + mstore(result, add(r, n)) // Store the length of `result`. + mstore(end, 0) // Zeroize the slot after `result`. + mstore(0x40, add(end, 0x20)) // Allocate memory. + break + } + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PRIVATE HELPERS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Updates the tail in `list`. + function _updateTail(List memory list, List memory result) private pure { + /// @solidity memory-safe-assembly + assembly { + let v := or(shr(mload(list), result), mload(list)) + let tail := shr(40, v) + mstore(list, xor(shl(40, xor(tail, result)), v)) // Update the tail. + mstore(tail, or(mload(tail), result)) // Make the previous tail point to `result`. + } + } +} + +library LibFacet { + using LibRLP for LibRLP.List; + + address constant facetInboxAddress = 0x00000000000000000000000000000000000FacE7; + bytes32 constant facetEventSignature = 0x00000000000000000000000000000000000000000000000000000000000face7; + uint8 constant facetTxType = 0x46; + + function sendFacetTransaction( + uint256 gasLimit, + bytes memory data + ) internal { + sendFacetTransaction({ + to: bytes(''), + value: 0, + gasLimit: gasLimit, + data: data, + mineBoost: bytes('') + }); + } + + function sendFacetTransaction( + address to, + uint256 gasLimit, + bytes memory data + ) internal { + sendFacetTransaction({ + to: abi.encodePacked(to), + value: 0, + gasLimit: gasLimit, + data: data, + mineBoost: bytes('') + }); + } + + function prepareFacetTransaction( + uint256 chainId, + bytes memory to, + uint256 value, + uint256 gasLimit, + bytes memory data, + bytes memory mineBoost + ) internal pure returns (bytes memory) { + LibRLP.List memory list; + + list.p(chainId); + list.p(to); + list.p(value); + list.p(gasLimit); + list.p(data); + list.p(mineBoost); + return abi.encodePacked(facetTxType, list.encode()); + } + + function prepareFacetTransaction( + bytes memory to, + uint256 value, + uint256 gasLimit, + bytes memory data, + bytes memory mineBoost + ) internal view returns (bytes memory) { + uint256 chainId; + + if (block.chainid == 1) { + chainId = 0xface7; + } else if (block.chainid == 11155111) { + chainId = 0xface7a; + } else { + revert("Unsupported chainId"); + } + + return prepareFacetTransaction({ + chainId: chainId, + to: to, + value: value, + gasLimit: gasLimit, + data: data, + mineBoost: mineBoost + }); + } + + function sendFacetTransaction( + bytes memory to, + uint256 value, + uint256 gasLimit, + bytes memory data, + bytes memory mineBoost + ) internal { + bytes memory payload = prepareFacetTransaction({ + to: to, + value: value, + gasLimit: gasLimit, + data: data, + mineBoost: mineBoost + }); + + assembly { + log1(add(payload, 32), mload(payload), facetEventSignature) + } + } +} + +library ERC1967FactoryConstants { + /// @dev The canonical ERC1967Factory address for EVM chains. + address internal constant ADDRESS = 0x0000000000006396FF2a80c067f99B3d2Ab4Df24; + + /// @dev The canonical ERC1967Factory bytecode for EVM chains. + /// Useful for forge tests: + /// `vm.etch(ADDRESS, BYTECODE)`. + bytes internal constant BYTECODE = + hex"6080604052600436106100b15760003560e01c8063545e7c611161006957806399a88ec41161004e57806399a88ec41461019d578063a97b90d5146101b0578063db4c545e146101c357600080fd5b8063545e7c61146101775780639623609d1461018a57600080fd5b80633729f9221161009a5780633729f922146101315780634314f120146101445780635414dff01461015757600080fd5b80631acfd02a146100b65780632abbef15146100d8575b600080fd5b3480156100c257600080fd5b506100d66100d1366004610604565b6101e6565b005b3480156100e457600080fd5b506101076100f3366004610637565b30600c908152600091909152602090205490565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b61010761013f366004610652565b610237565b6101076101523660046106d7565b61024e565b34801561016357600080fd5b50610107610172366004610738565b610267565b610107610185366004610604565b61029a565b6100d66101983660046106d7565b6102af565b6100d66101ab366004610604565b61035f565b6101076101be366004610751565b610370565b3480156101cf57600080fd5b506101d86103a9565b604051908152602001610128565b30600c52816000526020600c2033815414610209576382b429006000526004601cfd5b81905580827f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f600080a35050565b60006102468484843685610370565b949350505050565b600061025e8585838087876103c2565b95945050505050565b6000806102726103a9565b905060ff600053806035523060601b6001528260155260556000209150600060355250919050565b60006102a88383368461024e565b9392505050565b30600c5283600052336020600c2054146102d1576382b429006000526004601cfd5b6040518381527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc602082015281836040830137600080836040018334895af1610331573d610327576355299b496000526004601cfd5b3d6000803e3d6000fd5b5082847f5d611f318680d00598bb735d61bacf0c514c6b50e1e5ad30040a4df2b12791c7600080a350505050565b61036c82823660006102af565b5050565b60008360601c33148460601c151761039057632f6348366000526004601cfd5b61039f868686600187876103c2565b9695505050505050565b6000806103b461049c565b608960139091012092915050565b6000806103cd61049c565b90508480156103e757866089601384016000f592506103f3565b6089601383016000f092505b50816104075763301164256000526004601cfd5b8781527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc602082015282846040830137600080846040018334865af161045a573d6103275763301164256000526004601cfd5b30600c5281600052866020600c20558688837fc95935a66d15e0da5e412aca0ad27ae891d20b2fb91cf3994b6a3bf2b8178082600080a4509695505050505050565b6040513060701c801561054257666052573d6000fd607b8301527f3d356020355560408036111560525736038060403d373d3d355af43d6000803e60748301527f3735a920a3ca505d382bbc545af43d6000803e6052573d6000fd5b3d6000f35b60548301527f14605757363d3d37363d7f360894a13ba1a3210667c828492db98dca3e2076cc60348301523060148301526c607f3d8160093d39f33d3d337382525090565b66604c573d6000fd60758301527f3d3560203555604080361115604c5736038060403d373d3d355af43d6000803e606e8301527f3735a920a3ca505d382bbc545af43d6000803e604c573d6000fd5b3d6000f35b604e8301527f14605157363d3d37363d7f360894a13ba1a3210667c828492db98dca3e2076cc602e83015230600e8301526c60793d8160093d39f33d3d336d82525090565b803573ffffffffffffffffffffffffffffffffffffffff811681146105ff57600080fd5b919050565b6000806040838503121561061757600080fd5b610620836105db565b915061062e602084016105db565b90509250929050565b60006020828403121561064957600080fd5b6102a8826105db565b60008060006060848603121561066757600080fd5b610670846105db565b925061067e602085016105db565b9150604084013590509250925092565b60008083601f8401126106a057600080fd5b50813567ffffffffffffffff8111156106b857600080fd5b6020830191508360208285010111156106d057600080fd5b9250929050565b600080600080606085870312156106ed57600080fd5b6106f6856105db565b9350610704602086016105db565b9250604085013567ffffffffffffffff81111561072057600080fd5b61072c8782880161068e565b95989497509550505050565b60006020828403121561074a57600080fd5b5035919050565b60008060008060006080868803121561076957600080fd5b610772866105db565b9450610780602087016105db565b935060408601359250606086013567ffffffffffffffff8111156107a357600080fd5b6107af8882890161068e565b96999598509396509294939250505056fea26469706673582212200ac7c3ccbc2d311c48bf5465b021542e0e306fe3c462c060ba6a3d2f81ff6c5f64736f6c63430008130033"; + + /// @dev The initialization code used to deploy the canonical ERC1967Factory. + bytes internal constant INITCODE = abi.encodePacked( + hex"608060405234801561001057600080fd5b506107f6806100206000396000f3fe", BYTECODE + ); + + /// @dev For deterministic deployment via 0age's ImmutableCreate2Factory. + bytes32 internal constant SALT = + 0x0000000000000000000000000000000000000000e75e4f228818c80007508f33; +} + +library ECDSA { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The order of the secp256k1 elliptic curve. + uint256 internal constant N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141; + + /// @dev `N/2 + 1`. Used for checking the malleability of the signature. + uint256 private constant _HALF_N_PLUS_1 = + 0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a1; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The signature is invalid. + error InvalidSignature(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* RECOVERY OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. + function recover(bytes32 hash, bytes memory signature) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + for { let m := mload(0x40) } 1 { + mstore(0x00, 0x8baa579f) // `InvalidSignature()`. + revert(0x1c, 0x04) + } { + switch mload(signature) + case 64 { + let vs := mload(add(signature, 0x40)) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + } + case 65 { + mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. + mstore(0x60, mload(add(signature, 0x40))) // `s`. + } + default { continue } + mstore(0x00, hash) + mstore(0x40, mload(add(signature, 0x20))) // `r`. + result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if returndatasize() { break } + } + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. + function recoverCalldata(bytes32 hash, bytes calldata signature) + internal + view + returns (address result) + { + /// @solidity memory-safe-assembly + assembly { + for { let m := mload(0x40) } 1 { + mstore(0x00, 0x8baa579f) // `InvalidSignature()`. + revert(0x1c, 0x04) + } { + switch signature.length + case 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`. + } + case 65 { + mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`. + calldatacopy(0x40, signature.offset, 0x40) // Copy `r` and `s`. + } + default { continue } + mstore(0x00, hash) + result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if returndatasize() { break } + } + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, + /// and the EIP-2098 short form signature defined by `r` and `vs`. + function recover(bytes32 hash, bytes32 r, bytes32 vs) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, hash) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x40, r) + mstore(0x60, shr(1, shl(1, vs))) // `s`. + result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if iszero(returndatasize()) { + mstore(0x00, 0x8baa579f) // `InvalidSignature()`. + revert(0x1c, 0x04) + } + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, + /// and the signature defined by `v`, `r`, `s`. + function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) + internal + view + returns (address result) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, hash) + mstore(0x20, and(v, 0xff)) + mstore(0x40, r) + mstore(0x60, s) + result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if iszero(returndatasize()) { + mstore(0x00, 0x8baa579f) // `InvalidSignature()`. + revert(0x1c, 0x04) + } + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* TRY-RECOVER OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // WARNING! + // These functions will NOT revert upon recovery failure. + // Instead, they will return the zero address upon recovery failure. + // It is critical that the returned address is NEVER compared against + // a zero address (e.g. an uninitialized address variable). + + /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. + function tryRecover(bytes32 hash, bytes memory signature) + internal + view + returns (address result) + { + /// @solidity memory-safe-assembly + assembly { + for { let m := mload(0x40) } 1 {} { + switch mload(signature) + case 64 { + let vs := mload(add(signature, 0x40)) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + } + case 65 { + mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. + mstore(0x60, mload(add(signature, 0x40))) // `s`. + } + default { break } + mstore(0x00, hash) + mstore(0x40, mload(add(signature, 0x20))) // `r`. + pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + result := mload(xor(0x60, returndatasize())) + mstore(0x40, m) // Restore the free memory pointer. + break + } + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. + function tryRecoverCalldata(bytes32 hash, bytes calldata signature) + internal + view + returns (address result) + { + /// @solidity memory-safe-assembly + assembly { + for { let m := mload(0x40) } 1 {} { + switch signature.length + case 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`. + } + case 65 { + mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`. + calldatacopy(0x40, signature.offset, 0x40) // Copy `r` and `s`. + } + default { break } + mstore(0x00, hash) + pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + result := mload(xor(0x60, returndatasize())) + mstore(0x40, m) // Restore the free memory pointer. + break + } + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, + /// and the EIP-2098 short form signature defined by `r` and `vs`. + function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) + internal + view + returns (address result) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, hash) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x40, r) + mstore(0x60, shr(1, shl(1, vs))) // `s`. + pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + result := mload(xor(0x60, returndatasize())) + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, + /// and the signature defined by `v`, `r`, `s`. + function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) + internal + view + returns (address result) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, hash) + mstore(0x20, and(v, 0xff)) + mstore(0x40, r) + mstore(0x60, s) + pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + result := mload(xor(0x60, returndatasize())) + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* 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://ethereum.org/en/developers/docs/apis/json-rpc/#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`. + } + } + + /// @dev Returns an Ethereum Signed Message, created from `s`. + /// This produces a hash corresponding to the one signed with the + /// [`eth_sign`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign) + /// JSON-RPC method as part of EIP-191. + /// Note: Supports lengths of `s` up to 999999 bytes. + function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let sLength := mload(s) + let o := 0x20 + mstore(o, "\x19Ethereum Signed Message:\n") // 26 bytes, zero-right-padded. + mstore(0x00, 0x00) + // Convert the `s.length` to ASCII decimal representation: `base10(s.length)`. + for { let temp := sLength } 1 {} { + o := sub(o, 1) + mstore8(o, add(48, mod(temp, 10))) + temp := div(temp, 10) + if iszero(temp) { break } + } + let n := sub(0x3a, o) // Header length: `26 + 32 - o`. + // Throw an out-of-offset error (consumes all gas) if the header exceeds 32 bytes. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0x20)) + mstore(s, or(mload(0x00), mload(n))) // Temporarily store the header. + result := keccak256(add(s, sub(0x20, n)), add(n, sLength)) + mstore(s, sLength) // Restore the length. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CANONICAL HASH FUNCTIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // The following functions returns the hash of the signature in it's canonicalized format, + // which is the 65-byte `abi.encodePacked(r, s, uint8(v))`, where `v` is either 27 or 28. + // If `s` is greater than `N / 2` then it will be converted to `N - s` + // and the `v` value will be flipped. + // If the signature has an invalid length, or if `v` is invalid, + // a uniquely corrupt hash will be returned. + // These functions are useful for "poor-mans-VRF". + + /// @dev Returns the canonical hash of `signature`. + function canonicalHash(bytes memory signature) internal pure returns (bytes32 result) { + // @solidity memory-safe-assembly + assembly { + let l := mload(signature) + for {} 1 {} { + mstore(0x00, mload(add(signature, 0x20))) // `r`. + let s := mload(add(signature, 0x40)) + let v := mload(add(signature, 0x41)) + if eq(l, 64) { + v := add(shr(255, s), 27) + s := shr(1, shl(1, s)) + } + if iszero(lt(s, _HALF_N_PLUS_1)) { + v := xor(v, 7) + s := sub(N, s) + } + mstore(0x21, v) + mstore(0x20, s) + result := keccak256(0x00, 0x41) + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + break + } + + // If the length is neither 64 nor 65, return a uniquely corrupted hash. + if iszero(lt(sub(l, 64), 2)) { + // `bytes4(keccak256("InvalidSignatureLength"))`. + result := xor(keccak256(add(signature, 0x20), l), 0xd62f1ab2) + } + } + } + + /// @dev Returns the canonical hash of `signature`. + function canonicalHashCalldata(bytes calldata signature) + internal + pure + returns (bytes32 result) + { + // @solidity memory-safe-assembly + assembly { + for {} 1 {} { + mstore(0x00, calldataload(signature.offset)) // `r`. + let s := calldataload(add(signature.offset, 0x20)) + let v := calldataload(add(signature.offset, 0x21)) + if eq(signature.length, 64) { + v := add(shr(255, s), 27) + s := shr(1, shl(1, s)) + } + if iszero(lt(s, _HALF_N_PLUS_1)) { + v := xor(v, 7) + s := sub(N, s) + } + mstore(0x21, v) + mstore(0x20, s) + result := keccak256(0x00, 0x41) + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + break + } + // If the length is neither 64 nor 65, return a uniquely corrupted hash. + if iszero(lt(sub(signature.length, 64), 2)) { + calldatacopy(mload(0x40), signature.offset, signature.length) + // `bytes4(keccak256("InvalidSignatureLength"))`. + result := xor(keccak256(mload(0x40), signature.length), 0xd62f1ab2) + } + } + } + + /// @dev Returns the canonical hash of `signature`. + function canonicalHash(bytes32 r, bytes32 vs) internal pure returns (bytes32 result) { + // @solidity memory-safe-assembly + assembly { + mstore(0x00, r) // `r`. + let v := add(shr(255, vs), 27) + let s := shr(1, shl(1, vs)) + mstore(0x21, v) + mstore(0x20, s) + result := keccak256(0x00, 0x41) + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the canonical hash of `signature`. + function canonicalHash(uint8 v, bytes32 r, bytes32 s) internal pure returns (bytes32 result) { + // @solidity memory-safe-assembly + assembly { + mstore(0x00, r) // `r`. + if iszero(lt(s, _HALF_N_PLUS_1)) { + v := xor(v, 7) + s := sub(N, s) + } + mstore(0x21, v) + mstore(0x20, s) + result := keccak256(0x00, 0x41) + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* EMPTY CALLDATA HELPERS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns an empty calldata bytes. + function emptySignature() internal pure returns (bytes calldata signature) { + /// @solidity memory-safe-assembly + assembly { + signature.length := 0 + } + } +} + +library SafeTransferLib { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The ETH transfer has failed. + error ETHTransferFailed(); + + /// @dev The ERC20 `transferFrom` has failed. + error TransferFromFailed(); + + /// @dev The ERC20 `transfer` has failed. + error TransferFailed(); + + /// @dev The ERC20 `approve` has failed. + error ApproveFailed(); + + /// @dev The ERC20 `totalSupply` query has failed. + error TotalSupplyQueryFailed(); + + /// @dev The Permit2 operation has failed. + error Permit2Failed(); + + /// @dev The Permit2 amount must be less than `2**160 - 1`. + error Permit2AmountOverflow(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes. + uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300; + + /// @dev Suggested gas stipend for contract receiving ETH to perform a few + /// storage reads and writes, but low enough to prevent griefing. + uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000; + + /// @dev The unique EIP-712 domain domain separator for the DAI token contract. + bytes32 internal constant DAI_DOMAIN_SEPARATOR = + 0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7; + + /// @dev The address for the WETH9 contract on Ethereum mainnet. + address internal constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + + /// @dev The canonical Permit2 address. + /// [Github](https://github.com/Uniswap/permit2) + /// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3) + address internal constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ETH OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants. + // + // The regular variants: + // - Forwards all remaining gas to the target. + // - Reverts if the target reverts. + // - Reverts if the current contract has insufficient balance. + // + // The force variants: + // - Forwards with an optional gas stipend + // (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases). + // - If the target reverts, or if the gas stipend is exhausted, + // creates a temporary contract to force send the ETH via `SELFDESTRUCT`. + // Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758. + // - Reverts if the current contract has insufficient balance. + // + // The try variants: + // - Forwards with a mandatory gas stipend. + // - Instead of reverting, returns whether the transfer succeeded. + + /// @dev Sends `amount` (in wei) ETH to `to`. + function safeTransferETH(address to, uint256 amount) internal { + /// @solidity memory-safe-assembly + assembly { + if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Sends all the ETH in the current contract to `to`. + function safeTransferAllETH(address to) internal { + /// @solidity memory-safe-assembly + assembly { + // Transfer all the ETH and check if it succeeded or not. + if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`. + function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal { + /// @solidity memory-safe-assembly + assembly { + if lt(selfbalance(), amount) { + mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. + revert(0x1c, 0x04) + } + if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, to) // Store the address in scratch space. + mstore8(0x0b, 0x73) // Opcode `PUSH20`. + mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`. + if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation. + } + } + } + + /// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`. + function forceSafeTransferAllETH(address to, uint256 gasStipend) internal { + /// @solidity memory-safe-assembly + assembly { + if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, to) // Store the address in scratch space. + mstore8(0x0b, 0x73) // Opcode `PUSH20`. + mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`. + if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation. + } + } + } + + /// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`. + function forceSafeTransferETH(address to, uint256 amount) internal { + /// @solidity memory-safe-assembly + assembly { + if lt(selfbalance(), amount) { + mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. + revert(0x1c, 0x04) + } + if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, to) // Store the address in scratch space. + mstore8(0x0b, 0x73) // Opcode `PUSH20`. + mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`. + if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation. + } + } + } + + /// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`. + function forceSafeTransferAllETH(address to) internal { + /// @solidity memory-safe-assembly + assembly { + // forgefmt: disable-next-item + if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, to) // Store the address in scratch space. + mstore8(0x0b, 0x73) // Opcode `PUSH20`. + mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`. + if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation. + } + } + } + + /// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`. + function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend) + internal + returns (bool success) + { + /// @solidity memory-safe-assembly + assembly { + success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00) + } + } + + /// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`. + function trySafeTransferAllETH(address to, uint256 gasStipend) + internal + returns (bool success) + { + /// @solidity memory-safe-assembly + assembly { + success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00) + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ERC20 OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Sends `amount` of ERC20 `token` from `from` to `to`. + /// Reverts upon failure. + /// + /// The `from` account must have at least `amount` approved for + /// the current contract to manage. + function safeTransferFrom(address token, address from, address to, uint256 amount) internal { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, amount) // Store the `amount` argument. + mstore(0x40, to) // Store the `to` argument. + mstore(0x2c, shl(96, from)) // Store the `from` argument. + mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`. + let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20) + if iszero(and(eq(mload(0x00), 1), success)) { + if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) { + mstore(0x00, 0x7939f424) // `TransferFromFailed()`. + revert(0x1c, 0x04) + } + } + mstore(0x60, 0) // Restore the zero slot to zero. + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /// @dev Sends `amount` of ERC20 `token` from `from` to `to`. + /// + /// The `from` account must have at least `amount` approved for the current contract to manage. + function trySafeTransferFrom(address token, address from, address to, uint256 amount) + internal + returns (bool success) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, amount) // Store the `amount` argument. + mstore(0x40, to) // Store the `to` argument. + mstore(0x2c, shl(96, from)) // Store the `from` argument. + mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`. + success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20) + if iszero(and(eq(mload(0x00), 1), success)) { + success := lt(or(iszero(extcodesize(token)), returndatasize()), success) + } + mstore(0x60, 0) // Restore the zero slot to zero. + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /// @dev Sends all of ERC20 `token` from `from` to `to`. + /// Reverts upon failure. + /// + /// The `from` account must have their entire balance approved for the current contract to manage. + function safeTransferAllFrom(address token, address from, address to) + internal + returns (uint256 amount) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x40, to) // Store the `to` argument. + mstore(0x2c, shl(96, from)) // Store the `from` argument. + mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`. + // Read the balance, reverting upon failure. + if iszero( + and( // The arguments of `and` are evaluated from right to left. + gt(returndatasize(), 0x1f), // At least 32 bytes returned. + staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20) + ) + ) { + mstore(0x00, 0x7939f424) // `TransferFromFailed()`. + revert(0x1c, 0x04) + } + mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`. + amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it. + // Perform the transfer, reverting upon failure. + let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20) + if iszero(and(eq(mload(0x00), 1), success)) { + if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) { + mstore(0x00, 0x7939f424) // `TransferFromFailed()`. + revert(0x1c, 0x04) + } + } + mstore(0x60, 0) // Restore the zero slot to zero. + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /// @dev Sends `amount` of ERC20 `token` from the current contract to `to`. + /// Reverts upon failure. + function safeTransfer(address token, address to, uint256 amount) internal { + /// @solidity memory-safe-assembly + assembly { + mstore(0x14, to) // Store the `to` argument. + mstore(0x34, amount) // Store the `amount` argument. + mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`. + // Perform the transfer, reverting upon failure. + let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) + if iszero(and(eq(mload(0x00), 1), success)) { + if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) { + mstore(0x00, 0x90b8ec18) // `TransferFailed()`. + revert(0x1c, 0x04) + } + } + mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten. + } + } + + /// @dev Sends all of ERC20 `token` from the current contract to `to`. + /// Reverts upon failure. + function safeTransferAll(address token, address to) internal returns (uint256 amount) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`. + mstore(0x20, address()) // Store the address of the current contract. + // Read the balance, reverting upon failure. + if iszero( + and( // The arguments of `and` are evaluated from right to left. + gt(returndatasize(), 0x1f), // At least 32 bytes returned. + staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20) + ) + ) { + mstore(0x00, 0x90b8ec18) // `TransferFailed()`. + revert(0x1c, 0x04) + } + mstore(0x14, to) // Store the `to` argument. + amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it. + mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`. + // Perform the transfer, reverting upon failure. + let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) + if iszero(and(eq(mload(0x00), 1), success)) { + if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) { + mstore(0x00, 0x90b8ec18) // `TransferFailed()`. + revert(0x1c, 0x04) + } + } + mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten. + } + } + + /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract. + /// Reverts upon failure. + function safeApprove(address token, address to, uint256 amount) internal { + /// @solidity memory-safe-assembly + assembly { + mstore(0x14, to) // Store the `to` argument. + mstore(0x34, amount) // Store the `amount` argument. + mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`. + let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) + if iszero(and(eq(mload(0x00), 1), success)) { + if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) { + mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`. + revert(0x1c, 0x04) + } + } + mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten. + } + } + + /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract. + /// If the initial attempt to approve fails, attempts to reset the approved amount to zero, + /// then retries the approval again (some tokens, e.g. USDT, requires this). + /// Reverts upon failure. + function safeApproveWithRetry(address token, address to, uint256 amount) internal { + /// @solidity memory-safe-assembly + assembly { + mstore(0x14, to) // Store the `to` argument. + mstore(0x34, amount) // Store the `amount` argument. + mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`. + // Perform the approval, retrying upon failure. + let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) + if iszero(and(eq(mload(0x00), 1), success)) { + if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) { + mstore(0x34, 0) // Store 0 for the `amount`. + mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`. + pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval. + mstore(0x34, amount) // Store back the original `amount`. + // Retry the approval, reverting upon failure. + success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) + if iszero(and(eq(mload(0x00), 1), success)) { + // Check the `extcodesize` again just in case the token selfdestructs lol. + if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) { + mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`. + revert(0x1c, 0x04) + } + } + } + } + mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten. + } + } + + /// @dev Returns the amount of ERC20 `token` owned by `account`. + /// Returns zero if the `token` does not exist. + function balanceOf(address token, address account) internal view returns (uint256 amount) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x14, account) // Store the `account` argument. + mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`. + amount := + mul( // The arguments of `mul` are evaluated from right to left. + mload(0x20), + and( // The arguments of `and` are evaluated from right to left. + gt(returndatasize(), 0x1f), // At least 32 bytes returned. + staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20) + ) + ) + } + } + + /// @dev Returns the total supply of the `token`. + /// Reverts if the token does not exist or does not implement `totalSupply()`. + function totalSupply(address token) internal view returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, 0x18160ddd) // `totalSupply()`. + if iszero( + and(gt(returndatasize(), 0x1f), staticcall(gas(), token, 0x1c, 0x04, 0x00, 0x20)) + ) { + mstore(0x00, 0x54cd9435) // `TotalSupplyQueryFailed()`. + revert(0x1c, 0x04) + } + result := mload(0x00) + } + } + + /// @dev Sends `amount` of ERC20 `token` from `from` to `to`. + /// If the initial attempt fails, try to use Permit2 to transfer the token. + /// Reverts upon failure. + /// + /// The `from` account must have at least `amount` approved for the current contract to manage. + function safeTransferFrom2(address token, address from, address to, uint256 amount) internal { + if (!trySafeTransferFrom(token, from, to, amount)) { + permit2TransferFrom(token, from, to, amount); + } + } + + /// @dev Sends `amount` of ERC20 `token` from `from` to `to` via Permit2. + /// Reverts upon failure. + function permit2TransferFrom(address token, address from, address to, uint256 amount) + internal + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(add(m, 0x74), shr(96, shl(96, token))) + mstore(add(m, 0x54), amount) + mstore(add(m, 0x34), to) + mstore(add(m, 0x20), shl(96, from)) + // `transferFrom(address,address,uint160,address)`. + mstore(m, 0x36c78516000000000000000000000000) + let p := PERMIT2 + let exists := eq(chainid(), 1) + if iszero(exists) { exists := iszero(iszero(extcodesize(p))) } + if iszero( + and( + call(gas(), p, 0, add(m, 0x10), 0x84, codesize(), 0x00), + lt(iszero(extcodesize(token)), exists) // Token has code and Permit2 exists. + ) + ) { + mstore(0x00, 0x7939f4248757f0fd) // `TransferFromFailed()` or `Permit2AmountOverflow()`. + revert(add(0x18, shl(2, iszero(iszero(shr(160, amount))))), 0x04) + } + } + } + + /// @dev Permit a user to spend a given amount of + /// another user's tokens via native EIP-2612 permit if possible, falling + /// back to Permit2 if native permit fails or is not implemented on the token. + function permit2( + address token, + address owner, + address spender, + uint256 amount, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) internal { + bool success; + /// @solidity memory-safe-assembly + assembly { + for {} shl(96, xor(token, WETH9)) {} { + mstore(0x00, 0x3644e515) // `DOMAIN_SEPARATOR()`. + if iszero( + and( // The arguments of `and` are evaluated from right to left. + lt(iszero(mload(0x00)), eq(returndatasize(), 0x20)), // Returns 1 non-zero word. + // Gas stipend to limit gas burn for tokens that don't refund gas when + // an non-existing function is called. 5K should be enough for a SLOAD. + staticcall(5000, token, 0x1c, 0x04, 0x00, 0x20) + ) + ) { break } + // After here, we can be sure that token is a contract. + let m := mload(0x40) + mstore(add(m, 0x34), spender) + mstore(add(m, 0x20), shl(96, owner)) + mstore(add(m, 0x74), deadline) + if eq(mload(0x00), DAI_DOMAIN_SEPARATOR) { + mstore(0x14, owner) + mstore(0x00, 0x7ecebe00000000000000000000000000) // `nonces(address)`. + mstore(add(m, 0x94), staticcall(gas(), token, 0x10, 0x24, add(m, 0x54), 0x20)) + mstore(m, 0x8fcbaf0c000000000000000000000000) // `IDAIPermit.permit`. + // `nonces` is already at `add(m, 0x54)`. + // `1` is already stored at `add(m, 0x94)`. + mstore(add(m, 0xb4), and(0xff, v)) + mstore(add(m, 0xd4), r) + mstore(add(m, 0xf4), s) + success := call(gas(), token, 0, add(m, 0x10), 0x104, codesize(), 0x00) + break + } + mstore(m, 0xd505accf000000000000000000000000) // `IERC20Permit.permit`. + mstore(add(m, 0x54), amount) + mstore(add(m, 0x94), and(0xff, v)) + mstore(add(m, 0xb4), r) + mstore(add(m, 0xd4), s) + success := call(gas(), token, 0, add(m, 0x10), 0xe4, codesize(), 0x00) + break + } + } + if (!success) simplePermit2(token, owner, spender, amount, deadline, v, r, s); + } + + /// @dev Simple permit on the Permit2 contract. + function simplePermit2( + address token, + address owner, + address spender, + uint256 amount, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) internal { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, 0x927da105) // `allowance(address,address,address)`. + { + let addressMask := shr(96, not(0)) + mstore(add(m, 0x20), and(addressMask, owner)) + mstore(add(m, 0x40), and(addressMask, token)) + mstore(add(m, 0x60), and(addressMask, spender)) + mstore(add(m, 0xc0), and(addressMask, spender)) + } + let p := mul(PERMIT2, iszero(shr(160, amount))) + if iszero( + and( // The arguments of `and` are evaluated from right to left. + gt(returndatasize(), 0x5f), // Returns 3 words: `amount`, `expiration`, `nonce`. + staticcall(gas(), p, add(m, 0x1c), 0x64, add(m, 0x60), 0x60) + ) + ) { + mstore(0x00, 0x6b836e6b8757f0fd) // `Permit2Failed()` or `Permit2AmountOverflow()`. + revert(add(0x18, shl(2, iszero(p))), 0x04) + } + mstore(m, 0x2b67b570) // `Permit2.permit` (PermitSingle variant). + // `owner` is already `add(m, 0x20)`. + // `token` is already at `add(m, 0x40)`. + mstore(add(m, 0x60), amount) + mstore(add(m, 0x80), 0xffffffffffff) // `expiration = type(uint48).max`. + // `nonce` is already at `add(m, 0xa0)`. + // `spender` is already at `add(m, 0xc0)`. + mstore(add(m, 0xe0), deadline) + mstore(add(m, 0x100), 0x100) // `signature` offset. + mstore(add(m, 0x120), 0x41) // `signature` length. + mstore(add(m, 0x140), r) + mstore(add(m, 0x160), s) + mstore(add(m, 0x180), shl(248, v)) + if iszero( // Revert if token does not have code, or if the call fails. + mul(extcodesize(token), call(gas(), p, 0, add(m, 0x1c), 0x184, codesize(), 0x00))) { + mstore(0x00, 0x6b836e6b) // `Permit2Failed()`. + revert(0x1c, 0x04) + } + } + } +} + +library LibBytes { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* STRUCTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Goated bytes storage struct that totally MOGs, no cap, fr. + /// Uses less gas and bytecode than Solidity's native bytes storage. It's meta af. + /// Packs length with the first 31 bytes if <255 bytes, so it’s mad tight. + struct BytesStorage { + bytes32 _spacer; + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The constant returned when the `search` is not found in the bytes. + uint256 internal constant NOT_FOUND = type(uint256).max; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* BYTE STORAGE OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Sets the value of the bytes storage `$` to `s`. + function set(BytesStorage storage $, bytes memory s) internal { + /// @solidity memory-safe-assembly + assembly { + let n := mload(s) + let packed := or(0xff, shl(8, n)) + for { let i := 0 } 1 {} { + if iszero(gt(n, 0xfe)) { + i := 0x1f + packed := or(n, shl(8, mload(add(s, i)))) + if iszero(gt(n, i)) { break } + } + let o := add(s, 0x20) + mstore(0x00, $.slot) + for { let p := keccak256(0x00, 0x20) } 1 {} { + sstore(add(p, shr(5, i)), mload(add(o, i))) + i := add(i, 0x20) + if iszero(lt(i, n)) { break } + } + break + } + sstore($.slot, packed) + } + } + + /// @dev Sets the value of the bytes storage `$` to `s`. + function setCalldata(BytesStorage storage $, bytes calldata s) internal { + /// @solidity memory-safe-assembly + assembly { + let packed := or(0xff, shl(8, s.length)) + for { let i := 0 } 1 {} { + if iszero(gt(s.length, 0xfe)) { + i := 0x1f + packed := or(s.length, shl(8, shr(8, calldataload(s.offset)))) + if iszero(gt(s.length, i)) { break } + } + mstore(0x00, $.slot) + for { let p := keccak256(0x00, 0x20) } 1 {} { + sstore(add(p, shr(5, i)), calldataload(add(s.offset, i))) + i := add(i, 0x20) + if iszero(lt(i, s.length)) { break } + } + break + } + sstore($.slot, packed) + } + } + + /// @dev Sets the value of the bytes storage `$` to the empty bytes. + function clear(BytesStorage storage $) internal { + delete $._spacer; + } + + /// @dev Returns whether the value stored is `$` is the empty bytes "". + function isEmpty(BytesStorage storage $) internal view returns (bool) { + return uint256($._spacer) & 0xff == uint256(0); + } + + /// @dev Returns the length of the value stored in `$`. + function length(BytesStorage storage $) internal view returns (uint256 result) { + result = uint256($._spacer); + /// @solidity memory-safe-assembly + assembly { + let n := and(0xff, result) + result := or(mul(shr(8, result), eq(0xff, n)), mul(n, iszero(eq(0xff, n)))) + } + } + + /// @dev Returns the value stored in `$`. + function get(BytesStorage storage $) internal view returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + let o := add(result, 0x20) + let packed := sload($.slot) + let n := shr(8, packed) + for { let i := 0 } 1 {} { + if iszero(eq(or(packed, 0xff), packed)) { + mstore(o, packed) + n := and(0xff, packed) + i := 0x1f + if iszero(gt(n, i)) { break } + } + mstore(0x00, $.slot) + for { let p := keccak256(0x00, 0x20) } 1 {} { + mstore(add(o, i), sload(add(p, shr(5, i)))) + i := add(i, 0x20) + if iszero(lt(i, n)) { break } + } + break + } + mstore(result, n) // Store the length of the memory. + mstore(add(o, n), 0) // Zeroize the slot after the bytes. + mstore(0x40, add(add(o, n), 0x20)) // Allocate memory. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* BYTES OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns `subject` all occurrences of `needle` replaced with `replacement`. + function replace(bytes memory subject, bytes memory needle, bytes memory replacement) + internal + pure + returns (bytes memory result) + { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + let needleLen := mload(needle) + let replacementLen := mload(replacement) + let d := sub(result, subject) // Memory difference. + let i := add(subject, 0x20) // Subject bytes pointer. + mstore(0x00, add(i, mload(subject))) // End of subject. + if iszero(gt(needleLen, mload(subject))) { + let subjectSearchEnd := add(sub(mload(0x00), needleLen), 1) + let h := 0 // The hash of `needle`. + if iszero(lt(needleLen, 0x20)) { h := keccak256(add(needle, 0x20), needleLen) } + let s := mload(add(needle, 0x20)) + for { let m := shl(3, sub(0x20, and(needleLen, 0x1f))) } 1 {} { + let t := mload(i) + // Whether the first `needleLen % 32` bytes of `subject` and `needle` matches. + if iszero(shr(m, xor(t, s))) { + if h { + if iszero(eq(keccak256(i, needleLen), h)) { + mstore(add(i, d), t) + i := add(i, 1) + if iszero(lt(i, subjectSearchEnd)) { break } + continue + } + } + // Copy the `replacement` one word at a time. + for { let j := 0 } 1 {} { + mstore(add(add(i, d), j), mload(add(add(replacement, 0x20), j))) + j := add(j, 0x20) + if iszero(lt(j, replacementLen)) { break } + } + d := sub(add(d, replacementLen), needleLen) + if needleLen { + i := add(i, needleLen) + if iszero(lt(i, subjectSearchEnd)) { break } + continue + } + } + mstore(add(i, d), t) + i := add(i, 1) + if iszero(lt(i, subjectSearchEnd)) { break } + } + } + let end := mload(0x00) + let n := add(sub(d, add(result, 0x20)), end) + // Copy the rest of the bytes one word at a time. + for {} lt(i, end) { i := add(i, 0x20) } { mstore(add(i, d), mload(i)) } + let o := add(i, d) + mstore(o, 0) // Zeroize the slot after the bytes. + mstore(0x40, add(o, 0x20)) // Allocate memory. + mstore(result, n) // Store the length. + } + } + + /// @dev Returns the byte index of the first location of `needle` in `subject`, + /// needleing from left to right, starting from `from`. + /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found. + function indexOf(bytes memory subject, bytes memory needle, uint256 from) + internal + pure + returns (uint256 result) + { + /// @solidity memory-safe-assembly + assembly { + result := not(0) // Initialize to `NOT_FOUND`. + for { let subjectLen := mload(subject) } 1 {} { + if iszero(mload(needle)) { + result := from + if iszero(gt(from, subjectLen)) { break } + result := subjectLen + break + } + let needleLen := mload(needle) + let subjectStart := add(subject, 0x20) + + subject := add(subjectStart, from) + let end := add(sub(add(subjectStart, subjectLen), needleLen), 1) + let m := shl(3, sub(0x20, and(needleLen, 0x1f))) + let s := mload(add(needle, 0x20)) + + if iszero(and(lt(subject, end), lt(from, subjectLen))) { break } + + if iszero(lt(needleLen, 0x20)) { + for { let h := keccak256(add(needle, 0x20), needleLen) } 1 {} { + if iszero(shr(m, xor(mload(subject), s))) { + if eq(keccak256(subject, needleLen), h) { + result := sub(subject, subjectStart) + break + } + } + subject := add(subject, 1) + if iszero(lt(subject, end)) { break } + } + break + } + for {} 1 {} { + if iszero(shr(m, xor(mload(subject), s))) { + result := sub(subject, subjectStart) + break + } + subject := add(subject, 1) + if iszero(lt(subject, end)) { break } + } + break + } + } + } + + /// @dev Returns the byte index of the first location of `needle` in `subject`, + /// needleing from left to right. + /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found. + function indexOf(bytes memory subject, bytes memory needle) internal pure returns (uint256) { + return indexOf(subject, needle, 0); + } + + /// @dev Returns the byte index of the first location of `needle` in `subject`, + /// needleing from right to left, starting from `from`. + /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found. + function lastIndexOf(bytes memory subject, bytes memory needle, uint256 from) + internal + pure + returns (uint256 result) + { + /// @solidity memory-safe-assembly + assembly { + for {} 1 {} { + result := not(0) // Initialize to `NOT_FOUND`. + let needleLen := mload(needle) + if gt(needleLen, mload(subject)) { break } + let w := result + + let fromMax := sub(mload(subject), needleLen) + if iszero(gt(fromMax, from)) { from := fromMax } + + let end := add(add(subject, 0x20), w) + subject := add(add(subject, 0x20), from) + if iszero(gt(subject, end)) { break } + // As this function is not too often used, + // we shall simply use keccak256 for smaller bytecode size. + for { let h := keccak256(add(needle, 0x20), needleLen) } 1 {} { + if eq(keccak256(subject, needleLen), h) { + result := sub(subject, add(end, 1)) + break + } + subject := add(subject, w) // `sub(subject, 1)`. + if iszero(gt(subject, end)) { break } + } + break + } + } + } + + /// @dev Returns the byte index of the first location of `needle` in `subject`, + /// needleing from right to left. + /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found. + function lastIndexOf(bytes memory subject, bytes memory needle) + internal + pure + returns (uint256) + { + return lastIndexOf(subject, needle, type(uint256).max); + } + + /// @dev Returns true if `needle` is found in `subject`, false otherwise. + function contains(bytes memory subject, bytes memory needle) internal pure returns (bool) { + return indexOf(subject, needle) != NOT_FOUND; + } + + /// @dev Returns whether `subject` starts with `needle`. + function startsWith(bytes memory subject, bytes memory needle) + internal + pure + returns (bool result) + { + /// @solidity memory-safe-assembly + assembly { + let n := mload(needle) + // Just using keccak256 directly is actually cheaper. + let t := eq(keccak256(add(subject, 0x20), n), keccak256(add(needle, 0x20), n)) + result := lt(gt(n, mload(subject)), t) + } + } + + /// @dev Returns whether `subject` ends with `needle`. + function endsWith(bytes memory subject, bytes memory needle) + internal + pure + returns (bool result) + { + /// @solidity memory-safe-assembly + assembly { + let n := mload(needle) + let notInRange := gt(n, mload(subject)) + // `subject + 0x20 + max(subject.length - needle.length, 0)`. + let t := add(add(subject, 0x20), mul(iszero(notInRange), sub(mload(subject), n))) + // Just using keccak256 directly is actually cheaper. + result := gt(eq(keccak256(t, n), keccak256(add(needle, 0x20), n)), notInRange) + } + } + + /// @dev Returns `subject` repeated `times`. + function repeat(bytes memory subject, uint256 times) + internal + pure + returns (bytes memory result) + { + /// @solidity memory-safe-assembly + assembly { + let l := mload(subject) // Subject length. + if iszero(or(iszero(times), iszero(l))) { + result := mload(0x40) + subject := add(subject, 0x20) + let o := add(result, 0x20) + for {} 1 {} { + // Copy the `subject` one word at a time. + for { let j := 0 } 1 {} { + mstore(add(o, j), mload(add(subject, j))) + j := add(j, 0x20) + if iszero(lt(j, l)) { break } + } + o := add(o, l) + times := sub(times, 1) + if iszero(times) { break } + } + mstore(o, 0) // Zeroize the slot after the bytes. + mstore(0x40, add(o, 0x20)) // Allocate memory. + mstore(result, sub(o, add(result, 0x20))) // Store the length. + } + } + } + + /// @dev Returns a copy of `subject` sliced from `start` to `end` (exclusive). + /// `start` and `end` are byte offsets. + function slice(bytes memory subject, uint256 start, uint256 end) + internal + pure + returns (bytes memory result) + { + /// @solidity memory-safe-assembly + assembly { + let l := mload(subject) // Subject length. + if iszero(gt(l, end)) { end := l } + if iszero(gt(l, start)) { start := l } + if lt(start, end) { + result := mload(0x40) + let n := sub(end, start) + let i := add(subject, start) + let w := not(0x1f) + // Copy the `subject` one word at a time, backwards. + for { let j := and(add(n, 0x1f), w) } 1 {} { + mstore(add(result, j), mload(add(i, j))) + j := add(j, w) // `sub(j, 0x20)`. + if iszero(j) { break } + } + let o := add(add(result, 0x20), n) + mstore(o, 0) // Zeroize the slot after the bytes. + mstore(0x40, add(o, 0x20)) // Allocate memory. + mstore(result, n) // Store the length. + } + } + } + + /// @dev Returns a copy of `subject` sliced from `start` to the end of the bytes. + /// `start` is a byte offset. + function slice(bytes memory subject, uint256 start) + internal + pure + returns (bytes memory result) + { + result = slice(subject, start, type(uint256).max); + } + + /// @dev Returns a copy of `subject` sliced from `start` to `end` (exclusive). + /// `start` and `end` are byte offsets. Faster than Solidity's native slicing. + function sliceCalldata(bytes calldata subject, uint256 start, uint256 end) + internal + pure + returns (bytes calldata result) + { + /// @solidity memory-safe-assembly + assembly { + end := xor(end, mul(xor(end, subject.length), lt(subject.length, end))) + start := xor(start, mul(xor(start, subject.length), lt(subject.length, start))) + result.offset := add(subject.offset, start) + result.length := mul(lt(start, end), sub(end, start)) + } + } + + /// @dev Returns a copy of `subject` sliced from `start` to the end of the bytes. + /// `start` is a byte offset. Faster than Solidity's native slicing. + function sliceCalldata(bytes calldata subject, uint256 start) + internal + pure + returns (bytes calldata result) + { + /// @solidity memory-safe-assembly + assembly { + start := xor(start, mul(xor(start, subject.length), lt(subject.length, start))) + result.offset := add(subject.offset, start) + result.length := mul(lt(start, subject.length), sub(subject.length, start)) + } + } + + /// @dev Reduces the size of `subject` to `n`. + /// If `n` is greater than the size of `subject`, this will be a no-op. + function truncate(bytes memory subject, uint256 n) + internal + pure + returns (bytes memory result) + { + /// @solidity memory-safe-assembly + assembly { + result := subject + mstore(mul(lt(n, mload(result)), result), n) + } + } + + /// @dev Returns a copy of `subject`, with the length reduced to `n`. + /// If `n` is greater than the size of `subject`, this will be a no-op. + function truncatedCalldata(bytes calldata subject, uint256 n) + internal + pure + returns (bytes calldata result) + { + /// @solidity memory-safe-assembly + assembly { + result.offset := subject.offset + result.length := xor(n, mul(xor(n, subject.length), lt(subject.length, n))) + } + } + + /// @dev Returns all the indices of `needle` in `subject`. + /// The indices are byte offsets. + function indicesOf(bytes memory subject, bytes memory needle) + internal + pure + returns (uint256[] memory result) + { + /// @solidity memory-safe-assembly + assembly { + let searchLen := mload(needle) + if iszero(gt(searchLen, mload(subject))) { + result := mload(0x40) + let i := add(subject, 0x20) + let o := add(result, 0x20) + let subjectSearchEnd := add(sub(add(i, mload(subject)), searchLen), 1) + let h := 0 // The hash of `needle`. + if iszero(lt(searchLen, 0x20)) { h := keccak256(add(needle, 0x20), searchLen) } + let s := mload(add(needle, 0x20)) + for { let m := shl(3, sub(0x20, and(searchLen, 0x1f))) } 1 {} { + let t := mload(i) + // Whether the first `searchLen % 32` bytes of `subject` and `needle` matches. + if iszero(shr(m, xor(t, s))) { + if h { + if iszero(eq(keccak256(i, searchLen), h)) { + i := add(i, 1) + if iszero(lt(i, subjectSearchEnd)) { break } + continue + } + } + mstore(o, sub(i, add(subject, 0x20))) // Append to `result`. + o := add(o, 0x20) + i := add(i, searchLen) // Advance `i` by `searchLen`. + if searchLen { + if iszero(lt(i, subjectSearchEnd)) { break } + continue + } + } + i := add(i, 1) + if iszero(lt(i, subjectSearchEnd)) { break } + } + mstore(result, shr(5, sub(o, add(result, 0x20)))) // Store the length of `result`. + // Allocate memory for result. + // We allocate one more word, so this array can be recycled for {split}. + mstore(0x40, add(o, 0x20)) + } + } + } + + /// @dev Returns a arrays of bytess based on the `delimiter` inside of the `subject` bytes. + function split(bytes memory subject, bytes memory delimiter) + internal + pure + returns (bytes[] memory result) + { + uint256[] memory indices = indicesOf(subject, delimiter); + /// @solidity memory-safe-assembly + assembly { + let w := not(0x1f) + let indexPtr := add(indices, 0x20) + let indicesEnd := add(indexPtr, shl(5, add(mload(indices), 1))) + mstore(add(indicesEnd, w), mload(subject)) + mstore(indices, add(mload(indices), 1)) + for { let prevIndex := 0 } 1 {} { + let index := mload(indexPtr) + mstore(indexPtr, 0x60) + if iszero(eq(index, prevIndex)) { + let element := mload(0x40) + let l := sub(index, prevIndex) + mstore(element, l) // Store the length of the element. + // Copy the `subject` one word at a time, backwards. + for { let o := and(add(l, 0x1f), w) } 1 {} { + mstore(add(element, o), mload(add(add(subject, prevIndex), o))) + o := add(o, w) // `sub(o, 0x20)`. + if iszero(o) { break } + } + mstore(add(add(element, 0x20), l), 0) // Zeroize the slot after the bytes. + // Allocate memory for the length and the bytes, rounded up to a multiple of 32. + mstore(0x40, add(element, and(add(l, 0x3f), w))) + mstore(indexPtr, element) // Store the `element` into the array. + } + prevIndex := add(index, mload(delimiter)) + indexPtr := add(indexPtr, 0x20) + if iszero(lt(indexPtr, indicesEnd)) { break } + } + result := indices + if iszero(mload(delimiter)) { + result := add(indices, 0x20) + mstore(result, sub(mload(indices), 2)) + } + } + } + + /// @dev Returns a concatenated bytes of `a` and `b`. + /// Cheaper than `bytes.concat()` and does not de-align the free memory pointer. + function concat(bytes memory a, bytes memory b) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + let w := not(0x1f) + let aLen := mload(a) + // Copy `a` one word at a time, backwards. + for { let o := and(add(aLen, 0x20), w) } 1 {} { + mstore(add(result, o), mload(add(a, o))) + o := add(o, w) // `sub(o, 0x20)`. + if iszero(o) { break } + } + let bLen := mload(b) + let output := add(result, aLen) + // Copy `b` one word at a time, backwards. + for { let o := and(add(bLen, 0x20), w) } 1 {} { + mstore(add(output, o), mload(add(b, o))) + o := add(o, w) // `sub(o, 0x20)`. + if iszero(o) { break } + } + let totalLen := add(aLen, bLen) + let last := add(add(result, 0x20), totalLen) + mstore(last, 0) // Zeroize the slot after the bytes. + mstore(result, totalLen) // Store the length. + mstore(0x40, add(last, 0x20)) // Allocate memory. + } + } + + /// @dev Returns whether `a` equals `b`. + function eq(bytes memory a, bytes memory b) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + result := eq(keccak256(add(a, 0x20), mload(a)), keccak256(add(b, 0x20), mload(b))) + } + } + + /// @dev Returns whether `a` equals `b`, where `b` is a null-terminated small bytes. + function eqs(bytes memory a, bytes32 b) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + // These should be evaluated on compile time, as far as possible. + let m := not(shl(7, div(not(iszero(b)), 255))) // `0x7f7f ...`. + let x := not(or(m, or(b, add(m, and(b, m))))) + let r := shl(7, iszero(iszero(shr(128, x)))) + r := or(r, shl(6, iszero(iszero(shr(64, shr(r, x)))))) + r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) + r := or(r, shl(4, lt(0xffff, shr(r, x)))) + r := or(r, shl(3, lt(0xff, shr(r, x)))) + // forgefmt: disable-next-item + result := gt(eq(mload(a), add(iszero(x), xor(31, shr(3, r)))), + xor(shr(add(8, r), b), shr(add(8, r), mload(add(a, 0x20))))) + } + } + + /// @dev Directly returns `a` without copying. + function directReturn(bytes memory a) internal pure { + assembly { + // Assumes that the bytes does not start from the scratch space. + let retStart := sub(a, 0x20) + let retUnpaddedSize := add(mload(a), 0x40) + // Right pad with zeroes. Just in case the bytes is produced + // by a method that doesn't zero right pad. + mstore(add(retStart, retUnpaddedSize), 0) + mstore(retStart, 0x20) // Store the return offset. + // End the transaction, returning the bytes. + return(retStart, and(not(0x1f), add(0x1f, retUnpaddedSize))) + } + } + + /// @dev Directly returns `a` with minimal copying. + function directReturn(bytes[] memory a) internal pure { + assembly { + let n := mload(a) // `a.length`. + let o := add(a, 0x20) // Start of elements in `a`. + let u := a // Highest memory slot. + let w := not(0x1f) + for { let i := 0 } iszero(eq(i, n)) { i := add(i, 1) } { + let c := add(o, shl(5, i)) // Location of pointer to `a[i]`. + let s := mload(c) // `a[i]`. + let l := mload(s) // `a[i].length`. + let r := and(l, 0x1f) // `a[i].length % 32`. + let z := add(0x20, and(l, w)) // Offset of last word in `a[i]` from `s`. + // If `s` comes before `o`, or `s` is not zero right padded. + if iszero(lt(lt(s, o), or(iszero(r), iszero(shl(shl(3, r), mload(add(s, z))))))) { + let m := mload(0x40) + mstore(m, l) // Copy `a[i].length`. + for {} 1 {} { + mstore(add(m, z), mload(add(s, z))) // Copy `a[i]`, backwards. + z := add(z, w) // `sub(z, 0x20)`. + if iszero(z) { break } + } + let e := add(add(m, 0x20), l) + mstore(e, 0) // Zeroize the slot after the copied bytes. + mstore(0x40, add(e, 0x20)) // Allocate memory. + s := m + } + mstore(c, sub(s, o)) // Convert to calldata offset. + let t := add(l, add(s, 0x20)) + if iszero(lt(t, u)) { u := t } + } + let retStart := add(a, w) // Assumes `a` doesn't start from scratch space. + mstore(retStart, 0x20) // Store the return offset. + return(retStart, add(0x40, sub(u, retStart))) // End the transaction. + } + } + + /// @dev Returns the word at `offset`, without any bounds checks. + /// To load an address, you can use `address(bytes20(load(a, offset)))`. + function load(bytes memory a, uint256 offset) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(add(add(a, 0x20), offset)) + } + } + + /// @dev Returns the word at `offset`, without any bounds checks. + /// To load an address, you can use `address(bytes20(loadCalldata(a, offset)))`. + function loadCalldata(bytes calldata a, uint256 offset) + internal + pure + returns (bytes32 result) + { + /// @solidity memory-safe-assembly + assembly { + result := calldataload(add(a.offset, offset)) + } + } + + /// @dev Returns empty calldata bytes. For silencing the compiler. + function emptyCalldata() internal pure returns (bytes calldata result) { + /// @solidity memory-safe-assembly + assembly { + result.length := 0 + } + } +} + +library LibString { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* STRUCTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Goated string storage struct that totally MOGs, no cap, fr. + /// Uses less gas and bytecode than Solidity's native string storage. It's meta af. + /// Packs length with the first 31 bytes if <255 bytes, so it’s mad tight. + struct StringStorage { + bytes32 _spacer; + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The length of the output is too small to contain all the hex digits. + error HexLengthInsufficient(); + + /// @dev The length of the string is more than 32 bytes. + error TooBigForSmallString(); + + /// @dev The input string must be a 7-bit ASCII. + error StringNot7BitASCII(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The constant returned when the `search` is not found in the string. + uint256 internal constant NOT_FOUND = type(uint256).max; + + /// @dev Lookup for '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'. + uint128 internal constant ALPHANUMERIC_7_BIT_ASCII = 0x7fffffe07fffffe03ff000000000000; + + /// @dev Lookup for 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'. + uint128 internal constant LETTERS_7_BIT_ASCII = 0x7fffffe07fffffe0000000000000000; + + /// @dev Lookup for 'abcdefghijklmnopqrstuvwxyz'. + uint128 internal constant LOWERCASE_7_BIT_ASCII = 0x7fffffe000000000000000000000000; + + /// @dev Lookup for 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'. + uint128 internal constant UPPERCASE_7_BIT_ASCII = 0x7fffffe0000000000000000; + + /// @dev Lookup for '0123456789'. + uint128 internal constant DIGITS_7_BIT_ASCII = 0x3ff000000000000; + + /// @dev Lookup for '0123456789abcdefABCDEF'. + uint128 internal constant HEXDIGITS_7_BIT_ASCII = 0x7e0000007e03ff000000000000; + + /// @dev Lookup for '01234567'. + uint128 internal constant OCTDIGITS_7_BIT_ASCII = 0xff000000000000; + + /// @dev Lookup for '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'. + uint128 internal constant PRINTABLE_7_BIT_ASCII = 0x7fffffffffffffffffffffff00003e00; + + /// @dev Lookup for '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'. + uint128 internal constant PUNCTUATION_7_BIT_ASCII = 0x78000001f8000001fc00fffe00000000; + + /// @dev Lookup for ' \t\n\r\x0b\x0c'. + uint128 internal constant WHITESPACE_7_BIT_ASCII = 0x100003e00; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* STRING STORAGE OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Sets the value of the string storage `$` to `s`. + function set(StringStorage storage $, string memory s) internal { + LibBytes.set(bytesStorage($), bytes(s)); + } + + /// @dev Sets the value of the string storage `$` to `s`. + function setCalldata(StringStorage storage $, string calldata s) internal { + LibBytes.setCalldata(bytesStorage($), bytes(s)); + } + + /// @dev Sets the value of the string storage `$` to the empty string. + function clear(StringStorage storage $) internal { + delete $._spacer; + } + + /// @dev Returns whether the value stored is `$` is the empty string "". + function isEmpty(StringStorage storage $) internal view returns (bool) { + return uint256($._spacer) & 0xff == uint256(0); + } + + /// @dev Returns the length of the value stored in `$`. + function length(StringStorage storage $) internal view returns (uint256) { + return LibBytes.length(bytesStorage($)); + } + + /// @dev Returns the value stored in `$`. + function get(StringStorage storage $) internal view returns (string memory) { + return string(LibBytes.get(bytesStorage($))); + } + + /// @dev Helper to cast `$` to a `BytesStorage`. + function bytesStorage(StringStorage storage $) + internal + pure + returns (LibBytes.BytesStorage storage casted) + { + /// @solidity memory-safe-assembly + assembly { + casted.slot := $.slot + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* DECIMAL OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the base 10 decimal representation of `value`. + function toString(uint256 value) internal pure returns (string memory result) { + /// @solidity memory-safe-assembly + assembly { + // The maximum value of a uint256 contains 78 digits (1 byte per digit), but + // we allocate 0xa0 bytes to keep the free memory pointer 32-byte word aligned. + // We will need 1 word for the trailing zeros padding, 1 word for the length, + // and 3 words for a maximum of 78 digits. + result := add(mload(0x40), 0x80) + mstore(0x40, add(result, 0x20)) // Allocate memory. + mstore(result, 0) // Zeroize the slot after the string. + + let end := result // Cache the end of the memory to calculate the length later. + let w := not(0) // Tsk. + // We write the string from rightmost digit to leftmost digit. + // The following is essentially a do-while loop that also handles the zero case. + for { let temp := value } 1 {} { + result := add(result, w) // `sub(result, 1)`. + // Store the character to the pointer. + // The ASCII index of the '0' character is 48. + mstore8(result, add(48, mod(temp, 10))) + temp := div(temp, 10) // Keep dividing `temp` until zero. + if iszero(temp) { break } + } + let n := sub(end, result) + result := sub(result, 0x20) // Move the pointer 32 bytes back to make room for the length. + mstore(result, n) // Store the length. + } + } + + /// @dev Returns the base 10 decimal representation of `value`. + function toString(int256 value) internal pure returns (string memory result) { + if (value >= 0) return toString(uint256(value)); + unchecked { + result = toString(~uint256(value) + 1); + } + /// @solidity memory-safe-assembly + assembly { + // We still have some spare memory space on the left, + // as we have allocated 3 words (96 bytes) for up to 78 digits. + let n := mload(result) // Load the string length. + mstore(result, 0x2d) // Store the '-' character. + result := sub(result, 1) // Move back the string pointer by a byte. + mstore(result, add(n, 1)) // Update the string length. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* HEXADECIMAL OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the hexadecimal representation of `value`, + /// left-padded to an input length of `byteCount` bytes. + /// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte, + /// giving a total length of `byteCount * 2 + 2` bytes. + /// Reverts if `byteCount` is too small for the output to contain all the digits. + function toHexString(uint256 value, uint256 byteCount) + internal + pure + returns (string memory result) + { + result = toHexStringNoPrefix(value, byteCount); + /// @solidity memory-safe-assembly + assembly { + let n := add(mload(result), 2) // Compute the length. + mstore(result, 0x3078) // Store the "0x" prefix. + result := sub(result, 2) // Move the pointer. + mstore(result, n) // Store the length. + } + } + + /// @dev Returns the hexadecimal representation of `value`, + /// left-padded to an input length of `byteCount` bytes. + /// The output is not prefixed with "0x" and is encoded using 2 hexadecimal digits per byte, + /// giving a total length of `byteCount * 2` bytes. + /// Reverts if `byteCount` is too small for the output to contain all the digits. + function toHexStringNoPrefix(uint256 value, uint256 byteCount) + internal + pure + returns (string memory result) + { + /// @solidity memory-safe-assembly + assembly { + // We need 0x20 bytes for the trailing zeros padding, `byteCount * 2` bytes + // for the digits, 0x02 bytes for the prefix, and 0x20 bytes for the length. + // We add 0x20 to the total and round down to a multiple of 0x20. + // (0x20 + 0x20 + 0x02 + 0x20) = 0x62. + result := add(mload(0x40), and(add(shl(1, byteCount), 0x42), not(0x1f))) + mstore(0x40, add(result, 0x20)) // Allocate memory. + mstore(result, 0) // Zeroize the slot after the string. + + let end := result // Cache the end to calculate the length later. + // Store "0123456789abcdef" in scratch space. + mstore(0x0f, 0x30313233343536373839616263646566) + + let start := sub(result, add(byteCount, byteCount)) + let w := not(1) // Tsk. + let temp := value + // We write the string from rightmost digit to leftmost digit. + // The following is essentially a do-while loop that also handles the zero case. + for {} 1 {} { + result := add(result, w) // `sub(result, 2)`. + mstore8(add(result, 1), mload(and(temp, 15))) + mstore8(result, mload(and(shr(4, temp), 15))) + temp := shr(8, temp) + if iszero(xor(result, start)) { break } + } + if temp { + mstore(0x00, 0x2194895a) // `HexLengthInsufficient()`. + revert(0x1c, 0x04) + } + let n := sub(end, result) + result := sub(result, 0x20) + mstore(result, n) // Store the length. + } + } + + /// @dev Returns the hexadecimal representation of `value`. + /// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte. + /// As address are 20 bytes long, the output will left-padded to have + /// a length of `20 * 2 + 2` bytes. + function toHexString(uint256 value) internal pure returns (string memory result) { + result = toHexStringNoPrefix(value); + /// @solidity memory-safe-assembly + assembly { + let n := add(mload(result), 2) // Compute the length. + mstore(result, 0x3078) // Store the "0x" prefix. + result := sub(result, 2) // Move the pointer. + mstore(result, n) // Store the length. + } + } + + /// @dev Returns the hexadecimal representation of `value`. + /// The output is prefixed with "0x". + /// The output excludes leading "0" from the `toHexString` output. + /// `0x00: "0x0", 0x01: "0x1", 0x12: "0x12", 0x123: "0x123"`. + function toMinimalHexString(uint256 value) internal pure returns (string memory result) { + result = toHexStringNoPrefix(value); + /// @solidity memory-safe-assembly + assembly { + let o := eq(byte(0, mload(add(result, 0x20))), 0x30) // Whether leading zero is present. + let n := add(mload(result), 2) // Compute the length. + mstore(add(result, o), 0x3078) // Store the "0x" prefix, accounting for leading zero. + result := sub(add(result, o), 2) // Move the pointer, accounting for leading zero. + mstore(result, sub(n, o)) // Store the length, accounting for leading zero. + } + } + + /// @dev Returns the hexadecimal representation of `value`. + /// The output excludes leading "0" from the `toHexStringNoPrefix` output. + /// `0x00: "0", 0x01: "1", 0x12: "12", 0x123: "123"`. + function toMinimalHexStringNoPrefix(uint256 value) + internal + pure + returns (string memory result) + { + result = toHexStringNoPrefix(value); + /// @solidity memory-safe-assembly + assembly { + let o := eq(byte(0, mload(add(result, 0x20))), 0x30) // Whether leading zero is present. + let n := mload(result) // Get the length. + result := add(result, o) // Move the pointer, accounting for leading zero. + mstore(result, sub(n, o)) // Store the length, accounting for leading zero. + } + } + + /// @dev Returns the hexadecimal representation of `value`. + /// The output is encoded using 2 hexadecimal digits per byte. + /// As address are 20 bytes long, the output will left-padded to have + /// a length of `20 * 2` bytes. + function toHexStringNoPrefix(uint256 value) internal pure returns (string memory result) { + /// @solidity memory-safe-assembly + assembly { + // We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length, + // 0x02 bytes for the prefix, and 0x40 bytes for the digits. + // The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x40) is 0xa0. + result := add(mload(0x40), 0x80) + mstore(0x40, add(result, 0x20)) // Allocate memory. + mstore(result, 0) // Zeroize the slot after the string. + + let end := result // Cache the end to calculate the length later. + mstore(0x0f, 0x30313233343536373839616263646566) // Store the "0123456789abcdef" lookup. + + let w := not(1) // Tsk. + // We write the string from rightmost digit to leftmost digit. + // The following is essentially a do-while loop that also handles the zero case. + for { let temp := value } 1 {} { + result := add(result, w) // `sub(result, 2)`. + mstore8(add(result, 1), mload(and(temp, 15))) + mstore8(result, mload(and(shr(4, temp), 15))) + temp := shr(8, temp) + if iszero(temp) { break } + } + let n := sub(end, result) + result := sub(result, 0x20) + mstore(result, n) // Store the length. + } + } + + /// @dev Returns the hexadecimal representation of `value`. + /// The output is prefixed with "0x", encoded using 2 hexadecimal digits per byte, + /// and the alphabets are capitalized conditionally according to + /// https://eips.ethereum.org/EIPS/eip-55 + function toHexStringChecksummed(address value) internal pure returns (string memory result) { + result = toHexString(value); + /// @solidity memory-safe-assembly + assembly { + let mask := shl(6, div(not(0), 255)) // `0b010000000100000000 ...` + let o := add(result, 0x22) + let hashed := and(keccak256(o, 40), mul(34, mask)) // `0b10001000 ... ` + let t := shl(240, 136) // `0b10001000 << 240` + for { let i := 0 } 1 {} { + mstore(add(i, i), mul(t, byte(i, hashed))) + i := add(i, 1) + if eq(i, 20) { break } + } + mstore(o, xor(mload(o), shr(1, and(mload(0x00), and(mload(o), mask))))) + o := add(o, 0x20) + mstore(o, xor(mload(o), shr(1, and(mload(0x20), and(mload(o), mask))))) + } + } + + /// @dev Returns the hexadecimal representation of `value`. + /// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte. + function toHexString(address value) internal pure returns (string memory result) { + result = toHexStringNoPrefix(value); + /// @solidity memory-safe-assembly + assembly { + let n := add(mload(result), 2) // Compute the length. + mstore(result, 0x3078) // Store the "0x" prefix. + result := sub(result, 2) // Move the pointer. + mstore(result, n) // Store the length. + } + } + + /// @dev Returns the hexadecimal representation of `value`. + /// The output is encoded using 2 hexadecimal digits per byte. + function toHexStringNoPrefix(address value) internal pure returns (string memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + // Allocate memory. + // We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length, + // 0x02 bytes for the prefix, and 0x28 bytes for the digits. + // The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x28) is 0x80. + mstore(0x40, add(result, 0x80)) + mstore(0x0f, 0x30313233343536373839616263646566) // Store the "0123456789abcdef" lookup. + + result := add(result, 2) + mstore(result, 40) // Store the length. + let o := add(result, 0x20) + mstore(add(o, 40), 0) // Zeroize the slot after the string. + value := shl(96, value) + // We write the string from rightmost digit to leftmost digit. + // The following is essentially a do-while loop that also handles the zero case. + for { let i := 0 } 1 {} { + let p := add(o, add(i, i)) + let temp := byte(i, value) + mstore8(add(p, 1), mload(and(temp, 15))) + mstore8(p, mload(shr(4, temp))) + i := add(i, 1) + if eq(i, 20) { break } + } + } + } + + /// @dev Returns the hex encoded string from the raw bytes. + /// The output is encoded using 2 hexadecimal digits per byte. + function toHexString(bytes memory raw) internal pure returns (string memory result) { + result = toHexStringNoPrefix(raw); + /// @solidity memory-safe-assembly + assembly { + let n := add(mload(result), 2) // Compute the length. + mstore(result, 0x3078) // Store the "0x" prefix. + result := sub(result, 2) // Move the pointer. + mstore(result, n) // Store the length. + } + } + + /// @dev Returns the hex encoded string from the raw bytes. + /// The output is encoded using 2 hexadecimal digits per byte. + function toHexStringNoPrefix(bytes memory raw) internal pure returns (string memory result) { + /// @solidity memory-safe-assembly + assembly { + let n := mload(raw) + result := add(mload(0x40), 2) // Skip 2 bytes for the optional prefix. + mstore(result, add(n, n)) // Store the length of the output. + + mstore(0x0f, 0x30313233343536373839616263646566) // Store the "0123456789abcdef" lookup. + let o := add(result, 0x20) + let end := add(raw, n) + for {} iszero(eq(raw, end)) {} { + raw := add(raw, 1) + mstore8(add(o, 1), mload(and(mload(raw), 15))) + mstore8(o, mload(and(shr(4, mload(raw)), 15))) + o := add(o, 2) + } + mstore(o, 0) // Zeroize the slot after the string. + mstore(0x40, add(o, 0x20)) // Allocate memory. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* RUNE STRING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the number of UTF characters in the string. + function runeCount(string memory s) internal pure returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + if mload(s) { + mstore(0x00, div(not(0), 255)) + mstore(0x20, 0x0202020202020202020202020202020202020202020202020303030304040506) + let o := add(s, 0x20) + let end := add(o, mload(s)) + for { result := 1 } 1 { result := add(result, 1) } { + o := add(o, byte(0, mload(shr(250, mload(o))))) + if iszero(lt(o, end)) { break } + } + } + } + } + + /// @dev Returns if this string is a 7-bit ASCII string. + /// (i.e. all characters codes are in [0..127]) + function is7BitASCII(string memory s) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + result := 1 + let mask := shl(7, div(not(0), 255)) + let n := mload(s) + if n { + let o := add(s, 0x20) + let end := add(o, n) + let last := mload(end) + mstore(end, 0) + for {} 1 {} { + if and(mask, mload(o)) { + result := 0 + break + } + o := add(o, 0x20) + if iszero(lt(o, end)) { break } + } + mstore(end, last) + } + } + } + + /// @dev Returns if this string is a 7-bit ASCII string, + /// AND all characters are in the `allowed` lookup. + /// Note: If `s` is empty, returns true regardless of `allowed`. + function is7BitASCII(string memory s, uint128 allowed) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + result := 1 + if mload(s) { + let allowed_ := shr(128, shl(128, allowed)) + let o := add(s, 0x20) + for { let end := add(o, mload(s)) } 1 {} { + result := and(result, shr(byte(0, mload(o)), allowed_)) + o := add(o, 1) + if iszero(and(result, lt(o, end))) { break } + } + } + } + } + + /// @dev Converts the bytes in the 7-bit ASCII string `s` to + /// an allowed lookup for use in `is7BitASCII(s, allowed)`. + /// To save runtime gas, you can cache the result in an immutable variable. + function to7BitASCIIAllowedLookup(string memory s) internal pure returns (uint128 result) { + /// @solidity memory-safe-assembly + assembly { + if mload(s) { + let o := add(s, 0x20) + for { let end := add(o, mload(s)) } 1 {} { + result := or(result, shl(byte(0, mload(o)), 1)) + o := add(o, 1) + if iszero(lt(o, end)) { break } + } + if shr(128, result) { + mstore(0x00, 0xc9807e0d) // `StringNot7BitASCII()`. + revert(0x1c, 0x04) + } + } + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* BYTE STRING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // For performance and bytecode compactness, byte string operations are restricted + // to 7-bit ASCII strings. All offsets are byte offsets, not UTF character offsets. + // Usage of byte string operations on charsets with runes spanning two or more bytes + // can lead to undefined behavior. + + /// @dev Returns `subject` all occurrences of `needle` replaced with `replacement`. + function replace(string memory subject, string memory needle, string memory replacement) + internal + pure + returns (string memory) + { + return string(LibBytes.replace(bytes(subject), bytes(needle), bytes(replacement))); + } + + /// @dev Returns the byte index of the first location of `needle` in `subject`, + /// needleing from left to right, starting from `from`. + /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found. + function indexOf(string memory subject, string memory needle, uint256 from) + internal + pure + returns (uint256) + { + return LibBytes.indexOf(bytes(subject), bytes(needle), from); + } + + /// @dev Returns the byte index of the first location of `needle` in `subject`, + /// needleing from left to right. + /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found. + function indexOf(string memory subject, string memory needle) internal pure returns (uint256) { + return LibBytes.indexOf(bytes(subject), bytes(needle), 0); + } + + /// @dev Returns the byte index of the first location of `needle` in `subject`, + /// needleing from right to left, starting from `from`. + /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found. + function lastIndexOf(string memory subject, string memory needle, uint256 from) + internal + pure + returns (uint256) + { + return LibBytes.lastIndexOf(bytes(subject), bytes(needle), from); + } + + /// @dev Returns the byte index of the first location of `needle` in `subject`, + /// needleing from right to left. + /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found. + function lastIndexOf(string memory subject, string memory needle) + internal + pure + returns (uint256) + { + return LibBytes.lastIndexOf(bytes(subject), bytes(needle), type(uint256).max); + } + + /// @dev Returns true if `needle` is found in `subject`, false otherwise. + function contains(string memory subject, string memory needle) internal pure returns (bool) { + return LibBytes.contains(bytes(subject), bytes(needle)); + } + + /// @dev Returns whether `subject` starts with `needle`. + function startsWith(string memory subject, string memory needle) internal pure returns (bool) { + return LibBytes.startsWith(bytes(subject), bytes(needle)); + } + + /// @dev Returns whether `subject` ends with `needle`. + function endsWith(string memory subject, string memory needle) internal pure returns (bool) { + return LibBytes.endsWith(bytes(subject), bytes(needle)); + } + + /// @dev Returns `subject` repeated `times`. + function repeat(string memory subject, uint256 times) internal pure returns (string memory) { + return string(LibBytes.repeat(bytes(subject), times)); + } + + /// @dev Returns a copy of `subject` sliced from `start` to `end` (exclusive). + /// `start` and `end` are byte offsets. + function slice(string memory subject, uint256 start, uint256 end) + internal + pure + returns (string memory) + { + return string(LibBytes.slice(bytes(subject), start, end)); + } + + /// @dev Returns a copy of `subject` sliced from `start` to the end of the string. + /// `start` is a byte offset. + function slice(string memory subject, uint256 start) internal pure returns (string memory) { + return string(LibBytes.slice(bytes(subject), start, type(uint256).max)); + } + + /// @dev Returns all the indices of `needle` in `subject`. + /// The indices are byte offsets. + function indicesOf(string memory subject, string memory needle) + internal + pure + returns (uint256[] memory) + { + return LibBytes.indicesOf(bytes(subject), bytes(needle)); + } + + /// @dev Returns a arrays of strings based on the `delimiter` inside of the `subject` string. + function split(string memory subject, string memory delimiter) + internal + pure + returns (string[] memory result) + { + bytes[] memory a = LibBytes.split(bytes(subject), bytes(delimiter)); + /// @solidity memory-safe-assembly + assembly { + result := a + } + } + + /// @dev Returns a concatenated string of `a` and `b`. + /// Cheaper than `string.concat()` and does not de-align the free memory pointer. + function concat(string memory a, string memory b) internal pure returns (string memory) { + return string(LibBytes.concat(bytes(a), bytes(b))); + } + + /// @dev Returns a copy of the string in either lowercase or UPPERCASE. + /// WARNING! This function is only compatible with 7-bit ASCII strings. + function toCase(string memory subject, bool toUpper) + internal + pure + returns (string memory result) + { + /// @solidity memory-safe-assembly + assembly { + let n := mload(subject) + if n { + result := mload(0x40) + let o := add(result, 0x20) + let d := sub(subject, result) + let flags := shl(add(70, shl(5, toUpper)), 0x3ffffff) + for { let end := add(o, n) } 1 {} { + let b := byte(0, mload(add(d, o))) + mstore8(o, xor(and(shr(b, flags), 0x20), b)) + o := add(o, 1) + if eq(o, end) { break } + } + mstore(result, n) // Store the length. + mstore(o, 0) // Zeroize the slot after the string. + mstore(0x40, add(o, 0x20)) // Allocate memory. + } + } + } + + /// @dev Returns a string from a small bytes32 string. + /// `s` must be null-terminated, or behavior will be undefined. + function fromSmallString(bytes32 s) internal pure returns (string memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + let n := 0 + for {} byte(n, s) { n := add(n, 1) } {} // Scan for '\0'. + mstore(result, n) // Store the length. + let o := add(result, 0x20) + mstore(o, s) // Store the bytes of the string. + mstore(add(o, n), 0) // Zeroize the slot after the string. + mstore(0x40, add(result, 0x40)) // Allocate memory. + } + } + + /// @dev Returns the small string, with all bytes after the first null byte zeroized. + function normalizeSmallString(bytes32 s) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + for {} byte(result, s) { result := add(result, 1) } {} // Scan for '\0'. + mstore(0x00, s) + mstore(result, 0x00) + result := mload(0x00) + } + } + + /// @dev Returns the string as a normalized null-terminated small string. + function toSmallString(string memory s) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(s) + if iszero(lt(result, 33)) { + mstore(0x00, 0xec92f9a3) // `TooBigForSmallString()`. + revert(0x1c, 0x04) + } + result := shl(shl(3, sub(32, result)), mload(add(s, result))) + } + } + + /// @dev Returns a lowercased copy of the string. + /// WARNING! This function is only compatible with 7-bit ASCII strings. + function lower(string memory subject) internal pure returns (string memory result) { + result = toCase(subject, false); + } + + /// @dev Returns an UPPERCASED copy of the string. + /// WARNING! This function is only compatible with 7-bit ASCII strings. + function upper(string memory subject) internal pure returns (string memory result) { + result = toCase(subject, true); + } + + /// @dev Escapes the string to be used within HTML tags. + function escapeHTML(string memory s) internal pure returns (string memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + let end := add(s, mload(s)) + let o := add(result, 0x20) + // Store the bytes of the packed offsets and strides into the scratch space. + // `packed = (stride << 5) | offset`. Max offset is 20. Max stride is 6. + mstore(0x1f, 0x900094) + mstore(0x08, 0xc0000000a6ab) + // Store ""&'<>" into the scratch space. + mstore(0x00, shl(64, 0x2671756f743b26616d703b262333393b266c743b2667743b)) + for {} iszero(eq(s, end)) {} { + s := add(s, 1) + let c := and(mload(s), 0xff) + // Not in `["\"","'","&","<",">"]`. + if iszero(and(shl(c, 1), 0x500000c400000000)) { + mstore8(o, c) + o := add(o, 1) + continue + } + let t := shr(248, mload(c)) + mstore(o, mload(and(t, 0x1f))) + o := add(o, shr(5, t)) + } + mstore(o, 0) // Zeroize the slot after the string. + mstore(result, sub(o, add(result, 0x20))) // Store the length. + mstore(0x40, add(o, 0x20)) // Allocate memory. + } + } + + /// @dev Escapes the string to be used within double-quotes in a JSON. + /// If `addDoubleQuotes` is true, the result will be enclosed in double-quotes. + function escapeJSON(string memory s, bool addDoubleQuotes) + internal + pure + returns (string memory result) + { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + let o := add(result, 0x20) + if addDoubleQuotes { + mstore8(o, 34) + o := add(1, o) + } + // Store "\\u0000" in scratch space. + // Store "0123456789abcdef" in scratch space. + // Also, store `{0x08:"b", 0x09:"t", 0x0a:"n", 0x0c:"f", 0x0d:"r"}`. + // into the scratch space. + mstore(0x15, 0x5c75303030303031323334353637383961626364656662746e006672) + // Bitmask for detecting `["\"","\\"]`. + let e := or(shl(0x22, 1), shl(0x5c, 1)) + for { let end := add(s, mload(s)) } iszero(eq(s, end)) {} { + s := add(s, 1) + let c := and(mload(s), 0xff) + if iszero(lt(c, 0x20)) { + if iszero(and(shl(c, 1), e)) { + // Not in `["\"","\\"]`. + mstore8(o, c) + o := add(o, 1) + continue + } + mstore8(o, 0x5c) // "\\". + mstore8(add(o, 1), c) + o := add(o, 2) + continue + } + if iszero(and(shl(c, 1), 0x3700)) { + // Not in `["\b","\t","\n","\f","\d"]`. + mstore8(0x1d, mload(shr(4, c))) // Hex value. + mstore8(0x1e, mload(and(c, 15))) // Hex value. + mstore(o, mload(0x19)) // "\\u00XX". + o := add(o, 6) + continue + } + mstore8(o, 0x5c) // "\\". + mstore8(add(o, 1), mload(add(c, 8))) + o := add(o, 2) + } + if addDoubleQuotes { + mstore8(o, 34) + o := add(1, o) + } + mstore(o, 0) // Zeroize the slot after the string. + mstore(result, sub(o, add(result, 0x20))) // Store the length. + mstore(0x40, add(o, 0x20)) // Allocate memory. + } + } + + /// @dev Escapes the string to be used within double-quotes in a JSON. + function escapeJSON(string memory s) internal pure returns (string memory result) { + result = escapeJSON(s, false); + } + + /// @dev Encodes `s` so that it can be safely used in a URI, + /// just like `encodeURIComponent` in JavaScript. + /// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent + /// See: https://datatracker.ietf.org/doc/html/rfc2396 + /// See: https://datatracker.ietf.org/doc/html/rfc3986 + function encodeURIComponent(string memory s) internal pure returns (string memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + // Store "0123456789ABCDEF" in scratch space. + // Uppercased to be consistent with JavaScript's implementation. + mstore(0x0f, 0x30313233343536373839414243444546) + let o := add(result, 0x20) + for { let end := add(s, mload(s)) } iszero(eq(s, end)) {} { + s := add(s, 1) + let c := and(mload(s), 0xff) + // If not in `[0-9A-Z-a-z-_.!~*'()]`. + if iszero(and(1, shr(c, 0x47fffffe87fffffe03ff678200000000))) { + mstore8(o, 0x25) // '%'. + mstore8(add(o, 1), mload(and(shr(4, c), 15))) + mstore8(add(o, 2), mload(and(c, 15))) + o := add(o, 3) + continue + } + mstore8(o, c) + o := add(o, 1) + } + mstore(result, sub(o, add(result, 0x20))) // Store the length. + mstore(o, 0) // Zeroize the slot after the string. + mstore(0x40, add(o, 0x20)) // Allocate memory. + } + } + + /// @dev Returns whether `a` equals `b`. + function eq(string memory a, string memory b) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + result := eq(keccak256(add(a, 0x20), mload(a)), keccak256(add(b, 0x20), mload(b))) + } + } + + /// @dev Returns whether `a` equals `b`, where `b` is a null-terminated small string. + function eqs(string memory a, bytes32 b) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + // These should be evaluated on compile time, as far as possible. + let m := not(shl(7, div(not(iszero(b)), 255))) // `0x7f7f ...`. + let x := not(or(m, or(b, add(m, and(b, m))))) + let r := shl(7, iszero(iszero(shr(128, x)))) + r := or(r, shl(6, iszero(iszero(shr(64, shr(r, x)))))) + r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) + r := or(r, shl(4, lt(0xffff, shr(r, x)))) + r := or(r, shl(3, lt(0xff, shr(r, x)))) + // forgefmt: disable-next-item + result := gt(eq(mload(a), add(iszero(x), xor(31, shr(3, r)))), + xor(shr(add(8, r), b), shr(add(8, r), mload(add(a, 0x20))))) + } + } + + /// @dev Packs a single string with its length into a single word. + /// Returns `bytes32(0)` if the length is zero or greater than 31. + function packOne(string memory a) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + // We don't need to zero right pad the string, + // since this is our own custom non-standard packing scheme. + result := + mul( + // Load the length and the bytes. + mload(add(a, 0x1f)), + // `length != 0 && length < 32`. Abuses underflow. + // Assumes that the length is valid and within the block gas limit. + lt(sub(mload(a), 1), 0x1f) + ) + } + } + + /// @dev Unpacks a string packed using {packOne}. + /// Returns the empty string if `packed` is `bytes32(0)`. + /// If `packed` is not an output of {packOne}, the output behavior is undefined. + function unpackOne(bytes32 packed) internal pure returns (string memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) // Grab the free memory pointer. + mstore(0x40, add(result, 0x40)) // Allocate 2 words (1 for the length, 1 for the bytes). + mstore(result, 0) // Zeroize the length slot. + mstore(add(result, 0x1f), packed) // Store the length and bytes. + mstore(add(add(result, 0x20), mload(result)), 0) // Right pad with zeroes. + } + } + + /// @dev Packs two strings with their lengths into a single word. + /// Returns `bytes32(0)` if combined length is zero or greater than 30. + function packTwo(string memory a, string memory b) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let aLen := mload(a) + // We don't need to zero right pad the strings, + // since this is our own custom non-standard packing scheme. + result := + mul( + or( // Load the length and the bytes of `a` and `b`. + shl(shl(3, sub(0x1f, aLen)), mload(add(a, aLen))), mload(sub(add(b, 0x1e), aLen))), + // `totalLen != 0 && totalLen < 31`. Abuses underflow. + // Assumes that the lengths are valid and within the block gas limit. + lt(sub(add(aLen, mload(b)), 1), 0x1e) + ) + } + } + + /// @dev Unpacks strings packed using {packTwo}. + /// Returns the empty strings if `packed` is `bytes32(0)`. + /// If `packed` is not an output of {packTwo}, the output behavior is undefined. + function unpackTwo(bytes32 packed) + internal + pure + returns (string memory resultA, string memory resultB) + { + /// @solidity memory-safe-assembly + assembly { + resultA := mload(0x40) // Grab the free memory pointer. + resultB := add(resultA, 0x40) + // Allocate 2 words for each string (1 for the length, 1 for the byte). Total 4 words. + mstore(0x40, add(resultB, 0x40)) + // Zeroize the length slots. + mstore(resultA, 0) + mstore(resultB, 0) + // Store the lengths and bytes. + mstore(add(resultA, 0x1f), packed) + mstore(add(resultB, 0x1f), mload(add(add(resultA, 0x20), mload(resultA)))) + // Right pad with zeroes. + mstore(add(add(resultA, 0x20), mload(resultA)), 0) + mstore(add(add(resultB, 0x20), mload(resultB)), 0) + } + } + + /// @dev Directly returns `a` without copying. + function directReturn(string memory a) internal pure { + assembly { + // Assumes that the string does not start from the scratch space. + let retStart := sub(a, 0x20) + let retUnpaddedSize := add(mload(a), 0x40) + // Right pad with zeroes. Just in case the string is produced + // by a method that doesn't zero right pad. + mstore(add(retStart, retUnpaddedSize), 0) + mstore(retStart, 0x20) // Store the return offset. + // End the transaction, returning the string. + return(retStart, and(not(0x1f), add(0x1f, retUnpaddedSize))) + } + } +} + +abstract contract Initializable { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The contract is already initialized. + error InvalidInitialization(); + + /// @dev The contract is not initializing. + error NotInitializing(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* EVENTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Triggered when the contract has been initialized. + event Initialized(uint64 version); + + /// @dev `keccak256(bytes("Initialized(uint64)"))`. + bytes32 private constant _INTIALIZED_EVENT_SIGNATURE = + 0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* STORAGE */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The default initializable slot is given by: + /// `bytes32(~uint256(uint32(bytes4(keccak256("_INITIALIZABLE_SLOT")))))`. + /// + /// Bits Layout: + /// - [0] `initializing` + /// - [1..64] `initializedVersion` + bytes32 private constant _INITIALIZABLE_SLOT = + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffbf601132; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Override to return a custom storage slot if required. + function _initializableSlot() internal pure virtual returns (bytes32) { + return _INITIALIZABLE_SLOT; + } + + /// @dev Guards an initializer function so that it can be invoked at most once. + /// + /// You can guard a function with `onlyInitializing` such that it can be called + /// through a function guarded with `initializer`. + /// + /// This is similar to `reinitializer(1)`, except that in the context of a constructor, + /// an `initializer` guarded function can be invoked multiple times. + /// This can be useful during testing and is not expected to be used in production. + /// + /// Emits an {Initialized} event. + modifier initializer() virtual { + bytes32 s = _initializableSlot(); + /// @solidity memory-safe-assembly + assembly { + let i := sload(s) + // Set `initializing` to 1, `initializedVersion` to 1. + sstore(s, 3) + // If `!(initializing == 0 && initializedVersion == 0)`. + if i { + // If `!(address(this).code.length == 0 && initializedVersion == 1)`. + if iszero(lt(extcodesize(address()), eq(shr(1, i), 1))) { + mstore(0x00, 0xf92ee8a9) // `InvalidInitialization()`. + revert(0x1c, 0x04) + } + s := shl(shl(255, i), s) // Skip initializing if `initializing == 1`. + } + } + _; + /// @solidity memory-safe-assembly + assembly { + if s { + // Set `initializing` to 0, `initializedVersion` to 1. + sstore(s, 2) + // Emit the {Initialized} event. + mstore(0x20, 1) + log1(0x20, 0x20, _INTIALIZED_EVENT_SIGNATURE) + } + } + } + + /// @dev Guards an reinitialzer function so that it can be invoked at most once. + /// + /// You can guard a function with `onlyInitializing` such that it can be called + /// through a function guarded with `reinitializer`. + /// + /// Emits an {Initialized} event. + modifier reinitializer(uint64 version) virtual { + bytes32 s = _initializableSlot(); + /// @solidity memory-safe-assembly + assembly { + version := and(version, 0xffffffffffffffff) // Clean upper bits. + let i := sload(s) + // If `initializing == 1 || initializedVersion >= version`. + if iszero(lt(and(i, 1), lt(shr(1, i), version))) { + mstore(0x00, 0xf92ee8a9) // `InvalidInitialization()`. + revert(0x1c, 0x04) + } + // Set `initializing` to 1, `initializedVersion` to `version`. + sstore(s, or(1, shl(1, version))) + } + _; + /// @solidity memory-safe-assembly + assembly { + // Set `initializing` to 0, `initializedVersion` to `version`. + sstore(s, shl(1, version)) + // Emit the {Initialized} event. + mstore(0x20, version) + log1(0x20, 0x20, _INTIALIZED_EVENT_SIGNATURE) + } + } + + /// @dev Guards a function such that it can only be called in the scope + /// of a function guarded with `initializer` or `reinitializer`. + modifier onlyInitializing() virtual { + _checkInitializing(); + _; + } + + /// @dev Reverts if the contract is not initializing. + function _checkInitializing() internal view virtual { + bytes32 s = _initializableSlot(); + /// @solidity memory-safe-assembly + assembly { + if iszero(and(1, sload(s))) { + mstore(0x00, 0xd7e6bcf8) // `NotInitializing()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Locks any future initializations by setting the initialized version to `2**64 - 1`. + /// + /// Calling this in the constructor will prevent the contract from being initialized + /// or reinitialized. It is recommended to use this to lock implementation contracts + /// that are designed to be called through proxies. + /// + /// Emits an {Initialized} event the first time it is successfully called. + function _disableInitializers() internal virtual { + bytes32 s = _initializableSlot(); + /// @solidity memory-safe-assembly + assembly { + let i := sload(s) + if and(i, 1) { + mstore(0x00, 0xf92ee8a9) // `InvalidInitialization()`. + revert(0x1c, 0x04) + } + let uint64max := shr(192, s) // Computed to save bytecode. + if iszero(eq(shr(1, i), uint64max)) { + // Set `initializing` to 0, `initializedVersion` to `2**64 - 1`. + sstore(s, shl(1, uint64max)) + // Emit the {Initialized} event. + mstore(0x20, uint64max) + log1(0x20, 0x20, _INTIALIZED_EVENT_SIGNATURE) + } + } + } + + /// @dev Returns the highest version that has been initialized. + function _getInitializedVersion() internal view virtual returns (uint64 version) { + bytes32 s = _initializableSlot(); + /// @solidity memory-safe-assembly + assembly { + version := shr(1, sload(s)) + } + } + + /// @dev Returns whether the contract is currently initializing. + function _isInitializing() internal view virtual returns (bool result) { + bytes32 s = _initializableSlot(); + /// @solidity memory-safe-assembly + assembly { + result := and(1, sload(s)) + } + } +} + +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) {} + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* HASHING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the EIP-712 domain separator. + function _domainSeparator() internal view virtual returns (bytes32 separator) { + if (_domainNameAndVersionMayChange()) { + separator = _buildDomainSeparator(); + } else { + separator = _cachedDomainSeparator; + if (_cachedDomainSeparatorInvalidated()) separator = _buildDomainSeparator(); + } + } + + /// @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))) + } + } +} + +contract FacetEtherBridgeV6 is EIP712, Initializable { + using LibString for *; + using SafeTransferLib for address; + using ECDSA for bytes32; + + error FeatureDisabled(); + error InvalidAmount(); + error NotFactory(); + error ZeroAdminAddress(); + + struct WithdrawRequest { + address recipient; + uint256 amount; + bytes32 withdrawalId; + bytes32 blockHash; + uint256 blockNumber; + bytes signature; + } + + struct BridgeStorage { + mapping(bytes32 => bool) processedWithdraws; + bool ___depositEnabled; + bool ___withdrawEnabled; + address adminAddress; + address ___signerAddress; + address ___dumbContractAddress; + uint256 ___cancelBlockNumber; + uint256 ___withdrawDelay; + } + + function s() internal pure returns (BridgeStorage storage cs) { + bytes32 position = keccak256("BridgeStorage.contract.storage.v1"); + assembly { + cs.slot := position + } + } + + modifier onlyAdmin() { + require(msg.sender == s().adminAddress, "Not admin"); + _; + } + + constructor() { + _disableInitializers(); + } + + function initialize() external initializer { + require(msg.sender == ERC1967FactoryConstants.ADDRESS, "Not factory"); + require(s().adminAddress == address(0), "Already initialized"); + + s().adminAddress = 0xb2B01DeCb6cd36E7396b78D3744482627F22C525; + } + + function _hardCodedSignerAddress() internal view returns (address) { + if (block.chainid == 1) { + return 0x314d660b083675f415cCAA9c545FeedF377d1006; + } else if (block.chainid == 11155111) { + return 0x70DdEe29E1f8FbF9A0c67f2726A3fdEBBa1F391d; + } else { + revert("Unsupported chain"); + } + } + + function deposit() public payable { + require(msg.sender == tx.origin, "Only EOAs can use this bridge"); + + uint256 amount = msg.value; + address recipient = msg.sender; + + if (amount == 0) revert InvalidAmount(); + + bytes memory bridgeInData = abi.encodeWithSelector( + FacetEtherBridgeMintable.bridgeIn.selector, + recipient, + amount + ); + + LibFacet.sendFacetTransaction({ + to: _hardCodedDumbContractAddress(), + gasLimit: 1_000_000, + data: bridgeInData + }); + } + + function bridgeAndCall( + address recipient, + address dumbContractToCall, + bytes calldata functionCalldata, + uint64 gasLimit + ) external payable { + require(msg.sender == tx.origin, "Only EOAs can use this bridge"); + require(gasLimit < 10_000_000, "Gas limit too high"); + + uint256 amount = msg.value; + + if (amount == 0) revert InvalidAmount(); + + bytes memory bridgeInData = abi.encodeWithSelector( + FacetEtherBridgeMintable.bridgeAndCall.selector, + recipient, + amount, + dumbContractToCall, + functionCalldata + ); + + LibFacet.sendFacetTransaction({ + to: _hardCodedDumbContractAddress(), + gasLimit: gasLimit, + data: bridgeInData + }); + } + + function withdraw( + WithdrawRequest calldata req + ) external { + require(msg.sender == tx.origin, "Only EOAs can use this bridge"); + + bytes32 hashedMessage = _hashTypedData(keccak256(abi.encode( + keccak256( + "Withdraw(address recipient,address dumbContract,uint256 amount," + "bytes32 withdrawalId,bytes32 blockHash,uint256 blockNumber)" + ), + req.recipient, + _hardCodedDumbContractAddress(), + req.amount, + req.withdrawalId, + req.blockHash, + req.blockNumber + ))); + + address signer = hashedMessage.recoverCalldata(req.signature); + + require(signer == _hardCodedSignerAddress(), "Invalid signature"); + require(!s().processedWithdraws[req.withdrawalId], "Already processed"); + require(req.blockHash == bytes32(0) || blockhash(req.blockNumber) == req.blockHash, "Invalid block number or hash"); + + s().processedWithdraws[req.withdrawalId] = true; + + bytes memory markWithdrawalCompleteData = abi.encodeWithSelector( + FacetEtherBridgeMintable.markWithdrawalComplete.selector, + req.recipient, + req.withdrawalId + ); + + LibFacet.sendFacetTransaction({ + to: _hardCodedDumbContractAddress(), + gasLimit: 1_000_000, + data: markWithdrawalCompleteData + }); + + req.recipient.forceSafeTransferETH(req.amount, SafeTransferLib.GAS_STIPEND_NO_STORAGE_WRITES); + } + + receive() external payable { + deposit(); + } + + function adminMarkComplete(address recipient, bytes32 withdrawalId) external onlyAdmin { + s().processedWithdraws[withdrawalId] = true; + + bytes memory markWithdrawalCompleteData = abi.encodeWithSelector( + FacetEtherBridgeMintable.markWithdrawalComplete.selector, + recipient, + withdrawalId + ); + + LibFacet.sendFacetTransaction({ + to: _hardCodedDumbContractAddress(), + gasLimit: 1_000_000, + data: markWithdrawalCompleteData + }); + } + + function _hardCodedDumbContractAddress() internal pure returns (address) { + return 0x1673540243E793B0e77C038D4a88448efF524DcE; + } + + function adminWithdraw(address recipient, uint256 amount) external onlyAdmin { + recipient.forceSafeTransferETH(amount); + } + + function adminWithdrawFCT(address recipient, uint256 amount) external onlyAdmin { + LibFacet.sendFacetTransaction({ + to: abi.encodePacked(recipient), + gasLimit: 1_000_000, + value: amount, + data: bytes(''), + mineBoost: bytes('') + }); + } + + /// @notice Accepts ETH value without triggering a deposit to L2. + function donateETH() external payable {} + + function setAdmin(address admin) external onlyAdmin { + s().adminAddress = admin; + } + + function getSigner() external view returns (address) { + return _hardCodedSignerAddress(); + } + + function getAdmin() external view returns (address) { + return s().adminAddress; + } + + function getDumbContract() external pure returns (address) { + return _hardCodedDumbContractAddress(); + } + + function processedWithdraws(bytes32 withdrawalId) external view returns (bool) { + return s().processedWithdraws[withdrawalId]; + } + + function _domainNameAndVersion() + internal + pure + override + returns (string memory name, string memory version) + { + name = "Facet Ether Bridge"; + version = "1"; + } +} \ No newline at end of file diff --git a/packages/backend/discovery/_templates/facet/FacetEtherBridge/template.jsonc b/packages/backend/discovery/_templates/facet/FacetEtherBridge/template.jsonc new file mode 100644 index 00000000000..6160c862af2 --- /dev/null +++ b/packages/backend/discovery/_templates/facet/FacetEtherBridge/template.jsonc @@ -0,0 +1,27 @@ +{ + "$schema": "../../../../../discovery/schemas/contract.v2.schema.json", + "displayName": "FacetEtherBridge", + "description": "Official Facet implementation of the Ether Bridge.", + "fields": { + "getAdmin": { + "target": { + "permissions": [ + { + "type": "configure", + "description": "can withdraw all funds from the bridge." + } + ] + } + }, + "getSigner": { + "target": { + "permissions": [ + { + "type": "configure", + "description": "can sign arbitrary withdrawals for users." + } + ] + } + } + } +} diff --git a/packages/backend/discovery/_templates/facet/FacetSafeModule/shape/FacetSafeModule.sol b/packages/backend/discovery/_templates/facet/FacetSafeModule/shape/FacetSafeModule.sol new file mode 100644 index 00000000000..e3dd82f8beb --- /dev/null +++ b/packages/backend/discovery/_templates/facet/FacetSafeModule/shape/FacetSafeModule.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Unknown +pragma solidity 0.8.26; + +contract FacetSafeModule { + address public constant facetProxyAddress = 0xC9F2d55C56Ef9fE4262c4d5b48d8032241AF4d25; + + function sendFacetTransaction( + bytes calldata to, + uint256 value, + uint256 gasLimit, + bytes calldata data + ) external { + require( + GnosisSafe(msg.sender).execTransactionFromModule( + facetProxyAddress, + 0, + abi.encodeWithSelector( + FacetSafeProxy.sendFacetTransaction.selector, + to, + value, + gasLimit, + data + ), + Enum.Operation.DelegateCall + ), + "execTransactionFromModule failed" + ); + } +} \ No newline at end of file diff --git a/packages/backend/discovery/_templates/facet/FacetSafeModule/template.jsonc b/packages/backend/discovery/_templates/facet/FacetSafeModule/template.jsonc new file mode 100644 index 00000000000..038b19afb30 --- /dev/null +++ b/packages/backend/discovery/_templates/facet/FacetSafeModule/template.jsonc @@ -0,0 +1,5 @@ +{ + "$schema": "../../../../../discovery/schemas/contract.v2.schema.json", + "displayName": "FacetSafeModule", + "description": "Module that allows the Safe to send Facet transactions." +} diff --git a/packages/backend/discovery/_templates/facet/FacetSafeProxy/shape/FacetSafeProxy.sol b/packages/backend/discovery/_templates/facet/FacetSafeProxy/shape/FacetSafeProxy.sol new file mode 100644 index 00000000000..0b41ff18e20 --- /dev/null +++ b/packages/backend/discovery/_templates/facet/FacetSafeProxy/shape/FacetSafeProxy.sol @@ -0,0 +1,488 @@ +// SPDX-License-Identifier: Unknown +pragma solidity 0.8.26; + +library LibRLP { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* STRUCTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev A pointer to a RLP item list in memory. + struct List { + // Do NOT modify the `_data` directly. + uint256 _data; + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CREATE ADDRESS PREDICTION */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the address where a contract will be stored if deployed via + /// `deployer` with `nonce` using the `CREATE` opcode. + /// For the specification of the Recursive Length Prefix (RLP) + /// encoding scheme, please refer to p. 19 of the Ethereum Yellow Paper + /// (https://ethereum.github.io/yellowpaper/paper.pdf) + /// and the Ethereum Wiki (https://eth.wiki/fundamentals/rlp). + /// + /// Based on the EIP-161 (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md) + /// specification, all contract accounts on the Ethereum mainnet are initiated with + /// `nonce = 1`. Thus, the first contract address created by another contract + /// is calculated with a non-zero nonce. + /// + /// The theoretical allowed limit, based on EIP-2681 + /// (https://eips.ethereum.org/EIPS/eip-2681), for an account nonce is 2**64-2. + /// + /// Caution! This function will NOT check that the nonce is within the theoretical range. + /// This is for performance, as exceeding the range is extremely impractical. + /// It is the user's responsibility to ensure that the nonce is valid + /// (e.g. no dirty bits after packing / unpacking). + /// + /// This is equivalent to: + /// `address(uint160(uint256(keccak256(LibRLP.p(deployer).p(nonce).encode()))))`. + /// + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function computeAddress(address deployer, uint256 nonce) + internal + pure + returns (address deployed) + { + /// @solidity memory-safe-assembly + assembly { + for {} 1 {} { + // The integer zero is treated as an empty byte string, + // and as a result it only has a length prefix, 0x80, + // computed via `0x80 + 0`. + + // A one-byte integer in the [0x00, 0x7f] range uses its + // own value as a length prefix, + // there is no additional `0x80 + length` prefix that precedes it. + if iszero(gt(nonce, 0x7f)) { + mstore(0x00, deployer) + // Using `mstore8` instead of `or` naturally cleans + // any dirty upper bits of `deployer`. + mstore8(0x0b, 0x94) + mstore8(0x0a, 0xd6) + // `shl` 7 is equivalent to multiplying by 0x80. + mstore8(0x20, or(shl(7, iszero(nonce)), nonce)) + deployed := keccak256(0x0a, 0x17) + break + } + let i := 8 + // Just use a loop to generalize all the way with minimal bytecode size. + for {} shr(i, nonce) { i := add(i, 8) } {} + // `shr` 3 is equivalent to dividing by 8. + i := shr(3, i) + // Store in descending slot sequence to overlap the values correctly. + mstore(i, nonce) + mstore(0x00, shl(8, deployer)) + mstore8(0x1f, add(0x80, i)) + mstore8(0x0a, 0x94) + mstore8(0x09, add(0xd6, i)) + deployed := keccak256(0x09, add(0x17, i)) + break + } + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* RLP ENCODING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // Note: + // - addresses are treated like byte strings of length 20, agnostic of leading zero bytes. + // - uint256s are converted to byte strings, stripped of leading zero bytes, and encoded. + // - bools are converted to uint256s (`b ? 1 : 0`), then encoded with the uint256. + // - For bytes1 to bytes32, you must manually convert them to bytes memory + // with `abi.encodePacked(x)` before encoding. + + /// @dev Returns a new empty list. + function p() internal pure returns (List memory result) {} + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(uint256 x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(address x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(bool x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(bytes memory x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(List memory x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, uint256 x) internal pure returns (List memory result) { + result._data = x << 48; + _updateTail(list, result); + /// @solidity memory-safe-assembly + assembly { + // If `x` is too big, we cannot pack it inline with the node. + // We'll have to allocate a new slot for `x` and store the pointer to it in the node. + if shr(208, x) { + let m := mload(0x40) + mstore(m, x) + mstore(0x40, add(m, 0x20)) + mstore(result, shl(40, or(1, shl(8, m)))) + } + } + result = list; + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, address x) internal pure returns (List memory result) { + /// @solidity memory-safe-assembly + assembly { + mstore(result, shl(40, or(4, shl(8, x)))) + } + _updateTail(list, result); + result = list; + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, bool x) internal pure returns (List memory result) { + /// @solidity memory-safe-assembly + assembly { + mstore(result, shl(48, iszero(iszero(x)))) + } + _updateTail(list, result); + result = list; + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, bytes memory x) internal pure returns (List memory result) { + /// @solidity memory-safe-assembly + assembly { + mstore(result, shl(40, or(2, shl(8, x)))) + } + _updateTail(list, result); + result = list; + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, List memory x) internal pure returns (List memory result) { + /// @solidity memory-safe-assembly + assembly { + mstore(result, shl(40, or(3, shl(8, x)))) + } + _updateTail(list, result); + result = list; + } + + /// @dev Returns the RLP encoding of `list`. + function encode(List memory list) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + function encodeUint(x_, o_) -> _o { + _o := add(o_, 1) + if iszero(gt(x_, 0x7f)) { + mstore8(o_, or(shl(7, iszero(x_)), x_)) // Copy `x_`. + leave + } + let r_ := shl(7, lt(0xffffffffffffffffffffffffffffffff, x_)) + r_ := or(r_, shl(6, lt(0xffffffffffffffff, shr(r_, x_)))) + r_ := or(r_, shl(5, lt(0xffffffff, shr(r_, x_)))) + r_ := or(r_, shl(4, lt(0xffff, shr(r_, x_)))) + r_ := or(shr(3, r_), lt(0xff, shr(r_, x_))) + mstore8(o_, add(r_, 0x81)) // Store the prefix. + mstore(0x00, x_) + mstore(_o, mload(xor(31, r_))) // Copy `x_`. + _o := add(add(1, r_), _o) + } + function encodeAddress(x_, o_) -> _o { + _o := add(o_, 0x15) + mstore(o_, shl(88, x_)) + mstore8(o_, 0x94) + } + function encodeBytes(x_, o_, c_) -> _o { + _o := add(o_, 1) + let n_ := mload(x_) + if iszero(gt(n_, 55)) { + let f_ := mload(add(0x20, x_)) + if iszero(and(eq(1, n_), lt(byte(0, f_), 0x80))) { + mstore8(o_, add(n_, c_)) // Store the prefix. + mstore(add(0x21, o_), mload(add(0x40, x_))) + mstore(_o, f_) + _o := add(n_, _o) + leave + } + mstore(o_, f_) // Copy `x_`. + leave + } + returndatacopy(returndatasize(), returndatasize(), shr(32, n_)) + let r_ := add(1, add(lt(0xff, n_), add(lt(0xffff, n_), lt(0xffffff, n_)))) + mstore(o_, shl(248, add(r_, add(c_, 55)))) // Store the prefix. + // Copy `x`. + let i_ := add(r_, _o) + _o := add(i_, n_) + for { let d_ := sub(add(0x20, x_), i_) } 1 {} { + mstore(i_, mload(add(d_, i_))) + i_ := add(i_, 0x20) + if iszero(lt(i_, _o)) { break } + } + mstore(o_, or(mload(o_), shl(sub(248, shl(3, r_)), n_))) // Store the prefix. + } + function encodeList(l_, o_) -> _o { + if iszero(mload(l_)) { + mstore8(o_, 0xc0) + _o := add(o_, 1) + leave + } + let j_ := add(o_, 0x20) + for { let h_ := l_ } 1 {} { + h_ := and(mload(h_), 0xffffffffff) + if iszero(h_) { break } + let t_ := byte(26, mload(h_)) + if iszero(gt(t_, 1)) { + if iszero(t_) { + j_ := encodeUint(shr(48, mload(h_)), j_) + continue + } + j_ := encodeUint(mload(shr(48, mload(h_))), j_) + continue + } + if eq(t_, 2) { + j_ := encodeBytes(shr(48, mload(h_)), j_, 0x80) + continue + } + if eq(t_, 3) { + j_ := encodeList(shr(48, mload(h_)), j_) + continue + } + j_ := encodeAddress(shr(48, mload(h_)), j_) + } + mstore(o_, sub(j_, add(o_, 0x20))) + _o := encodeBytes(o_, o_, 0xc0) + } + result := mload(0x40) + let begin := add(result, 0x20) + let end := encodeList(list, begin) + mstore(result, sub(end, begin)) // Store the length of `result`. + mstore(end, 0) // Zeroize the slot after `result`. + mstore(0x40, add(end, 0x20)) // Allocate memory for `result`. + } + } + + /// @dev Returns the RLP encoding of `x`. + function encode(uint256 x) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + for {} 1 {} { + result := mload(0x40) + if iszero(gt(x, 0x7f)) { + mstore(result, 1) // Store the length of `result`. + mstore(add(result, 0x20), shl(248, or(shl(7, iszero(x)), x))) // Copy `x`. + mstore(0x40, add(result, 0x40)) // Allocate memory for `result`. + break + } + let r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x)) + r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x)))) + r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) + r := or(r, shl(4, lt(0xffff, shr(r, x)))) + r := add(2, or(shr(3, r), lt(0xff, shr(r, x)))) + mstore(add(r, result), x) // Copy `x`. + mstore(add(result, 1), add(r, 0x7f)) // Store the prefix. + mstore(result, r) // Store the length of `result`. + mstore(add(r, add(result, 0x20)), 0) // Zeroize the slot after `result`. + mstore(0x40, add(result, 0x60)) // Allocate memory for `result`. + break + } + } + } + + /// @dev Returns the RLP encoding of `x`. + function encode(address x) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + mstore(result, 0x15) + let o := add(0x20, result) + mstore(o, shl(88, x)) + mstore8(o, 0x94) + mstore(0x40, add(0x20, o)) + } + } + + /// @dev Returns the RLP encoding of `x`. + function encode(bool x) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + mstore(result, 1) + mstore(add(0x20, result), shl(add(0xf8, mul(7, iszero(x))), 0x01)) + mstore(0x40, add(0x40, result)) + } + } + + /// @dev Returns the RLP encoding of `x`. + function encode(bytes memory x) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := x + + for {} iszero(and(eq(1, mload(x)), lt(byte(0, mload(add(x, 0x20))), 0x80))) {} { + result := mload(0x40) + let n := mload(x) // Length of `x`. + if iszero(gt(n, 55)) { + mstore(0x40, add(result, 0x60)) + mstore(add(0x41, result), mload(add(0x40, x))) + mstore(add(0x21, result), mload(add(0x20, x))) + mstore(add(1, result), add(n, 0x80)) // Store the prefix. + mstore(result, add(1, n)) // Store the length of `result`. + mstore(add(add(result, 0x21), n), 0) // Zeroize the slot after `result`. + break + } + returndatacopy(returndatasize(), returndatasize(), shr(32, n)) // out of + let r := add(2, add(lt(0xff, n), add(lt(0xffff, n), lt(0xffffff, n)))) + // Copy `x`. + let i := add(r, add(0x20, result)) + let end := add(i, n) + for { let d := sub(add(0x20, x), i) } 1 {} { + mstore(i, mload(add(d, i))) + i := add(i, 0x20) + if iszero(lt(i, end)) { break } + } + mstore(add(r, result), n) // Store the prefix. + mstore(add(1, result), add(r, 0xb6)) // Store the prefix. + mstore(result, add(r, n)) // Store the length of `result`. + mstore(end, 0) // Zeroize the slot after `result`. + mstore(0x40, add(end, 0x20)) // Allocate memory. + break + } + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PRIVATE HELPERS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Updates the tail in `list`. + function _updateTail(List memory list, List memory result) private pure { + /// @solidity memory-safe-assembly + assembly { + let v := or(shr(mload(list), result), mload(list)) + let tail := shr(40, v) + mstore(list, xor(shl(40, xor(tail, result)), v)) // Update the tail. + mstore(tail, or(mload(tail), result)) // Make the previous tail point to `result`. + } + } +} + +library LibFacet { + using LibRLP for LibRLP.List; + + address constant facetInboxAddress = 0x00000000000000000000000000000000000FacE7; + bytes32 constant facetEventSignature = 0x00000000000000000000000000000000000000000000000000000000000face7; + uint8 constant facetTxType = 0x46; + + function sendFacetTransaction( + uint256 gasLimit, + bytes memory data + ) internal { + sendFacetTransaction({ + to: bytes(''), + value: 0, + gasLimit: gasLimit, + data: data, + mineBoost: bytes('') + }); + } + + function sendFacetTransaction( + address to, + uint256 gasLimit, + bytes memory data + ) internal { + sendFacetTransaction({ + to: abi.encodePacked(to), + value: 0, + gasLimit: gasLimit, + data: data, + mineBoost: bytes('') + }); + } + + function prepareFacetTransaction( + bytes memory to, + uint256 value, + uint256 gasLimit, + bytes memory data, + bytes memory mineBoost + ) internal view returns (bytes memory) { + uint256 chainId; + + if (block.chainid == 1) { + chainId = 0xface7; + } else if (block.chainid == 11155111) { + chainId = 0xface7a; + } else { + revert("Unsupported chainId"); + } + + LibRLP.List memory list; + + list.p(chainId); + list.p(to); + list.p(value); + list.p(gasLimit); + list.p(data); + list.p(mineBoost); + return abi.encodePacked(facetTxType, list.encode()); + } + + function sendFacetTransaction( + bytes memory to, + uint256 value, + uint256 gasLimit, + bytes memory data, + bytes memory mineBoost + ) internal { + bytes memory payload = prepareFacetTransaction({ + to: to, + value: value, + gasLimit: gasLimit, + data: data, + mineBoost: mineBoost + }); + + assembly { + log1(add(payload, 32), mload(payload), facetEventSignature) + } + } +} + +contract FacetSafeProxy { + address internal immutable deployedAddress; + + constructor() { + deployedAddress = address(this); + } + + function sendFacetTransaction( + bytes calldata to, + uint256 value, + uint256 gasLimit, + bytes calldata data + ) external { + require(deployedAddress != address(this), "Only Delegate Call"); + + LibFacet.sendFacetTransaction({ + to: to, + value: value, + gasLimit: gasLimit, + data: data, + mineBoost: bytes('') + }); + } +} \ No newline at end of file diff --git a/packages/backend/discovery/_templates/facet/FacetSafeProxy/template.jsonc b/packages/backend/discovery/_templates/facet/FacetSafeProxy/template.jsonc new file mode 100644 index 00000000000..dc1a645529f --- /dev/null +++ b/packages/backend/discovery/_templates/facet/FacetSafeProxy/template.jsonc @@ -0,0 +1,5 @@ +{ + "$schema": "../../../../../discovery/schemas/contract.v2.schema.json", + "displayName": "FacetSafeProxy", + "description": "Helper of the Safe Module that allows to send Facet transactions." +} diff --git a/packages/backend/discovery/_templates/opstack/L1CrossDomainMessenger/shape/L1CrossDomainMessenger_v2_4_0_facet.sol b/packages/backend/discovery/_templates/opstack/L1CrossDomainMessenger/shape/L1CrossDomainMessenger_v2_4_0_facet.sol new file mode 100644 index 00000000000..1db7dda93a3 --- /dev/null +++ b/packages/backend/discovery/_templates/opstack/L1CrossDomainMessenger/shape/L1CrossDomainMessenger_v2_4_0_facet.sol @@ -0,0 +1,1535 @@ +// SPDX-License-Identifier: Unknown +pragma solidity 0.8.15; + +interface ISemver { + /// @notice Getter for the semantic version of the contract. This is not + /// meant to be used onchain but instead meant to be used by offchain + /// tooling. + /// @return Semver contract version as a string. + function version() external view returns (string memory); +} + +contract CrossDomainMessengerLegacySpacer0 { + /// @custom:legacy + /// @custom:spacer libAddressManager + /// @notice Spacer for backwards compatibility. + address private spacer_0_0_20; +} + +library AddressUpgradeable { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + * + * [IMPORTANT] + * ==== + * You shouldn't rely on `isContract` to protect against flash loan attacks! + * + * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets + * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract + * constructor. + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize/address.code.length, which returns 0 + // for contracts in construction, since the code is only stored at the end + // of the constructor execution. + + return account.code.length > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the + * revert reason using the provided one. + * + * _Available since v4.3._ + */ + function verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) internal pure returns (bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + +abstract contract Initializable { + /** + * @dev Indicates that the contract has been initialized. + * @custom:oz-retyped-from bool + */ + uint8 private _initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private _initializing; + + /** + * @dev Triggered when the contract has been initialized or reinitialized. + */ + event Initialized(uint8 version); + + /** + * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, + * `onlyInitializing` functions can be used to initialize parent contracts. Equivalent to `reinitializer(1)`. + */ + modifier initializer() { + bool isTopLevelCall = !_initializing; + require( + (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1), + "Initializable: contract is already initialized" + ); + _initialized = 1; + if (isTopLevelCall) { + _initializing = true; + } + _; + if (isTopLevelCall) { + _initializing = false; + emit Initialized(1); + } + } + + /** + * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the + * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be + * used to initialize parent contracts. + * + * `initializer` is equivalent to `reinitializer(1)`, so a reinitializer may be used after the original + * initialization step. This is essential to configure modules that are added through upgrades and that require + * initialization. + * + * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in + * a contract, executing them in the right order is up to the developer or operator. + */ + modifier reinitializer(uint8 version) { + require(!_initializing && _initialized < version, "Initializable: contract is already initialized"); + _initialized = version; + _initializing = true; + _; + _initializing = false; + emit Initialized(version); + } + + /** + * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the + * {initializer} and {reinitializer} modifiers, directly or indirectly. + */ + modifier onlyInitializing() { + require(_initializing, "Initializable: contract is not initializing"); + _; + } + + /** + * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. + * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized + * to any version. It is recommended to use this to lock implementation contracts that are designed to be called + * through proxies. + */ + function _disableInitializers() internal virtual { + require(!_initializing, "Initializable: contract is initializing"); + if (_initialized < type(uint8).max) { + _initialized = type(uint8).max; + emit Initialized(type(uint8).max); + } + } +} + +contract CrossDomainMessengerLegacySpacer1 { + /// @custom:legacy + /// @custom:spacer ContextUpgradable's __gap + /// @notice Spacer for backwards compatibility. Comes from OpenZeppelin + /// ContextUpgradable. + uint256[50] private spacer_1_0_1600; + + /// @custom:legacy + /// @custom:spacer OwnableUpgradeable's _owner + /// @notice Spacer for backwards compatibility. + /// Come from OpenZeppelin OwnableUpgradeable. + address private spacer_51_0_20; + + /// @custom:legacy + /// @custom:spacer OwnableUpgradeable's __gap + /// @notice Spacer for backwards compatibility. Comes from OpenZeppelin + /// OwnableUpgradeable. + uint256[49] private spacer_52_0_1568; + + /// @custom:legacy + /// @custom:spacer PausableUpgradable's _paused + /// @notice Spacer for backwards compatibility. Comes from OpenZeppelin + /// PausableUpgradable. + bool private spacer_101_0_1; + + /// @custom:legacy + /// @custom:spacer PausableUpgradable's __gap + /// @notice Spacer for backwards compatibility. Comes from OpenZeppelin + /// PausableUpgradable. + uint256[49] private spacer_102_0_1568; + + /// @custom:legacy + /// @custom:spacer ReentrancyGuardUpgradeable's `_status` field. + /// @notice Spacer for backwards compatibility. + uint256 private spacer_151_0_32; + + /// @custom:legacy + /// @custom:spacer ReentrancyGuardUpgradeable's __gap + /// @notice Spacer for backwards compatibility. + uint256[49] private spacer_152_0_1568; + + /// @custom:legacy + /// @custom:spacer blockedMessages + /// @notice Spacer for backwards compatibility. + mapping(bytes32 => bool) private spacer_201_0_32; + + /// @custom:legacy + /// @custom:spacer relayedMessages + /// @notice Spacer for backwards compatibility. + mapping(bytes32 => bool) private spacer_202_0_32; +} + +library Types { + /// @notice OutputProposal represents a commitment to the L2 state. The timestamp is the L1 + /// timestamp that the output root is posted. This timestamp is used to verify that the + /// finalization period has passed since the output root was submitted. + /// @custom:field outputRoot Hash of the L2 output. + /// @custom:field timestamp Timestamp of the L1 block that the output root was submitted in. + /// @custom:field l2BlockNumber L2 block number that the output corresponds to. + struct OutputProposal { + bytes32 outputRoot; + uint128 timestamp; + uint128 l2BlockNumber; + } + + /// @notice Struct representing the elements that are hashed together to generate an output root + /// which itself represents a snapshot of the L2 state. + /// @custom:field version Version of the output root. + /// @custom:field stateRoot Root of the state trie at the block of this output. + /// @custom:field messagePasserStorageRoot Root of the message passer storage trie. + /// @custom:field latestBlockhash Hash of the block this output was generated from. + struct OutputRootProof { + bytes32 version; + bytes32 stateRoot; + bytes32 messagePasserStorageRoot; + bytes32 latestBlockhash; + } + + /// @notice Struct representing a deposit transaction (L1 => L2 transaction) created by an end + /// user (as opposed to a system deposit transaction generated by the system). + /// @custom:field from Address of the sender of the transaction. + /// @custom:field to Address of the recipient of the transaction. + /// @custom:field isCreation True if the transaction is a contract creation. + /// @custom:field value Value to send to the recipient. + /// @custom:field mint Amount of ETH to mint. + /// @custom:field gasLimit Gas limit of the transaction. + /// @custom:field data Data of the transaction. + /// @custom:field l1BlockHash Hash of the block the transaction was submitted in. + /// @custom:field logIndex Index of the log in the block the transaction was submitted in. + struct UserDepositTransaction { + address from; + address to; + bool isCreation; + uint256 value; + uint256 mint; + uint64 gasLimit; + bytes data; + bytes32 l1BlockHash; + uint256 logIndex; + } + + /// @notice Struct representing a withdrawal transaction. + /// @custom:field nonce Nonce of the withdrawal transaction + /// @custom:field sender Address of the sender of the transaction. + /// @custom:field target Address of the recipient of the transaction. + /// @custom:field value Value to send to the recipient. + /// @custom:field gasLimit Gas limit of the transaction. + /// @custom:field data Data of the transaction. + struct WithdrawalTransaction { + uint256 nonce; + address sender; + address target; + uint256 value; + uint256 gasLimit; + bytes data; + } +} + +library RLPWriter { + /// @notice RLP encodes a byte string. + /// @param _in The byte string to encode. + /// @return out_ The RLP encoded string in bytes. + function writeBytes(bytes memory _in) internal pure returns (bytes memory out_) { + if (_in.length == 1 && uint8(_in[0]) < 128) { + out_ = _in; + } else { + out_ = abi.encodePacked(_writeLength(_in.length, 128), _in); + } + } + + /// @notice RLP encodes a list of RLP encoded byte byte strings. + /// @param _in The list of RLP encoded byte strings. + /// @return list_ The RLP encoded list of items in bytes. + function writeList(bytes[] memory _in) internal pure returns (bytes memory list_) { + list_ = _flatten(_in); + list_ = abi.encodePacked(_writeLength(list_.length, 192), list_); + } + + /// @notice RLP encodes a string. + /// @param _in The string to encode. + /// @return out_ The RLP encoded string in bytes. + function writeString(string memory _in) internal pure returns (bytes memory out_) { + out_ = writeBytes(bytes(_in)); + } + + /// @notice RLP encodes an address. + /// @param _in The address to encode. + /// @return out_ The RLP encoded address in bytes. + function writeAddress(address _in) internal pure returns (bytes memory out_) { + out_ = writeBytes(abi.encodePacked(_in)); + } + + /// @notice RLP encodes a uint. + /// @param _in The uint256 to encode. + /// @return out_ The RLP encoded uint256 in bytes. + function writeUint(uint256 _in) internal pure returns (bytes memory out_) { + out_ = writeBytes(_toBinary(_in)); + } + + /// @notice RLP encodes a bool. + /// @param _in The bool to encode. + /// @return out_ The RLP encoded bool in bytes. + function writeBool(bool _in) internal pure returns (bytes memory out_) { + out_ = new bytes(1); + out_[0] = (_in ? bytes1(0x01) : bytes1(0x80)); + } + + /// @notice Encode the first byte and then the `len` in binary form if `length` is more than 55. + /// @param _len The length of the string or the payload. + /// @param _offset 128 if item is string, 192 if item is list. + /// @return out_ RLP encoded bytes. + function _writeLength(uint256 _len, uint256 _offset) private pure returns (bytes memory out_) { + if (_len < 56) { + out_ = new bytes(1); + out_[0] = bytes1(uint8(_len) + uint8(_offset)); + } else { + uint256 lenLen; + uint256 i = 1; + while (_len / i != 0) { + lenLen++; + i *= 256; + } + + out_ = new bytes(lenLen + 1); + out_[0] = bytes1(uint8(lenLen) + uint8(_offset) + 55); + for (i = 1; i <= lenLen; i++) { + out_[i] = bytes1(uint8((_len / (256 ** (lenLen - i))) % 256)); + } + } + } + + /// @notice Encode integer in big endian binary form with no leading zeroes. + /// @param _x The integer to encode. + /// @return out_ RLP encoded bytes. + function _toBinary(uint256 _x) private pure returns (bytes memory out_) { + bytes memory b = abi.encodePacked(_x); + + uint256 i = 0; + for (; i < 32; i++) { + if (b[i] != 0) { + break; + } + } + + out_ = new bytes(32 - i); + for (uint256 j = 0; j < out_.length; j++) { + out_[j] = b[i++]; + } + } + + /// @custom:attribution https://github.com/Arachnid/solidity-stringutils + /// @notice Copies a piece of memory to another location. + /// @param _dest Destination location. + /// @param _src Source location. + /// @param _len Length of memory to copy. + function _memcpy(uint256 _dest, uint256 _src, uint256 _len) private pure { + uint256 dest = _dest; + uint256 src = _src; + uint256 len = _len; + + for (; len >= 32; len -= 32) { + assembly { + mstore(dest, mload(src)) + } + dest += 32; + src += 32; + } + + uint256 mask; + unchecked { + mask = 256 ** (32 - len) - 1; + } + assembly { + let srcpart := and(mload(src), not(mask)) + let destpart := and(mload(dest), mask) + mstore(dest, or(destpart, srcpart)) + } + } + + /// @custom:attribution https://github.com/sammayo/solidity-rlp-encoder + /// @notice Flattens a list of byte strings into one byte string. + /// @param _list List of byte strings to flatten. + /// @return out_ The flattened byte string. + function _flatten(bytes[] memory _list) private pure returns (bytes memory out_) { + if (_list.length == 0) { + return new bytes(0); + } + + uint256 len; + uint256 i = 0; + for (; i < _list.length; i++) { + len += _list[i].length; + } + + out_ = new bytes(len); + uint256 flattenedPtr; + assembly { + flattenedPtr := add(out_, 0x20) + } + + for (i = 0; i < _list.length; i++) { + bytes memory item = _list[i]; + + uint256 listPtr; + assembly { + listPtr := add(item, 0x20) + } + + _memcpy(flattenedPtr, listPtr, item.length); + flattenedPtr += _list[i].length; + } + } +} + +library Encoding { + /// @notice RLP encodes the L2 transaction that would be generated when a given deposit is sent + /// to the L2 system. Useful for searching for a deposit in the L2 system. The + /// transaction is prefixed with 0x7e to identify its EIP-2718 type. + /// @param _tx User deposit transaction to encode. + /// @return RLP encoded L2 deposit transaction. + function encodeDepositTransaction(Types.UserDepositTransaction memory _tx) internal pure returns (bytes memory) { + bytes32 source = Hashing.hashDepositSource(_tx.l1BlockHash, _tx.logIndex); + bytes[] memory raw = new bytes[](8); + raw[0] = RLPWriter.writeBytes(abi.encodePacked(source)); + raw[1] = RLPWriter.writeAddress(_tx.from); + raw[2] = _tx.isCreation ? RLPWriter.writeBytes("") : RLPWriter.writeAddress(_tx.to); + raw[3] = RLPWriter.writeUint(_tx.mint); + raw[4] = RLPWriter.writeUint(_tx.value); + raw[5] = RLPWriter.writeUint(uint256(_tx.gasLimit)); + raw[6] = RLPWriter.writeBool(false); + raw[7] = RLPWriter.writeBytes(_tx.data); + return abi.encodePacked(uint8(0x7e), RLPWriter.writeList(raw)); + } + + /// @notice Encodes the cross domain message based on the version that is encoded into the + /// message nonce. + /// @param _nonce Message nonce with version encoded into the first two bytes. + /// @param _sender Address of the sender of the message. + /// @param _target Address of the target of the message. + /// @param _value ETH value to send to the target. + /// @param _gasLimit Gas limit to use for the message. + /// @param _data Data to send with the message. + /// @return Encoded cross domain message. + function encodeCrossDomainMessage( + uint256 _nonce, + address _sender, + address _target, + uint256 _value, + uint256 _gasLimit, + bytes memory _data + ) + internal + pure + returns (bytes memory) + { + (, uint16 version) = decodeVersionedNonce(_nonce); + if (version == 0) { + return encodeCrossDomainMessageV0(_target, _sender, _data, _nonce); + } else if (version == 1) { + return encodeCrossDomainMessageV1(_nonce, _sender, _target, _value, _gasLimit, _data); + } else { + revert("Encoding: unknown cross domain message version"); + } + } + + /// @notice Encodes a cross domain message based on the V0 (legacy) encoding. + /// @param _target Address of the target of the message. + /// @param _sender Address of the sender of the message. + /// @param _data Data to send with the message. + /// @param _nonce Message nonce. + /// @return Encoded cross domain message. + function encodeCrossDomainMessageV0( + address _target, + address _sender, + bytes memory _data, + uint256 _nonce + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSignature("relayMessage(address,address,bytes,uint256)", _target, _sender, _data, _nonce); + } + + /// @notice Encodes a cross domain message based on the V1 (current) encoding. + /// @param _nonce Message nonce. + /// @param _sender Address of the sender of the message. + /// @param _target Address of the target of the message. + /// @param _value ETH value to send to the target. + /// @param _gasLimit Gas limit to use for the message. + /// @param _data Data to send with the message. + /// @return Encoded cross domain message. + function encodeCrossDomainMessageV1( + uint256 _nonce, + address _sender, + address _target, + uint256 _value, + uint256 _gasLimit, + bytes memory _data + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSignature( + "relayMessage(uint256,address,address,uint256,uint256,bytes)", + _nonce, + _sender, + _target, + _value, + _gasLimit, + _data + ); + } + + /// @notice Adds a version number into the first two bytes of a message nonce. + /// @param _nonce Message nonce to encode into. + /// @param _version Version number to encode into the message nonce. + /// @return Message nonce with version encoded into the first two bytes. + function encodeVersionedNonce(uint240 _nonce, uint16 _version) internal pure returns (uint256) { + uint256 nonce; + assembly { + nonce := or(shl(240, _version), _nonce) + } + return nonce; + } + + /// @notice Pulls the version out of a version-encoded nonce. + /// @param _nonce Message nonce with version encoded into the first two bytes. + /// @return Nonce without encoded version. + /// @return Version of the message. + function decodeVersionedNonce(uint256 _nonce) internal pure returns (uint240, uint16) { + uint240 nonce; + uint16 version; + assembly { + nonce := and(_nonce, 0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) + version := shr(240, _nonce) + } + return (nonce, version); + } + + /// @notice Returns an appropriately encoded call to L1Block.setL1BlockValuesEcotone (without FCT params) + function encodeSetL1BlockValuesEcotone( + uint32 baseFeeScalar, + uint32 blobBaseFeeScalar, + uint64 sequenceNumber, + uint64 timestamp, + uint64 number, + uint256 baseFee, + uint256 blobBaseFee, + bytes32 hash, + bytes32 batcherHash + ) + internal + pure + returns (bytes memory) + { + return encodeSetL1BlockValuesEcotone( + baseFeeScalar, + blobBaseFeeScalar, + sequenceNumber, + timestamp, + number, + baseFee, + blobBaseFee, + hash, + batcherHash, + 0, // Default fctMintPeriodL1DataGas to 0 + 0 // Default fctMintRate to 0 + ); + } + + /// @notice Returns an appropriately encoded call to L1Block.setL1BlockValuesEcotone (with FCT params) + function encodeSetL1BlockValuesEcotone( + uint32 baseFeeScalar, + uint32 blobBaseFeeScalar, + uint64 sequenceNumber, + uint64 timestamp, + uint64 number, + uint256 baseFee, + uint256 blobBaseFee, + bytes32 hash, + bytes32 batcherHash, + uint128 fctMintPeriodL1DataGas, + uint128 fctMintRate + ) + internal + pure + returns (bytes memory) + { + bytes4 functionSignature = bytes4(keccak256("setL1BlockValuesEcotone()")); + return abi.encodePacked( + functionSignature, + baseFeeScalar, + blobBaseFeeScalar, + sequenceNumber, + timestamp, + number, + baseFee, + blobBaseFee, + hash, + batcherHash, + fctMintPeriodL1DataGas, + fctMintRate + ); + } + + /// @notice Returns an appropriately encoded call to L1Block.setL1BlockValuesInterop + /// @param _baseFeeScalar L1 base fee Scalar + /// @param _blobBaseFeeScalar L1 blob base fee Scalar + /// @param _sequenceNumber Number of L2 blocks since epoch start. + /// @param _timestamp L1 timestamp. + /// @param _number L1 blocknumber. + /// @param _baseFee L1 base fee. + /// @param _blobBaseFee L1 blob base fee. + /// @param _hash L1 blockhash. + /// @param _batcherHash Versioned hash to authenticate batcher by. + /// @param _dependencySet Array of the chain IDs in the interop dependency set. + function encodeSetL1BlockValuesInterop( + uint32 _baseFeeScalar, + uint32 _blobBaseFeeScalar, + uint64 _sequenceNumber, + uint64 _timestamp, + uint64 _number, + uint256 _baseFee, + uint256 _blobBaseFee, + bytes32 _hash, + bytes32 _batcherHash, + uint256[] memory _dependencySet + ) + internal + pure + returns (bytes memory) + { + require(_dependencySet.length <= type(uint8).max, "Encoding: dependency set length is too large"); + // Check that the batcher hash is just the address with 0 padding to the left for version 0. + require(uint160(uint256(_batcherHash)) == uint256(_batcherHash), "Encoding: invalid batcher hash"); + + bytes4 functionSignature = bytes4(keccak256("setL1BlockValuesInterop()")); + return abi.encodePacked( + functionSignature, + _baseFeeScalar, + _blobBaseFeeScalar, + _sequenceNumber, + _timestamp, + _number, + _baseFee, + _blobBaseFee, + _hash, + _batcherHash, + uint8(_dependencySet.length), + _dependencySet + ); + } +} + +library Hashing { + /// @notice Computes the hash of the RLP encoded L2 transaction that would be generated when a + /// given deposit is sent to the L2 system. Useful for searching for a deposit in the L2 + /// system. + /// @param _tx User deposit transaction to hash. + /// @return Hash of the RLP encoded L2 deposit transaction. + function hashDepositTransaction(Types.UserDepositTransaction memory _tx) internal pure returns (bytes32) { + return keccak256(Encoding.encodeDepositTransaction(_tx)); + } + + /// @notice Computes the deposit transaction's "source hash", a value that guarantees the hash + /// of the L2 transaction that corresponds to a deposit is unique and is + /// deterministically generated from L1 transaction data. + /// @param _l1BlockHash Hash of the L1 block where the deposit was included. + /// @param _logIndex The index of the log that created the deposit transaction. + /// @return Hash of the deposit transaction's "source hash". + function hashDepositSource(bytes32 _l1BlockHash, uint256 _logIndex) internal pure returns (bytes32) { + bytes32 depositId = keccak256(abi.encode(_l1BlockHash, _logIndex)); + return keccak256(abi.encode(bytes32(0), depositId)); + } + + /// @notice Hashes the cross domain message based on the version that is encoded into the + /// message nonce. + /// @param _nonce Message nonce with version encoded into the first two bytes. + /// @param _sender Address of the sender of the message. + /// @param _target Address of the target of the message. + /// @param _value ETH value to send to the target. + /// @param _gasLimit Gas limit to use for the message. + /// @param _data Data to send with the message. + /// @return Hashed cross domain message. + function hashCrossDomainMessage( + uint256 _nonce, + address _sender, + address _target, + uint256 _value, + uint256 _gasLimit, + bytes memory _data + ) + internal + pure + returns (bytes32) + { + (, uint16 version) = Encoding.decodeVersionedNonce(_nonce); + if (version == 0) { + return hashCrossDomainMessageV0(_target, _sender, _data, _nonce); + } else if (version == 1) { + return hashCrossDomainMessageV1(_nonce, _sender, _target, _value, _gasLimit, _data); + } else { + revert("Hashing: unknown cross domain message version"); + } + } + + /// @notice Hashes a cross domain message based on the V0 (legacy) encoding. + /// @param _target Address of the target of the message. + /// @param _sender Address of the sender of the message. + /// @param _data Data to send with the message. + /// @param _nonce Message nonce. + /// @return Hashed cross domain message. + function hashCrossDomainMessageV0( + address _target, + address _sender, + bytes memory _data, + uint256 _nonce + ) + internal + pure + returns (bytes32) + { + return keccak256(Encoding.encodeCrossDomainMessageV0(_target, _sender, _data, _nonce)); + } + + /// @notice Hashes a cross domain message based on the V1 (current) encoding. + /// @param _nonce Message nonce. + /// @param _sender Address of the sender of the message. + /// @param _target Address of the target of the message. + /// @param _value ETH value to send to the target. + /// @param _gasLimit Gas limit to use for the message. + /// @param _data Data to send with the message. + /// @return Hashed cross domain message. + function hashCrossDomainMessageV1( + uint256 _nonce, + address _sender, + address _target, + uint256 _value, + uint256 _gasLimit, + bytes memory _data + ) + internal + pure + returns (bytes32) + { + return keccak256(Encoding.encodeCrossDomainMessageV1(_nonce, _sender, _target, _value, _gasLimit, _data)); + } + + /// @notice Derives the withdrawal hash according to the encoding in the L2 Withdrawer contract + /// @param _tx Withdrawal transaction to hash. + /// @return Hashed withdrawal transaction. + function hashWithdrawal(Types.WithdrawalTransaction memory _tx) internal pure returns (bytes32) { + return keccak256(abi.encode(_tx.nonce, _tx.sender, _tx.target, _tx.value, _tx.gasLimit, _tx.data)); + } + + /// @notice Hashes the various elements of an output root proof into an output root hash which + /// can be used to check if the proof is valid. + /// @param _outputRootProof Output root proof which should hash to an output root. + /// @return Hashed output root proof. + function hashOutputRootProof(Types.OutputRootProof memory _outputRootProof) internal pure returns (bytes32) { + return keccak256( + abi.encode( + _outputRootProof.version, + _outputRootProof.stateRoot, + _outputRootProof.messagePasserStorageRoot, + _outputRootProof.latestBlockhash + ) + ); + } +} + +library SafeCall { + /// @notice Performs a low level call without copying any returndata. + /// @dev Passes no calldata to the call context. + /// @param _target Address to call + /// @param _gas Amount of gas to pass to the call + /// @param _value Amount of value to pass to the call + function send(address _target, uint256 _gas, uint256 _value) internal returns (bool success_) { + assembly { + success_ := + call( + _gas, // gas + _target, // recipient + _value, // ether value + 0, // inloc + 0, // inlen + 0, // outloc + 0 // outlen + ) + } + } + + /// @notice Perform a low level call with all gas without copying any returndata + /// @param _target Address to call + /// @param _value Amount of value to pass to the call + function send(address _target, uint256 _value) internal returns (bool success_) { + success_ = send(_target, gasleft(), _value); + } + + /// @notice Perform a low level call without copying any returndata + /// @param _target Address to call + /// @param _gas Amount of gas to pass to the call + /// @param _value Amount of value to pass to the call + /// @param _calldata Calldata to pass to the call + function call( + address _target, + uint256 _gas, + uint256 _value, + bytes memory _calldata + ) + internal + returns (bool success_) + { + assembly { + success_ := + call( + _gas, // gas + _target, // recipient + _value, // ether value + add(_calldata, 32), // inloc + mload(_calldata), // inlen + 0, // outloc + 0 // outlen + ) + } + } + + /// @notice Perform a low level call without copying any returndata + /// @param _target Address to call + /// @param _value Amount of value to pass to the call + /// @param _calldata Calldata to pass to the call + function call(address _target, uint256 _value, bytes memory _calldata) internal returns (bool success_) { + success_ = call({ _target: _target, _gas: gasleft(), _value: _value, _calldata: _calldata }); + } + + /// @notice Helper function to determine if there is sufficient gas remaining within the context + /// to guarantee that the minimum gas requirement for a call will be met as well as + /// optionally reserving a specified amount of gas for after the call has concluded. + /// @param _minGas The minimum amount of gas that may be passed to the target context. + /// @param _reservedGas Optional amount of gas to reserve for the caller after the execution + /// of the target context. + /// @return `true` if there is enough gas remaining to safely supply `_minGas` to the target + /// context as well as reserve `_reservedGas` for the caller after the execution of + /// the target context. + /// @dev !!!!! FOOTGUN ALERT !!!!! + /// 1.) The 40_000 base buffer is to account for the worst case of the dynamic cost of the + /// `CALL` opcode's `address_access_cost`, `positive_value_cost`, and + /// `value_to_empty_account_cost` factors with an added buffer of 5,700 gas. It is + /// still possible to self-rekt by initiating a withdrawal with a minimum gas limit + /// that does not account for the `memory_expansion_cost` & `code_execution_cost` + /// factors of the dynamic cost of the `CALL` opcode. + /// 2.) This function should *directly* precede the external call if possible. There is an + /// added buffer to account for gas consumed between this check and the call, but it + /// is only 5,700 gas. + /// 3.) Because EIP-150 ensures that a maximum of 63/64ths of the remaining gas in the call + /// frame may be passed to a subcontext, we need to ensure that the gas will not be + /// truncated. + /// 4.) Use wisely. This function is not a silver bullet. + function hasMinGas(uint256 _minGas, uint256 _reservedGas) internal view returns (bool) { + bool _hasMinGas; + assembly { + // Equation: gas × 63 ≥ minGas × 64 + 63(40_000 + reservedGas) + _hasMinGas := iszero(lt(mul(gas(), 63), add(mul(_minGas, 64), mul(add(40000, _reservedGas), 63)))) + } + return _hasMinGas; + } + + /// @notice Perform a low level call without copying any returndata. This function + /// will revert if the call cannot be performed with the specified minimum + /// gas. + /// @param _target Address to call + /// @param _minGas The minimum amount of gas that may be passed to the call + /// @param _value Amount of value to pass to the call + /// @param _calldata Calldata to pass to the call + function callWithMinGas( + address _target, + uint256 _minGas, + uint256 _value, + bytes memory _calldata + ) + internal + returns (bool) + { + bool _success; + bool _hasMinGas = hasMinGas(_minGas, 0); + assembly { + // Assertion: gasleft() >= (_minGas * 64) / 63 + 40_000 + if iszero(_hasMinGas) { + // Store the "Error(string)" selector in scratch space. + mstore(0, 0x08c379a0) + // Store the pointer to the string length in scratch space. + mstore(32, 32) + // Store the string. + // + // SAFETY: + // - We pad the beginning of the string with two zero bytes as well as the + // length (24) to ensure that we override the free memory pointer at offset + // 0x40. This is necessary because the free memory pointer is likely to + // be greater than 1 byte when this function is called, but it is incredibly + // unlikely that it will be greater than 3 bytes. As for the data within + // 0x60, it is ensured that it is 0 due to 0x60 being the zero offset. + // - It's fine to clobber the free memory pointer, we're reverting. + mstore(88, 0x0000185361666543616c6c3a204e6f7420656e6f75676820676173) + + // Revert with 'Error("SafeCall: Not enough gas")' + revert(28, 100) + } + + // The call will be supplied at least ((_minGas * 64) / 63) gas due to the + // above assertion. This ensures that, in all circumstances (except for when the + // `_minGas` does not account for the `memory_expansion_cost` and `code_execution_cost` + // factors of the dynamic cost of the `CALL` opcode), the call will receive at least + // the minimum amount of gas specified. + _success := + call( + gas(), // gas + _target, // recipient + _value, // ether value + add(_calldata, 32), // inloc + mload(_calldata), // inlen + 0x00, // outloc + 0x00 // outlen + ) + } + return _success; + } +} + +library Constants { + /// @notice Special address to be used as the tx origin for gas estimation calls in the + /// OptimismPortal and CrossDomainMessenger calls. You only need to use this address if + /// the minimum gas limit specified by the user is not actually enough to execute the + /// given message and you're attempting to estimate the actual necessary gas limit. We + /// use address(1) because it's the ecrecover precompile and therefore guaranteed to + /// never have any code on any EVM chain. + address internal constant ESTIMATION_ADDRESS = address(1); + + /// @notice Value used for the L2 sender storage slot in both the OptimismPortal and the + /// CrossDomainMessenger contracts before an actual sender is set. This value is + /// non-zero to reduce the gas cost of message passing transactions. + address internal constant DEFAULT_L2_SENDER = 0x000000000000000000000000000000000000dEaD; + + /// @notice The storage slot that holds the address of a proxy implementation. + /// @dev `bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)` + bytes32 internal constant PROXY_IMPLEMENTATION_ADDRESS = + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + /// @notice The storage slot that holds the address of the owner. + /// @dev `bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)` + bytes32 internal constant PROXY_OWNER_ADDRESS = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + + /// @notice The address that represents ether when dealing with ERC20 token addresses. + address internal constant ETHER = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + address internal constant FACET_COMPUTE_TOKEN = 0xFACE7fAcE7fAcE7FacE7FACE7FACe7FAcE7fACE7; + + /// @notice The address that represents the system caller responsible for L1 attributes + /// transactions. + address internal constant DEPOSITOR_ACCOUNT = 0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001; + + /// @notice Returns the default values for the ResourceConfig. These are the recommended values + /// for a production network. + function DEFAULT_RESOURCE_CONFIG() internal pure returns (ResourceMetering.ResourceConfig memory) { + ResourceMetering.ResourceConfig memory config = ResourceMetering.ResourceConfig({ + maxResourceLimit: 20_000_000, + elasticityMultiplier: 10, + baseFeeMaxChangeDenominator: 8, + minimumBaseFee: 1 gwei, + systemTxMaxGas: 1_000_000, + maximumBaseFee: type(uint128).max + }); + return config; + } +} + +abstract contract CrossDomainMessenger is + CrossDomainMessengerLegacySpacer0, + Initializable, + CrossDomainMessengerLegacySpacer1 +{ + /// @notice Current message version identifier. + uint16 public constant MESSAGE_VERSION = 1; + + /// @notice Constant overhead added to the base gas for a message. + uint64 public constant RELAY_CONSTANT_OVERHEAD = 200_000; + + /// @notice Numerator for dynamic overhead added to the base gas for a message. + uint64 public constant MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR = 64; + + /// @notice Denominator for dynamic overhead added to the base gas for a message. + uint64 public constant MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR = 63; + + /// @notice Extra gas added to base gas for each byte of calldata in a message. + uint64 public constant MIN_GAS_CALLDATA_OVERHEAD = 16; + + /// @notice Gas reserved for performing the external call in `relayMessage`. + uint64 public constant RELAY_CALL_OVERHEAD = 40_000; + + /// @notice Gas reserved for finalizing the execution of `relayMessage` after the safe call. + uint64 public constant RELAY_RESERVED_GAS = 40_000; + + /// @notice Gas reserved for the execution between the `hasMinGas` check and the external + /// call in `relayMessage`. + uint64 public constant RELAY_GAS_CHECK_BUFFER = 5_000; + + /// @notice Mapping of message hashes to boolean receipt values. Note that a message will only + /// be present in this mapping if it has successfully been relayed on this chain, and + /// can therefore not be relayed again. + mapping(bytes32 => bool) public successfulMessages; + + /// @notice Address of the sender of the currently executing message on the other chain. If the + /// value of this variable is the default value (0x00000000...dead) then no message is + /// currently being executed. Use the xDomainMessageSender getter which will throw an + /// error if this is the case. + address internal xDomainMsgSender; + + /// @notice Nonce for the next message to be sent, without the message version applied. Use the + /// messageNonce getter which will insert the message version into the nonce to give you + /// the actual nonce to be used for the message. + uint240 internal msgNonce; + + /// @notice Mapping of message hashes to a boolean if and only if the message has failed to be + /// executed at least once. A message will not be present in this mapping if it + /// successfully executed on the first attempt. + mapping(bytes32 => bool) public failedMessages; + + /// @notice CrossDomainMessenger contract on the other chain. + /// @custom:network-specific + CrossDomainMessenger public otherMessenger; + + /// @notice Reserve extra slots in the storage layout for future upgrades. + /// A gap size of 43 was chosen here, so that the first slot used in a child contract + /// would be 1 plus a multiple of 50. + uint256[43] private __gap; + + /// @notice Emitted whenever a message is sent to the other chain. + /// @param target Address of the recipient of the message. + /// @param sender Address of the sender of the message. + /// @param message Message to trigger the recipient address with. + /// @param messageNonce Unique nonce attached to the message. + /// @param gasLimit Minimum gas limit that the message can be executed with. + event SentMessage(address indexed target, address sender, bytes message, uint256 messageNonce, uint256 gasLimit); + + /// @notice Additional event data to emit, required as of Bedrock. Cannot be merged with the + /// SentMessage event without breaking the ABI of this contract, this is good enough. + /// @param sender Address of the sender of the message. + /// @param value ETH value sent along with the message to the recipient. + event SentMessageExtension1(address indexed sender, uint256 value); + + /// @notice Emitted whenever a message is successfully relayed on this chain. + /// @param msgHash Hash of the message that was relayed. + event RelayedMessage(bytes32 indexed msgHash); + + /// @notice Emitted whenever a message fails to be relayed on this chain. + /// @param msgHash Hash of the message that failed to be relayed. + event FailedRelayedMessage(bytes32 indexed msgHash); + + /// @notice Sends a message to some target address on the other chain. Note that if the call + /// always reverts, then the message will be unrelayable, and any ETH sent will be + /// permanently locked. The same will occur if the target on the other chain is + /// considered unsafe (see the _isUnsafeTarget() function). + /// @param _target Target contract or wallet address. + /// @param _message Message to trigger the target address with. + /// @param _minGasLimit Minimum gas limit that the message can be executed with. + function sendMessage(address _target, bytes calldata _message, uint32 _minGasLimit) external payable { + if (isCustomGasToken()) { + require(msg.value == 0, "CrossDomainMessenger: cannot send value with custom gas token"); + } + + // Triggers a message to the other messenger. Note that the amount of gas provided to the + // message is the amount of gas requested by the user PLUS the base gas value. We want to + // guarantee the property that the call to the target contract will always have at least + // the minimum gas limit specified by the user. + _sendMessage({ + _to: address(otherMessenger), + _gasLimit: baseGas(_message, _minGasLimit), + _value: msg.value, + _data: abi.encodeWithSelector( + this.relayMessage.selector, messageNonce(), msg.sender, _target, msg.value, _minGasLimit, _message + ) + }); + + emit SentMessage(_target, msg.sender, _message, messageNonce(), _minGasLimit); + emit SentMessageExtension1(msg.sender, msg.value); + + unchecked { + ++msgNonce; + } + } + + /// @notice Relays a message that was sent by the other CrossDomainMessenger contract. Can only + /// be executed via cross-chain call from the other messenger OR if the message was + /// already received once and is currently being replayed. + /// @param _nonce Nonce of the message being relayed. + /// @param _sender Address of the user who sent the message. + /// @param _target Address that the message is targeted at. + /// @param _value ETH value to send with the message. + /// @param _minGasLimit Minimum amount of gas that the message can be executed with. + /// @param _message Message to send to the target. + function relayMessage( + uint256 _nonce, + address _sender, + address _target, + uint256 _value, + uint256 _minGasLimit, + bytes calldata _message + ) + external + payable + { + // On L1 this function will check the Portal for its paused status. + // On L2 this function should be a no-op, because paused will always return false. + require(paused() == false, "CrossDomainMessenger: paused"); + + (, uint16 version) = Encoding.decodeVersionedNonce(_nonce); + require(version < 2, "CrossDomainMessenger: only version 0 or 1 messages are supported at this time"); + + // If the message is version 0, then it's a migrated legacy withdrawal. We therefore need + // to check that the legacy version of the message has not already been relayed. + if (version == 0) { + bytes32 oldHash = Hashing.hashCrossDomainMessageV0(_target, _sender, _message, _nonce); + require(successfulMessages[oldHash] == false, "CrossDomainMessenger: legacy withdrawal already relayed"); + } + + // We use the v1 message hash as the unique identifier for the message because it commits + // to the value and minimum gas limit of the message. + bytes32 versionedHash = + Hashing.hashCrossDomainMessageV1(_nonce, _sender, _target, _value, _minGasLimit, _message); + + if (_isOtherMessenger()) { + // These properties should always hold when the message is first submitted (as + // opposed to being replayed). + assert(msg.value == _value); + assert(!failedMessages[versionedHash]); + } else { + require(msg.value == 0, "CrossDomainMessenger: value must be zero unless message is from a system address"); + + require(failedMessages[versionedHash], "CrossDomainMessenger: message cannot be replayed"); + } + + require( + _isUnsafeTarget(_target) == false, "CrossDomainMessenger: cannot send message to blocked system address" + ); + + require(successfulMessages[versionedHash] == false, "CrossDomainMessenger: message has already been relayed"); + + // If there is not enough gas left to perform the external call and finish the execution, + // return early and assign the message to the failedMessages mapping. + // We are asserting that we have enough gas to: + // 1. Call the target contract (_minGasLimit + RELAY_CALL_OVERHEAD + RELAY_GAS_CHECK_BUFFER) + // 1.a. The RELAY_CALL_OVERHEAD is included in `hasMinGas`. + // 2. Finish the execution after the external call (RELAY_RESERVED_GAS). + // + // If `xDomainMsgSender` is not the default L2 sender, this function + // is being re-entered. This marks the message as failed to allow it to be replayed. + if ( + !SafeCall.hasMinGas(_minGasLimit, RELAY_RESERVED_GAS + RELAY_GAS_CHECK_BUFFER) + || xDomainMsgSender != Constants.DEFAULT_L2_SENDER + ) { + failedMessages[versionedHash] = true; + emit FailedRelayedMessage(versionedHash); + + // Revert in this case if the transaction was triggered by the estimation address. This + // should only be possible during gas estimation or we have bigger problems. Reverting + // here will make the behavior of gas estimation change such that the gas limit + // computed will be the amount required to relay the message, even if that amount is + // greater than the minimum gas limit specified by the user. + if (tx.origin == Constants.ESTIMATION_ADDRESS) { + revert("CrossDomainMessenger: failed to relay message"); + } + + return; + } + + xDomainMsgSender = _sender; + bool success = SafeCall.call(_target, gasleft() - RELAY_RESERVED_GAS, _value, _message); + xDomainMsgSender = Constants.DEFAULT_L2_SENDER; + + if (success) { + // This check is identical to one above, but it ensures that the same message cannot be relayed + // twice, and adds a layer of protection against rentrancy. + assert(successfulMessages[versionedHash] == false); + successfulMessages[versionedHash] = true; + emit RelayedMessage(versionedHash); + } else { + failedMessages[versionedHash] = true; + emit FailedRelayedMessage(versionedHash); + + // Revert in this case if the transaction was triggered by the estimation address. This + // should only be possible during gas estimation or we have bigger problems. Reverting + // here will make the behavior of gas estimation change such that the gas limit + // computed will be the amount required to relay the message, even if that amount is + // greater than the minimum gas limit specified by the user. + if (tx.origin == Constants.ESTIMATION_ADDRESS) { + revert("CrossDomainMessenger: failed to relay message"); + } + } + } + + /// @notice Retrieves the address of the contract or wallet that initiated the currently + /// executing message on the other chain. Will throw an error if there is no message + /// currently being executed. Allows the recipient of a call to see who triggered it. + /// @return Address of the sender of the currently executing message on the other chain. + function xDomainMessageSender() external view returns (address) { + require( + xDomainMsgSender != Constants.DEFAULT_L2_SENDER, "CrossDomainMessenger: xDomainMessageSender is not set" + ); + + return xDomainMsgSender; + } + + /// @notice Retrieves the address of the paired CrossDomainMessenger contract on the other chain + /// Public getter is legacy and will be removed in the future. Use `otherMessenger()` instead. + /// @return CrossDomainMessenger contract on the other chain. + /// @custom:legacy + function OTHER_MESSENGER() public view returns (CrossDomainMessenger) { + return otherMessenger; + } + + /// @notice Retrieves the next message nonce. Message version will be added to the upper two + /// bytes of the message nonce. Message version allows us to treat messages as having + /// different structures. + /// @return Nonce of the next message to be sent, with added message version. + function messageNonce() public view returns (uint256) { + return Encoding.encodeVersionedNonce(msgNonce, MESSAGE_VERSION); + } + + /// @notice Computes the amount of gas required to guarantee that a given message will be + /// received on the other chain without running out of gas. Guaranteeing that a message + /// will not run out of gas is important because this ensures that a message can always + /// be replayed on the other chain if it fails to execute completely. + /// @param _message Message to compute the amount of required gas for. + /// @param _minGasLimit Minimum desired gas limit when message goes to target. + /// @return Amount of gas required to guarantee message receipt. + function baseGas(bytes calldata _message, uint32 _minGasLimit) public pure returns (uint64) { + return + // Constant overhead + RELAY_CONSTANT_OVERHEAD + // Calldata overhead + + (uint64(_message.length) * MIN_GAS_CALLDATA_OVERHEAD) + // Dynamic overhead (EIP-150) + + ((_minGasLimit * MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR) / MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR) + // Gas reserved for the worst-case cost of 3/5 of the `CALL` opcode's dynamic gas + // factors. (Conservative) + + RELAY_CALL_OVERHEAD + // Relay reserved gas (to ensure execution of `relayMessage` completes after the + // subcontext finishes executing) (Conservative) + + RELAY_RESERVED_GAS + // Gas reserved for the execution between the `hasMinGas` check and the `CALL` + // opcode. (Conservative) + + RELAY_GAS_CHECK_BUFFER; + } + + /// @notice Returns the address of the gas token and the token's decimals. + function gasPayingToken() internal view virtual returns (address, uint8); + + /// @notice Returns whether the chain uses a custom gas token or not. + function isCustomGasToken() internal view returns (bool) { + (address token,) = gasPayingToken(); + return token != Constants.ETHER; + } + + /// @notice Initializer. + /// @param _otherMessenger CrossDomainMessenger contract on the other chain. + function __CrossDomainMessenger_init(CrossDomainMessenger _otherMessenger) internal onlyInitializing { + // We only want to set the xDomainMsgSender to the default value if it hasn't been initialized yet, + // meaning that this is a fresh contract deployment. + // This prevents resetting the xDomainMsgSender to the default value during an upgrade, which would enable + // a reentrant withdrawal to sandwhich the upgrade replay a withdrawal twice. + if (xDomainMsgSender == address(0)) { + xDomainMsgSender = Constants.DEFAULT_L2_SENDER; + } + otherMessenger = _otherMessenger; + } + + /// @notice Sends a low-level message to the other messenger. Needs to be implemented by child + /// contracts because the logic for this depends on the network where the messenger is + /// being deployed. + /// @param _to Recipient of the message on the other chain. + /// @param _gasLimit Minimum gas limit the message can be executed with. + /// @param _value Amount of ETH to send with the message. + /// @param _data Message data. + function _sendMessage(address _to, uint64 _gasLimit, uint256 _value, bytes memory _data) internal virtual; + + /// @notice Checks whether the message is coming from the other messenger. Implemented by child + /// contracts because the logic for this depends on the network where the messenger is + /// being deployed. + /// @return Whether the message is coming from the other messenger. + function _isOtherMessenger() internal view virtual returns (bool); + + /// @notice Checks whether a given call target is a system address that could cause the + /// messenger to peform an unsafe action. This is NOT a mechanism for blocking user + /// addresses. This is ONLY used to prevent the execution of messages to specific + /// system addresses that could cause security issues, e.g., having the + /// CrossDomainMessenger send messages to itself. + /// @param _target Address of the contract to check. + /// @return Whether or not the address is an unsafe system address. + function _isUnsafeTarget(address _target) internal view virtual returns (bool); + + /// @notice This function should return true if the contract is paused. + /// On L1 this function will check the SuperchainConfig for its paused status. + /// On L2 this function should be a no-op. + /// @return Whether or not the contract is paused. + function paused() public view virtual returns (bool) { + return false; + } +} + +contract L1CrossDomainMessenger is CrossDomainMessenger, ISemver { + /// @notice Contract of the SuperchainConfig. + SuperchainConfig public superchainConfig; + + /// @notice Contract of the OptimismPortal. + /// @custom:network-specific + OptimismPortal public portal; + + /// @notice Address of the SystemConfig contract. + SystemConfig public systemConfig; + + /// @notice Semantic version. + /// @custom:semver 2.4.0 + string public constant version = "2.4.0"; + + /// @notice Constructs the L1CrossDomainMessenger contract. + constructor() CrossDomainMessenger() { + initialize({ + _superchainConfig: SuperchainConfig(address(0)), + _portal: OptimismPortal(payable(address(0))), + _systemConfig: SystemConfig(address(0)), + _otherMessenger: CrossDomainMessenger(address(0)) + }); + } + + /// @notice Initializes the contract. + /// @param _superchainConfig Contract of the SuperchainConfig contract on this network. + /// @param _portal Contract of the OptimismPortal contract on this network. + /// @param _systemConfig Contract of the SystemConfig contract on this network. + function initialize( + SuperchainConfig _superchainConfig, + OptimismPortal _portal, + SystemConfig _systemConfig, + CrossDomainMessenger _otherMessenger + ) + public + initializer + { + superchainConfig = _superchainConfig; + portal = _portal; + systemConfig = _systemConfig; + __CrossDomainMessenger_init({ _otherMessenger: _otherMessenger }); + } + + /// @inheritdoc CrossDomainMessenger + function gasPayingToken() internal view override returns (address _addr, uint8 _decimals) { + (_addr, _decimals) = systemConfig.gasPayingToken(); + } + + /// @notice Getter function for the OptimismPortal contract on this chain. + /// Public getter is legacy and will be removed in the future. Use `portal()` instead. + /// @return Contract of the OptimismPortal on this chain. + /// @custom:legacy + function PORTAL() external view returns (OptimismPortal) { + return portal; + } + + /// @inheritdoc CrossDomainMessenger + function _sendMessage(address _to, uint64 _gasLimit, uint256 _value, bytes memory _data) internal override { + revert("Use LibFacet.sendFacetTransaction instead"); + } + + /// @inheritdoc CrossDomainMessenger + function _isOtherMessenger() internal view override returns (bool) { + return msg.sender == address(portal) && portal.l2Sender() == address(otherMessenger); + } + + /// @inheritdoc CrossDomainMessenger + function _isUnsafeTarget(address _target) internal view override returns (bool) { + return _target == address(this) || _target == address(portal); + } + + /// @inheritdoc CrossDomainMessenger + function paused() public view override returns (bool) { + return superchainConfig.paused(); + } +} \ No newline at end of file diff --git a/packages/backend/discovery/_templates/opstack/L1StandardBridge_facet/shape/PausedL1StandardBridge.sol b/packages/backend/discovery/_templates/opstack/L1StandardBridge_facet/shape/PausedL1StandardBridge.sol new file mode 100644 index 00000000000..2a9e8ee3fcf --- /dev/null +++ b/packages/backend/discovery/_templates/opstack/L1StandardBridge_facet/shape/PausedL1StandardBridge.sol @@ -0,0 +1,2294 @@ +// SPDX-License-Identifier: Unknown +pragma solidity 0.8.15; + +interface ISemver { + /// @notice Getter for the semantic version of the contract. This is not + /// meant to be used onchain but instead meant to be used by offchain + /// tooling. + /// @return Semver contract version as a string. + function version() external view returns (string memory); +} + +abstract contract Initializable { + /** + * @dev Indicates that the contract has been initialized. + * @custom:oz-retyped-from bool + */ + uint8 private _initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private _initializing; + + /** + * @dev Triggered when the contract has been initialized or reinitialized. + */ + event Initialized(uint8 version); + + /** + * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, + * `onlyInitializing` functions can be used to initialize parent contracts. Equivalent to `reinitializer(1)`. + */ + modifier initializer() { + bool isTopLevelCall = !_initializing; + require( + (isTopLevelCall && _initialized < 1) || (!Address.isContract(address(this)) && _initialized == 1), + "Initializable: contract is already initialized" + ); + _initialized = 1; + if (isTopLevelCall) { + _initializing = true; + } + _; + if (isTopLevelCall) { + _initializing = false; + emit Initialized(1); + } + } + + /** + * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the + * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be + * used to initialize parent contracts. + * + * `initializer` is equivalent to `reinitializer(1)`, so a reinitializer may be used after the original + * initialization step. This is essential to configure modules that are added through upgrades and that require + * initialization. + * + * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in + * a contract, executing them in the right order is up to the developer or operator. + */ + modifier reinitializer(uint8 version) { + require(!_initializing && _initialized < version, "Initializable: contract is already initialized"); + _initialized = version; + _initializing = true; + _; + _initializing = false; + emit Initialized(version); + } + + /** + * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the + * {initializer} and {reinitializer} modifiers, directly or indirectly. + */ + modifier onlyInitializing() { + require(_initializing, "Initializable: contract is not initializing"); + _; + } + + /** + * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. + * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized + * to any version. It is recommended to use this to lock implementation contracts that are designed to be called + * through proxies. + */ + function _disableInitializers() internal virtual { + require(!_initializing, "Initializable: contract is initializing"); + if (_initialized < type(uint8).max) { + _initialized = type(uint8).max; + emit Initialized(type(uint8).max); + } + } +} + +library SafeERC20 { + using Address for address; + + function safeTransfer( + IERC20 token, + address to, + uint256 value + ) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom( + IERC20 token, + address from, + address to, + uint256 value + ) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + /** + * @dev Deprecated. This function has issues similar to the ones found in + * {IERC20-approve}, and its usage is discouraged. + * + * Whenever possible, use {safeIncreaseAllowance} and + * {safeDecreaseAllowance} instead. + */ + function safeApprove( + IERC20 token, + address spender, + uint256 value + ) internal { + // safeApprove should only be called when setting an initial allowance, + // or when resetting it to zero. To increase and decrease it, use + // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' + require( + (value == 0) || (token.allowance(address(this), spender) == 0), + "SafeERC20: approve from non-zero to non-zero allowance" + ); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); + } + + function safeIncreaseAllowance( + IERC20 token, + address spender, + uint256 value + ) internal { + uint256 newAllowance = token.allowance(address(this), spender) + value; + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + function safeDecreaseAllowance( + IERC20 token, + address spender, + uint256 value + ) internal { + unchecked { + uint256 oldAllowance = token.allowance(address(this), spender); + require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); + uint256 newAllowance = oldAllowance - value; + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + } + + function safePermit( + IERC20Permit token, + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) internal { + uint256 nonceBefore = token.nonces(owner); + token.permit(owner, spender, value, deadline, v, r, s); + uint256 nonceAfter = token.nonces(owner); + require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed"); + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that + // the target address contains contract code and also asserts for success in the low-level call. + + bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); + if (returndata.length > 0) { + // Return data is optional + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} + +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + * + * [IMPORTANT] + * ==== + * You shouldn't rely on `isContract` to protect against flash loan attacks! + * + * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets + * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract + * constructor. + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize/address.code.length, which returns 0 + // for contracts in construction, since the code is only stored at the end + // of the constructor execution. + + return account.code.length > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + require(isContract(target), "Address: delegate call to non-contract"); + + (bool success, bytes memory returndata) = target.delegatecall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the + * revert reason using the provided one. + * + * _Available since v4.3._ + */ + function verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) internal pure returns (bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + +library AddressAliasHelper { + uint160 constant offset = uint160(0x1111000000000000000000000000000000001111); + + /// @notice Utility function that converts the address in the L1 that submitted a tx to + /// the inbox to the msg.sender viewed in the L2 + /// @param l1Address the address in the L1 that triggered the tx to L2 + /// @return l2Address L2 address as viewed in msg.sender + function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) { + unchecked { + l2Address = address(uint160(l1Address) + offset); + } + } + + /// @notice Utility function that converts the msg.sender viewed in the L2 to the + /// address in the L1 that submitted a tx to the inbox + /// @param l2Address L2 address as viewed in msg.sender + /// @return l1Address the address in the L1 that triggered the tx to L2 + function undoL1ToL2Alias(address l2Address) internal pure returns (address l1Address) { + unchecked { + l1Address = address(uint160(l2Address) - offset); + } + } +} + +library Constants { + /// @notice Special address to be used as the tx origin for gas estimation calls in the + /// OptimismPortal and CrossDomainMessenger calls. You only need to use this address if + /// the minimum gas limit specified by the user is not actually enough to execute the + /// given message and you're attempting to estimate the actual necessary gas limit. We + /// use address(1) because it's the ecrecover precompile and therefore guaranteed to + /// never have any code on any EVM chain. + address internal constant ESTIMATION_ADDRESS = address(1); + + /// @notice Value used for the L2 sender storage slot in both the OptimismPortal and the + /// CrossDomainMessenger contracts before an actual sender is set. This value is + /// non-zero to reduce the gas cost of message passing transactions. + address internal constant DEFAULT_L2_SENDER = 0x000000000000000000000000000000000000dEaD; + + /// @notice The storage slot that holds the address of a proxy implementation. + /// @dev `bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)` + bytes32 internal constant PROXY_IMPLEMENTATION_ADDRESS = + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + /// @notice The storage slot that holds the address of the owner. + /// @dev `bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)` + bytes32 internal constant PROXY_OWNER_ADDRESS = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + + /// @notice The address that represents ether when dealing with ERC20 token addresses. + address internal constant ETHER = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + address internal constant FACET_COMPUTE_TOKEN = 0xFACE7fAcE7fAcE7FacE7FACE7FACe7FAcE7fACE7; + + /// @notice The address that represents the system caller responsible for L1 attributes + /// transactions. + address internal constant DEPOSITOR_ACCOUNT = 0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001; + + /// @notice Returns the default values for the ResourceConfig. These are the recommended values + /// for a production network. + function DEFAULT_RESOURCE_CONFIG() internal pure returns (ResourceMetering.ResourceConfig memory) { + ResourceMetering.ResourceConfig memory config = ResourceMetering.ResourceConfig({ + maxResourceLimit: 20_000_000, + elasticityMultiplier: 10, + baseFeeMaxChangeDenominator: 8, + minimumBaseFee: 1 gwei, + systemTxMaxGas: 1_000_000, + maximumBaseFee: type(uint128).max + }); + return config; + } +} + +library SafeCall { + /// @notice Performs a low level call without copying any returndata. + /// @dev Passes no calldata to the call context. + /// @param _target Address to call + /// @param _gas Amount of gas to pass to the call + /// @param _value Amount of value to pass to the call + function send(address _target, uint256 _gas, uint256 _value) internal returns (bool success_) { + assembly { + success_ := + call( + _gas, // gas + _target, // recipient + _value, // ether value + 0, // inloc + 0, // inlen + 0, // outloc + 0 // outlen + ) + } + } + + /// @notice Perform a low level call with all gas without copying any returndata + /// @param _target Address to call + /// @param _value Amount of value to pass to the call + function send(address _target, uint256 _value) internal returns (bool success_) { + success_ = send(_target, gasleft(), _value); + } + + /// @notice Perform a low level call without copying any returndata + /// @param _target Address to call + /// @param _gas Amount of gas to pass to the call + /// @param _value Amount of value to pass to the call + /// @param _calldata Calldata to pass to the call + function call( + address _target, + uint256 _gas, + uint256 _value, + bytes memory _calldata + ) + internal + returns (bool success_) + { + assembly { + success_ := + call( + _gas, // gas + _target, // recipient + _value, // ether value + add(_calldata, 32), // inloc + mload(_calldata), // inlen + 0, // outloc + 0 // outlen + ) + } + } + + /// @notice Perform a low level call without copying any returndata + /// @param _target Address to call + /// @param _value Amount of value to pass to the call + /// @param _calldata Calldata to pass to the call + function call(address _target, uint256 _value, bytes memory _calldata) internal returns (bool success_) { + success_ = call({ _target: _target, _gas: gasleft(), _value: _value, _calldata: _calldata }); + } + + /// @notice Helper function to determine if there is sufficient gas remaining within the context + /// to guarantee that the minimum gas requirement for a call will be met as well as + /// optionally reserving a specified amount of gas for after the call has concluded. + /// @param _minGas The minimum amount of gas that may be passed to the target context. + /// @param _reservedGas Optional amount of gas to reserve for the caller after the execution + /// of the target context. + /// @return `true` if there is enough gas remaining to safely supply `_minGas` to the target + /// context as well as reserve `_reservedGas` for the caller after the execution of + /// the target context. + /// @dev !!!!! FOOTGUN ALERT !!!!! + /// 1.) The 40_000 base buffer is to account for the worst case of the dynamic cost of the + /// `CALL` opcode's `address_access_cost`, `positive_value_cost`, and + /// `value_to_empty_account_cost` factors with an added buffer of 5,700 gas. It is + /// still possible to self-rekt by initiating a withdrawal with a minimum gas limit + /// that does not account for the `memory_expansion_cost` & `code_execution_cost` + /// factors of the dynamic cost of the `CALL` opcode. + /// 2.) This function should *directly* precede the external call if possible. There is an + /// added buffer to account for gas consumed between this check and the call, but it + /// is only 5,700 gas. + /// 3.) Because EIP-150 ensures that a maximum of 63/64ths of the remaining gas in the call + /// frame may be passed to a subcontext, we need to ensure that the gas will not be + /// truncated. + /// 4.) Use wisely. This function is not a silver bullet. + function hasMinGas(uint256 _minGas, uint256 _reservedGas) internal view returns (bool) { + bool _hasMinGas; + assembly { + // Equation: gas × 63 ≥ minGas × 64 + 63(40_000 + reservedGas) + _hasMinGas := iszero(lt(mul(gas(), 63), add(mul(_minGas, 64), mul(add(40000, _reservedGas), 63)))) + } + return _hasMinGas; + } + + /// @notice Perform a low level call without copying any returndata. This function + /// will revert if the call cannot be performed with the specified minimum + /// gas. + /// @param _target Address to call + /// @param _minGas The minimum amount of gas that may be passed to the call + /// @param _value Amount of value to pass to the call + /// @param _calldata Calldata to pass to the call + function callWithMinGas( + address _target, + uint256 _minGas, + uint256 _value, + bytes memory _calldata + ) + internal + returns (bool) + { + bool _success; + bool _hasMinGas = hasMinGas(_minGas, 0); + assembly { + // Assertion: gasleft() >= (_minGas * 64) / 63 + 40_000 + if iszero(_hasMinGas) { + // Store the "Error(string)" selector in scratch space. + mstore(0, 0x08c379a0) + // Store the pointer to the string length in scratch space. + mstore(32, 32) + // Store the string. + // + // SAFETY: + // - We pad the beginning of the string with two zero bytes as well as the + // length (24) to ensure that we override the free memory pointer at offset + // 0x40. This is necessary because the free memory pointer is likely to + // be greater than 1 byte when this function is called, but it is incredibly + // unlikely that it will be greater than 3 bytes. As for the data within + // 0x60, it is ensured that it is 0 due to 0x60 being the zero offset. + // - It's fine to clobber the free memory pointer, we're reverting. + mstore(88, 0x0000185361666543616c6c3a204e6f7420656e6f75676820676173) + + // Revert with 'Error("SafeCall: Not enough gas")' + revert(28, 100) + } + + // The call will be supplied at least ((_minGas * 64) / 63) gas due to the + // above assertion. This ensures that, in all circumstances (except for when the + // `_minGas` does not account for the `memory_expansion_cost` and `code_execution_cost` + // factors of the dynamic cost of the `CALL` opcode), the call will receive at least + // the minimum amount of gas specified. + _success := + call( + gas(), // gas + _target, // recipient + _value, // ether value + add(_calldata, 32), // inloc + mload(_calldata), // inlen + 0x00, // outloc + 0x00 // outlen + ) + } + return _success; + } +} + +library LibRLP { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* STRUCTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev A pointer to a RLP item list in memory. + struct List { + // Do NOT modify the `_data` directly. + uint256 _data; + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CREATE ADDRESS PREDICTION */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the address where a contract will be stored if deployed via + /// `deployer` with `nonce` using the `CREATE` opcode. + /// For the specification of the Recursive Length Prefix (RLP) + /// encoding scheme, please refer to p. 19 of the Ethereum Yellow Paper + /// (https://ethereum.github.io/yellowpaper/paper.pdf) + /// and the Ethereum Wiki (https://eth.wiki/fundamentals/rlp). + /// + /// Based on the EIP-161 (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md) + /// specification, all contract accounts on the Ethereum mainnet are initiated with + /// `nonce = 1`. Thus, the first contract address created by another contract + /// is calculated with a non-zero nonce. + /// + /// The theoretical allowed limit, based on EIP-2681 + /// (https://eips.ethereum.org/EIPS/eip-2681), for an account nonce is 2**64-2. + /// + /// Caution! This function will NOT check that the nonce is within the theoretical range. + /// This is for performance, as exceeding the range is extremely impractical. + /// It is the user's responsibility to ensure that the nonce is valid + /// (e.g. no dirty bits after packing / unpacking). + /// + /// This is equivalent to: + /// `address(uint160(uint256(keccak256(LibRLP.p(deployer).p(nonce).encode()))))`. + /// + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function computeAddress(address deployer, uint256 nonce) + internal + pure + returns (address deployed) + { + /// @solidity memory-safe-assembly + assembly { + for {} 1 {} { + // The integer zero is treated as an empty byte string, + // and as a result it only has a length prefix, 0x80, + // computed via `0x80 + 0`. + + // A one-byte integer in the [0x00, 0x7f] range uses its + // own value as a length prefix, + // there is no additional `0x80 + length` prefix that precedes it. + if iszero(gt(nonce, 0x7f)) { + mstore(0x00, deployer) + // Using `mstore8` instead of `or` naturally cleans + // any dirty upper bits of `deployer`. + mstore8(0x0b, 0x94) + mstore8(0x0a, 0xd6) + // `shl` 7 is equivalent to multiplying by 0x80. + mstore8(0x20, or(shl(7, iszero(nonce)), nonce)) + deployed := keccak256(0x0a, 0x17) + break + } + let i := 8 + // Just use a loop to generalize all the way with minimal bytecode size. + for {} shr(i, nonce) { i := add(i, 8) } {} + // `shr` 3 is equivalent to dividing by 8. + i := shr(3, i) + // Store in descending slot sequence to overlap the values correctly. + mstore(i, nonce) + mstore(0x00, shl(8, deployer)) + mstore8(0x1f, add(0x80, i)) + mstore8(0x0a, 0x94) + mstore8(0x09, add(0xd6, i)) + deployed := keccak256(0x09, add(0x17, i)) + break + } + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* RLP ENCODING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // Note: + // - addresses are treated like byte strings of length 20, agnostic of leading zero bytes. + // - uint256s are converted to byte strings, stripped of leading zero bytes, and encoded. + // - bools are converted to uint256s (`b ? 1 : 0`), then encoded with the uint256. + // - For bytes1 to bytes32, you must manually convert them to bytes memory + // with `abi.encodePacked(x)` before encoding. + + /// @dev Returns a new empty list. + function p() internal pure returns (List memory result) {} + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(uint256 x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(address x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(bool x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(bytes memory x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(List memory x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, uint256 x) internal pure returns (List memory result) { + result._data = x << 48; + _updateTail(list, result); + /// @solidity memory-safe-assembly + assembly { + // If `x` is too big, we cannot pack it inline with the node. + // We'll have to allocate a new slot for `x` and store the pointer to it in the node. + if shr(208, x) { + let m := mload(0x40) + mstore(m, x) + mstore(0x40, add(m, 0x20)) + mstore(result, shl(40, or(1, shl(8, m)))) + } + } + result = list; + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, address x) internal pure returns (List memory result) { + /// @solidity memory-safe-assembly + assembly { + mstore(result, shl(40, or(4, shl(8, x)))) + } + _updateTail(list, result); + result = list; + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, bool x) internal pure returns (List memory result) { + /// @solidity memory-safe-assembly + assembly { + mstore(result, shl(48, iszero(iszero(x)))) + } + _updateTail(list, result); + result = list; + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, bytes memory x) internal pure returns (List memory result) { + /// @solidity memory-safe-assembly + assembly { + mstore(result, shl(40, or(2, shl(8, x)))) + } + _updateTail(list, result); + result = list; + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, List memory x) internal pure returns (List memory result) { + /// @solidity memory-safe-assembly + assembly { + mstore(result, shl(40, or(3, shl(8, x)))) + } + _updateTail(list, result); + result = list; + } + + /// @dev Returns the RLP encoding of `list`. + function encode(List memory list) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + function encodeUint(x_, o_) -> _o { + _o := add(o_, 1) + if iszero(gt(x_, 0x7f)) { + mstore8(o_, or(shl(7, iszero(x_)), x_)) // Copy `x_`. + leave + } + let r_ := shl(7, lt(0xffffffffffffffffffffffffffffffff, x_)) + r_ := or(r_, shl(6, lt(0xffffffffffffffff, shr(r_, x_)))) + r_ := or(r_, shl(5, lt(0xffffffff, shr(r_, x_)))) + r_ := or(r_, shl(4, lt(0xffff, shr(r_, x_)))) + r_ := or(shr(3, r_), lt(0xff, shr(r_, x_))) + mstore8(o_, add(r_, 0x81)) // Store the prefix. + mstore(0x00, x_) + mstore(_o, mload(xor(31, r_))) // Copy `x_`. + _o := add(add(1, r_), _o) + } + function encodeAddress(x_, o_) -> _o { + _o := add(o_, 0x15) + mstore(o_, shl(88, x_)) + mstore8(o_, 0x94) + } + function encodeBytes(x_, o_, c_) -> _o { + _o := add(o_, 1) + let n_ := mload(x_) + if iszero(gt(n_, 55)) { + let f_ := mload(add(0x20, x_)) + if iszero(and(eq(1, n_), lt(byte(0, f_), 0x80))) { + mstore8(o_, add(n_, c_)) // Store the prefix. + mstore(add(0x21, o_), mload(add(0x40, x_))) + mstore(_o, f_) + _o := add(n_, _o) + leave + } + mstore(o_, f_) // Copy `x_`. + leave + } + returndatacopy(returndatasize(), returndatasize(), shr(32, n_)) + let r_ := add(1, add(lt(0xff, n_), add(lt(0xffff, n_), lt(0xffffff, n_)))) + mstore(o_, shl(248, add(r_, add(c_, 55)))) // Store the prefix. + // Copy `x`. + let i_ := add(r_, _o) + _o := add(i_, n_) + for { let d_ := sub(add(0x20, x_), i_) } 1 {} { + mstore(i_, mload(add(d_, i_))) + i_ := add(i_, 0x20) + if iszero(lt(i_, _o)) { break } + } + mstore(o_, or(mload(o_), shl(sub(248, shl(3, r_)), n_))) // Store the prefix. + } + function encodeList(l_, o_) -> _o { + if iszero(mload(l_)) { + mstore8(o_, 0xc0) + _o := add(o_, 1) + leave + } + let j_ := add(o_, 0x20) + for { let h_ := l_ } 1 {} { + h_ := and(mload(h_), 0xffffffffff) + if iszero(h_) { break } + let t_ := byte(26, mload(h_)) + if iszero(gt(t_, 1)) { + if iszero(t_) { + j_ := encodeUint(shr(48, mload(h_)), j_) + continue + } + j_ := encodeUint(mload(shr(48, mload(h_))), j_) + continue + } + if eq(t_, 2) { + j_ := encodeBytes(shr(48, mload(h_)), j_, 0x80) + continue + } + if eq(t_, 3) { + j_ := encodeList(shr(48, mload(h_)), j_) + continue + } + j_ := encodeAddress(shr(48, mload(h_)), j_) + } + let n_ := sub(j_, add(o_, 0x20)) + if iszero(gt(n_, 55)) { + mstore8(o_, add(n_, 0xc0)) // Store the prefix. + mstore(add(0x01, o_), mload(add(0x20, o_))) + mstore(add(0x21, o_), mload(add(0x40, o_))) + _o := add(n_, add(0x01, o_)) + leave + } + mstore(o_, n_) + _o := encodeBytes(o_, o_, 0xc0) + } + result := mload(0x40) + let begin := add(result, 0x20) + let end := encodeList(list, begin) + mstore(result, sub(end, begin)) // Store the length of `result`. + mstore(end, 0) // Zeroize the slot after `result`. + mstore(0x40, add(end, 0x20)) // Allocate memory for `result`. + } + } + + /// @dev Returns the RLP encoding of `x`. + function encode(uint256 x) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + for {} 1 {} { + result := mload(0x40) + if iszero(gt(x, 0x7f)) { + mstore(result, 1) // Store the length of `result`. + mstore(add(result, 0x20), shl(248, or(shl(7, iszero(x)), x))) // Copy `x`. + mstore(0x40, add(result, 0x40)) // Allocate memory for `result`. + break + } + let r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x)) + r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x)))) + r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) + r := or(r, shl(4, lt(0xffff, shr(r, x)))) + r := add(2, or(shr(3, r), lt(0xff, shr(r, x)))) + mstore(add(r, result), x) // Copy `x`. + mstore(add(result, 1), add(r, 0x7f)) // Store the prefix. + mstore(result, r) // Store the length of `result`. + mstore(add(r, add(result, 0x20)), 0) // Zeroize the slot after `result`. + mstore(0x40, add(result, 0x60)) // Allocate memory for `result`. + break + } + } + } + + /// @dev Returns the RLP encoding of `x`. + function encode(address x) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + mstore(result, 0x15) + let o := add(0x20, result) + mstore(o, shl(88, x)) + mstore8(o, 0x94) + mstore(0x40, add(0x20, o)) + } + } + + /// @dev Returns the RLP encoding of `x`. + function encode(bool x) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + mstore(result, 1) + mstore(add(0x20, result), shl(add(0xf8, mul(7, iszero(x))), 0x01)) + mstore(0x40, add(0x40, result)) + } + } + + /// @dev Returns the RLP encoding of `x`. + function encode(bytes memory x) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := x + + for {} iszero(and(eq(1, mload(x)), lt(byte(0, mload(add(x, 0x20))), 0x80))) {} { + result := mload(0x40) + let n := mload(x) // Length of `x`. + if iszero(gt(n, 55)) { + mstore(0x40, add(result, 0x60)) + mstore(add(0x41, result), mload(add(0x40, x))) + mstore(add(0x21, result), mload(add(0x20, x))) + mstore(add(1, result), add(n, 0x80)) // Store the prefix. + mstore(result, add(1, n)) // Store the length of `result`. + mstore(add(add(result, 0x21), n), 0) // Zeroize the slot after `result`. + break + } + returndatacopy(returndatasize(), returndatasize(), shr(32, n)) + let r := add(2, add(lt(0xff, n), add(lt(0xffff, n), lt(0xffffff, n)))) + // Copy `x`. + let i := add(r, add(0x20, result)) + let end := add(i, n) + for { let d := sub(add(0x20, x), i) } 1 {} { + mstore(i, mload(add(d, i))) + i := add(i, 0x20) + if iszero(lt(i, end)) { break } + } + mstore(add(r, result), n) // Store the prefix. + mstore(add(1, result), add(r, 0xb6)) // Store the prefix. + mstore(result, add(r, n)) // Store the length of `result`. + mstore(end, 0) // Zeroize the slot after `result`. + mstore(0x40, add(end, 0x20)) // Allocate memory. + break + } + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PRIVATE HELPERS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Updates the tail in `list`. + function _updateTail(List memory list, List memory result) private pure { + /// @solidity memory-safe-assembly + assembly { + let v := or(shr(mload(list), result), mload(list)) + let tail := shr(40, v) + mstore(list, xor(shl(40, xor(tail, result)), v)) // Update the tail. + mstore(tail, or(mload(tail), result)) // Make the previous tail point to `result`. + } + } +} + +library LibFacet { + using LibRLP for LibRLP.List; + + address constant facetInboxAddress = 0x00000000000000000000000000000000000FacE7; + bytes32 constant facetEventSignature = 0x00000000000000000000000000000000000000000000000000000000000face7; + uint8 constant facetTxType = 0x46; + + function sendFacetTransaction( + uint256 gasLimit, + bytes memory data + ) internal { + sendFacetTransaction({ + to: bytes(''), + value: 0, + gasLimit: gasLimit, + data: data, + mineBoost: bytes('') + }); + } + + function sendFacetTransaction( + address to, + uint256 gasLimit, + bytes memory data + ) internal { + sendFacetTransaction({ + to: abi.encodePacked(to), + value: 0, + gasLimit: gasLimit, + data: data, + mineBoost: bytes('') + }); + } + + function prepareFacetTransaction( + bytes memory to, + uint256 value, + uint256 gasLimit, + bytes memory data, + bytes memory mineBoost + ) internal view returns (bytes memory) { + uint256 chainId; + + if (block.chainid == 1) { + chainId = 0xface7; + } else if (block.chainid == 11155111) { + chainId = 0xface7a; + } else { + revert("Unsupported chainId"); + } + + LibRLP.List memory list; + + list.p(chainId); + list.p(to); + list.p(value); + list.p(gasLimit); + list.p(data); + list.p(mineBoost); + return abi.encodePacked(facetTxType, list.encode()); + } + + function sendFacetTransaction( + bytes memory to, + uint256 value, + uint256 gasLimit, + bytes memory data, + bytes memory mineBoost + ) internal { + bytes memory payload = prepareFacetTransaction({ + to: to, + value: value, + gasLimit: gasLimit, + data: data, + mineBoost: mineBoost + }); + + assembly { + log1(add(payload, 32), mload(payload), facetEventSignature) + } + } +} + +library ERC165Checker { + // As per the EIP-165 spec, no interface should ever match 0xffffffff + bytes4 private constant _INTERFACE_ID_INVALID = 0xffffffff; + + /** + * @dev Returns true if `account` supports the {IERC165} interface, + */ + function supportsERC165(address account) internal view returns (bool) { + // Any contract that implements ERC165 must explicitly indicate support of + // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid + return + _supportsERC165Interface(account, type(IERC165).interfaceId) && + !_supportsERC165Interface(account, _INTERFACE_ID_INVALID); + } + + /** + * @dev Returns true if `account` supports the interface defined by + * `interfaceId`. Support for {IERC165} itself is queried automatically. + * + * See {IERC165-supportsInterface}. + */ + function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) { + // query support of both ERC165 as per the spec and support of _interfaceId + return supportsERC165(account) && _supportsERC165Interface(account, interfaceId); + } + + /** + * @dev Returns a boolean array where each value corresponds to the + * interfaces passed in and whether they're supported or not. This allows + * you to batch check interfaces for a contract where your expectation + * is that some interfaces may not be supported. + * + * See {IERC165-supportsInterface}. + * + * _Available since v3.4._ + */ + function getSupportedInterfaces(address account, bytes4[] memory interfaceIds) + internal + view + returns (bool[] memory) + { + // an array of booleans corresponding to interfaceIds and whether they're supported or not + bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length); + + // query support of ERC165 itself + if (supportsERC165(account)) { + // query support of each interface in interfaceIds + for (uint256 i = 0; i < interfaceIds.length; i++) { + interfaceIdsSupported[i] = _supportsERC165Interface(account, interfaceIds[i]); + } + } + + return interfaceIdsSupported; + } + + /** + * @dev Returns true if `account` supports all the interfaces defined in + * `interfaceIds`. Support for {IERC165} itself is queried automatically. + * + * Batch-querying can lead to gas savings by skipping repeated checks for + * {IERC165} support. + * + * See {IERC165-supportsInterface}. + */ + function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) { + // query support of ERC165 itself + if (!supportsERC165(account)) { + return false; + } + + // query support of each interface in _interfaceIds + for (uint256 i = 0; i < interfaceIds.length; i++) { + if (!_supportsERC165Interface(account, interfaceIds[i])) { + return false; + } + } + + // all interfaces supported + return true; + } + + /** + * @notice Query if a contract implements an interface, does not check ERC165 support + * @param account The address of the contract to query for support of an interface + * @param interfaceId The interface identifier, as specified in ERC-165 + * @return true if the contract at account indicates support of the interface with + * identifier interfaceId, false otherwise + * @dev Assumes that account contains a contract that supports ERC165, otherwise + * the behavior of this method is undefined. This precondition can be checked + * with {supportsERC165}. + * Interface identification is specified in ERC-165. + */ + function _supportsERC165Interface(address account, bytes4 interfaceId) private view returns (bool) { + // prepare call + bytes memory encodedParams = abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId); + + // perform static call + bool success; + uint256 returnSize; + uint256 returnValue; + assembly { + success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20) + returnSize := returndatasize() + returnValue := mload(0x00) + } + + return success && returnSize >= 0x20 && returnValue > 0; + } +} + +abstract contract StandardBridge is Initializable { + using SafeERC20 for IERC20; + + /// @notice The L2 gas limit set when eth is depoisited using the receive() function. + uint32 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 200_000; + + /// @custom:legacy + /// @custom:spacer messenger + /// @notice Spacer for backwards compatibility. + bytes30 private spacer_0_2_30; + + /// @custom:legacy + /// @custom:spacer l2TokenBridge + /// @notice Spacer for backwards compatibility. + address private spacer_1_0_20; + + /// @notice Mapping that stores deposits for a given pair of local and remote tokens. + mapping(address => mapping(address => uint256)) public deposits; + + /// @notice Messenger contract on this domain. + /// @custom:network-specific + CrossDomainMessenger public messenger; + + /// @notice Corresponding bridge on the other domain. + /// @custom:network-specific + StandardBridge public otherBridge; + + /// @notice Reserve extra slots (to a total of 50) in the storage layout for future upgrades. + /// A gap size of 45 was chosen here, so that the first slot used in a child contract + /// would be a multiple of 50. + uint256[45] private __gap; + + /// @notice Emitted when an ETH bridge is initiated to the other chain. + /// @param from Address of the sender. + /// @param to Address of the receiver. + /// @param amount Amount of ETH sent. + /// @param extraData Extra data sent with the transaction. + event ETHBridgeInitiated(address indexed from, address indexed to, uint256 amount, bytes extraData); + + /// @notice Emitted when an ETH bridge is finalized on this chain. + /// @param from Address of the sender. + /// @param to Address of the receiver. + /// @param amount Amount of ETH sent. + /// @param extraData Extra data sent with the transaction. + event ETHBridgeFinalized(address indexed from, address indexed to, uint256 amount, bytes extraData); + + /// @notice Emitted when an ERC20 bridge is initiated to the other chain. + /// @param localToken Address of the ERC20 on this chain. + /// @param remoteToken Address of the ERC20 on the remote chain. + /// @param from Address of the sender. + /// @param to Address of the receiver. + /// @param amount Amount of the ERC20 sent. + /// @param extraData Extra data sent with the transaction. + event ERC20BridgeInitiated( + address indexed localToken, + address indexed remoteToken, + address indexed from, + address to, + uint256 amount, + bytes extraData + ); + + event L1ERC20DepositAttempted( + bytes32 indexed depositId, + address indexed l1Token, + address indexed l2Token, + address from, + address to, + uint256 amount, + bytes extraData + ); + + /// @notice Emitted when an ERC20 bridge is finalized on this chain. + /// @param localToken Address of the ERC20 on this chain. + /// @param remoteToken Address of the ERC20 on the remote chain. + /// @param from Address of the sender. + /// @param to Address of the receiver. + /// @param amount Amount of the ERC20 sent. + /// @param extraData Extra data sent with the transaction. + event ERC20BridgeFinalized( + address indexed localToken, + address indexed remoteToken, + address indexed from, + address to, + uint256 amount, + bytes extraData + ); + + struct BridgeStorage { + mapping(bytes32 => bytes32) depositHashes; + mapping(bytes32 => bool) finalizedDeposits; + uint256 depositIdNonce; + } + + function getDepositHash(bytes32 _depositId) public view onlyOnL1 returns (bytes32) { + return s().depositHashes[_depositId]; + } + + function getFinalizedDeposit(bytes32 _depositId) public view onlyOnL2 returns (bool) { + return s().finalizedDeposits[_depositId]; + } + + function s() internal pure returns (BridgeStorage storage cs) { + bytes32 position = keccak256("BridgeStorage.contract.storage"); + assembly { + cs.slot := position + } + } + + modifier onlyOnL1() { + require(onL1(), "StandardBridge: function can only be called on L1"); + _; + } + + modifier onlyOnL2() { + require(onL2(), "StandardBridge: function can only be called on L2"); + _; + } + + function generateDepositId() internal onlyOnL1 returns (bytes32) { + s().depositIdNonce++; + return keccak256(abi.encode(address(this), msg.sender, s().depositIdNonce)); + } + + /// @notice Only allow EOAs to call the functions. Note that this is not safe against contracts + /// calling code within their constructors, but also doesn't really matter since we're + /// just trying to prevent users accidentally depositing with smart contract wallets. + modifier onlyEOA() { + require(!Address.isContract(msg.sender), "StandardBridge: function can only be called from an EOA"); + _; + } + + function onL1() internal pure virtual returns (bool); + + function onL2() internal pure returns (bool) { + return !onL1(); + } + + /// @notice Ensures that the caller is a cross-chain message from the other bridge. + modifier onlyOtherBridge() { + if (onL1()) { + require( + msg.sender == address(messenger) && messenger.xDomainMessageSender() == address(otherBridge), + "StandardBridge: function can only be called from the L2 bridge" + ); + } else { + require( + AddressAliasHelper.undoL1ToL2Alias(msg.sender) == address(otherBridge), + "StandardBridge: function can only be called from the L1 bridge" + ); + } + _; + } + + /// @notice Initializer. + /// @param _messenger Contract for CrossDomainMessenger on this network. + /// @param _otherBridge Contract for the other StandardBridge contract. + function __StandardBridge_init( + CrossDomainMessenger _messenger, + StandardBridge _otherBridge + ) + internal + onlyInitializing + { + messenger = _messenger; + otherBridge = _otherBridge; + } + + /// @notice Allows EOAs to bridge ETH by sending directly to the bridge. + /// Must be implemented by contracts that inherit. + receive() external payable virtual; + + /// @notice Returns the address of the custom gas token and the token's decimals. + function gasPayingToken() internal view virtual returns (address, uint8); + + /// @notice Returns whether the chain uses a custom gas token or not. + function isCustomGasToken() internal view returns (bool) { + (address token,) = gasPayingToken(); + return token != Constants.ETHER; + } + + /// @notice Getter for messenger contract. + /// Public getter is legacy and will be removed in the future. Use `messenger` instead. + /// @return Contract of the messenger on this domain. + /// @custom:legacy + function MESSENGER() external view returns (CrossDomainMessenger) { + return messenger; + } + + /// @notice Getter for the other bridge contract. + /// Public getter is legacy and will be removed in the future. Use `otherBridge` instead. + /// @return Contract of the bridge on the other network. + /// @custom:legacy + function OTHER_BRIDGE() external view returns (StandardBridge) { + return otherBridge; + } + + /// @notice This function should return true if the contract is paused. + /// On L1 this function will check the SuperchainConfig for its paused status. + /// On L2 this function should be a no-op. + /// @return Whether or not the contract is paused. + function paused() public view virtual returns (bool) { + return false; + } + + /// @notice Sends ETH to the sender's address on the other chain. + /// @param _minGasLimit Minimum amount of gas that the bridge can be relayed with. + /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will + /// not be triggered with this data, but it will be emitted and can be used + /// to identify the transaction. + function bridgeETH(uint32 _minGasLimit, bytes calldata _extraData) public payable onlyEOA { + _initiateBridgeETH(msg.sender, msg.sender, msg.value, _minGasLimit, _extraData); + } + + /// @notice Sends ETH to a receiver's address on the other chain. Note that if ETH is sent to a + /// smart contract and the call fails, the ETH will be temporarily locked in the + /// StandardBridge on the other chain until the call is replayed. If the call cannot be + /// replayed with any amount of gas (call always reverts), then the ETH will be + /// permanently locked in the StandardBridge on the other chain. ETH will also + /// be locked if the receiver is the other bridge, because finalizeBridgeETH will revert + /// in that case. + /// @param _to Address of the receiver. + /// @param _minGasLimit Minimum amount of gas that the bridge can be relayed with. + /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will + /// not be triggered with this data, but it will be emitted and can be used + /// to identify the transaction. + function bridgeETHTo(address _to, uint32 _minGasLimit, bytes calldata _extraData) public payable { + _initiateBridgeETH(msg.sender, _to, msg.value, _minGasLimit, _extraData); + } + + /// @notice Sends ERC20 tokens to the sender's address on the other chain. + /// @param _localToken Address of the ERC20 on this chain. + /// @param _remoteToken Address of the corresponding token on the remote chain. + /// @param _amount Amount of local tokens to deposit. + /// @param _minGasLimit Minimum amount of gas that the bridge can be relayed with. + /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will + /// not be triggered with this data, but it will be emitted and can be used + /// to identify the transaction. + function bridgeERC20( + address _localToken, + address _remoteToken, + uint256 _amount, + uint32 _minGasLimit, + bytes calldata _extraData + ) + public + virtual + onlyEOA + { + _initiateBridgeERC20(_localToken, _remoteToken, msg.sender, msg.sender, _amount, _minGasLimit, _extraData); + } + + /// @notice Sends ERC20 tokens to a receiver's address on the other chain. + /// @param _localToken Address of the ERC20 on this chain. + /// @param _remoteToken Address of the corresponding token on the remote chain. + /// @param _to Address of the receiver. + /// @param _amount Amount of local tokens to deposit. + /// @param _minGasLimit Minimum amount of gas that the bridge can be relayed with. + /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will + /// not be triggered with this data, but it will be emitted and can be used + /// to identify the transaction. + function bridgeERC20To( + address _localToken, + address _remoteToken, + address _to, + uint256 _amount, + uint32 _minGasLimit, + bytes calldata _extraData + ) + public + virtual + { + _initiateBridgeERC20(_localToken, _remoteToken, msg.sender, _to, _amount, _minGasLimit, _extraData); + } + + /// @notice Finalizes an ETH bridge on this chain. Can only be triggered by the other + /// StandardBridge contract on the remote chain. + /// @param _from Address of the sender. + /// @param _to Address of the receiver. + /// @param _amount Amount of ETH being bridged. + /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will + /// not be triggered with this data, but it will be emitted and can be used + /// to identify the transaction. + function finalizeBridgeETH( + address _from, + address _to, + uint256 _amount, + bytes calldata _extraData + ) + public + payable + onlyOtherBridge + { + require(paused() == false, "StandardBridge: paused"); + require(isCustomGasToken() == false, "StandardBridge: cannot bridge ETH with custom gas token"); + require(msg.value == _amount, "StandardBridge: amount sent does not match amount required"); + require(_to != address(this), "StandardBridge: cannot send to self"); + require(_to != address(messenger), "StandardBridge: cannot send to messenger"); + + // Emit the correct events. By default this will be _amount, but child + // contracts may override this function in order to emit legacy events as well. + _emitETHBridgeFinalized(_from, _to, _amount, _extraData); + + bool success = SafeCall.call(_to, gasleft(), _amount, hex""); + require(success, "StandardBridge: ETH transfer failed"); + } + + function replayERC20Deposit( + bytes32 _depositId, + address _l1Token, + address _l2Token, + address _from, + address _to, + uint256 _amount, + bytes memory _extraData + ) public onlyOnL1 { + _sendERC20DepositMessage({ + _depositId: _depositId, + _l1Token: _l1Token, + _l2Token: _l2Token, + _from: _from, + _to: _to, + _amount: _amount, + _extraData: _extraData, + _isInitialDeposit: false + }); + } + + function _sendERC20DepositMessage( + bytes32 _depositId, + address _l1Token, + address _l2Token, + address _from, + address _to, + uint256 _amount, + bytes memory _extraData, + bool _isInitialDeposit + ) internal onlyOnL1 { + bytes memory payload = abi.encode( + _l2Token, + _l1Token, + _from, + _to, + _amount, + _extraData + ); + + if (_isInitialDeposit) { + s().depositHashes[_depositId] = keccak256(payload); + } else { + require(s().depositHashes[_depositId] == keccak256(payload), "StandardBridge: invalid deposit parameters"); + } + + LibFacet.sendFacetTransaction({ + gasLimit: 500_000, + to: address(otherBridge), + data: abi.encodeWithSelector( + this.finalizeBridgeERC20Replayable.selector, + _depositId, + _l2Token, + _l1Token, + _from, + _to, + _amount, + _extraData + ) + }); + + emit L1ERC20DepositAttempted(_depositId, _l1Token, _l2Token, _from, _to, _amount, _extraData); + } + + function finalizeBridgeERC20Replayable( + bytes32 _depositId, + address _localToken, + address _remoteToken, + address _from, + address _to, + uint256 _amount, + bytes calldata _extraData + ) public onlyOnL2 onlyOtherBridge { + bool depositFinalized = s().finalizedDeposits[_depositId]; + require(!depositFinalized, "StandardBridge: deposit already finalized"); + + s().finalizedDeposits[_depositId] = true; + + // Call the non-replayable finalizeBridgeERC20 function + finalizeBridgeERC20( + _localToken, + _remoteToken, + _from, + _to, + _amount, + _extraData + ); + } + + /// @notice Finalizes an ERC20 bridge on this chain. Can only be triggered by the other + /// StandardBridge contract on the remote chain. + /// @param _localToken Address of the ERC20 on this chain. + /// @param _remoteToken Address of the corresponding token on the remote chain. + /// @param _from Address of the sender. + /// @param _to Address of the receiver. + /// @param _amount Amount of the ERC20 being bridged. + /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will + /// not be triggered with this data, but it will be emitted and can be used + /// to identify the transaction. + function finalizeBridgeERC20( + address _localToken, + address _remoteToken, + address _from, + address _to, + uint256 _amount, + bytes calldata _extraData + ) + public + onlyOtherBridge + { + require(paused() == false, "StandardBridge: paused"); + if (_isOptimismMintableERC20(_localToken)) { + require( + _isCorrectTokenPair(_localToken, _remoteToken), + "StandardBridge: wrong remote token for Optimism Mintable ERC20 local token" + ); + + OptimismMintableERC20(_localToken).mint(_to, _amount); + } else { + deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] - _amount; + IERC20(_localToken).safeTransfer(_to, _amount); + } + + // Emit the correct events. By default this will be ERC20BridgeFinalized, but child + // contracts may override this function in order to emit legacy events as well. + _emitERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); + } + + /// @notice Initiates a bridge of ETH through the CrossDomainMessenger. + /// @param _from Address of the sender. + /// @param _to Address of the receiver. + /// @param _amount Amount of ETH being bridged. + /// @param _minGasLimit Minimum amount of gas that the bridge can be relayed with. + /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will + /// not be triggered with this data, but it will be emitted and can be used + /// to identify the transaction. + function _initiateBridgeETH( + address _from, + address _to, + uint256 _amount, + uint32 _minGasLimit, + bytes memory _extraData + ) + internal + { + require(isCustomGasToken() == false, "StandardBridge: cannot bridge ETH with custom gas token"); + require(msg.value == _amount, "StandardBridge: bridging ETH must include sufficient ETH value"); + + // Emit the correct events. By default this will be _amount, but child + // contracts may override this function in order to emit legacy events as well. + _emitETHBridgeInitiated(_from, _to, _amount, _extraData); + + messenger.sendMessage{ value: _amount }({ + _target: address(otherBridge), + _message: abi.encodeWithSelector(this.finalizeBridgeETH.selector, _from, _to, _amount, _extraData), + _minGasLimit: _minGasLimit + }); + } + + function _initiateBridgeERC20( + address _localToken, + address _remoteToken, + address _from, + address _to, + uint256 _amount, + uint32 _minGasLimit, + bytes memory _extraData + ) + internal + { + _initiateBridgeERC20({ + _localToken: _localToken, + _remoteToken: _remoteToken, + _from: _from, + _to: _to, + _amount: _amount, + _minGasLimit: _minGasLimit, + _extraData: _extraData, + _performSafeTransferFrom: true, + _allowMsgValue: false + }); + } + + /// @notice Sends ERC20 tokens to a receiver's address on the other chain. + /// @param _localToken Address of the ERC20 on this chain. + /// @param _remoteToken Address of the corresponding token on the remote chain. + /// @param _to Address of the receiver. + /// @param _amount Amount of local tokens to deposit. + /// @param _minGasLimit Minimum amount of gas that the bridge can be relayed with. + /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will + /// not be triggered with this data, but it will be emitted and can be used + /// to identify the transaction. + function _initiateBridgeERC20( + address _localToken, + address _remoteToken, + address _from, + address _to, + uint256 _amount, + uint32 _minGasLimit, + bytes memory _extraData, + bool _performSafeTransferFrom, + bool _allowMsgValue + ) + internal + virtual + { + require(msg.value == 0 || _allowMsgValue, "StandardBridge: cannot send value"); + + if (_isOptimismMintableERC20(_localToken)) { + require( + _isCorrectTokenPair(_localToken, _remoteToken), + "StandardBridge: wrong remote token for Optimism Mintable ERC20 local token" + ); + + OptimismMintableERC20(_localToken).burn(_from, _amount); + } else { + if (_performSafeTransferFrom) { + IERC20(_localToken).safeTransferFrom(_from, address(this), _amount); + } + + deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] + _amount; + } + + // Emit the correct events. By default this will be ERC20BridgeInitiated, but child + // contracts may override this function in order to emit legacy events as well. + _emitERC20BridgeInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData); + + if (onL1()) { + bytes32 depositId = generateDepositId(); + + _sendERC20DepositMessage({ + _depositId: depositId, + _l1Token: _localToken, + _l2Token: _remoteToken, + _from: _from, + _to: _to, + _amount: _amount, + _extraData: _extraData, + _isInitialDeposit: true + }); + } else { + messenger.sendMessage({ + _target: address(otherBridge), + _message: abi.encodeWithSelector( + this.finalizeBridgeERC20.selector, + // Because this call will be executed on the remote chain, we reverse the order of + // the remote and local token addresses relative to their order in the + // finalizeBridgeERC20 function. + _remoteToken, + _localToken, + _from, + _to, + _amount, + _extraData + ), + _minGasLimit: _minGasLimit + }); + } + } + + /// @notice Checks if a given address is an OptimismMintableERC20. Not perfect, but good enough. + /// Just the way we like it. + /// @param _token Address of the token to check. + /// @return True if the token is an OptimismMintableERC20. + function _isOptimismMintableERC20(address _token) internal view returns (bool) { + return ERC165Checker.supportsInterface(_token, type(ILegacyMintableERC20).interfaceId) + || ERC165Checker.supportsInterface(_token, type(IOptimismMintableERC20).interfaceId); + } + + /// @notice Checks if the "other token" is the correct pair token for the OptimismMintableERC20. + /// Calls can be saved in the future by combining this logic with + /// `_isOptimismMintableERC20`. + /// @param _mintableToken OptimismMintableERC20 to check against. + /// @param _otherToken Pair token to check. + /// @return True if the other token is the correct pair token for the OptimismMintableERC20. + function _isCorrectTokenPair(address _mintableToken, address _otherToken) internal view returns (bool) { + if (ERC165Checker.supportsInterface(_mintableToken, type(ILegacyMintableERC20).interfaceId)) { + return _otherToken == ILegacyMintableERC20(_mintableToken).l1Token(); + } else { + return _otherToken == IOptimismMintableERC20(_mintableToken).remoteToken(); + } + } + + /// @notice Emits the ETHBridgeInitiated event and if necessary the appropriate legacy event + /// when an ETH bridge is finalized on this chain. + /// @param _from Address of the sender. + /// @param _to Address of the receiver. + /// @param _amount Amount of ETH sent. + /// @param _extraData Extra data sent with the transaction. + function _emitETHBridgeInitiated( + address _from, + address _to, + uint256 _amount, + bytes memory _extraData + ) + internal + virtual + { + emit ETHBridgeInitiated(_from, _to, _amount, _extraData); + } + + /// @notice Emits the ETHBridgeFinalized and if necessary the appropriate legacy event when an + /// ETH bridge is finalized on this chain. + /// @param _from Address of the sender. + /// @param _to Address of the receiver. + /// @param _amount Amount of ETH sent. + /// @param _extraData Extra data sent with the transaction. + function _emitETHBridgeFinalized( + address _from, + address _to, + uint256 _amount, + bytes memory _extraData + ) + internal + virtual + { + emit ETHBridgeFinalized(_from, _to, _amount, _extraData); + } + + /// @notice Emits the ERC20BridgeInitiated event and if necessary the appropriate legacy + /// event when an ERC20 bridge is initiated to the other chain. + /// @param _localToken Address of the ERC20 on this chain. + /// @param _remoteToken Address of the ERC20 on the remote chain. + /// @param _from Address of the sender. + /// @param _to Address of the receiver. + /// @param _amount Amount of the ERC20 sent. + /// @param _extraData Extra data sent with the transaction. + function _emitERC20BridgeInitiated( + address _localToken, + address _remoteToken, + address _from, + address _to, + uint256 _amount, + bytes memory _extraData + ) + internal + virtual + { + emit ERC20BridgeInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData); + } + + /// @notice Emits the ERC20BridgeFinalized event and if necessary the appropriate legacy + /// event when an ERC20 bridge is initiated to the other chain. + /// @param _localToken Address of the ERC20 on this chain. + /// @param _remoteToken Address of the ERC20 on the remote chain. + /// @param _from Address of the sender. + /// @param _to Address of the receiver. + /// @param _amount Amount of the ERC20 sent. + /// @param _extraData Extra data sent with the transaction. + function _emitERC20BridgeFinalized( + address _localToken, + address _remoteToken, + address _from, + address _to, + uint256 _amount, + bytes memory _extraData + ) + internal + virtual + { + emit ERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); + } +} + +contract PausedL1StandardBridge is StandardBridge, ISemver { + /// @custom:legacy + /// @notice Emitted whenever a deposit of ETH from L1 into L2 is initiated. + /// @param from Address of the depositor. + /// @param to Address of the recipient on L2. + /// @param amount Amount of ETH deposited. + /// @param extraData Extra data attached to the deposit. + event ETHDepositInitiated(address indexed from, address indexed to, uint256 amount, bytes extraData); + + /// @custom:legacy + /// @notice Emitted whenever a withdrawal of ETH from L2 to L1 is finalized. + /// @param from Address of the withdrawer. + /// @param to Address of the recipient on L1. + /// @param amount Amount of ETH withdrawn. + /// @param extraData Extra data attached to the withdrawal. + event ETHWithdrawalFinalized(address indexed from, address indexed to, uint256 amount, bytes extraData); + + /// @custom:legacy + /// @notice Emitted whenever an ERC20 deposit is initiated. + /// @param l1Token Address of the token on L1. + /// @param l2Token Address of the corresponding token on L2. + /// @param from Address of the depositor. + /// @param to Address of the recipient on L2. + /// @param amount Amount of the ERC20 deposited. + /// @param extraData Extra data attached to the deposit. + event ERC20DepositInitiated( + address indexed l1Token, + address indexed l2Token, + address indexed from, + address to, + uint256 amount, + bytes extraData + ); + + /// @custom:legacy + /// @notice Emitted whenever an ERC20 withdrawal is finalized. + /// @param l1Token Address of the token on L1. + /// @param l2Token Address of the corresponding token on L2. + /// @param from Address of the withdrawer. + /// @param to Address of the recipient on L1. + /// @param amount Amount of the ERC20 withdrawn. + /// @param extraData Extra data attached to the withdrawal. + event ERC20WithdrawalFinalized( + address indexed l1Token, + address indexed l2Token, + address indexed from, + address to, + uint256 amount, + bytes extraData + ); + + /// @notice Semantic version. + /// @custom:semver 2.2.0 + string public constant version = "2.2.0"; + + /// @notice Address of the SuperchainConfig contract. + SuperchainConfig public superchainConfig; + + /// @notice Address of the SystemConfig contract. + SystemConfig public systemConfig; + + function onL1() internal pure override returns (bool) { + return true; + } + + /// @notice Constructs the L1StandardBridge contract. + constructor() StandardBridge() { + initialize({ + _messenger: CrossDomainMessenger(address(0)), + _superchainConfig: SuperchainConfig(address(0)), + _systemConfig: SystemConfig(address(0)), + _otherBridge: StandardBridge(payable(address(0))) + }); + } + + /// @notice Initializer. + /// @param _messenger Contract for the CrossDomainMessenger on this network. + /// @param _superchainConfig Contract for the SuperchainConfig on this network. + function initialize( + CrossDomainMessenger _messenger, + SuperchainConfig _superchainConfig, + SystemConfig _systemConfig, + StandardBridge _otherBridge + ) + public + initializer + { + superchainConfig = _superchainConfig; + systemConfig = _systemConfig; + __StandardBridge_init({ + _messenger: _messenger, + _otherBridge: _otherBridge + }); + } + + function adminWithdraw(address recipient, uint256 amount) external { + require(msg.sender == admin(), "Only admin can call this function"); + + weth().withdraw(amount); + Donateable(recipient).donateETH{value: amount}(); + } + + function admin() public pure returns (address) { + return 0xb2B01DeCb6cd36E7396b78D3744482627F22C525; + } + + function weth() public view returns (IWETH) { + address addr; + + if (block.chainid == 1) { + addr = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + } else if (block.chainid == 11155111) { + addr = 0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9; + } else { + revert("Unsupported chain"); + } + + return IWETH(addr); + } + + receive() external payable override { + } + + /// @inheritdoc StandardBridge + function gasPayingToken() internal view override returns (address addr_, uint8 decimals_) { + (addr_, decimals_) = systemConfig.gasPayingToken(); + } + + /// @custom:legacy + /// @notice Deposits some amount of ETH into the sender's account on L2. + /// @param _minGasLimit Minimum gas limit for the deposit message on L2. + /// @param _extraData Optional data to forward to L2. + /// Data supplied here will not be used to execute any code on L2 and is + /// only emitted as extra data for the convenience of off-chain tooling. + function depositETH(uint32 _minGasLimit, bytes calldata _extraData) external payable onlyEOA { + _initiateETHDeposit(msg.sender, msg.sender, _minGasLimit, _extraData); + } + + /// @custom:legacy + /// @notice Deposits some amount of ETH into a target account on L2. + /// Note that if ETH is sent to a contract on L2 and the call fails, then that ETH will + /// be locked in the L2StandardBridge. ETH may be recoverable if the call can be + /// successfully replayed by increasing the amount of gas supplied to the call. If the + /// call will fail for any amount of gas, then the ETH will be locked permanently. + /// @param _to Address of the recipient on L2. + /// @param _minGasLimit Minimum gas limit for the deposit message on L2. + /// @param _extraData Optional data to forward to L2. + /// Data supplied here will not be used to execute any code on L2 and is + /// only emitted as extra data for the convenience of off-chain tooling. + function depositETHTo(address _to, uint32 _minGasLimit, bytes calldata _extraData) external payable { + _initiateETHDeposit(msg.sender, _to, _minGasLimit, _extraData); + } + + /// @custom:legacy + /// @notice Deposits some amount of ERC20 tokens into the sender's account on L2. + /// @param _l1Token Address of the L1 token being deposited. + /// @param _l2Token Address of the corresponding token on L2. + /// @param _amount Amount of the ERC20 to deposit. + /// @param _minGasLimit Minimum gas limit for the deposit message on L2. + /// @param _extraData Optional data to forward to L2. + /// Data supplied here will not be used to execute any code on L2 and is + /// only emitted as extra data for the convenience of off-chain tooling. + function depositERC20( + address _l1Token, + address _l2Token, + uint256 _amount, + uint32 _minGasLimit, + bytes calldata _extraData + ) + external + virtual + onlyEOA + { + _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, msg.sender, _amount, _minGasLimit, _extraData); + } + + /// @custom:legacy + /// @notice Deposits some amount of ERC20 tokens into a target account on L2. + /// @param _l1Token Address of the L1 token being deposited. + /// @param _l2Token Address of the corresponding token on L2. + /// @param _to Address of the recipient on L2. + /// @param _amount Amount of the ERC20 to deposit. + /// @param _minGasLimit Minimum gas limit for the deposit message on L2. + /// @param _extraData Optional data to forward to L2. + /// Data supplied here will not be used to execute any code on L2 and is + /// only emitted as extra data for the convenience of off-chain tooling. + function depositERC20To( + address _l1Token, + address _l2Token, + address _to, + uint256 _amount, + uint32 _minGasLimit, + bytes calldata _extraData + ) + external + virtual + { + _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, _to, _amount, _minGasLimit, _extraData); + } + + function bridgeETHToWETH( + IWETH _localWeth, + address _remoteToken, + uint256 _amount, + uint32 _minGasLimit, + bytes calldata _extraData + ) external virtual payable onlyEOA { + bridgeETHToWETHTo({ + _localWeth: _localWeth, + _remoteToken: _remoteToken, + _to: msg.sender, + _amount: _amount, + _minGasLimit: _minGasLimit, + _extraData: _extraData + }); + } + + function bridgeETHToWETHTo( + IWETH _localWeth, + address _remoteToken, + address _to, + uint256 _amount, + uint32 _minGasLimit, + bytes calldata _extraData + ) + public + virtual + payable + { + require(msg.value == _amount, "Invalid amount"); + require(msg.value > 0, "Invalid amount"); + + _localWeth.deposit{value: _amount}(); + + _initiateBridgeERC20({ + _localToken: address(_localWeth), + _remoteToken: _remoteToken, + _from: msg.sender, + _to: _to, + _amount: _amount, + _minGasLimit: _minGasLimit, + _extraData: _extraData, + _performSafeTransferFrom: false, + _allowMsgValue: true + }); + } + + /// @custom:legacy + /// @notice Finalizes a withdrawal of ETH from L2. + /// @param _from Address of the withdrawer on L2. + /// @param _to Address of the recipient on L1. + /// @param _amount Amount of ETH to withdraw. + /// @param _extraData Optional data forwarded from L2. + function finalizeETHWithdrawal( + address _from, + address _to, + uint256 _amount, + bytes calldata _extraData + ) + external + payable + { + finalizeBridgeETH(_from, _to, _amount, _extraData); + } + + /// @custom:legacy + /// @notice Finalizes a withdrawal of ERC20 tokens from L2. + /// @param _l1Token Address of the token on L1. + /// @param _l2Token Address of the corresponding token on L2. + /// @param _from Address of the withdrawer on L2. + /// @param _to Address of the recipient on L1. + /// @param _amount Amount of the ERC20 to withdraw. + /// @param _extraData Optional data forwarded from L2. + function finalizeERC20Withdrawal( + address _l1Token, + address _l2Token, + address _from, + address _to, + uint256 _amount, + bytes calldata _extraData + ) + external + { + finalizeBridgeERC20(_l1Token, _l2Token, _from, _to, _amount, _extraData); + } + + /// @custom:legacy + /// @notice Retrieves the access of the corresponding L2 bridge contract. + /// @return Address of the corresponding L2 bridge contract. + function l2TokenBridge() external view returns (address) { + return address(otherBridge); + } + + /// @notice Internal function for initiating an ETH deposit. + /// @param _from Address of the sender on L1. + /// @param _to Address of the recipient on L2. + /// @param _minGasLimit Minimum gas limit for the deposit message on L2. + /// @param _extraData Optional data to forward to L2. + function _initiateETHDeposit(address _from, address _to, uint32 _minGasLimit, bytes memory _extraData) internal { + _initiateBridgeETH(_from, _to, msg.value, _minGasLimit, _extraData); + } + + /// @notice Internal function for initiating an ERC20 deposit. + /// @param _l1Token Address of the L1 token being deposited. + /// @param _l2Token Address of the corresponding token on L2. + /// @param _from Address of the sender on L1. + /// @param _to Address of the recipient on L2. + /// @param _amount Amount of the ERC20 to deposit. + /// @param _minGasLimit Minimum gas limit for the deposit message on L2. + /// @param _extraData Optional data to forward to L2. + function _initiateERC20Deposit( + address _l1Token, + address _l2Token, + address _from, + address _to, + uint256 _amount, + uint32 _minGasLimit, + bytes memory _extraData + ) + internal + { + _initiateBridgeERC20(_l1Token, _l2Token, _from, _to, _amount, _minGasLimit, _extraData); + } + + function _initiateBridgeERC20( + address _localToken, + address _remoteToken, + address _from, + address _to, + uint256 _amount, + uint32 _minGasLimit, + bytes memory _extraData, + bool _performSafeTransferFrom, + bool _allowMsgValue + ) + internal + pure + override { + revert("Use the fast bridge"); + } + + /// @inheritdoc StandardBridge + /// @notice Emits the legacy ETHDepositInitiated event followed by the ETHBridgeInitiated event. + /// This is necessary for backwards compatibility with the legacy bridge. + function _emitETHBridgeInitiated( + address _from, + address _to, + uint256 _amount, + bytes memory _extraData + ) + internal + override + { + emit ETHDepositInitiated(_from, _to, _amount, _extraData); + super._emitETHBridgeInitiated(_from, _to, _amount, _extraData); + } + + /// @inheritdoc StandardBridge + /// @notice Emits the legacy ERC20DepositInitiated event followed by the ERC20BridgeInitiated + /// event. This is necessary for backwards compatibility with the legacy bridge. + function _emitETHBridgeFinalized( + address _from, + address _to, + uint256 _amount, + bytes memory _extraData + ) + internal + override + { + emit ETHWithdrawalFinalized(_from, _to, _amount, _extraData); + super._emitETHBridgeFinalized(_from, _to, _amount, _extraData); + } + + /// @inheritdoc StandardBridge + /// @notice Emits the legacy ERC20WithdrawalFinalized event followed by the ERC20BridgeFinalized + /// event. This is necessary for backwards compatibility with the legacy bridge. + function _emitERC20BridgeInitiated( + address _localToken, + address _remoteToken, + address _from, + address _to, + uint256 _amount, + bytes memory _extraData + ) + internal + override + { + emit ERC20DepositInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData); + super._emitERC20BridgeInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData); + } + + /// @inheritdoc StandardBridge + /// @notice Emits the legacy ERC20WithdrawalFinalized event followed by the ERC20BridgeFinalized + /// event. This is necessary for backwards compatibility with the legacy bridge. + function _emitERC20BridgeFinalized( + address _localToken, + address _remoteToken, + address _from, + address _to, + uint256 _amount, + bytes memory _extraData + ) + internal + override + { + emit ERC20WithdrawalFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); + super._emitERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); + } +} \ No newline at end of file diff --git a/packages/backend/discovery/_templates/opstack/L1StandardBridge_facet/template.jsonc b/packages/backend/discovery/_templates/opstack/L1StandardBridge_facet/template.jsonc new file mode 100644 index 00000000000..041d9f2f9d6 --- /dev/null +++ b/packages/backend/discovery/_templates/opstack/L1StandardBridge_facet/template.jsonc @@ -0,0 +1,18 @@ +{ + "$schema": "../../../../../discovery/schemas/contract.v2.schema.json", + "displayName": "L1StandardBridge", + "description": "The main entry point to deposit ERC20 tokens from host chain to this chain. This contract can store any token.", + "ignoreRelatives": ["OTHER_BRIDGE", "otherBridge", "l2TokenBridge", "weth"], + "fields": { + "$admin": { + "target": { + "permissions": [ + { + "type": "upgrade", + "description": "upgrading the bridge implementation can give access to all funds escrowed therein." + } + ] + } + } + } +} diff --git a/packages/backend/discovery/_templates/opstack/OptimismPortal/shape/OptimismPortal_v2_8_1_beta_1_facet.sol b/packages/backend/discovery/_templates/opstack/OptimismPortal/shape/OptimismPortal_v2_8_1_beta_1_facet.sol new file mode 100644 index 00000000000..890d67b7c09 --- /dev/null +++ b/packages/backend/discovery/_templates/opstack/OptimismPortal/shape/OptimismPortal_v2_8_1_beta_1_facet.sol @@ -0,0 +1,3395 @@ +// SPDX-License-Identifier: Unknown +pragma solidity 0.8.15; + +library Predeploys { + /// @notice Number of predeploy-namespace addresses reserved for protocol usage. + uint256 internal constant PREDEPLOY_COUNT = 2048; + + /// @custom:legacy + /// @notice Address of the LegacyMessagePasser predeploy. Deprecate. Use the updated + /// L2ToL1MessagePasser contract instead. + address internal constant LEGACY_MESSAGE_PASSER = 0x4200000000000000000000000000000000000000; + + /// @custom:legacy + /// @notice Address of the L1MessageSender predeploy. Deprecated. Use L2CrossDomainMessenger + /// or access tx.origin (or msg.sender) in a L1 to L2 transaction instead. + /// Not embedded into new OP-Stack chains. + address internal constant L1_MESSAGE_SENDER = 0x4200000000000000000000000000000000000001; + + /// @custom:legacy + /// @notice Address of the DeployerWhitelist predeploy. No longer active. + address internal constant DEPLOYER_WHITELIST = 0x4200000000000000000000000000000000000002; + + /// @notice Address of the canonical WETH contract. + address internal constant WETH = 0x4200000000000000000000000000000000000006; + + /// @notice Address of the L2CrossDomainMessenger predeploy. + address internal constant L2_CROSS_DOMAIN_MESSENGER = 0x4200000000000000000000000000000000000007; + + /// @notice Address of the GasPriceOracle predeploy. Includes fee information + /// and helpers for computing the L1 portion of the transaction fee. + address internal constant GAS_PRICE_ORACLE = 0x420000000000000000000000000000000000000F; + + /// @notice Address of the L2StandardBridge predeploy. + address internal constant L2_STANDARD_BRIDGE = 0x4200000000000000000000000000000000000010; + + //// @notice Address of the SequencerFeeWallet predeploy. + address internal constant SEQUENCER_FEE_WALLET = 0x4200000000000000000000000000000000000011; + + /// @notice Address of the OptimismMintableERC20Factory predeploy. + address internal constant OPTIMISM_MINTABLE_ERC20_FACTORY = 0x4200000000000000000000000000000000000012; + + /// @custom:legacy + /// @notice Address of the L1BlockNumber predeploy. Deprecated. Use the L1Block predeploy + /// instead, which exposes more information about the L1 state. + address internal constant L1_BLOCK_NUMBER = 0x4200000000000000000000000000000000000013; + + /// @notice Address of the L2ERC721Bridge predeploy. + address internal constant L2_ERC721_BRIDGE = 0x4200000000000000000000000000000000000014; + + /// @notice Address of the L1Block predeploy. + address internal constant L1_BLOCK_ATTRIBUTES = 0x4200000000000000000000000000000000000015; + + /// @notice Address of the L2ToL1MessagePasser predeploy. + address internal constant L2_TO_L1_MESSAGE_PASSER = 0x4200000000000000000000000000000000000016; + + /// @notice Address of the OptimismMintableERC721Factory predeploy. + address internal constant OPTIMISM_MINTABLE_ERC721_FACTORY = 0x4200000000000000000000000000000000000017; + + /// @notice Address of the ProxyAdmin predeploy. + address internal constant PROXY_ADMIN = 0x4200000000000000000000000000000000000018; + + /// @notice Address of the BaseFeeVault predeploy. + address internal constant BASE_FEE_VAULT = 0x4200000000000000000000000000000000000019; + + /// @notice Address of the L1FeeVault predeploy. + address internal constant L1_FEE_VAULT = 0x420000000000000000000000000000000000001A; + + /// @notice Address of the SchemaRegistry predeploy. + address internal constant SCHEMA_REGISTRY = 0x4200000000000000000000000000000000000020; + + /// @notice Address of the EAS predeploy. + address internal constant EAS = 0x4200000000000000000000000000000000000021; + + /// @notice Address of the GovernanceToken predeploy. + address internal constant GOVERNANCE_TOKEN = 0x4200000000000000000000000000000000000042; + + /// @custom:legacy + /// @notice Address of the LegacyERC20ETH predeploy. Deprecated. Balances are migrated to the + /// state trie as of the Bedrock upgrade. Contract has been locked and write functions + /// can no longer be accessed. + address internal constant LEGACY_ERC20_ETH = 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000; + + /// @notice Address of the CrossL2Inbox predeploy. + address internal constant CROSS_L2_INBOX = 0x4200000000000000000000000000000000000022; + + /// @notice Address of the L2ToL2CrossDomainMessenger predeploy. + address internal constant L2_TO_L2_CROSS_DOMAIN_MESSENGER = 0x4200000000000000000000000000000000000023; + + /// @notice Address of the SuperchainWETH predeploy. + address internal constant SUPERCHAIN_WETH = 0x4200000000000000000000000000000000000024; + + /// @notice Address of the ETHLiquidity predeploy. + address internal constant ETH_LIQUIDITY = 0x4200000000000000000000000000000000000025; + + /// TODO: Add correct predeploy address for OptimismSuperchainERC20Factory + /// @notice Address of the OptimismSuperchainERC20Factory predeploy. + address internal constant OPTIMISM_SUPERCHAIN_ERC20_FACTORY = 0x4200000000000000000000000000000000000026; + + /// @notice Returns the name of the predeploy at the given address. + function getName(address _addr) internal pure returns (string memory out_) { + require(isPredeployNamespace(_addr), "Predeploys: address must be a predeploy"); + if (_addr == LEGACY_MESSAGE_PASSER) return "LegacyMessagePasser"; + if (_addr == L1_MESSAGE_SENDER) return "L1MessageSender"; + if (_addr == DEPLOYER_WHITELIST) return "DeployerWhitelist"; + if (_addr == WETH) return "WETH"; + if (_addr == L2_CROSS_DOMAIN_MESSENGER) return "L2CrossDomainMessenger"; + if (_addr == GAS_PRICE_ORACLE) return "GasPriceOracle"; + if (_addr == L2_STANDARD_BRIDGE) return "L2StandardBridge"; + if (_addr == SEQUENCER_FEE_WALLET) return "SequencerFeeVault"; + if (_addr == OPTIMISM_MINTABLE_ERC20_FACTORY) return "OptimismMintableERC20Factory"; + if (_addr == L1_BLOCK_NUMBER) return "L1BlockNumber"; + if (_addr == L2_ERC721_BRIDGE) return "L2ERC721Bridge"; + if (_addr == L1_BLOCK_ATTRIBUTES) return "L1Block"; + if (_addr == L2_TO_L1_MESSAGE_PASSER) return "L2ToL1MessagePasser"; + if (_addr == OPTIMISM_MINTABLE_ERC721_FACTORY) return "OptimismMintableERC721Factory"; + if (_addr == PROXY_ADMIN) return "ProxyAdmin"; + if (_addr == BASE_FEE_VAULT) return "BaseFeeVault"; + if (_addr == L1_FEE_VAULT) return "L1FeeVault"; + if (_addr == SCHEMA_REGISTRY) return "SchemaRegistry"; + if (_addr == EAS) return "EAS"; + if (_addr == GOVERNANCE_TOKEN) return "GovernanceToken"; + if (_addr == LEGACY_ERC20_ETH) return "LegacyERC20ETH"; + if (_addr == CROSS_L2_INBOX) return "CrossL2Inbox"; + if (_addr == L2_TO_L2_CROSS_DOMAIN_MESSENGER) return "L2ToL2CrossDomainMessenger"; + if (_addr == SUPERCHAIN_WETH) return "SuperchainWETH"; + if (_addr == ETH_LIQUIDITY) return "ETHLiquidity"; + if (_addr == OPTIMISM_SUPERCHAIN_ERC20_FACTORY) return "OptimismSuperchainERC20Factory"; + revert("Predeploys: unnamed predeploy"); + } + + /// @notice Returns true if the predeploy is not proxied. + function notProxied(address _addr) internal pure returns (bool) { + return _addr == GOVERNANCE_TOKEN || _addr == WETH; + } + + /// @notice Returns true if the address is a defined predeploy that is embedded into new OP-Stack chains. + function isSupportedPredeploy(address _addr, bool) internal pure returns (bool) { + return _addr == WETH + || _addr == L1_BLOCK_ATTRIBUTES + || _addr == L2_TO_L1_MESSAGE_PASSER + || _addr == PROXY_ADMIN + || _addr == SCHEMA_REGISTRY + || _addr == EAS; + } + + function isPredeployNamespace(address _addr) internal pure returns (bool) { + return uint160(_addr) >> 11 == uint160(0x4200000000000000000000000000000000000000) >> 11; + } + + /// @notice Function to compute the expected address of the predeploy implementation + /// in the genesis state. + function predeployToCodeNamespace(address _addr) internal pure returns (address) { + require( + isPredeployNamespace(_addr), "Predeploys: can only derive code-namespace address for predeploy addresses" + ); + return address( + uint160(uint256(uint160(_addr)) & 0xffff | uint256(uint160(0xc0D3C0d3C0d3C0D3c0d3C0d3c0D3C0d3c0d30000))) + ); + } +} + +library AddressAliasHelper { + uint160 constant offset = uint160(0x1111000000000000000000000000000000001111); + + /// @notice Utility function that converts the address in the L1 that submitted a tx to + /// the inbox to the msg.sender viewed in the L2 + /// @param l1Address the address in the L1 that triggered the tx to L2 + /// @return l2Address L2 address as viewed in msg.sender + function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) { + unchecked { + l2Address = address(uint160(l1Address) + offset); + } + } + + /// @notice Utility function that converts the msg.sender viewed in the L2 to the + /// address in the L1 that submitted a tx to the inbox + /// @param l2Address L2 address as viewed in msg.sender + /// @return l1Address the address in the L1 that triggered the tx to L2 + function undoL1ToL2Alias(address l2Address) internal pure returns (address l1Address) { + unchecked { + l1Address = address(uint160(l2Address) - offset); + } + } +} + +library SafeCall { + /// @notice Performs a low level call without copying any returndata. + /// @dev Passes no calldata to the call context. + /// @param _target Address to call + /// @param _gas Amount of gas to pass to the call + /// @param _value Amount of value to pass to the call + function send(address _target, uint256 _gas, uint256 _value) internal returns (bool success_) { + assembly { + success_ := + call( + _gas, // gas + _target, // recipient + _value, // ether value + 0, // inloc + 0, // inlen + 0, // outloc + 0 // outlen + ) + } + } + + /// @notice Perform a low level call with all gas without copying any returndata + /// @param _target Address to call + /// @param _value Amount of value to pass to the call + function send(address _target, uint256 _value) internal returns (bool success_) { + success_ = send(_target, gasleft(), _value); + } + + /// @notice Perform a low level call without copying any returndata + /// @param _target Address to call + /// @param _gas Amount of gas to pass to the call + /// @param _value Amount of value to pass to the call + /// @param _calldata Calldata to pass to the call + function call( + address _target, + uint256 _gas, + uint256 _value, + bytes memory _calldata + ) + internal + returns (bool success_) + { + assembly { + success_ := + call( + _gas, // gas + _target, // recipient + _value, // ether value + add(_calldata, 32), // inloc + mload(_calldata), // inlen + 0, // outloc + 0 // outlen + ) + } + } + + /// @notice Perform a low level call without copying any returndata + /// @param _target Address to call + /// @param _value Amount of value to pass to the call + /// @param _calldata Calldata to pass to the call + function call(address _target, uint256 _value, bytes memory _calldata) internal returns (bool success_) { + success_ = call({ _target: _target, _gas: gasleft(), _value: _value, _calldata: _calldata }); + } + + /// @notice Helper function to determine if there is sufficient gas remaining within the context + /// to guarantee that the minimum gas requirement for a call will be met as well as + /// optionally reserving a specified amount of gas for after the call has concluded. + /// @param _minGas The minimum amount of gas that may be passed to the target context. + /// @param _reservedGas Optional amount of gas to reserve for the caller after the execution + /// of the target context. + /// @return `true` if there is enough gas remaining to safely supply `_minGas` to the target + /// context as well as reserve `_reservedGas` for the caller after the execution of + /// the target context. + /// @dev !!!!! FOOTGUN ALERT !!!!! + /// 1.) The 40_000 base buffer is to account for the worst case of the dynamic cost of the + /// `CALL` opcode's `address_access_cost`, `positive_value_cost`, and + /// `value_to_empty_account_cost` factors with an added buffer of 5,700 gas. It is + /// still possible to self-rekt by initiating a withdrawal with a minimum gas limit + /// that does not account for the `memory_expansion_cost` & `code_execution_cost` + /// factors of the dynamic cost of the `CALL` opcode. + /// 2.) This function should *directly* precede the external call if possible. There is an + /// added buffer to account for gas consumed between this check and the call, but it + /// is only 5,700 gas. + /// 3.) Because EIP-150 ensures that a maximum of 63/64ths of the remaining gas in the call + /// frame may be passed to a subcontext, we need to ensure that the gas will not be + /// truncated. + /// 4.) Use wisely. This function is not a silver bullet. + function hasMinGas(uint256 _minGas, uint256 _reservedGas) internal view returns (bool) { + bool _hasMinGas; + assembly { + // Equation: gas × 63 ≥ minGas × 64 + 63(40_000 + reservedGas) + _hasMinGas := iszero(lt(mul(gas(), 63), add(mul(_minGas, 64), mul(add(40000, _reservedGas), 63)))) + } + return _hasMinGas; + } + + /// @notice Perform a low level call without copying any returndata. This function + /// will revert if the call cannot be performed with the specified minimum + /// gas. + /// @param _target Address to call + /// @param _minGas The minimum amount of gas that may be passed to the call + /// @param _value Amount of value to pass to the call + /// @param _calldata Calldata to pass to the call + function callWithMinGas( + address _target, + uint256 _minGas, + uint256 _value, + bytes memory _calldata + ) + internal + returns (bool) + { + bool _success; + bool _hasMinGas = hasMinGas(_minGas, 0); + assembly { + // Assertion: gasleft() >= (_minGas * 64) / 63 + 40_000 + if iszero(_hasMinGas) { + // Store the "Error(string)" selector in scratch space. + mstore(0, 0x08c379a0) + // Store the pointer to the string length in scratch space. + mstore(32, 32) + // Store the string. + // + // SAFETY: + // - We pad the beginning of the string with two zero bytes as well as the + // length (24) to ensure that we override the free memory pointer at offset + // 0x40. This is necessary because the free memory pointer is likely to + // be greater than 1 byte when this function is called, but it is incredibly + // unlikely that it will be greater than 3 bytes. As for the data within + // 0x60, it is ensured that it is 0 due to 0x60 being the zero offset. + // - It's fine to clobber the free memory pointer, we're reverting. + mstore(88, 0x0000185361666543616c6c3a204e6f7420656e6f75676820676173) + + // Revert with 'Error("SafeCall: Not enough gas")' + revert(28, 100) + } + + // The call will be supplied at least ((_minGas * 64) / 63) gas due to the + // above assertion. This ensures that, in all circumstances (except for when the + // `_minGas` does not account for the `memory_expansion_cost` and `code_execution_cost` + // factors of the dynamic cost of the `CALL` opcode), the call will receive at least + // the minimum amount of gas specified. + _success := + call( + gas(), // gas + _target, // recipient + _value, // ether value + add(_calldata, 32), // inloc + mload(_calldata), // inlen + 0x00, // outloc + 0x00 // outlen + ) + } + return _success; + } +} + +library RLPReader { + /// @notice Custom pointer type to avoid confusion between pointers and uint256s. + type MemoryPointer is uint256; + + /// @notice RLP item types. + /// @custom:value DATA_ITEM Represents an RLP data item (NOT a list). + /// @custom:value LIST_ITEM Represents an RLP list item. + enum RLPItemType { + DATA_ITEM, + LIST_ITEM + } + + /// @notice Struct representing an RLP item. + /// @custom:field length Length of the RLP item. + /// @custom:field ptr Pointer to the RLP item in memory. + struct RLPItem { + uint256 length; + MemoryPointer ptr; + } + + /// @notice Max list length that this library will accept. + uint256 internal constant MAX_LIST_LENGTH = 32; + + /// @notice Converts bytes to a reference to memory position and length. + /// @param _in Input bytes to convert. + /// @return out_ Output memory reference. + function toRLPItem(bytes memory _in) internal pure returns (RLPItem memory out_) { + // Empty arrays are not RLP items. + if (_in.length == 0) revert EmptyItem(); + + MemoryPointer ptr; + assembly { + ptr := add(_in, 32) + } + + out_ = RLPItem({ length: _in.length, ptr: ptr }); + } + + /// @notice Reads an RLP list value into a list of RLP items. + /// @param _in RLP list value. + /// @return out_ Decoded RLP list items. + function readList(RLPItem memory _in) internal pure returns (RLPItem[] memory out_) { + (uint256 listOffset, uint256 listLength, RLPItemType itemType) = _decodeLength(_in); + + if (itemType != RLPItemType.LIST_ITEM) revert UnexpectedString(); + + if (listOffset + listLength != _in.length) revert InvalidDataRemainder(); + + // Solidity in-memory arrays can't be increased in size, but *can* be decreased in size by + // writing to the length. Since we can't know the number of RLP items without looping over + // the entire input, we'd have to loop twice to accurately size this array. It's easier to + // simply set a reasonable maximum list length and decrease the size before we finish. + out_ = new RLPItem[](MAX_LIST_LENGTH); + + uint256 itemCount = 0; + uint256 offset = listOffset; + while (offset < _in.length) { + (uint256 itemOffset, uint256 itemLength,) = _decodeLength( + RLPItem({ length: _in.length - offset, ptr: MemoryPointer.wrap(MemoryPointer.unwrap(_in.ptr) + offset) }) + ); + + // We don't need to check itemCount < out.length explicitly because Solidity already + // handles this check on our behalf, we'd just be wasting gas. + out_[itemCount] = RLPItem({ + length: itemLength + itemOffset, + ptr: MemoryPointer.wrap(MemoryPointer.unwrap(_in.ptr) + offset) + }); + + itemCount += 1; + offset += itemOffset + itemLength; + } + + // Decrease the array size to match the actual item count. + assembly { + mstore(out_, itemCount) + } + } + + /// @notice Reads an RLP list value into a list of RLP items. + /// @param _in RLP list value. + /// @return out_ Decoded RLP list items. + function readList(bytes memory _in) internal pure returns (RLPItem[] memory out_) { + out_ = readList(toRLPItem(_in)); + } + + /// @notice Reads an RLP bytes value into bytes. + /// @param _in RLP bytes value. + /// @return out_ Decoded bytes. + function readBytes(RLPItem memory _in) internal pure returns (bytes memory out_) { + (uint256 itemOffset, uint256 itemLength, RLPItemType itemType) = _decodeLength(_in); + + if (itemType != RLPItemType.DATA_ITEM) revert UnexpectedList(); + + if (_in.length != itemOffset + itemLength) revert InvalidDataRemainder(); + + out_ = _copy(_in.ptr, itemOffset, itemLength); + } + + /// @notice Reads an RLP bytes value into bytes. + /// @param _in RLP bytes value. + /// @return out_ Decoded bytes. + function readBytes(bytes memory _in) internal pure returns (bytes memory out_) { + out_ = readBytes(toRLPItem(_in)); + } + + /// @notice Reads the raw bytes of an RLP item. + /// @param _in RLP item to read. + /// @return out_ Raw RLP bytes. + function readRawBytes(RLPItem memory _in) internal pure returns (bytes memory out_) { + out_ = _copy(_in.ptr, 0, _in.length); + } + + /// @notice Decodes the length of an RLP item. + /// @param _in RLP item to decode. + /// @return offset_ Offset of the encoded data. + /// @return length_ Length of the encoded data. + /// @return type_ RLP item type (LIST_ITEM or DATA_ITEM). + function _decodeLength(RLPItem memory _in) + private + pure + returns (uint256 offset_, uint256 length_, RLPItemType type_) + { + // Short-circuit if there's nothing to decode, note that we perform this check when + // the user creates an RLP item via toRLPItem, but it's always possible for them to bypass + // that function and create an RLP item directly. So we need to check this anyway. + if (_in.length == 0) revert EmptyItem(); + + MemoryPointer ptr = _in.ptr; + uint256 prefix; + assembly { + prefix := byte(0, mload(ptr)) + } + + if (prefix <= 0x7f) { + // Single byte. + return (0, 1, RLPItemType.DATA_ITEM); + } else if (prefix <= 0xb7) { + // Short string. + + // slither-disable-next-line variable-scope + uint256 strLen = prefix - 0x80; + + if (_in.length <= strLen) revert ContentLengthMismatch(); + + bytes1 firstByteOfContent; + assembly { + firstByteOfContent := and(mload(add(ptr, 1)), shl(248, 0xff)) + } + + if (strLen == 1 && firstByteOfContent < 0x80) revert InvalidHeader(); + + return (1, strLen, RLPItemType.DATA_ITEM); + } else if (prefix <= 0xbf) { + // Long string. + uint256 lenOfStrLen = prefix - 0xb7; + + if (_in.length <= lenOfStrLen) revert ContentLengthMismatch(); + + bytes1 firstByteOfContent; + assembly { + firstByteOfContent := and(mload(add(ptr, 1)), shl(248, 0xff)) + } + + if (firstByteOfContent == 0x00) revert InvalidHeader(); + + uint256 strLen; + assembly { + strLen := shr(sub(256, mul(8, lenOfStrLen)), mload(add(ptr, 1))) + } + + if (strLen <= 55) revert InvalidHeader(); + + if (_in.length <= lenOfStrLen + strLen) revert ContentLengthMismatch(); + + return (1 + lenOfStrLen, strLen, RLPItemType.DATA_ITEM); + } else if (prefix <= 0xf7) { + // Short list. + // slither-disable-next-line variable-scope + uint256 listLen = prefix - 0xc0; + + if (_in.length <= listLen) revert ContentLengthMismatch(); + + return (1, listLen, RLPItemType.LIST_ITEM); + } else { + // Long list. + uint256 lenOfListLen = prefix - 0xf7; + + if (_in.length <= lenOfListLen) revert ContentLengthMismatch(); + + bytes1 firstByteOfContent; + assembly { + firstByteOfContent := and(mload(add(ptr, 1)), shl(248, 0xff)) + } + + if (firstByteOfContent == 0x00) revert InvalidHeader(); + + uint256 listLen; + assembly { + listLen := shr(sub(256, mul(8, lenOfListLen)), mload(add(ptr, 1))) + } + + if (listLen <= 55) revert InvalidHeader(); + + if (_in.length <= lenOfListLen + listLen) revert ContentLengthMismatch(); + + return (1 + lenOfListLen, listLen, RLPItemType.LIST_ITEM); + } + } + + /// @notice Copies the bytes from a memory location. + /// @param _src Pointer to the location to read from. + /// @param _offset Offset to start reading from. + /// @param _length Number of bytes to read. + /// @return out_ Copied bytes. + function _copy(MemoryPointer _src, uint256 _offset, uint256 _length) private pure returns (bytes memory out_) { + out_ = new bytes(_length); + if (_length == 0) { + return out_; + } + + // Mostly based on Solidity's copy_memory_to_memory: + // https://github.com/ethereum/solidity/blob/34dd30d71b4da730488be72ff6af7083cf2a91f6/libsolidity/codegen/YulUtilFunctions.cpp#L102-L114 + uint256 src = MemoryPointer.unwrap(_src) + _offset; + assembly { + let dest := add(out_, 32) + let i := 0 + for { } lt(i, _length) { i := add(i, 32) } { mstore(add(dest, i), mload(add(src, i))) } + + if gt(i, _length) { mstore(add(dest, _length), 0) } + } + } +} + +library Bytes { + /// @custom:attribution https://github.com/GNSPS/solidity-bytes-utils + /// @notice Slices a byte array with a given starting index and length. Returns a new byte array + /// as opposed to a pointer to the original array. Will throw if trying to slice more + /// bytes than exist in the array. + /// @param _bytes Byte array to slice. + /// @param _start Starting index of the slice. + /// @param _length Length of the slice. + /// @return Slice of the input byte array. + function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) { + unchecked { + require(_length + 31 >= _length, "slice_overflow"); + require(_start + _length >= _start, "slice_overflow"); + require(_bytes.length >= _start + _length, "slice_outOfBounds"); + } + + bytes memory tempBytes; + + assembly { + switch iszero(_length) + case 0 { + // Get a location of some free memory and store it in tempBytes as + // Solidity does for memory variables. + tempBytes := mload(0x40) + + // The first word of the slice result is potentially a partial + // word read from the original array. To read it, we calculate + // the length of that partial word and start copying that many + // bytes into the array. The first word we copy will start with + // data we don't care about, but the last `lengthmod` bytes will + // land at the beginning of the contents of the new array. When + // we're done copying, we overwrite the full first word with + // the actual length of the slice. + let lengthmod := and(_length, 31) + + // The multiplication in the next line is necessary + // because when slicing multiples of 32 bytes (lengthmod == 0) + // the following copy loop was copying the origin's length + // and then ending prematurely not copying everything it should. + let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) + let end := add(mc, _length) + + for { + // The multiplication in the next line has the same exact purpose + // as the one above. + let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) + } lt(mc, end) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { mstore(mc, mload(cc)) } + + mstore(tempBytes, _length) + + //update free-memory pointer + //allocating the array padded to 32 bytes like the compiler does now + mstore(0x40, and(add(mc, 31), not(31))) + } + //if we want a zero-length slice let's just return a zero-length array + default { + tempBytes := mload(0x40) + + //zero out the 32 bytes slice we are about to return + //we need to do it because Solidity does not garbage collect + mstore(tempBytes, 0) + + mstore(0x40, add(tempBytes, 0x20)) + } + } + + return tempBytes; + } + + /// @notice Slices a byte array with a given starting index up to the end of the original byte + /// array. Returns a new array rathern than a pointer to the original. + /// @param _bytes Byte array to slice. + /// @param _start Starting index of the slice. + /// @return Slice of the input byte array. + function slice(bytes memory _bytes, uint256 _start) internal pure returns (bytes memory) { + if (_start >= _bytes.length) { + return bytes(""); + } + return slice(_bytes, _start, _bytes.length - _start); + } + + /// @notice Converts a byte array into a nibble array by splitting each byte into two nibbles. + /// Resulting nibble array will be exactly twice as long as the input byte array. + /// @param _bytes Input byte array to convert. + /// @return Resulting nibble array. + function toNibbles(bytes memory _bytes) internal pure returns (bytes memory) { + bytes memory _nibbles; + assembly { + // Grab a free memory offset for the new array + _nibbles := mload(0x40) + + // Load the length of the passed bytes array from memory + let bytesLength := mload(_bytes) + + // Calculate the length of the new nibble array + // This is the length of the input array times 2 + let nibblesLength := shl(0x01, bytesLength) + + // Update the free memory pointer to allocate memory for the new array. + // To do this, we add the length of the new array + 32 bytes for the array length + // rounded up to the nearest 32 byte boundary to the current free memory pointer. + mstore(0x40, add(_nibbles, and(not(0x1F), add(nibblesLength, 0x3F)))) + + // Store the length of the new array in memory + mstore(_nibbles, nibblesLength) + + // Store the memory offset of the _bytes array's contents on the stack + let bytesStart := add(_bytes, 0x20) + + // Store the memory offset of the nibbles array's contents on the stack + let nibblesStart := add(_nibbles, 0x20) + + // Loop through each byte in the input array + for { let i := 0x00 } lt(i, bytesLength) { i := add(i, 0x01) } { + // Get the starting offset of the next 2 bytes in the nibbles array + let offset := add(nibblesStart, shl(0x01, i)) + // Load the byte at the current index within the `_bytes` array + let b := byte(0x00, mload(add(bytesStart, i))) + + // Pull out the first nibble and store it in the new array + mstore8(offset, shr(0x04, b)) + // Pull out the second nibble and store it in the new array + mstore8(add(offset, 0x01), and(b, 0x0F)) + } + } + return _nibbles; + } + + /// @notice Compares two byte arrays by comparing their keccak256 hashes. + /// @param _bytes First byte array to compare. + /// @param _other Second byte array to compare. + /// @return True if the two byte arrays are equal, false otherwise. + function equal(bytes memory _bytes, bytes memory _other) internal pure returns (bool) { + return keccak256(_bytes) == keccak256(_other); + } +} + +library MerkleTrie { + /// @notice Struct representing a node in the trie. + /// @custom:field encoded The RLP-encoded node. + /// @custom:field decoded The RLP-decoded node. + struct TrieNode { + bytes encoded; + RLPReader.RLPItem[] decoded; + } + + /// @notice Determines the number of elements per branch node. + uint256 internal constant TREE_RADIX = 16; + + /// @notice Branch nodes have TREE_RADIX elements and one value element. + uint256 internal constant BRANCH_NODE_LENGTH = TREE_RADIX + 1; + + /// @notice Leaf nodes and extension nodes have two elements, a `path` and a `value`. + uint256 internal constant LEAF_OR_EXTENSION_NODE_LENGTH = 2; + + /// @notice Prefix for even-nibbled extension node paths. + uint8 internal constant PREFIX_EXTENSION_EVEN = 0; + + /// @notice Prefix for odd-nibbled extension node paths. + uint8 internal constant PREFIX_EXTENSION_ODD = 1; + + /// @notice Prefix for even-nibbled leaf node paths. + uint8 internal constant PREFIX_LEAF_EVEN = 2; + + /// @notice Prefix for odd-nibbled leaf node paths. + uint8 internal constant PREFIX_LEAF_ODD = 3; + + /// @notice Verifies a proof that a given key/value pair is present in the trie. + /// @param _key Key of the node to search for, as a hex string. + /// @param _value Value of the node to search for, as a hex string. + /// @param _proof Merkle trie inclusion proof for the desired node. Unlike traditional Merkle + /// trees, this proof is executed top-down and consists of a list of RLP-encoded + /// nodes that make a path down to the target node. + /// @param _root Known root of the Merkle trie. Used to verify that the included proof is + /// correctly constructed. + /// @return valid_ Whether or not the proof is valid. + function verifyInclusionProof( + bytes memory _key, + bytes memory _value, + bytes[] memory _proof, + bytes32 _root + ) + internal + pure + returns (bool valid_) + { + valid_ = Bytes.equal(_value, get(_key, _proof, _root)); + } + + /// @notice Retrieves the value associated with a given key. + /// @param _key Key to search for, as hex bytes. + /// @param _proof Merkle trie inclusion proof for the key. + /// @param _root Known root of the Merkle trie. + /// @return value_ Value of the key if it exists. + function get(bytes memory _key, bytes[] memory _proof, bytes32 _root) internal pure returns (bytes memory value_) { + require(_key.length > 0, "MerkleTrie: empty key"); + + TrieNode[] memory proof = _parseProof(_proof); + bytes memory key = Bytes.toNibbles(_key); + bytes memory currentNodeID = abi.encodePacked(_root); + uint256 currentKeyIndex = 0; + + // Proof is top-down, so we start at the first element (root). + for (uint256 i = 0; i < proof.length; i++) { + TrieNode memory currentNode = proof[i]; + + // Key index should never exceed total key length or we'll be out of bounds. + require(currentKeyIndex <= key.length, "MerkleTrie: key index exceeds total key length"); + + if (currentKeyIndex == 0) { + // First proof element is always the root node. + require( + Bytes.equal(abi.encodePacked(keccak256(currentNode.encoded)), currentNodeID), + "MerkleTrie: invalid root hash" + ); + } else if (currentNode.encoded.length >= 32) { + // Nodes 32 bytes or larger are hashed inside branch nodes. + require( + Bytes.equal(abi.encodePacked(keccak256(currentNode.encoded)), currentNodeID), + "MerkleTrie: invalid large internal hash" + ); + } else { + // Nodes smaller than 32 bytes aren't hashed. + require(Bytes.equal(currentNode.encoded, currentNodeID), "MerkleTrie: invalid internal node hash"); + } + + if (currentNode.decoded.length == BRANCH_NODE_LENGTH) { + if (currentKeyIndex == key.length) { + // Value is the last element of the decoded list (for branch nodes). There's + // some ambiguity in the Merkle trie specification because bytes(0) is a + // valid value to place into the trie, but for branch nodes bytes(0) can exist + // even when the value wasn't explicitly placed there. Geth treats a value of + // bytes(0) as "key does not exist" and so we do the same. + value_ = RLPReader.readBytes(currentNode.decoded[TREE_RADIX]); + require(value_.length > 0, "MerkleTrie: value length must be greater than zero (branch)"); + + // Extra proof elements are not allowed. + require(i == proof.length - 1, "MerkleTrie: value node must be last node in proof (branch)"); + + return value_; + } else { + // We're not at the end of the key yet. + // Figure out what the next node ID should be and continue. + uint8 branchKey = uint8(key[currentKeyIndex]); + RLPReader.RLPItem memory nextNode = currentNode.decoded[branchKey]; + currentNodeID = _getNodeID(nextNode); + currentKeyIndex += 1; + } + } else if (currentNode.decoded.length == LEAF_OR_EXTENSION_NODE_LENGTH) { + bytes memory path = _getNodePath(currentNode); + uint8 prefix = uint8(path[0]); + uint8 offset = 2 - (prefix % 2); + bytes memory pathRemainder = Bytes.slice(path, offset); + bytes memory keyRemainder = Bytes.slice(key, currentKeyIndex); + uint256 sharedNibbleLength = _getSharedNibbleLength(pathRemainder, keyRemainder); + + // Whether this is a leaf node or an extension node, the path remainder MUST be a + // prefix of the key remainder (or be equal to the key remainder) or the proof is + // considered invalid. + require( + pathRemainder.length == sharedNibbleLength, + "MerkleTrie: path remainder must share all nibbles with key" + ); + + if (prefix == PREFIX_LEAF_EVEN || prefix == PREFIX_LEAF_ODD) { + // Prefix of 2 or 3 means this is a leaf node. For the leaf node to be valid, + // the key remainder must be exactly equal to the path remainder. We already + // did the necessary byte comparison, so it's more efficient here to check that + // the key remainder length equals the shared nibble length, which implies + // equality with the path remainder (since we already did the same check with + // the path remainder and the shared nibble length). + require( + keyRemainder.length == sharedNibbleLength, + "MerkleTrie: key remainder must be identical to path remainder" + ); + + // Our Merkle Trie is designed specifically for the purposes of the Ethereum + // state trie. Empty values are not allowed in the state trie, so we can safely + // say that if the value is empty, the key should not exist and the proof is + // invalid. + value_ = RLPReader.readBytes(currentNode.decoded[1]); + require(value_.length > 0, "MerkleTrie: value length must be greater than zero (leaf)"); + + // Extra proof elements are not allowed. + require(i == proof.length - 1, "MerkleTrie: value node must be last node in proof (leaf)"); + + return value_; + } else if (prefix == PREFIX_EXTENSION_EVEN || prefix == PREFIX_EXTENSION_ODD) { + // Prefix of 0 or 1 means this is an extension node. We move onto the next node + // in the proof and increment the key index by the length of the path remainder + // which is equal to the shared nibble length. + currentNodeID = _getNodeID(currentNode.decoded[1]); + currentKeyIndex += sharedNibbleLength; + } else { + revert("MerkleTrie: received a node with an unknown prefix"); + } + } else { + revert("MerkleTrie: received an unparseable node"); + } + } + + revert("MerkleTrie: ran out of proof elements"); + } + + /// @notice Parses an array of proof elements into a new array that contains both the original + /// encoded element and the RLP-decoded element. + /// @param _proof Array of proof elements to parse. + /// @return proof_ Proof parsed into easily accessible structs. + function _parseProof(bytes[] memory _proof) private pure returns (TrieNode[] memory proof_) { + uint256 length = _proof.length; + proof_ = new TrieNode[](length); + for (uint256 i = 0; i < length;) { + proof_[i] = TrieNode({ encoded: _proof[i], decoded: RLPReader.readList(_proof[i]) }); + unchecked { + ++i; + } + } + } + + /// @notice Picks out the ID for a node. Node ID is referred to as the "hash" within the + /// specification, but nodes < 32 bytes are not actually hashed. + /// @param _node Node to pull an ID for. + /// @return id_ ID for the node, depending on the size of its contents. + function _getNodeID(RLPReader.RLPItem memory _node) private pure returns (bytes memory id_) { + id_ = _node.length < 32 ? RLPReader.readRawBytes(_node) : RLPReader.readBytes(_node); + } + + /// @notice Gets the path for a leaf or extension node. + /// @param _node Node to get a path for. + /// @return nibbles_ Node path, converted to an array of nibbles. + function _getNodePath(TrieNode memory _node) private pure returns (bytes memory nibbles_) { + nibbles_ = Bytes.toNibbles(RLPReader.readBytes(_node.decoded[0])); + } + + /// @notice Utility; determines the number of nibbles shared between two nibble arrays. + /// @param _a First nibble array. + /// @param _b Second nibble array. + /// @return shared_ Number of shared nibbles. + function _getSharedNibbleLength(bytes memory _a, bytes memory _b) private pure returns (uint256 shared_) { + uint256 max = (_a.length < _b.length) ? _a.length : _b.length; + for (; shared_ < max && _a[shared_] == _b[shared_];) { + unchecked { + ++shared_; + } + } + } +} + +library SecureMerkleTrie { + /// @notice Verifies a proof that a given key/value pair is present in the Merkle trie. + /// @param _key Key of the node to search for, as a hex string. + /// @param _value Value of the node to search for, as a hex string. + /// @param _proof Merkle trie inclusion proof for the desired node. Unlike traditional Merkle + /// trees, this proof is executed top-down and consists of a list of RLP-encoded + /// nodes that make a path down to the target node. + /// @param _root Known root of the Merkle trie. Used to verify that the included proof is + /// correctly constructed. + /// @return valid_ Whether or not the proof is valid. + function verifyInclusionProof( + bytes memory _key, + bytes memory _value, + bytes[] memory _proof, + bytes32 _root + ) + internal + pure + returns (bool valid_) + { + bytes memory key = _getSecureKey(_key); + valid_ = MerkleTrie.verifyInclusionProof(key, _value, _proof, _root); + } + + /// @notice Retrieves the value associated with a given key. + /// @param _key Key to search for, as hex bytes. + /// @param _proof Merkle trie inclusion proof for the key. + /// @param _root Known root of the Merkle trie. + /// @return value_ Value of the key if it exists. + function get(bytes memory _key, bytes[] memory _proof, bytes32 _root) internal pure returns (bytes memory value_) { + bytes memory key = _getSecureKey(_key); + value_ = MerkleTrie.get(key, _proof, _root); + } + + /// @notice Computes the hashed version of the input key. + /// @param _key Key to hash. + /// @return hash_ Hashed version of the key. + function _getSecureKey(bytes memory _key) private pure returns (bytes memory hash_) { + hash_ = abi.encodePacked(keccak256(_key)); + } +} + +library RLPWriter { + /// @notice RLP encodes a byte string. + /// @param _in The byte string to encode. + /// @return out_ The RLP encoded string in bytes. + function writeBytes(bytes memory _in) internal pure returns (bytes memory out_) { + if (_in.length == 1 && uint8(_in[0]) < 128) { + out_ = _in; + } else { + out_ = abi.encodePacked(_writeLength(_in.length, 128), _in); + } + } + + /// @notice RLP encodes a list of RLP encoded byte byte strings. + /// @param _in The list of RLP encoded byte strings. + /// @return list_ The RLP encoded list of items in bytes. + function writeList(bytes[] memory _in) internal pure returns (bytes memory list_) { + list_ = _flatten(_in); + list_ = abi.encodePacked(_writeLength(list_.length, 192), list_); + } + + /// @notice RLP encodes a string. + /// @param _in The string to encode. + /// @return out_ The RLP encoded string in bytes. + function writeString(string memory _in) internal pure returns (bytes memory out_) { + out_ = writeBytes(bytes(_in)); + } + + /// @notice RLP encodes an address. + /// @param _in The address to encode. + /// @return out_ The RLP encoded address in bytes. + function writeAddress(address _in) internal pure returns (bytes memory out_) { + out_ = writeBytes(abi.encodePacked(_in)); + } + + /// @notice RLP encodes a uint. + /// @param _in The uint256 to encode. + /// @return out_ The RLP encoded uint256 in bytes. + function writeUint(uint256 _in) internal pure returns (bytes memory out_) { + out_ = writeBytes(_toBinary(_in)); + } + + /// @notice RLP encodes a bool. + /// @param _in The bool to encode. + /// @return out_ The RLP encoded bool in bytes. + function writeBool(bool _in) internal pure returns (bytes memory out_) { + out_ = new bytes(1); + out_[0] = (_in ? bytes1(0x01) : bytes1(0x80)); + } + + /// @notice Encode the first byte and then the `len` in binary form if `length` is more than 55. + /// @param _len The length of the string or the payload. + /// @param _offset 128 if item is string, 192 if item is list. + /// @return out_ RLP encoded bytes. + function _writeLength(uint256 _len, uint256 _offset) private pure returns (bytes memory out_) { + if (_len < 56) { + out_ = new bytes(1); + out_[0] = bytes1(uint8(_len) + uint8(_offset)); + } else { + uint256 lenLen; + uint256 i = 1; + while (_len / i != 0) { + lenLen++; + i *= 256; + } + + out_ = new bytes(lenLen + 1); + out_[0] = bytes1(uint8(lenLen) + uint8(_offset) + 55); + for (i = 1; i <= lenLen; i++) { + out_[i] = bytes1(uint8((_len / (256 ** (lenLen - i))) % 256)); + } + } + } + + /// @notice Encode integer in big endian binary form with no leading zeroes. + /// @param _x The integer to encode. + /// @return out_ RLP encoded bytes. + function _toBinary(uint256 _x) private pure returns (bytes memory out_) { + bytes memory b = abi.encodePacked(_x); + + uint256 i = 0; + for (; i < 32; i++) { + if (b[i] != 0) { + break; + } + } + + out_ = new bytes(32 - i); + for (uint256 j = 0; j < out_.length; j++) { + out_[j] = b[i++]; + } + } + + /// @custom:attribution https://github.com/Arachnid/solidity-stringutils + /// @notice Copies a piece of memory to another location. + /// @param _dest Destination location. + /// @param _src Source location. + /// @param _len Length of memory to copy. + function _memcpy(uint256 _dest, uint256 _src, uint256 _len) private pure { + uint256 dest = _dest; + uint256 src = _src; + uint256 len = _len; + + for (; len >= 32; len -= 32) { + assembly { + mstore(dest, mload(src)) + } + dest += 32; + src += 32; + } + + uint256 mask; + unchecked { + mask = 256 ** (32 - len) - 1; + } + assembly { + let srcpart := and(mload(src), not(mask)) + let destpart := and(mload(dest), mask) + mstore(dest, or(destpart, srcpart)) + } + } + + /// @custom:attribution https://github.com/sammayo/solidity-rlp-encoder + /// @notice Flattens a list of byte strings into one byte string. + /// @param _list List of byte strings to flatten. + /// @return out_ The flattened byte string. + function _flatten(bytes[] memory _list) private pure returns (bytes memory out_) { + if (_list.length == 0) { + return new bytes(0); + } + + uint256 len; + uint256 i = 0; + for (; i < _list.length; i++) { + len += _list[i].length; + } + + out_ = new bytes(len); + uint256 flattenedPtr; + assembly { + flattenedPtr := add(out_, 0x20) + } + + for (i = 0; i < _list.length; i++) { + bytes memory item = _list[i]; + + uint256 listPtr; + assembly { + listPtr := add(item, 0x20) + } + + _memcpy(flattenedPtr, listPtr, item.length); + flattenedPtr += _list[i].length; + } + } +} + +library Encoding { + /// @notice RLP encodes the L2 transaction that would be generated when a given deposit is sent + /// to the L2 system. Useful for searching for a deposit in the L2 system. The + /// transaction is prefixed with 0x7e to identify its EIP-2718 type. + /// @param _tx User deposit transaction to encode. + /// @return RLP encoded L2 deposit transaction. + function encodeDepositTransaction(Types.UserDepositTransaction memory _tx) internal pure returns (bytes memory) { + bytes32 source = Hashing.hashDepositSource(_tx.l1BlockHash, _tx.logIndex); + bytes[] memory raw = new bytes[](8); + raw[0] = RLPWriter.writeBytes(abi.encodePacked(source)); + raw[1] = RLPWriter.writeAddress(_tx.from); + raw[2] = _tx.isCreation ? RLPWriter.writeBytes("") : RLPWriter.writeAddress(_tx.to); + raw[3] = RLPWriter.writeUint(_tx.mint); + raw[4] = RLPWriter.writeUint(_tx.value); + raw[5] = RLPWriter.writeUint(uint256(_tx.gasLimit)); + raw[6] = RLPWriter.writeBool(false); + raw[7] = RLPWriter.writeBytes(_tx.data); + return abi.encodePacked(uint8(0x7e), RLPWriter.writeList(raw)); + } + + /// @notice Encodes the cross domain message based on the version that is encoded into the + /// message nonce. + /// @param _nonce Message nonce with version encoded into the first two bytes. + /// @param _sender Address of the sender of the message. + /// @param _target Address of the target of the message. + /// @param _value ETH value to send to the target. + /// @param _gasLimit Gas limit to use for the message. + /// @param _data Data to send with the message. + /// @return Encoded cross domain message. + function encodeCrossDomainMessage( + uint256 _nonce, + address _sender, + address _target, + uint256 _value, + uint256 _gasLimit, + bytes memory _data + ) + internal + pure + returns (bytes memory) + { + (, uint16 version) = decodeVersionedNonce(_nonce); + if (version == 0) { + return encodeCrossDomainMessageV0(_target, _sender, _data, _nonce); + } else if (version == 1) { + return encodeCrossDomainMessageV1(_nonce, _sender, _target, _value, _gasLimit, _data); + } else { + revert("Encoding: unknown cross domain message version"); + } + } + + /// @notice Encodes a cross domain message based on the V0 (legacy) encoding. + /// @param _target Address of the target of the message. + /// @param _sender Address of the sender of the message. + /// @param _data Data to send with the message. + /// @param _nonce Message nonce. + /// @return Encoded cross domain message. + function encodeCrossDomainMessageV0( + address _target, + address _sender, + bytes memory _data, + uint256 _nonce + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSignature("relayMessage(address,address,bytes,uint256)", _target, _sender, _data, _nonce); + } + + /// @notice Encodes a cross domain message based on the V1 (current) encoding. + /// @param _nonce Message nonce. + /// @param _sender Address of the sender of the message. + /// @param _target Address of the target of the message. + /// @param _value ETH value to send to the target. + /// @param _gasLimit Gas limit to use for the message. + /// @param _data Data to send with the message. + /// @return Encoded cross domain message. + function encodeCrossDomainMessageV1( + uint256 _nonce, + address _sender, + address _target, + uint256 _value, + uint256 _gasLimit, + bytes memory _data + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSignature( + "relayMessage(uint256,address,address,uint256,uint256,bytes)", + _nonce, + _sender, + _target, + _value, + _gasLimit, + _data + ); + } + + /// @notice Adds a version number into the first two bytes of a message nonce. + /// @param _nonce Message nonce to encode into. + /// @param _version Version number to encode into the message nonce. + /// @return Message nonce with version encoded into the first two bytes. + function encodeVersionedNonce(uint240 _nonce, uint16 _version) internal pure returns (uint256) { + uint256 nonce; + assembly { + nonce := or(shl(240, _version), _nonce) + } + return nonce; + } + + /// @notice Pulls the version out of a version-encoded nonce. + /// @param _nonce Message nonce with version encoded into the first two bytes. + /// @return Nonce without encoded version. + /// @return Version of the message. + function decodeVersionedNonce(uint256 _nonce) internal pure returns (uint240, uint16) { + uint240 nonce; + uint16 version; + assembly { + nonce := and(_nonce, 0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) + version := shr(240, _nonce) + } + return (nonce, version); + } + + /// @notice Returns an appropriately encoded call to L1Block.setL1BlockValuesEcotone (without FCT params) + function encodeSetL1BlockValuesEcotone( + uint32 baseFeeScalar, + uint32 blobBaseFeeScalar, + uint64 sequenceNumber, + uint64 timestamp, + uint64 number, + uint256 baseFee, + uint256 blobBaseFee, + bytes32 hash, + bytes32 batcherHash + ) + internal + pure + returns (bytes memory) + { + return encodeSetL1BlockValuesEcotone( + baseFeeScalar, + blobBaseFeeScalar, + sequenceNumber, + timestamp, + number, + baseFee, + blobBaseFee, + hash, + batcherHash, + 0, // Default fctMintPeriodL1DataGas to 0 + 0 // Default fctMintRate to 0 + ); + } + + /// @notice Returns an appropriately encoded call to L1Block.setL1BlockValuesEcotone (with FCT params) + function encodeSetL1BlockValuesEcotone( + uint32 baseFeeScalar, + uint32 blobBaseFeeScalar, + uint64 sequenceNumber, + uint64 timestamp, + uint64 number, + uint256 baseFee, + uint256 blobBaseFee, + bytes32 hash, + bytes32 batcherHash, + uint128 fctMintPeriodL1DataGas, + uint128 fctMintRate + ) + internal + pure + returns (bytes memory) + { + bytes4 functionSignature = bytes4(keccak256("setL1BlockValuesEcotone()")); + return abi.encodePacked( + functionSignature, + baseFeeScalar, + blobBaseFeeScalar, + sequenceNumber, + timestamp, + number, + baseFee, + blobBaseFee, + hash, + batcherHash, + fctMintPeriodL1DataGas, + fctMintRate + ); + } + + /// @notice Returns an appropriately encoded call to L1Block.setL1BlockValuesInterop + /// @param _baseFeeScalar L1 base fee Scalar + /// @param _blobBaseFeeScalar L1 blob base fee Scalar + /// @param _sequenceNumber Number of L2 blocks since epoch start. + /// @param _timestamp L1 timestamp. + /// @param _number L1 blocknumber. + /// @param _baseFee L1 base fee. + /// @param _blobBaseFee L1 blob base fee. + /// @param _hash L1 blockhash. + /// @param _batcherHash Versioned hash to authenticate batcher by. + /// @param _dependencySet Array of the chain IDs in the interop dependency set. + function encodeSetL1BlockValuesInterop( + uint32 _baseFeeScalar, + uint32 _blobBaseFeeScalar, + uint64 _sequenceNumber, + uint64 _timestamp, + uint64 _number, + uint256 _baseFee, + uint256 _blobBaseFee, + bytes32 _hash, + bytes32 _batcherHash, + uint256[] memory _dependencySet + ) + internal + pure + returns (bytes memory) + { + require(_dependencySet.length <= type(uint8).max, "Encoding: dependency set length is too large"); + // Check that the batcher hash is just the address with 0 padding to the left for version 0. + require(uint160(uint256(_batcherHash)) == uint256(_batcherHash), "Encoding: invalid batcher hash"); + + bytes4 functionSignature = bytes4(keccak256("setL1BlockValuesInterop()")); + return abi.encodePacked( + functionSignature, + _baseFeeScalar, + _blobBaseFeeScalar, + _sequenceNumber, + _timestamp, + _number, + _baseFee, + _blobBaseFee, + _hash, + _batcherHash, + uint8(_dependencySet.length), + _dependencySet + ); + } +} + +library Hashing { + /// @notice Computes the hash of the RLP encoded L2 transaction that would be generated when a + /// given deposit is sent to the L2 system. Useful for searching for a deposit in the L2 + /// system. + /// @param _tx User deposit transaction to hash. + /// @return Hash of the RLP encoded L2 deposit transaction. + function hashDepositTransaction(Types.UserDepositTransaction memory _tx) internal pure returns (bytes32) { + return keccak256(Encoding.encodeDepositTransaction(_tx)); + } + + /// @notice Computes the deposit transaction's "source hash", a value that guarantees the hash + /// of the L2 transaction that corresponds to a deposit is unique and is + /// deterministically generated from L1 transaction data. + /// @param _l1BlockHash Hash of the L1 block where the deposit was included. + /// @param _logIndex The index of the log that created the deposit transaction. + /// @return Hash of the deposit transaction's "source hash". + function hashDepositSource(bytes32 _l1BlockHash, uint256 _logIndex) internal pure returns (bytes32) { + bytes32 depositId = keccak256(abi.encode(_l1BlockHash, _logIndex)); + return keccak256(abi.encode(bytes32(0), depositId)); + } + + /// @notice Hashes the cross domain message based on the version that is encoded into the + /// message nonce. + /// @param _nonce Message nonce with version encoded into the first two bytes. + /// @param _sender Address of the sender of the message. + /// @param _target Address of the target of the message. + /// @param _value ETH value to send to the target. + /// @param _gasLimit Gas limit to use for the message. + /// @param _data Data to send with the message. + /// @return Hashed cross domain message. + function hashCrossDomainMessage( + uint256 _nonce, + address _sender, + address _target, + uint256 _value, + uint256 _gasLimit, + bytes memory _data + ) + internal + pure + returns (bytes32) + { + (, uint16 version) = Encoding.decodeVersionedNonce(_nonce); + if (version == 0) { + return hashCrossDomainMessageV0(_target, _sender, _data, _nonce); + } else if (version == 1) { + return hashCrossDomainMessageV1(_nonce, _sender, _target, _value, _gasLimit, _data); + } else { + revert("Hashing: unknown cross domain message version"); + } + } + + /// @notice Hashes a cross domain message based on the V0 (legacy) encoding. + /// @param _target Address of the target of the message. + /// @param _sender Address of the sender of the message. + /// @param _data Data to send with the message. + /// @param _nonce Message nonce. + /// @return Hashed cross domain message. + function hashCrossDomainMessageV0( + address _target, + address _sender, + bytes memory _data, + uint256 _nonce + ) + internal + pure + returns (bytes32) + { + return keccak256(Encoding.encodeCrossDomainMessageV0(_target, _sender, _data, _nonce)); + } + + /// @notice Hashes a cross domain message based on the V1 (current) encoding. + /// @param _nonce Message nonce. + /// @param _sender Address of the sender of the message. + /// @param _target Address of the target of the message. + /// @param _value ETH value to send to the target. + /// @param _gasLimit Gas limit to use for the message. + /// @param _data Data to send with the message. + /// @return Hashed cross domain message. + function hashCrossDomainMessageV1( + uint256 _nonce, + address _sender, + address _target, + uint256 _value, + uint256 _gasLimit, + bytes memory _data + ) + internal + pure + returns (bytes32) + { + return keccak256(Encoding.encodeCrossDomainMessageV1(_nonce, _sender, _target, _value, _gasLimit, _data)); + } + + /// @notice Derives the withdrawal hash according to the encoding in the L2 Withdrawer contract + /// @param _tx Withdrawal transaction to hash. + /// @return Hashed withdrawal transaction. + function hashWithdrawal(Types.WithdrawalTransaction memory _tx) internal pure returns (bytes32) { + return keccak256(abi.encode(_tx.nonce, _tx.sender, _tx.target, _tx.value, _tx.gasLimit, _tx.data)); + } + + /// @notice Hashes the various elements of an output root proof into an output root hash which + /// can be used to check if the proof is valid. + /// @param _outputRootProof Output root proof which should hash to an output root. + /// @return Hashed output root proof. + function hashOutputRootProof(Types.OutputRootProof memory _outputRootProof) internal pure returns (bytes32) { + return keccak256( + abi.encode( + _outputRootProof.version, + _outputRootProof.stateRoot, + _outputRootProof.messagePasserStorageRoot, + _outputRootProof.latestBlockhash + ) + ); + } +} + +library Types { + /// @notice OutputProposal represents a commitment to the L2 state. The timestamp is the L1 + /// timestamp that the output root is posted. This timestamp is used to verify that the + /// finalization period has passed since the output root was submitted. + /// @custom:field outputRoot Hash of the L2 output. + /// @custom:field timestamp Timestamp of the L1 block that the output root was submitted in. + /// @custom:field l2BlockNumber L2 block number that the output corresponds to. + struct OutputProposal { + bytes32 outputRoot; + uint128 timestamp; + uint128 l2BlockNumber; + } + + /// @notice Struct representing the elements that are hashed together to generate an output root + /// which itself represents a snapshot of the L2 state. + /// @custom:field version Version of the output root. + /// @custom:field stateRoot Root of the state trie at the block of this output. + /// @custom:field messagePasserStorageRoot Root of the message passer storage trie. + /// @custom:field latestBlockhash Hash of the block this output was generated from. + struct OutputRootProof { + bytes32 version; + bytes32 stateRoot; + bytes32 messagePasserStorageRoot; + bytes32 latestBlockhash; + } + + /// @notice Struct representing a deposit transaction (L1 => L2 transaction) created by an end + /// user (as opposed to a system deposit transaction generated by the system). + /// @custom:field from Address of the sender of the transaction. + /// @custom:field to Address of the recipient of the transaction. + /// @custom:field isCreation True if the transaction is a contract creation. + /// @custom:field value Value to send to the recipient. + /// @custom:field mint Amount of ETH to mint. + /// @custom:field gasLimit Gas limit of the transaction. + /// @custom:field data Data of the transaction. + /// @custom:field l1BlockHash Hash of the block the transaction was submitted in. + /// @custom:field logIndex Index of the log in the block the transaction was submitted in. + struct UserDepositTransaction { + address from; + address to; + bool isCreation; + uint256 value; + uint256 mint; + uint64 gasLimit; + bytes data; + bytes32 l1BlockHash; + uint256 logIndex; + } + + /// @notice Struct representing a withdrawal transaction. + /// @custom:field nonce Nonce of the withdrawal transaction + /// @custom:field sender Address of the sender of the transaction. + /// @custom:field target Address of the recipient of the transaction. + /// @custom:field value Value to send to the recipient. + /// @custom:field gasLimit Gas limit of the transaction. + /// @custom:field data Data of the transaction. + struct WithdrawalTransaction { + uint256 nonce; + address sender; + address target; + uint256 value; + uint256 gasLimit; + bytes data; + } +} + +library Constants { + /// @notice Special address to be used as the tx origin for gas estimation calls in the + /// OptimismPortal and CrossDomainMessenger calls. You only need to use this address if + /// the minimum gas limit specified by the user is not actually enough to execute the + /// given message and you're attempting to estimate the actual necessary gas limit. We + /// use address(1) because it's the ecrecover precompile and therefore guaranteed to + /// never have any code on any EVM chain. + address internal constant ESTIMATION_ADDRESS = address(1); + + /// @notice Value used for the L2 sender storage slot in both the OptimismPortal and the + /// CrossDomainMessenger contracts before an actual sender is set. This value is + /// non-zero to reduce the gas cost of message passing transactions. + address internal constant DEFAULT_L2_SENDER = 0x000000000000000000000000000000000000dEaD; + + /// @notice The storage slot that holds the address of a proxy implementation. + /// @dev `bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)` + bytes32 internal constant PROXY_IMPLEMENTATION_ADDRESS = + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + /// @notice The storage slot that holds the address of the owner. + /// @dev `bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)` + bytes32 internal constant PROXY_OWNER_ADDRESS = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + + /// @notice The address that represents ether when dealing with ERC20 token addresses. + address internal constant ETHER = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + address internal constant FACET_COMPUTE_TOKEN = 0xFACE7fAcE7fAcE7FacE7FACE7FACe7FAcE7fACE7; + + /// @notice The address that represents the system caller responsible for L1 attributes + /// transactions. + address internal constant DEPOSITOR_ACCOUNT = 0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001; + + /// @notice Returns the default values for the ResourceConfig. These are the recommended values + /// for a production network. + function DEFAULT_RESOURCE_CONFIG() internal pure returns (ResourceMetering.ResourceConfig memory) { + ResourceMetering.ResourceConfig memory config = ResourceMetering.ResourceConfig({ + maxResourceLimit: 20_000_000, + elasticityMultiplier: 10, + baseFeeMaxChangeDenominator: 8, + minimumBaseFee: 1 gwei, + systemTxMaxGas: 1_000_000, + maximumBaseFee: type(uint128).max + }); + return config; + } +} + +library SafeERC20 { + using Address for address; + + function safeTransfer( + IERC20 token, + address to, + uint256 value + ) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom( + IERC20 token, + address from, + address to, + uint256 value + ) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + /** + * @dev Deprecated. This function has issues similar to the ones found in + * {IERC20-approve}, and its usage is discouraged. + * + * Whenever possible, use {safeIncreaseAllowance} and + * {safeDecreaseAllowance} instead. + */ + function safeApprove( + IERC20 token, + address spender, + uint256 value + ) internal { + // safeApprove should only be called when setting an initial allowance, + // or when resetting it to zero. To increase and decrease it, use + // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' + require( + (value == 0) || (token.allowance(address(this), spender) == 0), + "SafeERC20: approve from non-zero to non-zero allowance" + ); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); + } + + function safeIncreaseAllowance( + IERC20 token, + address spender, + uint256 value + ) internal { + uint256 newAllowance = token.allowance(address(this), spender) + value; + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + function safeDecreaseAllowance( + IERC20 token, + address spender, + uint256 value + ) internal { + unchecked { + uint256 oldAllowance = token.allowance(address(this), spender); + require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); + uint256 newAllowance = oldAllowance - value; + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + } + + function safePermit( + IERC20Permit token, + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) internal { + uint256 nonceBefore = token.nonces(owner); + token.permit(owner, spender, value, deadline, v, r, s); + uint256 nonceAfter = token.nonces(owner); + require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed"); + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that + // the target address contains contract code and also asserts for success in the low-level call. + + bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); + if (returndata.length > 0) { + // Return data is optional + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} + +interface ISemver { + /// @notice Getter for the semantic version of the contract. This is not + /// meant to be used onchain but instead meant to be used by offchain + /// tooling. + /// @return Semver contract version as a string. + function version() external view returns (string memory); +} + +library SignedMath { + /** + * @dev Returns the largest of two signed numbers. + */ + function max(int256 a, int256 b) internal pure returns (int256) { + return a >= b ? a : b; + } + + /** + * @dev Returns the smallest of two signed numbers. + */ + function min(int256 a, int256 b) internal pure returns (int256) { + return a < b ? a : b; + } + + /** + * @dev Returns the average of two signed numbers without overflow. + * The result is rounded towards zero. + */ + function average(int256 a, int256 b) internal pure returns (int256) { + // Formula from the book "Hacker's Delight" + int256 x = (a & b) + ((a ^ b) >> 1); + return x + (int256(uint256(x) >> 255) & (a ^ b)); + } + + /** + * @dev Returns the absolute unsigned value of a signed value. + */ + function abs(int256 n) internal pure returns (uint256) { + unchecked { + // must be unchecked in order to support `n = type(int256).min` + return uint256(n >= 0 ? n : -n); + } + } +} + +library FixedPointMathLib { + /*////////////////////////////////////////////////////////////// + SIMPLIFIED FIXED POINT OPERATIONS + //////////////////////////////////////////////////////////////*/ + + uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s. + + function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) { + return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down. + } + + function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) { + return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up. + } + + function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) { + return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down. + } + + function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) { + return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up. + } + + function powWad(int256 x, int256 y) internal pure returns (int256) { + // Equivalent to x to the power of y because x ** y = (e ** ln(x)) ** y = e ** (ln(x) * y) + return expWad((lnWad(x) * y) / int256(WAD)); // Using ln(x) means x must be greater than 0. + } + + function expWad(int256 x) internal pure returns (int256 r) { + unchecked { + // When the result is < 0.5 we return zero. This happens when + // x <= floor(log(0.5e18) * 1e18) ~ -42e18 + if (x <= -42139678854452767551) return 0; + + // When the result is > (2**255 - 1) / 1e18 we can not represent it as an + // int. This happens when x >= floor(log((2**255 - 1) / 1e18) * 1e18) ~ 135. + if (x >= 135305999368893231589) revert("EXP_OVERFLOW"); + + // x is now in the range (-42, 136) * 1e18. Convert to (-42, 136) * 2**96 + // for more intermediate precision and a binary basis. This base conversion + // is a multiplication by 1e18 / 2**96 = 5**18 / 2**78. + x = (x << 78) / 5**18; + + // Reduce range of x to (-½ ln 2, ½ ln 2) * 2**96 by factoring out powers + // of two such that exp(x) = exp(x') * 2**k, where k is an integer. + // Solving this gives k = round(x / log(2)) and x' = x - k * log(2). + int256 k = ((x << 96) / 54916777467707473351141471128 + 2**95) >> 96; + x = x - k * 54916777467707473351141471128; + + // k is in the range [-61, 195]. + + // Evaluate using a (6, 7)-term rational approximation. + // p is made monic, we'll multiply by a scale factor later. + int256 y = x + 1346386616545796478920950773328; + y = ((y * x) >> 96) + 57155421227552351082224309758442; + int256 p = y + x - 94201549194550492254356042504812; + p = ((p * y) >> 96) + 28719021644029726153956944680412240; + p = p * x + (4385272521454847904659076985693276 << 96); + + // We leave p in 2**192 basis so we don't need to scale it back up for the division. + int256 q = x - 2855989394907223263936484059900; + q = ((q * x) >> 96) + 50020603652535783019961831881945; + q = ((q * x) >> 96) - 533845033583426703283633433725380; + q = ((q * x) >> 96) + 3604857256930695427073651918091429; + q = ((q * x) >> 96) - 14423608567350463180887372962807573; + q = ((q * x) >> 96) + 26449188498355588339934803723976023; + + assembly { + // Div in assembly because solidity adds a zero check despite the unchecked. + // The q polynomial won't have zeros in the domain as all its roots are complex. + // No scaling is necessary because p is already 2**96 too large. + r := sdiv(p, q) + } + + // r should be in the range (0.09, 0.25) * 2**96. + + // We now need to multiply r by: + // * the scale factor s = ~6.031367120. + // * the 2**k factor from the range reduction. + // * the 1e18 / 2**96 factor for base conversion. + // We do this all at once, with an intermediate result in 2**213 + // basis, so the final right shift is always by a positive amount. + r = int256((uint256(r) * 3822833074963236453042738258902158003155416615667) >> uint256(195 - k)); + } + } + + function lnWad(int256 x) internal pure returns (int256 r) { + unchecked { + require(x > 0, "UNDEFINED"); + + // We want to convert x from 10**18 fixed point to 2**96 fixed point. + // We do this by multiplying by 2**96 / 10**18. But since + // ln(x * C) = ln(x) + ln(C), we can simply do nothing here + // and add ln(2**96 / 10**18) at the end. + + // Reduce range of x to (1, 2) * 2**96 + // ln(2^k * x) = k * ln(2) + ln(x) + int256 k = int256(log2(uint256(x))) - 96; + x <<= uint256(159 - k); + x = int256(uint256(x) >> 159); + + // Evaluate using a (8, 8)-term rational approximation. + // p is made monic, we will multiply by a scale factor later. + int256 p = x + 3273285459638523848632254066296; + p = ((p * x) >> 96) + 24828157081833163892658089445524; + p = ((p * x) >> 96) + 43456485725739037958740375743393; + p = ((p * x) >> 96) - 11111509109440967052023855526967; + p = ((p * x) >> 96) - 45023709667254063763336534515857; + p = ((p * x) >> 96) - 14706773417378608786704636184526; + p = p * x - (795164235651350426258249787498 << 96); + + // We leave p in 2**192 basis so we don't need to scale it back up for the division. + // q is monic by convention. + int256 q = x + 5573035233440673466300451813936; + q = ((q * x) >> 96) + 71694874799317883764090561454958; + q = ((q * x) >> 96) + 283447036172924575727196451306956; + q = ((q * x) >> 96) + 401686690394027663651624208769553; + q = ((q * x) >> 96) + 204048457590392012362485061816622; + q = ((q * x) >> 96) + 31853899698501571402653359427138; + q = ((q * x) >> 96) + 909429971244387300277376558375; + assembly { + // Div in assembly because solidity adds a zero check despite the unchecked. + // The q polynomial is known not to have zeros in the domain. + // No scaling required because p is already 2**96 too large. + r := sdiv(p, q) + } + + // r is in the range (0, 0.125) * 2**96 + + // Finalization, we need to: + // * multiply by the scale factor s = 5.549… + // * add ln(2**96 / 10**18) + // * add k * ln(2) + // * multiply by 10**18 / 2**96 = 5**18 >> 78 + + // mul s * 5e18 * 2**96, base is now 5**18 * 2**192 + r *= 1677202110996718588342820967067443963516166; + // add ln(2) * k * 5e18 * 2**192 + r += 16597577552685614221487285958193947469193820559219878177908093499208371 * k; + // add ln(2**96 / 10**18) * 5e18 * 2**192 + r += 600920179829731861736702779321621459595472258049074101567377883020018308; + // base conversion: mul 2**18 / 2**192 + r >>= 174; + } + } + + /*////////////////////////////////////////////////////////////// + LOW LEVEL FIXED POINT OPERATIONS + //////////////////////////////////////////////////////////////*/ + + function mulDivDown( + uint256 x, + uint256 y, + uint256 denominator + ) internal pure returns (uint256 z) { + assembly { + // Store x * y in z for now. + z := mul(x, y) + + // Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y)) + if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) { + revert(0, 0) + } + + // Divide z by the denominator. + z := div(z, denominator) + } + } + + function mulDivUp( + uint256 x, + uint256 y, + uint256 denominator + ) internal pure returns (uint256 z) { + assembly { + // Store x * y in z for now. + z := mul(x, y) + + // Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y)) + if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) { + revert(0, 0) + } + + // First, divide z - 1 by the denominator and add 1. + // We allow z - 1 to underflow if z is 0, because we multiply the + // end result by 0 if z is zero, ensuring we return 0 if z is zero. + z := mul(iszero(iszero(z)), add(div(sub(z, 1), denominator), 1)) + } + } + + function rpow( + uint256 x, + uint256 n, + uint256 scalar + ) internal pure returns (uint256 z) { + assembly { + switch x + case 0 { + switch n + case 0 { + // 0 ** 0 = 1 + z := scalar + } + default { + // 0 ** n = 0 + z := 0 + } + } + default { + switch mod(n, 2) + case 0 { + // If n is even, store scalar in z for now. + z := scalar + } + default { + // If n is odd, store x in z for now. + z := x + } + + // Shifting right by 1 is like dividing by 2. + let half := shr(1, scalar) + + for { + // Shift n right by 1 before looping to halve it. + n := shr(1, n) + } n { + // Shift n right by 1 each iteration to halve it. + n := shr(1, n) + } { + // Revert immediately if x ** 2 would overflow. + // Equivalent to iszero(eq(div(xx, x), x)) here. + if shr(128, x) { + revert(0, 0) + } + + // Store x squared. + let xx := mul(x, x) + + // Round to the nearest number. + let xxRound := add(xx, half) + + // Revert if xx + half overflowed. + if lt(xxRound, xx) { + revert(0, 0) + } + + // Set x to scaled xxRound. + x := div(xxRound, scalar) + + // If n is even: + if mod(n, 2) { + // Compute z * x. + let zx := mul(z, x) + + // If z * x overflowed: + if iszero(eq(div(zx, x), z)) { + // Revert if x is non-zero. + if iszero(iszero(x)) { + revert(0, 0) + } + } + + // Round to the nearest number. + let zxRound := add(zx, half) + + // Revert if zx + half overflowed. + if lt(zxRound, zx) { + revert(0, 0) + } + + // Return properly scaled zxRound. + z := div(zxRound, scalar) + } + } + } + } + } + + /*////////////////////////////////////////////////////////////// + GENERAL NUMBER UTILITIES + //////////////////////////////////////////////////////////////*/ + + function sqrt(uint256 x) internal pure returns (uint256 z) { + assembly { + let y := x // We start y at x, which will help us make our initial estimate. + + z := 181 // The "correct" value is 1, but this saves a multiplication later. + + // This segment is to get a reasonable initial estimate for the Babylonian method. With a bad + // start, the correct # of bits increases ~linearly each iteration instead of ~quadratically. + + // We check y >= 2^(k + 8) but shift right by k bits + // each branch to ensure that if x >= 256, then y >= 256. + if iszero(lt(y, 0x10000000000000000000000000000000000)) { + y := shr(128, y) + z := shl(64, z) + } + if iszero(lt(y, 0x1000000000000000000)) { + y := shr(64, y) + z := shl(32, z) + } + if iszero(lt(y, 0x10000000000)) { + y := shr(32, y) + z := shl(16, z) + } + if iszero(lt(y, 0x1000000)) { + y := shr(16, y) + z := shl(8, z) + } + + // Goal was to get z*z*y within a small factor of x. More iterations could + // get y in a tighter range. Currently, we will have y in [256, 256*2^16). + // We ensured y >= 256 so that the relative difference between y and y+1 is small. + // That's not possible if x < 256 but we can just verify those cases exhaustively. + + // Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256. + // Correctness can be checked exhaustively for x < 256, so we assume y >= 256. + // Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps. + + // For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range + // (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256. + + // Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate + // sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18. + + // There is no overflow risk here since y < 2^136 after the first branch above. + z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181. + + // Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough. + z := shr(1, add(z, div(x, z))) + z := shr(1, add(z, div(x, z))) + z := shr(1, add(z, div(x, z))) + z := shr(1, add(z, div(x, z))) + z := shr(1, add(z, div(x, z))) + z := shr(1, add(z, div(x, z))) + z := shr(1, add(z, div(x, z))) + + // If x+1 is a perfect square, the Babylonian method cycles between + // floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor. + // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division + // Since the ceil is rare, we save gas on the assignment and repeat division in the rare case. + // If you don't care whether the floor or ceil square root is returned, you can remove this statement. + z := sub(z, lt(div(x, z), z)) + } + } + + function log2(uint256 x) internal pure returns (uint256 r) { + require(x > 0, "UNDEFINED"); + + assembly { + r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x)) + r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x)))) + r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) + r := or(r, shl(4, lt(0xffff, shr(r, x)))) + r := or(r, shl(3, lt(0xff, shr(r, x)))) + r := or(r, shl(2, lt(0xf, shr(r, x)))) + r := or(r, shl(1, lt(0x3, shr(r, x)))) + r := or(r, lt(0x1, shr(r, x))) + } + } +} + +library Arithmetic { + /// @notice Clamps a value between a minimum and maximum. + /// @param _value The value to clamp. + /// @param _min The minimum value. + /// @param _max The maximum value. + /// @return The clamped value. + function clamp(int256 _value, int256 _min, int256 _max) internal pure returns (int256) { + return SignedMath.min(SignedMath.max(_value, _min), _max); + } + + /// @notice (c)oefficient (d)enominator (exp)onentiation function. + /// Returns the result of: c * (1 - 1/d)^exp. + /// @param _coefficient Coefficient of the function. + /// @param _denominator Fractional denominator. + /// @param _exponent Power function exponent. + /// @return Result of c * (1 - 1/d)^exp. + function cdexp(int256 _coefficient, int256 _denominator, int256 _exponent) internal pure returns (int256) { + return (_coefficient * (FixedPointMathLib.powWad(1e18 - (1e18 / _denominator), _exponent * 1e18))) / 1e18; + } +} + +library Math { + enum Rounding { + Down, // Toward negative infinity + Up, // Toward infinity + Zero // Toward zero + } + + /** + * @dev Returns the largest of two numbers. + */ + function max(uint256 a, uint256 b) internal pure returns (uint256) { + return a >= b ? a : b; + } + + /** + * @dev Returns the smallest of two numbers. + */ + function min(uint256 a, uint256 b) internal pure returns (uint256) { + return a < b ? a : b; + } + + /** + * @dev Returns the average of two numbers. The result is rounded towards + * zero. + */ + function average(uint256 a, uint256 b) internal pure returns (uint256) { + // (a + b) / 2 can overflow. + return (a & b) + (a ^ b) / 2; + } + + /** + * @dev Returns the ceiling of the division of two numbers. + * + * This differs from standard division with `/` in that it rounds up instead + * of rounding down. + */ + function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { + // (a + b - 1) / b can overflow on addition, so we distribute. + return a == 0 ? 0 : (a - 1) / b + 1; + } + + /** + * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) + * with further edits by Uniswap Labs also under MIT license. + */ + function mulDiv( + uint256 x, + uint256 y, + uint256 denominator + ) internal pure returns (uint256 result) { + unchecked { + // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use + // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 + // variables such that product = prod1 * 2^256 + prod0. + uint256 prod0; // Least significant 256 bits of the product + uint256 prod1; // Most significant 256 bits of the product + assembly { + let mm := mulmod(x, y, not(0)) + prod0 := mul(x, y) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + + // Handle non-overflow cases, 256 by 256 division. + if (prod1 == 0) { + return prod0 / denominator; + } + + // Make sure the result is less than 2^256. Also prevents denominator == 0. + require(denominator > prod1); + + /////////////////////////////////////////////// + // 512 by 256 division. + /////////////////////////////////////////////// + + // Make division exact by subtracting the remainder from [prod1 prod0]. + uint256 remainder; + assembly { + // Compute remainder using mulmod. + remainder := mulmod(x, y, denominator) + + // Subtract 256 bit number from 512 bit number. + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) + } + + // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. + // See https://cs.stackexchange.com/q/138556/92363. + + // Does not overflow because the denominator cannot be zero at this stage in the function. + uint256 twos = denominator & (~denominator + 1); + assembly { + // Divide denominator by twos. + denominator := div(denominator, twos) + + // Divide [prod1 prod0] by twos. + prod0 := div(prod0, twos) + + // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. + twos := add(div(sub(0, twos), twos), 1) + } + + // Shift in bits from prod1 into prod0. + prod0 |= prod1 * twos; + + // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such + // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for + // four bits. That is, denominator * inv = 1 mod 2^4. + uint256 inverse = (3 * denominator) ^ 2; + + // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works + // in modular arithmetic, doubling the correct bits in each step. + inverse *= 2 - denominator * inverse; // inverse mod 2^8 + inverse *= 2 - denominator * inverse; // inverse mod 2^16 + inverse *= 2 - denominator * inverse; // inverse mod 2^32 + inverse *= 2 - denominator * inverse; // inverse mod 2^64 + inverse *= 2 - denominator * inverse; // inverse mod 2^128 + inverse *= 2 - denominator * inverse; // inverse mod 2^256 + + // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. + // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is + // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 + // is no longer required. + result = prod0 * inverse; + return result; + } + } + + /** + * @notice Calculates x * y / denominator with full precision, following the selected rounding direction. + */ + function mulDiv( + uint256 x, + uint256 y, + uint256 denominator, + Rounding rounding + ) internal pure returns (uint256) { + uint256 result = mulDiv(x, y, denominator); + if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) { + result += 1; + } + return result; + } + + /** + * @dev Returns the square root of a number. It the number is not a perfect square, the value is rounded down. + * + * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). + */ + function sqrt(uint256 a) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + + // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. + // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have + // `msb(a) <= a < 2*msb(a)`. + // We also know that `k`, the position of the most significant bit, is such that `msb(a) = 2**k`. + // This gives `2**k < a <= 2**(k+1)` → `2**(k/2) <= sqrt(a) < 2 ** (k/2+1)`. + // Using an algorithm similar to the msb conmputation, we are able to compute `result = 2**(k/2)` which is a + // good first aproximation of `sqrt(a)` with at least 1 correct bit. + uint256 result = 1; + uint256 x = a; + if (x >> 128 > 0) { + x >>= 128; + result <<= 64; + } + if (x >> 64 > 0) { + x >>= 64; + result <<= 32; + } + if (x >> 32 > 0) { + x >>= 32; + result <<= 16; + } + if (x >> 16 > 0) { + x >>= 16; + result <<= 8; + } + if (x >> 8 > 0) { + x >>= 8; + result <<= 4; + } + if (x >> 4 > 0) { + x >>= 4; + result <<= 2; + } + if (x >> 2 > 0) { + result <<= 1; + } + + // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, + // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at + // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision + // into the expected uint128 result. + unchecked { + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + return min(result, a / result); + } + } + + /** + * @notice Calculates sqrt(a), following the selected rounding direction. + */ + function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { + uint256 result = sqrt(a); + if (rounding == Rounding.Up && result * result < a) { + result += 1; + } + return result; + } +} + +library Burn { + /// @notice Burns a given amount of ETH. + /// @param _amount Amount of ETH to burn. + function eth(uint256 _amount) internal { + new Burner{ value: _amount }(); + } + + /// @notice Burns a given amount of gas. + /// @param _amount Amount of gas to burn. + function gas(uint256 _amount) internal view { + uint256 i = 0; + uint256 initialGas = gasleft(); + while (initialGas - gasleft() < _amount) { + ++i; + } + } +} + +abstract contract ResourceMetering is Initializable { + /// @notice Error returned when too much gas resource is consumed. + error OutOfGas(); + + /// @notice Represents the various parameters that control the way in which resources are + /// metered. Corresponds to the EIP-1559 resource metering system. + /// @custom:field prevBaseFee Base fee from the previous block(s). + /// @custom:field prevBoughtGas Amount of gas bought so far in the current block. + /// @custom:field prevBlockNum Last block number that the base fee was updated. + struct ResourceParams { + uint128 prevBaseFee; + uint64 prevBoughtGas; + uint64 prevBlockNum; + } + + /// @notice Represents the configuration for the EIP-1559 based curve for the deposit gas + /// market. These values should be set with care as it is possible to set them in + /// a way that breaks the deposit gas market. The target resource limit is defined as + /// maxResourceLimit / elasticityMultiplier. This struct was designed to fit within a + /// single word. There is additional space for additions in the future. + /// @custom:field maxResourceLimit Represents the maximum amount of deposit gas that + /// can be purchased per block. + /// @custom:field elasticityMultiplier Determines the target resource limit along with + /// the resource limit. + /// @custom:field baseFeeMaxChangeDenominator Determines max change on fee per block. + /// @custom:field minimumBaseFee The min deposit base fee, it is clamped to this + /// value. + /// @custom:field systemTxMaxGas The amount of gas supplied to the system + /// transaction. This should be set to the same + /// number that the op-node sets as the gas limit + /// for the system transaction. + /// @custom:field maximumBaseFee The max deposit base fee, it is clamped to this + /// value. + struct ResourceConfig { + uint32 maxResourceLimit; + uint8 elasticityMultiplier; + uint8 baseFeeMaxChangeDenominator; + uint32 minimumBaseFee; + uint32 systemTxMaxGas; + uint128 maximumBaseFee; + } + + /// @notice EIP-1559 style gas parameters. + ResourceParams public params; + + /// @notice Reserve extra slots (to a total of 50) in the storage layout for future upgrades. + uint256[48] private __gap; + + /// @notice Meters access to a function based an amount of a requested resource. + /// @param _amount Amount of the resource requested. + modifier metered(uint64 _amount) { + // Record initial gas amount so we can refund for it later. + uint256 initialGas = gasleft(); + + // Run the underlying function. + _; + + // Run the metering function. + _metered(_amount, initialGas); + } + + /// @notice An internal function that holds all of the logic for metering a resource. + /// @param _amount Amount of the resource requested. + /// @param _initialGas The amount of gas before any modifier execution. + function _metered(uint64 _amount, uint256 _initialGas) internal { + // Update block number and base fee if necessary. + uint256 blockDiff = block.number - params.prevBlockNum; + + ResourceConfig memory config = _resourceConfig(); + int256 targetResourceLimit = + int256(uint256(config.maxResourceLimit)) / int256(uint256(config.elasticityMultiplier)); + + if (blockDiff > 0) { + // Handle updating EIP-1559 style gas parameters. We use EIP-1559 to restrict the rate + // at which deposits can be created and therefore limit the potential for deposits to + // spam the L2 system. Fee scheme is very similar to EIP-1559 with minor changes. + int256 gasUsedDelta = int256(uint256(params.prevBoughtGas)) - targetResourceLimit; + int256 baseFeeDelta = (int256(uint256(params.prevBaseFee)) * gasUsedDelta) + / (targetResourceLimit * int256(uint256(config.baseFeeMaxChangeDenominator))); + + // Update base fee by adding the base fee delta and clamp the resulting value between + // min and max. + int256 newBaseFee = Arithmetic.clamp({ + _value: int256(uint256(params.prevBaseFee)) + baseFeeDelta, + _min: int256(uint256(config.minimumBaseFee)), + _max: int256(uint256(config.maximumBaseFee)) + }); + + // If we skipped more than one block, we also need to account for every empty block. + // Empty block means there was no demand for deposits in that block, so we should + // reflect this lack of demand in the fee. + if (blockDiff > 1) { + // Update the base fee by repeatedly applying the exponent 1-(1/change_denominator) + // blockDiff - 1 times. Simulates multiple empty blocks. Clamp the resulting value + // between min and max. + newBaseFee = Arithmetic.clamp({ + _value: Arithmetic.cdexp({ + _coefficient: newBaseFee, + _denominator: int256(uint256(config.baseFeeMaxChangeDenominator)), + _exponent: int256(blockDiff - 1) + }), + _min: int256(uint256(config.minimumBaseFee)), + _max: int256(uint256(config.maximumBaseFee)) + }); + } + + // Update new base fee, reset bought gas, and update block number. + params.prevBaseFee = uint128(uint256(newBaseFee)); + params.prevBoughtGas = 0; + params.prevBlockNum = uint64(block.number); + } + + // Make sure we can actually buy the resource amount requested by the user. + params.prevBoughtGas += _amount; + if (int256(uint256(params.prevBoughtGas)) > int256(uint256(config.maxResourceLimit))) { + revert OutOfGas(); + } + + // Determine the amount of ETH to be paid. + uint256 resourceCost = uint256(_amount) * uint256(params.prevBaseFee); + + // We currently charge for this ETH amount as an L1 gas burn, so we convert the ETH amount + // into gas by dividing by the L1 base fee. We assume a minimum base fee of 1 gwei to avoid + // division by zero for L1s that don't support 1559 or to avoid excessive gas burns during + // periods of extremely low L1 demand. One-day average gas fee hasn't dipped below 1 gwei + // during any 1 day period in the last 5 years, so should be fine. + uint256 gasCost = resourceCost / Math.max(block.basefee, 1 gwei); + + // Give the user a refund based on the amount of gas they used to do all of the work up to + // this point. Since we're at the end of the modifier, this should be pretty accurate. Acts + // effectively like a dynamic stipend (with a minimum value). + uint256 usedGas = _initialGas - gasleft(); + if (gasCost > usedGas) { + Burn.gas(gasCost - usedGas); + } + } + + /// @notice Adds an amount of L2 gas consumed to the prev bought gas params. This is meant to be used + /// when L2 system transactions are generated from L1. + /// @param _amount Amount of the L2 gas resource requested. + function useGas(uint32 _amount) internal { + params.prevBoughtGas += uint64(_amount); + } + + /// @notice Virtual function that returns the resource config. + /// Contracts that inherit this contract must implement this function. + /// @return ResourceConfig + function _resourceConfig() internal virtual returns (ResourceConfig memory); + + /// @notice Sets initial resource parameter values. + /// This function must either be called by the initializer function of an upgradeable + /// child contract. + function __ResourceMetering_init() internal onlyInitializing { + if (params.prevBlockNum == 0) { + params = ResourceParams({ prevBaseFee: 1 gwei, prevBoughtGas: 0, prevBlockNum: uint64(block.number) }); + } + } +} + +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + * + * [IMPORTANT] + * ==== + * You shouldn't rely on `isContract` to protect against flash loan attacks! + * + * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets + * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract + * constructor. + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize/address.code.length, which returns 0 + // for contracts in construction, since the code is only stored at the end + // of the constructor execution. + + return account.code.length > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + require(isContract(target), "Address: delegate call to non-contract"); + + (bool success, bytes memory returndata) = target.delegatecall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the + * revert reason using the provided one. + * + * _Available since v4.3._ + */ + function verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) internal pure returns (bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + +abstract contract Initializable { + /** + * @dev Indicates that the contract has been initialized. + * @custom:oz-retyped-from bool + */ + uint8 private _initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private _initializing; + + /** + * @dev Triggered when the contract has been initialized or reinitialized. + */ + event Initialized(uint8 version); + + /** + * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, + * `onlyInitializing` functions can be used to initialize parent contracts. Equivalent to `reinitializer(1)`. + */ + modifier initializer() { + bool isTopLevelCall = !_initializing; + require( + (isTopLevelCall && _initialized < 1) || (!Address.isContract(address(this)) && _initialized == 1), + "Initializable: contract is already initialized" + ); + _initialized = 1; + if (isTopLevelCall) { + _initializing = true; + } + _; + if (isTopLevelCall) { + _initializing = false; + emit Initialized(1); + } + } + + /** + * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the + * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be + * used to initialize parent contracts. + * + * `initializer` is equivalent to `reinitializer(1)`, so a reinitializer may be used after the original + * initialization step. This is essential to configure modules that are added through upgrades and that require + * initialization. + * + * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in + * a contract, executing them in the right order is up to the developer or operator. + */ + modifier reinitializer(uint8 version) { + require(!_initializing && _initialized < version, "Initializable: contract is already initialized"); + _initialized = version; + _initializing = true; + _; + _initializing = false; + emit Initialized(version); + } + + /** + * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the + * {initializer} and {reinitializer} modifiers, directly or indirectly. + */ + modifier onlyInitializing() { + require(_initializing, "Initializable: contract is not initializing"); + _; + } + + /** + * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. + * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized + * to any version. It is recommended to use this to lock implementation contracts that are designed to be called + * through proxies. + */ + function _disableInitializers() internal virtual { + require(!_initializing, "Initializable: contract is initializing"); + if (_initialized < type(uint8).max) { + _initialized = type(uint8).max; + emit Initialized(type(uint8).max); + } + } +} + +contract OptimismPortal is Initializable, ResourceMetering, ISemver { + /// @notice Allows for interactions with non standard ERC20 tokens. + using SafeERC20 for IERC20; + + /// @notice Represents a proven withdrawal. + /// @custom:field outputRoot Root of the L2 output this was proven against. + /// @custom:field timestamp Timestamp at whcih the withdrawal was proven. + /// @custom:field l2OutputIndex Index of the output this was proven against. + struct ProvenWithdrawal { + bytes32 outputRoot; + uint128 timestamp; + uint128 l2OutputIndex; + } + + /// @notice Version of the deposit event. + uint256 internal constant DEPOSIT_VERSION = 0; + + /// @notice The L2 gas limit set when eth is deposited using the receive() function. + uint64 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 100_000; + + /// @notice The L2 gas limit for system deposit transactions that are initiated from L1. + uint32 internal constant SYSTEM_DEPOSIT_GAS_LIMIT = 200_000; + + /// @notice Address of the L2 account which initiated a withdrawal in this transaction. + /// If the of this variable is the default L2 sender address, then we are NOT inside of + /// a call to finalizeWithdrawalTransaction. + address public l2Sender; + + /// @notice A list of withdrawal hashes which have been successfully finalized. + mapping(bytes32 => bool) public finalizedWithdrawals; + + /// @notice A mapping of withdrawal hashes to `ProvenWithdrawal` data. + mapping(bytes32 => ProvenWithdrawal) public provenWithdrawals; + + /// @custom:legacy + /// @custom:spacer paused + /// @notice Spacer for backwards compatibility. + bool private spacer_53_0_1; + + /// @notice Contract of the Superchain Config. + SuperchainConfig public superchainConfig; + + /// @notice Contract of the L2OutputOracle. + /// @custom:network-specific + L2OutputOracle public l2Oracle; + + /// @notice Contract of the SystemConfig. + /// @custom:network-specific + SystemConfig public systemConfig; + + /// @custom:spacer disputeGameFactory + /// @notice Spacer for backwards compatibility. + address private spacer_56_0_20; + + /// @custom:spacer provenWithdrawals + /// @notice Spacer for backwards compatibility. + bytes32 private spacer_57_0_32; + + /// @custom:spacer disputeGameBlacklist + /// @notice Spacer for backwards compatibility. + bytes32 private spacer_58_0_32; + + /// @custom:spacer respectedGameType + respectedGameTypeUpdatedAt + /// @notice Spacer for backwards compatibility. + bytes32 private spacer_59_0_32; + + /// @custom:spacer proofSubmitters + /// @notice Spacer for backwards compatibility. + bytes32 private spacer_60_0_32; + + /// @notice Represents the amount of native asset minted in L2. This may not + /// be 100% accurate due to the ability to send ether to the contract + /// without triggering a deposit transaction. It also is used to prevent + /// overflows for L2 account balances when custom gas tokens are used. + /// It is not safe to trust `ERC20.balanceOf` as it may lie. + uint256 internal _balance; + + /// @notice Emitted when a transaction is deposited from L1 to L2. + /// The parameters of this event are read by the rollup node and used to derive deposit + /// transactions on L2. + /// @param from Address that triggered the deposit transaction. + /// @param to Address that the deposit transaction is directed to. + /// @param version Version of this deposit transaction event. + /// @param opaqueData ABI encoded deposit data to be parsed off-chain. + event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData); + + /// @notice Emitted when a withdrawal transaction is proven. + /// @param withdrawalHash Hash of the withdrawal transaction. + /// @param from Address that triggered the withdrawal transaction. + /// @param to Address that the withdrawal transaction is directed to. + event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to); + + /// @notice Emitted when a withdrawal transaction is finalized. + /// @param withdrawalHash Hash of the withdrawal transaction. + /// @param success Whether the withdrawal transaction was successful. + event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success); + + /// @notice Reverts when paused. + modifier whenNotPaused() { + if (paused()) revert CallPaused(); + _; + } + + /// @notice Semantic version. + /// @custom:semver 2.8.1-beta.1 + function version() public pure virtual returns (string memory) { + return "2.8.1-beta.1"; + } + + /// @notice Constructs the OptimismPortal contract. + constructor() { + initialize({ + _l2Oracle: L2OutputOracle(address(0)), + _systemConfig: SystemConfig(address(0)), + _superchainConfig: SuperchainConfig(address(0)) + }); + } + + /// @notice Initializer. + /// @param _l2Oracle Contract of the L2OutputOracle. + /// @param _systemConfig Contract of the SystemConfig. + /// @param _superchainConfig Contract of the SuperchainConfig. + function initialize( + L2OutputOracle _l2Oracle, + SystemConfig _systemConfig, + SuperchainConfig _superchainConfig + ) + public + initializer + { + l2Oracle = _l2Oracle; + systemConfig = _systemConfig; + superchainConfig = _superchainConfig; + if (l2Sender == address(0)) { + l2Sender = Constants.DEFAULT_L2_SENDER; + } + __ResourceMetering_init(); + } + + /// @notice Getter for the balance of the contract. + function balance() public view returns (uint256) { + (address token,) = gasPayingToken(); + if (token == Constants.ETHER) { + return address(this).balance; + } else { + return _balance; + } + } + + /// @notice Getter function for the address of the guardian. + /// Public getter is legacy and will be removed in the future. Use `SuperchainConfig.guardian()` instead. + /// @return Address of the guardian. + /// @custom:legacy + function guardian() public view returns (address) { + return superchainConfig.guardian(); + } + + /// @notice Getter for the current paused status. + /// @return paused_ Whether or not the contract is paused. + function paused() public view returns (bool paused_) { + paused_ = superchainConfig.paused(); + } + + /// @notice Computes the minimum gas limit for a deposit. + /// The minimum gas limit linearly increases based on the size of the calldata. + /// This is to prevent users from creating L2 resource usage without paying for it. + /// This function can be used when interacting with the portal to ensure forwards + /// compatibility. + /// @param _byteCount Number of bytes in the calldata. + /// @return The minimum gas limit for a deposit. + function minimumGasLimit(uint64 _byteCount) public pure returns (uint64) { + return _byteCount * 16 + 21000; + } + + /// @notice Accepts value so that users can send ETH directly to this contract and have the + /// funds be deposited to their address on L2. This is intended as a convenience + /// function for EOAs. Contracts should call the depositTransaction() function directly + /// otherwise any deposited funds will be lost due to address aliasing. + receive() external payable { + depositTransaction(msg.sender, msg.value, RECEIVE_DEFAULT_GAS_LIMIT, false, bytes("")); + } + + /// @notice Accepts ETH value without triggering a deposit to L2. + /// This function mainly exists for the sake of the migration between the legacy + /// Optimism system and Bedrock. + function donateETH() external payable { + // Intentionally empty. + } + + /// @notice Returns the gas paying token and its decimals. + function gasPayingToken() internal view returns (address addr_, uint8 decimals_) { + (addr_, decimals_) = systemConfig.gasPayingToken(); + } + + /// @notice Getter for the resource config. + /// Used internally by the ResourceMetering contract. + /// The SystemConfig is the source of truth for the resource config. + /// @return ResourceMetering ResourceConfig + function _resourceConfig() internal view override returns (ResourceMetering.ResourceConfig memory) { + return systemConfig.resourceConfig(); + } + + /// @notice Proves a withdrawal transaction. + /// @param _tx Withdrawal transaction to finalize. + /// @param _l2OutputIndex L2 output index to prove against. + /// @param _outputRootProof Inclusion proof of the L2ToL1MessagePasser contract's storage root. + /// @param _withdrawalProof Inclusion proof of the withdrawal in L2ToL1MessagePasser contract. + function proveWithdrawalTransaction( + Types.WithdrawalTransaction memory _tx, + uint256 _l2OutputIndex, + Types.OutputRootProof calldata _outputRootProof, + bytes[] calldata _withdrawalProof + ) + external + whenNotPaused + { + // Prevent users from creating a deposit transaction where this address is the message + // sender on L2. Because this is checked here, we do not need to check again in + // `finalizeWithdrawalTransaction`. + if (_tx.target == address(this)) revert BadTarget(); + + // Get the output root and load onto the stack to prevent multiple mloads. This will + // revert if there is no output root for the given block number. + bytes32 outputRoot = l2Oracle.getL2Output(_l2OutputIndex).outputRoot; + + // Verify that the output root can be generated with the elements in the proof. + require( + outputRoot == Hashing.hashOutputRootProof(_outputRootProof), "OptimismPortal: invalid output root proof" + ); + + // Load the ProvenWithdrawal into memory, using the withdrawal hash as a unique identifier. + bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx); + ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[withdrawalHash]; + + // We generally want to prevent users from proving the same withdrawal multiple times + // because each successive proof will update the timestamp. A malicious user can take + // advantage of this to prevent other users from finalizing their withdrawal. However, + // since withdrawals are proven before an output root is finalized, we need to allow users + // to re-prove their withdrawal only in the case that the output root for their specified + // output index has been updated. + require( + provenWithdrawal.timestamp == 0 + || l2Oracle.getL2Output(provenWithdrawal.l2OutputIndex).outputRoot != provenWithdrawal.outputRoot, + "OptimismPortal: withdrawal hash has already been proven" + ); + + // Compute the storage slot of the withdrawal hash in the L2ToL1MessagePasser contract. + // Refer to the Solidity documentation for more information on how storage layouts are + // computed for mappings. + bytes32 storageKey = keccak256( + abi.encode( + withdrawalHash, + uint256(0) // The withdrawals mapping is at the first slot in the layout. + ) + ); + + // Verify that the hash of this withdrawal was stored in the L2toL1MessagePasser contract + // on L2. If this is true, under the assumption that the SecureMerkleTrie does not have + // bugs, then we know that this withdrawal was actually triggered on L2 and can therefore + // be relayed on L1. + require( + SecureMerkleTrie.verifyInclusionProof({ + _key: abi.encode(storageKey), + _value: hex"01", + _proof: _withdrawalProof, + _root: _outputRootProof.messagePasserStorageRoot + }), + "OptimismPortal: invalid withdrawal inclusion proof" + ); + + // Designate the withdrawalHash as proven by storing the `outputRoot`, `timestamp`, and + // `l2BlockNumber` in the `provenWithdrawals` mapping. A `withdrawalHash` can only be + // proven once unless it is submitted again with a different outputRoot. + provenWithdrawals[withdrawalHash] = ProvenWithdrawal({ + outputRoot: outputRoot, + timestamp: uint128(block.timestamp), + l2OutputIndex: uint128(_l2OutputIndex) + }); + + // Emit a `WithdrawalProven` event. + emit WithdrawalProven(withdrawalHash, _tx.sender, _tx.target); + } + + /// @notice Finalizes a withdrawal transaction. + /// @param _tx Withdrawal transaction to finalize. + function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external whenNotPaused { + // Make sure that the l2Sender has not yet been set. The l2Sender is set to a value other + // than the default value when a withdrawal transaction is being finalized. This check is + // a defacto reentrancy guard. + if (l2Sender != Constants.DEFAULT_L2_SENDER) revert NonReentrant(); + + // Grab the proven withdrawal from the `provenWithdrawals` map. + bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx); + ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[withdrawalHash]; + + // A withdrawal can only be finalized if it has been proven. We know that a withdrawal has + // been proven at least once when its timestamp is non-zero. Unproven withdrawals will have + // a timestamp of zero. + require(provenWithdrawal.timestamp != 0, "OptimismPortal: withdrawal has not been proven yet"); + + // As a sanity check, we make sure that the proven withdrawal's timestamp is greater than + // starting timestamp inside the L2OutputOracle. Not strictly necessary but extra layer of + // safety against weird bugs in the proving step. + require( + provenWithdrawal.timestamp >= l2Oracle.startingTimestamp(), + "OptimismPortal: withdrawal timestamp less than L2 Oracle starting timestamp" + ); + + // A proven withdrawal must wait at least the finalization period before it can be + // finalized. This waiting period can elapse in parallel with the waiting period for the + // output the withdrawal was proven against. In effect, this means that the minimum + // withdrawal time is proposal submission time + finalization period. + require( + _isFinalizationPeriodElapsed(provenWithdrawal.timestamp), + "OptimismPortal: proven withdrawal finalization period has not elapsed" + ); + + // Grab the OutputProposal from the L2OutputOracle, will revert if the output that + // corresponds to the given index has not been proposed yet. + Types.OutputProposal memory proposal = l2Oracle.getL2Output(provenWithdrawal.l2OutputIndex); + + // Check that the output root that was used to prove the withdrawal is the same as the + // current output root for the given output index. An output root may change if it is + // deleted by the challenger address and then re-proposed. + require( + proposal.outputRoot == provenWithdrawal.outputRoot, + "OptimismPortal: output root proven is not the same as current output root" + ); + + // Check that the output proposal has also been finalized. + require( + _isFinalizationPeriodElapsed(proposal.timestamp), + "OptimismPortal: output proposal finalization period has not elapsed" + ); + + // Check that this withdrawal has not already been finalized, this is replay protection. + require(finalizedWithdrawals[withdrawalHash] == false, "OptimismPortal: withdrawal has already been finalized"); + + // Mark the withdrawal as finalized so it can't be replayed. + finalizedWithdrawals[withdrawalHash] = true; + + // Set the l2Sender so contracts know who triggered this withdrawal on L2. + // This acts as a reentrancy guard. + l2Sender = _tx.sender; + + bool success; + (address token,) = gasPayingToken(); + if (token == Constants.FACET_COMPUTE_TOKEN) { + require(_tx.value == 0, "Facet: value is not supported for ETH"); + + // Trigger the call to the target contract. We use a custom low level method + // SafeCall.callWithMinGas to ensure two key properties + // 1. Target contracts cannot force this call to run out of gas by returning a very large + // amount of data (and this is OK because we don't care about the returndata here). + // 2. The amount of gas provided to the execution context of the target is at least the + // gas limit specified by the user. If there is not enough gas in the current context + // to accomplish this, `callWithMinGas` will revert. + success = SafeCall.callWithMinGas(_tx.target, _tx.gasLimit, _tx.value, _tx.data); + } else { + // Cannot call the token contract directly from the portal. This would allow an attacker + // to call approve from a withdrawal and drain the balance of the portal. + if (_tx.target == token) revert BadTarget(); + + // Only transfer value when a non zero value is specified. This saves gas in the case of + // using the standard bridge or arbitrary message passing. + if (_tx.value != 0) { + // Update the contracts internal accounting of the amount of native asset in L2. + _balance -= _tx.value; + + // Read the balance of the target contract before the transfer so the consistency + // of the transfer can be checked afterwards. + uint256 startBalance = IERC20(token).balanceOf(address(this)); + + // Transfer the ERC20 balance to the target, accounting for non standard ERC20 + // implementations that may not return a boolean. This reverts if the low level + // call is not successful. + IERC20(token).safeTransfer({ to: _tx.target, value: _tx.value }); + + // The balance must be transferred exactly. + if (IERC20(token).balanceOf(address(this)) != startBalance - _tx.value) { + revert TransferFailed(); + } + } + + // Make a call to the target contract only if there is calldata. + if (_tx.data.length != 0) { + success = SafeCall.callWithMinGas(_tx.target, _tx.gasLimit, 0, _tx.data); + } else { + success = true; + } + } + + // Reset the l2Sender back to the default value. + l2Sender = Constants.DEFAULT_L2_SENDER; + + // All withdrawals are immediately finalized. Replayability can + // be achieved through contracts built on top of this contract + emit WithdrawalFinalized(withdrawalHash, success); + + // Reverting here is useful for determining the exact gas cost to successfully execute the + // sub call to the target contract if the minimum gas limit specified by the user would not + // be sufficient to execute the sub call. + if (success == false && tx.origin == Constants.ESTIMATION_ADDRESS) { + revert GasEstimation(); + } + } + + /// @notice Entrypoint to depositing an ERC20 token as a custom gas token. + /// This function depends on a well formed ERC20 token. There are only + /// so many checks that can be done on chain for this so it is assumed + /// that chain operators will deploy chains with well formed ERC20 tokens. + /// @param _to Target address on L2. + /// @param _mint Units of ERC20 token to deposit into L2. + /// @param _value Units of ERC20 token to send on L2 to the recipient. + /// @param _gasLimit Amount of L2 gas to purchase by burning gas on L1. + /// @param _isCreation Whether or not the transaction is a contract creation. + /// @param _data Data to trigger the recipient with. + function depositERC20Transaction( + address _to, + uint256 _mint, + uint256 _value, + uint64 _gasLimit, + bool _isCreation, + bytes memory _data + ) + public + metered(_gasLimit) + { + // Can only be called if an ERC20 token is used for gas paying on L2 + (address token,) = gasPayingToken(); + if (token == Constants.ETHER) revert OnlyCustomGasToken(); + + // Gives overflow protection for L2 account balances. + _balance += _mint; + + // Get the balance of the portal before the transfer. + uint256 startBalance = IERC20(token).balanceOf(address(this)); + + // Take ownership of the token. It is assumed that the user has given the portal an approval. + IERC20(token).safeTransferFrom({ from: msg.sender, to: address(this), value: _mint }); + + // Double check that the portal now has the exact amount of token. + if (IERC20(token).balanceOf(address(this)) != startBalance + _mint) { + revert TransferFailed(); + } + + _depositTransaction({ + _to: _to, + _mint: _mint, + _value: _value, + _gasLimit: _gasLimit, + _isCreation: _isCreation, + _data: _data + }); + } + + /// @notice Accepts deposits of ETH and data, and emits a TransactionDeposited event for use in + /// deriving deposit transactions. Note that if a deposit is made by a contract, its + /// address will be aliased when retrieved using `tx.origin` or `msg.sender`. Consider + /// using the CrossDomainMessenger contracts for a simpler developer experience. + /// @param _to Target address on L2. + /// @param _value ETH value to send to the recipient. + /// @param _gasLimit Amount of L2 gas to purchase by burning gas on L1. + /// @param _isCreation Whether or not the transaction is a contract creation. + /// @param _data Data to trigger the recipient with. + function depositTransaction( + address _to, + uint256 _value, + uint64 _gasLimit, + bool _isCreation, + bytes memory _data + ) + public + payable + metered(_gasLimit) + { + (address token,) = gasPayingToken(); + if (token != Constants.ETHER && msg.value != 0) revert NoValue(); + + _depositTransaction({ + _to: _to, + _mint: msg.value, + _value: _value, + _gasLimit: _gasLimit, + _isCreation: _isCreation, + _data: _data + }); + } + + /// @notice Common logic for creating deposit transactions. + /// @param _to Target address on L2. + /// @param _mint Units of asset to deposit into L2. + /// @param _value Units of asset to send on L2 to the recipient. + /// @param _gasLimit Amount of L2 gas to purchase by burning gas on L1. + /// @param _isCreation Whether or not the transaction is a contract creation. + /// @param _data Data to trigger the recipient with. + function _depositTransaction( + address _to, + uint256 _mint, + uint256 _value, + uint64 _gasLimit, + bool _isCreation, + bytes memory _data + ) + internal + { + revert("Use LibFacet.sendFacetTransaction instead"); + + // Just to be safe, make sure that people specify address(0) as the target when doing + // contract creations. + if (_isCreation && _to != address(0)) revert BadTarget(); + + // Prevent depositing transactions that have too small of a gas limit. Users should pay + // more for more resource usage. + if (_gasLimit < minimumGasLimit(uint64(_data.length))) revert SmallGasLimit(); + + // Prevent the creation of deposit transactions that have too much calldata. This gives an + // upper limit on the size of unsafe blocks over the p2p network. 120kb is chosen to ensure + // that the transaction can fit into the p2p network policy of 128kb even though deposit + // transactions are not gossipped over the p2p network. + if (_data.length > 120_000) revert LargeCalldata(); + + // Transform the from-address to its alias if the caller is a contract. + address from = msg.sender; + if (msg.sender != tx.origin) { + from = AddressAliasHelper.applyL1ToL2Alias(msg.sender); + } + + // Compute the opaque data that will be emitted as part of the TransactionDeposited event. + // We use opaque data so that we can update the TransactionDeposited event in the future + // without breaking the current interface. + bytes memory opaqueData = abi.encodePacked(_mint, _value, _gasLimit, _isCreation, _data); + + // Emit a TransactionDeposited event so that the rollup node can derive a deposit + // transaction for this deposit. + emit TransactionDeposited(from, _to, DEPOSIT_VERSION, opaqueData); + } + + /// @notice Sets the gas paying token for the L2 system. This token is used as the + /// L2 native asset. Only the SystemConfig contract can call this function. + function setGasPayingToken(address _token, uint8 _decimals, bytes32 _name, bytes32 _symbol) external { + if (msg.sender != address(systemConfig)) revert Unauthorized(); + + // Set L2 deposit gas as used without paying burning gas. Ensures that deposits cannot use too much L2 gas. + // This value must be large enough to cover the cost of calling `L1Block.setGasPayingToken`. + useGas(SYSTEM_DEPOSIT_GAS_LIMIT); + + // Emit the special deposit transaction directly that sets the gas paying + // token in the L1Block predeploy contract. + emit TransactionDeposited( + Constants.DEPOSITOR_ACCOUNT, + Predeploys.L1_BLOCK_ATTRIBUTES, + DEPOSIT_VERSION, + abi.encodePacked( + uint256(0), // mint + uint256(0), // value + uint64(SYSTEM_DEPOSIT_GAS_LIMIT), // gasLimit + false, // isCreation, + abi.encodeCall(L1Block.setGasPayingToken, (_token, _decimals, _name, _symbol)) + ) + ); + } + + /// @notice Determine if a given output is finalized. + /// Reverts if the call to l2Oracle.getL2Output reverts. + /// Returns a boolean otherwise. + /// @param _l2OutputIndex Index of the L2 output to check. + /// @return Whether or not the output is finalized. + function isOutputFinalized(uint256 _l2OutputIndex) external view returns (bool) { + return _isFinalizationPeriodElapsed(l2Oracle.getL2Output(_l2OutputIndex).timestamp); + } + + /// @notice Determines whether the finalization period has elapsed with respect to + /// the provided block timestamp. + /// @param _timestamp Timestamp to check. + /// @return Whether or not the finalization period has elapsed. + function _isFinalizationPeriodElapsed(uint256 _timestamp) internal view returns (bool) { + return block.timestamp > _timestamp + l2Oracle.FINALIZATION_PERIOD_SECONDS(); + } +} \ No newline at end of file diff --git a/packages/backend/discovery/_templates/opstack/SystemConfig_facet/shape/SystemConfig_v2_3_0_beta_2_facet.sol b/packages/backend/discovery/_templates/opstack/SystemConfig_facet/shape/SystemConfig_v2_3_0_beta_2_facet.sol new file mode 100644 index 00000000000..a091a014c18 --- /dev/null +++ b/packages/backend/discovery/_templates/opstack/SystemConfig_facet/shape/SystemConfig_v2_3_0_beta_2_facet.sol @@ -0,0 +1,2215 @@ +// SPDX-License-Identifier: Unknown +pragma solidity 0.8.15; + +library Constants { + /// @notice Special address to be used as the tx origin for gas estimation calls in the + /// OptimismPortal and CrossDomainMessenger calls. You only need to use this address if + /// the minimum gas limit specified by the user is not actually enough to execute the + /// given message and you're attempting to estimate the actual necessary gas limit. We + /// use address(1) because it's the ecrecover precompile and therefore guaranteed to + /// never have any code on any EVM chain. + address internal constant ESTIMATION_ADDRESS = address(1); + + /// @notice Value used for the L2 sender storage slot in both the OptimismPortal and the + /// CrossDomainMessenger contracts before an actual sender is set. This value is + /// non-zero to reduce the gas cost of message passing transactions. + address internal constant DEFAULT_L2_SENDER = 0x000000000000000000000000000000000000dEaD; + + /// @notice The storage slot that holds the address of a proxy implementation. + /// @dev `bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)` + bytes32 internal constant PROXY_IMPLEMENTATION_ADDRESS = + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + /// @notice The storage slot that holds the address of the owner. + /// @dev `bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)` + bytes32 internal constant PROXY_OWNER_ADDRESS = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + + /// @notice The address that represents ether when dealing with ERC20 token addresses. + address internal constant ETHER = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + address internal constant FACET_COMPUTE_TOKEN = 0xFACE7fAcE7fAcE7FacE7FACE7FACe7FAcE7fACE7; + + /// @notice The address that represents the system caller responsible for L1 attributes + /// transactions. + address internal constant DEPOSITOR_ACCOUNT = 0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001; + + /// @notice Returns the default values for the ResourceConfig. These are the recommended values + /// for a production network. + function DEFAULT_RESOURCE_CONFIG() internal pure returns (ResourceMetering.ResourceConfig memory) { + ResourceMetering.ResourceConfig memory config = ResourceMetering.ResourceConfig({ + maxResourceLimit: 20_000_000, + elasticityMultiplier: 10, + baseFeeMaxChangeDenominator: 8, + minimumBaseFee: 1 gwei, + systemTxMaxGas: 1_000_000, + maximumBaseFee: type(uint128).max + }); + return config; + } +} + +library LibString { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The length of the output is too small to contain all the hex digits. + error HexLengthInsufficient(); + + /// @dev The length of the string is more than 32 bytes. + error TooBigForSmallString(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The constant returned when the `search` is not found in the string. + uint256 internal constant NOT_FOUND = type(uint256).max; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* DECIMAL OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the base 10 decimal representation of `value`. + function toString(uint256 value) internal pure returns (string memory str) { + /// @solidity memory-safe-assembly + assembly { + // The maximum value of a uint256 contains 78 digits (1 byte per digit), but + // we allocate 0xa0 bytes to keep the free memory pointer 32-byte word aligned. + // We will need 1 word for the trailing zeros padding, 1 word for the length, + // and 3 words for a maximum of 78 digits. + str := add(mload(0x40), 0x80) + // Update the free memory pointer to allocate. + mstore(0x40, add(str, 0x20)) + // Zeroize the slot after the string. + mstore(str, 0) + + // Cache the end of the memory to calculate the length later. + let end := str + + let w := not(0) // Tsk. + // We write the string from rightmost digit to leftmost digit. + // The following is essentially a do-while loop that also handles the zero case. + for { let temp := value } 1 {} { + str := add(str, w) // `sub(str, 1)`. + // Write the character to the pointer. + // The ASCII index of the '0' character is 48. + mstore8(str, add(48, mod(temp, 10))) + // Keep dividing `temp` until zero. + temp := div(temp, 10) + if iszero(temp) { break } + } + + let length := sub(end, str) + // Move the pointer 32 bytes leftwards to make room for the length. + str := sub(str, 0x20) + // Store the length. + mstore(str, length) + } + } + + /// @dev Returns the base 10 decimal representation of `value`. + function toString(int256 value) internal pure returns (string memory str) { + if (value >= 0) { + return toString(uint256(value)); + } + unchecked { + str = toString(uint256(-value)); + } + /// @solidity memory-safe-assembly + assembly { + // We still have some spare memory space on the left, + // as we have allocated 3 words (96 bytes) for up to 78 digits. + let length := mload(str) // Load the string length. + mstore(str, 0x2d) // Store the '-' character. + str := sub(str, 1) // Move back the string pointer by a byte. + mstore(str, add(length, 1)) // Update the string length. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* HEXADECIMAL OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the hexadecimal representation of `value`, + /// left-padded to an input length of `length` bytes. + /// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte, + /// giving a total length of `length * 2 + 2` bytes. + /// Reverts if `length` is too small for the output to contain all the digits. + function toHexString(uint256 value, uint256 length) internal pure returns (string memory str) { + str = toHexStringNoPrefix(value, length); + /// @solidity memory-safe-assembly + assembly { + let strLength := add(mload(str), 2) // Compute the length. + mstore(str, 0x3078) // Write the "0x" prefix. + str := sub(str, 2) // Move the pointer. + mstore(str, strLength) // Write the length. + } + } + + /// @dev Returns the hexadecimal representation of `value`, + /// left-padded to an input length of `length` bytes. + /// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte, + /// giving a total length of `length * 2` bytes. + /// Reverts if `length` is too small for the output to contain all the digits. + function toHexStringNoPrefix(uint256 value, uint256 length) + internal + pure + returns (string memory str) + { + /// @solidity memory-safe-assembly + assembly { + // We need 0x20 bytes for the trailing zeros padding, `length * 2` bytes + // for the digits, 0x02 bytes for the prefix, and 0x20 bytes for the length. + // We add 0x20 to the total and round down to a multiple of 0x20. + // (0x20 + 0x20 + 0x02 + 0x20) = 0x62. + str := add(mload(0x40), and(add(shl(1, length), 0x42), not(0x1f))) + // Allocate the memory. + mstore(0x40, add(str, 0x20)) + // Zeroize the slot after the string. + mstore(str, 0) + + // Cache the end to calculate the length later. + let end := str + // Store "0123456789abcdef" in scratch space. + mstore(0x0f, 0x30313233343536373839616263646566) + + let start := sub(str, add(length, length)) + let w := not(1) // Tsk. + let temp := value + // We write the string from rightmost digit to leftmost digit. + // The following is essentially a do-while loop that also handles the zero case. + for {} 1 {} { + str := add(str, w) // `sub(str, 2)`. + mstore8(add(str, 1), mload(and(temp, 15))) + mstore8(str, mload(and(shr(4, temp), 15))) + temp := shr(8, temp) + if iszero(xor(str, start)) { break } + } + + if temp { + mstore(0x00, 0x2194895a) // `HexLengthInsufficient()`. + revert(0x1c, 0x04) + } + + // Compute the string's length. + let strLength := sub(end, str) + // Move the pointer and write the length. + str := sub(str, 0x20) + mstore(str, strLength) + } + } + + /// @dev Returns the hexadecimal representation of `value`. + /// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte. + /// As address are 20 bytes long, the output will left-padded to have + /// a length of `20 * 2 + 2` bytes. + function toHexString(uint256 value) internal pure returns (string memory str) { + str = toHexStringNoPrefix(value); + /// @solidity memory-safe-assembly + assembly { + let strLength := add(mload(str), 2) // Compute the length. + mstore(str, 0x3078) // Write the "0x" prefix. + str := sub(str, 2) // Move the pointer. + mstore(str, strLength) // Write the length. + } + } + + /// @dev Returns the hexadecimal representation of `value`. + /// The output is prefixed with "0x". + /// The output excludes leading "0" from the `toHexString` output. + /// `0x00: "0x0", 0x01: "0x1", 0x12: "0x12", 0x123: "0x123"`. + function toMinimalHexString(uint256 value) internal pure returns (string memory str) { + str = toHexStringNoPrefix(value); + /// @solidity memory-safe-assembly + assembly { + let o := eq(byte(0, mload(add(str, 0x20))), 0x30) // Whether leading zero is present. + let strLength := add(mload(str), 2) // Compute the length. + mstore(add(str, o), 0x3078) // Write the "0x" prefix, accounting for leading zero. + str := sub(add(str, o), 2) // Move the pointer, accounting for leading zero. + mstore(str, sub(strLength, o)) // Write the length, accounting for leading zero. + } + } + + /// @dev Returns the hexadecimal representation of `value`. + /// The output excludes leading "0" from the `toHexStringNoPrefix` output. + /// `0x00: "0", 0x01: "1", 0x12: "12", 0x123: "123"`. + function toMinimalHexStringNoPrefix(uint256 value) internal pure returns (string memory str) { + str = toHexStringNoPrefix(value); + /// @solidity memory-safe-assembly + assembly { + let o := eq(byte(0, mload(add(str, 0x20))), 0x30) // Whether leading zero is present. + let strLength := mload(str) // Get the length. + str := add(str, o) // Move the pointer, accounting for leading zero. + mstore(str, sub(strLength, o)) // Write the length, accounting for leading zero. + } + } + + /// @dev Returns the hexadecimal representation of `value`. + /// The output is encoded using 2 hexadecimal digits per byte. + /// As address are 20 bytes long, the output will left-padded to have + /// a length of `20 * 2` bytes. + function toHexStringNoPrefix(uint256 value) internal pure returns (string memory str) { + /// @solidity memory-safe-assembly + assembly { + // We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length, + // 0x02 bytes for the prefix, and 0x40 bytes for the digits. + // The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x40) is 0xa0. + str := add(mload(0x40), 0x80) + // Allocate the memory. + mstore(0x40, add(str, 0x20)) + // Zeroize the slot after the string. + mstore(str, 0) + + // Cache the end to calculate the length later. + let end := str + // Store "0123456789abcdef" in scratch space. + mstore(0x0f, 0x30313233343536373839616263646566) + + let w := not(1) // Tsk. + // We write the string from rightmost digit to leftmost digit. + // The following is essentially a do-while loop that also handles the zero case. + for { let temp := value } 1 {} { + str := add(str, w) // `sub(str, 2)`. + mstore8(add(str, 1), mload(and(temp, 15))) + mstore8(str, mload(and(shr(4, temp), 15))) + temp := shr(8, temp) + if iszero(temp) { break } + } + + // Compute the string's length. + let strLength := sub(end, str) + // Move the pointer and write the length. + str := sub(str, 0x20) + mstore(str, strLength) + } + } + + /// @dev Returns the hexadecimal representation of `value`. + /// The output is prefixed with "0x", encoded using 2 hexadecimal digits per byte, + /// and the alphabets are capitalized conditionally according to + /// https://eips.ethereum.org/EIPS/eip-55 + function toHexStringChecksummed(address value) internal pure returns (string memory str) { + str = toHexString(value); + /// @solidity memory-safe-assembly + assembly { + let mask := shl(6, div(not(0), 255)) // `0b010000000100000000 ...` + let o := add(str, 0x22) + let hashed := and(keccak256(o, 40), mul(34, mask)) // `0b10001000 ... ` + let t := shl(240, 136) // `0b10001000 << 240` + for { let i := 0 } 1 {} { + mstore(add(i, i), mul(t, byte(i, hashed))) + i := add(i, 1) + if eq(i, 20) { break } + } + mstore(o, xor(mload(o), shr(1, and(mload(0x00), and(mload(o), mask))))) + o := add(o, 0x20) + mstore(o, xor(mload(o), shr(1, and(mload(0x20), and(mload(o), mask))))) + } + } + + /// @dev Returns the hexadecimal representation of `value`. + /// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte. + function toHexString(address value) internal pure returns (string memory str) { + str = toHexStringNoPrefix(value); + /// @solidity memory-safe-assembly + assembly { + let strLength := add(mload(str), 2) // Compute the length. + mstore(str, 0x3078) // Write the "0x" prefix. + str := sub(str, 2) // Move the pointer. + mstore(str, strLength) // Write the length. + } + } + + /// @dev Returns the hexadecimal representation of `value`. + /// The output is encoded using 2 hexadecimal digits per byte. + function toHexStringNoPrefix(address value) internal pure returns (string memory str) { + /// @solidity memory-safe-assembly + assembly { + str := mload(0x40) + + // Allocate the memory. + // We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length, + // 0x02 bytes for the prefix, and 0x28 bytes for the digits. + // The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x28) is 0x80. + mstore(0x40, add(str, 0x80)) + + // Store "0123456789abcdef" in scratch space. + mstore(0x0f, 0x30313233343536373839616263646566) + + str := add(str, 2) + mstore(str, 40) + + let o := add(str, 0x20) + mstore(add(o, 40), 0) + + value := shl(96, value) + + // We write the string from rightmost digit to leftmost digit. + // The following is essentially a do-while loop that also handles the zero case. + for { let i := 0 } 1 {} { + let p := add(o, add(i, i)) + let temp := byte(i, value) + mstore8(add(p, 1), mload(and(temp, 15))) + mstore8(p, mload(shr(4, temp))) + i := add(i, 1) + if eq(i, 20) { break } + } + } + } + + /// @dev Returns the hex encoded string from the raw bytes. + /// The output is encoded using 2 hexadecimal digits per byte. + function toHexString(bytes memory raw) internal pure returns (string memory str) { + str = toHexStringNoPrefix(raw); + /// @solidity memory-safe-assembly + assembly { + let strLength := add(mload(str), 2) // Compute the length. + mstore(str, 0x3078) // Write the "0x" prefix. + str := sub(str, 2) // Move the pointer. + mstore(str, strLength) // Write the length. + } + } + + /// @dev Returns the hex encoded string from the raw bytes. + /// The output is encoded using 2 hexadecimal digits per byte. + function toHexStringNoPrefix(bytes memory raw) internal pure returns (string memory str) { + /// @solidity memory-safe-assembly + assembly { + let length := mload(raw) + str := add(mload(0x40), 2) // Skip 2 bytes for the optional prefix. + mstore(str, add(length, length)) // Store the length of the output. + + // Store "0123456789abcdef" in scratch space. + mstore(0x0f, 0x30313233343536373839616263646566) + + let o := add(str, 0x20) + let end := add(raw, length) + + for {} iszero(eq(raw, end)) {} { + raw := add(raw, 1) + mstore8(add(o, 1), mload(and(mload(raw), 15))) + mstore8(o, mload(and(shr(4, mload(raw)), 15))) + o := add(o, 2) + } + mstore(o, 0) // Zeroize the slot after the string. + mstore(0x40, add(o, 0x20)) // Allocate the memory. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* RUNE STRING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the number of UTF characters in the string. + function runeCount(string memory s) internal pure returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + if mload(s) { + mstore(0x00, div(not(0), 255)) + mstore(0x20, 0x0202020202020202020202020202020202020202020202020303030304040506) + let o := add(s, 0x20) + let end := add(o, mload(s)) + for { result := 1 } 1 { result := add(result, 1) } { + o := add(o, byte(0, mload(shr(250, mload(o))))) + if iszero(lt(o, end)) { break } + } + } + } + } + + /// @dev Returns if this string is a 7-bit ASCII string. + /// (i.e. all characters codes are in [0..127]) + function is7BitASCII(string memory s) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + let mask := shl(7, div(not(0), 255)) + result := 1 + let n := mload(s) + if n { + let o := add(s, 0x20) + let end := add(o, n) + let last := mload(end) + mstore(end, 0) + for {} 1 {} { + if and(mask, mload(o)) { + result := 0 + break + } + o := add(o, 0x20) + if iszero(lt(o, end)) { break } + } + mstore(end, last) + } + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* BYTE STRING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // For performance and bytecode compactness, byte string operations are restricted + // to 7-bit ASCII strings. All offsets are byte offsets, not UTF character offsets. + // Usage of byte string operations on charsets with runes spanning two or more bytes + // can lead to undefined behavior. + + /// @dev Returns `subject` all occurrences of `search` replaced with `replacement`. + function replace(string memory subject, string memory search, string memory replacement) + internal + pure + returns (string memory result) + { + /// @solidity memory-safe-assembly + assembly { + let subjectLength := mload(subject) + let searchLength := mload(search) + let replacementLength := mload(replacement) + + subject := add(subject, 0x20) + search := add(search, 0x20) + replacement := add(replacement, 0x20) + result := add(mload(0x40), 0x20) + + let subjectEnd := add(subject, subjectLength) + if iszero(gt(searchLength, subjectLength)) { + let subjectSearchEnd := add(sub(subjectEnd, searchLength), 1) + let h := 0 + if iszero(lt(searchLength, 0x20)) { h := keccak256(search, searchLength) } + let m := shl(3, sub(0x20, and(searchLength, 0x1f))) + let s := mload(search) + for {} 1 {} { + let t := mload(subject) + // Whether the first `searchLength % 32` bytes of + // `subject` and `search` matches. + if iszero(shr(m, xor(t, s))) { + if h { + if iszero(eq(keccak256(subject, searchLength), h)) { + mstore(result, t) + result := add(result, 1) + subject := add(subject, 1) + if iszero(lt(subject, subjectSearchEnd)) { break } + continue + } + } + // Copy the `replacement` one word at a time. + for { let o := 0 } 1 {} { + mstore(add(result, o), mload(add(replacement, o))) + o := add(o, 0x20) + if iszero(lt(o, replacementLength)) { break } + } + result := add(result, replacementLength) + subject := add(subject, searchLength) + if searchLength { + if iszero(lt(subject, subjectSearchEnd)) { break } + continue + } + } + mstore(result, t) + result := add(result, 1) + subject := add(subject, 1) + if iszero(lt(subject, subjectSearchEnd)) { break } + } + } + + let resultRemainder := result + result := add(mload(0x40), 0x20) + let k := add(sub(resultRemainder, result), sub(subjectEnd, subject)) + // Copy the rest of the string one word at a time. + for {} lt(subject, subjectEnd) {} { + mstore(resultRemainder, mload(subject)) + resultRemainder := add(resultRemainder, 0x20) + subject := add(subject, 0x20) + } + result := sub(result, 0x20) + let last := add(add(result, 0x20), k) // Zeroize the slot after the string. + mstore(last, 0) + mstore(0x40, add(last, 0x20)) // Allocate the memory. + mstore(result, k) // Store the length. + } + } + + /// @dev Returns the byte index of the first location of `search` in `subject`, + /// searching from left to right, starting from `from`. + /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found. + function indexOf(string memory subject, string memory search, uint256 from) + internal + pure + returns (uint256 result) + { + /// @solidity memory-safe-assembly + assembly { + for { let subjectLength := mload(subject) } 1 {} { + if iszero(mload(search)) { + if iszero(gt(from, subjectLength)) { + result := from + break + } + result := subjectLength + break + } + let searchLength := mload(search) + let subjectStart := add(subject, 0x20) + + result := not(0) // Initialize to `NOT_FOUND`. + + subject := add(subjectStart, from) + let end := add(sub(add(subjectStart, subjectLength), searchLength), 1) + + let m := shl(3, sub(0x20, and(searchLength, 0x1f))) + let s := mload(add(search, 0x20)) + + if iszero(and(lt(subject, end), lt(from, subjectLength))) { break } + + if iszero(lt(searchLength, 0x20)) { + for { let h := keccak256(add(search, 0x20), searchLength) } 1 {} { + if iszero(shr(m, xor(mload(subject), s))) { + if eq(keccak256(subject, searchLength), h) { + result := sub(subject, subjectStart) + break + } + } + subject := add(subject, 1) + if iszero(lt(subject, end)) { break } + } + break + } + for {} 1 {} { + if iszero(shr(m, xor(mload(subject), s))) { + result := sub(subject, subjectStart) + break + } + subject := add(subject, 1) + if iszero(lt(subject, end)) { break } + } + break + } + } + } + + /// @dev Returns the byte index of the first location of `search` in `subject`, + /// searching from left to right. + /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found. + function indexOf(string memory subject, string memory search) + internal + pure + returns (uint256 result) + { + result = indexOf(subject, search, 0); + } + + /// @dev Returns the byte index of the first location of `search` in `subject`, + /// searching from right to left, starting from `from`. + /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found. + function lastIndexOf(string memory subject, string memory search, uint256 from) + internal + pure + returns (uint256 result) + { + /// @solidity memory-safe-assembly + assembly { + for {} 1 {} { + result := not(0) // Initialize to `NOT_FOUND`. + let searchLength := mload(search) + if gt(searchLength, mload(subject)) { break } + let w := result + + let fromMax := sub(mload(subject), searchLength) + if iszero(gt(fromMax, from)) { from := fromMax } + + let end := add(add(subject, 0x20), w) + subject := add(add(subject, 0x20), from) + if iszero(gt(subject, end)) { break } + // As this function is not too often used, + // we shall simply use keccak256 for smaller bytecode size. + for { let h := keccak256(add(search, 0x20), searchLength) } 1 {} { + if eq(keccak256(subject, searchLength), h) { + result := sub(subject, add(end, 1)) + break + } + subject := add(subject, w) // `sub(subject, 1)`. + if iszero(gt(subject, end)) { break } + } + break + } + } + } + + /// @dev Returns the byte index of the first location of `search` in `subject`, + /// searching from right to left. + /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found. + function lastIndexOf(string memory subject, string memory search) + internal + pure + returns (uint256 result) + { + result = lastIndexOf(subject, search, uint256(int256(-1))); + } + + /// @dev Returns true if `search` is found in `subject`, false otherwise. + function contains(string memory subject, string memory search) internal pure returns (bool) { + return indexOf(subject, search) != NOT_FOUND; + } + + /// @dev Returns whether `subject` starts with `search`. + function startsWith(string memory subject, string memory search) + internal + pure + returns (bool result) + { + /// @solidity memory-safe-assembly + assembly { + let searchLength := mload(search) + // Just using keccak256 directly is actually cheaper. + // forgefmt: disable-next-item + result := and( + iszero(gt(searchLength, mload(subject))), + eq( + keccak256(add(subject, 0x20), searchLength), + keccak256(add(search, 0x20), searchLength) + ) + ) + } + } + + /// @dev Returns whether `subject` ends with `search`. + function endsWith(string memory subject, string memory search) + internal + pure + returns (bool result) + { + /// @solidity memory-safe-assembly + assembly { + let searchLength := mload(search) + let subjectLength := mload(subject) + // Whether `search` is not longer than `subject`. + let withinRange := iszero(gt(searchLength, subjectLength)) + // Just using keccak256 directly is actually cheaper. + // forgefmt: disable-next-item + result := and( + withinRange, + eq( + keccak256( + // `subject + 0x20 + max(subjectLength - searchLength, 0)`. + add(add(subject, 0x20), mul(withinRange, sub(subjectLength, searchLength))), + searchLength + ), + keccak256(add(search, 0x20), searchLength) + ) + ) + } + } + + /// @dev Returns `subject` repeated `times`. + function repeat(string memory subject, uint256 times) + internal + pure + returns (string memory result) + { + /// @solidity memory-safe-assembly + assembly { + let subjectLength := mload(subject) + if iszero(or(iszero(times), iszero(subjectLength))) { + subject := add(subject, 0x20) + result := mload(0x40) + let output := add(result, 0x20) + for {} 1 {} { + // Copy the `subject` one word at a time. + for { let o := 0 } 1 {} { + mstore(add(output, o), mload(add(subject, o))) + o := add(o, 0x20) + if iszero(lt(o, subjectLength)) { break } + } + output := add(output, subjectLength) + times := sub(times, 1) + if iszero(times) { break } + } + mstore(output, 0) // Zeroize the slot after the string. + let resultLength := sub(output, add(result, 0x20)) + mstore(result, resultLength) // Store the length. + // Allocate the memory. + mstore(0x40, add(result, add(resultLength, 0x20))) + } + } + } + + /// @dev Returns a copy of `subject` sliced from `start` to `end` (exclusive). + /// `start` and `end` are byte offsets. + function slice(string memory subject, uint256 start, uint256 end) + internal + pure + returns (string memory result) + { + /// @solidity memory-safe-assembly + assembly { + let subjectLength := mload(subject) + if iszero(gt(subjectLength, end)) { end := subjectLength } + if iszero(gt(subjectLength, start)) { start := subjectLength } + if lt(start, end) { + result := mload(0x40) + let resultLength := sub(end, start) + mstore(result, resultLength) + subject := add(subject, start) + let w := not(0x1f) + // Copy the `subject` one word at a time, backwards. + for { let o := and(add(resultLength, 0x1f), w) } 1 {} { + mstore(add(result, o), mload(add(subject, o))) + o := add(o, w) // `sub(o, 0x20)`. + if iszero(o) { break } + } + // Zeroize the slot after the string. + mstore(add(add(result, 0x20), resultLength), 0) + // Allocate memory for the length and the bytes, + // rounded up to a multiple of 32. + mstore(0x40, add(result, and(add(resultLength, 0x3f), w))) + } + } + } + + /// @dev Returns a copy of `subject` sliced from `start` to the end of the string. + /// `start` is a byte offset. + function slice(string memory subject, uint256 start) + internal + pure + returns (string memory result) + { + result = slice(subject, start, uint256(int256(-1))); + } + + /// @dev Returns all the indices of `search` in `subject`. + /// The indices are byte offsets. + function indicesOf(string memory subject, string memory search) + internal + pure + returns (uint256[] memory result) + { + /// @solidity memory-safe-assembly + assembly { + let subjectLength := mload(subject) + let searchLength := mload(search) + + if iszero(gt(searchLength, subjectLength)) { + subject := add(subject, 0x20) + search := add(search, 0x20) + result := add(mload(0x40), 0x20) + + let subjectStart := subject + let subjectSearchEnd := add(sub(add(subject, subjectLength), searchLength), 1) + let h := 0 + if iszero(lt(searchLength, 0x20)) { h := keccak256(search, searchLength) } + let m := shl(3, sub(0x20, and(searchLength, 0x1f))) + let s := mload(search) + for {} 1 {} { + let t := mload(subject) + // Whether the first `searchLength % 32` bytes of + // `subject` and `search` matches. + if iszero(shr(m, xor(t, s))) { + if h { + if iszero(eq(keccak256(subject, searchLength), h)) { + subject := add(subject, 1) + if iszero(lt(subject, subjectSearchEnd)) { break } + continue + } + } + // Append to `result`. + mstore(result, sub(subject, subjectStart)) + result := add(result, 0x20) + // Advance `subject` by `searchLength`. + subject := add(subject, searchLength) + if searchLength { + if iszero(lt(subject, subjectSearchEnd)) { break } + continue + } + } + subject := add(subject, 1) + if iszero(lt(subject, subjectSearchEnd)) { break } + } + let resultEnd := result + // Assign `result` to the free memory pointer. + result := mload(0x40) + // Store the length of `result`. + mstore(result, shr(5, sub(resultEnd, add(result, 0x20)))) + // Allocate memory for result. + // We allocate one more word, so this array can be recycled for {split}. + mstore(0x40, add(resultEnd, 0x20)) + } + } + } + + /// @dev Returns a arrays of strings based on the `delimiter` inside of the `subject` string. + function split(string memory subject, string memory delimiter) + internal + pure + returns (string[] memory result) + { + uint256[] memory indices = indicesOf(subject, delimiter); + /// @solidity memory-safe-assembly + assembly { + let w := not(0x1f) + let indexPtr := add(indices, 0x20) + let indicesEnd := add(indexPtr, shl(5, add(mload(indices), 1))) + mstore(add(indicesEnd, w), mload(subject)) + mstore(indices, add(mload(indices), 1)) + let prevIndex := 0 + for {} 1 {} { + let index := mload(indexPtr) + mstore(indexPtr, 0x60) + if iszero(eq(index, prevIndex)) { + let element := mload(0x40) + let elementLength := sub(index, prevIndex) + mstore(element, elementLength) + // Copy the `subject` one word at a time, backwards. + for { let o := and(add(elementLength, 0x1f), w) } 1 {} { + mstore(add(element, o), mload(add(add(subject, prevIndex), o))) + o := add(o, w) // `sub(o, 0x20)`. + if iszero(o) { break } + } + // Zeroize the slot after the string. + mstore(add(add(element, 0x20), elementLength), 0) + // Allocate memory for the length and the bytes, + // rounded up to a multiple of 32. + mstore(0x40, add(element, and(add(elementLength, 0x3f), w))) + // Store the `element` into the array. + mstore(indexPtr, element) + } + prevIndex := add(index, mload(delimiter)) + indexPtr := add(indexPtr, 0x20) + if iszero(lt(indexPtr, indicesEnd)) { break } + } + result := indices + if iszero(mload(delimiter)) { + result := add(indices, 0x20) + mstore(result, sub(mload(indices), 2)) + } + } + } + + /// @dev Returns a concatenated string of `a` and `b`. + /// Cheaper than `string.concat()` and does not de-align the free memory pointer. + function concat(string memory a, string memory b) + internal + pure + returns (string memory result) + { + /// @solidity memory-safe-assembly + assembly { + let w := not(0x1f) + result := mload(0x40) + let aLength := mload(a) + // Copy `a` one word at a time, backwards. + for { let o := and(add(aLength, 0x20), w) } 1 {} { + mstore(add(result, o), mload(add(a, o))) + o := add(o, w) // `sub(o, 0x20)`. + if iszero(o) { break } + } + let bLength := mload(b) + let output := add(result, aLength) + // Copy `b` one word at a time, backwards. + for { let o := and(add(bLength, 0x20), w) } 1 {} { + mstore(add(output, o), mload(add(b, o))) + o := add(o, w) // `sub(o, 0x20)`. + if iszero(o) { break } + } + let totalLength := add(aLength, bLength) + let last := add(add(result, 0x20), totalLength) + // Zeroize the slot after the string. + mstore(last, 0) + // Stores the length. + mstore(result, totalLength) + // Allocate memory for the length and the bytes, + // rounded up to a multiple of 32. + mstore(0x40, and(add(last, 0x1f), w)) + } + } + + /// @dev Returns a copy of the string in either lowercase or UPPERCASE. + /// WARNING! This function is only compatible with 7-bit ASCII strings. + function toCase(string memory subject, bool toUpper) + internal + pure + returns (string memory result) + { + /// @solidity memory-safe-assembly + assembly { + let length := mload(subject) + if length { + result := add(mload(0x40), 0x20) + subject := add(subject, 1) + let flags := shl(add(70, shl(5, toUpper)), 0x3ffffff) + let w := not(0) + for { let o := length } 1 {} { + o := add(o, w) + let b := and(0xff, mload(add(subject, o))) + mstore8(add(result, o), xor(b, and(shr(b, flags), 0x20))) + if iszero(o) { break } + } + result := mload(0x40) + mstore(result, length) // Store the length. + let last := add(add(result, 0x20), length) + mstore(last, 0) // Zeroize the slot after the string. + mstore(0x40, add(last, 0x20)) // Allocate the memory. + } + } + } + + /// @dev Returns a string from a small bytes32 string. + /// `s` must be null-terminated, or behavior will be undefined. + function fromSmallString(bytes32 s) internal pure returns (string memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + let n := 0 + for {} byte(n, s) { n := add(n, 1) } {} // Scan for '\0'. + mstore(result, n) + let o := add(result, 0x20) + mstore(o, s) + mstore(add(o, n), 0) + mstore(0x40, add(result, 0x40)) + } + } + + /// @dev Returns the small string, with all bytes after the first null byte zeroized. + function normalizeSmallString(bytes32 s) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + for {} byte(result, s) { result := add(result, 1) } {} // Scan for '\0'. + mstore(0x00, s) + mstore(result, 0x00) + result := mload(0x00) + } + } + + /// @dev Returns the string as a normalized null-terminated small string. + function toSmallString(string memory s) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(s) + if iszero(lt(result, 33)) { + mstore(0x00, 0xec92f9a3) // `TooBigForSmallString()`. + revert(0x1c, 0x04) + } + result := shl(shl(3, sub(32, result)), mload(add(s, result))) + } + } + + /// @dev Returns a lowercased copy of the string. + /// WARNING! This function is only compatible with 7-bit ASCII strings. + function lower(string memory subject) internal pure returns (string memory result) { + result = toCase(subject, false); + } + + /// @dev Returns an UPPERCASED copy of the string. + /// WARNING! This function is only compatible with 7-bit ASCII strings. + function upper(string memory subject) internal pure returns (string memory result) { + result = toCase(subject, true); + } + + /// @dev Escapes the string to be used within HTML tags. + function escapeHTML(string memory s) internal pure returns (string memory result) { + /// @solidity memory-safe-assembly + assembly { + let end := add(s, mload(s)) + result := add(mload(0x40), 0x20) + // Store the bytes of the packed offsets and strides into the scratch space. + // `packed = (stride << 5) | offset`. Max offset is 20. Max stride is 6. + mstore(0x1f, 0x900094) + mstore(0x08, 0xc0000000a6ab) + // Store ""&'<>" into the scratch space. + mstore(0x00, shl(64, 0x2671756f743b26616d703b262333393b266c743b2667743b)) + for {} iszero(eq(s, end)) {} { + s := add(s, 1) + let c := and(mload(s), 0xff) + // Not in `["\"","'","&","<",">"]`. + if iszero(and(shl(c, 1), 0x500000c400000000)) { + mstore8(result, c) + result := add(result, 1) + continue + } + let t := shr(248, mload(c)) + mstore(result, mload(and(t, 0x1f))) + result := add(result, shr(5, t)) + } + let last := result + mstore(last, 0) // Zeroize the slot after the string. + result := mload(0x40) + mstore(result, sub(last, add(result, 0x20))) // Store the length. + mstore(0x40, add(last, 0x20)) // Allocate the memory. + } + } + + /// @dev Escapes the string to be used within double-quotes in a JSON. + /// If `addDoubleQuotes` is true, the result will be enclosed in double-quotes. + function escapeJSON(string memory s, bool addDoubleQuotes) + internal + pure + returns (string memory result) + { + /// @solidity memory-safe-assembly + assembly { + let end := add(s, mload(s)) + result := add(mload(0x40), 0x20) + if addDoubleQuotes { + mstore8(result, 34) + result := add(1, result) + } + // Store "\\u0000" in scratch space. + // Store "0123456789abcdef" in scratch space. + // Also, store `{0x08:"b", 0x09:"t", 0x0a:"n", 0x0c:"f", 0x0d:"r"}`. + // into the scratch space. + mstore(0x15, 0x5c75303030303031323334353637383961626364656662746e006672) + // Bitmask for detecting `["\"","\\"]`. + let e := or(shl(0x22, 1), shl(0x5c, 1)) + for {} iszero(eq(s, end)) {} { + s := add(s, 1) + let c := and(mload(s), 0xff) + if iszero(lt(c, 0x20)) { + if iszero(and(shl(c, 1), e)) { + // Not in `["\"","\\"]`. + mstore8(result, c) + result := add(result, 1) + continue + } + mstore8(result, 0x5c) // "\\". + mstore8(add(result, 1), c) + result := add(result, 2) + continue + } + if iszero(and(shl(c, 1), 0x3700)) { + // Not in `["\b","\t","\n","\f","\d"]`. + mstore8(0x1d, mload(shr(4, c))) // Hex value. + mstore8(0x1e, mload(and(c, 15))) // Hex value. + mstore(result, mload(0x19)) // "\\u00XX". + result := add(result, 6) + continue + } + mstore8(result, 0x5c) // "\\". + mstore8(add(result, 1), mload(add(c, 8))) + result := add(result, 2) + } + if addDoubleQuotes { + mstore8(result, 34) + result := add(1, result) + } + let last := result + mstore(last, 0) // Zeroize the slot after the string. + result := mload(0x40) + mstore(result, sub(last, add(result, 0x20))) // Store the length. + mstore(0x40, add(last, 0x20)) // Allocate the memory. + } + } + + /// @dev Escapes the string to be used within double-quotes in a JSON. + function escapeJSON(string memory s) internal pure returns (string memory result) { + result = escapeJSON(s, false); + } + + /// @dev Returns whether `a` equals `b`. + function eq(string memory a, string memory b) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + result := eq(keccak256(add(a, 0x20), mload(a)), keccak256(add(b, 0x20), mload(b))) + } + } + + /// @dev Returns whether `a` equals `b`, where `b` is a null-terminated small string. + function eqs(string memory a, bytes32 b) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + // These should be evaluated on compile time, as far as possible. + let m := not(shl(7, div(not(iszero(b)), 255))) // `0x7f7f ...`. + let x := not(or(m, or(b, add(m, and(b, m))))) + let r := shl(7, iszero(iszero(shr(128, x)))) + r := or(r, shl(6, iszero(iszero(shr(64, shr(r, x)))))) + r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) + r := or(r, shl(4, lt(0xffff, shr(r, x)))) + r := or(r, shl(3, lt(0xff, shr(r, x)))) + // forgefmt: disable-next-item + result := gt(eq(mload(a), add(iszero(x), xor(31, shr(3, r)))), + xor(shr(add(8, r), b), shr(add(8, r), mload(add(a, 0x20))))) + } + } + + /// @dev Packs a single string with its length into a single word. + /// Returns `bytes32(0)` if the length is zero or greater than 31. + function packOne(string memory a) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + // We don't need to zero right pad the string, + // since this is our own custom non-standard packing scheme. + result := + mul( + // Load the length and the bytes. + mload(add(a, 0x1f)), + // `length != 0 && length < 32`. Abuses underflow. + // Assumes that the length is valid and within the block gas limit. + lt(sub(mload(a), 1), 0x1f) + ) + } + } + + /// @dev Unpacks a string packed using {packOne}. + /// Returns the empty string if `packed` is `bytes32(0)`. + /// If `packed` is not an output of {packOne}, the output behavior is undefined. + function unpackOne(bytes32 packed) internal pure returns (string memory result) { + /// @solidity memory-safe-assembly + assembly { + // Grab the free memory pointer. + result := mload(0x40) + // Allocate 2 words (1 for the length, 1 for the bytes). + mstore(0x40, add(result, 0x40)) + // Zeroize the length slot. + mstore(result, 0) + // Store the length and bytes. + mstore(add(result, 0x1f), packed) + // Right pad with zeroes. + mstore(add(add(result, 0x20), mload(result)), 0) + } + } + + /// @dev Packs two strings with their lengths into a single word. + /// Returns `bytes32(0)` if combined length is zero or greater than 30. + function packTwo(string memory a, string memory b) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let aLength := mload(a) + // We don't need to zero right pad the strings, + // since this is our own custom non-standard packing scheme. + result := + mul( + // Load the length and the bytes of `a` and `b`. + or( + shl(shl(3, sub(0x1f, aLength)), mload(add(a, aLength))), + mload(sub(add(b, 0x1e), aLength)) + ), + // `totalLength != 0 && totalLength < 31`. Abuses underflow. + // Assumes that the lengths are valid and within the block gas limit. + lt(sub(add(aLength, mload(b)), 1), 0x1e) + ) + } + } + + /// @dev Unpacks strings packed using {packTwo}. + /// Returns the empty strings if `packed` is `bytes32(0)`. + /// If `packed` is not an output of {packTwo}, the output behavior is undefined. + function unpackTwo(bytes32 packed) + internal + pure + returns (string memory resultA, string memory resultB) + { + /// @solidity memory-safe-assembly + assembly { + // Grab the free memory pointer. + resultA := mload(0x40) + resultB := add(resultA, 0x40) + // Allocate 2 words for each string (1 for the length, 1 for the byte). Total 4 words. + mstore(0x40, add(resultB, 0x40)) + // Zeroize the length slots. + mstore(resultA, 0) + mstore(resultB, 0) + // Store the lengths and bytes. + mstore(add(resultA, 0x1f), packed) + mstore(add(resultB, 0x1f), mload(add(add(resultA, 0x20), mload(resultA)))) + // Right pad with zeroes. + mstore(add(add(resultA, 0x20), mload(resultA)), 0) + mstore(add(add(resultB, 0x20), mload(resultB)), 0) + } + } + + /// @dev Directly returns `a` without copying. + function directReturn(string memory a) internal pure { + assembly { + // Assumes that the string does not start from the scratch space. + let retStart := sub(a, 0x20) + let retSize := add(mload(a), 0x40) + // Right pad with zeroes. Just in case the string is produced + // by a method that doesn't zero right pad. + mstore(add(retStart, retSize), 0) + // Store the return offset. + mstore(retStart, 0x20) + // End the transaction, returning the string. + return(retStart, retSize) + } + } +} + +library GasPayingToken { + /// @notice The storage slot that contains the address and decimals of the gas paying token + bytes32 internal constant GAS_PAYING_TOKEN_SLOT = bytes32(uint256(keccak256("opstack.gaspayingtoken")) - 1); + + /// @notice The storage slot that contains the ERC20 `name()` of the gas paying token + bytes32 internal constant GAS_PAYING_TOKEN_NAME_SLOT = bytes32(uint256(keccak256("opstack.gaspayingtokenname")) - 1); + + /// @notice the storage slot that contains the ERC20 `symbol()` of the gas paying token + bytes32 internal constant GAS_PAYING_TOKEN_SYMBOL_SLOT = + bytes32(uint256(keccak256("opstack.gaspayingtokensymbol")) - 1); + + /// @notice Reads the gas paying token and its decimals from the magic + /// storage slot. If nothing is set in storage, then the ether + /// address is returned instead. + function getToken() internal view returns (address addr_, uint8 decimals_) { + bytes32 slot = Storage.getBytes32(GAS_PAYING_TOKEN_SLOT); + addr_ = address(uint160(uint256(slot) & uint256(type(uint160).max))); + if (addr_ == address(0)) { + addr_ = Constants.FACET_COMPUTE_TOKEN; + decimals_ = 18; + } else { + decimals_ = uint8(uint256(slot) >> 160); + } + } + + /// @notice Reads the gas paying token's name from the magic storage slot. + /// If nothing is set in storage, then the ether name, 'Ether', is returned instead. + function getName() internal view returns (string memory name_) { + (address addr,) = getToken(); + if (addr == Constants.FACET_COMPUTE_TOKEN) { + name_ = "Facet Compute Token"; + } else { + name_ = LibString.fromSmallString(Storage.getBytes32(GAS_PAYING_TOKEN_NAME_SLOT)); + } + } + + /// @notice Reads the gas paying token's symbol from the magic storage slot. + /// If nothing is set in storage, then the ether symbol, 'ETH', is returned instead. + function getSymbol() internal view returns (string memory symbol_) { + (address addr,) = getToken(); + if (addr == Constants.FACET_COMPUTE_TOKEN) { + symbol_ = "FCT"; + } else { + symbol_ = LibString.fromSmallString(Storage.getBytes32(GAS_PAYING_TOKEN_SYMBOL_SLOT)); + } + } + + /// @notice Writes the gas paying token, its decimals, name and symbol to the magic storage slot. + function set(address _token, uint8 _decimals, bytes32 _name, bytes32 _symbol) internal { + Storage.setBytes32(GAS_PAYING_TOKEN_SLOT, bytes32(uint256(_decimals) << 160 | uint256(uint160(_token)))); + Storage.setBytes32(GAS_PAYING_TOKEN_NAME_SLOT, _name); + Storage.setBytes32(GAS_PAYING_TOKEN_SYMBOL_SLOT, _symbol); + } + + /// @notice Maps a string to a normalized null-terminated small string. + function sanitize(string memory _str) internal pure returns (bytes32) { + require(bytes(_str).length <= 32, "GasPayingToken: string cannot be greater than 32 bytes"); + + return LibString.toSmallString(_str); + } +} + +library Storage { + /// @notice Returns an address stored in an arbitrary storage slot. + /// These storage slots decouple the storage layout from + /// solc's automation. + /// @param _slot The storage slot to retrieve the address from. + function getAddress(bytes32 _slot) internal view returns (address addr_) { + assembly { + addr_ := sload(_slot) + } + } + + /// @notice Stores an address in an arbitrary storage slot, `_slot`. + /// @param _slot The storage slot to store the address in. + /// @param _address The protocol version to store + /// @dev WARNING! This function must be used cautiously, as it allows for overwriting addresses + /// in arbitrary storage slots. + function setAddress(bytes32 _slot, address _address) internal { + assembly { + sstore(_slot, _address) + } + } + + /// @notice Returns a uint256 stored in an arbitrary storage slot. + /// These storage slots decouple the storage layout from + /// solc's automation. + /// @param _slot The storage slot to retrieve the address from. + function getUint(bytes32 _slot) internal view returns (uint256 value_) { + assembly { + value_ := sload(_slot) + } + } + + /// @notice Stores a value in an arbitrary storage slot, `_slot`. + /// @param _slot The storage slot to store the address in. + /// @param _value The protocol version to store + /// @dev WARNING! This function must be used cautiously, as it allows for overwriting values + /// in arbitrary storage slots. + function setUint(bytes32 _slot, uint256 _value) internal { + assembly { + sstore(_slot, _value) + } + } + + /// @notice Returns a bytes32 stored in an arbitrary storage slot. + /// These storage slots decouple the storage layout from + /// solc's automation. + /// @param _slot The storage slot to retrieve the address from. + function getBytes32(bytes32 _slot) internal view returns (bytes32 value_) { + assembly { + value_ := sload(_slot) + } + } + + /// @notice Stores a bytes32 value in an arbitrary storage slot, `_slot`. + /// @param _slot The storage slot to store the address in. + /// @param _value The bytes32 value to store. + /// @dev WARNING! This function must be used cautiously, as it allows for overwriting values + /// in arbitrary storage slots. + function setBytes32(bytes32 _slot, bytes32 _value) internal { + assembly { + sstore(_slot, _value) + } + } + + /// @notice Stores a bool value in an arbitrary storage slot, `_slot`. + /// @param _slot The storage slot to store the bool in. + /// @param _value The bool value to store + /// @dev WARNING! This function must be used cautiously, as it allows for overwriting values + /// in arbitrary storage slots. + function setBool(bytes32 _slot, bool _value) internal { + assembly { + sstore(_slot, _value) + } + } + + /// @notice Returns a bool stored in an arbitrary storage slot. + /// @param _slot The storage slot to retrieve the bool from. + function getBool(bytes32 _slot) internal view returns (bool value_) { + assembly { + value_ := sload(_slot) + } + } +} + +interface IGasToken { + /// @notice Getter for the ERC20 token address that is used to pay for gas and its decimals. + function gasPayingToken() external view returns (address, uint8); + /// @notice Returns the gas token name. + function gasPayingTokenName() external view returns (string memory); + /// @notice Returns the gas token symbol. + function gasPayingTokenSymbol() external view returns (string memory); + /// @notice Returns true if the network uses a custom gas token. + function isCustomGasToken() external view returns (bool); +} + +interface ISemver { + /// @notice Getter for the semantic version of the contract. This is not + /// meant to be used onchain but instead meant to be used by offchain + /// tooling. + /// @return Semver contract version as a string. + function version() external view returns (string memory); +} + +library AddressUpgradeable { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + * + * [IMPORTANT] + * ==== + * You shouldn't rely on `isContract` to protect against flash loan attacks! + * + * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets + * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract + * constructor. + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize/address.code.length, which returns 0 + // for contracts in construction, since the code is only stored at the end + // of the constructor execution. + + return account.code.length > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the + * revert reason using the provided one. + * + * _Available since v4.3._ + */ + function verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) internal pure returns (bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + +abstract contract Initializable { + /** + * @dev Indicates that the contract has been initialized. + * @custom:oz-retyped-from bool + */ + uint8 private _initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private _initializing; + + /** + * @dev Triggered when the contract has been initialized or reinitialized. + */ + event Initialized(uint8 version); + + /** + * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, + * `onlyInitializing` functions can be used to initialize parent contracts. Equivalent to `reinitializer(1)`. + */ + modifier initializer() { + bool isTopLevelCall = !_initializing; + require( + (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1), + "Initializable: contract is already initialized" + ); + _initialized = 1; + if (isTopLevelCall) { + _initializing = true; + } + _; + if (isTopLevelCall) { + _initializing = false; + emit Initialized(1); + } + } + + /** + * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the + * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be + * used to initialize parent contracts. + * + * `initializer` is equivalent to `reinitializer(1)`, so a reinitializer may be used after the original + * initialization step. This is essential to configure modules that are added through upgrades and that require + * initialization. + * + * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in + * a contract, executing them in the right order is up to the developer or operator. + */ + modifier reinitializer(uint8 version) { + require(!_initializing && _initialized < version, "Initializable: contract is already initialized"); + _initialized = version; + _initializing = true; + _; + _initializing = false; + emit Initialized(version); + } + + /** + * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the + * {initializer} and {reinitializer} modifiers, directly or indirectly. + */ + modifier onlyInitializing() { + require(_initializing, "Initializable: contract is not initializing"); + _; + } + + /** + * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. + * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized + * to any version. It is recommended to use this to lock implementation contracts that are designed to be called + * through proxies. + */ + function _disableInitializers() internal virtual { + require(!_initializing, "Initializable: contract is initializing"); + if (_initialized < type(uint8).max) { + _initialized = type(uint8).max; + emit Initialized(type(uint8).max); + } + } +} + +abstract contract ContextUpgradeable is Initializable { + function __Context_init() internal onlyInitializing { + } + + function __Context_init_unchained() internal onlyInitializing { + } + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + uint256[50] private __gap; +} + +abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + function __Ownable_init() internal onlyInitializing { + __Ownable_init_unchained(); + } + + function __Ownable_init_unchained() internal onlyInitializing { + _transferOwnership(_msgSender()); + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + _checkOwner(); + _; + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if the sender is not the owner. + */ + function _checkOwner() internal view virtual { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _transferOwnership(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + _transferOwnership(newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Internal function without access restriction. + */ + function _transferOwnership(address newOwner) internal virtual { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + uint256[49] private __gap; +} + +contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { + /// @notice Enum representing different types of updates. + /// @custom:value BATCHER Represents an update to the batcher hash. + /// @custom:value GAS_CONFIG Represents an update to txn fee config on L2. + /// @custom:value GAS_LIMIT Represents an update to gas limit on L2. + /// @custom:value UNSAFE_BLOCK_SIGNER Represents an update to the signer key for unsafe + /// block distrubution. + enum UpdateType { + BATCHER, + GAS_CONFIG, + GAS_LIMIT, + UNSAFE_BLOCK_SIGNER + } + + /// @notice Struct representing the addresses of L1 system contracts. These should be the + /// contracts that users interact with (not implementations for proxied contracts) + /// and are network specific. + struct Addresses { + address l1CrossDomainMessenger; + address l1ERC721Bridge; + address l1StandardBridge; + address disputeGameFactory; + address optimismPortal; + address optimismMintableERC20Factory; + address gasPayingToken; + } + + /// @notice Version identifier, used for upgrades. + uint256 public constant VERSION = 0; + + /// @notice Storage slot that the unsafe block signer is stored at. + /// Storing it at this deterministic storage slot allows for decoupling the storage + /// layout from the way that `solc` lays out storage. The `op-node` uses a storage + /// proof to fetch this value. + /// @dev NOTE: this value will be migrated to another storage slot in a future version. + /// User input should not be placed in storage in this contract until this migration + /// happens. It is unlikely that keccak second preimage resistance will be broken, + /// but it is better to be safe than sorry. + bytes32 public constant UNSAFE_BLOCK_SIGNER_SLOT = keccak256("systemconfig.unsafeblocksigner"); + + /// @notice Storage slot that the L1CrossDomainMessenger address is stored at. + bytes32 public constant L1_CROSS_DOMAIN_MESSENGER_SLOT = + bytes32(uint256(keccak256("systemconfig.l1crossdomainmessenger")) - 1); + + /// @notice Storage slot that the L1ERC721Bridge address is stored at. + bytes32 public constant L1_ERC_721_BRIDGE_SLOT = bytes32(uint256(keccak256("systemconfig.l1erc721bridge")) - 1); + + /// @notice Storage slot that the L1StandardBridge address is stored at. + bytes32 public constant L1_STANDARD_BRIDGE_SLOT = bytes32(uint256(keccak256("systemconfig.l1standardbridge")) - 1); + + /// @notice Storage slot that the OptimismPortal address is stored at. + bytes32 public constant OPTIMISM_PORTAL_SLOT = bytes32(uint256(keccak256("systemconfig.optimismportal")) - 1); + + /// @notice Storage slot that the OptimismMintableERC20Factory address is stored at. + bytes32 public constant OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT = + bytes32(uint256(keccak256("systemconfig.optimismmintableerc20factory")) - 1); + + /// @notice Storage slot that the batch inbox address is stored at. + bytes32 public constant BATCH_INBOX_SLOT = bytes32(uint256(keccak256("systemconfig.batchinbox")) - 1); + + /// @notice Storage slot for block at which the op-node can start searching for logs from. + bytes32 public constant START_BLOCK_SLOT = bytes32(uint256(keccak256("systemconfig.startBlock")) - 1); + + /// @notice Storage slot for the DisputeGameFactory address. + bytes32 public constant DISPUTE_GAME_FACTORY_SLOT = + bytes32(uint256(keccak256("systemconfig.disputegamefactory")) - 1); + + /// @notice The number of decimals that the gas paying token has. + uint8 internal constant GAS_PAYING_TOKEN_DECIMALS = 18; + + /// @notice The maximum gas limit that can be set for L2 blocks. This limit is used to enforce that the blocks + /// on L2 are not too large to process and prove. Over time, this value can be increased as various + /// optimizations and improvements are made to the system at large. + uint64 internal constant MAX_GAS_LIMIT = 200_000_000; + + /// @notice Fixed L2 gas overhead. Used as part of the L2 fee calculation. + /// Deprecated since the Ecotone network upgrade + uint256 public overhead; + + /// @notice Dynamic L2 gas overhead. Used as part of the L2 fee calculation. + /// The most significant byte is used to determine the version since the + /// Ecotone network upgrade. + uint256 public scalar; + + /// @notice Identifier for the batcher. + /// For version 1 of this configuration, this is represented as an address left-padded + /// with zeros to 32 bytes. + bytes32 public batcherHash; + + /// @notice L2 block gas limit. + uint64 public gasLimit; + + /// @notice Basefee scalar value. Part of the L2 fee calculation since the Ecotone network upgrade. + uint32 public basefeeScalar; + + /// @notice Blobbasefee scalar value. Part of the L2 fee calculation since the Ecotone network upgrade. + uint32 public blobbasefeeScalar; + + /// @notice The configuration for the deposit fee market. + /// Used by the OptimismPortal to meter the cost of buying L2 gas on L1. + /// Set as internal with a getter so that the struct is returned instead of a tuple. + ResourceMetering.ResourceConfig internal _resourceConfig; + + /// @notice Emitted when configuration is updated. + /// @param version SystemConfig version. + /// @param updateType Type of update. + /// @param data Encoded update data. + event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data); + + /// @notice Semantic version. + /// @custom:semver 2.3.0-beta.2 + function version() public pure virtual returns (string memory) { + return "2.3.0-beta.2"; + } + + /// @notice Constructs the SystemConfig contract. Cannot set + /// the owner to `address(0)` due to the Ownable contract's + /// implementation, so set it to `address(0xdEaD)` + /// @dev START_BLOCK_SLOT is set to type(uint256).max here so that it will be a dead value + /// in the singleton and is skipped by initialize when setting the start block. + constructor() { + Storage.setUint(START_BLOCK_SLOT, type(uint256).max); + initialize({ + _owner: address(0xdEaD), + _basefeeScalar: 0, + _blobbasefeeScalar: 0, + _batcherHash: bytes32(0), + _gasLimit: 1, + _unsafeBlockSigner: address(0), + _config: ResourceMetering.ResourceConfig({ + maxResourceLimit: 1, + elasticityMultiplier: 1, + baseFeeMaxChangeDenominator: 2, + minimumBaseFee: 0, + systemTxMaxGas: 0, + maximumBaseFee: 0 + }), + _batchInbox: address(0), + _addresses: SystemConfig.Addresses({ + l1CrossDomainMessenger: address(0), + l1ERC721Bridge: address(0), + l1StandardBridge: address(0), + disputeGameFactory: address(0), + optimismPortal: address(0), + optimismMintableERC20Factory: address(0), + gasPayingToken: address(0) + }) + }); + } + + /// @notice Initializer. + /// The resource config must be set before the require check. + /// @param _owner Initial owner of the contract. + /// @param _basefeeScalar Initial basefee scalar value. + /// @param _blobbasefeeScalar Initial blobbasefee scalar value. + /// @param _batcherHash Initial batcher hash. + /// @param _gasLimit Initial gas limit. + /// @param _unsafeBlockSigner Initial unsafe block signer address. + /// @param _config Initial ResourceConfig. + /// @param _batchInbox Batch inbox address. An identifier for the op-node to find + /// canonical data. + /// @param _addresses Set of L1 contract addresses. These should be the proxies. + function initialize( + address _owner, + uint32 _basefeeScalar, + uint32 _blobbasefeeScalar, + bytes32 _batcherHash, + uint64 _gasLimit, + address _unsafeBlockSigner, + ResourceMetering.ResourceConfig memory _config, + address _batchInbox, + SystemConfig.Addresses memory _addresses + ) + public + initializer + { + __Ownable_init(); + transferOwnership(_owner); + + // These are set in ascending order of their UpdateTypes. + _setBatcherHash(_batcherHash); + _setGasConfigEcotone({ _basefeeScalar: _basefeeScalar, _blobbasefeeScalar: _blobbasefeeScalar }); + _setGasLimit(_gasLimit); + + Storage.setAddress(UNSAFE_BLOCK_SIGNER_SLOT, _unsafeBlockSigner); + Storage.setAddress(BATCH_INBOX_SLOT, _batchInbox); + Storage.setAddress(L1_CROSS_DOMAIN_MESSENGER_SLOT, _addresses.l1CrossDomainMessenger); + Storage.setAddress(L1_ERC_721_BRIDGE_SLOT, _addresses.l1ERC721Bridge); + Storage.setAddress(L1_STANDARD_BRIDGE_SLOT, _addresses.l1StandardBridge); + Storage.setAddress(DISPUTE_GAME_FACTORY_SLOT, _addresses.disputeGameFactory); + Storage.setAddress(OPTIMISM_PORTAL_SLOT, _addresses.optimismPortal); + Storage.setAddress(OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT, _addresses.optimismMintableERC20Factory); + + _setStartBlock(); + _setGasPayingToken(_addresses.gasPayingToken); + + _setResourceConfig(_config); + require(_gasLimit >= minimumGasLimit(), "SystemConfig: gas limit too low"); + } + + /// @notice Returns the minimum L2 gas limit that can be safely set for the system to + /// operate. The L2 gas limit must be larger than or equal to the amount of + /// gas that is allocated for deposits per block plus the amount of gas that + /// is allocated for the system transaction. + /// This function is used to determine if changes to parameters are safe. + /// @return uint64 Minimum gas limit. + function minimumGasLimit() public view returns (uint64) { + return uint64(_resourceConfig.maxResourceLimit) + uint64(_resourceConfig.systemTxMaxGas); + } + + /// @notice Returns the maximum L2 gas limit that can be safely set for the system to + /// operate. This bound is used to prevent the gas limit from being set too high + /// and causing the system to be unable to process and/or prove L2 blocks. + /// @return uint64 Maximum gas limit. + function maximumGasLimit() public pure returns (uint64) { + return MAX_GAS_LIMIT; + } + + /// @notice High level getter for the unsafe block signer address. + /// Unsafe blocks can be propagated across the p2p network if they are signed by the + /// key corresponding to this address. + /// @return addr_ Address of the unsafe block signer. + function unsafeBlockSigner() public view returns (address addr_) { + addr_ = Storage.getAddress(UNSAFE_BLOCK_SIGNER_SLOT); + } + + /// @notice Getter for the L1CrossDomainMessenger address. + function l1CrossDomainMessenger() external view returns (address addr_) { + addr_ = Storage.getAddress(L1_CROSS_DOMAIN_MESSENGER_SLOT); + } + + /// @notice Getter for the L1ERC721Bridge address. + function l1ERC721Bridge() external view returns (address addr_) { + addr_ = Storage.getAddress(L1_ERC_721_BRIDGE_SLOT); + } + + /// @notice Getter for the L1StandardBridge address. + function l1StandardBridge() external view returns (address addr_) { + addr_ = Storage.getAddress(L1_STANDARD_BRIDGE_SLOT); + } + + /// @notice Getter for the DisputeGameFactory address. + function disputeGameFactory() external view returns (address addr_) { + addr_ = Storage.getAddress(DISPUTE_GAME_FACTORY_SLOT); + } + + /// @notice Getter for the OptimismPortal address. + function optimismPortal() public view returns (address addr_) { + addr_ = Storage.getAddress(OPTIMISM_PORTAL_SLOT); + } + + /// @notice Getter for the OptimismMintableERC20Factory address. + function optimismMintableERC20Factory() external view returns (address addr_) { + addr_ = Storage.getAddress(OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT); + } + + /// @notice Getter for the BatchInbox address. + function batchInbox() external view returns (address addr_) { + addr_ = Storage.getAddress(BATCH_INBOX_SLOT); + } + + /// @notice Getter for the StartBlock number. + function startBlock() external view returns (uint256 startBlock_) { + startBlock_ = Storage.getUint(START_BLOCK_SLOT); + } + + /// @notice Getter for the gas paying asset address. + function gasPayingToken() public view returns (address addr_, uint8 decimals_) { + (addr_, decimals_) = GasPayingToken.getToken(); + } + + /// @notice Getter for custom gas token paying networks. Returns true if the + /// network uses a custom gas token. + function isCustomGasToken() public view returns (bool) { + (address token,) = gasPayingToken(); + return token != Constants.ETHER; + } + + /// @notice Getter for the gas paying token name. + function gasPayingTokenName() external view returns (string memory name_) { + name_ = GasPayingToken.getName(); + } + + /// @notice Getter for the gas paying token symbol. + function gasPayingTokenSymbol() external view returns (string memory symbol_) { + symbol_ = GasPayingToken.getSymbol(); + } + + /// @notice Internal setter for the gas paying token address, includes validation. + /// The token must not already be set and must be non zero and not the ether address + /// to set the token address. This prevents the token address from being changed + /// and makes it explicitly opt-in to use custom gas token. + /// @param _token Address of the gas paying token. + function _setGasPayingToken(address _token) internal virtual { + if (_token != address(0) && _token != Constants.ETHER && !isCustomGasToken()) { + require( + ERC20(_token).decimals() == GAS_PAYING_TOKEN_DECIMALS, "SystemConfig: bad decimals of gas paying token" + ); + bytes32 name = GasPayingToken.sanitize(ERC20(_token).name()); + bytes32 symbol = GasPayingToken.sanitize(ERC20(_token).symbol()); + + // Set the gas paying token in storage and in the OptimismPortal. + GasPayingToken.set({ _token: _token, _decimals: GAS_PAYING_TOKEN_DECIMALS, _name: name, _symbol: symbol }); + OptimismPortal(payable(optimismPortal())).setGasPayingToken({ + _token: _token, + _decimals: GAS_PAYING_TOKEN_DECIMALS, + _name: name, + _symbol: symbol + }); + } + } + + /// @notice Updates the unsafe block signer address. Can only be called by the owner. + /// @param _unsafeBlockSigner New unsafe block signer address. + function setUnsafeBlockSigner(address _unsafeBlockSigner) external onlyOwner { + _setUnsafeBlockSigner(_unsafeBlockSigner); + } + + /// @notice Updates the unsafe block signer address. + /// @param _unsafeBlockSigner New unsafe block signer address. + function _setUnsafeBlockSigner(address _unsafeBlockSigner) internal { + Storage.setAddress(UNSAFE_BLOCK_SIGNER_SLOT, _unsafeBlockSigner); + + bytes memory data = abi.encode(_unsafeBlockSigner); + emit ConfigUpdate(VERSION, UpdateType.UNSAFE_BLOCK_SIGNER, data); + } + + /// @notice Updates the batcher hash. Can only be called by the owner. + /// @param _batcherHash New batcher hash. + function setBatcherHash(bytes32 _batcherHash) external onlyOwner { + _setBatcherHash(_batcherHash); + } + + /// @notice Internal function for updating the batcher hash. + /// @param _batcherHash New batcher hash. + function _setBatcherHash(bytes32 _batcherHash) internal { + batcherHash = _batcherHash; + + bytes memory data = abi.encode(_batcherHash); + emit ConfigUpdate(VERSION, UpdateType.BATCHER, data); + } + + /// @notice Updates gas config. Can only be called by the owner. + /// Deprecated in favor of setGasConfigEcotone since the Ecotone upgrade. + /// @param _overhead New overhead value. + /// @param _scalar New scalar value. + function setGasConfig(uint256 _overhead, uint256 _scalar) external onlyOwner { + _setGasConfig(_overhead, _scalar); + } + + /// @notice Internal function for updating the gas config. + /// @param _overhead New overhead value. + /// @param _scalar New scalar value. + function _setGasConfig(uint256 _overhead, uint256 _scalar) internal { + require((uint256(0xff) << 248) & _scalar == 0, "SystemConfig: scalar exceeds max."); + + overhead = _overhead; + scalar = _scalar; + + bytes memory data = abi.encode(_overhead, _scalar); + emit ConfigUpdate(VERSION, UpdateType.GAS_CONFIG, data); + } + + /// @notice Updates gas config as of the Ecotone upgrade. Can only be called by the owner. + /// @param _basefeeScalar New basefeeScalar value. + /// @param _blobbasefeeScalar New blobbasefeeScalar value. + function setGasConfigEcotone(uint32 _basefeeScalar, uint32 _blobbasefeeScalar) external onlyOwner { + _setGasConfigEcotone(_basefeeScalar, _blobbasefeeScalar); + } + + /// @notice Internal function for updating the fee scalars as of the Ecotone upgrade. + /// @param _basefeeScalar New basefeeScalar value. + /// @param _blobbasefeeScalar New blobbasefeeScalar value. + function _setGasConfigEcotone(uint32 _basefeeScalar, uint32 _blobbasefeeScalar) internal { + basefeeScalar = _basefeeScalar; + blobbasefeeScalar = _blobbasefeeScalar; + + scalar = (uint256(0x01) << 248) | (uint256(_blobbasefeeScalar) << 32) | _basefeeScalar; + + bytes memory data = abi.encode(overhead, scalar); + emit ConfigUpdate(VERSION, UpdateType.GAS_CONFIG, data); + } + + /// @notice Updates the L2 gas limit. Can only be called by the owner. + /// @param _gasLimit New gas limit. + function setGasLimit(uint64 _gasLimit) external onlyOwner { + _setGasLimit(_gasLimit); + } + + /// @notice Internal function for updating the L2 gas limit. + /// @param _gasLimit New gas limit. + function _setGasLimit(uint64 _gasLimit) internal { + require(_gasLimit >= minimumGasLimit(), "SystemConfig: gas limit too low"); + require(_gasLimit <= maximumGasLimit(), "SystemConfig: gas limit too high"); + gasLimit = _gasLimit; + + bytes memory data = abi.encode(_gasLimit); + emit ConfigUpdate(VERSION, UpdateType.GAS_LIMIT, data); + } + + /// @notice Sets the start block in a backwards compatible way. Proxies + /// that were initialized before the startBlock existed in storage + /// can have their start block set by a user provided override. + /// A start block of 0 indicates that there is no override and the + /// start block will be set by `block.number`. + /// @dev This logic is used to patch legacy deployments with new storage values. + /// Use the override if it is provided as a non zero value and the value + /// has not already been set in storage. Use `block.number` if the value + /// has already been set in storage + function _setStartBlock() internal { + if (Storage.getUint(START_BLOCK_SLOT) == 0) { + Storage.setUint(START_BLOCK_SLOT, block.number); + } + } + + /// @notice A getter for the resource config. + /// Ensures that the struct is returned instead of a tuple. + /// @return ResourceConfig + function resourceConfig() external view returns (ResourceMetering.ResourceConfig memory) { + return _resourceConfig; + } + + /// @notice An internal setter for the resource config. + /// Ensures that the config is sane before storing it by checking for invariants. + /// In the future, this method may emit an event that the `op-node` picks up + /// for when the resource config is changed. + /// @param _config The new resource config. + function _setResourceConfig(ResourceMetering.ResourceConfig memory _config) internal { + // Min base fee must be less than or equal to max base fee. + require( + _config.minimumBaseFee <= _config.maximumBaseFee, "SystemConfig: min base fee must be less than max base" + ); + // Base fee change denominator must be greater than 1. + require(_config.baseFeeMaxChangeDenominator > 1, "SystemConfig: denominator must be larger than 1"); + // Max resource limit plus system tx gas must be less than or equal to the L2 gas limit. + // The gas limit must be increased before these values can be increased. + require(_config.maxResourceLimit + _config.systemTxMaxGas <= gasLimit, "SystemConfig: gas limit too low"); + // Elasticity multiplier must be greater than 0. + require(_config.elasticityMultiplier > 0, "SystemConfig: elasticity multiplier cannot be 0"); + // No precision loss when computing target resource limit. + require( + ((_config.maxResourceLimit / _config.elasticityMultiplier) * _config.elasticityMultiplier) + == _config.maxResourceLimit, + "SystemConfig: precision loss with target resource limit" + ); + + _resourceConfig = _config; + } +} \ No newline at end of file diff --git a/packages/backend/discovery/_templates/opstack/SystemConfig_facet/template.jsonc b/packages/backend/discovery/_templates/opstack/SystemConfig_facet/template.jsonc new file mode 100644 index 00000000000..b626fa0c295 --- /dev/null +++ b/packages/backend/discovery/_templates/opstack/SystemConfig_facet/template.jsonc @@ -0,0 +1,34 @@ +{ + "$schema": "../../../../../discovery/schemas/contract.v2.schema.json", + "displayName": "SystemConfig_facet", + "ignoreInWatchMode": ["scalar", "overhead"], + "ignoreRelatives": ["gasPayingToken"], + "fields": { + "sequencerInbox": { + "handler": { + "type": "hardcoded", + "value": "0x00000000000000000000000000000000000FacE7" + } + }, + "sequencerAddress": { + "handler": { + "type": "hardcoded", + "value": "0x0000000000000000000000000000000000000000" + } + }, + "gasLimit": { + "description": "Gas limit for blocks on L2.", + "severity": "LOW" + }, + "owner": { + "target": { + "permissions": [ + { + "type": "configure", + "description": "it can update the preconfer address, the batch submitter (Sequencer) address and the gas configuration of the system." + } + ] + } + } + } +} diff --git a/packages/backend/discovery/facet/ethereum/config.jsonc b/packages/backend/discovery/facet/ethereum/config.jsonc new file mode 100644 index 00000000000..e6dd8a3dd17 --- /dev/null +++ b/packages/backend/discovery/facet/ethereum/config.jsonc @@ -0,0 +1,59 @@ +{ + "$schema": "../../../../discovery/schemas/config.v2.schema.json", + "name": "facet", + "chain": "ethereum", + "initialAddresses": [ + "0x8F75466D69a52EF53C7363F38834bEfC027A2909", // L1StandardBridge, + "0x0000000000000b07ed001607f5263d85bf28ce4c" // OrbiterStyleBridge + ], + "names": { + "0xC1E935F25f9c1198200ec442c6F02f1A2F04534e": "SystemConfig", + "0x0000000000000b07ED001607f5263D85bf28Ce4C": "FacetEtherBridgeV6", + "0xb2B01DeCb6cd36E7396b78D3744482627F22C525": "FacetMultisig", + "0x8F75466D69a52EF53C7363F38834bEfC027A2909": "L1StandardBridge" + }, + "ignoreDiscovery": "0xaCde2ce9a9Bc89ED083FaA80685E2bA2c9ec72E9", // idk why it get discovered, should be ignored + "overrides": { + "FacetEtherBridgeV6": { + "extends": "facet/FacetEtherBridge" + }, + "SystemConfig": { + "ignoreInWatchMode": ["scalar", "overhead"], + "ignoreRelatives": ["gasPayingToken"], + "fields": { + "sequencerInbox": { + "handler": { + "type": "hardcoded", + "value": "0x00000000000000000000000000000000000FacE7" + } + }, + "sequencerAddress": { + "handler": { + "type": "hardcoded", + "value": "0x0000000000000000000000000000000000000000" + } + }, + "batcherHash": { + "handler": { + "type": "hardcoded", + "value": "0x0000000000000000000000000000000000000000" + } + }, + "gasLimit": { + "description": "Gas limit for blocks on L2.", + "severity": "LOW" + }, + "owner": { + "target": { + "permissions": [ + { + "type": "configure", + "description": "it can update the preconfer address, the batch submitter (Sequencer) address and the gas configuration of the system." + } + ] + } + } + } + } + } +} diff --git a/packages/backend/discovery/facet/ethereum/diffHistory.md b/packages/backend/discovery/facet/ethereum/diffHistory.md new file mode 100644 index 00000000000..c686397af76 --- /dev/null +++ b/packages/backend/discovery/facet/ethereum/diffHistory.md @@ -0,0 +1,96 @@ +Generated with discovered.json: 0x54e0eea976975dbaf70c242572171682ad00ba8a + +# Diff at Fri, 03 Jan 2025 11:17:26 GMT: + +- author: Luca Donno () +- current block number: 21543602 + +## Description + +Initial discovery. + +## Initial discovery + +```diff ++ Status: CREATED + contract FacetEtherBridgeV6 (0x0000000000000b07ED001607f5263D85bf28Ce4C) + +++ description: Official Facet implementation of the Ether Bridge. +``` + +```diff ++ Status: CREATED + contract AddressManager (0x2D96455AAbb3206f77E7CdC8E4E5c29F76FD33aA) + +++ description: Legacy contract used to manage a mapping of string names to addresses. Modern OP stack uses a different standard proxy system instead, but this contract is still necessary for backwards compatibility with several older contracts. +``` + +```diff ++ Status: CREATED + contract FacetSafeModule (0x3235AdE33cF7013f5b5A51089390396e931e6BCF) + +++ description: Module that allows the Safe to send Facet transactions. +``` + +```diff ++ Status: CREATED + contract OptimismPortal (0x8649Db4A287413567E8dc0EBe1dd62ee02B71eDD) + +++ description: The main entry point to deposit funds from host chain to this chain. It also allows to prove and finalize withdrawals. +``` + +```diff ++ Status: CREATED + contract L1StandardBridge (0x8F75466D69a52EF53C7363F38834bEfC027A2909) + +++ description: The main entry point to deposit ERC20 tokens from host chain to this chain. This contract can store any token. +``` + +```diff ++ Status: CREATED + contract L1CrossDomainMessenger (0xa1233c2DB638D41893a101B0e9dd44cb681270E8) + +++ description: Sends messages from host chain to this chain, and relays messages back onto host chain. In the event that a message sent from host chain to this chain is rejected for exceeding this chain's epoch gas limit, it can be resubmitted via this contract's replay function. +``` + +```diff ++ Status: CREATED + contract FacetMultisig (0xb2B01DeCb6cd36E7396b78D3744482627F22C525) + +++ description: None +``` + +```diff ++ Status: CREATED + contract SystemConfig (0xC1E935F25f9c1198200ec442c6F02f1A2F04534e) + +++ description: None +``` + +```diff ++ Status: CREATED + contract FacetSafeProxy (0xC9F2d55C56Ef9fE4262c4d5b48d8032241AF4d25) + +++ description: Helper of the Safe Module that allows to send Facet transactions. +``` + +```diff ++ Status: CREATED + contract L2OutputOracle (0xD1e4cf142fDf7688A9f7734A5eE74d079696C5A6) + +++ description: Contains a list of proposed state roots which Proposers assert to be a result of block execution. Currently only the PROPOSER address can submit new state roots. +``` + +```diff ++ Status: CREATED + contract EthscriptionsSafeModule (0xDB866fD9241cd32851Df760c1Ec536f3199B22cE) + +++ description: Module that allows the Safe to interact with Ethscriptions. +``` + +```diff ++ Status: CREATED + contract ProxyAdmin (0xe2A3bda6CD571943DD4224d0B8872e221EB5997C) + +++ description: None +``` + +```diff ++ Status: CREATED + contract SuperchainConfig (0xec3a1bd0B6d435Fe8A6e0de728AE87229176EA59) + +++ description: This is NOT the shared SuperchainConfig contract of the OP stack Superchain but rather a local fork. It manages the `PAUSED_SLOT`, a boolean value indicating whether the local chain is paused, and `GUARDIAN_SLOT`, the address of the guardian which can pause and unpause the system. +``` + +```diff ++ Status: CREATED + contract EthscriptionsSafeProxy (0xeEd444Fc821b866b002f30f502C53e88E15d5095) + +++ description: Helper of the Safe Module that allows to send Ethscriptions transactions. +``` diff --git a/packages/backend/discovery/facet/ethereum/discovered.json b/packages/backend/discovery/facet/ethereum/discovered.json new file mode 100644 index 00000000000..f2d43929560 --- /dev/null +++ b/packages/backend/discovery/facet/ethereum/discovered.json @@ -0,0 +1,1480 @@ +{ + "name": "facet", + "chain": "ethereum", + "blockNumber": 21543602, + "configHash": "0xb17117f134aeb02ea7d123883473d6bcfd48f2a01e602ec918a54a66b843cb92", + "contracts": [ + { + "name": "FacetEtherBridgeV6", + "address": "0x0000000000000b07ED001607f5263D85bf28Ce4C", + "unverified": true, + "template": "facet/FacetEtherBridge", + "proxyType": "EIP1967 proxy", + "displayName": "FacetEtherBridge", + "description": "Official Facet implementation of the Ether Bridge.", + "issuedPermissions": [ + { + "permission": "configure", + "target": "0x314d660b083675f415cCAA9c545FeedF377d1006", + "via": [] + }, + { + "permission": "configure", + "target": "0x3235AdE33cF7013f5b5A51089390396e931e6BCF", + "via": [ + { + "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "delay": 0, + "description": "can withdraw all funds from the bridge." + } + ] + }, + { + "permission": "configure", + "target": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "via": [] + }, + { + "permission": "configure", + "target": "0xDB866fD9241cd32851Df760c1Ec536f3199B22cE", + "via": [ + { + "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "delay": 0, + "description": "can withdraw all funds from the bridge." + } + ] + } + ], + "sinceTimestamp": 1734701579, + "values": { + "$admin": "0x0000000000000000000000000000000000000000", + "$implementation": "0x100524b68fe88035623F1309Bb3Db9b64e924724", + "$pastUpgrades": [], + "$upgradeCount": 0, + "eip712Domain": { + "fields": "0x0f", + "name": "Facet Ether Bridge", + "version": "1", + "chainId": 1, + "verifyingContract": "0x0000000000000b07ED001607f5263D85bf28Ce4C", + "salt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extensions": [] + }, + "getAdmin": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "getDumbContract": "0x1673540243E793B0e77C038D4a88448efF524DcE", + "getSigner": "0x314d660b083675f415cCAA9c545FeedF377d1006" + }, + "derivedName": "FacetEtherBridgeV6" + }, + { + "name": "AddressManager", + "address": "0x2D96455AAbb3206f77E7CdC8E4E5c29F76FD33aA", + "template": "opstack/AddressManager", + "sourceHashes": [ + "0xdc86a850f11dc2b5c0472a05d0e3c14f239baf2c3b1ab19631591b0827985380" + ], + "description": "Legacy contract used to manage a mapping of string names to addresses. Modern OP stack uses a different standard proxy system instead, but this contract is still necessary for backwards compatibility with several older contracts.", + "issuedPermissions": [ + { + "permission": "configure", + "target": "0x3235AdE33cF7013f5b5A51089390396e931e6BCF", + "via": [ + { + "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "delay": 0 + }, + { + "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C", + "delay": 0, + "description": "set and change address mappings." + } + ] + }, + { + "permission": "configure", + "target": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "via": [ + { + "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C", + "delay": 0, + "description": "set and change address mappings." + } + ] + }, + { + "permission": "configure", + "target": "0xDB866fD9241cd32851Df760c1Ec536f3199B22cE", + "via": [ + { + "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "delay": 0 + }, + { + "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C", + "delay": 0, + "description": "set and change address mappings." + } + ] + } + ], + "sinceTimestamp": 1733855411, + "values": { + "$immutable": true, + "owner": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C" + } + }, + { + "name": "FacetSafeModule", + "address": "0x3235AdE33cF7013f5b5A51089390396e931e6BCF", + "template": "facet/FacetSafeModule", + "sourceHashes": [ + "0xe72f875e22302b6f4ba7cf79658f0038ab2f434e188a6a5cf5db5dc59475f168" + ], + "description": "Module that allows the Safe to send Facet transactions.", + "receivedPermissions": [ + { + "permission": "configure", + "target": "0x0000000000000b07ED001607f5263D85bf28Ce4C", + "description": "can withdraw all funds from the bridge.", + "via": [{ "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525" }] + }, + { + "permission": "configure", + "target": "0x2D96455AAbb3206f77E7CdC8E4E5c29F76FD33aA", + "description": "set and change address mappings.", + "via": [ + { "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C" }, + { "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525" } + ] + }, + { + "permission": "configure", + "target": "0xC1E935F25f9c1198200ec442c6F02f1A2F04534e", + "description": "it can update the preconfer address, the batch submitter (Sequencer) address and the gas configuration of the system.", + "via": [{ "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525" }] + }, + { + "permission": "guard", + "target": "0x8649Db4A287413567E8dc0EBe1dd62ee02B71eDD", + "via": [{ "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525" }] + }, + { + "permission": "guard", + "target": "0xec3a1bd0B6d435Fe8A6e0de728AE87229176EA59", + "via": [{ "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525" }] + }, + { + "permission": "upgrade", + "target": "0x8649Db4A287413567E8dc0EBe1dd62ee02B71eDD", + "via": [ + { "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C" }, + { "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525" } + ] + }, + { + "permission": "upgrade", + "target": "0x8F75466D69a52EF53C7363F38834bEfC027A2909", + "description": "upgrading the bridge implementation can give access to all funds escrowed therein.", + "via": [ + { "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C" }, + { "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525" } + ] + }, + { + "permission": "upgrade", + "target": "0xC1E935F25f9c1198200ec442c6F02f1A2F04534e", + "via": [ + { "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C" }, + { "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525" } + ] + }, + { + "permission": "upgrade", + "target": "0xD1e4cf142fDf7688A9f7734A5eE74d079696C5A6", + "via": [ + { "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C" }, + { "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525" } + ] + }, + { + "permission": "upgrade", + "target": "0xec3a1bd0B6d435Fe8A6e0de728AE87229176EA59", + "via": [ + { "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C" }, + { "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525" } + ] + } + ], + "directlyReceivedPermissions": [ + { + "permission": "act", + "target": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525" + } + ], + "sinceTimestamp": 1733593847, + "values": { + "$immutable": true, + "facetProxyAddress": "0xC9F2d55C56Ef9fE4262c4d5b48d8032241AF4d25" + } + }, + { + "name": "OptimismPortal", + "address": "0x8649Db4A287413567E8dc0EBe1dd62ee02B71eDD", + "template": "opstack/OptimismPortal", + "sourceHashes": [ + "0x7913a1d7d0c47796c94eb6f8fd87a89ae9f2716eda57c9be4fd2b27c70bed617", + "0x67a50e00d3fb9626bce6a635a533dd909d5a48bf86cacb4d5cee89aa02f7635b" + ], + "proxyType": "EIP1967 proxy", + "description": "The main entry point to deposit funds from host chain to this chain. It also allows to prove and finalize withdrawals.", + "issuedPermissions": [ + { + "permission": "guard", + "target": "0x3235AdE33cF7013f5b5A51089390396e931e6BCF", + "via": [ + { + "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "delay": 0 + } + ] + }, + { + "permission": "guard", + "target": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "via": [] + }, + { + "permission": "guard", + "target": "0xDB866fD9241cd32851Df760c1Ec536f3199B22cE", + "via": [ + { + "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "delay": 0 + } + ] + }, + { + "permission": "upgrade", + "target": "0x3235AdE33cF7013f5b5A51089390396e931e6BCF", + "via": [ + { + "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "delay": 0 + }, + { + "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C", + "delay": 0 + } + ] + }, + { + "permission": "upgrade", + "target": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "via": [ + { + "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C", + "delay": 0 + } + ] + }, + { + "permission": "upgrade", + "target": "0xDB866fD9241cd32851Df760c1Ec536f3199B22cE", + "via": [ + { + "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "delay": 0 + }, + { + "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C", + "delay": 0 + } + ] + } + ], + "ignoreInWatchMode": ["params"], + "sinceTimestamp": 1733855495, + "values": { + "$admin": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C", + "$implementation": "0x059249eBf6e7F1B6c7E7bFb9079f6BCd8581635E", + "$pastUpgrades": [ + [ + "2024-12-10T18:33:47.000Z", + "0x4505f159cd12117daef74f152cc9acfbf36c7118d319f188f98e0cf30c31eb7f", + ["0x059249eBf6e7F1B6c7E7bFb9079f6BCd8581635E"] + ] + ], + "$upgradeCount": 1, + "guardian": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "l2Oracle": "0xD1e4cf142fDf7688A9f7734A5eE74d079696C5A6", + "l2Sender": "0x000000000000000000000000000000000000dEaD", + "params": { + "prevBaseFee": 1000000000, + "prevBoughtGas": 0, + "prevBlockNum": 21373922 + }, + "paused": false, + "superchainConfig": "0xec3a1bd0B6d435Fe8A6e0de728AE87229176EA59", + "systemConfig": "0xC1E935F25f9c1198200ec442c6F02f1A2F04534e", + "version": "2.8.1-beta.1" + } + }, + { + "name": "L1StandardBridge", + "address": "0x8F75466D69a52EF53C7363F38834bEfC027A2909", + "template": "opstack/L1StandardBridge_facet", + "sourceHashes": [ + "0xbfb58685ff2f2f07eaa01a3c4e3c33c97686bfd3ae7c50c49f9da6ef5098cb31", + "0x9060db4f3f7bf996f9a1db86c049a15d8a68dff34b2e9bdd31e9e53ba3e79c8f" + ], + "proxyType": "EIP1967 proxy", + "description": "The main entry point to deposit ERC20 tokens from host chain to this chain. This contract can store any token.", + "issuedPermissions": [ + { + "permission": "upgrade", + "target": "0x3235AdE33cF7013f5b5A51089390396e931e6BCF", + "via": [ + { + "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "delay": 0 + }, + { + "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C", + "delay": 0, + "description": "upgrading the bridge implementation can give access to all funds escrowed therein." + } + ] + }, + { + "permission": "upgrade", + "target": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "via": [ + { + "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C", + "delay": 0, + "description": "upgrading the bridge implementation can give access to all funds escrowed therein." + } + ] + }, + { + "permission": "upgrade", + "target": "0xDB866fD9241cd32851Df760c1Ec536f3199B22cE", + "via": [ + { + "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "delay": 0 + }, + { + "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C", + "delay": 0, + "description": "upgrading the bridge implementation can give access to all funds escrowed therein." + } + ] + } + ], + "sinceTimestamp": 1733855519, + "values": { + "$admin": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C", + "$implementation": "0x77764Bdf2B52C4B2635A73927945541B65DF74E9", + "$pastUpgrades": [], + "$upgradeCount": 0, + "admin": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "l2TokenBridge": "0xaCde2ce9a9Bc89ED083FaA80685E2bA2c9ec72E9", + "messenger": "0xa1233c2DB638D41893a101B0e9dd44cb681270E8", + "MESSENGER": "0xa1233c2DB638D41893a101B0e9dd44cb681270E8", + "OTHER_BRIDGE": "0xaCde2ce9a9Bc89ED083FaA80685E2bA2c9ec72E9", + "otherBridge": "0xaCde2ce9a9Bc89ED083FaA80685E2bA2c9ec72E9", + "paused": false, + "superchainConfig": "0xec3a1bd0B6d435Fe8A6e0de728AE87229176EA59", + "systemConfig": "0xC1E935F25f9c1198200ec442c6F02f1A2F04534e", + "version": "2.2.0", + "weth": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + }, + "derivedName": "PausedL1StandardBridge" + }, + { + "name": "L1CrossDomainMessenger", + "address": "0xa1233c2DB638D41893a101B0e9dd44cb681270E8", + "template": "opstack/L1CrossDomainMessenger", + "sourceHashes": [ + "0x20a2eb4d3677fc8a15e944f7b1843acd01b2e92acdc4c7a7f7a35b07b891149b", + "0xa7921942f5ee71c3376b7248987c8cedb6060c2bbab4f50927686cde0f024360" + ], + "proxyType": "resolved delegate proxy", + "description": "Sends messages from host chain to this chain, and relays messages back onto host chain. In the event that a message sent from host chain to this chain is rejected for exceeding this chain's epoch gas limit, it can be resubmitted via this contract's replay function.", + "ignoreInWatchMode": ["messageNonce"], + "sinceTimestamp": 1733855531, + "values": { + "$immutable": false, + "$implementation": "0xa711dC056C2Db08ee2Ba89be3ED504f48Bf4dbfA", + "$pastUpgrades": [ + [ + "2024-12-10T18:34:59.000Z", + "0x77ff91e1101954acf9e6c6c61933aa22dd84adf6b17968db7950314e5a1172f4", + ["0xa711dC056C2Db08ee2Ba89be3ED504f48Bf4dbfA"] + ] + ], + "$upgradeCount": 1, + "MESSAGE_VERSION": 1, + "messageNonce": "1766847064778384329583297500742918515827483896875618958121606201292619776", + "MIN_GAS_CALLDATA_OVERHEAD": 16, + "MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR": 63, + "MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR": 64, + "OTHER_MESSENGER": "0x0272ea8cf3572f818Bb0b00b0D3D3b33f9B4b1f6", + "otherMessenger": "0x0272ea8cf3572f818Bb0b00b0D3D3b33f9B4b1f6", + "paused": false, + "portal": "0x8649Db4A287413567E8dc0EBe1dd62ee02B71eDD", + "PORTAL": "0x8649Db4A287413567E8dc0EBe1dd62ee02B71eDD", + "RELAY_CALL_OVERHEAD": 40000, + "RELAY_CONSTANT_OVERHEAD": 200000, + "RELAY_GAS_CHECK_BUFFER": 5000, + "RELAY_RESERVED_GAS": 40000, + "ResolvedDelegateProxy_addressManager": "0x2D96455AAbb3206f77E7CdC8E4E5c29F76FD33aA", + "ResolvedDelegateProxy_implementationName": "OVM_L1CrossDomainMessenger", + "superchainConfig": "0xec3a1bd0B6d435Fe8A6e0de728AE87229176EA59", + "systemConfig": "0xC1E935F25f9c1198200ec442c6F02f1A2F04534e", + "version": "2.4.0" + } + }, + { + "name": "FacetMultisig", + "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "template": "GnosisSafe", + "sourceHashes": [ + "0x81a7349eebb98ac33b0bc6842e3cb258034a8f2a4ba004570bb8e2e25947f9ff", + "0xd42bbf9f7dcd3720a7fc6bdc6edfdfae8800a37d6dd4decfa0ef6ca4a2e88940" + ], + "proxyType": "gnosis safe", + "receivedPermissions": [ + { + "permission": "configure", + "target": "0x0000000000000b07ED001607f5263D85bf28Ce4C", + "description": "can withdraw all funds from the bridge." + }, + { + "permission": "configure", + "target": "0x2D96455AAbb3206f77E7CdC8E4E5c29F76FD33aA", + "description": "set and change address mappings.", + "via": [{ "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C" }] + }, + { + "permission": "configure", + "target": "0xC1E935F25f9c1198200ec442c6F02f1A2F04534e", + "description": "it can update the preconfer address, the batch submitter (Sequencer) address and the gas configuration of the system." + }, + { + "permission": "guard", + "target": "0x8649Db4A287413567E8dc0EBe1dd62ee02B71eDD" + }, + { + "permission": "guard", + "target": "0xec3a1bd0B6d435Fe8A6e0de728AE87229176EA59" + }, + { + "permission": "upgrade", + "target": "0x8649Db4A287413567E8dc0EBe1dd62ee02B71eDD", + "via": [{ "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C" }] + }, + { + "permission": "upgrade", + "target": "0x8F75466D69a52EF53C7363F38834bEfC027A2909", + "description": "upgrading the bridge implementation can give access to all funds escrowed therein.", + "via": [{ "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C" }] + }, + { + "permission": "upgrade", + "target": "0xC1E935F25f9c1198200ec442c6F02f1A2F04534e", + "via": [{ "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C" }] + }, + { + "permission": "upgrade", + "target": "0xD1e4cf142fDf7688A9f7734A5eE74d079696C5A6", + "via": [{ "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C" }] + }, + { + "permission": "upgrade", + "target": "0xec3a1bd0B6d435Fe8A6e0de728AE87229176EA59", + "via": [{ "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C" }] + } + ], + "directlyReceivedPermissions": [ + { + "permission": "act", + "target": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C" + } + ], + "ignoreInWatchMode": ["nonce"], + "sinceTimestamp": 1701094787, + "values": { + "$immutable": false, + "$implementation": "0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552", + "$members": [ + "0x77610267a344Eb39955c20908978830f61e2373C", + "0xD66Cb98865181a890ffee5654fAe1D6b4D1827a7", + "0x75deB70b12689e9CaeF4b316eDD04F213Af06127" + ], + "$threshold": 2, + "domainSeparator": "0x4a566a7839f4fa7ba86474f484e7e11b96ee4927109cfcfdddad250cd7a3fec3", + "getChainId": 1, + "GnosisSafe_modules": [ + "0x3235AdE33cF7013f5b5A51089390396e931e6BCF", + "0xDB866fD9241cd32851Df760c1Ec536f3199B22cE" + ], + "multisigThreshold": "2 of 3 (67%)", + "nonce": 287, + "VERSION": "1.3.0" + }, + "derivedName": "GnosisSafe" + }, + { + "name": "SystemConfig", + "address": "0xC1E935F25f9c1198200ec442c6F02f1A2F04534e", + "template": "opstack/SystemConfig_facet", + "sourceHashes": [ + "0x7913a1d7d0c47796c94eb6f8fd87a89ae9f2716eda57c9be4fd2b27c70bed617", + "0x77a8405865bba6fd2e1d9522ac1279c1c7c1a325530ad51f3d2ba686e8a6b057" + ], + "proxyType": "EIP1967 proxy", + "issuedPermissions": [ + { + "permission": "configure", + "target": "0x3235AdE33cF7013f5b5A51089390396e931e6BCF", + "via": [ + { + "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "delay": 0, + "description": "it can update the preconfer address, the batch submitter (Sequencer) address and the gas configuration of the system." + } + ] + }, + { + "permission": "configure", + "target": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "via": [] + }, + { + "permission": "configure", + "target": "0xDB866fD9241cd32851Df760c1Ec536f3199B22cE", + "via": [ + { + "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "delay": 0, + "description": "it can update the preconfer address, the batch submitter (Sequencer) address and the gas configuration of the system." + } + ] + }, + { + "permission": "upgrade", + "target": "0x3235AdE33cF7013f5b5A51089390396e931e6BCF", + "via": [ + { + "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "delay": 0 + }, + { + "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C", + "delay": 0 + } + ] + }, + { + "permission": "upgrade", + "target": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "via": [ + { + "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C", + "delay": 0 + } + ] + }, + { + "permission": "upgrade", + "target": "0xDB866fD9241cd32851Df760c1Ec536f3199B22cE", + "via": [ + { + "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "delay": 0 + }, + { + "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C", + "delay": 0 + } + ] + } + ], + "ignoreInWatchMode": ["scalar", "overhead"], + "sinceTimestamp": 1733855507, + "values": { + "$admin": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C", + "$implementation": "0x484EEcfCa1E83eA73Aa569F1cA1eBaf9316F5da3", + "$pastUpgrades": [ + [ + "2024-12-10T18:33:59.000Z", + "0x9d06a8dc2b5378151257c77661a6fa4c705429498d07e5ce24d054a58180157c", + ["0x484EEcfCa1E83eA73Aa569F1cA1eBaf9316F5da3"] + ] + ], + "$upgradeCount": 1, + "basefeeScalar": 1368, + "BATCH_INBOX_SLOT": "0x71ac12829d66ee73d8d95bff50b3589745ce57edae70a3fb111a2342464dc597", + "batcherHash": "0x0000000000000000000000000000000000000000", + "batchInbox": "0x0000000000000000000000000000000000000000", + "blobbasefeeScalar": 810949, + "DISPUTE_GAME_FACTORY_SLOT": "0x52322a25d9f59ea17656545543306b7aef62bc0cc53a0e65ccfa0c75b97aa906", + "disputeGameFactory": "0x0000000000000000000000000000000000000000", + "gasLimit": 200000000, + "gasPayingToken": { + "addr_": "0xFACE7fAcE7fAcE7FacE7FACE7FACe7FAcE7fACE7", + "decimals_": 18 + }, + "gasPayingTokenName": "Facet Compute Token", + "gasPayingTokenSymbol": "FCT", + "isCustomGasToken": true, + "L1_CROSS_DOMAIN_MESSENGER_SLOT": "0x383f291819e6d54073bc9a648251d97421076bdd101933c0c022219ce9580636", + "L1_ERC_721_BRIDGE_SLOT": "0x46adcbebc6be8ce551740c29c47c8798210f23f7f4086c41752944352568d5a7", + "L1_STANDARD_BRIDGE_SLOT": "0x9904ba90dde5696cda05c9e0dab5cbaa0fea005ace4d11218a02ac668dad6376", + "l1CrossDomainMessenger": "0xa1233c2DB638D41893a101B0e9dd44cb681270E8", + "l1ERC721Bridge": "0x0000000000000000000000000000000000000000", + "l1StandardBridge": "0x8F75466D69a52EF53C7363F38834bEfC027A2909", + "maximumGasLimit": 200000000, + "minimumGasLimit": 21000000, + "OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT": "0xa04c5bb938ca6fc46d95553abf0a76345ce3e722a30bf4f74928b8e7d852320c", + "OPTIMISM_PORTAL_SLOT": "0x4b6c74f9e688cb39801f2112c14a8c57232a3fc5202e1444126d4bce86eb19ac", + "optimismMintableERC20Factory": "0x0000000000000000000000000000000000000000", + "optimismPortal": "0x8649Db4A287413567E8dc0EBe1dd62ee02B71eDD", + "overhead": 0, + "owner": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "resourceConfig": { + "maxResourceLimit": 20000000, + "elasticityMultiplier": 10, + "baseFeeMaxChangeDenominator": 8, + "minimumBaseFee": 1000000000, + "systemTxMaxGas": 1000000, + "maximumBaseFee": "340282366920938463463374607431768211455" + }, + "scalar": "452312848583266388373324160190187140051835877600158453279134670530344387928", + "sequencerAddress": "0x0000000000000000000000000000000000000000", + "sequencerInbox": "0x00000000000000000000000000000000000FacE7", + "START_BLOCK_SLOT": "0xa11ee3ab75b40e88a0105e935d17cd36c8faee0138320d776c411291bdbbb19f", + "startBlock": 21373923, + "UNSAFE_BLOCK_SIGNER_SLOT": "0x65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c08", + "unsafeBlockSigner": "0x0000000000000000000000000000000000000000", + "version": "2.3.0-beta.2", + "VERSION": 0 + }, + "fieldMeta": { + "gasLimit": { + "severity": "LOW", + "description": "Gas limit for blocks on L2." + } + }, + "derivedName": "SystemConfig" + }, + { + "name": "FacetSafeProxy", + "address": "0xC9F2d55C56Ef9fE4262c4d5b48d8032241AF4d25", + "template": "facet/FacetSafeProxy", + "sourceHashes": [ + "0xeeb64378c57fe40198d92b6136616a0243788e51e4d363d57f38a4c6ad891ff8" + ], + "description": "Helper of the Safe Module that allows to send Facet transactions.", + "sinceTimestamp": 1733593775, + "values": { "$immutable": true } + }, + { + "name": "L2OutputOracle", + "address": "0xD1e4cf142fDf7688A9f7734A5eE74d079696C5A6", + "template": "opstack/L2OutputOracle", + "sourceHashes": [ + "0x7913a1d7d0c47796c94eb6f8fd87a89ae9f2716eda57c9be4fd2b27c70bed617", + "0x025c187b0231be4785898f25f98d749f953f5d06781772aef242812e2ecf52e3" + ], + "proxyType": "EIP1967 proxy", + "description": "Contains a list of proposed state roots which Proposers assert to be a result of block execution. Currently only the PROPOSER address can submit new state roots.", + "issuedPermissions": [ + { + "permission": "challenge", + "target": "0x034B0a32395D15C0F63F3e88931Bf7e1D9627eE3", + "via": [] + }, + { + "permission": "propose", + "target": "0x034B0a32395D15C0F63F3e88931Bf7e1D9627eE3", + "via": [] + }, + { + "permission": "upgrade", + "target": "0x3235AdE33cF7013f5b5A51089390396e931e6BCF", + "via": [ + { + "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "delay": 0 + }, + { + "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C", + "delay": 0 + } + ] + }, + { + "permission": "upgrade", + "target": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "via": [ + { + "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C", + "delay": 0 + } + ] + }, + { + "permission": "upgrade", + "target": "0xDB866fD9241cd32851Df760c1Ec536f3199B22cE", + "via": [ + { + "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "delay": 0 + }, + { + "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C", + "delay": 0 + } + ] + } + ], + "ignoreInWatchMode": [ + "nextBlockNumber", + "nextOutputIndex", + "latestBlockNumber", + "latestOutputIndex" + ], + "sinceTimestamp": 1733855543, + "values": { + "$admin": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C", + "$implementation": "0xEcc75A86c91D857C6Cb8ea991F88eaE40819C6AC", + "$pastUpgrades": [ + [ + "2024-12-10T18:35:23.000Z", + "0xa993935275111e485ff69538d24a0f8c3bf916f00416f0537e815bd226af3bf6", + ["0xEcc75A86c91D857C6Cb8ea991F88eaE40819C6AC"] + ] + ], + "$upgradeCount": 1, + "challenger": "0x034B0a32395D15C0F63F3e88931Bf7e1D9627eE3", + "CHALLENGER": "0x034B0a32395D15C0F63F3e88931Bf7e1D9627eE3", + "deletedOutputs": [], + "FINALIZATION_PERIOD_SECONDS": 12, + "finalizationPeriodSeconds": 12, + "L2_BLOCK_TIME": 12, + "l2BlockTime": 12, + "latestBlockNumber": 166445, + "latestOutputIndex": 22, + "nextBlockNumber": 173645, + "nextOutputIndex": 23, + "proposer": "0x034B0a32395D15C0F63F3e88931Bf7e1D9627eE3", + "PROPOSER": "0x034B0a32395D15C0F63F3e88931Bf7e1D9627eE3", + "startingBlockNumber": 845, + "startingTimestamp": 1733854679, + "SUBMISSION_INTERVAL": 7200, + "submissionInterval": 7200, + "version": "1.8.0" + }, + "fieldMeta": { + "FINALIZATION_PERIOD_SECONDS": { + "description": "Challenge period (Number of seconds until a state root is finalized)." + } + } + }, + { + "name": "EthscriptionsSafeModule", + "address": "0xDB866fD9241cd32851Df760c1Ec536f3199B22cE", + "template": "facet/EthscriptionsSafeModule", + "sourceHashes": [ + "0x3b42bbbb2e985d16af9ccbee812a73e53882232fc47dab8cf43369b5b853df33" + ], + "description": "Module that allows the Safe to interact with Ethscriptions.", + "receivedPermissions": [ + { + "permission": "configure", + "target": "0x0000000000000b07ED001607f5263D85bf28Ce4C", + "description": "can withdraw all funds from the bridge.", + "via": [{ "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525" }] + }, + { + "permission": "configure", + "target": "0x2D96455AAbb3206f77E7CdC8E4E5c29F76FD33aA", + "description": "set and change address mappings.", + "via": [ + { "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C" }, + { "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525" } + ] + }, + { + "permission": "configure", + "target": "0xC1E935F25f9c1198200ec442c6F02f1A2F04534e", + "description": "it can update the preconfer address, the batch submitter (Sequencer) address and the gas configuration of the system.", + "via": [{ "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525" }] + }, + { + "permission": "guard", + "target": "0x8649Db4A287413567E8dc0EBe1dd62ee02B71eDD", + "via": [{ "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525" }] + }, + { + "permission": "guard", + "target": "0xec3a1bd0B6d435Fe8A6e0de728AE87229176EA59", + "via": [{ "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525" }] + }, + { + "permission": "upgrade", + "target": "0x8649Db4A287413567E8dc0EBe1dd62ee02B71eDD", + "via": [ + { "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C" }, + { "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525" } + ] + }, + { + "permission": "upgrade", + "target": "0x8F75466D69a52EF53C7363F38834bEfC027A2909", + "description": "upgrading the bridge implementation can give access to all funds escrowed therein.", + "via": [ + { "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C" }, + { "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525" } + ] + }, + { + "permission": "upgrade", + "target": "0xC1E935F25f9c1198200ec442c6F02f1A2F04534e", + "via": [ + { "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C" }, + { "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525" } + ] + }, + { + "permission": "upgrade", + "target": "0xD1e4cf142fDf7688A9f7734A5eE74d079696C5A6", + "via": [ + { "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C" }, + { "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525" } + ] + }, + { + "permission": "upgrade", + "target": "0xec3a1bd0B6d435Fe8A6e0de728AE87229176EA59", + "via": [ + { "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C" }, + { "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525" } + ] + } + ], + "directlyReceivedPermissions": [ + { + "permission": "act", + "target": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525" + } + ], + "sinceTimestamp": 1697832707, + "values": { + "$immutable": true, + "ethscriptionsProxyAddress": "0xeEd444Fc821b866b002f30f502C53e88E15d5095" + } + }, + { + "name": "ProxyAdmin", + "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C", + "template": "global/ProxyAdmin", + "sourceHashes": [ + "0x96d2f0fa1bd83ebd61ba6a2351c64c7fda7aa580b11ea67bb6bf4338e5c28512" + ], + "directlyReceivedPermissions": [ + { + "permission": "configure", + "target": "0x2D96455AAbb3206f77E7CdC8E4E5c29F76FD33aA", + "description": "set and change address mappings." + }, + { + "permission": "upgrade", + "target": "0x8649Db4A287413567E8dc0EBe1dd62ee02B71eDD" + }, + { + "permission": "upgrade", + "target": "0x8F75466D69a52EF53C7363F38834bEfC027A2909", + "description": "upgrading the bridge implementation can give access to all funds escrowed therein." + }, + { + "permission": "upgrade", + "target": "0xC1E935F25f9c1198200ec442c6F02f1A2F04534e" + }, + { + "permission": "upgrade", + "target": "0xD1e4cf142fDf7688A9f7734A5eE74d079696C5A6" + }, + { + "permission": "upgrade", + "target": "0xec3a1bd0B6d435Fe8A6e0de728AE87229176EA59" + } + ], + "sinceTimestamp": 1733855423, + "values": { + "$immutable": true, + "addressManager": "0x2D96455AAbb3206f77E7CdC8E4E5c29F76FD33aA", + "isUpgrading": false, + "owner": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525" + } + }, + { + "name": "SuperchainConfig", + "address": "0xec3a1bd0B6d435Fe8A6e0de728AE87229176EA59", + "template": "opstack/SuperchainConfigFake", + "sourceHashes": [ + "0x7913a1d7d0c47796c94eb6f8fd87a89ae9f2716eda57c9be4fd2b27c70bed617", + "0x3ac96c9c95e25f689f65a50f24b325e3f891029cb1cea96dc642418bbb535b1d" + ], + "proxyType": "EIP1967 proxy", + "description": "This is NOT the shared SuperchainConfig contract of the OP stack Superchain but rather a local fork. It manages the `PAUSED_SLOT`, a boolean value indicating whether the local chain is paused, and `GUARDIAN_SLOT`, the address of the guardian which can pause and unpause the system.", + "issuedPermissions": [ + { + "permission": "guard", + "target": "0x3235AdE33cF7013f5b5A51089390396e931e6BCF", + "via": [ + { + "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "delay": 0 + } + ] + }, + { + "permission": "guard", + "target": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "via": [] + }, + { + "permission": "guard", + "target": "0xDB866fD9241cd32851Df760c1Ec536f3199B22cE", + "via": [ + { + "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "delay": 0 + } + ] + }, + { + "permission": "upgrade", + "target": "0x3235AdE33cF7013f5b5A51089390396e931e6BCF", + "via": [ + { + "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "delay": 0 + }, + { + "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C", + "delay": 0 + } + ] + }, + { + "permission": "upgrade", + "target": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "via": [ + { + "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C", + "delay": 0 + } + ] + }, + { + "permission": "upgrade", + "target": "0xDB866fD9241cd32851Df760c1Ec536f3199B22cE", + "via": [ + { + "address": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "delay": 0 + }, + { + "address": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C", + "delay": 0 + } + ] + } + ], + "sinceTimestamp": 1733855459, + "values": { + "$admin": "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C", + "$implementation": "0x12df0dfDb02cf4FB18d13eA133Ad9C0F9284f267", + "$pastUpgrades": [ + [ + "2024-12-10T18:31:23.000Z", + "0x359dc5273b0b70ac686aad7d55053d71857b7e2a041cc449ccff42dfd0763b68", + ["0x12df0dfDb02cf4FB18d13eA133Ad9C0F9284f267"] + ] + ], + "$upgradeCount": 1, + "guardian": "0xb2B01DeCb6cd36E7396b78D3744482627F22C525", + "GUARDIAN_SLOT": "0xd30e835d3f35624761057ff5b27d558f97bd5be034621e62240e5c0b784abe68", + "paused": false, + "PAUSED_SLOT": "0x54176ff9944c4784e5857ec4e5ef560a462c483bf534eda43f91bb01a470b1b6", + "version": "1.1.0" + } + }, + { + "name": "EthscriptionsSafeProxy", + "address": "0xeEd444Fc821b866b002f30f502C53e88E15d5095", + "template": "facet/EthscriptionsSafeProxy", + "sourceHashes": [ + "0x09dadea0389245a8882bad041f0349cda9669a53f590677017514fdbe7ec0c8a" + ], + "description": "Helper of the Safe Module that allows to send Ethscriptions transactions.", + "sinceTimestamp": 1697832623, + "values": { "$immutable": true } + } + ], + "eoas": [ + { + "address": "0x0000000000000000000000000000000000000000", + "directlyReceivedPermissions": [ + { + "permission": "upgrade", + "target": "0x0000000000000b07ED001607f5263D85bf28Ce4C" + } + ] + }, + { "address": "0x000000000000000000000000000000000000dEaD" }, + { "address": "0x00000000000000000000000000000000000FacE7" }, + { + "address": "0x034B0a32395D15C0F63F3e88931Bf7e1D9627eE3", + "receivedPermissions": [ + { + "permission": "challenge", + "target": "0xD1e4cf142fDf7688A9f7734A5eE74d079696C5A6" + }, + { + "permission": "propose", + "target": "0xD1e4cf142fDf7688A9f7734A5eE74d079696C5A6" + } + ] + }, + { "address": "0x1673540243E793B0e77C038D4a88448efF524DcE" }, + { + "address": "0x314d660b083675f415cCAA9c545FeedF377d1006", + "receivedPermissions": [ + { + "permission": "configure", + "target": "0x0000000000000b07ED001607f5263D85bf28Ce4C", + "description": "can sign arbitrary withdrawals for users." + } + ] + }, + { "address": "0x75deB70b12689e9CaeF4b316eDD04F213Af06127" }, + { "address": "0x77610267a344Eb39955c20908978830f61e2373C" }, + { "address": "0xD66Cb98865181a890ffee5654fAe1D6b4D1827a7" } + ], + "abis": { + "0x059249eBf6e7F1B6c7E7bFb9079f6BCd8581635E": [ + "constructor()", + "error BadTarget()", + "error CallPaused()", + "error ContentLengthMismatch()", + "error EmptyItem()", + "error GasEstimation()", + "error InvalidDataRemainder()", + "error InvalidHeader()", + "error LargeCalldata()", + "error NoValue()", + "error NonReentrant()", + "error OnlyCustomGasToken()", + "error OutOfGas()", + "error SmallGasLimit()", + "error TransferFailed()", + "error Unauthorized()", + "error UnexpectedList()", + "error UnexpectedString()", + "event Initialized(uint8 version)", + "event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData)", + "event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success)", + "event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to)", + "function balance() view returns (uint256)", + "function depositERC20Transaction(address _to, uint256 _mint, uint256 _value, uint64 _gasLimit, bool _isCreation, bytes _data)", + "function depositTransaction(address _to, uint256 _value, uint64 _gasLimit, bool _isCreation, bytes _data) payable", + "function donateETH() payable", + "function finalizeWithdrawalTransaction(tuple(uint256 nonce, address sender, address target, uint256 value, uint256 gasLimit, bytes data) _tx)", + "function finalizedWithdrawals(bytes32) view returns (bool)", + "function guardian() view returns (address)", + "function initialize(address _l2Oracle, address _systemConfig, address _superchainConfig)", + "function isOutputFinalized(uint256 _l2OutputIndex) view returns (bool)", + "function l2Oracle() view returns (address)", + "function l2Sender() view returns (address)", + "function minimumGasLimit(uint64 _byteCount) pure returns (uint64)", + "function params() view returns (uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum)", + "function paused() view returns (bool paused_)", + "function proveWithdrawalTransaction(tuple(uint256 nonce, address sender, address target, uint256 value, uint256 gasLimit, bytes data) _tx, uint256 _l2OutputIndex, tuple(bytes32 version, bytes32 stateRoot, bytes32 messagePasserStorageRoot, bytes32 latestBlockhash) _outputRootProof, bytes[] _withdrawalProof)", + "function provenWithdrawals(bytes32) view returns (bytes32 outputRoot, uint128 timestamp, uint128 l2OutputIndex)", + "function setGasPayingToken(address _token, uint8 _decimals, bytes32 _name, bytes32 _symbol)", + "function superchainConfig() view returns (address)", + "function systemConfig() view returns (address)", + "function version() pure returns (string)" + ], + "0x100524b68fe88035623F1309Bb3Db9b64e924724": [ + "constructor()", + "error FeatureDisabled()", + "error InvalidAmount()", + "error InvalidInitialization()", + "error NotFactory()", + "error NotInitializing()", + "error ZeroAdminAddress()", + "event Initialized(uint64 version)", + "function adminMarkComplete(address recipient, bytes32 withdrawalId)", + "function adminWithdraw(address recipient, uint256 amount)", + "function adminWithdrawFCT(address recipient, uint256 amount)", + "function bridgeAndCall(address recipient, address dumbContractToCall, bytes functionCalldata, uint64 gasLimit) payable", + "function deposit() payable", + "function donateETH() payable", + "function eip712Domain() view returns (bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions)", + "function getAdmin() view returns (address)", + "function getDumbContract() pure returns (address)", + "function getSigner() view returns (address)", + "function initialize()", + "function processedWithdraws(bytes32 withdrawalId) view returns (bool)", + "function setAdmin(address admin)", + "function withdraw(tuple(address recipient, uint256 amount, bytes32 withdrawalId, bytes32 blockHash, uint256 blockNumber, bytes signature) req)" + ], + "0x12df0dfDb02cf4FB18d13eA133Ad9C0F9284f267": [ + "constructor()", + "event ConfigUpdate(uint8 indexed updateType, bytes data)", + "event Initialized(uint8 version)", + "event Paused(string identifier)", + "event Unpaused()", + "function GUARDIAN_SLOT() view returns (bytes32)", + "function PAUSED_SLOT() view returns (bytes32)", + "function guardian() view returns (address guardian_)", + "function initialize(address _guardian, bool _paused)", + "function pause(string _identifier)", + "function paused() view returns (bool paused_)", + "function unpause()", + "function version() view returns (string)" + ], + "0x2D96455AAbb3206f77E7CdC8E4E5c29F76FD33aA": [ + "event AddressSet(string indexed name, address newAddress, address oldAddress)", + "event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)", + "function getAddress(string _name) view returns (address)", + "function owner() view returns (address)", + "function renounceOwnership()", + "function setAddress(string _name, address _address)", + "function transferOwnership(address newOwner)" + ], + "0x3235AdE33cF7013f5b5A51089390396e931e6BCF": [ + "function facetProxyAddress() view returns (address)", + "function sendFacetTransaction(bytes to, uint256 value, uint256 gasLimit, bytes data)" + ], + "0x484EEcfCa1E83eA73Aa569F1cA1eBaf9316F5da3": [ + "constructor()", + "event ConfigUpdate(uint256 indexed version, uint8 indexed updateType, bytes data)", + "event Initialized(uint8 version)", + "event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)", + "function BATCH_INBOX_SLOT() view returns (bytes32)", + "function DISPUTE_GAME_FACTORY_SLOT() view returns (bytes32)", + "function L1_CROSS_DOMAIN_MESSENGER_SLOT() view returns (bytes32)", + "function L1_ERC_721_BRIDGE_SLOT() view returns (bytes32)", + "function L1_STANDARD_BRIDGE_SLOT() view returns (bytes32)", + "function OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT() view returns (bytes32)", + "function OPTIMISM_PORTAL_SLOT() view returns (bytes32)", + "function START_BLOCK_SLOT() view returns (bytes32)", + "function UNSAFE_BLOCK_SIGNER_SLOT() view returns (bytes32)", + "function VERSION() view returns (uint256)", + "function basefeeScalar() view returns (uint32)", + "function batchInbox() view returns (address addr_)", + "function batcherHash() view returns (bytes32)", + "function blobbasefeeScalar() view returns (uint32)", + "function disputeGameFactory() view returns (address addr_)", + "function gasLimit() view returns (uint64)", + "function gasPayingToken() view returns (address addr_, uint8 decimals_)", + "function gasPayingTokenName() view returns (string name_)", + "function gasPayingTokenSymbol() view returns (string symbol_)", + "function initialize(address _owner, uint32 _basefeeScalar, uint32 _blobbasefeeScalar, bytes32 _batcherHash, uint64 _gasLimit, address _unsafeBlockSigner, tuple(uint32 maxResourceLimit, uint8 elasticityMultiplier, uint8 baseFeeMaxChangeDenominator, uint32 minimumBaseFee, uint32 systemTxMaxGas, uint128 maximumBaseFee) _config, address _batchInbox, tuple(address l1CrossDomainMessenger, address l1ERC721Bridge, address l1StandardBridge, address disputeGameFactory, address optimismPortal, address optimismMintableERC20Factory, address gasPayingToken) _addresses)", + "function isCustomGasToken() view returns (bool)", + "function l1CrossDomainMessenger() view returns (address addr_)", + "function l1ERC721Bridge() view returns (address addr_)", + "function l1StandardBridge() view returns (address addr_)", + "function maximumGasLimit() pure returns (uint64)", + "function minimumGasLimit() view returns (uint64)", + "function optimismMintableERC20Factory() view returns (address addr_)", + "function optimismPortal() view returns (address addr_)", + "function overhead() view returns (uint256)", + "function owner() view returns (address)", + "function renounceOwnership()", + "function resourceConfig() view returns (tuple(uint32 maxResourceLimit, uint8 elasticityMultiplier, uint8 baseFeeMaxChangeDenominator, uint32 minimumBaseFee, uint32 systemTxMaxGas, uint128 maximumBaseFee))", + "function scalar() view returns (uint256)", + "function setBatcherHash(bytes32 _batcherHash)", + "function setGasConfig(uint256 _overhead, uint256 _scalar)", + "function setGasConfigEcotone(uint32 _basefeeScalar, uint32 _blobbasefeeScalar)", + "function setGasLimit(uint64 _gasLimit)", + "function setUnsafeBlockSigner(address _unsafeBlockSigner)", + "function startBlock() view returns (uint256 startBlock_)", + "function transferOwnership(address newOwner)", + "function unsafeBlockSigner() view returns (address addr_)", + "function version() pure returns (string)" + ], + "0x77764Bdf2B52C4B2635A73927945541B65DF74E9": [ + "constructor()", + "event ERC20BridgeFinalized(address indexed localToken, address indexed remoteToken, address indexed from, address to, uint256 amount, bytes extraData)", + "event ERC20BridgeInitiated(address indexed localToken, address indexed remoteToken, address indexed from, address to, uint256 amount, bytes extraData)", + "event ERC20DepositInitiated(address indexed l1Token, address indexed l2Token, address indexed from, address to, uint256 amount, bytes extraData)", + "event ERC20WithdrawalFinalized(address indexed l1Token, address indexed l2Token, address indexed from, address to, uint256 amount, bytes extraData)", + "event ETHBridgeFinalized(address indexed from, address indexed to, uint256 amount, bytes extraData)", + "event ETHBridgeInitiated(address indexed from, address indexed to, uint256 amount, bytes extraData)", + "event ETHDepositInitiated(address indexed from, address indexed to, uint256 amount, bytes extraData)", + "event ETHWithdrawalFinalized(address indexed from, address indexed to, uint256 amount, bytes extraData)", + "event Initialized(uint8 version)", + "event L1ERC20DepositAttempted(bytes32 indexed depositId, address indexed l1Token, address indexed l2Token, address from, address to, uint256 amount, bytes extraData)", + "function MESSENGER() view returns (address)", + "function OTHER_BRIDGE() view returns (address)", + "function admin() pure returns (address)", + "function adminWithdraw(address recipient, uint256 amount)", + "function bridgeERC20(address _localToken, address _remoteToken, uint256 _amount, uint32 _minGasLimit, bytes _extraData)", + "function bridgeERC20To(address _localToken, address _remoteToken, address _to, uint256 _amount, uint32 _minGasLimit, bytes _extraData)", + "function bridgeETH(uint32 _minGasLimit, bytes _extraData) payable", + "function bridgeETHTo(address _to, uint32 _minGasLimit, bytes _extraData) payable", + "function bridgeETHToWETH(address _localWeth, address _remoteToken, uint256 _amount, uint32 _minGasLimit, bytes _extraData) payable", + "function bridgeETHToWETHTo(address _localWeth, address _remoteToken, address _to, uint256 _amount, uint32 _minGasLimit, bytes _extraData) payable", + "function depositERC20(address _l1Token, address _l2Token, uint256 _amount, uint32 _minGasLimit, bytes _extraData)", + "function depositERC20To(address _l1Token, address _l2Token, address _to, uint256 _amount, uint32 _minGasLimit, bytes _extraData)", + "function depositETH(uint32 _minGasLimit, bytes _extraData) payable", + "function depositETHTo(address _to, uint32 _minGasLimit, bytes _extraData) payable", + "function deposits(address, address) view returns (uint256)", + "function finalizeBridgeERC20(address _localToken, address _remoteToken, address _from, address _to, uint256 _amount, bytes _extraData)", + "function finalizeBridgeERC20Replayable(bytes32 _depositId, address _localToken, address _remoteToken, address _from, address _to, uint256 _amount, bytes _extraData)", + "function finalizeBridgeETH(address _from, address _to, uint256 _amount, bytes _extraData) payable", + "function finalizeERC20Withdrawal(address _l1Token, address _l2Token, address _from, address _to, uint256 _amount, bytes _extraData)", + "function finalizeETHWithdrawal(address _from, address _to, uint256 _amount, bytes _extraData) payable", + "function getDepositHash(bytes32 _depositId) view returns (bytes32)", + "function getFinalizedDeposit(bytes32 _depositId) view returns (bool)", + "function initialize(address _messenger, address _superchainConfig, address _systemConfig, address _otherBridge)", + "function l2TokenBridge() view returns (address)", + "function messenger() view returns (address)", + "function otherBridge() view returns (address)", + "function paused() view returns (bool)", + "function replayERC20Deposit(bytes32 _depositId, address _l1Token, address _l2Token, address _from, address _to, uint256 _amount, bytes _extraData)", + "function superchainConfig() view returns (address)", + "function systemConfig() view returns (address)", + "function version() view returns (string)", + "function weth() view returns (address)" + ], + "0x8649Db4A287413567E8dc0EBe1dd62ee02B71eDD": [ + "constructor(address _admin)", + "event AdminChanged(address previousAdmin, address newAdmin)", + "event Upgraded(address indexed implementation)", + "function admin() returns (address)", + "function changeAdmin(address _admin)", + "function implementation() returns (address)", + "function upgradeTo(address _implementation)", + "function upgradeToAndCall(address _implementation, bytes _data) payable returns (bytes)" + ], + "0x8F75466D69a52EF53C7363F38834bEfC027A2909": [ + "constructor(address _owner)", + "function getImplementation() returns (address)", + "function getOwner() returns (address)", + "function setCode(bytes _code)", + "function setOwner(address _owner)", + "function setStorage(bytes32 _key, bytes32 _value)" + ], + "0xa1233c2DB638D41893a101B0e9dd44cb681270E8": [ + "constructor(address _addressManager, string _implementationName)" + ], + "0xa711dC056C2Db08ee2Ba89be3ED504f48Bf4dbfA": [ + "constructor()", + "event FailedRelayedMessage(bytes32 indexed msgHash)", + "event Initialized(uint8 version)", + "event RelayedMessage(bytes32 indexed msgHash)", + "event SentMessage(address indexed target, address sender, bytes message, uint256 messageNonce, uint256 gasLimit)", + "event SentMessageExtension1(address indexed sender, uint256 value)", + "function MESSAGE_VERSION() view returns (uint16)", + "function MIN_GAS_CALLDATA_OVERHEAD() view returns (uint64)", + "function MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR() view returns (uint64)", + "function MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR() view returns (uint64)", + "function OTHER_MESSENGER() view returns (address)", + "function PORTAL() view returns (address)", + "function RELAY_CALL_OVERHEAD() view returns (uint64)", + "function RELAY_CONSTANT_OVERHEAD() view returns (uint64)", + "function RELAY_GAS_CHECK_BUFFER() view returns (uint64)", + "function RELAY_RESERVED_GAS() view returns (uint64)", + "function baseGas(bytes _message, uint32 _minGasLimit) pure returns (uint64)", + "function failedMessages(bytes32) view returns (bool)", + "function initialize(address _superchainConfig, address _portal, address _systemConfig, address _otherMessenger)", + "function messageNonce() view returns (uint256)", + "function otherMessenger() view returns (address)", + "function paused() view returns (bool)", + "function portal() view returns (address)", + "function relayMessage(uint256 _nonce, address _sender, address _target, uint256 _value, uint256 _minGasLimit, bytes _message) payable", + "function sendMessage(address _target, bytes _message, uint32 _minGasLimit) payable", + "function successfulMessages(bytes32) view returns (bool)", + "function superchainConfig() view returns (address)", + "function systemConfig() view returns (address)", + "function version() view returns (string)", + "function xDomainMessageSender() view returns (address)" + ], + "0xb2B01DeCb6cd36E7396b78D3744482627F22C525": [ + "constructor(address _singleton)" + ], + "0xC1E935F25f9c1198200ec442c6F02f1A2F04534e": [ + "constructor(address _admin)", + "event AdminChanged(address previousAdmin, address newAdmin)", + "event Upgraded(address indexed implementation)", + "function admin() returns (address)", + "function changeAdmin(address _admin)", + "function implementation() returns (address)", + "function upgradeTo(address _implementation)", + "function upgradeToAndCall(address _implementation, bytes _data) payable returns (bytes)" + ], + "0xC9F2d55C56Ef9fE4262c4d5b48d8032241AF4d25": [ + "constructor()", + "function sendFacetTransaction(bytes to, uint256 value, uint256 gasLimit, bytes data)" + ], + "0xD1e4cf142fDf7688A9f7734A5eE74d079696C5A6": [ + "constructor(address _admin)", + "event AdminChanged(address previousAdmin, address newAdmin)", + "event Upgraded(address indexed implementation)", + "function admin() returns (address)", + "function changeAdmin(address _admin)", + "function implementation() returns (address)", + "function upgradeTo(address _implementation)", + "function upgradeToAndCall(address _implementation, bytes _data) payable returns (bytes)" + ], + "0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552": [ + "constructor()", + "event AddedOwner(address owner)", + "event ApproveHash(bytes32 indexed approvedHash, address indexed owner)", + "event ChangedFallbackHandler(address handler)", + "event ChangedGuard(address guard)", + "event ChangedThreshold(uint256 threshold)", + "event DisabledModule(address module)", + "event EnabledModule(address module)", + "event ExecutionFailure(bytes32 txHash, uint256 payment)", + "event ExecutionFromModuleFailure(address indexed module)", + "event ExecutionFromModuleSuccess(address indexed module)", + "event ExecutionSuccess(bytes32 txHash, uint256 payment)", + "event RemovedOwner(address owner)", + "event SafeReceived(address indexed sender, uint256 value)", + "event SafeSetup(address indexed initiator, address[] owners, uint256 threshold, address initializer, address fallbackHandler)", + "event SignMsg(bytes32 indexed msgHash)", + "function VERSION() view returns (string)", + "function addOwnerWithThreshold(address owner, uint256 _threshold)", + "function approveHash(bytes32 hashToApprove)", + "function approvedHashes(address, bytes32) view returns (uint256)", + "function changeThreshold(uint256 _threshold)", + "function checkNSignatures(bytes32 dataHash, bytes data, bytes signatures, uint256 requiredSignatures) view", + "function checkSignatures(bytes32 dataHash, bytes data, bytes signatures) view", + "function disableModule(address prevModule, address module)", + "function domainSeparator() view returns (bytes32)", + "function enableModule(address module)", + "function encodeTransactionData(address to, uint256 value, bytes data, uint8 operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address refundReceiver, uint256 _nonce) view returns (bytes)", + "function execTransaction(address to, uint256 value, bytes data, uint8 operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address refundReceiver, bytes signatures) payable returns (bool success)", + "function execTransactionFromModule(address to, uint256 value, bytes data, uint8 operation) returns (bool success)", + "function execTransactionFromModuleReturnData(address to, uint256 value, bytes data, uint8 operation) returns (bool success, bytes returnData)", + "function getChainId() view returns (uint256)", + "function getModulesPaginated(address start, uint256 pageSize) view returns (address[] array, address next)", + "function getOwners() view returns (address[])", + "function getStorageAt(uint256 offset, uint256 length) view returns (bytes)", + "function getThreshold() view returns (uint256)", + "function getTransactionHash(address to, uint256 value, bytes data, uint8 operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address refundReceiver, uint256 _nonce) view returns (bytes32)", + "function isModuleEnabled(address module) view returns (bool)", + "function isOwner(address owner) view returns (bool)", + "function nonce() view returns (uint256)", + "function removeOwner(address prevOwner, address owner, uint256 _threshold)", + "function requiredTxGas(address to, uint256 value, bytes data, uint8 operation) returns (uint256)", + "function setFallbackHandler(address handler)", + "function setGuard(address guard)", + "function setup(address[] _owners, uint256 _threshold, address to, bytes data, address fallbackHandler, address paymentToken, uint256 payment, address paymentReceiver)", + "function signedMessages(bytes32) view returns (uint256)", + "function simulateAndRevert(address targetContract, bytes calldataPayload)", + "function swapOwner(address prevOwner, address oldOwner, address newOwner)" + ], + "0xDB866fD9241cd32851Df760c1Ec536f3199B22cE": [ + "function createEthscription(address to, string dataURI)", + "function ethscriptionsProxyAddress() view returns (address)", + "function transferEthscription(address to, bytes32 ethscriptionId)" + ], + "0xe2A3bda6CD571943DD4224d0B8872e221EB5997C": [ + "constructor(address _owner)", + "event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)", + "function addressManager() view returns (address)", + "function changeProxyAdmin(address _proxy, address _newAdmin)", + "function getProxyAdmin(address _proxy) view returns (address)", + "function getProxyImplementation(address _proxy) view returns (address)", + "function implementationName(address) view returns (string)", + "function isUpgrading() view returns (bool)", + "function owner() view returns (address)", + "function proxyType(address) view returns (uint8)", + "function renounceOwnership()", + "function setAddress(string _name, address _address)", + "function setAddressManager(address _address)", + "function setImplementationName(address _address, string _name)", + "function setProxyType(address _address, uint8 _type)", + "function setUpgrading(bool _upgrading)", + "function transferOwnership(address newOwner)", + "function upgrade(address _proxy, address _implementation)", + "function upgradeAndCall(address _proxy, address _implementation, bytes _data) payable" + ], + "0xec3a1bd0B6d435Fe8A6e0de728AE87229176EA59": [ + "constructor(address _admin)", + "event AdminChanged(address previousAdmin, address newAdmin)", + "event Upgraded(address indexed implementation)", + "function admin() returns (address)", + "function changeAdmin(address _admin)", + "function implementation() returns (address)", + "function upgradeTo(address _implementation)", + "function upgradeToAndCall(address _implementation, bytes _data) payable returns (bytes)" + ], + "0xEcc75A86c91D857C6Cb8ea991F88eaE40819C6AC": [ + "constructor()", + "event Initialized(uint8 version)", + "event OutputProposed(bytes32 indexed outputRoot, uint256 indexed l2OutputIndex, uint256 indexed l2BlockNumber, uint256 l1Timestamp)", + "event OutputsDeleted(uint256 indexed prevNextOutputIndex, uint256 indexed newNextOutputIndex)", + "function CHALLENGER() view returns (address)", + "function FINALIZATION_PERIOD_SECONDS() view returns (uint256)", + "function L2_BLOCK_TIME() view returns (uint256)", + "function PROPOSER() view returns (address)", + "function SUBMISSION_INTERVAL() view returns (uint256)", + "function challenger() view returns (address)", + "function computeL2Timestamp(uint256 _l2BlockNumber) view returns (uint256)", + "function deleteL2Outputs(uint256 _l2OutputIndex)", + "function finalizationPeriodSeconds() view returns (uint256)", + "function getL2Output(uint256 _l2OutputIndex) view returns (tuple(bytes32 outputRoot, uint128 timestamp, uint128 l2BlockNumber))", + "function getL2OutputAfter(uint256 _l2BlockNumber) view returns (tuple(bytes32 outputRoot, uint128 timestamp, uint128 l2BlockNumber))", + "function getL2OutputIndexAfter(uint256 _l2BlockNumber) view returns (uint256)", + "function initialize(uint256 _submissionInterval, uint256 _l2BlockTime, uint256 _startingBlockNumber, uint256 _startingTimestamp, address _proposer, address _challenger, uint256 _finalizationPeriodSeconds)", + "function l2BlockTime() view returns (uint256)", + "function latestBlockNumber() view returns (uint256)", + "function latestOutputIndex() view returns (uint256)", + "function nextBlockNumber() view returns (uint256)", + "function nextOutputIndex() view returns (uint256)", + "function proposeL2Output(bytes32 _outputRoot, uint256 _l2BlockNumber, bytes32 _l1BlockHash, uint256 _l1BlockNumber) payable", + "function proposer() view returns (address)", + "function startingBlockNumber() view returns (uint256)", + "function startingTimestamp() view returns (uint256)", + "function submissionInterval() view returns (uint256)", + "function version() view returns (string)" + ], + "0xeEd444Fc821b866b002f30f502C53e88E15d5095": [ + "constructor()", + "event ethscriptions_protocol_CreateEthscription(address indexed initialOwner, string contentURI)", + "event ethscriptions_protocol_TransferEthscription(address indexed recipient, bytes32 indexed ethscriptionId)", + "function createEthscription(address to, string dataURI)", + "function transferEthscription(address to, bytes32 ethscriptionId)" + ] + }, + "usedTemplates": { + "facet/EthscriptionsSafeModule": "0x4d91495f0f82b0e93af7667f9888370d7b8b7f4610063109e310ce018943191d", + "facet/EthscriptionsSafeProxy": "0xdd908d7cd9802d6ed1e598dee4c013ba63bf160a38ffe1d6cba039f25a9ebab1", + "facet/FacetEtherBridge": "0xb6c88e1fb1992a6e5cc20e1f74936e15445e45a4422382c93b7f7d2348879927", + "facet/FacetSafeModule": "0x332ff2c5196fdfff3ca60e253f5dc23ae989cb0664f94c9f150a1ed4050ced9f", + "facet/FacetSafeProxy": "0xd70caa2730c5629d514032913fceb9cd841104534024b32bbea98b1b7b190005", + "global/ProxyAdmin": "0x171ea6062ecb94d6e7913ecd058660d724f0bcc40120cd2c11b836e5c0450091", + "GnosisSafe": "0x18527e82c3800311291da7323caa876f588cde67692c1c84d5ecb5161da61359", + "opstack/AddressManager": "0x10c898265c0f6d0de6612e994bd41456f435196949f9bc6069e03da9aa8bb9ba", + "opstack/L1CrossDomainMessenger": "0xcf3e4600ef72e34e18a7c977b796d1f5a5d7878a503dd52cefad07854366a764", + "opstack/L1StandardBridge_facet": "0xba26e87e9bc779ace0e829eff0f94bbbffcc5b678a2290d8612652d4396b9589", + "opstack/L2OutputOracle": "0x2da233df369518b2564031e603ae272763f51cec1bc780ab0a814e46046b47a2", + "opstack/OptimismPortal": "0x5fb419891d613086f39879ae360e765fdb6d154ef5ea5b2f5f98a4443a1b4839", + "opstack/SuperchainConfigFake": "0x49e6353b6089be214c9fc1f021124f840323a29f519b5240594dd16c3a775ca8", + "opstack/SystemConfig_facet": "0xfe79564f7d10c698767a9e1a16b29def47d8fc49596cc362eeb1523dc5814f87" + } +} diff --git a/packages/config/src/common/riskView.ts b/packages/config/src/common/riskView.ts index bbc284ad80d..2a3b33bff20 100644 --- a/packages/config/src/common/riskView.ts +++ b/packages/config/src/common/riskView.ts @@ -352,6 +352,13 @@ export function SEQUENCER_SELF_SEQUENCE( } } +const SEQUENCER_SELF_SEQUENCE_NO_SEQUENCER: ScalingProjectRiskViewEntry = { + value: 'Self sequence', + description: + 'Users can self sequence transactions by sending them on L1. There is no privileged operator.', + sentiment: 'good', +} + export function SEQUENCER_SELF_SEQUENCE_ZK( delay?: number, ): ScalingProjectRiskViewEntry { @@ -676,6 +683,7 @@ export const RISK_VIEW = { // sequencerFailure SEQUENCER_SELF_SEQUENCE, SEQUENCER_SELF_SEQUENCE_ZK, + SEQUENCER_SELF_SEQUENCE_NO_SEQUENCER, SEQUENCER_FORCE_VIA_L1, SEQUENCER_FORCE_VIA_L1_STARKEX_PERPETUAL, SEQUENCER_FORCE_VIA_L1_LOOPRING, diff --git a/packages/config/src/projects/layer2s/facet.ts b/packages/config/src/projects/layer2s/facet.ts new file mode 100644 index 00000000000..ce775ac6779 --- /dev/null +++ b/packages/config/src/projects/layer2s/facet.ts @@ -0,0 +1,89 @@ +import { UnixTime } from '@l2beat/shared-pure/build/types/UnixTime' +import { OPERATOR, RISK_VIEW } from '../../common' +import { REASON_FOR_BEING_OTHER } from '../../common/ReasonForBeingInOther' +import { ProjectDiscovery } from '../../discovery/ProjectDiscovery' +import { Badge } from '../badges' +import { opStackL2 } from './templates/opStack' +import { Layer2 } from './types' + +const discovery = new ProjectDiscovery('facet') +const FINALIZATION_PERIOD_SECONDS: number = discovery.getContractValue( + 'L2OutputOracle', + 'FINALIZATION_PERIOD_SECONDS', +) + +export const facet: Layer2 = opStackL2({ + createdAt: new UnixTime(1735889012), // 2025-01-03T01:36:52Z + discovery, + additionalBadges: [Badge.Other.BasedSequencing], + display: { + category: 'Other', + reasonsForBeingOther: [REASON_FOR_BEING_OTHER.NO_PROOFS], + name: 'Facet', + slug: 'facet', + description: + 'Facet is a based rollup built on the OP stack. It uses FCT as its native gas token, which is mintable by spending gas on L1.', + links: { + websites: ['https://facet.org/'], + apps: ['https://facetswap.com/bridge'], + documentation: ['https://docs.facet.org/docs/facet-network/intro'], + explorers: ['https://explorer.facet.org/'], + repositories: ['https://github.com/0xFacet'], + socialMedia: [ + 'https://x.com/0xFacet', + 'https://discord.com/invite/facet', + ], + }, + activityDataSource: 'Blockchain RPC', + }, + riskView: { + stateValidation: RISK_VIEW.STATE_NONE, + dataAvailability: RISK_VIEW.DATA_ON_CHAIN, + exitWindow: RISK_VIEW.EXIT_WINDOW(0, FINALIZATION_PERIOD_SECONDS), + sequencerFailure: RISK_VIEW.SEQUENCER_SELF_SEQUENCE_NO_SEQUENCER, + proposerFailure: RISK_VIEW.PROPOSER_CANNOT_WITHDRAW, + }, + nonTemplateTechnology: { + operator: OPERATOR.DECENTRALIZED_OPERATOR, + exitMechanisms: [ + { + name: 'Withdrawals are initiated on L1', + description: + 'Users can initiate a withdrawal from the L1StandardBridge escrow by sending a transaction to the L1 contract, forcing the operator to either process it, halt all withdrawals or produce an invalid state transition. Deposits from the L1StandardBridge are disabled, and the use of the fast bridge is encouraged. There is no way to force the fast bridge operator (EOA) to process a withdrawal.', + references: [ + { + text: 'PausedL1StandardBridge.sol - Etherscan source code, disabled _initiateBridgeERC20 function', + href: 'https://etherscan.io/address//0x8F75466D69a52EF53C7363F38834bEfC027A2909#code', + }, + ], + risks: [ + { + category: 'Funds can be lost if', + text: 'the fast bridge EOA operator signs an invalid withdrawal.', + isCritical: true, + }, + { + category: 'Funds can be frozen if', + text: 'the operator halts withdrawals.', + isCritical: true, + }, + ], + }, + ], + }, + architectureImage: 'facet', + rpcUrl: 'https://mainnet.facet.org/', + genesisTimestamp: new UnixTime(1733855495), + milestones: [ + { + name: 'Facet Mainnet Launch', + link: 'https://x.com/0xFacet/status/1866610169620336761', + date: '2024-12-10T00:00:00Z', + description: 'Facet launches at Ethereum block 21373000.', + type: 'general', + }, + ], + discoveryDrivenData: true, + usesBlobs: false, // uses calldata + isNodeAvailable: 'UnderReview', +}) diff --git a/packages/config/src/projects/layer2s/index.ts b/packages/config/src/projects/layer2s/index.ts index 81845f6b62d..60d2f9fe044 100644 --- a/packages/config/src/projects/layer2s/index.ts +++ b/packages/config/src/projects/layer2s/index.ts @@ -43,6 +43,7 @@ import { eclipse } from './eclipse' import { edgeless } from './edgeless' import { ethernity } from './ethernity' import { everclear } from './everclear' +import { facet } from './facet' import { fhenix } from './fhenix' import { fluence } from './fluence' import { fluent } from './fluent' @@ -233,6 +234,7 @@ export const layer2s: Layer2[] = [ edgeless, ethernity, everclear, + facet, fhenix, fluence, fluent, diff --git a/packages/config/src/projects/layer2s/templates/opStack.ts b/packages/config/src/projects/layer2s/templates/opStack.ts index 144fc4f97bb..8445f235607 100644 --- a/packages/config/src/projects/layer2s/templates/opStack.ts +++ b/packages/config/src/projects/layer2s/templates/opStack.ts @@ -95,6 +95,7 @@ interface DAProvider { } interface OpStackConfigCommon { + architectureImage?: string isArchived?: true createdAt: UnixTime daProvider?: DAProvider @@ -138,6 +139,7 @@ interface OpStackConfigCommon { additionalBadges?: BadgeId[] discoveryDrivenData?: boolean additionalPurposes?: ScalingProjectPurpose[] + riskView?: ScalingProjectRiskView } export interface OpStackConfigL2 extends OpStackConfigCommon { @@ -171,9 +173,12 @@ function opStackCommon( templateVars.discovery.getContractValue('SystemConfig', 'sequencerInbox'), ) - const postsToCelestia = templateVars.discovery.getContractValue<{ - isSomeTxsLengthEqualToCelestiaDAExample: boolean - }>('SystemConfig', 'opStackDA').isSomeTxsLengthEqualToCelestiaDAExample + // if usesBlobs is set to false at this point it means that it uses calldata + const postsToCelestia = + templateVars.usesBlobs ?? + templateVars.discovery.getContractValue<{ + isSomeTxsLengthEqualToCelestiaDAExample: boolean + }>('SystemConfig', 'opStackDA').isSomeTxsLengthEqualToCelestiaDAExample const daProvider = templateVars.daProvider ?? (postsToCelestia ? CELESTIA_DA_PROVIDER : undefined) @@ -429,19 +434,22 @@ export function opStackL2(templateVars: OpStackConfigL2): Layer2 { upgradeDelay: 'No delay', } - const usesBlobs = + // if usesBlobs is set to false at this point it means that it uses calldata + const postsToCelestia = templateVars.usesBlobs ?? templateVars.discovery.getContractValue<{ - isSequencerSendingBlobTx: boolean - }>('SystemConfig', 'opStackDA').isSequencerSendingBlobTx - - const postsToCelestia = templateVars.discovery.getContractValue<{ - isSomeTxsLengthEqualToCelestiaDAExample: boolean - }>('SystemConfig', 'opStackDA').isSomeTxsLengthEqualToCelestiaDAExample + isSomeTxsLengthEqualToCelestiaDAExample: boolean + }>('SystemConfig', 'opStackDA').isSomeTxsLengthEqualToCelestiaDAExample const daProvider = templateVars.daProvider ?? (postsToCelestia ? CELESTIA_DA_PROVIDER : undefined) + const usesBlobs = + templateVars.usesBlobs ?? + templateVars.discovery.getContractValue<{ + isSequencerSendingBlobTx: boolean + }>('SystemConfig', 'opStackDA').isSequencerSendingBlobTx + if (daProvider === undefined) { assert( templateVars.isNodeAvailable !== undefined, @@ -464,7 +472,7 @@ export function opStackL2(templateVars: OpStackConfigL2): Layer2 { ...opStackCommon(templateVars), display: { purposes: ['Universal', ...(templateVars.additionalPurposes ?? [])], - architectureImage, + architectureImage: templateVars.architectureImage ?? architectureImage, ...templateVars.display, provider: 'OP Stack', category: @@ -589,7 +597,7 @@ export function opStackL2(templateVars: OpStackConfigL2): Layer2 { mode: DA_MODES.TRANSACTION_DATA_COMPRESSED, }), ], - riskView: { + riskView: templateVars.riskView ?? { stateValidation: { ...RISK_VIEW.STATE_NONE, secondLine: formatChallengePeriod(FINALIZATION_PERIOD_SECONDS), diff --git a/packages/config/src/test/snapshots/facet.riskView.snapshot b/packages/config/src/test/snapshots/facet.riskView.snapshot new file mode 100644 index 00000000000..2d1fc1ad69e --- /dev/null +++ b/packages/config/src/test/snapshots/facet.riskView.snapshot @@ -0,0 +1,34 @@ +// facet riskView didn't change 1 + +{ + dataAvailability: { + definingMetric: Infinity + description: "All of the data needed for proof construction is published on Ethereum L1." + sentiment: "good" + value: "Onchain" + } + exitWindow: { + definingMetric: -12 + description: "There is no window for users to exit in case of an unwanted regular upgrade since contracts are instantly upgradable." + secondLine: undefined + sentiment: "bad" + value: "None" + } + proposerFailure: { + definingMetric: -Infinity + description: "Only the whitelisted proposers can publish state roots on L1, so in the event of failure the withdrawals are frozen." + sentiment: "bad" + value: "Cannot withdraw" + } + sequencerFailure: { + description: "Users can self sequence transactions by sending them on L1. There is no privileged operator." + sentiment: "good" + value: "Self sequence" + } + stateValidation: { + definingMetric: -Infinity + description: "Currently the system permits invalid state roots. More details in project overview." + sentiment: "bad" + value: "None" + } +} diff --git a/packages/config/src/verification/ethereum/manuallyVerified.jsonc b/packages/config/src/verification/ethereum/manuallyVerified.jsonc index 7a4c5344a2a..387d31ffa91 100644 --- a/packages/config/src/verification/ethereum/manuallyVerified.jsonc +++ b/packages/config/src/verification/ethereum/manuallyVerified.jsonc @@ -11,5 +11,6 @@ "0x03a72B00D036C479105fF98A1953b15d9c510110": "https://circuit-release.s3.us-west-2.amazonaws.com/release-v0.11.4/evm_verifier.yul", // Scroll's PlonkVerifierV1-1 (blobs, Curie upgrade) written in Yul+ "0x8759E83b6570A0bA46c3CE7eB359F354F816c9a9": "https://github.com/scroll-tech/scroll-prover/blob/main/release-v0.12.0/evm_verifier.yul", // Scroll's PlonkVerifierV2 (bundles, Darwin upgrade) written in Yul+ "0x8c1b52757b5c571ADcB5572E992679d4D48e30f7": "https://github.com/scroll-tech/scroll-prover/blob/main/release-v0.13.0/evm_verifier.yul", // Scroll's PlonkVerifierV2-1 (bundles, DarwinV2 upgrade) written in Yul+ - "0x8FafAE7Dd957044088b3d0F67359C327c6200d18": "https://docs.layerzero.network/v2/developers/evm/technical-reference/dvn-addresses" // Stargate Verifier, can be abstracted as a single signer in the LayerZero message protocol + "0x8FafAE7Dd957044088b3d0F67359C327c6200d18": "https://docs.layerzero.network/v2/developers/evm/technical-reference/dvn-addresses", // Stargate Verifier, can be abstracted as a single signer in the LayerZero message protocol + "0x0000000000000b07ED001607f5263D85bf28Ce4C": "https://github.com/vectorized/solady/blob/main/src/utils/ERC1967Factory.sol" // Facet proxy deployed through ERC1967 factory by Solady } diff --git a/packages/frontend/public/icons/facet.png b/packages/frontend/public/icons/facet.png new file mode 100644 index 00000000000..75d3fa4f7a8 Binary files /dev/null and b/packages/frontend/public/icons/facet.png differ diff --git a/packages/frontend/public/images/architecture/facet.png b/packages/frontend/public/images/architecture/facet.png new file mode 100644 index 00000000000..3a19c1655c8 Binary files /dev/null and b/packages/frontend/public/images/architecture/facet.png differ diff --git a/packages/frontend/scripts/logos/tinifiedLogos.json b/packages/frontend/scripts/logos/tinifiedLogos.json index bfcb4cb8eab..4ac281875c1 100644 --- a/packages/frontend/scripts/logos/tinifiedLogos.json +++ b/packages/frontend/scripts/logos/tinifiedLogos.json @@ -302,5 +302,6 @@ "solo.png": "bbc3c6d8a6ed21ca9dea3918a16e0e82", "geist.png": "dc33ce729479f169d42f10c17087dd3c", "pandasea.png": "f35ae333724095a021edeac7b389cb70", - "hyperliquid.png": "99c5fe0d00ec10b0719c3ec91cf50213" + "hyperliquid.png": "99c5fe0d00ec10b0719c3ec91cf50213", + "facet.png": "d672f59e98ade64b954d4c5504dc8427" }