diff --git a/zksync/src/contracts/utils/ScriptUtilsZkSync.sol b/zksync/src/contracts/utils/ScriptUtilsZkSync.sol index b531ccf..285ce12 100644 --- a/zksync/src/contracts/utils/ScriptUtilsZkSync.sol +++ b/zksync/src/contracts/utils/ScriptUtilsZkSync.sol @@ -3,20 +3,77 @@ pragma solidity ^0.8.0; import {ICreate2Factory} from './interfaces/ICreate2Factory.sol'; +/*** + * @title Create2UtilsZkSync + * @author BGD Labs + * @notice Script utilities to create smart contracts with deterministic addresses on the zkSync network. + * @dev Deployment on the zkSync network with Foundry differs from the Ethereum network. To deploy a contract + * on the zkSync network using the CREATE2 opcode, you need to either use the Solidity structure: `new Contract{salt: salt}()` or + * use the Create2Factory contract from the era-contracts repository. When using the Create2Factory to deploy a smart contract with + * a deterministic address, you need to provide not the contract creation code, but the hash of the contract bytecode. + * Then zkSync tries to match the provided bytecode hash with contracts that already exist on the network. If it finds one, + * the contract will be deployed. These utils use the Create2Factory contract, so before trying to deploy a contract with a + * deterministic address, you first need to deploy it at a random address to let zkSync remember its bytecode. + * @dev This library uses the contract's creation code to compute the bytecode hash. + * You can get the creation code using `type(Contract).creationCode`. + * IMPORTANT: There might be a bug while compiling and executing tests or scripts with the --zksync flag. + * If you simply use `type(Contract).creationCode`, it will return the solc compiler bytecode. However, if + * `type(Contract).creationCode` is used in a deployed contract, it will return the correct (zksolc) bytecode. + * So, if you want to use a function with creation code, you need to get it from the contract like this: + * + * contract ContractBytecodeRetriever { + * function getCreationCode() external view returns (bytes memory) { + * return type(Contract).creationCode; + * } + * } + * + */ library Create2UtilsZkSync { // https://github.com/matter-labs/era-contracts/blob/main/system-contracts/contracts/Create2Factory.sol address public constant CREATE2_FACTORY = 0x0000000000000000000000000000000000010000; - // @dev the bytecodeHash is the unsanitized input we get via `type(Contract).creationCode` - function create2Deploy(bytes32 salt, bytes memory bytecodeHash, bytes memory arguments) internal returns (address) { - return create2Deploy(salt, bytes32(sliceBytes(bytecodeHash, 36, 32)), arguments); + /** + * @dev Deploys a contract using the CREATE2 opcode. + * @param salt A salt to influence the address of the deployed contract. + * @param creationCode The creation bytecode of the contract to be deployed. IMPORTANT: Check the library description + * @param arguments The constructor arguments for the contract. + * @return The address of the deployed contract. + */ + function create2Deploy( + bytes32 salt, + bytes memory creationCode, + bytes memory arguments + ) internal returns (address) { + return create2Deploy(salt, getBytecodeHashFromBytecode(creationCode), arguments); + } + + /** + * @dev Deploys a contract using the CREATE2 opcode. + * @param salt A salt to influence the address of the deployed contract. + * @param creationCode The bytecode of the contract to be deployed. IMPORTANT: Check the library description + * @return The address of the deployed contract. + */ + function create2Deploy(bytes32 salt, bytes memory creationCode) internal returns (address) { + return create2Deploy(salt, getBytecodeHashFromBytecode(creationCode)); } - // @dev the bytecodeHash is the unsanitized input we get via `type(Contract).creationCode` - function create2Deploy(bytes32 salt, bytes memory bytecodeHash) internal returns (address) { - return create2Deploy(salt, bytes32(sliceBytes(bytecodeHash, 36, 32))); + /** + * @dev Deploys a contract using the CREATE2 opcode. + * @param salt A salt to influence the address of the deployed contract. + * @param bytecodeHash The bytecode hash of the contract to be deployed. + * @return The address of the deployed contract. + */ + function create2Deploy(bytes32 salt, bytes32 bytecodeHash) internal returns (address) { + return create2Deploy(salt, bytecodeHash, ''); } + /** + * @dev Deploys a contract using the CREATE2 opcode. + * @param salt A salt to influence the address of the deployed contract. + * @param bytecodeHash The bytecode hash of the contract to be deployed. + * @param arguments The constructor arguments for the contract. + * @return The address of the deployed contract. + */ function create2Deploy( bytes32 salt, bytes32 bytecodeHash, @@ -30,37 +87,56 @@ library Create2UtilsZkSync { if (isContractDeployed(computed)) { return computed; } else { - address deployedAt = ICreate2Factory(CREATE2_FACTORY).create2( - salt, - bytecodeHash, - arguments - ); + address deployedAt = ICreate2Factory(CREATE2_FACTORY).create2(salt, bytecodeHash, arguments); require(deployedAt == computed, 'failure at create2 address derivation'); return deployedAt; } } - function create2Deploy(bytes32 salt, bytes32 bytecodeHash) internal returns (address) { - if (isContractDeployed(CREATE2_FACTORY) == false) { - revert('MISSING_CREATE2_FACTORY'); - } - address computed = computeCreate2Address(salt, bytecodeHash); + /** + * @dev Checks if a contract is deployed at the given address. + * @param _addr The address to check. + * @return isContract True if a contract is deployed at the address, false otherwise. + */ + function isContractDeployed(address _addr) internal view returns (bool isContract) { + return (_addr.code.length > 0); + } - if (isContractDeployed(computed)) { - return computed; - } else { - address deployedAt = ICreate2Factory(CREATE2_FACTORY).create2( - salt, - bytecodeHash, - '' - ); + /** + * @dev Computes the CREATE2 address for a contract deployment. + * @param salt A salt to ensure unique addresses. + * @param creationCode The bytecode of the contract to be deployed. IMPORTANT: Check the library description + * @return The computed CREATE2 address. + */ + function computeCreate2Address( + bytes32 salt, + bytes memory creationCode + ) internal pure returns (address) { + return computeCreate2Address(salt, getBytecodeHashFromBytecode(creationCode)); + } - require(deployedAt == computed, 'failure at create2 address derivation'); - return deployedAt; - } + /** + * @dev Computes the CREATE2 address for a contract deployment with constructor arguments. + * @param salt A salt to ensure unique addresses. + * @param creationCode The bytecode of the contract to be deployed. IMPORTANT: Check the library description + * @param arguments The constructor arguments for the contract. + * @return The computed CREATE2 address. + */ + function computeCreate2Address( + bytes32 salt, + bytes memory creationCode, + bytes memory arguments + ) internal pure returns (address) { + return computeCreate2Address(salt, getBytecodeHashFromBytecode(creationCode), arguments); } + /** + * @dev Computes the CREATE2 address for a contract deployment using a bytecode hash. + * @param salt A salt to ensure unique addresses. + * @param bytecodeHash The hash of the contract bytecode. + * @return The computed CREATE2 address. + */ function computeCreate2Address( bytes32 salt, bytes32 bytecodeHash @@ -68,6 +144,13 @@ library Create2UtilsZkSync { return computeCreate2Address(salt, bytecodeHash, ''); } + /** + * @dev Computes the CREATE2 address for a contract deployment using a bytecode hash and constructor arguments. + * @param salt A salt to ensure unique addresses. + * @param bytecodeHash The hash of the contract bytecode. + * @param arguments The constructor arguments for the contract. + * @return The computed CREATE2 address. + */ function computeCreate2Address( bytes32 salt, bytes32 bytecodeHash, @@ -86,28 +169,30 @@ library Create2UtilsZkSync { return address(uint160(uint256(addressHash))); } - function isContractDeployed(address _addr) internal view returns (bool isContract) { - return (_addr.code.length > 0); - } + /** + * @dev Extracts a 32-byte hash from the given bytecode. + * To deploy smart contracts on the zkSync network, you need to provide + * the zkSync contract bytecode hash. This is a specific hash for the zkSync network. + * This hash is stored in the contract creation bytecode, starting from + * the 36th byte and has a length of 32 bytes. This function extracts this hash. + * @param data The bytecode from which to extract the hash. + * @return The extracted 32-byte hash. + */ + function getBytecodeHashFromBytecode(bytes memory data) internal pure returns (bytes32) { + uint256 start = 36; + uint256 length = 32; + require(start + length <= data.length, 'Invalid bytecode'); - function sliceBytes(bytes memory data, uint256 start, uint256 length) internal pure returns (bytes memory) { - require(start + length <= data.length, 'Slice out of bounds'); bytes memory result = new bytes(length); - assembly { let dataPtr := add(data, 32) let resultPtr := add(result, 32) - // Copy the slice - let words := div(add(length, 31), 32) - let srcPtr := add(dataPtr, start) - - for { let i := 0 } lt(i, words) { i := add(i, 1) } { - mstore(add(resultPtr, mul(i, 32)), mload(add(srcPtr, mul(i, 32)))) - } + // Use mcopy to efficiently copy the slice + mcopy(resultPtr, add(dataPtr, start), length) mstore(result, length) } - return result; + return bytes32(result); } }