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
2 changes: 2 additions & 0 deletions docs/modules/cli/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ evmc::address computeCreateAddress(evmc::address, uint64_t nonce);
bool readBinaryFile(path, std::vector<uint8_t>&);
bool saveState(evmc::MockedHost const&, path);
bool loadState(evmc::MockedHost&, path);
EvmUpfrontGasResult applyEvmUpfrontGas(evmc::MockedHost&, evmc_message&, uint64_t, evmc_revision);
void applyEvmPostExecutionSettlement(evmc::MockedHost&, const evmc_message&, uint64_t, const evmc::Result&, evmc_revision);

// zen::utils (logging)
std::shared_ptr<ILogger> createConsoleLogger(name, LoggerLevel);
Expand Down
4 changes: 4 additions & 0 deletions docs/modules/utils/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ The utils module is DTVM's **utility and shared facilities layer**, providing cr
| `bytesToHex` | `std::string bytesToHex(const std::vector<uint8_t>& Value)` | Byte vector to hex |
| `uint256beToBytes` | `std::vector<uint8_t> uint256beToBytes(const evmc::uint256be& Value)` | uint256be to bytes |
| `computeCreateAddress` | `evmc::address computeCreateAddress(const evmc::address& Sender, uint64_t SenderNonce)` | Create contract address |
| `computeRefundCap` | `uint64_t computeRefundCap(evmc_revision Revision, uint64_t GasUsed)` | Revision-aware EVM gas refund cap |
| `computeEvmTransactionFees` | `EvmFeeComponents computeEvmTransactionFees(const evmc::uint256be& EffectiveOrMaxFeePerGas, const evmc::uint256be& BaseFee, const std::optional<evmc::uint256be>& MaxPriorityFee)` | Effective gas price and priority fee |
| `applyEvmUpfrontGas` | `EvmUpfrontGasResult applyEvmUpfrontGas(evmc::MockedHost& Host, evmc_message& Msg, uint64_t GasLimit, evmc_revision Revision)` | Deduct intrinsic gas, prewarm accounts, and prepay gas |
| `applyEvmPostExecutionSettlement` | `void applyEvmPostExecutionSettlement(evmc::MockedHost& Host, const evmc_message& Msg, uint64_t GasLimit, const evmc::Result& Result, evmc_revision Revision)` | Apply refund cap, refund unused gas, and pay priority fee |
| `saveState` | `bool saveState(const evmc::MockedHost& Host, const std::string& FilePath)` | MockedHost state persistence |
| `loadState` | `bool loadState(evmc::MockedHost& Host, const std::string& FilePath)` | MockedHost state load |

Expand Down
93 changes: 29 additions & 64 deletions src/cli/dtvm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -387,16 +387,6 @@ int main(int argc, char *argv[]) {
return exitMain(EXIT_FAILURE, RT.get());
}

MayBe<EVMInstance *> InstRet = Iso->createEVMInstance(*Mod, GasLimit);
if (!InstRet) {
const Error &Err = InstRet.getError();
ZEN_ASSERT(!Err.isEmpty());
const auto &ErrMsg = Err.getFormattedMessage(false);
SIMPLE_LOG_ERROR("failed to create EVM instance: %s", ErrMsg.c_str());
return exitMain(EXIT_FAILURE, RT.get());
}
EVMInstance *Inst = *InstRet;
Inst->setRevision(EvmRevision);
evmc_call_kind MsgKind = DeployMode ? EVMC_CREATE : EVMC_CALL;
evmc::Result ExeResult;
std::vector<uint8_t> Bytecode;
Expand Down Expand Up @@ -443,66 +433,41 @@ int main(int argc, char *argv[]) {
}
}

// Deduct intrinsic gas before EVM execution.
const int64_t IntrinsicGas = zen::utils::computeIntrinsicGas(
EvmRevision, MsgKind, Msg.input_data, Msg.input_size);
if (Msg.gas < IntrinsicGas) {
ZEN_LOG_ERROR("intrinsic gas (%ld) exceeds gas limit (%ld)",
(long)IntrinsicGas, (long)Msg.gas);
return exitMain(EVMC_OUT_OF_GAS, RT.get());
}
Msg.gas -= IntrinsicGas;

// EIP-2929/EIP-3651: Pre-warm transaction-level accounts.
zen::utils::prewarmTransactionAccounts(
MockedHost, EvmRevision, Msg.sender, Msg.recipient,
MockedHost.tx_context.block_coinbase);

// Deduct upfront gas cost from sender's balance before execution.
// Per EVM spec (Yellow Paper §6), the sender's balance is reduced by
// effective_gas_price * gas_limit at the start of transaction execution.
intx::uint256 GasPrice =
intx::be::load<intx::uint256>(MockedHost.tx_context.tx_gas_price);
intx::uint256 BaseFee =
intx::be::load<intx::uint256>(MockedHost.tx_context.block_base_fee);
intx::uint256 EffectiveGasPrice = GasPrice > BaseFee ? GasPrice : BaseFee;
intx::uint256 UpfrontGasCost = intx::uint256(GasLimit) * EffectiveGasPrice;
auto &SenderAccount = MockedHost.accounts[Msg.sender];
intx::uint256 SenderBalance =
intx::be::load<intx::uint256>(SenderAccount.balance);
if (SenderBalance < UpfrontGasCost) {
// Apply upfront gas: deduct intrinsic gas, pre-warm accounts, and deduct
// upfront balance. Failure exits with the appropriate EVM status code.
const auto UpfrontResult =
zen::utils::applyEvmUpfrontGas(MockedHost, Msg, GasLimit, EvmRevision);
if (UpfrontResult != zen::utils::EvmUpfrontGasResult::Success) {
if (UpfrontResult ==
zen::utils::EvmUpfrontGasResult::IntrinsicGasExceedsLimit) {
const int64_t IntrinsicGas = zen::utils::computeIntrinsicGas(
EvmRevision, MsgKind, Msg.input_data, Msg.input_size);
ZEN_LOG_ERROR("intrinsic gas (%ld) exceeds gas limit (%ld)",
(long)IntrinsicGas, (long)(GasLimit));
return exitMain(EVMC_OUT_OF_GAS, RT.get());
}
ZEN_LOG_ERROR("sender balance insufficient for upfront gas cost");
return exitMain(EVMC_INSUFFICIENT_BALANCE, RT.get());
}
SenderBalance -= UpfrontGasCost;
SenderAccount.balance = intx::be::store<evmc::bytes32>(SenderBalance);

RT->callEVMMain(*Inst, Msg, ExeResult);

// Settle gas charges after execution: refund unused gas to sender,
// pay priority fee to coinbase.
uint64_t GasUsed = static_cast<uint64_t>(
GasLimit - (ExeResult.gas_left > 0 ? ExeResult.gas_left : 0));
GasUsed += static_cast<uint64_t>(IntrinsicGas);
intx::uint256 PriorityFee =
GasPrice > BaseFee ? GasPrice - BaseFee : intx::uint256{0};
// Refund unused gas: (GasLimit - GasUsed) * EffectiveGasPrice
if (GasLimit > GasUsed) {
intx::uint256 Refund =
intx::uint256(GasLimit - GasUsed) * EffectiveGasPrice;
SenderBalance += Refund;
SenderAccount.balance = intx::be::store<evmc::bytes32>(SenderBalance);
}
// Pay priority fee to coinbase: GasUsed * PriorityFee
if (PriorityFee != intx::uint256{0}) {
auto &CoinbaseAccount =
MockedHost.accounts[MockedHost.tx_context.block_coinbase];
intx::uint256 CoinbaseBalance =
intx::be::load<intx::uint256>(CoinbaseAccount.balance);
CoinbaseBalance += intx::uint256(GasUsed) * PriorityFee;
CoinbaseAccount.balance = intx::be::store<evmc::bytes32>(CoinbaseBalance);
// Create the instance only after Msg.gas has been reduced by intrinsic
// gas so that both interpreter and JIT paths start with the same gas.
MayBe<EVMInstance *> InstRet =
Iso->createEVMInstance(*Mod, static_cast<uint64_t>(Msg.gas));
if (!InstRet) {
const Error &Err = InstRet.getError();
ZEN_ASSERT(!Err.isEmpty());
const auto &ErrMsg = Err.getFormattedMessage(false);
SIMPLE_LOG_ERROR("failed to create EVM instance: %s", ErrMsg.c_str());
return exitMain(EXIT_FAILURE, RT.get());
}
EVMInstance *Inst = *InstRet;
Inst->setRevision(EvmRevision);

RT->callEVMMain(*Inst, Msg, ExeResult);

zen::utils::applyEvmPostExecutionSettlement(MockedHost, Msg, GasLimit,
ExeResult, EvmRevision);
if (EVMC_CREATE == MsgKind && ExeResult.status_code == EVMC_SUCCESS) {
evmc::address DeployerAddr = zen::utils::parseAddress(SenderAddress);
auto &DeployerAccount = MockedHost.accounts[DeployerAddr];
Expand Down
Loading
Loading