Skip to content

Commit a938e00

Browse files
authored
Merge branch 'main' into aa755/rm_rb_assert
2 parents c50bb4e + 7e7d363 commit a938e00

19 files changed

Lines changed: 1488 additions & 13 deletions

category/execution/ethereum/state3/state.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,21 @@ bytes32_t State::get_code_hash(Address const &address)
239239
return NULL_HASH;
240240
}
241241

242+
bool State::is_destructed(Address const &address)
243+
{
244+
auto const &account_state = recent_account_state(address);
245+
return account_state.is_destructed();
246+
}
247+
248+
bool State::is_current_incarnation(Address const &address)
249+
{
250+
auto const &account = recent_account(address);
251+
if (MONAD_LIKELY(account.has_value())) {
252+
return account.value().incarnation == incarnation_;
253+
}
254+
return false;
255+
}
256+
242257
bytes32_t State::get_storage(Address const &address, bytes32_t const &key)
243258
{
244259
auto const it = current_.find(address);
@@ -439,7 +454,10 @@ State::selfdestruct(Address const &address, Address const &beneficiary)
439454
}
440455
}
441456

442-
return {account_state.destruct(), initial_balance};
457+
bool const inserted = account_state.destruct();
458+
// Recompute reserve-balance status after setting the destructed flag.
459+
rb_.on_debit(address);
460+
return {inserted, initial_balance};
443461
}
444462

445463
EXPLICIT_TRAITS_MEMBER(State::selfdestruct);

category/execution/ethereum/state3/state.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ class State
134134

135135
bytes32_t get_code_hash(Address const &);
136136

137+
bool is_destructed(Address const &);
138+
139+
bool is_current_incarnation(Address const &);
140+
137141
bytes32_t get_storage(Address const &, bytes32_t const &key);
138142

139143
bytes32_t get_transient_storage(Address const &, bytes32_t const &key);

category/execution/ethereum/test/test_monad_chain.cpp

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,90 @@ TYPED_TEST(MonadTraitsTest, reserve_checks_empty_code_hash)
703703
}
704704
}
705705

706+
TYPED_TEST(MonadTraitsTest, reserve_checks_prefunded_init_selfdestruct)
707+
{
708+
using traits = typename TestFixture::Trait;
709+
constexpr Address SENDER{1};
710+
constexpr Address NEW_CONTRACT{2};
711+
constexpr Address BENEFICIARY{3};
712+
constexpr uint64_t BASE_FEE_PER_GAS = 10;
713+
auto const to_wei = [](uint64_t mon) {
714+
return uint256_t{mon} * 1000000000000000000ULL;
715+
};
716+
717+
InMemoryMachine machine;
718+
mpt::Db db{machine};
719+
TrieDb tdb{db};
720+
vm::VM vm;
721+
BlockState bs{tdb, vm};
722+
723+
{
724+
State init_state{bs, Incarnation{0, 0}};
725+
init_state.add_to_balance(SENDER, to_wei(20));
726+
init_state.add_to_balance(NEW_CONTRACT, to_wei(3));
727+
MONAD_ASSERT(bs.can_merge(init_state));
728+
bs.merge(init_state);
729+
}
730+
731+
Transaction const tx{
732+
.max_fee_per_gas = BASE_FEE_PER_GAS,
733+
.gas_limit = 1,
734+
.type = TransactionType::legacy,
735+
.max_priority_fee_per_gas = 0,
736+
};
737+
uint256_t const gas_cost =
738+
uint256_t{BASE_FEE_PER_GAS} * uint256_t{tx.gas_limit};
739+
740+
ankerl::unordered_dense::segmented_set<Address> const
741+
empty_grandparent_senders_and_authorities;
742+
ankerl::unordered_dense::segmented_set<Address> const
743+
empty_parent_senders_and_authorities;
744+
std::vector<Address> const senders = {SENDER};
745+
std::vector<std::vector<std::optional<Address>>> const authorities = {{}};
746+
ankerl::unordered_dense::segmented_set<Address> senders_and_authorities;
747+
senders_and_authorities.insert(SENDER);
748+
ChainContext<traits> const context{
749+
.grandparent_senders_and_authorities =
750+
empty_grandparent_senders_and_authorities,
751+
.parent_senders_and_authorities = empty_parent_senders_and_authorities,
752+
.senders_and_authorities = senders_and_authorities,
753+
.senders = senders,
754+
.authorities = authorities};
755+
756+
State state{bs, Incarnation{1, 1}};
757+
init_reserve_balance_context<traits>(
758+
state, SENDER, tx, BASE_FEE_PER_GAS, 0, context);
759+
state.subtract_from_balance(SENDER, gas_cost);
760+
761+
// Model constructor-time SELFDESTRUCT at a pre-funded address:
762+
// create the account in current incarnation, then selfdestruct it before
763+
// any runtime code is set.
764+
state.create_contract(NEW_CONTRACT);
765+
auto const [inserted, initial_balance] =
766+
state.selfdestruct<traits>(NEW_CONTRACT, BENEFICIARY);
767+
EXPECT_TRUE(inserted);
768+
EXPECT_EQ(initial_balance, to_wei(3));
769+
EXPECT_EQ(state.get_balance(NEW_CONTRACT), 0);
770+
EXPECT_EQ(state.get_balance(BENEFICIARY), to_wei(3));
771+
772+
bool const should_revert = revert_transaction<traits>(
773+
SENDER, tx, BASE_FEE_PER_GAS, 0, state, context);
774+
bool const should_revert_cached = revert_transaction_cached<traits>(state);
775+
776+
if constexpr (traits::monad_rev() < MONAD_FOUR) {
777+
EXPECT_FALSE(should_revert);
778+
EXPECT_FALSE(should_revert_cached);
779+
}
780+
else if constexpr (traits::monad_rev() >= MONAD_NEXT) {
781+
EXPECT_FALSE(should_revert);
782+
EXPECT_FALSE(should_revert_cached);
783+
}
784+
else {
785+
EXPECT_TRUE(should_revert);
786+
EXPECT_TRUE(should_revert_cached);
787+
}
788+
}
789+
706790
TYPED_TEST(MonadTraitsTest, system_transaction_sender_is_authority)
707791
{
708792
InMemoryMachine machine;

category/execution/monad/reserve_balance.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ bool dipped_into_reserve(
6161
MONAD_ASSERT(i < ctx.authorities.size());
6262
MONAD_ASSERT(ctx.senders.size() == ctx.authorities.size());
6363

64+
static constexpr bool allow_init_selfdestruct_exemption =
65+
traits::monad_rev() >= MONAD_NEXT;
66+
6467
uint256_t const gas_fees =
6568
uint256_t{tx.gas_limit} * gas_price<traits>(tx, base_fee_per_gas);
6669
auto const &orig = state.original();
@@ -90,6 +93,11 @@ bool dipped_into_reserve(
9093
continue;
9194
}
9295
}
96+
else if (
97+
allow_init_selfdestruct_exemption && state.is_destructed(addr) &&
98+
state.is_current_incarnation(addr)) {
99+
continue;
100+
}
93101

94102
// Check if dipped into reserve
95103
std::optional<uint256_t> const violation_threshold =
@@ -208,6 +216,14 @@ void ReserveBalance::update_violation_status(Address const &address)
208216
}
209217

210218
auto &violation_threshold = violation_thresholds_[address];
219+
if (allow_init_selfdestruct_exemption_ && state_->is_destructed(address) &&
220+
state_->is_current_incarnation(address)) {
221+
// Contracts that selfdestruct during init never get a code hash.
222+
violation_threshold = uint256_t{0};
223+
failed_.erase(address);
224+
return;
225+
}
226+
211227
if (!violation_threshold.has_value()) {
212228
if (!subject_account(address)) {
213229
violation_threshold = uint256_t{0};
@@ -318,6 +334,7 @@ void ReserveBalance::init_from_tx(
318334
if constexpr (tracking_disabled) {
319335
tracking_enabled_ = false;
320336
use_recent_code_hash_ = false;
337+
allow_init_selfdestruct_exemption_ = false;
321338
sender_ = {};
322339
sender_gas_fees_ = 0;
323340
sender_can_dip_ = false;
@@ -330,6 +347,7 @@ void ReserveBalance::init_from_tx(
330347
MONAD_ASSERT(i < ctx.authorities.size());
331348
MONAD_ASSERT(ctx.senders.size() == ctx.authorities.size());
332349
use_recent_code_hash_ = traits::monad_rev() >= MONAD_EIGHT;
350+
allow_init_selfdestruct_exemption_ = traits::monad_rev() >= MONAD_NEXT;
333351
bytes32_t const sender_code_hash =
334352
use_recent_code_hash_
335353
? state_->get_code_hash(sender)

category/execution/monad/reserve_balance.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class ReserveBalance
4747
State *state_;
4848
bool tracking_enabled_{false};
4949
bool use_recent_code_hash_{false};
50+
bool allow_init_selfdestruct_exemption_{false};
5051
Address sender_{};
5152
uint256_t sender_gas_fees_{0};
5253
bool sender_can_dip_{false};

category/execution/monad/reserve_balance/reserve_balance_contract.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <category/execution/ethereum/core/contract/abi_signatures.hpp>
1919
#include <category/execution/ethereum/core/contract/events.hpp>
2020
#include <category/execution/ethereum/core/contract/storage_variable.hpp>
21+
#include <category/execution/ethereum/reserve_balance.hpp>
2122
#include <category/execution/monad/reserve_balance/reserve_balance_contract.hpp>
2223
#include <category/execution/monad/reserve_balance/reserve_balance_error.hpp>
2324
#include <category/vm/evm/explicit_traits.hpp>
@@ -27,10 +28,24 @@
2728

2829
MONAD_ANONYMOUS_NAMESPACE_BEGIN
2930

31+
////////////////////////
32+
// Function Selectors //
33+
////////////////////////
34+
35+
struct PrecompileSelector
36+
{
37+
static constexpr uint32_t DIPPED_INTO_RESERVE =
38+
abi_encode_selector("dippedIntoReserve()");
39+
};
40+
41+
static_assert(PrecompileSelector::DIPPED_INTO_RESERVE == 0x3a61584e);
42+
3043
//
3144
// Gas Costs
3245
//
3346

47+
constexpr uint64_t DIPPED_INTO_RESERVE_OP_COST = 100; // warm sload coast
48+
3449
constexpr uint64_t FALLBACK_COST = 40'000;
3550

3651
MONAD_ANONYMOUS_NAMESPACE_END
@@ -49,6 +64,19 @@ ReserveBalanceContract::ReserveBalanceContract(
4964
state_.add_to_balance(RESERVE_BALANCE_CA, 0);
5065
}
5166

67+
Result<void> function_not_payable(evmc_uint256be const &value)
68+
{
69+
bool const all_zero = std::all_of(
70+
value.bytes,
71+
value.bytes + sizeof(evmc_uint256be),
72+
[](uint8_t const byte) { return byte == 0; });
73+
74+
if (MONAD_UNLIKELY(!all_zero)) {
75+
return ReserveBalanceError::ValueNonZero;
76+
}
77+
return outcome::success();
78+
}
79+
5280
template <Traits traits>
5381
std::pair<ReserveBalanceContract::PrecompileFunc, uint64_t>
5482
ReserveBalanceContract::precompile_dispatch(byte_string_view &input)
@@ -62,13 +90,30 @@ ReserveBalanceContract::precompile_dispatch(byte_string_view &input)
6290
input.remove_prefix(4);
6391

6492
switch (signature) {
93+
case PrecompileSelector::DIPPED_INTO_RESERVE:
94+
return {
95+
&ReserveBalanceContract::precompile_dipped_into_reserve<traits>,
96+
DIPPED_INTO_RESERVE_OP_COST};
6597
default:
6698
return {&ReserveBalanceContract::precompile_fallback, FALLBACK_COST};
6799
}
68100
}
69101

70102
EXPLICIT_MONAD_TRAITS(ReserveBalanceContract::precompile_dispatch);
71103

104+
template <Traits traits>
105+
Result<byte_string> ReserveBalanceContract::precompile_dipped_into_reserve(
106+
byte_string_view, evmc_address const &, evmc_uint256be const &msg_value)
107+
{
108+
BOOST_OUTCOME_TRY(function_not_payable(msg_value));
109+
110+
return byte_string{
111+
abi_encode_bool(revert_transaction_cached<traits>(state_))};
112+
}
113+
114+
EXPLICIT_MONAD_TRAITS_MEMBER(
115+
ReserveBalanceContract::precompile_dipped_into_reserve);
116+
72117
Result<byte_string> ReserveBalanceContract::precompile_fallback(
73118
byte_string_view, evmc_address const &, evmc_uint256be const &)
74119
{

category/execution/monad/reserve_balance/reserve_balance_contract.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ class ReserveBalanceContract
4848
static std::pair<PrecompileFunc, uint64_t>
4949
precompile_dispatch(byte_string_view &);
5050

51+
template <Traits traits>
52+
Result<byte_string> precompile_dipped_into_reserve(
53+
byte_string_view, evmc_address const &, evmc_uint256be const &);
54+
5155
Result<byte_string> precompile_fallback(
5256
byte_string_view, evmc_address const &, evmc_uint256be const &);
5357
};

0 commit comments

Comments
 (0)