-
Notifications
You must be signed in to change notification settings - Fork 24
test(evm): add opcode microbenchmarks generation #382
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,301 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Copyright (C) 2025 the DTVM authors. All Rights Reserved. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // SPDX-License-Identifier: Apache-2.0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <benchmark/benchmark.h> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <evmc/evmc.hpp> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <evmc/loader.h> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <evmc/mocked_host.hpp> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include "host/evm/crypto.h" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include "tests/solidity_test_helpers.h" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include "utils/evm.h" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <iostream> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <filesystem> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using namespace zen::evm_test_utils; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using namespace zen::utils; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Custom EVMC Host that uses evmc::VM for execution, allowing recursive CALLs | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class EVMCBenchmarkHost : public evmc::MockedHost { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| evmc::VM* Vm = nullptr; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| evmc_revision Rev = EVMC_CANCUN; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void SetVm(evmc::VM* VmParam) { Vm = VmParam; } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void SetRevision(evmc_revision RevParam) { Rev = RevParam; } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| evmc::Result call(const evmc_message& Msg) noexcept override { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Record the access | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (recorded_account_accesses.empty()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recorded_account_accesses.reserve(200); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (recorded_account_accesses.size() < 200) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recorded_account_accesses.emplace_back(Msg.recipient); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (Msg.kind == EVMC_CREATE || Msg.kind == EVMC_CREATE2) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| evmc::address NewAddress; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (Msg.kind == EVMC_CREATE) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| NewAddress = computeCreateAddress(Msg.sender, accounts[Msg.sender].nonce++); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| accounts[Msg.sender].nonce++; // nonce is incremented for both CREATE and CREATE2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::vector<uint8_t> InitCode(Msg.input_data, Msg.input_data + Msg.input_size); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::vector<uint8_t> InitCodeHash = zen::host::evm::crypto::keccak256(InitCode); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::vector<uint8_t> Buffer; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Buffer.reserve(1 + sizeof(Msg.sender.bytes) + sizeof(Msg.create2_salt.bytes) + InitCodeHash.size()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Buffer.push_back(0xff); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Buffer.insert(Buffer.end(), std::begin(Msg.sender.bytes), std::end(Msg.sender.bytes)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Buffer.insert(Buffer.end(), std::begin(Msg.create2_salt.bytes), std::end(Msg.create2_salt.bytes)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Buffer.insert(Buffer.end(), InitCodeHash.begin(), InitCodeHash.end()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::vector<uint8_t> FinalHash = zen::host::evm::crypto::keccak256(Buffer); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::copy_n(FinalHash.end() - sizeof(NewAddress.bytes), sizeof(NewAddress.bytes), NewAddress.bytes); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Create a new message for execution with the computed recipient | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| evmc_message ExecMsg = Msg; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ExecMsg.recipient = NewAddress; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ExecMsg.input_data = nullptr; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ExecMsg.input_size = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // For CREATE, we execute the init code | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| evmc::Result Result = Vm->execute(*this, Rev, ExecMsg, Msg.input_data, Msg.input_size); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (Result.status_code == EVMC_SUCCESS && Result.output_size > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Save the deployed code | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| auto& Account = accounts[NewAddress]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Account.code = evmc::bytes(Result.output_data, Result.output_size); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Result.create_address = NewAddress; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Result; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Normal CALL | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| auto It = accounts.find(Msg.recipient); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (It == accounts.end() || It->second.code.empty()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // No code, just transfer value and return success | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return evmc::Result{EVMC_SUCCESS, Msg.gas, 0, nullptr, 0}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const auto& Code = It->second.code; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Vm->execute(*this, Rev, Msg, Code.data(), Code.size()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| static std::unique_ptr<evmc::VM> GlobalVm; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| static evmc_revision GlobalRev = EVMC_CANCUN; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| static std::map<std::string, evmc::address> SetupHostFromContractTest(EVMCBenchmarkHost& Host, const SolidityContractTestData& ContractTest, uint64_t GasLimit) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| evmc::address Deployer = parseAddress("1000000000000000000000000000000000000000"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| auto& DeployerAcc = Host.accounts[Deployer]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DeployerAcc.nonce = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DeployerAcc.set_balance(100000000000ULL); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Precompute all contract addresses so forward references work | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::map<std::string, evmc::address> ResolvedAddresses; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (size_t I = 0; I < ContractTest.DeployContracts.size(); ++I) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ResolvedAddresses[ContractTest.DeployContracts[I]] = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| computeCreateAddress(Deployer, I); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::map<std::string, evmc::address> DeployedAddresses; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const std::string& Name : ContractTest.DeployContracts) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| auto It = ContractTest.ContractDataMap.find(Name); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (It == ContractTest.ContractDataMap.end()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw std::runtime_error("Contract data not found for: " + Name); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::vector<std::pair<std::string, std::string>> CtorArgs; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| auto ArgsIt = ContractTest.ConstructorArgs.find(Name); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (ArgsIt != ContractTest.ConstructorArgs.end()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CtorArgs = ArgsIt->second; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::string DeployHex = It->second.DeployBytecode + encodeConstructorParams(CtorArgs, ResolvedAddresses); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| auto DeployBytecode = fromHex(DeployHex); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!DeployBytecode) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw std::runtime_error("Invalid hex for deployment of " + Name); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| evmc::address ContractAddr = computeCreateAddress(Deployer, Host.accounts[Deployer].nonce); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| evmc_message Msg = {}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Msg.kind = EVMC_CREATE; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Msg.gas = GasLimit; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Msg.recipient = ContractAddr; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Msg.sender = Deployer; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Msg.input_data = DeployBytecode->data(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Msg.input_size = DeployBytecode->size(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Msg.depth = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| evmc::Result Res = GlobalVm->execute(Host, GlobalRev, Msg, DeployBytecode->data(), DeployBytecode->size()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (Res.status_code != EVMC_SUCCESS) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw std::runtime_error("Deploy failed for " + Name + " status: " + std::to_string(Res.status_code)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (Res.output_size > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Host.accounts[ContractAddr].code = evmc::bytes(Res.output_data, Res.output_size); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Host.accounts[Deployer].nonce++; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DeployedAddresses[Name] = ContractAddr; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return DeployedAddresses; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Helper function to build calldata from a test case | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| static std::vector<uint8_t> BuildCalldata(const SolidityTestCase& Tc, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const std::map<std::string, evmc::address>& Addrs) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Currently, SolidityTestCase does not expose typed arguments; rely on raw calldata. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (void)Addrs; // unused until dynamic argument encoding is wired through SolidityTestCase | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+147
to
+148
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Currently, SolidityTestCase does not expose typed arguments; rely on raw calldata. | |
| (void)Addrs; // unused until dynamic argument encoding is wired through SolidityTestCase | |
| // Currently this helper relies on raw calldata when provided. | |
| if (Addrs.size() == static_cast<size_t>(-1)) { | |
| // This block is never executed; Addrs is intentionally unused here for now. | |
| } |
Copilot
AI
Mar 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This benchmark harness currently ignores SolidityTestCase.Args / Function and only uses raw Calldata. With the new args support in test_cases.json (e.g., the setup_* ERC20 funding calls), Tc.Calldata will be empty and these setup calls will be silently skipped, changing benchmark behavior. BuildCalldata() should mirror the logic in src/tests/solidity_contract_tests.cpp by generating selector + ABI-encoded args when Tc.Args is non-empty.
| // Currently, SolidityTestCase does not expose typed arguments; rely on raw calldata. | |
| (void)Addrs; // unused until dynamic argument encoding is wired through SolidityTestCase | |
| if (!Tc.Calldata.empty()) { | |
| auto Opt = fromHex(Tc.Calldata); | |
| if (Opt) return *Opt; | |
| } | |
| // Prefer raw calldata when provided. | |
| (void)Addrs; // reserved for future use (e.g., resolving address arguments) | |
| if (!Tc.Calldata.empty()) { | |
| auto Opt = fromHex(Tc.Calldata); | |
| if (Opt) return *Opt; | |
| } | |
| // If no raw calldata is provided but a function selector and ABI-encoded args are, | |
| // build calldata as: selector || encoded_args. | |
| if (!Tc.Function.empty() && !Tc.Args.empty()) { | |
| std::string CalldataHex; | |
| CalldataHex.reserve(Tc.Function.size() + | |
| std::accumulate(Tc.Args.begin(), Tc.Args.end(), std::size_t{0}, | |
| [](std::size_t Acc, const auto& Arg) { | |
| return Acc + Arg.size(); | |
| })); | |
| CalldataHex += Tc.Function; | |
| for (const auto& Arg : Tc.Args) { | |
| CalldataHex += Arg; | |
| } | |
| auto Opt = fromHex(CalldataHex); | |
| if (Opt) return *Opt; | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
--save-baselineargument is using a misspelled/garbled variable name ($ ENCHMARK_SAVE_BASELINE), which will expand to an empty string or a different variable and cause the baseline-save path to write to an invalid location (or fail argument parsing). Fix the variable reference toBENCHMARK_SAVE_BASELINEand remove the embedded spaces so the argument value is correct.