From 6e6b8520fe2d0c6d68c8acb516b98a4d12d755ef Mon Sep 17 00:00:00 2001 From: Vladislav Volosnikov Date: Fri, 6 Dec 2024 19:22:18 +0100 Subject: [PATCH] fix(EVM): Make CREATE flow equivalent to EVM (#1127) --- .../contracts/ContractDeployer.sol | 9 +- system-contracts/contracts/EvmEmulator.yul | 86 +++++++++++++------ .../EvmEmulatorFunctions.template.yul | 43 +++++++--- 3 files changed, 94 insertions(+), 44 deletions(-) diff --git a/system-contracts/contracts/ContractDeployer.sol b/system-contracts/contracts/ContractDeployer.sol index 7032bacad..3b36ed732 100644 --- a/system-contracts/contracts/ContractDeployer.sol +++ b/system-contracts/contracts/ContractDeployer.sol @@ -238,13 +238,6 @@ contract ContractDeployer is IContractDeployer, SystemContractBase { newAddress = Utils.getNewAddressCreateEVM(msg.sender, senderNonce); } - // Unfortunately we can not provide revert reason as it would break EVM compatibility - // we should not increase nonce in case of collision - // solhint-disable-next-line reason-string, gas-custom-errors - require(NONCE_HOLDER_SYSTEM_CONTRACT.getRawNonce(newAddress) == 0x0); - // solhint-disable-next-line reason-string, gas-custom-errors - require(ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT.getCodeHash(uint256(uint160(newAddress))) == 0x0); - return newAddress; } @@ -257,7 +250,7 @@ contract ContractDeployer is IContractDeployer, SystemContractBase { address _newAddress, bytes calldata _initCode ) external payable onlySystemCallFromEvmEmulator returns (uint256, address) { - uint256 constructorReturnEvmGas = _evmDeployOnAddress(msg.sender, _newAddress, _initCode); + uint256 constructorReturnEvmGas = _performDeployOnAddressEVM(msg.sender, _newAddress, AccountAbstractionVersion.None, _initCode); return (constructorReturnEvmGas, _newAddress); } diff --git a/system-contracts/contracts/EvmEmulator.yul b/system-contracts/contracts/EvmEmulator.yul index f1a37ca8d..c913a49ad 100644 --- a/system-contracts/contracts/EvmEmulator.yul +++ b/system-contracts/contracts/EvmEmulator.yul @@ -1134,9 +1134,15 @@ object "EvmEmulator" { function _executeCreate(offset, size, value, evmGasLeftOld, isCreate2, salt) -> evmGasLeft, addr { let gasForTheCall := capGasForCall(evmGasLeftOld, evmGasLeftOld) // pass 63/64 of remaining gas - let bytecodeHash := 0 + let bytecodeHash if isCreate2 { - bytecodeHash := keccak256(offset, size) + switch size + case 0 { + bytecodeHash := EMPTY_KECCAK() + } + default { + bytecodeHash := keccak256(offset, size) + } } // we want to calculate the address of new contract, and if it is deployable (no collision), @@ -1146,22 +1152,35 @@ object "EvmEmulator" { mstore(0, 0xf81dae8600000000000000000000000000000000000000000000000000000000) mstore(4, salt) mstore(36, bytecodeHash) - let precreateResult := performSystemCallRevertable(DEPLOYER_SYSTEM_CONTRACT(), 68) - - if iszero(precreateResult) { - // Collision, nonce overflow or EVM not allowed. - // This is *internal* panic, consuming all passed gas. - // Note: we should not consume all gas if nonce overflowed, but this should not happen in reality anyway - evmGasLeft := chargeGas(evmGasLeftOld, gasForTheCall) - } + let canBeDeployed := performSystemCallRevertable(DEPLOYER_SYSTEM_CONTRACT(), 68) - if precreateResult { + if canBeDeployed { returndatacopy(0, 0, 32) addr := mload(0) pop($llvm_AlwaysInline_llvm$_warmAddress(addr)) // will stay warm even if constructor reverts // so even if constructor reverts, nonce stays incremented and addr stays warm - + + // check for code collision + canBeDeployed := 0 + if iszero(getRawCodeHash(addr)) { + // check for nonce collision + if iszero(getRawNonce(addr)) { + canBeDeployed := 1 + } + } + } + + if iszero(canBeDeployed) { + // Nonce overflow, EVM not allowed or collision. + // This is *internal* panic, consuming all passed gas. + // Note: we should not consume all gas if nonce overflowed, but this should not happen in reality anyway + evmGasLeft := chargeGas(evmGasLeftOld, gasForTheCall) + addr := 0 + } + + + if canBeDeployed { // verification of the correctness of the deployed bytecode and payment of gas for its storage will occur in the frame of the new contract pushEvmFrame(gasForTheCall, false) @@ -4292,9 +4311,15 @@ object "EvmEmulator" { function _executeCreate(offset, size, value, evmGasLeftOld, isCreate2, salt) -> evmGasLeft, addr { let gasForTheCall := capGasForCall(evmGasLeftOld, evmGasLeftOld) // pass 63/64 of remaining gas - let bytecodeHash := 0 + let bytecodeHash if isCreate2 { - bytecodeHash := keccak256(offset, size) + switch size + case 0 { + bytecodeHash := EMPTY_KECCAK() + } + default { + bytecodeHash := keccak256(offset, size) + } } // we want to calculate the address of new contract, and if it is deployable (no collision), @@ -4304,22 +4329,35 @@ object "EvmEmulator" { mstore(0, 0xf81dae8600000000000000000000000000000000000000000000000000000000) mstore(4, salt) mstore(36, bytecodeHash) - let precreateResult := performSystemCallRevertable(DEPLOYER_SYSTEM_CONTRACT(), 68) - - if iszero(precreateResult) { - // Collision, nonce overflow or EVM not allowed. - // This is *internal* panic, consuming all passed gas. - // Note: we should not consume all gas if nonce overflowed, but this should not happen in reality anyway - evmGasLeft := chargeGas(evmGasLeftOld, gasForTheCall) - } + let canBeDeployed := performSystemCallRevertable(DEPLOYER_SYSTEM_CONTRACT(), 68) - if precreateResult { + if canBeDeployed { returndatacopy(0, 0, 32) addr := mload(0) pop($llvm_AlwaysInline_llvm$_warmAddress(addr)) // will stay warm even if constructor reverts // so even if constructor reverts, nonce stays incremented and addr stays warm - + + // check for code collision + canBeDeployed := 0 + if iszero(getRawCodeHash(addr)) { + // check for nonce collision + if iszero(getRawNonce(addr)) { + canBeDeployed := 1 + } + } + } + + if iszero(canBeDeployed) { + // Nonce overflow, EVM not allowed or collision. + // This is *internal* panic, consuming all passed gas. + // Note: we should not consume all gas if nonce overflowed, but this should not happen in reality anyway + evmGasLeft := chargeGas(evmGasLeftOld, gasForTheCall) + addr := 0 + } + + + if canBeDeployed { // verification of the correctness of the deployed bytecode and payment of gas for its storage will occur in the frame of the new contract pushEvmFrame(gasForTheCall, false) diff --git a/system-contracts/evm-emulator/EvmEmulatorFunctions.template.yul b/system-contracts/evm-emulator/EvmEmulatorFunctions.template.yul index eb24341b3..28edace55 100644 --- a/system-contracts/evm-emulator/EvmEmulatorFunctions.template.yul +++ b/system-contracts/evm-emulator/EvmEmulatorFunctions.template.yul @@ -1072,9 +1072,15 @@ function $llvm_NoInline_llvm$_genericCreate(offset, size, value, evmGasLeftOld, function _executeCreate(offset, size, value, evmGasLeftOld, isCreate2, salt) -> evmGasLeft, addr { let gasForTheCall := capGasForCall(evmGasLeftOld, evmGasLeftOld) // pass 63/64 of remaining gas - let bytecodeHash := 0 + let bytecodeHash if isCreate2 { - bytecodeHash := keccak256(offset, size) + switch size + case 0 { + bytecodeHash := EMPTY_KECCAK() + } + default { + bytecodeHash := keccak256(offset, size) + } } // we want to calculate the address of new contract, and if it is deployable (no collision), @@ -1084,22 +1090,35 @@ function _executeCreate(offset, size, value, evmGasLeftOld, isCreate2, salt) -> mstore(0, 0xf81dae8600000000000000000000000000000000000000000000000000000000) mstore(4, salt) mstore(36, bytecodeHash) - let precreateResult := performSystemCallRevertable(DEPLOYER_SYSTEM_CONTRACT(), 68) + let canBeDeployed := performSystemCallRevertable(DEPLOYER_SYSTEM_CONTRACT(), 68) - if iszero(precreateResult) { - // Collision, nonce overflow or EVM not allowed. - // This is *internal* panic, consuming all passed gas. - // Note: we should not consume all gas if nonce overflowed, but this should not happen in reality anyway - evmGasLeft := chargeGas(evmGasLeftOld, gasForTheCall) - } - - if precreateResult { + if canBeDeployed { returndatacopy(0, 0, 32) addr := mload(0) pop($llvm_AlwaysInline_llvm$_warmAddress(addr)) // will stay warm even if constructor reverts // so even if constructor reverts, nonce stays incremented and addr stays warm - + + // check for code collision + canBeDeployed := 0 + if iszero(getRawCodeHash(addr)) { + // check for nonce collision + if iszero(getRawNonce(addr)) { + canBeDeployed := 1 + } + } + } + + if iszero(canBeDeployed) { + // Nonce overflow, EVM not allowed or collision. + // This is *internal* panic, consuming all passed gas. + // Note: we should not consume all gas if nonce overflowed, but this should not happen in reality anyway + evmGasLeft := chargeGas(evmGasLeftOld, gasForTheCall) + addr := 0 + } + + + if canBeDeployed { // verification of the correctness of the deployed bytecode and payment of gas for its storage will occur in the frame of the new contract pushEvmFrame(gasForTheCall, false)