From cb7eb89341132f301680848d8faea6a5568dc326 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> Date: Tue, 29 Oct 2024 05:34:19 +1100 Subject: [PATCH] fix: `state.stateObject.empty()` with extra payload (#62) ## Why this should be merged Fixes invariant broken by introduction of `types.StateAccountExtra`. ## How this works `state.stateObject.empty()` now also requires that the underlying account's `Extra` field carries the zero value if a type is registered, or is itself nil. ## How this was tested New unit test. --------- Co-authored-by: Darioush Jalali --- core/state/state_object.go | 2 +- core/state/state_object.libevm_test.go | 79 ++++++++++++++++++++++++++ core/types/rlp_payload.libevm.go | 7 +++ 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 core/state/state_object.libevm_test.go diff --git a/core/state/state_object.go b/core/state/state_object.go index bf77f0d8f20c..07ebd20c7a68 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -93,7 +93,7 @@ type stateObject struct { // empty returns whether the account is considered empty. func (s *stateObject) empty() bool { - return s.data.Nonce == 0 && s.data.Balance.IsZero() && bytes.Equal(s.data.CodeHash, types.EmptyCodeHash.Bytes()) + return s.data.Nonce == 0 && s.data.Balance.IsZero() && bytes.Equal(s.data.CodeHash, types.EmptyCodeHash.Bytes()) && s.data.Extra.IsZero() } // newObject creates a state object. diff --git a/core/state/state_object.libevm_test.go b/core/state/state_object.libevm_test.go new file mode 100644 index 000000000000..c4e07ec4b573 --- /dev/null +++ b/core/state/state_object.libevm_test.go @@ -0,0 +1,79 @@ +// Copyright 2024 the libevm authors. +// +// The libevm additions to go-ethereum are free software: you can redistribute +// them and/or modify them under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The libevm additions are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see +// . + +package state + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/core/types" +) + +func TestStateObjectEmpty(t *testing.T) { + tests := []struct { + name string + registerAndSet func(*types.StateAccount) + wantEmpty bool + }{ + { + name: "no registered types.StateAccount extra payload", + registerAndSet: func(*types.StateAccount) {}, + wantEmpty: true, + }, + { + name: "erroneously non-nil types.StateAccountExtra when no registered payload", + registerAndSet: func(acc *types.StateAccount) { + acc.Extra = &types.StateAccountExtra{} + }, + wantEmpty: true, + }, + { + name: "explicit false bool", + registerAndSet: func(acc *types.StateAccount) { + types.RegisterExtras[bool]().SetOnStateAccount(acc, false) + }, + wantEmpty: true, + }, + { + name: "implicit false bool", + registerAndSet: func(*types.StateAccount) { + types.RegisterExtras[bool]() + }, + wantEmpty: true, + }, + { + name: "true bool", + registerAndSet: func(acc *types.StateAccount) { + types.RegisterExtras[bool]().SetOnStateAccount(acc, true) + }, + wantEmpty: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + types.TestOnlyClearRegisteredExtras() + t.Cleanup(types.TestOnlyClearRegisteredExtras) + + obj := newObject(nil, common.Address{}, nil) + tt.registerAndSet(&obj.data) + require.Equalf(t, tt.wantEmpty, obj.empty(), "%T.empty()", obj) + }) + } +} diff --git a/core/types/rlp_payload.libevm.go b/core/types/rlp_payload.libevm.go index 53f84705e455..46af867d8454 100644 --- a/core/types/rlp_payload.libevm.go +++ b/core/types/rlp_payload.libevm.go @@ -160,6 +160,13 @@ func (e *StateAccountExtra) Equal(f *StateAccountExtra) bool { return e.t.Equal(f.t) } +// IsZero reports whether e carries the the zero value for its type, as +// registered via [RegisterExtras]. It returns true if no type was registered or +// if `e == nil`. +func (e *StateAccountExtra) IsZero() bool { + return e == nil || e.t == nil || e.t.IsZero() +} + var _ interface { rlp.Encoder rlp.Decoder