From fec5c2c420065db183c5bdf18c6c4a1cde61846a Mon Sep 17 00:00:00 2001 From: cmgCr Date: Wed, 8 Apr 2026 14:52:32 +0000 Subject: [PATCH 1/4] fix(evm): set tx_origin from sender address in CLI and state loader ORIGIN opcode returned zero address because tx_origin was never set. loadState() now parses tx_origin from state.json, and the CLI falls back to --sender when tx_origin is still unset. This aligns DTVM behavior with Geth for contracts that read tx.origin. --- src/cli/dtvm.cpp | 7 +++++++ src/utils/evm.cpp | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/src/cli/dtvm.cpp b/src/cli/dtvm.cpp index aba274676..e7a7a406c 100644 --- a/src/cli/dtvm.cpp +++ b/src/cli/dtvm.cpp @@ -379,6 +379,13 @@ int main(int argc, char *argv[]) { .SenderAddress = SenderAddress, .ContractAddress = ContractAddress}; evmc_message Msg = createEvmMessage(MockedHost, MsgConfig, Bytecode); + + // Set tx_origin from sender address (fallback if not loaded from state) + evmc::address SenderAddr = zen::utils::parseAddress(SenderAddress); + if (MockedHost.tx_context.tx_origin == evmc::address{}) { + MockedHost.tx_context.tx_origin = SenderAddr; + } + RT->callEVMMain(*Inst, Msg, ExeResult); if (EVMC_CREATE == MsgKind && ExeResult.status_code == EVMC_SUCCESS) { diff --git a/src/utils/evm.cpp b/src/utils/evm.cpp index fd4fd6545..f75f91f74 100644 --- a/src/utils/evm.cpp +++ b/src/utils/evm.cpp @@ -418,6 +418,11 @@ bool loadState(evmc::MockedHost &Host, const std::string &FilePath) { Host.tx_context.block_base_fee = zen::utils::parseUint256(TxContext["block_base_fee"].GetString()); } + + if (TxContext.HasMember("tx_origin") && TxContext["tx_origin"].IsString()) { + Host.tx_context.tx_origin = + zen::utils::parseAddress(TxContext["tx_origin"].GetString()); + } } return true; From 0e3607e61b1429e05f081bade096186b6781f7ae Mon Sep 17 00:00:00 2001 From: cmgCr Date: Thu, 9 Apr 2026 00:42:47 +0800 Subject: [PATCH 2/4] test(evm): update origin.expected to match tx_origin fix --- tests/evm_asm/origin.expected | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/evm_asm/origin.expected b/tests/evm_asm/origin.expected index 481e245e2..66a32f68b 100644 --- a/tests/evm_asm/origin.expected +++ b/tests/evm_asm/origin.expected @@ -1,8 +1,8 @@ status: success error_code: 0 stack: [] -memory: '0000000000000000000000000000000000000000000000000000000000000000' +memory: '0000000000000000000000001000000000000000000000000000000000000000' storage: {} transient_storage: {} -return: '0000000000000000000000000000000000000000000000000000000000000000' +return: '0000000000000000000000001000000000000000000000000000000000000000' events: [] From 583aa664f6b4b53a82b4fe6e2e9c82f899260b8c Mon Sep 17 00:00:00 2001 From: cmgCr Date: Wed, 8 Apr 2026 17:15:05 +0000 Subject: [PATCH 3/4] test(evm): set tx_origin in ctest host to match CLI behavior --- src/tests/evm_interp_tests.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tests/evm_interp_tests.cpp b/src/tests/evm_interp_tests.cpp index 057866830..0a2953764 100644 --- a/src/tests/evm_interp_tests.cpp +++ b/src/tests/evm_interp_tests.cpp @@ -169,6 +169,7 @@ EVMExecutionResult executeEvmBytecodeFile(const std::string &FilePath, Config.Mode = Mode; auto MockedHost = std::make_unique(); + MockedHost->tx_context.tx_origin = zen::evm::DEFAULT_DEPLOYER_ADDRESS; auto RT = Runtime::newEVMRuntime(Config, MockedHost.get()); EXPECT_TRUE(RT != nullptr) << "Failed to create runtime"; if (!RT) { @@ -268,6 +269,7 @@ TEST_P(EVMSampleTest, ExecuteSample) { Config.Mode = common::RunMode::InterpMode; auto MockedHost = std::make_unique(); + MockedHost->tx_context.tx_origin = zen::evm::DEFAULT_DEPLOYER_ADDRESS; auto RT = Runtime::newEVMRuntime(Config, MockedHost.get()); ASSERT_TRUE(RT != nullptr) << "Failed to create runtime"; From d75c32adfaf7d73a2f71c1a9be74aacf06597918 Mon Sep 17 00:00:00 2001 From: cmgCr Date: Thu, 9 Apr 2026 08:18:47 +0000 Subject: [PATCH 4/4] fix(evm): set tx_origin before loadState and add saveState symmetry - Move tx_origin initialization from a post-loadState fallback to pre-loadState default. This sets tx_origin from --sender before calling loadState(), allowing loadState() to override it when tx_origin is present in state.json. Avoids the ambiguity of treating zero address as 'not set' vs 'explicitly set to zero'. - Add tx_origin serialization in saveState() to keep save/load symmetric, preventing tx_origin from being lost during state round-tripping via --save-state/--load-state. --- src/cli/dtvm.cpp | 10 ++++------ src/utils/evm.cpp | 4 ++++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/cli/dtvm.cpp b/src/cli/dtvm.cpp index e7a7a406c..4ddfb1719 100644 --- a/src/cli/dtvm.cpp +++ b/src/cli/dtvm.cpp @@ -303,6 +303,10 @@ int main(int argc, char *argv[]) { #ifdef ZEN_ENABLE_EVM if (Config.Format == InputFormat::EVM) { auto MockedEVMHost = std::make_unique(); + // Set tx_origin from sender address before loading state, + // so loadState() can override it if tx_origin is present in state.json + MockedEVMHost->tx_context.tx_origin = + zen::utils::parseAddress(SenderAddress); // Load state if specified if (!LoadStateFile.empty() && !zen::utils::loadState(*MockedEVMHost, LoadStateFile)) { @@ -380,12 +384,6 @@ int main(int argc, char *argv[]) { .ContractAddress = ContractAddress}; evmc_message Msg = createEvmMessage(MockedHost, MsgConfig, Bytecode); - // Set tx_origin from sender address (fallback if not loaded from state) - evmc::address SenderAddr = zen::utils::parseAddress(SenderAddress); - if (MockedHost.tx_context.tx_origin == evmc::address{}) { - MockedHost.tx_context.tx_origin = SenderAddr; - } - RT->callEVMMain(*Inst, Msg, ExeResult); if (EVMC_CREATE == MsgKind && ExeResult.status_code == EVMC_SUCCESS) { diff --git a/src/utils/evm.cpp b/src/utils/evm.cpp index f75f91f74..5aa940299 100644 --- a/src/utils/evm.cpp +++ b/src/utils/evm.cpp @@ -269,6 +269,10 @@ bool saveState(const evmc::MockedHost &Host, const std::string &FilePath) { File << " \"block_base_fee\": "; writeJsonString(File, toHex(Host.tx_context.block_base_fee.bytes, sizeof(Host.tx_context.block_base_fee.bytes))); + File << ",\n"; + File << " \"tx_origin\": "; + writeJsonString(File, toHex(Host.tx_context.tx_origin.bytes, + sizeof(Host.tx_context.tx_origin.bytes))); File << "\n"; File << " }\n";