diff --git a/contracts/scripts/native_solc_zksolc_compile_ccip b/contracts/scripts/native_solc_zksolc_compile_ccip index 8b823171a0..19022b1e84 100755 --- a/contracts/scripts/native_solc_zksolc_compile_ccip +++ b/contracts/scripts/native_solc_zksolc_compile_ccip @@ -6,7 +6,7 @@ echo " ┌─────────────────────── echo " │ Compiling CCIP contracts... │" echo " └──────────────────────────────────────────────┘" -SOLC_VERSION="0.8.24" +SOLC_VERSION="0.8.19" OPTIMIZE_RUNS=26000 OPTIMIZE_RUNS_OFFRAMP=18000 OPTIMIZE_RUNS_ONRAMP=4100 @@ -15,7 +15,7 @@ OPTIMIZE_RUNS_MULTI_OFFRAMP=800 SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" python3 -m pip install --require-hashes -r "$SCRIPTPATH"/requirements.txt -solc-select install $SOLC_VERSION +# solc-select install $SOLC_VERSION solc-select use $SOLC_VERSION export SOLC_VERSION=$SOLC_VERSION @@ -87,9 +87,18 @@ compileContractZK () { } +# compileContract ccip/Storage.sol +# compileContractZK ccip/Storage.sol + +# compileContract ccip/EVMCustom.sol +# compileContractZK ccip/EVMCustom.sol + +compileContract ccip/RBACTimelock.sol +compileContractZK ccip/RBACTimelock.sol + +compileContract ccip/manyChainMultisig.sol +compileContractZK ccip/manyChainMultisig.sol -compileContract ccip/Storage.sol -compileContractZK ccip/Storage.sol # Solc produces and overwrites intermediary contracts. # Contracts should be ordered in reverse-import-complexity-order to minimize overwrite risks. # compileContract ccip/offRamp/EVM2EVMOffRamp.sol @@ -207,16 +216,17 @@ compileContractZK ccip/Storage.sol # compileContractZK tests/MockV3Aggregator.sol -# SOLC_VERSION="0.8.19" +# SOLC_VERSION="0.5.16" # solc-select install $SOLC_VERSION # solc-select use $SOLC_VERSION # export SOLC_VERSION=$SOLC_VERSION + # compileContractShared () { # local contract # contract=$(basename "$1" ".sol") -# solc --overwrite --optimize --optimize-runs $OPTIMIZE_RUNS --metadata-hash none \ +# solc --overwrite --optimize --optimize-runs $OPTIMIZE_RUNS \ # -o "$ROOT"/contracts/solc/v$SOLC_VERSION/"$contract" \ # --abi --bin --allow-paths "$ROOT"/contracts/src/v0.8\ # "$ROOT"/contracts/src/v0.8/"$1" @@ -232,5 +242,7 @@ compileContractZK ccip/Storage.sol # "$ROOT"/contracts/src/v0.8/"$1" # } +# compileContractShared ccip/opCodes.sol +# compileContractSharedZK ccip/opCodes.sol # compileContractShared shared/token/ERC677/LinkToken.sol # compileContractSharedZK shared/token/ERC677/LinkToken.sol \ No newline at end of file diff --git a/contracts/src/v0.8/ccip/AccessControlEnumerable.sol b/contracts/src/v0.8/ccip/AccessControlEnumerable.sol new file mode 100644 index 0000000000..92f8a7f34d --- /dev/null +++ b/contracts/src/v0.8/ccip/AccessControlEnumerable.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControlEnumerable.sol) + +pragma solidity ^0.8.0; + +import "./IAccessControlEnumerable.sol"; +import "./AccessControl.sol"; +import "../utils/structs/EnumerableSet.sol"; + +/** + * @dev Extension of {AccessControl} that allows enumerating the members of each role. + */ +abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl { + using EnumerableSet for EnumerableSet.AddressSet; + + mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers; + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId); + } + + /** + * @dev Returns one of the accounts that have `role`. `index` must be a + * value between 0 and {getRoleMemberCount}, non-inclusive. + * + * Role bearers are not sorted in any particular way, and their ordering may + * change at any point. + * + * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure + * you perform all queries on the same block. See the following + * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post] + * for more information. + */ + function getRoleMember(bytes32 role, uint256 index) public view virtual override returns (address) { + return _roleMembers[role].at(index); + } + + /** + * @dev Returns the number of accounts that have `role`. Can be used + * together with {getRoleMember} to enumerate all bearers of a role. + */ + function getRoleMemberCount(bytes32 role) public view virtual override returns (uint256) { + return _roleMembers[role].length(); + } + + /** + * @dev Overload {_grantRole} to track enumerable memberships + */ + function _grantRole(bytes32 role, address account) internal virtual override { + super._grantRole(role, account); + _roleMembers[role].add(account); + } + + /** + * @dev Overload {_revokeRole} to track enumerable memberships + */ + function _revokeRole(bytes32 role, address account) internal virtual override { + super._revokeRole(role, account); + _roleMembers[role].remove(account); + } +} \ No newline at end of file diff --git a/contracts/src/v0.8/ccip/EVMCustom.sol b/contracts/src/v0.8/ccip/EVMCustom.sol new file mode 100644 index 0000000000..b24f02d035 --- /dev/null +++ b/contracts/src/v0.8/ccip/EVMCustom.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +// https://docs.moonbeam.network/builders/pallets-precompiles/precompiles/eth-mainnet/#hashing-with-sha256 +contract EVMCustomTest { + + //----------------- SHA-256 TEST -----------------// + + bytes32 public expected256Hash = + 0x7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069; + + function calculate256Hash() internal pure returns (bytes32) { + string memory word = "Hello World!"; + bytes32 hash = sha256(bytes(word)); + + return hash; + } + + function check256Hash() public view returns (bool) { + return (calculate256Hash() == expected256Hash); + } + + //----------------- RIPEMD160 TEST -----------------// + + bytes20 public expectedHash = hex"8476ee4631b9b30ac2754b0ee0c47e161d3f724c"; + + function calculate160Hash() internal pure returns (bytes20) { + string memory word = "Hello World!"; + bytes20 hash = ripemd160(bytes(word)); + + return hash; + } + + function checkRipeMd160Hash() public view returns (bool) { + return (calculate160Hash() == expectedHash); + } + + //----------------- GLOBAL VARIABLE TEST -----------------// + + function getBlockVariables() public view returns (uint, uint, bytes32) + { + return (block.number, block.timestamp, blockhash(block.number - 1)); + } + + //----------------- ECRECOVER TEST -----------------// + + address addressTest = 0x12Cb274aAD8251C875c0bf6872b67d9983E53fDd; + bytes32 msgHash = + 0xc51dac836bc7841a01c4b631fa620904fc8724d7f9f1d3c420f0e02adf229d50; + uint8 v = 0x1b; + bytes32 r = + 0x44287513919034a471a7dc2b2ed121f95984ae23b20f9637ba8dff471b6719ef; + bytes32 s = + 0x7d7dc30309a3baffbfd9342b97d0e804092c0aeb5821319aa732bc09146eafb4; + + function verifyECrecover() public view returns (bool) { + // Use ECRECOVER to verify address + return (ecrecover(msgHash, v, r, s) == (addressTest)); + } + + //----------------- BIG MOD EXP TEST -----------------// + + uint public checkResult; + + function verify(uint _base, uint _exp, uint _modulus) public { + checkResult = modExp(_base, _exp, _modulus); + } + + function modExp( + uint256 _b, + uint256 _e, + uint256 _m + ) public returns (uint256 result) { + assembly { + // Free memory pointer + let pointer := mload(0x40) + // Define length of base, exponent and modulus. 0x20 == 32 bytes + mstore(pointer, 0x20) + mstore(add(pointer, 0x20), 0x20) + mstore(add(pointer, 0x40), 0x20) + // Define variables base, exponent and modulus + mstore(add(pointer, 0x60), _b) + mstore(add(pointer, 0x80), _e) + mstore(add(pointer, 0xa0), _m) + // Store the result + let value := mload(0xc0) + // Call the precompiled contract 0x05 = bigModExp + if iszero(call(not(0), 0x05, 0, pointer, 0xc0, value, 0x20)) { + revert(0, 0) + } + result := mload(value) + } + } + + //----------------- GAS PRICE OPCODE TEST -----------------// + + uint64 gasprice; + uint64 dummy; // dummy variable to trick G++ provider to estimate correct gasLimit + + function setGasPrice(uint64 t) external { + dummy = t + 1; + uint64 gp; + assembly { + gp := gasprice() + } + gasprice = gp; + } + + function getGasPrice() external view returns (uint64) { + return gasprice; + } + + //----------------- DATA COPY TEST -----------------// + + bytes public memoryStored; + + function callDatacopy(bytes memory data) public returns (bytes memory) { + bytes memory result = new bytes(data.length); + assembly { + let len := mload(data) + if iszero( + call( + gas(), + 0x04, + 0, + add(data, 0x20), + len, + add(result, 0x20), + len + ) + ) { + invalid() + } + } + + memoryStored = result; + + return result; + } +} \ No newline at end of file diff --git a/contracts/src/v0.8/ccip/IERC1155Receiver.sol b/contracts/src/v0.8/ccip/IERC1155Receiver.sol new file mode 100644 index 0000000000..02cf1a6451 --- /dev/null +++ b/contracts/src/v0.8/ccip/IERC1155Receiver.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol) + +pragma solidity ^0.8.0; + +import "../../utils/introspection/IERC165.sol"; + +/** + * @dev _Available since v3.1._ + */ +interface IERC1155Receiver is IERC165 { + /** + * @dev Handles the receipt of a single ERC1155 token type. This function is + * called at the end of a `safeTransferFrom` after the balance has been updated. + * + * NOTE: To accept the transfer, this must return + * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` + * (i.e. 0xf23a6e61, or its own function selector). + * + * @param operator The address which initiated the transfer (i.e. msg.sender) + * @param from The address which previously owned the token + * @param id The ID of the token being transferred + * @param value The amount of tokens being transferred + * @param data Additional data with no specified format + * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed + */ + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) external returns (bytes4); + + /** + * @dev Handles the receipt of a multiple ERC1155 token types. This function + * is called at the end of a `safeBatchTransferFrom` after the balances have + * been updated. + * + * NOTE: To accept the transfer(s), this must return + * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` + * (i.e. 0xbc197c81, or its own function selector). + * + * @param operator The address which initiated the batch transfer (i.e. msg.sender) + * @param from The address which previously owned the token + * @param ids An array containing ids of each token being transferred (order and length must match values array) + * @param values An array containing amounts of each token being transferred (order and length must match ids array) + * @param data Additional data with no specified format + * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed + */ + function onERC1155BatchReceived( + address operator, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external returns (bytes4); +} \ No newline at end of file diff --git a/contracts/src/v0.8/ccip/IERC721Receiver.sol b/contracts/src/v0.8/ccip/IERC721Receiver.sol new file mode 100644 index 0000000000..2ff3e0e88d --- /dev/null +++ b/contracts/src/v0.8/ccip/IERC721Receiver.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol) + +pragma solidity ^0.8.0; + +/** + * @title ERC721 token receiver interface + * @dev Interface for any contract that wants to support safeTransfers + * from ERC721 asset contracts. + */ +interface IERC721Receiver { + /** + * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} + * by `operator` from `from`, this function is called. + * + * It must return its Solidity selector to confirm the token transfer. + * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted. + * + * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`. + */ + function onERC721Received( + address operator, + address from, + uint256 tokenId, + bytes calldata data + ) external returns (bytes4); +} \ No newline at end of file diff --git a/contracts/src/v0.8/ccip/MerkleProof.sol b/contracts/src/v0.8/ccip/MerkleProof.sol new file mode 100644 index 0000000000..77c7181e29 --- /dev/null +++ b/contracts/src/v0.8/ccip/MerkleProof.sol @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.2) (utils/cryptography/MerkleProof.sol) + +pragma solidity ^0.8.0; + +/** + * @dev These functions deal with verification of Merkle Tree proofs. + * + * The tree and the proofs can be generated using our + * https://github.com/OpenZeppelin/merkle-tree[JavaScript library]. + * You will find a quickstart guide in the readme. + * + * WARNING: You should avoid using leaf values that are 64 bytes long prior to + * hashing, or use a hash function other than keccak256 for hashing leaves. + * This is because the concatenation of a sorted pair of internal nodes in + * the merkle tree could be reinterpreted as a leaf value. + * OpenZeppelin's JavaScript library generates merkle trees that are safe + * against this attack out of the box. + */ +library MerkleProof { + /** + * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree + * defined by `root`. For this, a `proof` must be provided, containing + * sibling hashes on the branch from the leaf to the root of the tree. Each + * pair of leaves and each pair of pre-images are assumed to be sorted. + */ + function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) { + return processProof(proof, leaf) == root; + } + + /** + * @dev Calldata version of {verify} + * + * _Available since v4.7._ + */ + function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) { + return processProofCalldata(proof, leaf) == root; + } + + /** + * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up + * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt + * hash matches the root of the tree. When processing the proof, the pairs + * of leafs & pre-images are assumed to be sorted. + * + * _Available since v4.4._ + */ + function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) { + bytes32 computedHash = leaf; + for (uint256 i = 0; i < proof.length; i++) { + computedHash = _hashPair(computedHash, proof[i]); + } + return computedHash; + } + + /** + * @dev Calldata version of {processProof} + * + * _Available since v4.7._ + */ + function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) { + bytes32 computedHash = leaf; + for (uint256 i = 0; i < proof.length; i++) { + computedHash = _hashPair(computedHash, proof[i]); + } + return computedHash; + } + + /** + * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a merkle tree defined by + * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}. + * + * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details. + * + * _Available since v4.7._ + */ + function multiProofVerify( + bytes32[] memory proof, + bool[] memory proofFlags, + bytes32 root, + bytes32[] memory leaves + ) internal pure returns (bool) { + return processMultiProof(proof, proofFlags, leaves) == root; + } + + /** + * @dev Calldata version of {multiProofVerify} + * + * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details. + * + * _Available since v4.7._ + */ + function multiProofVerifyCalldata( + bytes32[] calldata proof, + bool[] calldata proofFlags, + bytes32 root, + bytes32[] memory leaves + ) internal pure returns (bool) { + return processMultiProofCalldata(proof, proofFlags, leaves) == root; + } + + /** + * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction + * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another + * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false + * respectively. + * + * CAUTION: Not all merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree + * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the + * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). + * + * _Available since v4.7._ + */ + function processMultiProof( + bytes32[] memory proof, + bool[] memory proofFlags, + bytes32[] memory leaves + ) internal pure returns (bytes32 merkleRoot) { + // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by + // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the + // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of + // the merkle tree. + uint256 leavesLen = leaves.length; + uint256 proofLen = proof.length; + uint256 totalHashes = proofFlags.length; + + // Check proof validity. + require(leavesLen + proofLen - 1 == totalHashes, "MerkleProof: invalid multiproof"); + + // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using + // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". + bytes32[] memory hashes = new bytes32[](totalHashes); + uint256 leafPos = 0; + uint256 hashPos = 0; + uint256 proofPos = 0; + // At each step, we compute the next hash using two values: + // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we + // get the next hash. + // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the + // `proof` array. + for (uint256 i = 0; i < totalHashes; i++) { + bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; + bytes32 b = proofFlags[i] + ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) + : proof[proofPos++]; + hashes[i] = _hashPair(a, b); + } + + if (totalHashes > 0) { + require(proofPos == proofLen, "MerkleProof: invalid multiproof"); + unchecked { + return hashes[totalHashes - 1]; + } + } else if (leavesLen > 0) { + return leaves[0]; + } else { + return proof[0]; + } + } + + /** + * @dev Calldata version of {processMultiProof}. + * + * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details. + * + * _Available since v4.7._ + */ + function processMultiProofCalldata( + bytes32[] calldata proof, + bool[] calldata proofFlags, + bytes32[] memory leaves + ) internal pure returns (bytes32 merkleRoot) { + // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by + // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the + // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of + // the merkle tree. + uint256 leavesLen = leaves.length; + uint256 proofLen = proof.length; + uint256 totalHashes = proofFlags.length; + + // Check proof validity. + require(leavesLen + proofLen - 1 == totalHashes, "MerkleProof: invalid multiproof"); + + // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using + // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". + bytes32[] memory hashes = new bytes32[](totalHashes); + uint256 leafPos = 0; + uint256 hashPos = 0; + uint256 proofPos = 0; + // At each step, we compute the next hash using two values: + // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we + // get the next hash. + // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the + // `proof` array. + for (uint256 i = 0; i < totalHashes; i++) { + bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; + bytes32 b = proofFlags[i] + ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) + : proof[proofPos++]; + hashes[i] = _hashPair(a, b); + } + + if (totalHashes > 0) { + require(proofPos == proofLen, "MerkleProof: invalid multiproof"); + unchecked { + return hashes[totalHashes - 1]; + } + } else if (leavesLen > 0) { + return leaves[0]; + } else { + return proof[0]; + } + } + + function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) { + return a < b ? _efficientHash(a, b) : _efficientHash(b, a); + } + + function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, a) + mstore(0x20, b) + value := keccak256(0x00, 0x40) + } + } +} \ No newline at end of file diff --git a/contracts/src/v0.8/ccip/Ownable.sol b/contracts/src/v0.8/ccip/Ownable.sol new file mode 100644 index 0000000000..58ecfcb1b0 --- /dev/null +++ b/contracts/src/v0.8/ccip/Ownable.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol) + +pragma solidity ^0.8.0; + +import "../utils/Context.sol"; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor() { + _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. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby disabling 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); + } +} \ No newline at end of file diff --git a/contracts/src/v0.8/ccip/Ownable2Step.sol b/contracts/src/v0.8/ccip/Ownable2Step.sol new file mode 100644 index 0000000000..6a54cdb0b9 --- /dev/null +++ b/contracts/src/v0.8/ccip/Ownable2Step.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol) + +pragma solidity ^0.8.0; + +import "./Ownable.sol"; + +/** + * @dev Contract module which provides access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership} and {acceptOwnership}. + * + * This module is used through inheritance. It will make available all functions + * from parent (Ownable). + */ +abstract contract Ownable2Step is Ownable { + address private _pendingOwner; + + event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Returns the address of the pending owner. + */ + function pendingOwner() public view virtual returns (address) { + return _pendingOwner; + } + + /** + * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one. + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual override onlyOwner { + _pendingOwner = newOwner; + emit OwnershipTransferStarted(owner(), newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner. + * Internal function without access restriction. + */ + function _transferOwnership(address newOwner) internal virtual override { + delete _pendingOwner; + super._transferOwnership(newOwner); + } + + /** + * @dev The new owner accepts the ownership transfer. + */ + function acceptOwnership() public virtual { + address sender = _msgSender(); + require(pendingOwner() == sender, "Ownable2Step: caller is not the new owner"); + _transferOwnership(sender); + } +} \ No newline at end of file diff --git a/contracts/src/v0.8/ccip/RBACTimelock.sol b/contracts/src/v0.8/ccip/RBACTimelock.sol new file mode 100644 index 0000000000..617265c22a --- /dev/null +++ b/contracts/src/v0.8/ccip/RBACTimelock.sol @@ -0,0 +1,503 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.19; + +import "../vendor/openzeppelin-solidity/v4.8.3/contracts/access/AccessControlEnumerable.sol"; +import "../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC721/IERC721Receiver.sol"; +import "../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC1155/IERC1155Receiver.sol"; +import "../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/Address.sol"; +import "../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/structs/EnumerableSet.sol"; + +/** + * @notice Contract module which acts as a timelocked controller with role-based + * access control. When set as the owner of an `Ownable` smart contract, it + * can enforce a timelock on `onlyOwner` maintenance operations and prevent + * a list of blocked functions from being called. The timelock can be bypassed + * by a bypasser or an admin in emergency situations that require quick action. + * + * Non-emergency actions are expected to follow the timelock. + * + * The contract has five roles. Each role can be inhabited by multiple + * (potentially overlapping) addresses. + * + * 1) Admin: The admin manages membership for all roles (including the admin + * role itself). The admin automatically inhabits all other roles. The admin + * can call the bypasserExecuteBatch function to bypass any restrictions like + * the delay imposed by the timelock and the list of blocked functions. The + * admin can manage the list of blocked functions. In practice, the admin + * role is expected to (1) be inhabited by a contract requiring a secure + * quorum of votes before taking any action and (2) to be used rarely, namely + * only for emergency actions or configuration of the RBACTimelock. + * + * 2) Proposer: The proposer can schedule delayed operations that don't use any + * blocked function selector. + * + * 3) Executor: The executor can execute previously scheduled operations once + * their delay has expired. The contract enforces that the calls in an + * operation are executed with the correct args (target, data, value), but + * the executor can freely choose the gas limit. Since the executor is + * typically not particularly trusted, we recommend that (transitive) callees + * implement standard behavior of simply reverting if insufficient gas is + * provided. In particular, this means callees should not have non-reverting + * gas-dependent branches. + * + * 4) Canceller: The canceller can cancel operations that have been scheduled + * but not yet executed. + * + * 5) Bypasser: The bypasser can bypass any restrictions like the delay imposed + * by the timelock and the list of blocked functions to immediately execute + * operations, e.g. in case of emergencies. + * + * Note that this contract doesn't place any restrictions on the gas limit used + * when executing operations. See the above comment on the executor role for + * more details. + * + * @dev This contract is a modified version of OpenZeppelin's + * contracts/governance/TimelockController.sol contract from v4.7.0, accessed in + * commit 561d1061fc568f04c7a65853538e834a889751e8 of + * github.com/OpenZeppelin/openzeppelin-contracts + * Said contract is under "Copyright (c) 2016-2023 zOS Global Limited and + * contributors" and its original MIT license can be found at + * https://github.com/OpenZeppelin/../vendor/openzeppelin-solidity/v4.8.3/contracts/blob/561d1061fc568f04c7a65853538e834a889751e8/LICENSE + */ +contract RBACTimelock is AccessControlEnumerable, IERC721Receiver, IERC1155Receiver { + using EnumerableSet for EnumerableSet.Bytes32Set; + + struct Call { + address target; + uint256 value; + bytes data; + } + + bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); + bytes32 public constant PROPOSER_ROLE = keccak256("PROPOSER_ROLE"); + bytes32 public constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE"); + bytes32 public constant CANCELLER_ROLE = keccak256("CANCELLER_ROLE"); + bytes32 public constant BYPASSER_ROLE = keccak256("BYPASSER_ROLE"); + uint256 internal constant _DONE_TIMESTAMP = uint256(1); + + mapping(bytes32 => uint256) private _timestamps; + uint256 private _minDelay; + EnumerableSet.Bytes32Set private _blockedFunctionSelectors; + + /** + * @dev Emitted when a call is scheduled as part of operation `id`. + */ + event CallScheduled( + bytes32 indexed id, + uint256 indexed index, + address target, + uint256 value, + bytes data, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ); + + /** + * @dev Emitted when a call is performed as part of operation `id`. + */ + event CallExecuted(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data); + + /** + * @dev Emitted when a call is performed via bypasser. + */ + event BypasserCallExecuted(uint256 indexed index, address target, uint256 value, bytes data); + + /** + * @dev Emitted when operation `id` is cancelled. + */ + event Cancelled(bytes32 indexed id); + + /** + * @dev Emitted when the minimum delay for future operations is modified. + */ + event MinDelayChange(uint256 oldDuration, uint256 newDuration); + + /** + * @dev Emitted when a function selector is blocked. + */ + event FunctionSelectorBlocked(bytes4 indexed selector); + + /** + * @dev Emitted when a function selector is unblocked. + */ + event FunctionSelectorUnblocked(bytes4 indexed selector); + + + /** + * @dev Initializes the contract with the following parameters: + * + * - `minDelay`: initial minimum delay for operations + * - `admin`: account to be granted admin role + * - `proposers`: accounts to be granted proposer role + * - `executors`: accounts to be granted executor role + * - `cancellers`: accounts to be granted canceller role + * - `bypassers`: accounts to be granted bypasser role + */ + constructor( + uint256 minDelay, + address admin, + address[] memory proposers, + address[] memory executors, + address[] memory cancellers, + address[] memory bypassers + ) { + _setRoleAdmin(ADMIN_ROLE, ADMIN_ROLE); + _setRoleAdmin(PROPOSER_ROLE, ADMIN_ROLE); + _setRoleAdmin(EXECUTOR_ROLE, ADMIN_ROLE); + _setRoleAdmin(CANCELLER_ROLE, ADMIN_ROLE); + _setRoleAdmin(BYPASSER_ROLE, ADMIN_ROLE); + + _setupRole(ADMIN_ROLE, admin); + + // register proposers + for (uint256 i = 0; i < proposers.length; ++i) { + _setupRole(PROPOSER_ROLE, proposers[i]); + } + + // register executors + for (uint256 i = 0; i < executors.length; ++i) { + _setupRole(EXECUTOR_ROLE, executors[i]); + } + + // register cancellers + for (uint256 i = 0; i < cancellers.length; ++i) { + _setupRole(CANCELLER_ROLE, cancellers[i]); + } + + // register bypassers + for (uint256 i = 0; i < bypassers.length; ++i) { + _setupRole(BYPASSER_ROLE, bypassers[i]); + } + + _minDelay = minDelay; + emit MinDelayChange(0, minDelay); + } + + /** + * @dev Modifier to make a function callable only by a certain role or the + * admin role. + */ + modifier onlyRoleOrAdminRole(bytes32 role) { + address sender = _msgSender(); + if (!hasRole(ADMIN_ROLE, sender)) { + _checkRole(role, sender); + } + _; + } + + /** + * @dev Contract might receive/hold ETH as part of the maintenance process. + */ + receive() external payable {} + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, AccessControlEnumerable) returns (bool) { + return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId); + } + + /** + * @dev Returns whether an id correspond to a registered operation. This + * includes both Pending, Ready and Done operations. + */ + function isOperation(bytes32 id) public view virtual returns (bool registered) { + return getTimestamp(id) > 0; + } + + /** + * @dev Returns whether an operation is pending or not. + */ + function isOperationPending(bytes32 id) public view virtual returns (bool pending) { + return getTimestamp(id) > _DONE_TIMESTAMP; + } + + /** + * @dev Returns whether an operation is ready or not. + */ + function isOperationReady(bytes32 id) public view virtual returns (bool ready) { + uint256 timestamp = getTimestamp(id); + return timestamp > _DONE_TIMESTAMP && timestamp <= block.timestamp; + } + + /** + * @dev Returns whether an operation is done or not. + */ + function isOperationDone(bytes32 id) public view virtual returns (bool done) { + return getTimestamp(id) == _DONE_TIMESTAMP; + } + + /** + * @dev Returns the timestamp at with an operation becomes ready (0 for + * unset operations, 1 for done operations). + */ + function getTimestamp(bytes32 id) public view virtual returns (uint256 timestamp) { + return _timestamps[id]; + } + + /** + * @dev Returns the minimum delay for an operation to become valid. + * + * This value can be changed by executing an operation that calls `updateDelay`. + */ + function getMinDelay() public view virtual returns (uint256 duration) { + return _minDelay; + } + + /** + * @dev Returns the identifier of an operation containing a batch of + * transactions. + */ + function hashOperationBatch( + Call[] calldata calls, + bytes32 predecessor, + bytes32 salt + ) public pure virtual returns (bytes32 hash) { + return keccak256(abi.encode(calls, predecessor, salt)); + } + + /** + * @dev Schedule an operation containing a batch of transactions. + * + * Emits one {CallScheduled} event per transaction in the batch. + * + * Requirements: + * + * - the caller must have the 'proposer' or 'admin' role. + * - all payloads must not start with a blocked function selector. + */ + function scheduleBatch( + Call[] calldata calls, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) public virtual onlyRoleOrAdminRole(PROPOSER_ROLE) { + bytes32 id = hashOperationBatch(calls, predecessor, salt); + _schedule(id, delay); + for (uint256 i = 0; i < calls.length; ++i) { + _checkFunctionSelectorNotBlocked(calls[i].data); + emit CallScheduled(id, i, calls[i].target, calls[i].value, calls[i].data, predecessor, salt, delay); + } + } + + /** + * @dev Schedule an operation that becomes valid after a given delay. + */ + function _schedule(bytes32 id, uint256 delay) private { + require(!isOperation(id), "RBACTimelock: operation already scheduled"); + require(delay >= getMinDelay(), "RBACTimelock: insufficient delay"); + _timestamps[id] = block.timestamp + delay; + } + + /** + * @dev Cancel an operation. + * + * Requirements: + * + * - the caller must have the 'canceller' or 'admin' role. + */ + function cancel(bytes32 id) public virtual onlyRoleOrAdminRole(CANCELLER_ROLE) { + require(isOperationPending(id), "RBACTimelock: operation cannot be cancelled"); + delete _timestamps[id]; + + emit Cancelled(id); + } + + /** + * @dev Execute an (ready) operation containing a batch of transactions. + * Note that we perform a raw call to each target. Raw calls to targets that + * don't have associated contract code will always succeed regardless of + * payload. + * + * Emits one {CallExecuted} event per transaction in the batch. + * + * Requirements: + * + * - the caller must have the 'executor' or 'admin' role. + */ + function executeBatch( + Call[] calldata calls, + bytes32 predecessor, + bytes32 salt + ) public payable virtual onlyRoleOrAdminRole(EXECUTOR_ROLE) { + bytes32 id = hashOperationBatch(calls, predecessor, salt); + + _beforeCall(id, predecessor); + for (uint256 i = 0; i < calls.length; ++i) { + _execute(calls[i]); + emit CallExecuted(id, i, calls[i].target, calls[i].value, calls[i].data); + } + _afterCall(id); + } + + /** + * @dev Execute an operation's call. + */ + function _execute( + Call calldata call + ) internal virtual { + (bool success, ) = call.target.call{value: call.value}(call.data); + require(success, "RBACTimelock: underlying transaction reverted"); + } + + /** + * @dev Checks before execution of an operation's calls. + */ + function _beforeCall(bytes32 id, bytes32 predecessor) private view { + require(isOperationReady(id), "RBACTimelock: operation is not ready"); + require(predecessor == bytes32(0) || isOperationDone(predecessor), "RBACTimelock: missing dependency"); + } + + /** + * @dev Checks after execution of an operation's calls. + */ + function _afterCall(bytes32 id) private { + require(isOperationReady(id), "RBACTimelock: operation is not ready"); + _timestamps[id] = _DONE_TIMESTAMP; + } + + /** + * @dev Changes the minimum timelock duration for future operations. + * + * Emits a {MinDelayChange} event. + * + * Requirements: + * + * - the caller must have the 'admin' role. + */ + function updateDelay(uint256 newDelay) external virtual onlyRole(ADMIN_ROLE) { + emit MinDelayChange(_minDelay, newDelay); + _minDelay = newDelay; + } + + /** + * @dev See {IERC721Receiver-onERC721Received}. + */ + function onERC721Received( + address, + address, + uint256, + bytes memory + ) public virtual override returns (bytes4) { + return this.onERC721Received.selector; + } + + /** + * @dev See {IERC1155Receiver-onERC1155Received}. + */ + function onERC1155Received( + address, + address, + uint256, + uint256, + bytes memory + ) public virtual override returns (bytes4) { + return this.onERC1155Received.selector; + } + + /** + * @dev See {IERC1155Receiver-onERC1155BatchReceived}. + */ + function onERC1155BatchReceived( + address, + address, + uint256[] memory, + uint256[] memory, + bytes memory + ) public virtual override returns (bytes4) { + return this.onERC1155BatchReceived.selector; + } + + /* + * New functions not present in original OpenZeppelin TimelockController + */ + + /** + * @dev Blocks a function selector from being used, i.e. schedule + * operations with this function selector will revert. + * Note that blocked selectors are only checked when an operation is being + * scheduled, not when it is executed. You may want to check any pending + * operations for whether they contain the blocked selector and cancel them. + * + * Requirements: + * + * - the caller must have the 'admin' role. + */ + function blockFunctionSelector(bytes4 selector) external onlyRole(ADMIN_ROLE) { + if (_blockedFunctionSelectors.add(selector)) { + emit FunctionSelectorBlocked(selector); + } + } + + /** + * @dev Unblocks a previously blocked function selector so it can be used again. + * Requirements: + * + * - the caller must have the 'admin' role. + */ + function unblockFunctionSelector(bytes4 selector) external onlyRole(ADMIN_ROLE) { + if (_blockedFunctionSelectors.remove(selector)) { + emit FunctionSelectorUnblocked(selector); + } + } + + /** + * @dev Returns the number of blocked function selectors. + */ + function getBlockedFunctionSelectorCount() external view returns (uint256) { + return _blockedFunctionSelectors.length(); + } + + /** + * @dev Returns the blocked function selector with the given index. Function + * selectors are not sorted in any particular way, and their ordering may + * change at any point. + * + * WARNING: When using {getBlockedFunctionSelectorCount} and + * {getBlockedFunctionSelectorAt} via RPC, make sure you perform all queries + * on the same block. When using these functions within an onchain + * transaction, make sure that the state of this contract hasn't changed in + * between invocations to avoid time-of-check time-of-use bugs. + * See the following + * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-../vendor/openzeppelin-solidity/v4.8.3/contracts/2296[forum + * post] for more information. + */ + function getBlockedFunctionSelectorAt(uint256 index) external view returns (bytes4) { + return bytes4(_blockedFunctionSelectors.at(index)); + } + + /** + * @dev Directly execute a batch of transactions, bypassing any other + * checks. + * Note that we perform a raw call to each target. Raw calls to targets that + * don't have associated contract code will always succeed regardless of + * payload. + * + * Emits one {BypasserCallExecuted} event per transaction in the batch. + * + * Requirements: + * + * - the caller must have the 'bypasser' or 'admin' role. + */ + function bypasserExecuteBatch( + Call[] calldata calls + ) public payable virtual onlyRoleOrAdminRole(BYPASSER_ROLE) { + for (uint256 i = 0; i < calls.length; ++i) { + _execute(calls[i]); + emit BypasserCallExecuted(i, calls[i].target, calls[i].value, calls[i].data); + } + } + + /** + * @dev Checks to see if the function being scheduled is blocked. This + * is used when trying to schedule or batch schedule an operation. + */ + function _checkFunctionSelectorNotBlocked(bytes calldata data) private view { + if (data.length < 4) { + return; + } + bytes4 selector = bytes4(data[:4]); + require(!_blockedFunctionSelectors.contains(bytes32(selector)), "RBACTimelock: selector is blocked"); + } +} \ No newline at end of file diff --git a/contracts/src/v0.8/ccip/manyChainMultisig.sol b/contracts/src/v0.8/ccip/manyChainMultisig.sol new file mode 100644 index 0000000000..df0708cafb --- /dev/null +++ b/contracts/src/v0.8/ccip/manyChainMultisig.sol @@ -0,0 +1,602 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.19; + +import "../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/cryptography/MerkleProof.sol"; +import "../vendor/openzeppelin-solidity/v4.8.3/contracts/access/Ownable2Step.sol"; +import "../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/cryptography/ECDSA.sol"; + +// Should be used as the first 32 bytes of the pre-image of the leaf that holds a +// op. This value is for domain separation of the different values stored in the +// Merkle tree. +bytes32 constant MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_OP = + keccak256("MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_OP"); + +// Should be used as the first 32 bytes of the pre-image of the leaf that holds the +// root metadata. This value is for domain separation of the different values stored in the +// Merkle tree. +bytes32 constant MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA = + keccak256("MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA"); + +/// @notice This is a multi-sig contract that supports signing many transactions (called "ops" in +/// the context of this contract to prevent confusion with transactions on the underlying chain) +/// targeting many chains with a single set of signatures. Authorized ops along with some metadata +/// are stored in a Merkle tree, which is generated offchain. Each op has an associated chain id, +/// ManyChainMultiSig contract address and nonce. The nonce enables us to enforce the +/// (per-ManyChainMultiSig contract instance) ordering of ops. +/// +/// At any time, this contract stores at most one Merkle root. In the typical case, all ops +/// in the Merkle tree are expected to be executed before another root is set. Since the Merkle root +/// itself reveals ~ no information about the tree's contents, we take two measures to improve +/// transparency. First, we attach an expiration time to each Merkle root after which it cannot +/// be used any more. Second, we embed metadata in the tree itself that has to be proven/revealed +/// to the contract when a new root is set; the metadata contains the range of nonces (and thus +/// number of ops) in the tree intended for the ManyChainMultiSig contract instance on which the +/// root is being set. +/// +/// Once a root is registered, *anyone* is allowed to furnish proofs of op inclusion in the Merkle +/// tree and execute the corresponding op. The contract enforces that ops are executed in the +/// correct order and with the correct arguments. A notable exception to this is the gas limit of +/// the call, which can be freely determined by the executor. We expect (transitive) callees to +/// implement standard behavior of simply reverting if insufficient gas is provided. In particular, +/// this means callees should not have non-reverting gas-dependent branches. +/// +/// Note: In the typical case, we expect the time from a root being set to all of the ops +/// therein having been executed to be on the order of a couple of minutes. +contract ManyChainMultiSig is Ownable2Step { + receive() external payable {} + + uint8 public constant NUM_GROUPS = 32; + uint8 public constant MAX_NUM_SIGNERS = 200; + + struct Signer { + address addr; + uint8 index; // index of signer in s_config.signers + uint8 group; // 0 <= group < NUM_GROUPS. Each signer can only be in one group. + } + + // s_signers is used to easily validate the existence of the signer by its address. We still + // have signers stored in s_config in order to easily deactivate them when a new config is set. + mapping(address => Signer) s_signers; + + // Signing groups are arranged in a tree. Each group is an interior node and has its own quorum. + // Signers are the leaves of the tree. A signer/leaf node is successful iff it furnishes a valid + // signature. A group/interior node is successful iff a quorum of its children are successful. + // setRoot succeeds only if the root group is successful. + // Here is an example: + // + // ┌──────┐ + // ┌─►│2-of-3│◄───────┐ + // │ └──────┘ │ + // │ ▲ │ + // │ │ │ + // ┌──┴───┐ ┌──┴───┐ ┌───┴────┐ + // ┌──►│1-of-2│ │2-of-2│ │signer A│ + // │ └──────┘ └──────┘ └────────┘ + // │ ▲ ▲ ▲ + // │ │ │ │ ┌──────┐ + // │ │ │ └─────┤1-of-2│◄─┐ + // │ │ │ └──────┘ │ + // ┌───────┴┐ ┌────┴───┐ ┌┴───────┐ ▲ │ + // │signer B│ │signer C│ │signer D│ │ │ + // └────────┘ └────────┘ └────────┘ │ │ + // │ │ + // ┌──────┴─┐ ┌────┴───┐ + // │signer E│ │signer F│ + // └────────┘ └────────┘ + // + // - If signers [A, B] sign, they can set a root. + // - If signers [B, D, E] sign, they can set a root. + // - If signers [B, D, E, F] sign, they can set a root. (Either E's or F's signature was + // superfluous.) + // - If signers [B, C, D] sign, they cannot set a root, because the 2-of-2 group on the second + // level isn't successful and therefore the root group isn't successful either. + // + // To map this tree to a Config, we: + // - create an entry in signers for each signer (sorted by address in ascending order) + // - assign the root group to index 0 and have it be its own parent + // - assign an index to each non-root group, such that each group's parent has a lower index + // than the group itself + // For example, we could transform the above tree structure into: + // groupQuorums = [2, 1, 2, 1] + [0, 0, ...] (rightpad with 0s to NUM_GROUPS) + // groupParents = [0, 0, 0, 2] + [0, 0, ...] (rightpad with 0s to NUM_GROUPS) + // and assuming that address(A) < address(C) < address(E) < address(F) < address(D) < address(B) + // signers = [ + // {addr: address(A), index: 0, group: 0}, {addr: address(C), index: 1, group: 1}, + // {addr: address(E), index: 2, group: 3}, {addr: address(F), index: 3, group: 3}, + // {addr: address(D), index: 4, group: 2}, {addr: address(B), index: 5, group: 1}, + // ] + struct Config { + Signer[] signers; + // groupQuorums[i] stores the quorum for the i-th signer group. Any group with + // groupQuorums[i] = 0 is considered disabled. The i-th group is successful if + // it is enabled and at least groupQuorums[i] of its children are successful. + uint8[NUM_GROUPS] groupQuorums; + // groupParents[i] stores the parent group of the i-th signer group. We ensure that the + // groups form a tree structure (where the root/0-th signer group points to itself as + // parent) by enforcing + // - (i != 0) implies (groupParents[i] < i) + // - groupParents[0] == 0 + uint8[NUM_GROUPS] groupParents; + } + + Config s_config; + + // Remember signedHashes that this contract has seen. Each signedHash can only be set once. + mapping(bytes32 => bool) s_seenSignedHashes; + + // MerkleRoots are a bit tricky since they reveal almost no information about the contents of + // the tree they authenticate. To mitigate this, we enforce that this contract can only execute + // ops from a single root at any given point in time. We further associate an expiry + // with each root to ensure that messages are executed in a timely manner. setRoot and various + // execute calls are expected to happen in quick succession. We put the expiring root and + // opCount in same struct in order to reduce gas costs of reading and writing. + struct ExpiringRootAndOpCount { + bytes32 root; + // We prefer using block.timestamp instead of block.number, as a single + // root may target many chains. We assume that block.timestamp can + // be manipulated by block producers but only within relatively tight + // bounds (a few minutes at most). + uint32 validUntil; + // each ManyChainMultiSig instance has it own independent opCount. + uint40 opCount; + } + + ExpiringRootAndOpCount s_expiringRootAndOpCount; + + /// @notice Each root also authenticates metadata about itself (stored as one of the leaves) + /// which must be revealed when the root is set. + /// + /// @dev We need to be careful that abi.encode(MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA, RootMetadata) + /// is greater than 64 bytes to prevent collisions with internal nodes in the Merkle tree. See + /// ../vendor/openzeppelin-solidity/v4.8.3/contracts/contracts/utils/cryptography/MerkleProof.sol:15 for details. + struct RootMetadata { + // chainId and multiSig uniquely identify a ManyChainMultiSig contract instance that the + // root is destined for. + // uint256 since it is unclear if we can represent chainId as uint64. There is a proposal ( + // https://ethereum-magicians.org/t/eip-2294-explicit-bound-to-chain-id/11090) to + // bound chainid to 64 bits, but it is still unresolved. + uint256 chainId; + address multiSig; + // opCount before adding this root + uint40 preOpCount; + // opCount after executing all ops in this root + uint40 postOpCount; + // override whatever root was already stored in this contract even if some of its + // ops weren't executed. + // Important: it is strongly recommended that offchain code set this to false by default. + // Be careful setting this to true as it may break assumptions about what transactions from + // the previous root have already been executed. + bool overridePreviousRoot; + } + + RootMetadata s_rootMetadata; + + /// @notice An ECDSA signature. + struct Signature { + uint8 v; + bytes32 r; + bytes32 s; + } + + /// @notice setRoot Sets a new expiring root. + /// + /// @param root is the new expiring root. + /// @param validUntil is the time by which root is valid + /// @param metadata is the authenticated metadata about the root, which is stored as one of + /// the leaves. + /// @param metadataProof is the MerkleProof of inclusion of the metadata in the Merkle tree. + /// @param signatures the ECDSA signatures on (root, validUntil). + /// + /// @dev the message (root, validUntil) should be signed by a sufficient set of signers. + /// This signature authenticates also the metadata. + /// + /// @dev this method can be executed by anyone who has the root and valid signatures. + /// as we validate the correctness of signatures, this imposes no risk. + function setRoot( + bytes32 root, + uint32 validUntil, + RootMetadata calldata metadata, + bytes32[] calldata metadataProof, + Signature[] calldata signatures + ) external { + bytes32 signedHash = ECDSA.toEthSignedMessageHash(keccak256(abi.encode(root, validUntil))); + + // Each (root, validUntil) tuple can only bet set once. For example, this prevents a + // scenario where there are two signed roots with overridePreviousRoot = true and + // an adversary keeps alternatively calling setRoot(root1), setRoot(root2), + // setRoot(root1), ... + if (s_seenSignedHashes[signedHash]) { + revert SignedHashAlreadySeen(); + } + + // verify ECDSA signatures on (root, validUntil) and ensure that the root group is successful + { + // verify sigs and count number of signers in each group + Signer memory signer; + address prevAddress = address(0x0); + uint8[NUM_GROUPS] memory groupVoteCounts; // number of votes per group + for (uint256 i = 0; i < signatures.length; i++) { + Signature calldata sig = signatures[i]; + address signerAddress = ECDSA.recover(signedHash, sig.v, sig.r, sig.s); + // the off-chain system is required to sort the signatures by the + // signer address in an increasing order + if (prevAddress >= signerAddress) { + revert SignersAddressesMustBeStrictlyIncreasing(); + } + prevAddress = signerAddress; + + signer = s_signers[signerAddress]; + if (signer.addr != signerAddress) { + revert InvalidSigner(); + } + uint8 group = signer.group; + while (true) { + groupVoteCounts[group]++; + if (groupVoteCounts[group] != s_config.groupQuorums[group]) { + // bail out unless we just hit the quorum. we only hit each quorum once, + // so we never move on to the parent of a group more than once. + break; + } + if (group == 0) { + // reached root + break; + } + + group = s_config.groupParents[group]; + } + } + // the group at the root of the tree (with index 0) determines whether the vote passed, + // we cannot proceed if it isn't configured with a valid (non-zero) quorum + if (s_config.groupQuorums[0] == 0) { + revert MissingConfig(); + } + // did the root group reach its quorum? + if (groupVoteCounts[0] < s_config.groupQuorums[0]) { + revert InsufficientSigners(); + } + } + + if (validUntil < block.timestamp) { + revert ValidUntilHasAlreadyPassed(); + } + + { + // verify metadataProof + bytes32 hashedLeaf = + keccak256(abi.encode(MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA, metadata)); + if (!MerkleProof.verify(metadataProof, root, hashedLeaf)) { + revert ProofCannotBeVerified(); + } + } + + if (block.chainid != metadata.chainId) { + revert WrongChainId(); + } + + if (address(this) != metadata.multiSig) { + revert WrongMultiSig(); + } + + uint40 opCount = s_expiringRootAndOpCount.opCount; + + // don't allow a new root to be set if there are still outstanding ops that have not been + // executed, unless overridePreviousRoot is set + if (opCount != s_rootMetadata.postOpCount && !metadata.overridePreviousRoot) { + revert PendingOps(); + } + + // the signers are responsible for tracking opCount offchain and ensuring that + // preOpCount equals to opCount + if (opCount != metadata.preOpCount) { + revert WrongPreOpCount(); + } + + if (metadata.preOpCount > metadata.postOpCount) { + revert WrongPostOpCount(); + } + + // done with validation, persist in in contract state + s_seenSignedHashes[signedHash] = true; + s_expiringRootAndOpCount = ExpiringRootAndOpCount({ + root: root, + validUntil: validUntil, + opCount: metadata.preOpCount + }); + s_rootMetadata = metadata; + emit NewRoot(root, validUntil, metadata); + } + + /// @notice an op to be executed by the ManyChainMultiSig contract + /// + /// @dev We need to be careful that abi.encode(LEAF_OP_DOMAIN_SEPARATOR, RootMetadata) + /// is greater than 64 bytes to prevent collisions with internal nodes in the Merkle tree. See + /// ../vendor/openzeppelin-solidity/v4.8.3/contracts/contracts/utils/cryptography/MerkleProof.sol:15 for details. + struct Op { + uint256 chainId; + address multiSig; + uint40 nonce; + address to; + uint256 value; + bytes data; + } + + /// @notice Execute the received op after verifying the proof of its inclusion in the + /// current Merkle tree. The op should be the next op according to the order + /// enforced by the merkle tree whose root is stored in s_expiringRootAndOpCount, i.e., the + /// nonce of the op should be equal to s_expiringRootAndOpCount.opCount. + /// + /// @param op is Op to be executed + /// @param proof is the MerkleProof for the op's inclusion in the MerkleTree which its + /// root is the s_expiringRootAndOpCount.root. + /// + /// @dev ANYONE can call this function! That's intentional. Callers can only execute verified, + /// ordered ops in the Merkle tree. + /// + /// @dev we perform a raw call to each target. Raw calls to targets that don't have associated + /// contract code will always succeed regardless of data. + /// + /// @dev the gas limit of the call can be freely determined by the caller of this function. + /// We expect callees to revert if they run out of gas. + function execute(Op calldata op, bytes32[] calldata proof) external payable { + ExpiringRootAndOpCount memory currentExpiringRootAndOpCount = s_expiringRootAndOpCount; + + if (s_rootMetadata.postOpCount <= currentExpiringRootAndOpCount.opCount) { + revert PostOpCountReached(); + } + + if (op.chainId != block.chainid) { + revert WrongChainId(); + } + + if (op.multiSig != address(this)) { + revert WrongMultiSig(); + } + + if (block.timestamp > currentExpiringRootAndOpCount.validUntil) { + revert RootExpired(); + } + + if (op.nonce != currentExpiringRootAndOpCount.opCount) { + revert WrongNonce(); + } + + // verify that the op exists in the merkle tree + bytes32 hashedLeaf = keccak256(abi.encode(MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_OP, op)); + if (!MerkleProof.verify(proof, currentExpiringRootAndOpCount.root, hashedLeaf)) { + revert ProofCannotBeVerified(); + } + + // increase the counter *before* execution to prevent reentrancy issues + s_expiringRootAndOpCount.opCount = currentExpiringRootAndOpCount.opCount + 1; + + _execute(op.to, op.value, op.data); + emit OpExecuted(op.nonce, op.to, op.data, op.value); + } + + /// @notice sets a new s_config. If clearRoot is true, then it also invalidates + /// s_expiringRootAndOpCount.root. + /// + /// @param signerAddresses holds the addresses of the active signers. The addresses must be in + /// ascending order. + /// @param signerGroups maps each signer to its group + /// @param groupQuorums holds the required number of valid signatures in each group. + /// A group i is called successful group if at least groupQuorum[i] distinct signers provide a + /// valid signature. + /// @param groupParents holds each group's parent. The groups must be arranged in a tree s.t. + /// group 0 is the root of the tree and the i-th group's parent has index j less than i. + /// Iff setRoot is called with a set of signatures that causes the root group to be successful, + /// setRoot allows a root to be set. + /// @param clearRoot, if set to true, invalidates the current root. This option is needed to + /// invalidate the current root, so to prevent further ops from being executed. This + /// might be used when the current root was signed under a loser group configuration or when + /// some previous signers aren't trusted any more. + function setConfig( + address[] calldata signerAddresses, + uint8[] calldata signerGroups, + uint8[NUM_GROUPS] calldata groupQuorums, + uint8[NUM_GROUPS] calldata groupParents, + bool clearRoot + ) external onlyOwner { + if (signerAddresses.length == 0 || signerAddresses.length > MAX_NUM_SIGNERS) { + revert OutOfBoundsNumOfSigners(); + } + + if (signerAddresses.length != signerGroups.length) { + revert SignerGroupsLengthMismatch(); + } + + { + // validate group structure + // counts the number of children of each group + uint8[NUM_GROUPS] memory groupChildrenCounts; + // first, we count the signers as children + for (uint256 i = 0; i < signerGroups.length; i++) { + if (signerGroups[i] >= NUM_GROUPS) { + revert OutOfBoundsGroup(); + } + groupChildrenCounts[signerGroups[i]]++; + } + // second, we iterate backwards so as to check each group and propagate counts from + // child group to parent groups up the tree to the root + for (uint256 j = 0; j < NUM_GROUPS; j++) { + uint256 i = NUM_GROUPS - 1 - j; + // ensure we have a well-formed group tree. the root should have itself as parent + if ((i != 0 && groupParents[i] >= i) || (i == 0 && groupParents[i] != 0)) { + revert GroupTreeNotWellFormed(); + } + bool disabled = groupQuorums[i] == 0; + if (disabled) { + // a disabled group shouldn't have any children + if (0 < groupChildrenCounts[i]) { + revert SignerInDisabledGroup(); + } + } else { + // ensure that the group quorum can be met + if (groupChildrenCounts[i] < groupQuorums[i]) { + revert OutOfBoundsGroupQuorum(); + } + groupChildrenCounts[groupParents[i]]++; + // the above line clobbers groupChildrenCounts[0] in last iteration, don't use it after the loop ends + } + } + } + + Signer[] memory oldSigners = s_config.signers; + // remove any old signer addresses + for (uint256 i = 0; i < oldSigners.length; i++) { + address oldSignerAddress = oldSigners[i].addr; + delete s_signers[oldSignerAddress]; + s_config.signers.pop(); + } + + // we cannot just write s_config = Config({...}) because solc doesn't support that + assert(s_config.signers.length == 0); + s_config.groupQuorums = groupQuorums; + s_config.groupParents = groupParents; + + // add new signers' addresses, we require that the signers' list be a strictly monotone + // increasing sequence + address prevSigner = address(0x0); + for (uint256 i = 0; i < signerAddresses.length; i++) { + if (prevSigner >= signerAddresses[i]) { + revert SignersAddressesMustBeStrictlyIncreasing(); + } + Signer memory signer = + Signer({addr: signerAddresses[i], index: uint8(i), group: signerGroups[i]}); + s_signers[signerAddresses[i]] = signer; + s_config.signers.push(signer); + prevSigner = signerAddresses[i]; + } + + if (clearRoot) { + // clearRoot is equivalent to overriding with a completely empty root + uint40 opCount = s_expiringRootAndOpCount.opCount; + s_expiringRootAndOpCount = + ExpiringRootAndOpCount({root: 0, validUntil: 0, opCount: opCount}); + s_rootMetadata = RootMetadata({ + chainId: block.chainid, + multiSig: address(this), + preOpCount: opCount, + postOpCount: opCount, + overridePreviousRoot: true + }); + } + emit ConfigSet(s_config, clearRoot); + } + + /// @notice Execute an op's call. Performs a raw call that always succeeds if the + /// target isn't a contract. + function _execute(address target, uint256 value, bytes calldata data) internal virtual { + (bool success, bytes memory ret) = target.call{value: value}(data); + if (!success) { + revert CallReverted(ret); + } + } + + /* + * Getters + */ + + function getConfig() public view returns (Config memory) { + return s_config; + } + + function getOpCount() public view returns (uint40) { + return s_expiringRootAndOpCount.opCount; + } + + function getRoot() public view returns (bytes32 root, uint32 validUntil) { + ExpiringRootAndOpCount memory currentRootAndOpCount = s_expiringRootAndOpCount; + return (currentRootAndOpCount.root, currentRootAndOpCount.validUntil); + } + + function getRootMetadata() public view returns (RootMetadata memory) { + return s_rootMetadata; + } + + /* + * Events and Errors + */ + + /// @notice Emitted when a new root is set. + event NewRoot(bytes32 indexed root, uint32 validUntil, RootMetadata metadata); + + /// @notice Emitted when a new config is set. + event ConfigSet(Config config, bool isRootCleared); + + /// @notice Emitted when an op gets successfully executed. + event OpExecuted(uint40 indexed nonce, address to, bytes data, uint256 value); + + /// @notice Thrown when number of signers is 0 or greater than MAX_NUM_SIGNERS. + error OutOfBoundsNumOfSigners(); + + /// @notice Thrown when signerAddresses and signerGroups have different lengths. + error SignerGroupsLengthMismatch(); + + /// @notice Thrown when number of some signer's group is greater than (NUM_GROUPS-1). + error OutOfBoundsGroup(); + + /// @notice Thrown when the group tree isn't well-formed. + error GroupTreeNotWellFormed(); + + /// @notice Thrown when the quorum of some group is larger than the number of signers in it. + error OutOfBoundsGroupQuorum(); + + /// @notice Thrown when a disabled group contains a signer. + error SignerInDisabledGroup(); + + /// @notice Thrown when the signers' addresses are not a strictly increasing monotone sequence. + /// Prevents signers from including more than one signature. + error SignersAddressesMustBeStrictlyIncreasing(); + + /// @notice Thrown when the signature corresponds to invalid signer. + error InvalidSigner(); + + /// @notice Thrown when there is no sufficient set of valid signatures provided to make the + /// root group successful. + error InsufficientSigners(); + + /// @notice Thrown when attempt to set metadata or execute op for another chain. + error WrongChainId(); + + /// @notice Thrown when the multiSig address in metadata or op is + /// incompatible with the address of this contract. + error WrongMultiSig(); + + /// @notice Thrown when the preOpCount <= postOpCount invariant is violated. + error WrongPostOpCount(); + + /// @notice Thrown when attempting to set a new root while there are still pending ops + /// from the previous root without explicitly overriding it. + error PendingOps(); + + /// @notice Thrown when preOpCount in metadata is incompatible with the current opCount. + error WrongPreOpCount(); + + /// @notice Thrown when the provided merkle proof cannot be verified. + error ProofCannotBeVerified(); + + /// @notice Thrown when attempt to execute an op after + /// s_expiringRootAndOpCount.validUntil has passed. + error RootExpired(); + + /// @notice Thrown when attempt to bypass the enforced ops' order in the merkle tree or + /// re-execute an op. + error WrongNonce(); + + /// @notice Thrown when attempting to execute an op even though opCount equals + /// metadata.postOpCount. + error PostOpCountReached(); + + /// @notice Thrown when the underlying call in _execute() reverts. + error CallReverted(bytes error); + + /// @notice Thrown when attempt to set past validUntil for the root. + error ValidUntilHasAlreadyPassed(); + + /// @notice Thrown when setRoot() is called before setting a config. + error MissingConfig(); + + /// @notice Thrown when attempt to set the same (root, validUntil) in setRoot(). + error SignedHashAlreadySeen(); +} \ No newline at end of file diff --git a/contracts/src/v0.8/ccip/opCodes.sol b/contracts/src/v0.8/ccip/opCodes.sol new file mode 100644 index 0000000000..40dac6a1de --- /dev/null +++ b/contracts/src/v0.8/ccip/opCodes.sol @@ -0,0 +1,322 @@ +pragma solidity >=0.4.21 <0.6.0; + +contract Test1 { + function isSameAddress(address a, address b) public returns(bool){ //Simply add the two arguments and return + if (a == b) return true; + return false; + } +} + +contract OpCodes { + + Test1 test1; + + constructor() public { //Constructor function + test1 = new Test1(); //Create new "Test1" function + } + + modifier onlyOwner(address _owner) { + require(msg.sender == _owner); + _; + } + // Add a todo to the list + function test() public { + + //simple_instructions + /*assembly { pop(sub(dup1, mul(dup1, dup1))) }*/ + + //keywords + assembly { pop(address) return(2, byte(2,1)) } + + //label_complex + /*assembly { 7 abc: 8 eq jump(abc) jumpi(eq(7, 8), abc) pop } + assembly { pop(jumpi(eq(7, 8), abc)) jump(abc) }*/ + + //functional + /*assembly { let x := 2 add(7, mul(6, x)) mul(7, 8) add =: x }*/ + + //for_statement + assembly { for { let i := 1 } lt(i, 5) { i := add(i, 1) } {} } + assembly { for { let i := 6 } gt(i, 5) { i := add(i, 1) } {} } + assembly { for { let i := 1 } slt(i, 5) { i := add(i, 1) } {} } + assembly { for { let i := 6 } sgt(i, 5) { i := add(i, 1) } {} } + + //no_opcodes_in_strict + assembly { pop(callvalue()) } + + //no_dup_swap_in_strict + /*assembly { swap1() }*/ + + //print_functional + assembly { let x := mul(sload(0x12), 7) } + + //print_if + assembly { if 2 { pop(mload(0)) }} + + //function_definitions_multiple_args + assembly { function f(a, d){ mstore(a, d) } function g(a, d) -> x, y {}} + + //sstore + assembly { function f(a, d){ sstore(a, d) } function g(a, d) -> x, y {}} + + //mstore8 + assembly { function f(a, d){ mstore8(a, d) } function g(a, d) -> x, y {}} + + //calldatacopy + assembly { + let a := mload(0x40) + let b := add(a, 32) + calldatacopy(a, 4, 32) + /*calldatacopy(b, add(4, 32), 32)*/ + /*result := add(mload(a), mload(b))*/ + } + + //codecopy + assembly { + let a := mload(0x40) + let b := add(a, 32) + codecopy(a, 4, 32) + } + + //codecopy + assembly { + let a := mload(0x40) + let b := add(a, 32) + extcodecopy(0, a, 4, 32) + } + + //for_statement + assembly { let x := calldatasize() for { let i := 0} lt(i, x) { i := add(i, 1) } { mstore(i, 2) } } + + //keccak256 + assembly { pop(keccak256(0,0)) } + + //returndatasize + assembly { let r := returndatasize } + + //returndatacopy + assembly { returndatacopy(64, 32, 0) } + //byzantium vs const Constantinople + //staticcall + assembly { pop(staticcall(10000, 0x123, 64, 0x10, 128, 0x10)) } + + /*//create2 Constantinople + assembly { pop(create2(10, 0x123, 32, 64)) }*/ + + //create Constantinople + assembly { pop(create(10, 0x123, 32)) } + + //shift Constantinople + /*assembly { pop(shl(10, 32)) } + assembly { pop(shr(10, 32)) } + assembly { pop(sar(10, 32)) }*/ + + + //not + assembly { pop( not(0x1f)) } + + //exp + assembly { pop( exp(2, 226)) } + + //mod + assembly { pop( mod(3, 9)) } + + //smod + assembly { pop( smod(3, 9)) } + + //div + assembly { pop( div(4, 2)) } + + //sdiv + assembly { pop( sdiv(4, 2)) } + + //iszero + assembly { pop(iszero(1)) } + + //and + assembly { pop(and(2,3)) } + + //or + assembly { pop(or(3,3)) } + + //xor + assembly { pop(xor(3,3)) } + + //addmod + assembly { pop(addmod(3,3,6)) } + + //mulmod + assembly { pop(mulmod(3,3,3)) } + + //signextend + assembly { pop(signextend(1, 10)) } + + //sha3 + assembly { pop(calldataload(0)) } + + //blockhash + assembly { pop(blockhash(sub(number(), 1))) } + + //balance + assembly { pop(balance(0x0)) } + + //caller + assembly { pop(caller()) } + + //codesize + assembly { pop(codesize()) } + + //extcodesize + assembly { pop(extcodesize(0x1)) } + + //origin + assembly { pop(origin()) } + + //gas + assembly { pop(gas())} + + //msize + assembly { pop(msize())} + + //pc + assembly { pop(pc())} + + //gasprice + assembly { pop(gasprice())} + + //coinbase + assembly { pop(coinbase())} + + //timestamp + assembly { pop(timestamp())} + + //number + assembly { pop(number())} + + //difficulty + assembly { pop(difficulty())} + + //gaslimit + assembly { pop(gaslimit())} + + //call + address contractAddr = address(test1); + bytes4 sig = bytes4(keccak256("isSameAddress(address,address)")); //Function signature + address a = msg.sender; + + assembly { + let x := mload(0x40) //Find empty storage location using "free memory pointer" + mstore(x,sig) //Place signature at beginning of empty storage + mstore(add(x,0x04),a) // first address parameter. just after signature + mstore(add(x,0x24),a) // 2nd address parameter - first padded. add 32 bytes (not 20 bytes) + mstore(0x40,add(x,0x64)) // this is missing in other examples. Set free pointer before function call. so it is used by called function. + // new free pointer position after the output values of the called function. + + let success := call( + 5000, //5k gas + contractAddr, //To addr + 0, //No wei passed + x, // Inputs are at location x + 0x44, //Inputs size two padded, so 68 bytes + x, //Store output over input + 0x20) //Output is 32 bytes long + } + + //callcode + assembly { + let x := mload(0x40) //Find empty storage location using "free memory pointer" + mstore(x,sig) //Place signature at beginning of empty storage + mstore(add(x,0x04),a) // first address parameter. just after signature + mstore(add(x,0x24),a) // 2nd address parameter - first padded. add 32 bytes (not 20 bytes) + mstore(0x40,add(x,0x64)) // this is missing in other examples. Set free pointer before function call. so it is used by called function. + // new free pointer position after the output values of the called function. + + let success := callcode( + 5000, //5k gas + contractAddr, //To addr + 0, //No wei passed + x, // Inputs are at location x + 0x44, //Inputs size two padded, so 68 bytes + x, //Store output over input + 0x20) //Output is 32 bytes long + } + + //delegatecall + assembly { + let x := mload(0x40) //Find empty storage location using "free memory pointer" + mstore(x,sig) //Place signature at beginning of empty storage + mstore(add(x,0x04),a) // first address parameter. just after signature + mstore(add(x,0x24),a) // 2nd address parameter - first padded. add 32 bytes (not 20 bytes) + mstore(0x40,add(x,0x64)) // this is missing in other examples. Set free pointer before function call. so it is used by called function. + // new free pointer position after the output values of the called function. + + let success := delegatecall( + 5000, //5k gas + contractAddr, //To addr + x, // Inputs are at location x + 0x44, //Inputs size two padded, so 68 bytes + x, //Store output over input + 0x20) //Output is 32 bytes long + } + + uint256 _id = 0x420042; + + //log0 + log0( + bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20) + ); + + //log1 + log1( + bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20), + bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20) + ); + + //log2 + log2( + bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20), + bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20), + bytes32(uint256(msg.sender)) + ); + + //log3 + log3( + bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20), + bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20), + bytes32(uint256(msg.sender)), + bytes32(_id) + ); + + //log4 + log4( + bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20), + bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20), + bytes32(uint256(msg.sender)), + bytes32(_id), + bytes32(_id) + + ); + + //selfdestruct + assembly { selfdestruct(0x02) } + } + + function test_revert() public { + + //revert + assembly{ revert(0, 0) } + } + + function test_invalid() public { + + //revert + assembly{ invalid() } + } + + function test_stop() public { + + //revert + assembly{ stop() } + } + +} \ No newline at end of file diff --git a/core/gethwrappers/ccip/go_gen_zk.go b/core/gethwrappers/ccip/go_gen_zk.go index 9ebc3016e5..0515670a72 100644 --- a/core/gethwrappers/ccip/go_gen_zk.go +++ b/core/gethwrappers/ccip/go_gen_zk.go @@ -1,24 +1,11 @@ package ccip -// go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/ARMProxy/ARMProxy.abi ../../../contracts/solc/v0.8.24/ARMProxy/ARMProxy.bin RMNProxyContract rmn_proxy_contract ../../../contracts/zksolc/v0.8.24/ARMProxy/ARMProxy.sol/ARMProxy.zbin -// go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/RMN/RMN.abi ../../../contracts/solc/v0.8.24/RMN/RMN.bin RMNContract rmn_contract ../../../contracts/zksolc/v0.8.24/RMN/RMN.sol/RMN.zbin - -// go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/TokenAdminRegistry/TokenAdminRegistry.abi ../../../contracts/solc/v0.8.24/TokenAdminRegistry/TokenAdminRegistry.bin TokenAdminRegistry token_admin_registry ../../../contracts/zksolc/v0.8.24/TokenAdminRegistry/TokenAdminRegistry.sol/TokenAdminRegistry.zbin -// go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/RegistryModuleOwnerCustom/RegistryModuleOwnerCustom.abi ../../../contracts/solc/v0.8.24/RegistryModuleOwnerCustom/RegistryModuleOwnerCustom.bin RegistryModuleOwnerCustom registry_module_owner_custom ../../../contracts/zksolc/v0.8.24/RegistryModuleOwnerCustom/RegistryModuleOwnerCustom.sol/RegistryModuleOwnerCustom.zbin -// go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/FeeQuoter/FeeQuoter.abi ../../../contracts/solc/v0.8.24/FeeQuoter/FeeQuoter.bin FeeQuoter fee_quoter ../../../contracts/zksolc/v0.8.24/FeeQuoter/FeeQuoter.sol/FeeQuoter.zbin - -// go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/EVM2EVMOnRamp/EVM2EVMOnRamp.abi ../../../contracts/solc/v0.8.24/EVM2EVMOnRamp/EVM2EVMOnRamp.bin EVM2EVMOnRamp evm_2_evm_onramp ../../../contracts/zksolc/v0.8.24/EVM2EVMOnRamp/EVM2EVMOnRamp.sol/EVM2EVMOnRamp.zbin -// go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/EVM2EVMOffRamp/EVM2EVMOffRamp.abi ../../../contracts/solc/v0.8.24/EVM2EVMOffRamp/EVM2EVMOffRamp.bin EVM2EVMOffRamp evm_2_evm_offramp ../../../contracts/zksolc/v0.8.24/EVM2EVMOffRamp/EVM2EVMOffRamp.sol/EVM2EVMOffRamp.zbin -// go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/CommitStore/CommitStore.abi ../../../contracts/solc/v0.8.24/CommitStore/CommitStore.bin CommitStore commit_store ../../../contracts/zksolc/v0.8.24/CommitStore/CommitStore.sol/CommitStore.zbin - -// go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/Router/Router.abi ../../../contracts/solc/v0.8.24/Router/Router.bin Router router ../../../contracts/zksolc/v0.8.24/Router/Router.sol/Router.zbin -// go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/PriceRegistry/PriceRegistry.abi ../../../contracts/solc/v0.8.24/PriceRegistry/PriceRegistry.bin PriceRegistry price_registry_1_2_0 ../../../contracts/zksolc/v0.8.24/PriceRegistry/PriceRegistry.sol/PriceRegistry.zbin +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/Storage/Storage.abi ../../../contracts/solc/v0.8.24/Storage/Storage.bin Storage storage ../../../contracts/zksolc/v0.8.24/Storage/Storage.sol/Storage.zbin -// go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/LockReleaseTokenPool/LockReleaseTokenPool.abi ../../../contracts/solc/v0.8.24/LockReleaseTokenPool/LockReleaseTokenPool.bin LockReleaseTokenPool lock_release_token_pool ../../../contracts/zksolc/v0.8.24/LockReleaseTokenPool/LockReleaseTokenPool.sol/LockReleaseTokenPool.zbin +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/EVMCustom/EVMCustomTest.abi ../../../contracts/solc/v0.8.24/EVMCustom/EVMCustomTest.bin EvmcustomContract evmcustom_contract ../../../contracts/zksolc/v0.8.24/EVMCustom/EVMCustom.sol/EVMCustomTest.zbin -//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/LinkToken/LinkToken.abi ../../../contracts/solc/v0.8.19/LinkToken/LinkToken.bin LinkToken link_token ../../../contracts/zksolc/v0.8.19/LinkToken/LinkToken.sol/LinkToken.zbin -//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/WETH9/WETH9.abi ../../../contracts/solc/v0.8.24/WETH9/WETH9.bin WETH9 weth9 ../../../contracts/zksolc/v0.8.24/WETH9/WETH9.sol/WETH9.zbin +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.5.0/opCodes/OpCodes.abi ../../../contracts/solc/v0.5.0/opCodes/OpCodes.bin OpcodesContract opcodes_contract ../../../contracts/zksolc/v0.5.0/opCodes/opCodes.sol/OpCodes.zbin -//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/SelfFundedPingPong/SelfFundedPingPong.abi ../../../contracts/solc/v0.8.24/SelfFundedPingPong/SelfFundedPingPong.bin SelfFundedPingPong self_funded_ping_pong ../../../contracts/zksolc/v0.8.24/SelfFundedPingPong/SelfFundedPingPong.sol/SelfFundedPingPong.zbin +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/manyChainMultisig/ManyChainMultiSig.abi ../../../contracts/solc/v0.8.19/manyChainMultisig/ManyChainMultiSig.bin ManychainmultisigContract manychainmultisig_contract ../../../contracts/zksolc/v0.8.19/manyChainMultisig/ManyChainMultiSig.sol/ManyChainMultiSig.zbin -//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/Storage/Storage.abi ../../../contracts/solc/v0.8.24/Storage/Storage.bin Storage storage ../../../contracts/zksolc/v0.8.24/Storage/Storage.sol/Storage.zbin +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/RBACTimelock/RBACTimelock.abi ../../../contracts/solc/v0.8.19/RBACTimelock/RBACTimelock.bin RbactimelockContract rbactimelock_contract ../../../contracts/zksolc/v0.8.19/RBACTimelock/RBACTimelock.sol/RBACTimelock.zbin