Skip to content

Commit

Permalink
Merge pull request #214 from eosnetworkfoundation/elmato/count-storag…
Browse files Browse the repository at this point in the history
…e-gas

Track storage usage
  • Loading branch information
elmato authored Oct 25, 2024
2 parents c42fe0c + d8b359c commit b5b543b
Show file tree
Hide file tree
Showing 6 changed files with 444 additions and 115 deletions.
107 changes: 80 additions & 27 deletions silkworm/core/execution/evm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,24 @@

namespace silkworm {

void revert_speculative_gas(evmc::Result& res) {
res.gas_left += res.storage_gas_consumed;
res.gas_left += res.speculative_cpu_gas_consumed;
res.gas_refund = 0;
res.storage_gas_consumed = 0;
res.storage_gas_refund = 0;
res.speculative_cpu_gas_consumed = 0;
};

void revert_speculative_gas(evmc_result& res) {
res.gas_left += res.storage_gas_consumed;
res.gas_left += res.speculative_cpu_gas_consumed;
res.gas_refund = 0;
res.storage_gas_consumed = 0;
res.storage_gas_refund = 0;
res.speculative_cpu_gas_consumed = 0;
};

class DelegatingTracer : public evmone::Tracer {
public:
explicit DelegatingTracer(EvmTracer& tracer, IntraBlockState& intra_block_state) noexcept
Expand Down Expand Up @@ -96,11 +114,14 @@ CallResult EVM::execute(const Transaction& txn, uint64_t gas) noexcept {

const auto gas_left = static_cast<uint64_t>(res.gas_left);
const auto gas_refund = static_cast<uint64_t>(res.gas_refund);
return {res.status_code, gas_left, gas_refund, {res.output_data, res.output_size}};
const auto storage_gas_consumed = static_cast<uint64_t>(res.storage_gas_consumed);
const auto storage_gas_refund = static_cast<uint64_t>(res.storage_gas_refund);
const auto speculative_cpu_gas_consumed = static_cast<uint64_t>(res.speculative_cpu_gas_consumed);
return {res.status_code, gas_left, gas_refund, storage_gas_consumed, storage_gas_refund, speculative_cpu_gas_consumed, {res.output_data, res.output_size}};
}

evmc::Result EVM::create(const evmc_message& message) noexcept {
evmc::Result res{EVMC_SUCCESS, message.gas, 0};
evmc::Result res(EVMC_SUCCESS, message.gas, 0, 0, 0, 0, nullptr, 0);

auto value{intx::be::load<intx::uint256>(message.value)};
if (state_.get_balance(message.sender) < value) {
Expand Down Expand Up @@ -160,14 +181,26 @@ evmc::Result EVM::create(const evmc_message& message) noexcept {

if (evm_res.status_code == EVMC_SUCCESS) {
const size_t code_len{evm_res.output_size};
const uint64_t code_deploy_gas{code_len * (eos_evm_version_ > 0 ? gas_params_.G_codedeposit : protocol::fee::kGCodeDeposit)};

uint64_t code_deploy_gas{code_len * (eos_evm_version_ > 0 ? gas_params_.G_codedeposit : protocol::fee::kGCodeDeposit)};
if (rev >= EVMC_SPURIOUS_DRAGON && code_len > protocol::kMaxCodeSize) {
// EIP-170: Contract code size limit
evm_res.status_code = EVMC_OUT_OF_GAS;
} else if (rev >= EVMC_LONDON && code_len > 0 && evm_res.output_data[0] == 0xEF) {
// EIP-3541: Reject new contract code starting with the 0xEF byte
evm_res.status_code = EVMC_CONTRACT_VALIDATION_FAILURE;
} else if (eos_evm_version_ >= 3) {
evmone::gas_state_t tmp_gas_state(eos_evm_version_, evm_res.gas_refund, evm_res.storage_gas_consumed, evm_res.storage_gas_refund, evm_res.speculative_cpu_gas_consumed);
code_deploy_gas = static_cast<uint64_t>(tmp_gas_state.apply_storage_gas_delta(static_cast<int64_t>(code_deploy_gas)));
if((evm_res.gas_left -= static_cast<int64_t>(code_deploy_gas)) < 0) {
evm_res.status_code = EVMC_OUT_OF_GAS;
} else {
state_.set_code(contract_addr, {evm_res.output_data, evm_res.output_size});
}
assert(tmp_gas_state.cpu_gas_refund() == emv_res.gas_refund);
assert(tmp_gas_state.speculative_cpu_gas_consumed() == emv_res.speculative_cpu_gas_consumed);
evm_res.storage_gas_consumed = tmp_gas_state.storage_gas_consumed();
evm_res.storage_gas_refund = tmp_gas_state.storage_gas_refund();
state_.set_code(contract_addr, {evm_res.output_data, evm_res.output_size});
} else if (evm_res.gas_left >= 0 && static_cast<uint64_t>(evm_res.gas_left) >= code_deploy_gas) {
evm_res.gas_left -= static_cast<int64_t>(code_deploy_gas);
state_.set_code(contract_addr, {evm_res.output_data, evm_res.output_size});
Expand All @@ -180,9 +213,13 @@ evmc::Result EVM::create(const evmc_message& message) noexcept {
evm_res.create_address = contract_addr;
} else {
state_.revert_to_snapshot(snapshot);
evm_res.gas_refund = 0;
if (evm_res.status_code != EVMC_REVERT) {
evm_res.gas_left = 0;
if(eos_evm_version_ >= 3) {
revert_speculative_gas(evm_res);
} else {
evm_res.gas_refund = 0;
if (evm_res.status_code != EVMC_REVERT) {
evm_res.gas_left = 0;
}
}
}

Expand All @@ -194,8 +231,9 @@ evmc::Result EVM::create(const evmc_message& message) noexcept {
return evmc::Result{evm_res};
}

evmc::Result EVM::call(const evmc_message& message) noexcept {
evmc::Result res{EVMC_SUCCESS, message.gas};
evmc::Result EVM::call(const evmc_message& msg) noexcept {
evmc_message message{msg};
evmc::Result res(EVMC_SUCCESS, message.gas, 0, 0, 0, 0, nullptr, 0);

auto at_exit = gsl::finally([&] {
if(res.status_code == EVMC_SUCCESS && message_filter_ && (*message_filter_)(message)) {
Expand Down Expand Up @@ -255,38 +293,54 @@ evmc::Result EVM::call(const evmc_message& message) noexcept {
tracer.get().on_execution_end(res.raw(),state_);
}
} else {

evmc_message revisted_message{message};
if(eos_evm_version_ > 0 && revisted_message.depth == 0 && !is_zero(evmc::bytes32{revisted_message.value}) && !recipient_exists) {
if ((res.gas_left -= static_cast<int64_t>(gas_params_.G_txnewaccount)) < 0) {
if(eos_evm_version_ > 0 && message.depth == 0 && !is_zero(evmc::bytes32{message.value}) && !recipient_exists) {
int64_t cost = static_cast<int64_t>(gas_params_.G_txnewaccount);
if( eos_evm_version_ >= 3 ) {
assert(res.storage_gas_consumed == 0);
assert(res.storage_gas_refund == 0);
res.storage_gas_consumed = cost; // This is equivalent to state.apply_storage_gas_delta(cost)
}
if ((res.gas_left -= cost) < 0) {
res.status_code = EVMC_OUT_OF_GAS;
// If we run out of gas lets do everything here
state_.revert_to_snapshot(snapshot);
res.status_code = EVMC_OUT_OF_GAS;
res.gas_refund = 0;
res.gas_left = 0;
if( eos_evm_version_ >= 3 ) {
revert_speculative_gas(res);
} else {
res.gas_refund = 0;
res.gas_left = 0;
}
for (auto tracer : tracers_) {
tracer.get().on_execution_start(rev, revisted_message, {});
tracer.get().on_execution_start(rev, message, {});
tracer.get().on_execution_end(res.raw(), state_);
}
return res;
}
revisted_message.gas = res.gas_left;
message.gas = res.gas_left;
}

const ByteView code{state_.get_code(revisted_message.code_address)};
const ByteView code{state_.get_code(message.code_address)};
if (code.empty() && tracers_.empty()) { // Do not skip execution if there are any tracers
return res;
}

const evmc::bytes32 code_hash{state_.get_code_hash(revisted_message.code_address)};
res = evmc::Result{execute(revisted_message, code, &code_hash)};
const evmc::bytes32 code_hash{state_.get_code_hash(message.code_address)};
const auto storage_gas_consumed = res.storage_gas_consumed;
res = evmc::Result{execute(message, code, &code_hash)};
if( eos_evm_version_ >= 3 ) {
res.storage_gas_consumed += storage_gas_consumed;
}
}

if (res.status_code != EVMC_SUCCESS) {
state_.revert_to_snapshot(snapshot);
res.gas_refund = 0;
if (res.status_code != EVMC_REVERT) {
res.gas_left = 0;
if(eos_evm_version_ >= 3) {
revert_speculative_gas(res);
} else {
res.gas_refund = 0;
if (res.status_code != EVMC_REVERT) {
res.gas_left = 0;
}
}
}

Expand Down Expand Up @@ -343,8 +397,7 @@ evmc_result EVM::execute_with_baseline_interpreter(evmc_revision rev, const evmc
EvmHost host{*this};
gsl::owner<evmone::ExecutionState*> state{acquire_state()};

state->reset(msg, rev, host.get_interface(), host.to_context(), code, gas_params_);
state->eos_evm_version = eos_evm_version_;
state->reset(msg, rev, host.get_interface(), host.to_context(), code, gas_params_, eos_evm_version_);

const auto vm{static_cast<evmone::VM*>(evm1_)};
evmc_result res{evmone::baseline::execute(*vm, msg.gas, *state, *analysis)};
Expand Down Expand Up @@ -497,7 +550,7 @@ evmc::Result EvmHost::call(const evmc_message& message) noexcept {
// geth returns CREATE output only in case of REVERT
return res;
} else {
evmc::Result res_with_no_output{res.status_code, res.gas_left, res.gas_refund};
evmc::Result res_with_no_output(res.status_code, res.gas_left, res.gas_refund, res.storage_gas_consumed, res.storage_gas_refund, res.speculative_cpu_gas_consumed, nullptr, 0);
res_with_no_output.create_address = res.create_address;
return res_with_no_output;
}
Expand Down
3 changes: 3 additions & 0 deletions silkworm/core/execution/evm.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ struct CallResult {
evmc_status_code status{EVMC_SUCCESS};
uint64_t gas_left{0};
uint64_t gas_refund{0};
uint64_t storage_gas_consumed{0};
uint64_t storage_gas_refund{0};
uint64_t speculative_cpu_gas_consumed{0};
Bytes data;
};

Expand Down
152 changes: 151 additions & 1 deletion silkworm/core/execution/evm_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,11 @@ class TestTracer : public EvmTracer {
execution_end_called_ = true;
const auto gas_left = static_cast<uint64_t>(res.gas_left);
const auto gas_refund = static_cast<uint64_t>(res.gas_refund);
result_ = {res.status_code, gas_left, gas_refund, {res.output_data, res.output_size}};
const auto storage_gas_consumed = static_cast<uint64_t>(res.storage_gas_consumed);
const auto storage_gas_refund = static_cast<uint64_t>(res.storage_gas_refund);
const auto speculative_cpu_gas_consumed = static_cast<uint64_t>(res.speculative_cpu_gas_consumed);

result_ = {res.status_code, gas_left, gas_refund, storage_gas_consumed, storage_gas_refund, speculative_cpu_gas_consumed, {res.output_data, res.output_size}};
if (contract_address_ && !pc_stack_.empty()) {
const auto pc = pc_stack_.back();
storage_stack_[pc] =
Expand Down Expand Up @@ -749,6 +753,90 @@ TEST_CASE("EOS EVM codedeposit test") {
CallResult res = evm.execute(txn, 1'500'000);
CHECK(res.status == EVMC_SUCCESS);
CHECK(gas-res.gas_left == 123 + 500*371); //G_codedeposit=500, codelen=371
CHECK(res.storage_gas_consumed == 0);
CHECK(res.storage_gas_refund == 0);
CHECK(res.speculative_cpu_gas_consumed == 0);
}

TEST_CASE("EOS EVM codedeposit v3 test") {
Block block{};
block.header.number = 1;
block.header.nonce = eosevm::version_to_nonce(3);

evmc::address caller{0x0a6bb546b9208cfab9e8fa2b9b2c042b18df7030_address};
Bytes code{*from_hex("608060405234801561001057600080fd5b50610173806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c806313bdfacd14610030575b600080fd5b61003861004e565b604051610045919061011b565b60405180910390f35b60606040518060400160405280600c81526020017f48656c6c6f20576f726c64210000000000000000000000000000000000000000815250905090565b600081519050919050565b600082825260208201905092915050565b60005b838110156100c55780820151818401526020810190506100aa565b60008484015250505050565b6000601f19601f8301169050919050565b60006100ed8261008b565b6100f78185610096565b93506101078185602086016100a7565b610110816100d1565b840191505092915050565b6000602082019050818103600083015261013581846100e2565b90509291505056fea264697066735822122046344ce4c7e7c91dba98aef897cc7773af40fbff6b6da10885c36037b9d37a3764736f6c63430008110033")};

evmone::gas_parameters gas_params;
gas_params.G_codedeposit = 500;

InMemoryState db;
IntraBlockState state{db};
state.set_balance(caller, intx::uint256{1e18});
state.set_nonce(caller, 10);
EVM evm{block, state, test::kIstanbulTrustConfig, gas_params};

Transaction txn{};
txn.from = caller;
txn.data = code;

uint64_t gas = 1'500'000;
CallResult res = evm.execute(txn, gas);
CHECK(res.status == EVMC_SUCCESS);
CHECK(gas-res.gas_left == 123 + 500*371); //G_codedeposit=500, codelen=371
CHECK(res.storage_gas_consumed == 500*371);
CHECK(res.storage_gas_refund == 0);
CHECK(res.speculative_cpu_gas_consumed == 0);

// verify that the code was indeed persisted
auto contract_addr = create_address(caller, 10);
auto c = state.get_code(contract_addr);
CHECK(c.size() != 0);
}

TEST_CASE("EOS EVM codedeposit v3 test oog") {
Block block{};
block.header.number = 1;
block.header.nonce = eosevm::version_to_nonce(3);

evmc::address caller{0x0a6bb546b9208cfab9e8fa2b9b2c042b18df7030_address};
Bytes code{*from_hex("608060405234801561001057600080fd5b50610173806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c806313bdfacd14610030575b600080fd5b61003861004e565b604051610045919061011b565b60405180910390f35b60606040518060400160405280600c81526020017f48656c6c6f20576f726c64210000000000000000000000000000000000000000815250905090565b600081519050919050565b600082825260208201905092915050565b60005b838110156100c55780820151818401526020810190506100aa565b60008484015250505050565b6000601f19601f8301169050919050565b60006100ed8261008b565b6100f78185610096565b93506101078185602086016100a7565b610110816100d1565b840191505092915050565b6000602082019050818103600083015261013581846100e2565b90509291505056fea264697066735822122046344ce4c7e7c91dba98aef897cc7773af40fbff6b6da10885c36037b9d37a3764736f6c63430008110033")};

evmone::gas_parameters gas_params;
gas_params.G_codedeposit = 500;

InMemoryState db;
IntraBlockState state{db};
state.set_balance(caller, intx::uint256{1e18});
EVM evm{block, state, test::kIstanbulTrustConfig, gas_params};

Transaction txn{};
txn.from = caller;
txn.data = code;

uint64_t gas = 123 + 500*370; // enough to run initialization (real), but not enough to store code (speculative)
CallResult res = evm.execute(txn, gas);
CHECK(res.status == EVMC_OUT_OF_GAS);
CHECK(res.gas_left == 500*370);
CHECK(res.storage_gas_consumed == 0);
CHECK(res.storage_gas_refund == 0);
CHECK(res.speculative_cpu_gas_consumed == 0);

InMemoryState db2;
IntraBlockState state2{db2};
state2.set_balance(caller, intx::uint256{1e18});
EVM evm2{block, state2, test::kIstanbulTrustConfig, gas_params};

Transaction txn2{};
txn2.from = caller;
txn2.data = code;

uint64_t gas2 = 122; // not-enough to run initialization (real)
CallResult res2 = evm2.execute(txn, gas2);
CHECK(res2.status == EVMC_OUT_OF_GAS);
CHECK(res2.gas_left == 0);
CHECK(res2.storage_gas_consumed == 0);
CHECK(res2.storage_gas_refund == 0);
CHECK(res2.speculative_cpu_gas_consumed == 0);
}

TEST_CASE("EOS EVM G_txnewaccount") {
Expand Down Expand Up @@ -777,6 +865,9 @@ TEST_CASE("EOS EVM G_txnewaccount") {
CHECK(res.status == EVMC_SUCCESS);
CHECK(res.gas_left == 0);
CHECK(res.gas_refund == 0);
CHECK(res.storage_gas_consumed == 0);
CHECK(res.storage_gas_refund == 0);
CHECK(res.speculative_cpu_gas_consumed == 0);

txn.to = receiver2;
gas_params.G_txnewaccount = 1;
Expand All @@ -786,9 +877,61 @@ TEST_CASE("EOS EVM G_txnewaccount") {
CHECK(res.status == EVMC_OUT_OF_GAS);
CHECK(res.gas_refund == 0);
CHECK(res.gas_left == 0);
CHECK(res.storage_gas_consumed == 0);
CHECK(res.storage_gas_refund == 0);
CHECK(res.speculative_cpu_gas_consumed == 0);
}

TEST_CASE("EOS EVM G_txnewaccount v3") {
Block block{};
block.header.number = 1;
block.header.nonce = eosevm::version_to_nonce(3);

evmc::address sender{0x0a6bb546b9208cfab9e8fa2b9b2c042b18df7030_address};
evmc::address receiver1{0x1a6bb546b9208cfab9e8fa2b9b2c042b18df7030_address};
evmc::address receiver2{0x1000000000000000000000000000000000000001_address};

evmone::gas_parameters gas_params;
gas_params.G_txnewaccount = 1000;

InMemoryState db;
IntraBlockState state{db};
state.set_balance(sender, intx::uint256{1e18});
EVM evm{block, state, test::kIstanbulTrustConfig, gas_params};

Transaction txn{};
txn.from = sender;
txn.to = receiver1;
txn.value = intx::uint256{1};

CallResult res = evm.execute(txn, 1000);
CHECK(res.status == EVMC_SUCCESS);
CHECK(res.gas_left == 0);
CHECK(res.gas_refund == 0);
CHECK(res.storage_gas_consumed == 1000);
CHECK(res.storage_gas_refund == 0);
CHECK(res.speculative_cpu_gas_consumed == 0);

InMemoryState db2;
IntraBlockState state2{db2};
state2.set_balance(sender, intx::uint256{1e18});
EVM evm2{block, state2, test::kIstanbulTrustConfig, gas_params};

Transaction txn2{};
txn2.from = sender;
txn2.to = receiver1;
txn2.value = intx::uint256{1};

CallResult res2 = evm2.execute(txn2, 999);
CHECK(res2.status == EVMC_OUT_OF_GAS);
CHECK(res2.gas_left == 999);
CHECK(res2.gas_refund == 0);
CHECK(res2.storage_gas_consumed == 0);
CHECK(res2.storage_gas_refund == 0);
CHECK(res2.speculative_cpu_gas_consumed == 0);
}


TEST_CASE("EOS EVM send value to reserved address (tx)") {

auto send_tx_to_reserved_address = [&](uint64_t version, const evmone::gas_parameters& gas_params, uint64_t gas_limit) {
Expand Down Expand Up @@ -822,13 +965,20 @@ TEST_CASE("EOS EVM send value to reserved address (tx)") {
CHECK(res1.status == EVMC_SUCCESS);
CHECK(res1.gas_left == 1000);
CHECK(res1.gas_refund == 0);
CHECK(res1.storage_gas_consumed == 0);
CHECK(res1.storage_gas_refund == 0);
CHECK(res1.speculative_cpu_gas_consumed == 0);

//version = 2, G_txnewaccount = 5000, gas_limit = 4999
gas_params.G_txnewaccount = 5000;
auto res2 = send_tx_to_reserved_address(1, gas_params, 4999);
CHECK(res2.status == EVMC_SUCCESS);
CHECK(res2.gas_left == 4999);
CHECK(res2.gas_refund == 0);
CHECK(res2.storage_gas_consumed == 0);
CHECK(res2.storage_gas_refund == 0);
CHECK(res2.speculative_cpu_gas_consumed == 0);

}

} // namespace silkworm
Loading

0 comments on commit b5b543b

Please sign in to comment.