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

New evmone API: StateView & StateDiff #802

Merged
merged 6 commits into from
Oct 9, 2024
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 test/state/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ target_sources(
rlp.hpp
state.hpp
state.cpp
state_diff.hpp
state_view.hpp
system_contracts.hpp
system_contracts.cpp
test_state.hpp
Expand Down
24 changes: 19 additions & 5 deletions test/state/account.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace evmone::state
using evmc::address;
using evmc::bytes;
using evmc::bytes32;
using namespace evmc::literals;

/// The representation of the account storage value.
struct StorageValue
Expand All @@ -31,25 +32,38 @@ struct Account
/// The maximum allowed nonce value.
static constexpr auto NonceMax = std::numeric_limits<uint64_t>::max();

/// The keccak256 hash of the empty input. Used to identify empty account's code.
static constexpr auto EMPTY_CODE_HASH =
0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470_bytes32;

/// The account nonce.
uint64_t nonce = 0;

/// The account balance.
intx::uint256 balance;

/// The account storage map.
bytes32 code_hash = EMPTY_CODE_HASH;

/// If the account has non-empty initial storage (when accessing the cold account).
bool has_initial_storage = false;

/// The cached and modified account storage entries.
std::unordered_map<bytes32, StorageValue> storage;

/// The EIP-1153 transient (transaction-level lifetime) storage.
std::unordered_map<bytes32, bytes32> transient_storage;

/// The account code.
/// The cache of the account code.
///
/// Check code_hash to know if an account code is empty.
/// Empty here only means it has not been loaded from the initial storage.
bytes code;

/// The account has been destructed and should be erased at the end of of a transaction.
/// The account has been destructed and should be erased at the end of a transaction.
bool destructed = false;

/// The account should be erased if it is empty at the end of a transaction.
/// This flag means the account has been "touched" as defined in EIP-161
/// This flag means the account has been "touched" as defined in EIP-161,
/// or it is a newly created temporary account.
///
/// Yellow Paper uses term "delete" but it is a keyword in C++ while
Expand All @@ -63,7 +77,7 @@ struct Account

[[nodiscard]] bool is_empty() const noexcept
{
return code.empty() && nonce == 0 && balance == 0;
return nonce == 0 && balance == 0 && code_hash == EMPTY_CODE_HASH;
}
};
} // namespace evmone::state
49 changes: 27 additions & 22 deletions test/state/host.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@

bytes32 Host::get_storage(const address& addr, const bytes32& key) const noexcept
{
const auto& acc = m_state.get(addr);
if (const auto it = acc.storage.find(key); it != acc.storage.end())
return it->second.current;
return {};
return m_state.get_storage(addr, key).current;
}

evmc_storage_status Host::set_storage(
Expand All @@ -30,7 +27,7 @@
// Follow EVMC documentation https://evmc.ethereum.org/storagestatus.html#autotoc_md3
// and EIP-2200 specification https://eips.ethereum.org/EIPS/eip-2200.

auto& storage_slot = m_state.get(addr).storage[key];
auto& storage_slot = m_state.get_storage(addr, key);
const auto& [current, original, _] = storage_slot;

const auto dirty = original != current;
Expand Down Expand Up @@ -92,41 +89,49 @@
/// as defined in the [EIP-7610](https://eips.ethereum.org/EIPS/eip-7610).
[[nodiscard]] bool is_create_collision(const Account& acc) noexcept
{
if (acc.nonce != 0 || !acc.code.empty())
// TODO: This requires much more testing:
Copy link
Collaborator

Choose a reason for hiding this comment

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

better file as issue(s)?

Copy link
Member Author

Choose a reason for hiding this comment

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

// - what if an account had storage but is destructed?
// - what if an account had cold storage but it was emptied?
// - what if an account without cold storage gain one?
if (acc.nonce != 0)
return true;

// acc.storage may have entries from access list, even if account storage is empty.
// Check for non-zero current values.
if (std::ranges::any_of(
acc.storage, [](auto& e) noexcept { return !is_zero(e.second.current); }))
if (acc.code_hash != Account::EMPTY_CODE_HASH)
return true;

Check warning on line 99 in test/state/host.cpp

View check run for this annotation

Codecov / codecov/patch

test/state/host.cpp#L99

Added line #L99 was not covered by tests
Copy link
Member Author

Choose a reason for hiding this comment

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

This is not covered by any tests, so more work for #1037. Should I remove/shorten the TODO comments here?

if (acc.has_initial_storage)
return true;

// The hot storage is ignored because it can contain elements from access list.
// TODO: Is this correct for destructed accounts?
assert(!acc.destructed && "untested");
return false;
}
} // namespace

size_t Host::get_code_size(const address& addr) const noexcept
{
const auto* const acc = m_state.find(addr);
return (acc != nullptr) ? extcode(acc->code).size() : 0;
const auto raw_code = m_state.get_code(addr);
return extcode(raw_code).size();
}

bytes32 Host::get_code_hash(const address& addr) const noexcept
{
const auto* const acc = m_state.find(addr);
if (acc == nullptr || acc->is_empty())
return {};
if (is_eof_container(acc->code))

// Load code and check if not EOF.
// TODO: Optimize the second account lookup here.
if (is_eof_container(m_state.get_code(addr)))
return EOF_CODE_HASH_SENTINEL;
// TODO: Cache code hash. It will be needed also to compute the MPT hash.
return keccak256(acc->code);

return acc->code_hash;
}

size_t Host::copy_code(const address& addr, size_t code_offset, uint8_t* buffer_data,
size_t buffer_size) const noexcept
{
const auto* const acc = m_state.find(addr);
const auto code = (acc != nullptr) ? extcode(acc->code) : bytes_view{};
const auto raw_code = m_state.get_code(addr);
const auto code = extcode(raw_code);
const auto code_slice = code.substr(std::min(code_offset, code.size()));
const auto num_bytes = std::min(buffer_size, code_slice.size());
std::copy_n(code_slice.begin(), num_bytes, buffer_data);
Expand Down Expand Up @@ -367,6 +372,7 @@
}
}

new_acc->code_hash = keccak256(code);
new_acc->code = code;

return evmc::Result{result.status_code, gas_left, result.gas_refund, msg.recipient};
Expand Down Expand Up @@ -409,9 +415,8 @@
if (is_precompile(m_rev, msg.code_address))
return call_precompile(m_rev, msg);

// In case msg.recipient == msg.code_address, this is the second lookup of the same address.
Copy link
Member

Choose a reason for hiding this comment

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

Comment is still true I think.

Copy link
Member Author

Choose a reason for hiding this comment

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

Kind of true, but now this is the State::get_code() API issue: how to combine local account lookup with code lookup.

const auto* const code_acc = m_state.find(msg.code_address);
const auto code = code_acc != nullptr ? bytes_view{code_acc->code} : bytes_view{};
// TODO: get_code() performs the account lookup. Add a way to get an account with code?
const auto code = m_state.get_code(msg.code_address);
return m_vm.execute(*this, m_rev, msg, code.data(), code.size());
}

Expand Down Expand Up @@ -503,7 +508,7 @@

evmc_access_status Host::access_storage(const address& addr, const bytes32& key) noexcept
{
auto& storage_slot = m_state.get(addr).storage[key];
auto& storage_slot = m_state.get_storage(addr, key);
m_state.journal_storage_change(addr, key, storage_slot);
return std::exchange(storage_slot.access_status, EVMC_ACCESS_WARM);
}
Expand Down
Loading