diff --git a/src/compiler/evm_frontend/evm_imported.cpp b/src/compiler/evm_frontend/evm_imported.cpp index 6dba90c9..00040843 100644 --- a/src/compiler/evm_frontend/evm_imported.cpp +++ b/src/compiler/evm_frontend/evm_imported.cpp @@ -258,11 +258,15 @@ const RuntimeFunctions &getRuntimeFunctionTable() { .HandleCreate = &evmHandleCreate, .HandleCreate2 = &evmHandleCreate2, .HandleCall = &evmHandleCall, + .HandleCallNoExpand = &evmHandleCallNoExpand, .HandleCallCode = &evmHandleCallCode, + .HandleCallCodeNoExpand = &evmHandleCallCodeNoExpand, .SetReturn = &evmSetReturn, .SetReturnNoExpand = &evmSetReturnNoExpand, .HandleDelegateCall = &evmHandleDelegateCall, + .HandleDelegateCallNoExpand = &evmHandleDelegateCallNoExpand, .HandleStaticCall = &evmHandleStaticCall, + .HandleStaticCallNoExpand = &evmHandleStaticCallNoExpand, .SetRevert = &evmSetRevert, .SetRevertNoExpand = &evmSetRevertNoExpand, .HandleInvalid = &evmHandleInvalid, @@ -1088,10 +1092,13 @@ const uint8_t *evmHandleCreate2(zen::runtime::EVMInstance *Instance, } // Helper function for all call types -static uint64_t evmHandleCallInternal( - zen::runtime::EVMInstance *Instance, evmc_call_kind CallKind, uint64_t Gas, - const uint8_t *ToAddr, const intx::uint256 &Value, uint64_t ArgsOffset, - uint64_t ArgsSize, uint64_t RetOffset, uint64_t RetSize, bool ForceStatic) { +static uint64_t evmHandleCallInternal(zen::runtime::EVMInstance *Instance, + evmc_call_kind CallKind, uint64_t Gas, + const uint8_t *ToAddr, + const intx::uint256 &Value, + uint64_t ArgsOffset, uint64_t ArgsSize, + uint64_t RetOffset, uint64_t RetSize, + bool ForceStatic, bool MemoryPrepared) { const zen::runtime::EVMModule *Module = Instance->getModule(); ZEN_ASSERT(Module && Module->Host); @@ -1122,25 +1129,28 @@ static uint64_t evmHandleCallInternal( return 0; } - // Calculate required memory sizes for input and output - // Only expand memory if we actually need to access it - bool needArgsMemory = ArgsSize > 0; - bool needRetMemory = RetSize > 0; - if (needArgsMemory && needRetMemory) { - if (!Instance->expandMemoryChecked(ArgsOffset, ArgsSize, RetOffset, - RetSize)) { - Instance->setReturnData({}); - return 0; - } - } else if (needArgsMemory) { - if (!Instance->expandMemoryChecked(ArgsOffset, ArgsSize)) { - Instance->setReturnData({}); - return 0; - } - } else if (needRetMemory) { - if (!Instance->expandMemoryChecked(RetOffset, RetSize)) { - Instance->setReturnData({}); - return 0; + // No-expand callers must prove both non-empty ranges before entering this + // helper. Account access, static-mode checks, and all call gas handling stay + // in their original order. + if (!MemoryPrepared) { + const bool NeedArgsMemory = ArgsSize > 0; + const bool NeedRetMemory = RetSize > 0; + if (NeedArgsMemory && NeedRetMemory) { + if (!Instance->expandMemoryChecked(ArgsOffset, ArgsSize, RetOffset, + RetSize)) { + Instance->setReturnData({}); + return 0; + } + } else if (NeedArgsMemory) { + if (!Instance->expandMemoryChecked(ArgsOffset, ArgsSize)) { + Instance->setReturnData({}); + return 0; + } + } else if (NeedRetMemory) { + if (!Instance->expandMemoryChecked(RetOffset, RetSize)) { + Instance->setReturnData({}); + return 0; + } } } @@ -1271,7 +1281,18 @@ uint64_t evmHandleCall(zen::runtime::EVMInstance *Instance, uint64_t Gas, uint64_t ArgsOffset, uint64_t ArgsSize, uint64_t RetOffset, uint64_t RetSize) { return evmHandleCallInternal(Instance, EVMC_CALL, Gas, ToAddr, Value, - ArgsOffset, ArgsSize, RetOffset, RetSize, false); + ArgsOffset, ArgsSize, RetOffset, RetSize, false, + false); +} + +uint64_t evmHandleCallNoExpand(zen::runtime::EVMInstance *Instance, + uint64_t Gas, const uint8_t *ToAddr, + const intx::uint256 &Value, uint64_t ArgsOffset, + uint64_t ArgsSize, uint64_t RetOffset, + uint64_t RetSize) { + return evmHandleCallInternal(Instance, EVMC_CALL, Gas, ToAddr, Value, + ArgsOffset, ArgsSize, RetOffset, RetSize, false, + true); } uint64_t evmHandleCallCode(zen::runtime::EVMInstance *Instance, uint64_t Gas, @@ -1279,7 +1300,18 @@ uint64_t evmHandleCallCode(zen::runtime::EVMInstance *Instance, uint64_t Gas, uint64_t ArgsOffset, uint64_t ArgsSize, uint64_t RetOffset, uint64_t RetSize) { return evmHandleCallInternal(Instance, EVMC_CALLCODE, Gas, ToAddr, Value, - ArgsOffset, ArgsSize, RetOffset, RetSize, false); + ArgsOffset, ArgsSize, RetOffset, RetSize, false, + false); +} + +uint64_t evmHandleCallCodeNoExpand(zen::runtime::EVMInstance *Instance, + uint64_t Gas, const uint8_t *ToAddr, + const intx::uint256 &Value, + uint64_t ArgsOffset, uint64_t ArgsSize, + uint64_t RetOffset, uint64_t RetSize) { + return evmHandleCallInternal(Instance, EVMC_CALLCODE, Gas, ToAddr, Value, + ArgsOffset, ArgsSize, RetOffset, RetSize, false, + true); } void evmHandleInvalid(zen::runtime::EVMInstance *Instance) { @@ -1312,7 +1344,16 @@ uint64_t evmHandleDelegateCall(zen::runtime::EVMInstance *Instance, uint64_t RetOffset, uint64_t RetSize) { return evmHandleCallInternal(Instance, EVMC_DELEGATECALL, Gas, ToAddr, intx::uint256{0}, ArgsOffset, ArgsSize, - RetOffset, RetSize, false); + RetOffset, RetSize, false, false); +} + +uint64_t evmHandleDelegateCallNoExpand(zen::runtime::EVMInstance *Instance, + uint64_t Gas, const uint8_t *ToAddr, + uint64_t ArgsOffset, uint64_t ArgsSize, + uint64_t RetOffset, uint64_t RetSize) { + return evmHandleCallInternal(Instance, EVMC_DELEGATECALL, Gas, ToAddr, + intx::uint256{0}, ArgsOffset, ArgsSize, + RetOffset, RetSize, false, true); } // EVMC spec: STATICCALL uses kind=EVMC_CALL + flags=EVMC_STATIC @@ -1324,7 +1365,16 @@ uint64_t evmHandleStaticCall(zen::runtime::EVMInstance *Instance, uint64_t Gas, uint64_t RetSize) { return evmHandleCallInternal(Instance, EVMC_CALL, Gas, ToAddr, intx::uint256{0}, ArgsOffset, ArgsSize, - RetOffset, RetSize, true); + RetOffset, RetSize, true, false); +} + +uint64_t evmHandleStaticCallNoExpand(zen::runtime::EVMInstance *Instance, + uint64_t Gas, const uint8_t *ToAddr, + uint64_t ArgsOffset, uint64_t ArgsSize, + uint64_t RetOffset, uint64_t RetSize) { + return evmHandleCallInternal(Instance, EVMC_CALL, Gas, ToAddr, + intx::uint256{0}, ArgsOffset, ArgsSize, + RetOffset, RetSize, true, true); } void evmSetRevert(zen::runtime::EVMInstance *Instance, uint64_t Offset, diff --git a/src/compiler/evm_frontend/evm_imported.h b/src/compiler/evm_frontend/evm_imported.h index cc9e4768..62eeccc9 100644 --- a/src/compiler/evm_frontend/evm_imported.h +++ b/src/compiler/evm_frontend/evm_imported.h @@ -147,11 +147,15 @@ struct RuntimeFunctions { CreateFn HandleCreate; Create2Fn HandleCreate2; CallFn HandleCall; + CallFn HandleCallNoExpand; CallFn HandleCallCode; + CallFn HandleCallCodeNoExpand; VoidWithUInt64UInt64Fn SetReturn; VoidWithUInt64UInt64Fn SetReturnNoExpand; DelegateCallFn HandleDelegateCall; + DelegateCallFn HandleDelegateCallNoExpand; DelegateCallFn HandleStaticCall; + DelegateCallFn HandleStaticCallNoExpand; VoidWithUInt64UInt64Fn SetRevert; VoidWithUInt64UInt64Fn SetRevertNoExpand; VoidFn HandleInvalid; @@ -285,10 +289,20 @@ uint64_t evmHandleCall(zen::runtime::EVMInstance *Instance, uint64_t Gas, const uint8_t *ToAddr, const intx::uint256 &Value, uint64_t ArgsOffset, uint64_t ArgsSize, uint64_t RetOffset, uint64_t RetSize); +uint64_t evmHandleCallNoExpand(zen::runtime::EVMInstance *Instance, + uint64_t Gas, const uint8_t *ToAddr, + const intx::uint256 &Value, uint64_t ArgsOffset, + uint64_t ArgsSize, uint64_t RetOffset, + uint64_t RetSize); uint64_t evmHandleCallCode(zen::runtime::EVMInstance *Instance, uint64_t Gas, const uint8_t *ToAddr, const intx::uint256 &Value, uint64_t ArgsOffset, uint64_t ArgsSize, uint64_t RetOffset, uint64_t RetSize); +uint64_t evmHandleCallCodeNoExpand(zen::runtime::EVMInstance *Instance, + uint64_t Gas, const uint8_t *ToAddr, + const intx::uint256 &Value, + uint64_t ArgsOffset, uint64_t ArgsSize, + uint64_t RetOffset, uint64_t RetSize); void evmSetReturn(zen::runtime::EVMInstance *Instance, uint64_t MemOffset, uint64_t Length); void evmSetReturnNoExpand(zen::runtime::EVMInstance *Instance, @@ -297,10 +311,18 @@ uint64_t evmHandleDelegateCall(zen::runtime::EVMInstance *Instance, uint64_t Gas, const uint8_t *ToAddr, uint64_t ArgsOffset, uint64_t ArgsSize, uint64_t RetOffset, uint64_t RetSize); +uint64_t evmHandleDelegateCallNoExpand(zen::runtime::EVMInstance *Instance, + uint64_t Gas, const uint8_t *ToAddr, + uint64_t ArgsOffset, uint64_t ArgsSize, + uint64_t RetOffset, uint64_t RetSize); uint64_t evmHandleStaticCall(zen::runtime::EVMInstance *Instance, uint64_t Gas, const uint8_t *ToAddr, uint64_t ArgsOffset, uint64_t ArgsSize, uint64_t RetOffset, uint64_t RetSize); +uint64_t evmHandleStaticCallNoExpand(zen::runtime::EVMInstance *Instance, + uint64_t Gas, const uint8_t *ToAddr, + uint64_t ArgsOffset, uint64_t ArgsSize, + uint64_t RetOffset, uint64_t RetSize); void evmSetRevert(zen::runtime::EVMInstance *Instance, uint64_t Offset, uint64_t Size); void evmSetRevertNoExpand(zen::runtime::EVMInstance *Instance, uint64_t Offset, diff --git a/src/compiler/evm_frontend/evm_mir_compiler.cpp b/src/compiler/evm_frontend/evm_mir_compiler.cpp index 9ff0e22f..aaf6f640 100644 --- a/src/compiler/evm_frontend/evm_mir_compiler.cpp +++ b/src/compiler/evm_frontend/evm_mir_compiler.cpp @@ -5594,11 +5594,39 @@ typename EVMMirBuilder::Operand EVMMirBuilder::handleCreate2(Operand ValueOp, return Result; } +bool EVMMirBuilder::canUseGuaranteedCallMemoryRanges(const Operand &ArgsOffset, + const Operand &ArgsSize, + const Operand &RetOffset, + const Operand &RetSize) { + const auto IsCovered = [this](const Operand &Offset, const Operand &Size) { + if (Size.isZeroConstant()) { + return true; + } + if (!Offset.isConstU64() || !Size.isConstU64()) { + return false; + } + return tryUseGuaranteedMinBytesExpansionElision( + true, Offset.getConstValue()[0], Size.getConstValue()[0]); + }; + + const bool CanReuse = + IsCovered(ArgsOffset, ArgsSize) && IsCovered(RetOffset, RetSize); +#ifdef ZEN_ENABLE_MULTIPASS_JIT_LOGGING + if (CanReuse) { + ZEN_LOG_DEBUG("[EVM-CALL-MEMORY-PROOF] pc=%llu", + static_cast(CurrentMemoryOpPC)); + } +#endif + return CanReuse; +} + typename EVMMirBuilder::Operand EVMMirBuilder::handleCall(Operand GasOp, Operand ToAddrOp, Operand ValueOp, Operand ArgsOffsetOp, Operand ArgsSizeOp, Operand RetOffsetOp, Operand RetSizeOp) { const auto &RuntimeFunctions = getRuntimeFunctionTable(); + const bool UsePreparedMemory = canUseGuaranteedCallMemoryRanges( + ArgsOffsetOp, ArgsSizeOp, RetOffsetOp, RetSizeOp); // When gas value exceeds 64 bits, use max uint64 as fallback. // The runtime will cap it to available gas per EIP-150. uint64_t Non64Value = std::numeric_limits::max(); @@ -5613,8 +5641,10 @@ EVMMirBuilder::handleCall(Operand GasOp, Operand ToAddrOp, Operand ValueOp, callRuntimeForWithErrorCheck( - RuntimeFunctions.HandleCall, GasOp, ToAddrOp, ValueOp, ArgsOffsetOp, - ArgsSizeOp, RetOffsetOp, RetSizeOp); + UsePreparedMemory ? RuntimeFunctions.HandleCallNoExpand + : RuntimeFunctions.HandleCall, + GasOp, ToAddrOp, ValueOp, ArgsOffsetOp, ArgsSizeOp, RetOffsetOp, + RetSizeOp); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif @@ -5627,6 +5657,8 @@ EVMMirBuilder::handleCallCode(Operand GasOp, Operand ToAddrOp, Operand ValueOp, Operand ArgsOffsetOp, Operand ArgsSizeOp, Operand RetOffsetOp, Operand RetSizeOp) { const auto &RuntimeFunctions = getRuntimeFunctionTable(); + const bool UsePreparedMemory = canUseGuaranteedCallMemoryRanges( + ArgsOffsetOp, ArgsSizeOp, RetOffsetOp, RetSizeOp); // When gas value exceeds 64 bits, use max uint64 as fallback. // The runtime will cap it to available gas per EIP-150. uint64_t Non64Value = std::numeric_limits::max(); @@ -5641,8 +5673,10 @@ EVMMirBuilder::handleCallCode(Operand GasOp, Operand ToAddrOp, Operand ValueOp, callRuntimeForWithErrorCheck( - RuntimeFunctions.HandleCallCode, GasOp, ToAddrOp, ValueOp, - ArgsOffsetOp, ArgsSizeOp, RetOffsetOp, RetSizeOp); + UsePreparedMemory ? RuntimeFunctions.HandleCallCodeNoExpand + : RuntimeFunctions.HandleCallCode, + GasOp, ToAddrOp, ValueOp, ArgsOffsetOp, ArgsSizeOp, RetOffsetOp, + RetSizeOp); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif @@ -5678,6 +5712,8 @@ EVMMirBuilder::handleDelegateCall(Operand GasOp, Operand ToAddrOp, Operand ArgsOffsetOp, Operand ArgsSizeOp, Operand RetOffsetOp, Operand RetSizeOp) { const auto &RuntimeFunctions = getRuntimeFunctionTable(); + const bool UsePreparedMemory = canUseGuaranteedCallMemoryRanges( + ArgsOffsetOp, ArgsSizeOp, RetOffsetOp, RetSizeOp); // When gas value exceeds 64 bits, use max uint64 as fallback. // The runtime will cap it to available gas per EIP-150. uint64_t Non64Value = std::numeric_limits::max(); @@ -5691,8 +5727,9 @@ EVMMirBuilder::handleDelegateCall(Operand GasOp, Operand ToAddrOp, auto Result = callRuntimeForWithErrorCheck( - RuntimeFunctions.HandleDelegateCall, GasOp, ToAddrOp, ArgsOffsetOp, - ArgsSizeOp, RetOffsetOp, RetSizeOp); + UsePreparedMemory ? RuntimeFunctions.HandleDelegateCallNoExpand + : RuntimeFunctions.HandleDelegateCall, + GasOp, ToAddrOp, ArgsOffsetOp, ArgsSizeOp, RetOffsetOp, RetSizeOp); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif @@ -5705,6 +5742,8 @@ EVMMirBuilder::handleStaticCall(Operand GasOp, Operand ToAddrOp, Operand ArgsOffsetOp, Operand ArgsSizeOp, Operand RetOffsetOp, Operand RetSizeOp) { const auto &RuntimeFunctions = getRuntimeFunctionTable(); + const bool UsePreparedMemory = canUseGuaranteedCallMemoryRanges( + ArgsOffsetOp, ArgsSizeOp, RetOffsetOp, RetSizeOp); // When gas value exceeds 64 bits, use max uint64 as fallback. // The runtime will cap it to available gas per EIP-150. uint64_t Non64Value = std::numeric_limits::max(); @@ -5718,8 +5757,9 @@ EVMMirBuilder::handleStaticCall(Operand GasOp, Operand ToAddrOp, auto Result = callRuntimeForWithErrorCheck( - RuntimeFunctions.HandleStaticCall, GasOp, ToAddrOp, ArgsOffsetOp, - ArgsSizeOp, RetOffsetOp, RetSizeOp); + UsePreparedMemory ? RuntimeFunctions.HandleStaticCallNoExpand + : RuntimeFunctions.HandleStaticCall, + GasOp, ToAddrOp, ArgsOffsetOp, ArgsSizeOp, RetOffsetOp, RetSizeOp); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif diff --git a/src/compiler/evm_frontend/evm_mir_compiler.h b/src/compiler/evm_frontend/evm_mir_compiler.h index b2c045f3..6bf58633 100644 --- a/src/compiler/evm_frontend/evm_mir_compiler.h +++ b/src/compiler/evm_frontend/evm_mir_compiler.h @@ -1851,6 +1851,10 @@ class EVMMirBuilder final { bool tryUseGuaranteedMinBytesExpansionElision(bool OffsetWasConst, uint64_t ConstOffset, uint64_t AccessSize); + bool canUseGuaranteedCallMemoryRanges(const Operand &ArgsOffset, + const Operand &ArgsSize, + const Operand &RetOffset, + const Operand &RetSize); void applyMemoryExpansionPlan(const MemoryExpansionPlan &Plan); void noteMemoryExpansionPlan(const MemoryExpansionPlan &Plan); void noteMemoryExpansionPlanDiagnostics( diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 009cc6e2..fbb8e86b 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -69,6 +69,7 @@ if(ZEN_ENABLE_SPEC_TEST) add_executable(evmJitFrontendTests evm_jit_frontend_tests.cpp) add_executable(evmRangeAnalyzerTests evm_range_analyzer_tests.cpp) add_executable(evmDifferentialTests evm_differential_tests.cpp) + target_sources(evmJitFrontendTests PRIVATE evm_call_memory_tests.cpp) add_executable( evmMemoryExecuteOnlyBench evm_memory_execute_only_bench.cpp ) diff --git a/src/tests/evm_call_memory_tests.cpp b/src/tests/evm_call_memory_tests.cpp new file mode 100644 index 00000000..2d0e6bb2 --- /dev/null +++ b/src/tests/evm_call_memory_tests.cpp @@ -0,0 +1,114 @@ +// Copyright (C) 2025 the DTVM authors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#include "compiler/evm_frontend/evm_imported.h" +#include "compiler/evm_frontend/evm_mir_compiler.h" +#include "compiler/mir/module.h" + +#include "llvm/Support/raw_ostream.h" +#include + +#include +#include +#include +#include + +namespace { + +using COMPILER::EVMMirBuilder; + +#ifdef ZEN_ENABLE_EVM_MEMORY_PLAN_FRAMEWORK +std::optional +compileCallMemoryMir(const std::vector &Bytecode) { + COMPILER::EVMFrontendContext Ctx; + Ctx.setRevision(EVMC_CANCUN); + Ctx.setBytecode(reinterpret_cast(Bytecode.data()), + Bytecode.size()); + + COMPILER::MModule Mod(Ctx); + std::array ParamTypes = { + COMPILER::MPointerType::create(Ctx, Ctx.VoidType)}; + COMPILER::MFunctionType *FuncType = COMPILER::MFunctionType::create( + Ctx, Ctx.VoidType, llvm::ArrayRef(ParamTypes)); + Mod.addFuncType(FuncType); + + COMPILER::MFunction Func(Ctx, 0); + Func.setFunctionType(FuncType); + EVMMirBuilder Builder(Ctx, Func); + if (!Builder.compile(&Ctx)) { + return std::nullopt; + } + + std::string Mir; + llvm::raw_string_ostream OS(Mir); + Func.print(OS); + OS.flush(); + return Mir; +} + +template +bool containsRuntimeCall(const std::string &Mir, FuncType Function) { + const std::string Needle = + "target = const.i64 " + + std::to_string(COMPILER::getFunctionAddress(Function)) + ", "; + return Mir.find(Needle) != std::string::npos; +} + +std::vector makeCallBytecode(evmc_opcode Opcode, + uint8_t RetOffset = 0xa0) { + std::vector Bytecode = { + OP_PUSH1, 0x01, OP_PUSH1, 0xa0, OP_MSTORE, OP_PUSH1, + 0x08, OP_JUMP, OP_JUMPDEST, OP_PUSH1, 0x20, OP_PUSH1, + RetOffset, OP_PUSH1, 0x20, OP_PUSH1, 0x80}; + if (Opcode == OP_CALL || Opcode == OP_CALLCODE) { + Bytecode.push_back(OP_PUSH0); + } + Bytecode.insert(Bytecode.end(), {OP_PUSH1, 0x04, OP_PUSH2, 0xff, 0xff, + static_cast(Opcode), OP_STOP}); + return Bytecode; +} + +TEST(EVMMirBuilderCallMemoryProofTest, SelectsPreparedHelpersForAllCallKinds) { + const auto &RuntimeFunctions = COMPILER::getRuntimeFunctionTable(); + + const auto CallMir = compileCallMemoryMir(makeCallBytecode(OP_CALL)); + const auto CallCodeMir = compileCallMemoryMir(makeCallBytecode(OP_CALLCODE)); + const auto DelegateCallMir = + compileCallMemoryMir(makeCallBytecode(OP_DELEGATECALL)); + const auto StaticCallMir = + compileCallMemoryMir(makeCallBytecode(OP_STATICCALL)); + ASSERT_TRUE(CallMir.has_value()); + ASSERT_TRUE(CallCodeMir.has_value()); + ASSERT_TRUE(DelegateCallMir.has_value()); + ASSERT_TRUE(StaticCallMir.has_value()); + + EXPECT_TRUE( + containsRuntimeCall(*CallMir, RuntimeFunctions.HandleCallNoExpand)); + EXPECT_TRUE(containsRuntimeCall(*CallCodeMir, + RuntimeFunctions.HandleCallCodeNoExpand)); + EXPECT_TRUE(containsRuntimeCall(*DelegateCallMir, + RuntimeFunctions.HandleDelegateCallNoExpand)); + EXPECT_TRUE(containsRuntimeCall(*StaticCallMir, + RuntimeFunctions.HandleStaticCallNoExpand)); +} + +TEST(EVMMirBuilderCallMemoryProofTest, + KeepsGenericHelperWhenEitherRangeIsUncovered) { + const auto &RuntimeFunctions = COMPILER::getRuntimeFunctionTable(); + const auto CallMir = compileCallMemoryMir(makeCallBytecode(OP_CALL, 0xc0)); + const auto StaticCallMir = + compileCallMemoryMir(makeCallBytecode(OP_STATICCALL, 0xc0)); + ASSERT_TRUE(CallMir.has_value()); + ASSERT_TRUE(StaticCallMir.has_value()); + + EXPECT_TRUE(containsRuntimeCall(*CallMir, RuntimeFunctions.HandleCall)); + EXPECT_FALSE( + containsRuntimeCall(*CallMir, RuntimeFunctions.HandleCallNoExpand)); + EXPECT_TRUE( + containsRuntimeCall(*StaticCallMir, RuntimeFunctions.HandleStaticCall)); + EXPECT_FALSE(containsRuntimeCall(*StaticCallMir, + RuntimeFunctions.HandleStaticCallNoExpand)); +} +#endif + +} // namespace diff --git a/src/tests/evm_differential_tests.cpp b/src/tests/evm_differential_tests.cpp index 82dcecd9..94401367 100644 --- a/src/tests/evm_differential_tests.cpp +++ b/src/tests/evm_differential_tests.cpp @@ -1142,6 +1142,58 @@ std::vector log0StaticHighOffsetBytecode() { return Code; } +std::vector staticCallPreparedMemoryBytecode() { + std::vector Code; + appendPush32Pattern(Code, 0x21); + Code.insert(Code.end(), {0x60, 0x80, 0x52}); // MSTORE(0x80, pattern) + Code.insert(Code.end(), {0x5f, 0x60, 0xa0, 0x52}); + // Transfer to a successor block so the CALL consumes a propagated CFG fact. + Code.push_back(0x60); + const size_t JumpDestImmediate = Code.size(); + Code.push_back(0x00); + Code.push_back(0x56); + Code[JumpDestImmediate] = static_cast(Code.size()); + Code.push_back(0x5b); + + Code.insert(Code.end(), { + 0x60, 0x20, // return size + 0x60, 0xa0, // return offset + 0x60, 0x20, // argument size + 0x60, 0x80, // argument offset + 0x60, 0x04, // identity precompile + 0x61, 0xff, 0xff, + 0xfa, // STATICCALL + 0x50, // POP success + 0x60, 0x20, // RETURN size + 0x60, 0xa0, // RETURN offset + 0xf3, + }); + return Code; +} + +std::vector staticCallEmptyHighOffsetsBytecode() { + std::vector Code; + Code.push_back(0x5f); // return size + appendPush32HighOffset(Code); + Code.push_back(0x5f); // argument size + appendPush32HighOffset(Code); + Code.insert(Code.end(), { + 0x60, + 0x04, // identity precompile + 0x61, + 0xff, + 0xff, + 0xfa, // STATICCALL + 0x5f, + 0x52, // MSTORE(0, success) + 0x60, + 0x20, + 0x5f, + 0xf3, + }); + return Code; +} + } // namespace TEST(EVMLogMemoryPreexpandDifferential, LogDataMatchesInterpreter) { @@ -1176,4 +1228,19 @@ TEST(EVMLogMemoryPreexpandDifferential, StaticModePrecedesMemoryExpansion) { EXPECT_EQ(Multi.LogsSignature, Interp.LogsSignature); } +TEST(EVMCallMemoryProofDifferential, PreparedIdentityCallMatchesInterpreter) { + const auto Output = expectInterpMatchesMultipassWithGas( + "staticcall_prepared_memory", staticCallPreparedMemoryBytecode(), {}); + EXPECT_EQ(Output, + "2122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F40"); +} + +TEST(EVMCallMemoryProofDifferential, EmptyRangesIgnoreHighOffsets) { + const auto Output = expectInterpMatchesMultipassWithGas( + "staticcall_empty_high_offsets", staticCallEmptyHighOffsetsBytecode(), + {}); + EXPECT_EQ(Output, + "0000000000000000000000000000000000000000000000000000000000000001"); +} + #endif