Skip to content
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

Implement gas refund #493

Merged
merged 8 commits into from
Aug 19, 2022
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
5 changes: 3 additions & 2 deletions lib/evmone/advanced_execution.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ evmc_result execute(AdvancedExecutionState& state, const AdvancedCodeAnalysis& a

const auto gas_left =
(state.status == EVMC_SUCCESS || state.status == EVMC_REVERT) ? state.gas_left : 0;
const auto gas_refund = (state.status == EVMC_SUCCESS) ? state.gas_refund : 0;

assert(state.output_size != 0 || state.output_offset == 0);
return evmc::make_result(
state.status, gas_left, 0, state.memory.data() + state.output_offset, state.output_size);
return evmc::make_result(state.status, gas_left, gas_refund,
state.memory.data() + state.output_offset, state.output_size);
}

evmc_result execute(evmc_vm* /*unused*/, const evmc_host_interface* host, evmc_host_context* ctx,
Expand Down
3 changes: 2 additions & 1 deletion lib/evmone/baseline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,10 @@ evmc_result execute(const VM& vm, ExecutionState& state, const CodeAnalysis& ana
exit:
const auto gas_left =
(state.status == EVMC_SUCCESS || state.status == EVMC_REVERT) ? state.gas_left : 0;
const auto gas_refund = (state.status == EVMC_SUCCESS) ? state.gas_refund : 0;

assert(state.output_size != 0 || state.output_offset == 0);
const auto result = evmc::make_result(state.status, gas_left, 0,
const auto result = evmc::make_result(state.status, gas_left, gas_refund,
state.output_size != 0 ? &state.memory[state.output_offset] : nullptr, state.output_size);

if constexpr (TracingEnabled)
Expand Down
1 change: 1 addition & 0 deletions lib/evmone/execution_state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ class ExecutionState
{
public:
int64_t gas_left = 0;
int64_t gas_refund = 0;
Memory memory;
const evmc_message* msg = nullptr;
evmc::HostContext host;
Expand Down
6 changes: 5 additions & 1 deletion lib/evmone/instructions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,11 @@ inline StopToken selfdestruct(StackTop stack, ExecutionState& state) noexcept
}
}

state.host.selfdestruct(state.msg->recipient, beneficiary);
if (state.host.selfdestruct(state.msg->recipient, beneficiary))
{
if (state.rev < EVMC_LONDON)
state.gas_refund += 24000;
}
return {EVMC_SUCCESS};
}

Expand Down
2 changes: 2 additions & 0 deletions lib/evmone/instructions_calls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ evmc_status_code call_impl(StackTop stack, ExecutionState& state) noexcept

const auto gas_used = msg.gas - result.gas_left;
state.gas_left -= gas_used;
state.gas_refund += result.gas_refund;
return EVMC_SUCCESS;
}

Expand Down Expand Up @@ -161,6 +162,7 @@ evmc_status_code create_impl(StackTop stack, ExecutionState& state) noexcept

const auto result = state.host.call(msg);
state.gas_left -= msg.gas - result.gas_left;
state.gas_refund += result.gas_refund;

state.return_data.assign(result.output_data, result.output_size);
if (result.status_code == EVMC_SUCCESS)
Expand Down
148 changes: 94 additions & 54 deletions lib/evmone/instructions_storage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,91 @@

namespace evmone::instr::core
{
namespace
{
/// The gas cost specification for storage instructions.
struct StorageCostSpec
{
bool net_cost; ///< Is this net gas cost metering schedule?
int16_t warm_access; ///< Storage warm access cost, YP: G_{warmaccess}
int16_t set; ///< Storage addition cost, YP: G_{sset}
int16_t reset; ///< Storage modification cost, YP: G_{sreset}
int16_t clear; ///< Storage deletion refund, YP: R_{sclear}
};

/// Table of gas cost specification for storage instructions per EVM revision.
/// TODO: This can be moved to instruction traits and be used in other places: e.g.
/// SLOAD cost, replacement for warm_storage_read_cost.
constexpr auto storage_cost_spec = []() noexcept {
std::array<StorageCostSpec, EVMC_MAX_REVISION + 1> tbl{};

// Legacy cost schedule.
for (auto rev : {EVMC_FRONTIER, EVMC_HOMESTEAD, EVMC_TANGERINE_WHISTLE, EVMC_SPURIOUS_DRAGON,
EVMC_BYZANTIUM, EVMC_PETERSBURG})
tbl[rev] = {false, 200, 20000, 5000, 15000};

// Net cost schedule.
tbl[EVMC_CONSTANTINOPLE] = {true, 200, 20000, 5000, 15000};
tbl[EVMC_ISTANBUL] = {true, 800, 20000, 5000, 15000};
tbl[EVMC_BERLIN] = {
true, instr::warm_storage_read_cost, 20000, 5000 - instr::cold_sload_cost, 15000};
tbl[EVMC_LONDON] = {
true, instr::warm_storage_read_cost, 20000, 5000 - instr::cold_sload_cost, 4800};
tbl[EVMC_PARIS] = tbl[EVMC_LONDON];
tbl[EVMC_SHANGHAI] = tbl[EVMC_LONDON];
tbl[EVMC_CANCUN] = tbl[EVMC_LONDON];
return tbl;
}();


struct StorageStoreCost
{
int16_t gas_cost;
int16_t gas_refund;
};

// The lookup table of SSTORE costs by the storage update status.
constexpr auto sstore_costs = []() noexcept {
std::array<std::array<StorageStoreCost, EVMC_STORAGE_MODIFIED_RESTORED + 1>,
EVMC_MAX_REVISION + 1>
tbl{};

for (size_t rev = EVMC_FRONTIER; rev <= EVMC_MAX_REVISION; ++rev)
{
auto& e = tbl[rev];
if (const auto c = storage_cost_spec[rev]; !c.net_cost) // legacy
{
e[EVMC_STORAGE_ADDED] = {c.set, 0};
e[EVMC_STORAGE_DELETED] = {c.reset, c.clear};
e[EVMC_STORAGE_MODIFIED] = {c.reset, 0};
e[EVMC_STORAGE_ASSIGNED] = e[EVMC_STORAGE_MODIFIED];
e[EVMC_STORAGE_DELETED_ADDED] = e[EVMC_STORAGE_ADDED];
e[EVMC_STORAGE_MODIFIED_DELETED] = e[EVMC_STORAGE_DELETED];
e[EVMC_STORAGE_DELETED_RESTORED] = e[EVMC_STORAGE_ADDED];
e[EVMC_STORAGE_ADDED_DELETED] = e[EVMC_STORAGE_DELETED];
e[EVMC_STORAGE_MODIFIED_RESTORED] = e[EVMC_STORAGE_MODIFIED];
}
else // net cost
{
e[EVMC_STORAGE_ASSIGNED] = {c.warm_access, 0};
e[EVMC_STORAGE_ADDED] = {c.set, 0};
e[EVMC_STORAGE_DELETED] = {c.reset, c.clear};
e[EVMC_STORAGE_MODIFIED] = {c.reset, 0};
e[EVMC_STORAGE_DELETED_ADDED] = {c.warm_access, static_cast<int16_t>(-c.clear)};
e[EVMC_STORAGE_MODIFIED_DELETED] = {c.warm_access, c.clear};
e[EVMC_STORAGE_DELETED_RESTORED] = {
c.warm_access, static_cast<int16_t>(c.reset - c.warm_access - c.clear)};
e[EVMC_STORAGE_ADDED_DELETED] = {
c.warm_access, static_cast<int16_t>(c.set - c.warm_access)};
e[EVMC_STORAGE_MODIFIED_RESTORED] = {
c.warm_access, static_cast<int16_t>(c.reset - c.warm_access)};
}
}

return tbl;
}();
} // namespace

evmc_status_code sload(StackTop stack, ExecutionState& state) noexcept
{
auto& x = stack.top();
Expand Down Expand Up @@ -38,63 +123,18 @@ evmc_status_code sstore(StackTop stack, ExecutionState& state) noexcept
const auto key = intx::be::store<evmc::bytes32>(stack.pop());
const auto value = intx::be::store<evmc::bytes32>(stack.pop());

int cost = 0;
if (state.rev >= EVMC_BERLIN &&
state.host.access_storage(state.msg->recipient, key) == EVMC_ACCESS_COLD)
cost = instr::cold_sload_cost;

const auto gas_cost_cold =
(state.rev >= EVMC_BERLIN &&
state.host.access_storage(state.msg->recipient, key) == EVMC_ACCESS_COLD) ?
instr::cold_sload_cost :
0;
const auto status = state.host.set_storage(state.msg->recipient, key, value);

if (state.rev <= EVMC_BYZANTIUM || state.rev == EVMC_PETERSBURG) // legacy
{
switch (status)
{
case EVMC_STORAGE_ASSIGNED:
case EVMC_STORAGE_MODIFIED_DELETED:
case EVMC_STORAGE_ADDED_DELETED:
case EVMC_STORAGE_MODIFIED_RESTORED:
case EVMC_STORAGE_MODIFIED:
case EVMC_STORAGE_DELETED:
cost = 5000;
break;
case EVMC_STORAGE_ADDED:
case EVMC_STORAGE_DELETED_ADDED:
case EVMC_STORAGE_DELETED_RESTORED:
cost = 20000;
break;
}
}
else // net gas cost metering
{
switch (status)
{
case EVMC_STORAGE_ASSIGNED:
case EVMC_STORAGE_DELETED_ADDED:
case EVMC_STORAGE_DELETED_RESTORED:
case EVMC_STORAGE_MODIFIED_DELETED:
case EVMC_STORAGE_ADDED_DELETED:
case EVMC_STORAGE_MODIFIED_RESTORED:
if (state.rev >= EVMC_BERLIN)
cost += instr::warm_storage_read_cost;
else if (state.rev == EVMC_ISTANBUL)
cost = 800;
else
cost = 200; // Constantinople
break;
case EVMC_STORAGE_MODIFIED:
case EVMC_STORAGE_DELETED:
if (state.rev >= EVMC_BERLIN)
cost += 5000 - instr::cold_sload_cost;
else
cost = 5000;
break;
case EVMC_STORAGE_ADDED:
cost += 20000;
break;
}
}
if ((state.gas_left -= cost) < 0)
const auto [gas_cost_warm, gas_refund] = sstore_costs[state.rev][status];
const auto gas_cost = gas_cost_warm + gas_cost_cold;
if ((state.gas_left -= gas_cost) < 0)
return EVMC_OUT_OF_GAS;
state.gas_refund += gas_refund;
return EVMC_SUCCESS;
}
} // namespace evmone::instr::core
49 changes: 49 additions & 0 deletions test/unittests/evm_calls_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -738,3 +738,52 @@ TEST_P(evm, returndatacopy_outofrange)
execute(735, "60008080808080fa6000600260003e");
EXPECT_EQ(result.status_code, EVMC_INVALID_MEMORY_ACCESS);
}

TEST_P(evm, call_gas_refund_propagation)
{
rev = EVMC_LONDON;
host.accounts[msg.recipient].set_balance(1);
host.call_result.status_code = EVMC_SUCCESS;
host.call_result.gas_refund = 1;

const auto code_prolog = 7 * push(1);
for (const auto op :
{OP_CALL, OP_CALLCODE, OP_DELEGATECALL, OP_STATICCALL, OP_CREATE, OP_CREATE2})
{
execute(code_prolog + op);
EXPECT_STATUS(EVMC_SUCCESS);
EXPECT_EQ(result.gas_refund, 1);
}
}

TEST_P(evm, call_gas_refund_aggregation_different_calls)
{
rev = EVMC_LONDON;
host.accounts[msg.recipient].set_balance(1);
host.call_result.status_code = EVMC_SUCCESS;
host.call_result.gas_refund = 1;

const auto a = 0xaa_address;
const auto code =
call(a) + callcode(a) + delegatecall(a) + staticcall(a) + create() + create2();
Copy link
Member

Choose a reason for hiding this comment

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

may be redundant, but I thought of having two calls of each type to check that it's also aggregated for same type calls.

execute(code);
EXPECT_STATUS(EVMC_SUCCESS);
EXPECT_EQ(result.gas_refund, 6);
}

TEST_P(evm, call_gas_refund_aggregation_same_calls)
{
rev = EVMC_LONDON;
host.accounts[msg.recipient].set_balance(2);
host.call_result.status_code = EVMC_SUCCESS;
host.call_result.gas_refund = 1;

const auto code_prolog = 14 * push(1);
for (const auto op :
{OP_CALL, OP_CALLCODE, OP_DELEGATECALL, OP_STATICCALL, OP_CREATE, OP_CREATE2})
{
execute(code_prolog + 2 * op);
EXPECT_STATUS(EVMC_SUCCESS);
EXPECT_EQ(result.gas_refund, 2);
}
}
28 changes: 28 additions & 0 deletions test/unittests/evm_state_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,34 @@ TEST_P(evm, selfdestruct_with_balance)
host.recorded_account_accesses.clear();
}

TEST_P(evm, selfdestruct_gas_refund)
{
rev = EVMC_BERLIN; // The last revision with gas refund.
const auto code = selfdestruct(0xbe);
execute(code);
EXPECT_GAS_USED(EVMC_SUCCESS, 7603); // Cold access to 0xbe.
EXPECT_EQ(result.gas_refund, 24000);

// Second selfdestruct of the same account.
Copy link
Member

Choose a reason for hiding this comment

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

Maybe also add a test for another selfdestruct of different account

execute(code);
EXPECT_GAS_USED(EVMC_SUCCESS, 5003); // Warm access to 0xbe.
EXPECT_EQ(result.gas_refund, 0); // No refund.

// Third selfdestruct - from different account.
msg.recipient = 0x01_address;
execute(code);
EXPECT_GAS_USED(EVMC_SUCCESS, 5003); // Warm access to 0xbe.
EXPECT_EQ(result.gas_refund, 24000);
}

TEST_P(evm, selfdestruct_no_gas_refund)
{
rev = EVMC_LONDON; // Since London there is no gas refund.
execute(selfdestruct(0xbe));
EXPECT_GAS_USED(EVMC_SUCCESS, 7603);
EXPECT_EQ(result.gas_refund, 0);
}


TEST_P(evm, blockhash)
{
Expand Down
Loading