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
35 changes: 12 additions & 23 deletions src/compiler/evm_frontend/evm_imported.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,31 +227,16 @@ const intx::uint256 *evmGetMulMod(zen::runtime::EVMInstance *Instance,
const intx::uint256 *evmGetExp(zen::runtime::EVMInstance *Instance,
const intx::uint256 &Base,
const intx::uint256 &Exponent) {
// Handle edge cases
if (Exponent == 0) {
return storeUint256Result(intx::uint256{1});
}
if (Base == 0) {
return storeUint256Result(intx::uint256{0});
}
if (Exponent == 1) {
return storeUint256Result(Base);
}
// EIP-160: 50 gas per byte of exponent (pre-Spurious Dragon is cheaper).
const uint64_t ExponentByteSize = intx::count_significant_bytes(Exponent);
const auto Rev = Instance->getRevision();
const uint64_t GasPerByte = Rev < EVMC_SPURIOUS_DRAGON
? zen::evm::EXP_BYTE_GAS_PRE_SPURIOUS_DRAGON
: zen::evm::EXP_BYTE_GAS;
Instance->chargeGas(ExponentByteSize * GasPerByte);

// EVM: (Base ^ Exponent) % (2^256)
intx::uint256 Result = 1;
intx::uint256 CurrentBase = Base;
intx::uint256 ExponentCopy = Exponent;

while (ExponentCopy > 0) {
if (ExponentCopy & 1) {
Result *= CurrentBase;
}
CurrentBase *= CurrentBase;
ExponentCopy >>= 1;
}

return storeUint256Result(Result);
return storeUint256Result(intx::exp(Base, Exponent));
}

const uint8_t *evmGetAddress(zen::runtime::EVMInstance *Instance) {
Expand Down Expand Up @@ -676,6 +661,10 @@ static void evmEmitLogGeneric(zen::runtime::EVMInstance *Instance,
if (!Instance->expandMemoryChecked(Offset, Size)) {
return;
}
const uint64_t LogDataCost = 8 * Size;
if (LogDataCost != 0) {
Instance->chargeGas(LogDataCost);
}
Comment on lines +665 to +667

Copilot AI Jan 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The conditional check for LogDataCost != 0 before charging gas is unnecessary. Similar to the EXP opcode issue, when Size is 0, LogDataCost will be 0, and chargeGas with 0 is safe. This check is inconsistent with the interpreter implementation in opcode_handlers.cpp (line 1511) which doesn't have this conditional. Remove the conditional to maintain consistency.

Suggested change
if (LogDataCost != 0) {
Instance->chargeGas(LogDataCost);
}
Instance->chargeGas(LogDataCost);

Copilot uses AI. Check for mistakes.
uint8_t *MemoryBase = Instance->getMemoryBase();
Data = MemoryBase + Offset;
}
Expand Down
2 changes: 1 addition & 1 deletion src/evm/opcode_handlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,10 @@ DEFINE_CALCULATE_GAS(Slt, OP_SLT);
DEFINE_CALCULATE_GAS(Sgt, OP_SGT);

// Arithmetic operations
DEFINE_NOT_TEMPLATE_CALCULATE_GAS(Exp, OP_EXP);
DEFINE_NOT_TEMPLATE_CALCULATE_GAS(SignExtend, OP_SIGNEXTEND);
DEFINE_NOT_TEMPLATE_CALCULATE_GAS(Byte, OP_BYTE);
DEFINE_NOT_TEMPLATE_CALCULATE_GAS(Sar, OP_SAR);
DEFINE_NOT_TEMPLATE_CALCULATE_GAS(Exp, OP_EXP);

// Environmental information
DEFINE_NOT_TEMPLATE_CALCULATE_GAS(Address, OP_ADDRESS);
Expand Down
2 changes: 1 addition & 1 deletion src/tests/evm_state_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace {
constexpr bool DEBUG = true;
constexpr bool PRINT_FAILURE_DETAILS = true;
// TODO: RunMode selection logic will be refactored in the future.
constexpr auto STATE_TEST_RUN_MODE = common::RunMode::InterpMode;
constexpr auto STATE_TEST_RUN_MODE = common::RunMode::MultipassMode;

struct TxIntrinsicCost {
int64_t Intrinsic = 0;
Expand Down
8 changes: 5 additions & 3 deletions src/tests/evm_test_helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,17 @@ calculateLogsHashImpl(const std::vector<evmc::MockedHost::log_record> &Logs) {
std::vector<uint8_t> TopicBytes(Topic.bytes, Topic.bytes + 32);
TopicsEncoded.push_back(zen::evm::rlp::encodeString(TopicBytes));
}
LogComponents.push_back(zen::evm::rlp::encodeList(TopicsEncoded));
LogComponents.push_back(
zen::evm::rlp::encodeListFromEncodedItems(TopicsEncoded));

std::vector<uint8_t> DataBytes(Log.data.begin(), Log.data.end());
LogComponents.push_back(zen::evm::rlp::encodeString(DataBytes));

EncodedLogs.push_back(zen::evm::rlp::encodeList(LogComponents));
EncodedLogs.push_back(
zen::evm::rlp::encodeListFromEncodedItems(LogComponents));
}

auto RlpEncodedLogs = zen::evm::rlp::encodeList(EncodedLogs);
auto RlpEncodedLogs = zen::evm::rlp::encodeListFromEncodedItems(EncodedLogs);

auto Hash = zen::host::evm::crypto::keccak256(RlpEncodedLogs);

Expand Down
12 changes: 12 additions & 0 deletions src/utils/rlp_encoding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,16 @@ encodeList(const std::vector<std::vector<uint8_t>> &Items) {
return LengthBytes;
}

std::vector<uint8_t>
encodeListFromEncodedItems(const std::vector<std::vector<uint8_t>> &Items) {
std::vector<uint8_t> Payload;
for (const auto &Item : Items) {
Payload.insert(Payload.end(), Item.begin(), Item.end());
}

auto LengthBytes = encodeLength(Payload.size(), RLP_OFFSET_SHORT_LIST);
LengthBytes.insert(LengthBytes.end(), Payload.begin(), Payload.end());
return LengthBytes;
}
Comment on lines +59 to +69

Copilot AI Jan 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new function encodeListFromEncodedItems lacks documentation explaining its purpose and how it differs from encodeList. Add a comment explaining that this function is for encoding a list of items that are already RLP-encoded, whereas encodeList expects raw byte vectors that need to be encoded as strings first.

Copilot uses AI. Check for mistakes.

} // namespace zen::evm::rlp
2 changes: 2 additions & 0 deletions src/utils/rlp_encoding.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ extern const uint8_t RLP_OFFSET_SHORT_LIST;
std::vector<uint8_t> encodeLength(size_t Length, uint8_t Offset);
std::vector<uint8_t> encodeString(const std::vector<uint8_t> &Input);
std::vector<uint8_t> encodeList(const std::vector<std::vector<uint8_t>> &Items);
std::vector<uint8_t>
encodeListFromEncodedItems(const std::vector<std::vector<uint8_t>> &Items);

} // namespace zen::evm::rlp

Expand Down
48 changes: 48 additions & 0 deletions tests/evm_spec_test/state_tests/frontier/opcodes/test_exp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""
Test EXP opcode.
"""

import pytest
from execution_testing import (
Alloc,
Fork,
Op,
StateTestFiller,
gas_test,
)

REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
Comment on lines +14 to +15

Copilot AI Jan 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The REFERENCE_SPEC_GIT_PATH should be set to a proper reference instead of "N/A". For the EXP opcode dynamic gas cost, this should reference "EIPS/eip-160.md" as the EXP opcode's dynamic gas cost (50 gas per byte of exponent) was defined in EIP-160.

Suggested change
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
REFERENCE_SPEC_GIT_PATH = "EIPS/eip-160.md"
REFERENCE_SPEC_VERSION = "EIP-160"

Copilot uses AI. Check for mistakes.


@pytest.mark.valid_from("Berlin")
@pytest.mark.parametrize(
"a", [0, 1, pytest.param(2**256 - 1, id="a2to256minus1")]
)
@pytest.mark.parametrize(
"exponent",
[
0,
1,
2,
1023,
1024,
pytest.param(2**255, id="exponent2to255"),
pytest.param(2**256 - 1, id="exponent2to256minus1"),
],
)
def test_gas(
state_test: StateTestFiller,
pre: Alloc,
a: int,
exponent: int,
fork: Fork,
) -> None:
"""Test that EXP gas works as expected."""
gas_test(
fork=fork,
state_test=state_test,
pre=pre,
setup_code=Op.PUSH32(exponent) + Op.PUSH32(a),
Comment on lines +20 to +46

Copilot AI Jan 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter name "a" is ambiguous. It should be renamed to "base" to match the EXP opcode semantics where EXP takes two operands: base and exponent. This would make the test more readable and align with the variable names used in the implementation (Base and Exponent in opcode_handlers.cpp line 316-317).

Copilot uses AI. Check for mistakes.
subject_code=Op.EXP(exponent=exponent),
)
Loading