diff --git a/system-contracts/contracts/EvmEmulator.yul b/system-contracts/contracts/EvmEmulator.yul index 181e8c7fd8..27ff7f3276 100644 --- a/system-contracts/contracts/EvmEmulator.yul +++ b/system-contracts/contracts/EvmEmulator.yul @@ -398,8 +398,7 @@ object "EvmEmulator" { isEVM := fetchFromSystemContract(ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT(), 36) } - function isConstructedEvmContract(addr) -> isConstructedEVM { - let rawCodeHash := getRawCodeHash(addr) + function isHashOfConstructedEvmContract(rawCodeHash) -> isConstructedEVM { let version := shr(248, rawCodeHash) let isConstructedFlag := xor(shr(240, rawCodeHash), 1) isConstructedEVM := and(eq(version, 2), isConstructedFlag) @@ -693,21 +692,16 @@ object "EvmEmulator" { //////////////////////////////////////////////////////////////// function performCall(oldSp, evmGasLeft, oldStackHead, isStatic) -> newGasLeft, sp, stackHead { - let gasToPass, addr, value, argsOffset, argsSize, retOffset, retSize + let gasToPass, rawAddr, value, argsOffset, argsSize, retOffset, retSize popStackCheck(oldSp, 7) gasToPass, sp, stackHead := popStackItemWithoutCheck(oldSp, oldStackHead) - addr, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) + rawAddr, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) value, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) argsOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) argsSize, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) retOffset, sp, retSize := popStackItemWithoutCheck(sp, stackHead) - addr := and(addr, 0xffffffffffffffffffffffffffffffffffffffff) - - checkMemIsAccessible(argsOffset, argsSize) - checkMemIsAccessible(retOffset, retSize) - // static_gas = 0 // dynamic_gas = memory_expansion_cost + code_execution_cost + address_access_cost + positive_value_cost + value_to_empty_account_cost // code_execution_cost is the cost of the called code execution (limited by the gas parameter). @@ -715,13 +709,7 @@ object "EvmEmulator" { // If value is not 0, then positive_value_cost is 9000. In this case there is also a call stipend that is given to make sure that a basic fallback function can be called. // If value is not 0 and the address given points to an empty account, then value_to_empty_account_cost is 25000. An account is empty if its balance is 0, its nonce is 0 and it has no code. - let gasUsed := 100 // warm address access cost - if iszero($llvm_AlwaysInline_llvm$_warmAddress(addr)) { - gasUsed := 2600 // cold address access cost - } - - // memory_expansion_cost - gasUsed := add(gasUsed, expandMemory2(retOffset, retSize, argsOffset, argsSize)) + let addr, gasUsed := _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize) if gt(value, 0) { if isStatic { @@ -759,27 +747,16 @@ object "EvmEmulator" { } function performStaticCall(oldSp, evmGasLeft, oldStackHead) -> newGasLeft, sp, stackHead { - let gasToPass,addr, argsOffset, argsSize, retOffset, retSize + let gasToPass, rawAddr, argsOffset, argsSize, retOffset, retSize popStackCheck(oldSp, 6) gasToPass, sp, stackHead := popStackItemWithoutCheck(oldSp, oldStackHead) - addr, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) + rawAddr, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) argsOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) argsSize, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) retOffset, sp, retSize := popStackItemWithoutCheck(sp, stackHead) - addr := and(addr, 0xffffffffffffffffffffffffffffffffffffffff) - - checkMemIsAccessible(argsOffset, argsSize) - checkMemIsAccessible(retOffset, retSize) - - let gasUsed := 100 // warm address access cost - if iszero($llvm_AlwaysInline_llvm$_warmAddress(addr)) { - gasUsed := 2600 // cold address access cost - } - - // memory_expansion_cost - gasUsed := add(gasUsed, expandMemory2(retOffset, retSize, argsOffset, argsSize)) + let addr, gasUsed := _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize) evmGasLeft := chargeGas(evmGasLeft, gasUsed) gasToPass := capGasForCall(evmGasLeft, gasToPass) @@ -802,59 +779,82 @@ object "EvmEmulator" { function performDelegateCall(oldSp, evmGasLeft, isStatic, oldStackHead) -> newGasLeft, sp, stackHead { - let addr, gasToPass, argsOffset, argsSize, retOffset, retSize + let gasToPass, rawAddr, argsOffset, argsSize, retOffset, retSize popStackCheck(oldSp, 6) gasToPass, sp, stackHead := popStackItemWithoutCheck(oldSp, oldStackHead) - addr, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) + rawAddr, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) argsOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) argsSize, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) retOffset, sp, retSize := popStackItemWithoutCheck(sp, stackHead) - addr := and(addr, 0xffffffffffffffffffffffffffffffffffffffff) + let addr, gasUsed := _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize) - checkMemIsAccessible(argsOffset, argsSize) - checkMemIsAccessible(retOffset, retSize) + newGasLeft := chargeGas(evmGasLeft, gasUsed) + gasToPass := capGasForCall(newGasLeft, gasToPass) - let gasUsed := 100 // warm address access cost - if iszero($llvm_AlwaysInline_llvm$_warmAddress(addr)) { - gasUsed := 2600 // cold address access cost - } + newGasLeft := sub(newGasLeft, gasToPass) - // memory_expansion_cost - gasUsed := add(gasUsed, expandMemory2(retOffset, retSize, argsOffset, argsSize)) + let success + let frameGasLeft := gasToPass - evmGasLeft := chargeGas(evmGasLeft, gasUsed) + let rawCodeHash := getRawCodeHash(addr) + switch isHashOfConstructedEvmContract(rawCodeHash) + case 0 { + // Not a constructed EVM contract + let precompileCost := getGasForPrecompiles(addr, argsOffset, argsSize) + switch precompileCost + case 0 { + // Not a precompile + switch eq(1, shr(248, rawCodeHash)) + case 0 { + // Empty contract or EVM contract being constructed + success := delegatecall(gas(), addr, add(MEM_OFFSET(), argsOffset), argsSize, retOffset, retSize) + _saveReturndataAfterZkEVMCall() + } + default { + // We forbid delegatecalls to EraVM native contracts + _eraseReturndataPointer() + } + } + default { + // Precompile. Simlate using staticcall, since EraVM behavior differs here + success, frameGasLeft := callPrecompile(addr, precompileCost, gasToPass, 0, argsOffset, argsSize, retOffset, retSize, true) + } + } + default { + // Constructed EVM contract + pushEvmFrame(gasToPass, isStatic) + // pass all remaining native gas + success := delegatecall(gas(), addr, add(MEM_OFFSET(), argsOffset), argsSize, 0, 0) - // it is also not possible to delegatecall precompiles - if iszero(isEvmContract(addr)) { - revertWithGas(evmGasLeft) + frameGasLeft := _saveReturndataAfterEVMCall(add(MEM_OFFSET(), retOffset), retSize) + if iszero(success) { + resetEvmFrame() + } } - gasToPass := capGasForCall(evmGasLeft, gasToPass) - evmGasLeft := sub(evmGasLeft, gasToPass) + newGasLeft := add(newGasLeft, frameGasLeft) + stackHead := success + } - pushEvmFrame(gasToPass, isStatic) - let success := delegatecall( - gas(), // pass all remaining native gas - addr, - add(MEM_OFFSET(), argsOffset), - argsSize, - 0, - 0 - ) + function _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize) -> addr, gasUsed { + addr := and(rawAddr, 0xffffffffffffffffffffffffffffffffffffffff) - let frameGasLeft := _saveReturndataAfterEVMCall(add(MEM_OFFSET(), retOffset), retSize) - if iszero(success) { - resetEvmFrame() + checkMemIsAccessible(argsOffset, argsSize) + checkMemIsAccessible(retOffset, retSize) + + gasUsed := 100 // warm address access cost + if iszero($llvm_AlwaysInline_llvm$_warmAddress(addr)) { + gasUsed := 2600 // cold address access cost } - newGasLeft := add(evmGasLeft, frameGasLeft) - stackHead := success + // memory_expansion_cost + gasUsed := add(gasUsed, expandMemory2(retOffset, retSize, argsOffset, argsSize)) } function _genericCall(addr, gasToPass, value, argsOffset, argsSize, retOffset, retSize, isStatic) -> success, frameGasLeft { - switch isConstructedEvmContract(addr) + switch isHashOfConstructedEvmContract(getRawCodeHash(addr)) case 0 { // zkEVM native call let precompileCost := getGasForPrecompiles(addr, argsOffset, argsSize) @@ -3501,8 +3501,7 @@ object "EvmEmulator" { isEVM := fetchFromSystemContract(ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT(), 36) } - function isConstructedEvmContract(addr) -> isConstructedEVM { - let rawCodeHash := getRawCodeHash(addr) + function isHashOfConstructedEvmContract(rawCodeHash) -> isConstructedEVM { let version := shr(248, rawCodeHash) let isConstructedFlag := xor(shr(240, rawCodeHash), 1) isConstructedEVM := and(eq(version, 2), isConstructedFlag) @@ -3796,21 +3795,16 @@ object "EvmEmulator" { //////////////////////////////////////////////////////////////// function performCall(oldSp, evmGasLeft, oldStackHead, isStatic) -> newGasLeft, sp, stackHead { - let gasToPass, addr, value, argsOffset, argsSize, retOffset, retSize + let gasToPass, rawAddr, value, argsOffset, argsSize, retOffset, retSize popStackCheck(oldSp, 7) gasToPass, sp, stackHead := popStackItemWithoutCheck(oldSp, oldStackHead) - addr, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) + rawAddr, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) value, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) argsOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) argsSize, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) retOffset, sp, retSize := popStackItemWithoutCheck(sp, stackHead) - addr := and(addr, 0xffffffffffffffffffffffffffffffffffffffff) - - checkMemIsAccessible(argsOffset, argsSize) - checkMemIsAccessible(retOffset, retSize) - // static_gas = 0 // dynamic_gas = memory_expansion_cost + code_execution_cost + address_access_cost + positive_value_cost + value_to_empty_account_cost // code_execution_cost is the cost of the called code execution (limited by the gas parameter). @@ -3818,13 +3812,7 @@ object "EvmEmulator" { // If value is not 0, then positive_value_cost is 9000. In this case there is also a call stipend that is given to make sure that a basic fallback function can be called. // If value is not 0 and the address given points to an empty account, then value_to_empty_account_cost is 25000. An account is empty if its balance is 0, its nonce is 0 and it has no code. - let gasUsed := 100 // warm address access cost - if iszero($llvm_AlwaysInline_llvm$_warmAddress(addr)) { - gasUsed := 2600 // cold address access cost - } - - // memory_expansion_cost - gasUsed := add(gasUsed, expandMemory2(retOffset, retSize, argsOffset, argsSize)) + let addr, gasUsed := _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize) if gt(value, 0) { if isStatic { @@ -3862,27 +3850,16 @@ object "EvmEmulator" { } function performStaticCall(oldSp, evmGasLeft, oldStackHead) -> newGasLeft, sp, stackHead { - let gasToPass,addr, argsOffset, argsSize, retOffset, retSize + let gasToPass, rawAddr, argsOffset, argsSize, retOffset, retSize popStackCheck(oldSp, 6) gasToPass, sp, stackHead := popStackItemWithoutCheck(oldSp, oldStackHead) - addr, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) + rawAddr, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) argsOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) argsSize, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) retOffset, sp, retSize := popStackItemWithoutCheck(sp, stackHead) - addr := and(addr, 0xffffffffffffffffffffffffffffffffffffffff) - - checkMemIsAccessible(argsOffset, argsSize) - checkMemIsAccessible(retOffset, retSize) - - let gasUsed := 100 // warm address access cost - if iszero($llvm_AlwaysInline_llvm$_warmAddress(addr)) { - gasUsed := 2600 // cold address access cost - } - - // memory_expansion_cost - gasUsed := add(gasUsed, expandMemory2(retOffset, retSize, argsOffset, argsSize)) + let addr, gasUsed := _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize) evmGasLeft := chargeGas(evmGasLeft, gasUsed) gasToPass := capGasForCall(evmGasLeft, gasToPass) @@ -3905,59 +3882,82 @@ object "EvmEmulator" { function performDelegateCall(oldSp, evmGasLeft, isStatic, oldStackHead) -> newGasLeft, sp, stackHead { - let addr, gasToPass, argsOffset, argsSize, retOffset, retSize + let gasToPass, rawAddr, argsOffset, argsSize, retOffset, retSize popStackCheck(oldSp, 6) gasToPass, sp, stackHead := popStackItemWithoutCheck(oldSp, oldStackHead) - addr, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) + rawAddr, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) argsOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) argsSize, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) retOffset, sp, retSize := popStackItemWithoutCheck(sp, stackHead) - addr := and(addr, 0xffffffffffffffffffffffffffffffffffffffff) + let addr, gasUsed := _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize) - checkMemIsAccessible(argsOffset, argsSize) - checkMemIsAccessible(retOffset, retSize) + newGasLeft := chargeGas(evmGasLeft, gasUsed) + gasToPass := capGasForCall(newGasLeft, gasToPass) - let gasUsed := 100 // warm address access cost - if iszero($llvm_AlwaysInline_llvm$_warmAddress(addr)) { - gasUsed := 2600 // cold address access cost - } + newGasLeft := sub(newGasLeft, gasToPass) - // memory_expansion_cost - gasUsed := add(gasUsed, expandMemory2(retOffset, retSize, argsOffset, argsSize)) + let success + let frameGasLeft := gasToPass - evmGasLeft := chargeGas(evmGasLeft, gasUsed) + let rawCodeHash := getRawCodeHash(addr) + switch isHashOfConstructedEvmContract(rawCodeHash) + case 0 { + // Not a constructed EVM contract + let precompileCost := getGasForPrecompiles(addr, argsOffset, argsSize) + switch precompileCost + case 0 { + // Not a precompile + switch eq(1, shr(248, rawCodeHash)) + case 0 { + // Empty contract or EVM contract being constructed + success := delegatecall(gas(), addr, add(MEM_OFFSET(), argsOffset), argsSize, retOffset, retSize) + _saveReturndataAfterZkEVMCall() + } + default { + // We forbid delegatecalls to EraVM native contracts + _eraseReturndataPointer() + } + } + default { + // Precompile. Simlate using staticcall, since EraVM behavior differs here + success, frameGasLeft := callPrecompile(addr, precompileCost, gasToPass, 0, argsOffset, argsSize, retOffset, retSize, true) + } + } + default { + // Constructed EVM contract + pushEvmFrame(gasToPass, isStatic) + // pass all remaining native gas + success := delegatecall(gas(), addr, add(MEM_OFFSET(), argsOffset), argsSize, 0, 0) - // it is also not possible to delegatecall precompiles - if iszero(isEvmContract(addr)) { - revertWithGas(evmGasLeft) + frameGasLeft := _saveReturndataAfterEVMCall(add(MEM_OFFSET(), retOffset), retSize) + if iszero(success) { + resetEvmFrame() + } } - gasToPass := capGasForCall(evmGasLeft, gasToPass) - evmGasLeft := sub(evmGasLeft, gasToPass) + newGasLeft := add(newGasLeft, frameGasLeft) + stackHead := success + } - pushEvmFrame(gasToPass, isStatic) - let success := delegatecall( - gas(), // pass all remaining native gas - addr, - add(MEM_OFFSET(), argsOffset), - argsSize, - 0, - 0 - ) + function _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize) -> addr, gasUsed { + addr := and(rawAddr, 0xffffffffffffffffffffffffffffffffffffffff) - let frameGasLeft := _saveReturndataAfterEVMCall(add(MEM_OFFSET(), retOffset), retSize) - if iszero(success) { - resetEvmFrame() + checkMemIsAccessible(argsOffset, argsSize) + checkMemIsAccessible(retOffset, retSize) + + gasUsed := 100 // warm address access cost + if iszero($llvm_AlwaysInline_llvm$_warmAddress(addr)) { + gasUsed := 2600 // cold address access cost } - newGasLeft := add(evmGasLeft, frameGasLeft) - stackHead := success + // memory_expansion_cost + gasUsed := add(gasUsed, expandMemory2(retOffset, retSize, argsOffset, argsSize)) } function _genericCall(addr, gasToPass, value, argsOffset, argsSize, retOffset, retSize, isStatic) -> success, frameGasLeft { - switch isConstructedEvmContract(addr) + switch isHashOfConstructedEvmContract(getRawCodeHash(addr)) case 0 { // zkEVM native call let precompileCost := getGasForPrecompiles(addr, argsOffset, argsSize) diff --git a/system-contracts/evm-emulator/EvmEmulatorFunctions.template.yul b/system-contracts/evm-emulator/EvmEmulatorFunctions.template.yul index 92f35ba6bc..a1a904f4de 100644 --- a/system-contracts/evm-emulator/EvmEmulatorFunctions.template.yul +++ b/system-contracts/evm-emulator/EvmEmulatorFunctions.template.yul @@ -336,8 +336,7 @@ function isEvmContract(addr) -> isEVM { isEVM := fetchFromSystemContract(ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT(), 36) } -function isConstructedEvmContract(addr) -> isConstructedEVM { - let rawCodeHash := getRawCodeHash(addr) +function isHashOfConstructedEvmContract(rawCodeHash) -> isConstructedEVM { let version := shr(248, rawCodeHash) let isConstructedFlag := xor(shr(240, rawCodeHash), 1) isConstructedEVM := and(eq(version, 2), isConstructedFlag) @@ -631,21 +630,16 @@ function resetEvmFrame() { //////////////////////////////////////////////////////////////// function performCall(oldSp, evmGasLeft, oldStackHead, isStatic) -> newGasLeft, sp, stackHead { - let gasToPass, addr, value, argsOffset, argsSize, retOffset, retSize + let gasToPass, rawAddr, value, argsOffset, argsSize, retOffset, retSize popStackCheck(oldSp, 7) gasToPass, sp, stackHead := popStackItemWithoutCheck(oldSp, oldStackHead) - addr, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) + rawAddr, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) value, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) argsOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) argsSize, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) retOffset, sp, retSize := popStackItemWithoutCheck(sp, stackHead) - addr := and(addr, 0xffffffffffffffffffffffffffffffffffffffff) - - checkMemIsAccessible(argsOffset, argsSize) - checkMemIsAccessible(retOffset, retSize) - // static_gas = 0 // dynamic_gas = memory_expansion_cost + code_execution_cost + address_access_cost + positive_value_cost + value_to_empty_account_cost // code_execution_cost is the cost of the called code execution (limited by the gas parameter). @@ -653,13 +647,7 @@ function performCall(oldSp, evmGasLeft, oldStackHead, isStatic) -> newGasLeft, s // If value is not 0, then positive_value_cost is 9000. In this case there is also a call stipend that is given to make sure that a basic fallback function can be called. // If value is not 0 and the address given points to an empty account, then value_to_empty_account_cost is 25000. An account is empty if its balance is 0, its nonce is 0 and it has no code. - let gasUsed := 100 // warm address access cost - if iszero($llvm_AlwaysInline_llvm$_warmAddress(addr)) { - gasUsed := 2600 // cold address access cost - } - - // memory_expansion_cost - gasUsed := add(gasUsed, expandMemory2(retOffset, retSize, argsOffset, argsSize)) + let addr, gasUsed := _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize) if gt(value, 0) { if isStatic { @@ -697,27 +685,16 @@ function performCall(oldSp, evmGasLeft, oldStackHead, isStatic) -> newGasLeft, s } function performStaticCall(oldSp, evmGasLeft, oldStackHead) -> newGasLeft, sp, stackHead { - let gasToPass,addr, argsOffset, argsSize, retOffset, retSize + let gasToPass, rawAddr, argsOffset, argsSize, retOffset, retSize popStackCheck(oldSp, 6) gasToPass, sp, stackHead := popStackItemWithoutCheck(oldSp, oldStackHead) - addr, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) + rawAddr, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) argsOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) argsSize, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) retOffset, sp, retSize := popStackItemWithoutCheck(sp, stackHead) - addr := and(addr, 0xffffffffffffffffffffffffffffffffffffffff) - - checkMemIsAccessible(argsOffset, argsSize) - checkMemIsAccessible(retOffset, retSize) - - let gasUsed := 100 // warm address access cost - if iszero($llvm_AlwaysInline_llvm$_warmAddress(addr)) { - gasUsed := 2600 // cold address access cost - } - - // memory_expansion_cost - gasUsed := add(gasUsed, expandMemory2(retOffset, retSize, argsOffset, argsSize)) + let addr, gasUsed := _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize) evmGasLeft := chargeGas(evmGasLeft, gasUsed) gasToPass := capGasForCall(evmGasLeft, gasToPass) @@ -740,59 +717,82 @@ function performStaticCall(oldSp, evmGasLeft, oldStackHead) -> newGasLeft, sp, s function performDelegateCall(oldSp, evmGasLeft, isStatic, oldStackHead) -> newGasLeft, sp, stackHead { - let addr, gasToPass, argsOffset, argsSize, retOffset, retSize + let gasToPass, rawAddr, argsOffset, argsSize, retOffset, retSize popStackCheck(oldSp, 6) gasToPass, sp, stackHead := popStackItemWithoutCheck(oldSp, oldStackHead) - addr, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) + rawAddr, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) argsOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) argsSize, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) retOffset, sp, retSize := popStackItemWithoutCheck(sp, stackHead) - addr := and(addr, 0xffffffffffffffffffffffffffffffffffffffff) - - checkMemIsAccessible(argsOffset, argsSize) - checkMemIsAccessible(retOffset, retSize) + let addr, gasUsed := _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize) - let gasUsed := 100 // warm address access cost - if iszero($llvm_AlwaysInline_llvm$_warmAddress(addr)) { - gasUsed := 2600 // cold address access cost - } + newGasLeft := chargeGas(evmGasLeft, gasUsed) + gasToPass := capGasForCall(newGasLeft, gasToPass) - // memory_expansion_cost - gasUsed := add(gasUsed, expandMemory2(retOffset, retSize, argsOffset, argsSize)) + newGasLeft := sub(newGasLeft, gasToPass) - evmGasLeft := chargeGas(evmGasLeft, gasUsed) + let success + let frameGasLeft := gasToPass - // it is also not possible to delegatecall precompiles - if iszero(isEvmContract(addr)) { - revertWithGas(evmGasLeft) + let rawCodeHash := getRawCodeHash(addr) + switch isHashOfConstructedEvmContract(rawCodeHash) + case 0 { + // Not a constructed EVM contract + let precompileCost := getGasForPrecompiles(addr, argsOffset, argsSize) + switch precompileCost + case 0 { + // Not a precompile + switch eq(1, shr(248, rawCodeHash)) + case 0 { + // Empty contract or EVM contract being constructed + success := delegatecall(gas(), addr, add(MEM_OFFSET(), argsOffset), argsSize, retOffset, retSize) + _saveReturndataAfterZkEVMCall() + } + default { + // We forbid delegatecalls to EraVM native contracts + _eraseReturndataPointer() + } + } + default { + // Precompile. Simlate using staticcall, since EraVM behavior differs here + success, frameGasLeft := callPrecompile(addr, precompileCost, gasToPass, 0, argsOffset, argsSize, retOffset, retSize, true) + } + } + default { + // Constructed EVM contract + pushEvmFrame(gasToPass, isStatic) + // pass all remaining native gas + success := delegatecall(gas(), addr, add(MEM_OFFSET(), argsOffset), argsSize, 0, 0) + + frameGasLeft := _saveReturndataAfterEVMCall(add(MEM_OFFSET(), retOffset), retSize) + if iszero(success) { + resetEvmFrame() + } } - gasToPass := capGasForCall(evmGasLeft, gasToPass) - evmGasLeft := sub(evmGasLeft, gasToPass) + newGasLeft := add(newGasLeft, frameGasLeft) + stackHead := success +} - pushEvmFrame(gasToPass, isStatic) - let success := delegatecall( - gas(), // pass all remaining native gas - addr, - add(MEM_OFFSET(), argsOffset), - argsSize, - 0, - 0 - ) +function _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize) -> addr, gasUsed { + addr := and(rawAddr, 0xffffffffffffffffffffffffffffffffffffffff) - let frameGasLeft := _saveReturndataAfterEVMCall(add(MEM_OFFSET(), retOffset), retSize) - if iszero(success) { - resetEvmFrame() + checkMemIsAccessible(argsOffset, argsSize) + checkMemIsAccessible(retOffset, retSize) + + gasUsed := 100 // warm address access cost + if iszero($llvm_AlwaysInline_llvm$_warmAddress(addr)) { + gasUsed := 2600 // cold address access cost } - newGasLeft := add(evmGasLeft, frameGasLeft) - stackHead := success + // memory_expansion_cost + gasUsed := add(gasUsed, expandMemory2(retOffset, retSize, argsOffset, argsSize)) } function _genericCall(addr, gasToPass, value, argsOffset, argsSize, retOffset, retSize, isStatic) -> success, frameGasLeft { - switch isConstructedEvmContract(addr) + switch isHashOfConstructedEvmContract(getRawCodeHash(addr)) case 0 { // zkEVM native call let precompileCost := getGasForPrecompiles(addr, argsOffset, argsSize)