Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 102 additions & 2 deletions src/tests/evm_interp_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1231,8 +1231,7 @@ TEST(EVMStateSaveLoad, LoadedStorageInitializesOriginalForNewTransaction) {
"nonce": 0,
"storage": {
"0000000000000000000000000000000000000000000000000000000000000000": {
"value": "0000000000000000000000000000000000000000000000000000000000000005",
"access_status": 0
"value": "0000000000000000000000000000000000000000000000000000000000000005"
}
}
},
Expand Down Expand Up @@ -1308,6 +1307,107 @@ TEST(EVMStateSaveLoad, LoadedStorageInitializesOriginalForNewTransaction) {
std::filesystem::remove(StateFilePath);
}

// Regression test for https://github.com/DTVMStack/DTVM/issues/590.
// Warm/cold access is transaction execution state, not world state. A legacy
// prestate's access_status must not cold-skip the next transaction's first
// access, and freshly saved states must not carry the field.
TEST(EVMStateSaveLoad, AccessStatusIsNotPrestateState) {
const std::string StateFilePath = "/tmp/dtvm_issue590_state.json";
const std::string SavedStateFilePath = "/tmp/dtvm_issue590_saved_state.json";
const std::string ContractAddr = "00000000000000000000000000000000000000f1";
const std::string SenderAddr = "a94f5374fce5edbc8e2a8697c15331677e6ebf0b";
const std::vector<uint8_t> Bytecode = {0x60, 0x03, 0x60, 0x00, 0x55, 0x00};

{
std::ofstream StateFile(StateFilePath);
ASSERT_TRUE(StateFile) << "Failed to create issue #590 state file";
StateFile << R"({
"accounts": {
")" << ContractAddr
<< R"(": {
"balance": "0000000000000000000000000000000000000000000000000000000000000000",
"code": "0x600360005500",
"nonce": 0,
"storage": {
"0000000000000000000000000000000000000000000000000000000000000000": {
"value": "0000000000000000000000000000000000000000000000000000000000000000",
"access_status": 1
}
}
},
")" << SenderAddr
<< R"(": {
"balance": "0000000000000000000000000000000000000000000000000de0b6b3a7640000",
"code": "0x",
"nonce": 0,
"storage": {}
}
},
"tx_context": {
"gas_price": "0000000000000000000000000000000000000000000000000000000000000010",
"block_number": 1,
"block_timestamp": 1000,
"block_coinbase": "b94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"block_prev_randao": "0000000000000000000000000000000000000000000000000000000000000020",
"block_gas_limit": 10000000,
"block_base_fee": "0000000000000000000000000000000000000000000000000000000000000010",
"tx_origin": ")"
<< SenderAddr << R"("
}
})";
}

auto HostPtr = std::make_unique<zen::evm::ZenMockedEVMHost>();
ASSERT_TRUE(zen::utils::loadState(*HostPtr, StateFilePath));

const evmc::address ContractAddress = zen::utils::parseAddress(ContractAddr);
const evmc::address SenderAddress = zen::utils::parseAddress(SenderAddr);
const evmc::bytes32 StorageKey{};
const auto &Slot = HostPtr->accounts[ContractAddress].storage.at(StorageKey);
EXPECT_EQ(Slot.access_status, EVMC_ACCESS_COLD)
<< "Prestate access_status must not warm storage for a new transaction";

RuntimeConfig Config;
Config.Mode = common::RunMode::InterpMode;
auto RT = Runtime::newEVMRuntime(Config, HostPtr.get());
ASSERT_TRUE(RT);
HostPtr->setRuntime(RT.get());

zen::evm::ZenMockedEVMHost::TransactionExecutionConfig ExecConfig;
ExecConfig.ModuleName = "issue590";
ExecConfig.Bytecode = Bytecode.data();
ExecConfig.BytecodeSize = Bytecode.size();
ExecConfig.Revision = EVMC_CANCUN;
ExecConfig.GasLimit = 100000;
evmc_message Msg{};
Msg.kind = EVMC_CALL;
Msg.gas = 100000;
Msg.sender = SenderAddress;
Msg.recipient = ContractAddress;
Msg.code_address = ContractAddress;
ExecConfig.Message = Msg;

auto Result = HostPtr->executeTransaction(ExecConfig);
ASSERT_TRUE(Result.Success) << Result.ErrorMessage;
EXPECT_EQ(Result.Status, EVMC_SUCCESS);
EXPECT_EQ(Result.GasUsed, 22106u)
<< "The first SSTORE must pay cold access plus set cost";

ASSERT_TRUE(zen::utils::saveState(*HostPtr, SavedStateFilePath));
std::ifstream SavedStateFile(SavedStateFilePath);
std::string SavedState((std::istreambuf_iterator<char>(SavedStateFile)),
std::istreambuf_iterator<char>());
ASSERT_TRUE(SavedStateFile) << "Failed to read saved state";
EXPECT_EQ(SavedState.find("access_status"), std::string::npos)
<< "Saved state must not contain transaction access status";
auto SavedHost = std::make_unique<zen::evm::ZenMockedEVMHost>();
EXPECT_TRUE(zen::utils::loadState(*SavedHost, SavedStateFilePath))
<< "Saved state must remain valid JSON";

std::filesystem::remove(StateFilePath);
std::filesystem::remove(SavedStateFilePath);
}

namespace {

// Helper that builds and returns the final sender balance for a transaction
Expand Down
12 changes: 4 additions & 8 deletions src/utils/evm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,7 @@ bool saveState(const evmc::MockedHost &Host, const std::string &FilePath) {
File << " \"value\": ";
writeJsonString(File,
toHex(Value.current.bytes, sizeof(Value.current.bytes)));
File << ",\n";
File << " \"access_status\": " << Value.access_status << "\n";
File << "\n";
File << " }";
}
if (!FirstStorage)
Expand Down Expand Up @@ -386,7 +385,9 @@ bool loadState(evmc::MockedHost &Host, const std::string &FilePath) {
evmc::StorageValue StorageVal;

if (StorageValue.IsObject()) {
// New format with value and access_status
// The optional original value allows callers to provide a
// transaction-specific original. access_status is never part of a
// pre-state; legacy files may include it and it must be ignored.
if (StorageValue.HasMember("value") &&
StorageValue["value"].IsString()) {
StorageVal.current =
Expand All @@ -402,11 +403,6 @@ bool loadState(evmc::MockedHost &Host, const std::string &FilePath) {
// current is also the transaction's original value.
StorageVal.original = StorageVal.current;
}
if (StorageValue.HasMember("access_status") &&
StorageValue["access_status"].IsUint()) {
StorageVal.access_status = static_cast<evmc_access_status>(
StorageValue["access_status"].GetUint());
}
} else if (StorageValue.IsString()) {
// Old format with just value
StorageVal.current =
Expand Down
Loading