From a7135f66a99ddceacf5a7a973e400be8207b4ee5 Mon Sep 17 00:00:00 2001 From: 0xfoobar <83792390+0xfoobar@users.noreply.github.com> Date: Thu, 21 Sep 2023 09:59:10 -0400 Subject: [PATCH] C4 audit fixes and mainnet deployment (#63) C4 audit fixes and mainnet deployment to 0x00000000000000447e69651d841bD8D104Bed493 --- foundry.toml | 6 +- gasbenchmark10mil | 24 ++- hashbenchmark10mil | 12 +- remappings.txt | 4 +- script/Deploy.s.sol | 15 +- src/DelegateRegistry.sol | 10 +- src/IDelegateRegistry.sol | 4 +- src/examples/IPLicenseCheck.sol | 2 +- src/libraries/RegistryHashes.sol | 234 ++++++++++++++--------------- src/libraries/RegistryStorage.sol | 8 +- src/singlesig/Singlesig.sol | 57 ++++--- test/DelegateRegistry.t.sol | 2 +- test/InitCodeHash.t.sol | 36 +++++ test/examples/IPLicenseCheck.t.sol | 2 +- 14 files changed, 245 insertions(+), 171 deletions(-) create mode 100644 test/InitCodeHash.t.sol diff --git a/foundry.toml b/foundry.toml index aeff742..b126fb9 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,6 +2,10 @@ src = 'src' out = 'out' libs = ['lib'] + +auto_detect_remappings = false +# remappings = [] + solc_version = "0.8.21" # EVM version must be Paris not Shanghai to prevent PUSH0 incompatibility with other EVM chains # Extra 137 deployment size, extra 0.1% runtime gas costs from using older version @@ -18,4 +22,4 @@ via_ir = false line_length = 180 wrap_comments = true # Increases readability of comments -# See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options \ No newline at end of file diff --git a/gasbenchmark10mil b/gasbenchmark10mil index c12eea7..7274bdb 100644 --- a/gasbenchmark10mil +++ b/gasbenchmark10mil @@ -1,16 +1,26 @@ +No files changed, compilation skipped + +Running 1 test for test/GasBenchmark.t.sol:GasBenchmark +[PASS] testGas(address,bytes32) (runs: 256, μ: 13573356, ~: 13573452) +Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 247.64ms | src/DelegateRegistry.sol:DelegateRegistry contract | | | | | | |----------------------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | -| 1995913 | 10001 | | | | | +| 2011327 | 10078 | | | | | | Function Name | min | avg | median | max | # calls | -| checkDelegateForAll | 2910 | 3106 | 3106 | 3303 | 2 | -| checkDelegateForContract | 5399 | 5807 | 5807 | 6216 | 2 | -| checkDelegateForERC1155 | 7840 | 8458 | 8458 | 9077 | 2 | -| checkDelegateForERC20 | 7790 | 8402 | 8402 | 9014 | 2 | -| checkDelegateForERC721 | 7883 | 8515 | 8515 | 9148 | 2 | +| checkDelegateForAll | 3002 | 3198 | 3198 | 3395 | 2 | +| checkDelegateForContract | 5491 | 5899 | 5899 | 6308 | 2 | +| checkDelegateForERC1155 | 7932 | 8550 | 8550 | 9169 | 2 | +| checkDelegateForERC20 | 7882 | 8494 | 8494 | 9106 | 2 | +| checkDelegateForERC721 | 7975 | 8607 | 8607 | 9240 | 2 | | delegateAll | 135825 | 135825 | 135825 | 135825 | 2 | | delegateContract | 114433 | 125383 | 125383 | 136333 | 2 | | delegateERC1155 | 5710 | 93282 | 93282 | 180854 | 2 | | delegateERC20 | 5357 | 81865 | 81865 | 158374 | 2 | | delegateERC721 | 136921 | 147871 | 147871 | 158821 | 2 | -| multicall | 404294 | 404294 | 404294 | 404294 | 1 | \ No newline at end of file +| multicall | 404294 | 404294 | 404294 | 404294 | 1 | + + + + +Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests) diff --git a/hashbenchmark10mil b/hashbenchmark10mil index 2ab4ee6..4b54e1b 100644 --- a/hashbenchmark10mil +++ b/hashbenchmark10mil @@ -1,3 +1,8 @@ +No files changed, compilation skipped + +Running 1 test for test/HashBenchmark.t.sol:HashBenchmark +[PASS] testHashGas(address,bytes32,address,uint256,address,bytes32) (runs: 256, μ: 19906, ~: 19906) +Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 32.07ms | test/HashBenchmark.t.sol:HashHarness contract | | | | | | |-----------------------------------------------|-----------------|-----|--------|-----|---------| | Deployment Cost | Deployment Size | | | | | @@ -14,4 +19,9 @@ | erc20Location | 793 | 793 | 793 | 793 | 1 | | erc721Hash | 830 | 830 | 830 | 830 | 1 | | erc721Location | 867 | 867 | 867 | 867 | 1 | -| location | 384 | 384 | 384 | 384 | 1 | \ No newline at end of file +| location | 384 | 384 | 384 | 384 | 1 | + + + + +Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests) diff --git a/remappings.txt b/remappings.txt index a530ee7..16e2a3c 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,6 +1,4 @@ ds-test/=lib/forge-std/lib/ds-test/src/ -erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/ forge-std/=lib/forge-std/src/ murky/=lib/murky/src/ -openzeppelin-contracts/=lib/openzeppelin-contracts/ -openzeppelin/=lib/openzeppelin-contracts/contracts/ +openzeppelin/=lib/openzeppelin-contracts/contracts/ \ No newline at end of file diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 9beed4a..ece1dfd 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console2} from "forge-std/console2.sol"; import {DelegateRegistry} from "../src/DelegateRegistry.sol"; +import {Singlesig} from "../src/singlesig/Singlesig.sol"; interface ImmutableCreate2Factory { function safeCreate2(bytes32 salt, bytes calldata initCode) external payable returns (address deploymentAddress); @@ -14,15 +15,27 @@ interface ImmutableCreate2Factory { contract Deploy is Script { ImmutableCreate2Factory immutable factory = ImmutableCreate2Factory(0x0000000000FFe8B47B3e2130213B802212439497); bytes initCode = type(DelegateRegistry).creationCode; - bytes32 salt = 0x00000000000000000000000000000000000000008b99e5a778edb02572010000; + // bytes32 salt = 0x0000000000000000000000000000000000000000fbe49ecfc3decb1164228b89; + bytes32 salt = 0x00000000000000000000000000000000000000002bbc593dd77cb93fbb932d5f; + + // bytes initCode = abi.encodePacked(type(Singlesig).creationCode, abi.encode(address(0x6Ed7D526b020780f694f3c10Dfb25E1b134D3215))); + // bytes32 salt = 0x000000000000000000000000000000000000000016c7768a8c7a2824b846321d; function run() external { vm.startBroadcast(); + // address singlesigAddress = factory.safeCreate2(salt, initCode); + // Singlesig singlesig = Singlesig(payable(singlesigAddress)); + // console2.log(address(singlesig)); + address registryAddress = factory.safeCreate2(salt, initCode); DelegateRegistry registry = DelegateRegistry(registryAddress); console2.log(address(registry)); + // address registryAddress = factory.safeCreate2(salt, initCode); + // DelegateRegistry registry = DelegateRegistry(registryAddress); + // console2.log(address(registry)); + vm.stopBroadcast(); } } diff --git a/src/DelegateRegistry.sol b/src/DelegateRegistry.sol index 26b1986..8cf0a00 100644 --- a/src/DelegateRegistry.sol +++ b/src/DelegateRegistry.sol @@ -117,7 +117,7 @@ contract DelegateRegistry is IDelegateRegistry { } } else if (loadedFrom == msg.sender) { _updateFrom(location, Storage.DELEGATION_REVOKED); - _writeDelegation(location, Storage.POSITIONS_AMOUNT, amount); + _writeDelegation(location, Storage.POSITIONS_AMOUNT, uint256(0)); } emit DelegateERC20(msg.sender, to, contract_, rights, amount); } @@ -149,11 +149,9 @@ contract DelegateRegistry is IDelegateRegistry { /// @dev Transfer native token out function sweep() external { - // TODO: Replace this with correct address at deployment time - // This hardcoded address is a CREATE2 factory counterfactual smart contract wallet that will always accept native token transfers - uint256 sc = uint256(uint160(0x0000000000000000000000000000000000000000)); assembly ("memory-safe") { - let result := call(gas(), sc, selfbalance(), 0, 0, 0, 0) + // This hardcoded address is a CREATE2 factory counterfactual smart contract wallet that will always accept native token transfers + let result := call(gas(), 0x000000dE1E80ea5a234FB5488fee2584251BC7e8, selfbalance(), 0, 0, 0, 0) } } @@ -365,7 +363,7 @@ contract DelegateRegistry is IDelegateRegistry { } } - /// @dev Helper function that writes from whilst preserving the rest of the storage slot + /// @dev Helper function that writes `from` while preserving the rest of the storage slot function _updateFrom(bytes32 location, address from) internal { uint256 firstPacked = Storage.POSITIONS_FIRST_PACKED; uint256 cleanAddress = Storage.CLEAN_ADDRESS; diff --git a/src/IDelegateRegistry.sol b/src/IDelegateRegistry.sol index 68d5478..8c7cb10 100644 --- a/src/IDelegateRegistry.sol +++ b/src/IDelegateRegistry.sol @@ -210,12 +210,12 @@ interface IDelegateRegistry { */ /** - * @notice allows external contract to read arbitrary storage slot + * @notice Allows external contracts to read arbitrary storage slots */ function readSlot(bytes32 location) external view returns (bytes32); /** - * @notice allows external contracts to read an arbitrary array of storage slots + * @notice Allows external contracts to read an arbitrary array of storage slots */ function readSlots(bytes32[] calldata locations) external view returns (bytes32[] memory); } diff --git a/src/examples/IPLicenseCheck.sol b/src/examples/IPLicenseCheck.sol index 1afb460..47fc6b8 100644 --- a/src/examples/IPLicenseCheck.sol +++ b/src/examples/IPLicenseCheck.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.21; -import {IERC721} from "openzeppelin-contracts/contracts/token/ERC721/IERC721.sol"; +import {IERC721} from "openzeppelin/token/ERC721/IERC721.sol"; import {IDelegateRegistry} from "src/IDelegateRegistry.sol"; /** diff --git a/src/libraries/RegistryHashes.sol b/src/libraries/RegistryHashes.sol index 9c8b801..6437df1 100644 --- a/src/libraries/RegistryHashes.sol +++ b/src/libraries/RegistryHashes.sol @@ -6,7 +6,7 @@ import {IDelegateRegistry} from "../IDelegateRegistry.sol"; /** * @title Library for calculating the hashes and storage locations used in the delegate registry * - * The encoding for the 5 types of delegate registry hashes should be as follows + * The encoding for the 5 types of delegate registry hashes should be as follows: * * ALL: keccak256(abi.encodePacked(rights, from, to)) * CONTRACT: keccak256(abi.encodePacked(rights, from, to, contract_)) @@ -14,29 +14,26 @@ import {IDelegateRegistry} from "../IDelegateRegistry.sol"; * ERC20: keccak256(abi.encodePacked(rights, from, to, contract_)) * ERC1155: keccak256(abi.encodePacked(rights, from, to, contract_, tokenId)) * - * To avoid collisions between the hashes with respect to type, the hash is shifted left by one byte and the last byte is then encoded with a unique number for the - * delegation type. + * To avoid collisions between the hashes with respect to type, the hash is shifted left by one byte + * and the last byte is then encoded with a unique number for the delegation type * */ library RegistryHashes { /// @dev Used to delete everything but the last byte of a 32 byte word with and(word, EXTRACT_LAST_BYTE) uint256 internal constant EXTRACT_LAST_BYTE = 0xff; - /// @dev uint256 constant for the delegate registry delegation type enumeration, related unit test should fail if these mismatch + /// @dev Constants for the delegate registry delegation type enumeration uint256 internal constant ALL_TYPE = 1; uint256 internal constant CONTRACT_TYPE = 2; uint256 internal constant ERC721_TYPE = 3; uint256 internal constant ERC20_TYPE = 4; uint256 internal constant ERC1155_TYPE = 5; - /// @dev uint256 constant for the location of the delegations array in the delegate registry, assumed to be zero + /// @dev Constant for the location of the delegations array in the delegate registry, defined to be zero uint256 internal constant DELEGATION_SLOT = 0; /** - * @notice Helper function to decode last byte of a delegation hash to obtain its delegation type - * @param inputHash to decode the type from - * @return decodedType of the delegation - * @dev function itself will not revert if decodedType > type(IDelegateRegistry.DelegationType).max - * @dev may lead to a revert with Conversion into non-existent enum type after the function is called if inputHash was encoded with type outside the DelegationType - * enum range + * @notice Helper function to decode last byte of a delegation hash into its delegation type enum + * @param inputHash The bytehash to decode the type from + * @return decodedType The delegation type */ function decodeType(bytes32 inputHash) internal pure returns (IDelegateRegistry.DelegationType decodedType) { assembly { @@ -46,10 +43,10 @@ library RegistryHashes { /** * @notice Helper function that computes the storage location of a particular delegation array - * @param inputHash is the hash of the delegation - * @return computedLocation is the storage key of the delegation array at position 0 * @dev Storage keys further down the array can be obtained by adding computedLocation with the element position * @dev Follows the solidity storage location encoding for a mapping(bytes32 => fixedArray) at the position of the delegationSlot + * @param inputHash The bytehash to decode the type from + * @return computedLocation is the storage key of the delegation array at position 0 */ function location(bytes32 inputHash) internal pure returns (bytes32 computedLocation) { assembly ("memory-safe") { @@ -61,251 +58,244 @@ library RegistryHashes { } /** - * @notice Helper function to compute delegation hash for all delegation - * @param from is the address making the delegation - * @param rights it the rights specified by the delegation - * @param to is the address receiving the delegation - * @return hash of the delegation parameters encoded with ALL_TYPE - * @dev returned hash should be equivalent to keccak256(abi.encodePacked(rights, from, to)) followed by a shift left by 1 byte and writing the delegation type to the - * cleaned last byte - * @dev will not revert if from or to are > uint160, any input larger than uint160 for from and to will be cleaned to their last 20 bytes + * @notice Helper function to compute delegation hash for `DelegationType.ALL` + * @dev Equivalent to `keccak256(abi.encodePacked(rights, from, to))` then left-shift by 1 byte and write the delegation type to the cleaned last byte + * @dev Will not revert if `from` or `to` are > uint160, any input larger than uint160 for `from` and `to` will be cleaned to its lower 20 bytes + * @param from The address making the delegation + * @param rights The rights specified by the delegation + * @param to The address receiving the delegation + * @return hash The delegation parameters encoded with ALL_TYPE */ function allHash(address from, bytes32 rights, address to) internal pure returns (bytes32 hash) { assembly ("memory-safe") { // This block only allocates memory after the free memory pointer let ptr := mload(64) // Load the free memory pointer - // Layout the variables from last to first, agnostic to upper 96 bits of address words. + // Lay out the variables from last to first, agnostic to upper 96 bits of address words. mstore(add(ptr, 40), to) mstore(add(ptr, 20), from) mstore(ptr, rights) - hash := or(shl(8, keccak256(ptr, 72)), ALL_TYPE) // Runs keccak over the packed encoding, shifts left by one byte, then writes the type to the last - // byte + hash := or(shl(8, keccak256(ptr, 72)), ALL_TYPE) // Keccak-hashes the packed encoding, left-shifts by one byte, then writes type to the lowest-order byte } } /** - * @notice Helper function to compute delegation location for all delegation - * @param from is the address making the delegation - * @param rights is the rights specified by the delegation - * @param to is the address receiving the delegation - * @return computedLocation is the storage location of the all delegation with those parameters in the delegations mapping - * @dev gives the same location hash as location(allHash(rights, from, to)) would - * @dev will not revert if from or to are > uint160, any input larger than uint160 for from and to will be cleaned to their last 20 bytes + * @notice Helper function to compute delegation location for `DelegationType.ALL` + * @dev Equivalent to `location(allHash(rights, from, to))` + * @dev Will not revert if `from` or `to` are > uint160, any input larger than uint160 for `from` and `to` will be cleaned to its lower 20 bytes + * @param from The address making the delegation + * @param rights The rights specified by the delegation + * @param to The address receiving the delegation + * @return computedLocation The storage location of the all delegation with those parameters in the delegations mapping */ function allLocation(address from, bytes32 rights, address to) internal pure returns (bytes32 computedLocation) { assembly ("memory-safe") { // This block only allocates memory after the free memory pointer and in the scratch space let ptr := mload(64) // Load the free memory pointer - // Layout the variables from last to first, agnostic to upper 96 bits of address words. + // Lay out the variables from last to first, agnostic to upper 96 bits of address words. mstore(add(ptr, 40), to) mstore(add(ptr, 20), from) mstore(ptr, rights) - mstore(0, or(shl(8, keccak256(ptr, 72)), ALL_TYPE)) // Runs keccak over the packed encoding, shifts left by one byte, then writes the type to the - // last byte, and stores the result in the scratch space + mstore(0, or(shl(8, keccak256(ptr, 72)), ALL_TYPE)) // Computes `allHash`, then stores the result in scratch space mstore(32, DELEGATION_SLOT) computedLocation := keccak256(0, 64) // Runs keccak over the scratch space to obtain the storage key } } /** - * @notice Helper function to compute delegation hash for contract delegation - * @param from is the address making the delegation - * @param rights is the rights specified by the delegation - * @param to is the address receiving the delegation - * @param contract_ is the address of the contract specified by the delegation - * @return hash of the delegation parameters encoded with CONTRACT_TYPE - * @dev returned hash should be equivalent to keccak256(abi.encodePacked(rights, from, to, contract_)) with the last byte overwritten with CONTRACT_TYPE - * @dev will not revert if from, to, or contract_ are > uint160, any input larger than uint160 for from, to, or contract_ will be cleaned to their last 20 bytes + * @notice Helper function to compute delegation hash for `DelegationType.CONTRACT` + * @dev Equivalent to keccak256(abi.encodePacked(rights, from, to, contract_)) left-shifted by 1 then last byte overwritten with CONTRACT_TYPE + * @dev Will not revert if `from`, `to` or `contract_` are > uint160, these inputs will be cleaned to their lower 20 bytes + * @param from The address making the delegation + * @param rights The rights specified by the delegation + * @param to The address receiving the delegation + * @param contract_ The address of the contract specified by the delegation + * @return hash The delegation parameters encoded with CONTRACT_TYPE */ function contractHash(address from, bytes32 rights, address to, address contract_) internal pure returns (bytes32 hash) { assembly ("memory-safe") { // This block only allocates memory after the free memory pointer let ptr := mload(64) // Load the free memory pointer - // Layout the variables from last to first, agnostic to upper 96 bits of address words. + // Lay out the variables from last to first, agnostic to upper 96 bits of address words. mstore(add(ptr, 60), contract_) mstore(add(ptr, 40), to) mstore(add(ptr, 20), from) mstore(ptr, rights) - hash := or(shl(8, keccak256(ptr, 92)), CONTRACT_TYPE) // Runs keccak over the packed encoding, shifts left by one byte, then writes the type to the last byte + hash := or(shl(8, keccak256(ptr, 92)), CONTRACT_TYPE) // Keccak-hashes the packed encoding, left-shifts by one byte, then writes type to the lowest-order byte } } /** - * @notice Helper function to compute delegation location for contract delegation - * @param from is the address making the delegation - * @param rights is the rights specified by the delegation - * @param to is the address receiving the delegation - * @param contract_ is the address of the contract specified by the delegation - * @return computedLocation is the storage location of the contract delegation with those parameters in the delegations mapping - * @dev gives the same location hash as location(contractHash(rights, from, to, contract_)) would - * @dev will not revert if from, to, or contract_ are > uint160, any input larger than uint160 for from, to, or contract_ will be cleaned to their last 20 bytes + * @notice Helper function to compute delegation location for `DelegationType.CONTRACT` + * @dev Equivalent to `location(contractHash(rights, from, to, contract_))` + * @dev Will not revert if `from`, `to` or `contract_` are > uint160, these inputs will be cleaned to their lower 20 bytes + * @param from The address making the delegation + * @param rights The rights specified by the delegation + * @param to The address receiving the delegation + * @param contract_ The address of the contract specified by the delegation + * @return computedLocation The storage location of the contract delegation with those parameters in the delegations mapping */ function contractLocation(address from, bytes32 rights, address to, address contract_) internal pure returns (bytes32 computedLocation) { assembly ("memory-safe") { // This block only allocates memory after the free memory pointer and in the scratch space let ptr := mload(64) // Load free memory pointer - // Layout the variables from last to first, agnostic to upper 96 bits of address words. + // Lay out the variables from last to first, agnostic to upper 96 bits of address words. mstore(add(ptr, 60), contract_) mstore(add(ptr, 40), to) mstore(add(ptr, 20), from) mstore(ptr, rights) - mstore(0, or(shl(8, keccak256(ptr, 92)), CONTRACT_TYPE)) // Runs keccak over the packed encoding, shifts left by one byte, then writes the type to the - // last byte, and stores the result in the scratch space + mstore(0, or(shl(8, keccak256(ptr, 92)), CONTRACT_TYPE)) // Computes `contractHash`, then stores the result in scratch space mstore(32, DELEGATION_SLOT) computedLocation := keccak256(0, 64) // Runs keccak over the scratch space to obtain the storage key } } /** - * @notice Helper function to compute delegation hash for ERC721 delegation - * @param from is the address making the delegation - * @param rights is the rights specified by the delegation - * @param to is the address receiving the delegation - * @param tokenId is the id of the token specified by the delegation - * @param contract_ is the address of the contract specified by the delegation - * @return hash of the parameters encoded with ERC721_TYPE - * @dev returned hash should be equivalent to keccak256(abi.encodePacked(rights, from, to, contract_, tokenId)) with the last byte overwritten with ERC721_TYPE - * @dev will not revert if from, to, or contract_ are > uint160, any input larger than uint160 for from, to, or contract_ will be cleaned to their last 20 bytes + * @notice Helper function to compute delegation hash for `DelegationType.ERC721` + * @dev Equivalent to `keccak256(abi.encodePacked(rights, from, to, contract_, tokenId)) left-shifted by 1 then last byte overwritten with ERC721_TYPE + * @dev Will not revert if `from`, `to` or `contract_` are > uint160, these inputs will be cleaned to their lower 20 bytes + * @param from The address making the delegation + * @param rights The rights specified by the delegation + * @param to The address receiving the delegation + * @param tokenId The id of the token specified by the delegation + * @param contract_ The address of the contract specified by the delegation + * @return hash The delegation parameters encoded with ERC721_TYPE */ function erc721Hash(address from, bytes32 rights, address to, uint256 tokenId, address contract_) internal pure returns (bytes32 hash) { assembly ("memory-safe") { // This block only allocates memory after the free memory pointer let ptr := mload(64) // Cache the free memory pointer. - // Layout the variables from last to first, agnostic to upper 96 bits of address words. + // Lay out the variables from last to first, agnostic to upper 96 bits of address words. mstore(add(ptr, 92), tokenId) mstore(add(ptr, 60), contract_) mstore(add(ptr, 40), to) mstore(add(ptr, 20), from) mstore(ptr, rights) - hash := or(shl(8, keccak256(ptr, 124)), ERC721_TYPE) // Runs keccak over the packed encoding, shifts left by one byte, then writes the type to the last byte + hash := or(shl(8, keccak256(ptr, 124)), ERC721_TYPE) // Keccak-hashes the packed encoding, left-shifts by one byte, then writes type to the lowest-order byte } } /** - * @notice Helper function to compute delegation location for ERC721 delegation - * @param from is the address making the delegation - * @param rights is the rights specified by the delegation - * @param to is the address receiving the delegation - * @param tokenId is the id of the erc721 token - * @param contract_ is the address of the erc721 token contract - * @return computedLocation is the storage location of the erc721 delegation with those parameters in the delegations mapping - * @dev gives the same location hash as location(erc721Hash(rights, from, to, contract_, tokenId)) would - * @dev will not revert if from, to, or contract_ are > uint160, any input larger than uint160 for from, to, or contract_ will be cleaned to their last 20 bytes + * @notice Helper function to compute delegation location for `DelegationType.ERC721` + * @dev Equivalent to `location(ERC721Hash(rights, from, to, contract_, tokenId))` + * @dev Will not revert if `from`, `to` or `contract_` are > uint160, these inputs will be cleaned to their lower 20 bytes + * @param from The address making the delegation + * @param rights The rights specified by the delegation + * @param to The address receiving the delegation + * @param tokenId The id of the ERC721 token + * @param contract_ The address of the ERC721 token contract + * @return computedLocation The storage location of the ERC721 delegation with those parameters in the delegations mapping */ function erc721Location(address from, bytes32 rights, address to, uint256 tokenId, address contract_) internal pure returns (bytes32 computedLocation) { assembly ("memory-safe") { // This block only allocates memory after the free memory pointer and in the scratch space let ptr := mload(64) // Cache the free memory pointer. - // Layout the variables from last to first, agnostic to upper 96 bits of address words. + // Lay out the variables from last to first, agnostic to upper 96 bits of address words. mstore(add(ptr, 92), tokenId) mstore(add(ptr, 60), contract_) mstore(add(ptr, 40), to) mstore(add(ptr, 20), from) mstore(ptr, rights) - mstore(0, or(shl(8, keccak256(ptr, 124)), ERC721_TYPE)) // Runs keccak over the packed encoding, shifts left by one byte, then writes the type to the - // last byte, and stores the result in the scratch space + mstore(0, or(shl(8, keccak256(ptr, 124)), ERC721_TYPE)) // Computes erc721Hash, then stores the result in scratch space mstore(32, DELEGATION_SLOT) computedLocation := keccak256(0, 64) // Runs keccak256 over the scratch space to obtain the storage key } } /** - * @notice Helper function to compute delegation hash for ERC20 delegation - * @param from is the address making the delegation - * @param rights is the rights specified by the delegation - * @param to is the address receiving the delegation - * @param contract_ is the address of the erc20 token contract - * @return hash of the parameters encoded with ERC20_TYPE - * @dev returned hash should be equivalent to keccak256(abi.encodePacked(rights, from, to, contract_)) with the last byte overwritten with ERC20_TYPE - * @dev will not revert if from, to, or contract_ are > uint160, any input larger than uint160 for from, to, or contract_ will be cleaned to their last 20 bytes + * @notice Helper function to compute delegation hash for `DelegationType.ERC20` + * @dev Equivalent to `keccak256(abi.encodePacked(rights, from, to, contract_))` with the last byte overwritten with ERC20_TYPE + * @dev Will not revert if `from`, `to` or `contract_` are > uint160, these inputs will be cleaned to their lower 20 bytes + * @param from The address making the delegation + * @param rights The rights specified by the delegation + * @param to The address receiving the delegation + * @param contract_ The address of the ERC20 token contract + * @return hash The parameters encoded with ERC20_TYPE */ function erc20Hash(address from, bytes32 rights, address to, address contract_) internal pure returns (bytes32 hash) { assembly ("memory-safe") { // This block only allocates memory after the free memory pointer let ptr := mload(64) // Load free memory pointer - // Layout the variables from last to first, agnostic to upper 96 bits of address words. + // Lay out the variables from last to first, agnostic to upper 96 bits of address words. mstore(add(ptr, 60), contract_) mstore(add(ptr, 40), to) mstore(add(ptr, 20), from) mstore(ptr, rights) - hash := or(shl(8, keccak256(ptr, 92)), ERC20_TYPE) // Runs keccak over the packed encoding, shifts left by one byte, then writes the type to the last byte + hash := or(shl(8, keccak256(ptr, 92)), ERC20_TYPE) // Keccak-hashes the packed encoding, left-shifts by one byte, then writes type to the lowest-order byte } } /** - * @notice Helper function to compute delegation location for ERC20 delegation - * @param from is the address making the delegation - * @param rights is the rights specified by the delegation - * @param to is the address receiving the delegation - * @param contract_ is the address of the erc20 token contract - * @return computedLocation is the storage location of the erc20 delegation with those parameters in the delegations mapping - * @dev gives the same location hash as location(erc20Hash(rights, from, to, contract_)) would - * @dev will not revert if from, to, or contract_ are > uint160, any input larger than uint160 for from, to, or contract_ will be cleaned to their last 20 bytes + * @notice Helper function to compute delegation location for `DelegationType.ERC20` + * @dev Equivalent to `location(ERC20Hash(rights, from, to, contract_))` + * @dev Will not revert if `from`, `to` or `contract_` are > uint160, these inputs will be cleaned to their lower 20 bytes + * @param from The address making the delegation + * @param rights The rights specified by the delegation + * @param to The address receiving the delegation + * @param contract_ The address of the ERC20 token contract + * @return computedLocation The storage location of the ERC20 delegation with those parameters in the delegations mapping */ function erc20Location(address from, bytes32 rights, address to, address contract_) internal pure returns (bytes32 computedLocation) { assembly ("memory-safe") { // This block only allocates memory after the free memory pointer and in the scratch space let ptr := mload(64) // Loads the free memory pointer - // Layout the variables from last to first, agnostic to upper 96 bits of address words. + // Lay out the variables from last to first, agnostic to upper 96 bits of address words. mstore(add(ptr, 60), contract_) mstore(add(ptr, 40), to) mstore(add(ptr, 20), from) mstore(ptr, rights) - mstore(0, or(shl(8, keccak256(ptr, 92)), ERC20_TYPE)) // Runs keccak over the packed encoding, shifts left by one byte, then writes the type to the - // last byte, and stores the result in the scratch space + mstore(0, or(shl(8, keccak256(ptr, 92)), ERC20_TYPE)) // Computes erc20Hash, then stores the result in scratch space mstore(32, DELEGATION_SLOT) computedLocation := keccak256(0, 64) // Runs keccak over the scratch space to obtain the storage key } } /** - * @notice Helper function to compute delegation hash for ERC1155 delegation - * @param from is the address making the delegation - * @param rights is the rights specified by the delegation - * @param to is the address receiving the delegation - * @param tokenId is the id of the erc1155 token - * @param contract_ is the address of the erc1155 token contract - * @return hash of the parameters encoded with ERC1155_TYPE - * @dev returned hash should be equivalent to keccak256(abi.encodePacked(rights, from, to, contract_, tokenId)) with the last byte overwritten with ERC1155_TYPE - * @dev will not revert if from, to, or contract_ are > uint160, any input larger than uint160 for from, to, or contract_ will be cleaned to their last 20 bytes + * @notice Helper function to compute delegation hash for `DelegationType.ERC1155` + * @dev Equivalent to keccak256(abi.encodePacked(rights, from, to, contract_, tokenId)) left-shifted with the last byte overwritten with ERC1155_TYPE + * @dev Will not revert if `from`, `to` or `contract_` are > uint160, these inputs will be cleaned to their lower 20 bytes + * @param from The address making the delegation + * @param rights The rights specified by the delegation + * @param to The address receiving the delegation + * @param tokenId The id of the ERC1155 token + * @param contract_ The address of the ERC1155 token contract + * @return hash The parameters encoded with ERC1155_TYPE */ function erc1155Hash(address from, bytes32 rights, address to, uint256 tokenId, address contract_) internal pure returns (bytes32 hash) { assembly ("memory-safe") { // This block only allocates memory after the free memory pointer let ptr := mload(64) // Load the free memory pointer. - // Layout the variables from last to first, agnostic to upper 96 bits of address words. + // Lay out the variables from last to first, agnostic to upper 96 bits of address words. mstore(add(ptr, 92), tokenId) mstore(add(ptr, 60), contract_) mstore(add(ptr, 40), to) mstore(add(ptr, 20), from) mstore(ptr, rights) - hash := or(shl(8, keccak256(ptr, 124)), ERC1155_TYPE) // Runs keccak over the packed encoding, shifts left by one byte, then writes the type to the last byte + hash := or(shl(8, keccak256(ptr, 124)), ERC1155_TYPE) // Keccak-hashes the packed encoding, left-shifts by one byte, then writes type to the lowest-order byte } } /** - * @notice Helper function to compute delegation hash for ERC1155 delegation - * @param from is the address making the delegation - * @param rights is the rights specified by the delegation - * @param to is the address receiving the delegation - * @param tokenId is the id of the erc1155 token - * @param contract_ is the address of the erc1155 token contract - * @return computedLocation is the storage location of the erc1155 delegation with those parameters in the delegations mapping - * @dev gives the same location hash as location(erc1155Hash(rights, from, to, contract_, tokenId)) would - * @dev will not revert if from, to, or contract_ are > uint160, any input larger than uint160 for from, to, or contract_ will be cleaned to their last 20 bytes + * @notice Helper function to compute delegation location for `DelegationType.ERC1155` + * @dev Equivalent to `location(ERC1155Hash(rights, from, to, contract_, tokenId))` + * @dev Will not revert if `from`, `to` or `contract_` are > uint160, these inputs will be cleaned to their lower 20 bytes + * @param from The address making the delegation + * @param rights The rights specified by the delegation + * @param to The address receiving the delegation + * @param tokenId The id of the ERC1155 token + * @param contract_ The address of the ERC1155 token contract + * @return computedLocation The storage location of the ERC1155 delegation with those parameters in the delegations mapping */ function erc1155Location(address from, bytes32 rights, address to, uint256 tokenId, address contract_) internal pure returns (bytes32 computedLocation) { assembly ("memory-safe") { // This block only allocates memory after the free memory pointer and in the scratch space let ptr := mload(64) // Cache the free memory pointer. - // Layout the variables from last to first, agnostic to upper 96 bits of address words. + // Lay out the variables from last to first, agnostic to upper 96 bits of address words. mstore(add(ptr, 92), tokenId) mstore(add(ptr, 60), contract_) mstore(add(ptr, 40), to) mstore(add(ptr, 20), from) mstore(ptr, rights) - mstore(0, or(shl(8, keccak256(ptr, 124)), ERC1155_TYPE)) // Runs keccak over the packed encoding, shifts left by one byte, then writes the type to the - // last byte, and stores the result in the scratch space + mstore(0, or(shl(8, keccak256(ptr, 124)), ERC1155_TYPE)) // Computes erc1155Hash, then stores the result in scratch space mstore(32, DELEGATION_SLOT) computedLocation := keccak256(0, 64) // Runs keccak over the scratch space to obtain the storage key } diff --git a/src/libraries/RegistryStorage.sol b/src/libraries/RegistryStorage.sol index 2a914c4..93e8f17 100644 --- a/src/libraries/RegistryStorage.sol +++ b/src/libraries/RegistryStorage.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.21; library RegistryStorage { - /// @dev Standardizes from storage flags to prevent double-writes in the delegation in/outbox if the same delegation is revoked and rewritten + /// @dev Standardizes `from` storage flags to prevent double-writes in the delegation in/outbox if the same delegation is revoked and rewritten address internal constant DELEGATION_EMPTY = address(0); address internal constant DELEGATION_REVOKED = address(1); @@ -13,7 +13,7 @@ library RegistryStorage { uint256 internal constant POSITIONS_TOKEN_ID = 3; uint256 internal constant POSITIONS_AMOUNT = 4; - /// @dev Used to clean address types of dirty bits with and(address, CLEAN_ADDRESS) + /// @dev Used to clean address types of dirty bits with `and(address, CLEAN_ADDRESS)` uint256 internal constant CLEAN_ADDRESS = 0x00ffffffffffffffffffffffffffffffffffffffff; /// @dev Used to clean everything but the first 8 bytes of an address @@ -29,7 +29,7 @@ library RegistryStorage { * @param contract_ The contract address associated with the delegation (optional) * @return firstPacked The firstPacked storage configured with the parameters * @return secondPacked The secondPacked storage configured with the parameters - * @dev Will not revert if from, to, and contract_ are > uint160, any inputs with dirty bits outside the last 20 bytes will be cleaned + * @dev Will not revert if `from`, `to`, and `contract_` are > uint160, any inputs with dirty bits outside the last 20 bytes will be cleaned */ function packAddresses(address from, address to, address contract_) internal pure returns (bytes32 firstPacked, bytes32 secondPacked) { assembly { @@ -45,7 +45,7 @@ library RegistryStorage { * @return from The address making the delegation * @return to The address receiving the delegation * @return contract_ The contract address associated with the delegation - * @dev Will not revert if from, to, and contract_ are > uint160, any inputs with dirty bits outside the last 20 bytes will be cleaned + * @dev Will not revert if `from`, `to`, and `contract_` are > uint160, any inputs with dirty bits outside the last 20 bytes will be cleaned */ function unpackAddresses(bytes32 firstPacked, bytes32 secondPacked) internal pure returns (address from, address to, address contract_) { assembly { diff --git a/src/singlesig/Singlesig.sol b/src/singlesig/Singlesig.sol index d4ecba2..3de1896 100644 --- a/src/singlesig/Singlesig.sol +++ b/src/singlesig/Singlesig.sol @@ -1,35 +1,38 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.4; -/// @dev Does not include receiver callbacks for "safe" transfer methods of ERC721, ERC1155, etc +/// @title 1-of-1 smart contract wallet with rotatable ownership for counterfactually receiving tokens at the same address across many EVM chains contract Singlesig { address public owner; address public pendingOwner; event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner); - event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /// @dev Initializes the contract setting the address provided by the deployer as the initial owner - /// @dev Distinct constructor args will lead to distinct CREATE2 deployment bytecode, so no collision risk here + /// @dev Distinct constructor args will lead to distinct CREATE2 initialization bytecode, so no collision risk here constructor(address initialOwner) { owner = initialOwner; emit OwnershipTransferred(address(0), initialOwner); } - // @dev Function to receive Ether. msg.data must be empty - receive() external payable {} - - // @dev Fallback function is called when msg.data is not empty - fallback() external payable {} - /// @dev Throws if called by any account other than the owner modifier onlyOwner() { require(owner == msg.sender, "Ownable2Step: caller is not the owner"); _; } - /// @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one + /// @notice Executes a call with provided parameters + /// @dev This method doesn't perform any sanity check of the transaction + /// @param to Destination address + /// @param value Native token value in wei + /// @param data Data payload + /// @return success Boolean flag indicating if the call succeeded + function execute(address to, uint256 value, bytes memory data) public onlyOwner returns (bool success) { + (success,) = to.call{value: value}(data); + } + + /// @dev Offers to transfer ownership permissions to a new account function transferOwnership(address newOwner) external onlyOwner { pendingOwner = newOwner; emit OwnershipTransferStarted(owner, newOwner); @@ -42,15 +45,27 @@ contract Singlesig { owner = pendingOwner; } - /** - * @notice Executes a call with provided parameters - * @dev This method doesn't perform any sanity check of the transaction - * @param to Destination address - * @param value Ether value in wei - * @param data Data payload - * @return success Boolean flag indicating if the call succeeded - */ - function execute(address to, uint256 value, bytes memory data) public onlyOwner returns (bool success) { - (success,) = to.call{value: value}(data); + /// @dev Function to receive native tokens when `msg.data` is empty + receive() external payable {} + + /// @dev Fallback function is called when `msg.data` is not empty + fallback() external payable {} + + function supportsInterface(bytes4 interfaceId) external pure returns (bool) { + return interfaceId == 0x01ffc9a7 // ERC165 Interface ID for ERC165 + || interfaceId == 0x150b7a02 // ERC165 Interface ID for ERC721TokenReceiver + || interfaceId == 0x4e2312e0; // ERC165 Interface ID for ERC1155TokenReceiver + } + + function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) { + return 0x150b7a02; //bytes4(keccak256("onERC721Received(address,uint256,bytes)")); + } + + function onERC1155Received(address, address, uint256, uint256, bytes calldata) external pure returns (bytes4) { + return 0xf23a6e61; // bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)")); + } + + function onERC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata) external pure returns (bytes4) { + return 0xbc197c81; // bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)")) } } diff --git a/test/DelegateRegistry.t.sol b/test/DelegateRegistry.t.sol index 59dabb2..4631fc0 100644 --- a/test/DelegateRegistry.t.sol +++ b/test/DelegateRegistry.t.sol @@ -255,7 +255,7 @@ contract DelegateRegistryTest is Test { } function testSweep(address to, address contract_, uint256 tokenId, uint256 amount, bytes32 rights_, bool enable) public { - address sc = address(0); + address sc = address(0x000000dE1E80ea5a234FB5488fee2584251BC7e8); uint256 regBalanceBefore = address(reg).balance; uint256 scBalanceBefore = address(sc).balance; bytes[] memory data = new bytes[](1); diff --git a/test/InitCodeHash.t.sol b/test/InitCodeHash.t.sol new file mode 100644 index 0000000..31c76b5 --- /dev/null +++ b/test/InitCodeHash.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import {Test} from "forge-std/Test.sol"; +import {console2} from "forge-std/console2.sol"; +import {DelegateRegistry} from "src/DelegateRegistry.sol"; +import {Singlesig} from "src/singlesig/Singlesig.sol"; + +// Singlesig inithash: 0xf911e320d18e7274491e7ab207bfff830e2926248f86c6a987668e8e72e1ed77 +// salt 0x000000000000000000000000000000000000000016c7768a8c7a2824b846321d => 0x000000dE1E80ea5a234FB5488fee2584251BC7e8 + +// Registry inithash: 0x78bdba7d5e0c91d9aedc93c97bf84433daaf008a83bfe921c2d27ab77301d6d9 +// salt 0x0000000000000000000000000000000000000000fbe49ecfc3decb1164228b89 => 0x0000000000006DE22EeA995bE2f0511186b8e013 => 16777216 + +contract InitCodeHashTest is Test { + DelegateRegistry reg; + Singlesig sig; + + function setUp() public { + reg = new DelegateRegistry(); + sig = new Singlesig(0x6Ed7D526b020780f694f3c10Dfb25E1b134D3215); + } + + function getInitHash() public pure returns (bytes32) { + bytes memory initCode = type(DelegateRegistry).creationCode; + // bytes memory initCode = abi.encodePacked(type(Singlesig).creationCode, abi.encode(address(0x6Ed7D526b020780f694f3c10Dfb25E1b134D3215))); + // console2.logBytes(initCode); + + return keccak256(abi.encodePacked(initCode)); + } + + function testInitHash() public { + bytes32 initHash = getInitHash(); + emit log_bytes32(initHash); + } +} diff --git a/test/examples/IPLicenseCheck.t.sol b/test/examples/IPLicenseCheck.t.sol index 79cdba4..c351eba 100644 --- a/test/examples/IPLicenseCheck.t.sol +++ b/test/examples/IPLicenseCheck.t.sol @@ -5,7 +5,7 @@ import {Test} from "forge-std/Test.sol"; import {IPLicenseCheck} from "src/examples/IPLicenseCheck.sol"; import {DelegateRegistry} from "src/DelegateRegistry.sol"; -import {ERC721} from "openzeppelin-contracts/contracts/token/ERC721/ERC721.sol"; +import {ERC721} from "openzeppelin/token/ERC721/ERC721.sol"; contract NFT is ERC721 { constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) {