diff --git a/eth/core/precompile/container/stateful_test.go b/eth/core/precompile/container/stateful_test.go index 1cfd03410..2f65f1ff6 100644 --- a/eth/core/precompile/container/stateful_test.go +++ b/eth/core/precompile/container/stateful_test.go @@ -21,7 +21,6 @@ import ( "reflect" "github.com/berachain/stargazer/eth/core/precompile/container" - "github.com/berachain/stargazer/eth/core/state" coretypes "github.com/berachain/stargazer/eth/core/types" "github.com/berachain/stargazer/eth/core/vm" "github.com/berachain/stargazer/lib/common" @@ -48,7 +47,7 @@ var _ = Describe("Stateful Container", func() { ctx = testutil.NewContext() sc = container.NewStateful(&mockStateful{&mockBase{}}, mockIdsToMethods) empty = container.NewStateful(nil, nil) - sdb = &mockSdb{&state.StateDB{}, 0} + sdb = &mockSdb{nil, 0} }) Describe("Test Required Gas", func() { diff --git a/eth/core/state/interfaces.go b/eth/core/state/interfaces.go index 7342e3269..d0a4101cf 100644 --- a/eth/core/state/interfaces.go +++ b/eth/core/state/interfaces.go @@ -15,11 +15,44 @@ package state import ( + "math/big" + coretypes "github.com/berachain/stargazer/eth/core/types" "github.com/berachain/stargazer/lib/common" libtypes "github.com/berachain/stargazer/lib/types" ) +// `StatePlugin` is a plugin which tracks the accounts (balances, nonces, codes, states) in the +// native vm. This also handles removing suicided accounts. +type StatePlugin interface { //nolint:revive // vibes. + libtypes.Controllable[string] + + CreateAccount(common.Address) + // Exist reports whether the given account exists in state. + // Notably this should also return true for suicided accounts. + Exist(common.Address) bool + + GetBalance(common.Address) *big.Int + SubBalance(common.Address, *big.Int) + AddBalance(common.Address, *big.Int) + TransferBalance(common.Address, common.Address, *big.Int) + + GetNonce(common.Address) uint64 + SetNonce(common.Address, uint64) + + GetCodeHash(common.Address) common.Hash + GetCode(common.Address) []byte + SetCode(common.Address, []byte) + GetCodeSize(common.Address) int + + GetCommittedState(common.Address, common.Hash) common.Hash + GetState(common.Address, common.Hash) common.Hash + SetState(common.Address, common.Hash, common.Hash) + ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) error + + DeleteSuicides([]common.Address) +} + // `LogsPlugin` defines the interface for tracking logs created during a state transition. type LogsPlugin interface { // `LogsPlugin` implements `libtypes.Controllable`. diff --git a/eth/core/state/plugin/constants.go b/eth/core/state/plugin/constants.go index 70ef37223..366f16b8d 100644 --- a/eth/core/state/plugin/constants.go +++ b/eth/core/state/plugin/constants.go @@ -18,7 +18,7 @@ const ( // `initJournalCapacity` is the initial capacity of the plugins' journals. initJournalCapacity = 32 // `refundRegistryKey` is the registry key for the refund plugin. - refundRegistryKey = "refund" + refundRegistryKey = `refund` // `logsRegistryKey` is the registry key for the logs plugin. logsRegistryKey = `logs` // `maxUint` is used to check that a uint does not underflow. diff --git a/x/evm/plugins/state/store/cachekv/cache_value.go b/eth/core/state/plugin/mock/logs.go similarity index 52% rename from x/evm/plugins/state/store/cachekv/cache_value.go rename to eth/core/state/plugin/mock/logs.go index a3b2e6184..30867b86c 100644 --- a/x/evm/plugins/state/store/cachekv/cache_value.go +++ b/eth/core/state/plugin/mock/logs.go @@ -12,30 +12,37 @@ // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -package cachekv +package mock -import libtypes "github.com/berachain/stargazer/lib/types" +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) -// Compile-time assertion that `cacheValue` implements `types.Cloneable`. -var _ libtypes.Cloneable[*cacheValue] = (*cacheValue)(nil) +//go:generate moq -out ./logs.mock.go -pkg mock ../../ LogsPlugin -// `cacheValue` represents a cached value in the cachekv store. -// If dirty is true, it indicates the cached value is different from the underlying value. -type cacheValue struct { - value []byte - dirty bool -} - -// `newCacheValue` creates a new `cacheValue` object with the given `value` and `dirty` flag. -func newCacheValue(v []byte, d bool) *cacheValue { - return &cacheValue{ - value: v, - dirty: d, +// `NewEmptyLogsPlugin` returns an empty `LogsPluginMock`. +func NewEmptyLogsPlugin() *LogsPluginMock { + // make and configure a mocked state.LogsPlugin + return &LogsPluginMock{ + AddLogFunc: func(log *types.Log) { + panic("mock out the AddLog method") + }, + BuildLogsAndClearFunc: func(hash1 common.Hash, hash2 common.Hash, v1 uint, v2 uint) []*types.Log { + panic("mock out the BuildLogsAndClear method") + }, + FinalizeFunc: func() { + // no-op + }, + RegistryKeyFunc: func() string { + return "emptylogs" + }, + RevertToSnapshotFunc: func(n int) { + // no-op + }, + SnapshotFunc: func() int { + // no-op + return 0 + }, } } - -// `Clone` implements `types.Cloneable`. -func (cv *cacheValue) Clone() *cacheValue { - // Return a new cacheValue with the same value and dirty flag - return newCacheValue(append([]byte(nil), cv.value...), cv.dirty) -} diff --git a/eth/core/state/plugin/mock/logs.mock.go b/eth/core/state/plugin/mock/logs.mock.go new file mode 100644 index 000000000..f81b5b8a9 --- /dev/null +++ b/eth/core/state/plugin/mock/logs.mock.go @@ -0,0 +1,294 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package mock + +import ( + "github.com/berachain/stargazer/eth/core/state" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "sync" +) + +// Ensure, that LogsPluginMock does implement state.LogsPlugin. +// If this is not the case, regenerate this file with moq. +var _ state.LogsPlugin = &LogsPluginMock{} + +// LogsPluginMock is a mock implementation of state.LogsPlugin. +// +// func TestSomethingThatUsesLogsPlugin(t *testing.T) { +// +// // make and configure a mocked state.LogsPlugin +// mockedLogsPlugin := &LogsPluginMock{ +// AddLogFunc: func(log *types.Log) { +// panic("mock out the AddLog method") +// }, +// BuildLogsAndClearFunc: func(hash1 common.Hash, hash2 common.Hash, v1 uint, v2 uint) []*types.Log { +// panic("mock out the BuildLogsAndClear method") +// }, +// FinalizeFunc: func() { +// panic("mock out the Finalize method") +// }, +// RegistryKeyFunc: func() string { +// panic("mock out the RegistryKey method") +// }, +// RevertToSnapshotFunc: func(n int) { +// panic("mock out the RevertToSnapshot method") +// }, +// SnapshotFunc: func() int { +// panic("mock out the Snapshot method") +// }, +// } +// +// // use mockedLogsPlugin in code that requires state.LogsPlugin +// // and then make assertions. +// +// } +type LogsPluginMock struct { + // AddLogFunc mocks the AddLog method. + AddLogFunc func(log *types.Log) + + // BuildLogsAndClearFunc mocks the BuildLogsAndClear method. + BuildLogsAndClearFunc func(hash1 common.Hash, hash2 common.Hash, v1 uint, v2 uint) []*types.Log + + // FinalizeFunc mocks the Finalize method. + FinalizeFunc func() + + // RegistryKeyFunc mocks the RegistryKey method. + RegistryKeyFunc func() string + + // RevertToSnapshotFunc mocks the RevertToSnapshot method. + RevertToSnapshotFunc func(n int) + + // SnapshotFunc mocks the Snapshot method. + SnapshotFunc func() int + + // calls tracks calls to the methods. + calls struct { + // AddLog holds details about calls to the AddLog method. + AddLog []struct { + // Log is the log argument value. + Log *types.Log + } + // BuildLogsAndClear holds details about calls to the BuildLogsAndClear method. + BuildLogsAndClear []struct { + // Hash1 is the hash1 argument value. + Hash1 common.Hash + // Hash2 is the hash2 argument value. + Hash2 common.Hash + // V1 is the v1 argument value. + V1 uint + // V2 is the v2 argument value. + V2 uint + } + // Finalize holds details about calls to the Finalize method. + Finalize []struct { + } + // RegistryKey holds details about calls to the RegistryKey method. + RegistryKey []struct { + } + // RevertToSnapshot holds details about calls to the RevertToSnapshot method. + RevertToSnapshot []struct { + // N is the n argument value. + N int + } + // Snapshot holds details about calls to the Snapshot method. + Snapshot []struct { + } + } + lockAddLog sync.RWMutex + lockBuildLogsAndClear sync.RWMutex + lockFinalize sync.RWMutex + lockRegistryKey sync.RWMutex + lockRevertToSnapshot sync.RWMutex + lockSnapshot sync.RWMutex +} + +// AddLog calls AddLogFunc. +func (mock *LogsPluginMock) AddLog(log *types.Log) { + if mock.AddLogFunc == nil { + panic("LogsPluginMock.AddLogFunc: method is nil but LogsPlugin.AddLog was just called") + } + callInfo := struct { + Log *types.Log + }{ + Log: log, + } + mock.lockAddLog.Lock() + mock.calls.AddLog = append(mock.calls.AddLog, callInfo) + mock.lockAddLog.Unlock() + mock.AddLogFunc(log) +} + +// AddLogCalls gets all the calls that were made to AddLog. +// Check the length with: +// +// len(mockedLogsPlugin.AddLogCalls()) +func (mock *LogsPluginMock) AddLogCalls() []struct { + Log *types.Log +} { + var calls []struct { + Log *types.Log + } + mock.lockAddLog.RLock() + calls = mock.calls.AddLog + mock.lockAddLog.RUnlock() + return calls +} + +// BuildLogsAndClear calls BuildLogsAndClearFunc. +func (mock *LogsPluginMock) BuildLogsAndClear(hash1 common.Hash, hash2 common.Hash, v1 uint, v2 uint) []*types.Log { + if mock.BuildLogsAndClearFunc == nil { + panic("LogsPluginMock.BuildLogsAndClearFunc: method is nil but LogsPlugin.BuildLogsAndClear was just called") + } + callInfo := struct { + Hash1 common.Hash + Hash2 common.Hash + V1 uint + V2 uint + }{ + Hash1: hash1, + Hash2: hash2, + V1: v1, + V2: v2, + } + mock.lockBuildLogsAndClear.Lock() + mock.calls.BuildLogsAndClear = append(mock.calls.BuildLogsAndClear, callInfo) + mock.lockBuildLogsAndClear.Unlock() + return mock.BuildLogsAndClearFunc(hash1, hash2, v1, v2) +} + +// BuildLogsAndClearCalls gets all the calls that were made to BuildLogsAndClear. +// Check the length with: +// +// len(mockedLogsPlugin.BuildLogsAndClearCalls()) +func (mock *LogsPluginMock) BuildLogsAndClearCalls() []struct { + Hash1 common.Hash + Hash2 common.Hash + V1 uint + V2 uint +} { + var calls []struct { + Hash1 common.Hash + Hash2 common.Hash + V1 uint + V2 uint + } + mock.lockBuildLogsAndClear.RLock() + calls = mock.calls.BuildLogsAndClear + mock.lockBuildLogsAndClear.RUnlock() + return calls +} + +// Finalize calls FinalizeFunc. +func (mock *LogsPluginMock) Finalize() { + if mock.FinalizeFunc == nil { + panic("LogsPluginMock.FinalizeFunc: method is nil but LogsPlugin.Finalize was just called") + } + callInfo := struct { + }{} + mock.lockFinalize.Lock() + mock.calls.Finalize = append(mock.calls.Finalize, callInfo) + mock.lockFinalize.Unlock() + mock.FinalizeFunc() +} + +// FinalizeCalls gets all the calls that were made to Finalize. +// Check the length with: +// +// len(mockedLogsPlugin.FinalizeCalls()) +func (mock *LogsPluginMock) FinalizeCalls() []struct { +} { + var calls []struct { + } + mock.lockFinalize.RLock() + calls = mock.calls.Finalize + mock.lockFinalize.RUnlock() + return calls +} + +// RegistryKey calls RegistryKeyFunc. +func (mock *LogsPluginMock) RegistryKey() string { + if mock.RegistryKeyFunc == nil { + panic("LogsPluginMock.RegistryKeyFunc: method is nil but LogsPlugin.RegistryKey was just called") + } + callInfo := struct { + }{} + mock.lockRegistryKey.Lock() + mock.calls.RegistryKey = append(mock.calls.RegistryKey, callInfo) + mock.lockRegistryKey.Unlock() + return mock.RegistryKeyFunc() +} + +// RegistryKeyCalls gets all the calls that were made to RegistryKey. +// Check the length with: +// +// len(mockedLogsPlugin.RegistryKeyCalls()) +func (mock *LogsPluginMock) RegistryKeyCalls() []struct { +} { + var calls []struct { + } + mock.lockRegistryKey.RLock() + calls = mock.calls.RegistryKey + mock.lockRegistryKey.RUnlock() + return calls +} + +// RevertToSnapshot calls RevertToSnapshotFunc. +func (mock *LogsPluginMock) RevertToSnapshot(n int) { + if mock.RevertToSnapshotFunc == nil { + panic("LogsPluginMock.RevertToSnapshotFunc: method is nil but LogsPlugin.RevertToSnapshot was just called") + } + callInfo := struct { + N int + }{ + N: n, + } + mock.lockRevertToSnapshot.Lock() + mock.calls.RevertToSnapshot = append(mock.calls.RevertToSnapshot, callInfo) + mock.lockRevertToSnapshot.Unlock() + mock.RevertToSnapshotFunc(n) +} + +// RevertToSnapshotCalls gets all the calls that were made to RevertToSnapshot. +// Check the length with: +// +// len(mockedLogsPlugin.RevertToSnapshotCalls()) +func (mock *LogsPluginMock) RevertToSnapshotCalls() []struct { + N int +} { + var calls []struct { + N int + } + mock.lockRevertToSnapshot.RLock() + calls = mock.calls.RevertToSnapshot + mock.lockRevertToSnapshot.RUnlock() + return calls +} + +// Snapshot calls SnapshotFunc. +func (mock *LogsPluginMock) Snapshot() int { + if mock.SnapshotFunc == nil { + panic("LogsPluginMock.SnapshotFunc: method is nil but LogsPlugin.Snapshot was just called") + } + callInfo := struct { + }{} + mock.lockSnapshot.Lock() + mock.calls.Snapshot = append(mock.calls.Snapshot, callInfo) + mock.lockSnapshot.Unlock() + return mock.SnapshotFunc() +} + +// SnapshotCalls gets all the calls that were made to Snapshot. +// Check the length with: +// +// len(mockedLogsPlugin.SnapshotCalls()) +func (mock *LogsPluginMock) SnapshotCalls() []struct { +} { + var calls []struct { + } + mock.lockSnapshot.RLock() + calls = mock.calls.Snapshot + mock.lockSnapshot.RUnlock() + return calls +} diff --git a/x/evm/plugins/state/store/journal/mock/cache_entry.mock.go b/eth/core/state/plugin/mock/refund.go similarity index 57% rename from x/evm/plugins/state/store/journal/mock/cache_entry.mock.go rename to eth/core/state/plugin/mock/refund.go index de989219c..ab2f02492 100644 --- a/x/evm/plugins/state/store/journal/mock/cache_entry.mock.go +++ b/eth/core/state/plugin/mock/refund.go @@ -14,29 +14,32 @@ package mock -import "github.com/berachain/stargazer/x/evm/plugins/state/store/journal" +//go:generate moq -out ./refund.mock.go -pkg mock ../../ RefundPlugin -// `MockCacheEntry` is a basic CacheEntry which increases num by 1 on `Revert()`. -type CacheEntry struct { - num int -} - -// `NewCacheEntry` creates a new `MockCacheEntry`. -func NewCacheEntry() *CacheEntry { - return &CacheEntry{} -} - -// `Revert` implements `CacheEntry`. -func (m *CacheEntry) Revert() { - m.num++ -} - -// `Clone` implements `CacheEntry`. -func (m *CacheEntry) Clone() journal.CacheEntry { - return &CacheEntry{num: m.num} -} - -// `RevertCallCount` returns the number of times `Revert` has been called. -func (m *CacheEntry) RevertCallCount() int { - return m.num +// `NewEmptyRefundPlugin` returns an empty `RefundPluginMock`. +func NewEmptyRefundPlugin() *RefundPluginMock { + return &RefundPluginMock{ + AddRefundFunc: func(gas uint64) { + panic("mock out the AddRefund method") + }, + FinalizeFunc: func() { + // no-op + }, + GetRefundFunc: func() uint64 { + panic("mock out the GetRefund method") + }, + RegistryKeyFunc: func() string { + return "emptyrefund" + }, + RevertToSnapshotFunc: func(n int) { + // no-op + }, + SnapshotFunc: func() int { + // no-op + return 0 + }, + SubRefundFunc: func(gas uint64) { + panic("mock out the SubRefund method") + }, + } } diff --git a/eth/core/state/plugin/mock/refund.mock.go b/eth/core/state/plugin/mock/refund.mock.go new file mode 100644 index 000000000..7b07d9114 --- /dev/null +++ b/eth/core/state/plugin/mock/refund.mock.go @@ -0,0 +1,311 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package mock + +import ( + "github.com/berachain/stargazer/eth/core/state" + "sync" +) + +// Ensure, that RefundPluginMock does implement state.RefundPlugin. +// If this is not the case, regenerate this file with moq. +var _ state.RefundPlugin = &RefundPluginMock{} + +// RefundPluginMock is a mock implementation of state.RefundPlugin. +// +// func TestSomethingThatUsesRefundPlugin(t *testing.T) { +// +// // make and configure a mocked state.RefundPlugin +// mockedRefundPlugin := &RefundPluginMock{ +// AddRefundFunc: func(gas uint64) { +// panic("mock out the AddRefund method") +// }, +// FinalizeFunc: func() { +// panic("mock out the Finalize method") +// }, +// GetRefundFunc: func() uint64 { +// panic("mock out the GetRefund method") +// }, +// RegistryKeyFunc: func() string { +// panic("mock out the RegistryKey method") +// }, +// RevertToSnapshotFunc: func(n int) { +// panic("mock out the RevertToSnapshot method") +// }, +// SnapshotFunc: func() int { +// panic("mock out the Snapshot method") +// }, +// SubRefundFunc: func(gas uint64) { +// panic("mock out the SubRefund method") +// }, +// } +// +// // use mockedRefundPlugin in code that requires state.RefundPlugin +// // and then make assertions. +// +// } +type RefundPluginMock struct { + // AddRefundFunc mocks the AddRefund method. + AddRefundFunc func(gas uint64) + + // FinalizeFunc mocks the Finalize method. + FinalizeFunc func() + + // GetRefundFunc mocks the GetRefund method. + GetRefundFunc func() uint64 + + // RegistryKeyFunc mocks the RegistryKey method. + RegistryKeyFunc func() string + + // RevertToSnapshotFunc mocks the RevertToSnapshot method. + RevertToSnapshotFunc func(n int) + + // SnapshotFunc mocks the Snapshot method. + SnapshotFunc func() int + + // SubRefundFunc mocks the SubRefund method. + SubRefundFunc func(gas uint64) + + // calls tracks calls to the methods. + calls struct { + // AddRefund holds details about calls to the AddRefund method. + AddRefund []struct { + // Gas is the gas argument value. + Gas uint64 + } + // Finalize holds details about calls to the Finalize method. + Finalize []struct { + } + // GetRefund holds details about calls to the GetRefund method. + GetRefund []struct { + } + // RegistryKey holds details about calls to the RegistryKey method. + RegistryKey []struct { + } + // RevertToSnapshot holds details about calls to the RevertToSnapshot method. + RevertToSnapshot []struct { + // N is the n argument value. + N int + } + // Snapshot holds details about calls to the Snapshot method. + Snapshot []struct { + } + // SubRefund holds details about calls to the SubRefund method. + SubRefund []struct { + // Gas is the gas argument value. + Gas uint64 + } + } + lockAddRefund sync.RWMutex + lockFinalize sync.RWMutex + lockGetRefund sync.RWMutex + lockRegistryKey sync.RWMutex + lockRevertToSnapshot sync.RWMutex + lockSnapshot sync.RWMutex + lockSubRefund sync.RWMutex +} + +// AddRefund calls AddRefundFunc. +func (mock *RefundPluginMock) AddRefund(gas uint64) { + if mock.AddRefundFunc == nil { + panic("RefundPluginMock.AddRefundFunc: method is nil but RefundPlugin.AddRefund was just called") + } + callInfo := struct { + Gas uint64 + }{ + Gas: gas, + } + mock.lockAddRefund.Lock() + mock.calls.AddRefund = append(mock.calls.AddRefund, callInfo) + mock.lockAddRefund.Unlock() + mock.AddRefundFunc(gas) +} + +// AddRefundCalls gets all the calls that were made to AddRefund. +// Check the length with: +// +// len(mockedRefundPlugin.AddRefundCalls()) +func (mock *RefundPluginMock) AddRefundCalls() []struct { + Gas uint64 +} { + var calls []struct { + Gas uint64 + } + mock.lockAddRefund.RLock() + calls = mock.calls.AddRefund + mock.lockAddRefund.RUnlock() + return calls +} + +// Finalize calls FinalizeFunc. +func (mock *RefundPluginMock) Finalize() { + if mock.FinalizeFunc == nil { + panic("RefundPluginMock.FinalizeFunc: method is nil but RefundPlugin.Finalize was just called") + } + callInfo := struct { + }{} + mock.lockFinalize.Lock() + mock.calls.Finalize = append(mock.calls.Finalize, callInfo) + mock.lockFinalize.Unlock() + mock.FinalizeFunc() +} + +// FinalizeCalls gets all the calls that were made to Finalize. +// Check the length with: +// +// len(mockedRefundPlugin.FinalizeCalls()) +func (mock *RefundPluginMock) FinalizeCalls() []struct { +} { + var calls []struct { + } + mock.lockFinalize.RLock() + calls = mock.calls.Finalize + mock.lockFinalize.RUnlock() + return calls +} + +// GetRefund calls GetRefundFunc. +func (mock *RefundPluginMock) GetRefund() uint64 { + if mock.GetRefundFunc == nil { + panic("RefundPluginMock.GetRefundFunc: method is nil but RefundPlugin.GetRefund was just called") + } + callInfo := struct { + }{} + mock.lockGetRefund.Lock() + mock.calls.GetRefund = append(mock.calls.GetRefund, callInfo) + mock.lockGetRefund.Unlock() + return mock.GetRefundFunc() +} + +// GetRefundCalls gets all the calls that were made to GetRefund. +// Check the length with: +// +// len(mockedRefundPlugin.GetRefundCalls()) +func (mock *RefundPluginMock) GetRefundCalls() []struct { +} { + var calls []struct { + } + mock.lockGetRefund.RLock() + calls = mock.calls.GetRefund + mock.lockGetRefund.RUnlock() + return calls +} + +// RegistryKey calls RegistryKeyFunc. +func (mock *RefundPluginMock) RegistryKey() string { + if mock.RegistryKeyFunc == nil { + panic("RefundPluginMock.RegistryKeyFunc: method is nil but RefundPlugin.RegistryKey was just called") + } + callInfo := struct { + }{} + mock.lockRegistryKey.Lock() + mock.calls.RegistryKey = append(mock.calls.RegistryKey, callInfo) + mock.lockRegistryKey.Unlock() + return mock.RegistryKeyFunc() +} + +// RegistryKeyCalls gets all the calls that were made to RegistryKey. +// Check the length with: +// +// len(mockedRefundPlugin.RegistryKeyCalls()) +func (mock *RefundPluginMock) RegistryKeyCalls() []struct { +} { + var calls []struct { + } + mock.lockRegistryKey.RLock() + calls = mock.calls.RegistryKey + mock.lockRegistryKey.RUnlock() + return calls +} + +// RevertToSnapshot calls RevertToSnapshotFunc. +func (mock *RefundPluginMock) RevertToSnapshot(n int) { + if mock.RevertToSnapshotFunc == nil { + panic("RefundPluginMock.RevertToSnapshotFunc: method is nil but RefundPlugin.RevertToSnapshot was just called") + } + callInfo := struct { + N int + }{ + N: n, + } + mock.lockRevertToSnapshot.Lock() + mock.calls.RevertToSnapshot = append(mock.calls.RevertToSnapshot, callInfo) + mock.lockRevertToSnapshot.Unlock() + mock.RevertToSnapshotFunc(n) +} + +// RevertToSnapshotCalls gets all the calls that were made to RevertToSnapshot. +// Check the length with: +// +// len(mockedRefundPlugin.RevertToSnapshotCalls()) +func (mock *RefundPluginMock) RevertToSnapshotCalls() []struct { + N int +} { + var calls []struct { + N int + } + mock.lockRevertToSnapshot.RLock() + calls = mock.calls.RevertToSnapshot + mock.lockRevertToSnapshot.RUnlock() + return calls +} + +// Snapshot calls SnapshotFunc. +func (mock *RefundPluginMock) Snapshot() int { + if mock.SnapshotFunc == nil { + panic("RefundPluginMock.SnapshotFunc: method is nil but RefundPlugin.Snapshot was just called") + } + callInfo := struct { + }{} + mock.lockSnapshot.Lock() + mock.calls.Snapshot = append(mock.calls.Snapshot, callInfo) + mock.lockSnapshot.Unlock() + return mock.SnapshotFunc() +} + +// SnapshotCalls gets all the calls that were made to Snapshot. +// Check the length with: +// +// len(mockedRefundPlugin.SnapshotCalls()) +func (mock *RefundPluginMock) SnapshotCalls() []struct { +} { + var calls []struct { + } + mock.lockSnapshot.RLock() + calls = mock.calls.Snapshot + mock.lockSnapshot.RUnlock() + return calls +} + +// SubRefund calls SubRefundFunc. +func (mock *RefundPluginMock) SubRefund(gas uint64) { + if mock.SubRefundFunc == nil { + panic("RefundPluginMock.SubRefundFunc: method is nil but RefundPlugin.SubRefund was just called") + } + callInfo := struct { + Gas uint64 + }{ + Gas: gas, + } + mock.lockSubRefund.Lock() + mock.calls.SubRefund = append(mock.calls.SubRefund, callInfo) + mock.lockSubRefund.Unlock() + mock.SubRefundFunc(gas) +} + +// SubRefundCalls gets all the calls that were made to SubRefund. +// Check the length with: +// +// len(mockedRefundPlugin.SubRefundCalls()) +func (mock *RefundPluginMock) SubRefundCalls() []struct { + Gas uint64 +} { + var calls []struct { + Gas uint64 + } + mock.lockSubRefund.RLock() + calls = mock.calls.SubRefund + mock.lockSubRefund.RUnlock() + return calls +} diff --git a/eth/core/state/plugin/mock/state.go b/eth/core/state/plugin/mock/state.go new file mode 100644 index 000000000..a1fcb3f32 --- /dev/null +++ b/eth/core/state/plugin/mock/state.go @@ -0,0 +1,135 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package mock + +import ( + "math/big" + + "github.com/berachain/stargazer/lib/crypto" + "github.com/ethereum/go-ethereum/common" +) + +//go:generate moq -out ./state.mock.go -pkg mock ../../ StatePlugin + +var Accounts map[common.Address]*Account + +type Account struct { + Balance *big.Int + Code []byte + CodeHash common.Hash +} + +// `NewEmptyStatePlugin` returns an empty `StatePluginMock`. +func NewEmptyStatePlugin() *StatePluginMock { + Accounts = make(map[common.Address]*Account) + return &StatePluginMock{ + AddBalanceFunc: func(address common.Address, intMoqParam *big.Int) { + if _, ok := Accounts[address]; !ok { + panic("acct doesnt exist") + } + Accounts[address] = &Account{ + Balance: Accounts[address].Balance.Add(Accounts[address].Balance, intMoqParam), + Code: Accounts[address].Code, + CodeHash: Accounts[address].CodeHash, + } + }, + CreateAccountFunc: func(address common.Address) { + Accounts[address] = &Account{ + Balance: big.NewInt(0), + CodeHash: crypto.Keccak256Hash(nil), + } + }, + DeleteSuicidesFunc: func(addresss []common.Address) { + for _, addr := range addresss { + delete(Accounts, addr) + } + }, + ExistFunc: func(address common.Address) bool { + panic("mock out the Exist method") + }, + FinalizeFunc: func() { + // no-op + }, + ForEachStorageFunc: func(address common.Address, fn func(common.Hash, common.Hash) bool) error { + panic("mock out the ForEachStorage method") + }, + GetBalanceFunc: func(address common.Address) *big.Int { + if _, ok := Accounts[address]; !ok { + panic("acct doesnt exist") + } + return Accounts[address].Balance + }, + GetCodeFunc: func(address common.Address) []byte { + if _, ok := Accounts[address]; !ok { + panic("acct doesnt exist") + } + return Accounts[address].Code + }, + GetCodeHashFunc: func(address common.Address) common.Hash { + if _, ok := Accounts[address]; !ok { + panic("acct doesnt exist") + } + return Accounts[address].CodeHash + }, + GetCodeSizeFunc: func(address common.Address) int { + panic("mock out the GetCodeSize method") + }, + GetCommittedStateFunc: func(address common.Address, hash common.Hash) common.Hash { + panic("mock out the GetCommittedState method") + }, + GetNonceFunc: func(address common.Address) uint64 { + return 0 + }, + GetStateFunc: func(address common.Address, hash common.Hash) common.Hash { + panic("mock out the GetState method") + }, + RegistryKeyFunc: func() string { + return "mockstate" + }, + RevertToSnapshotFunc: func(n int) { + // no-op + }, + SetCodeFunc: func(address common.Address, bytes []byte) { + if _, ok := Accounts[address]; !ok { + panic("acct doesnt exist") + } + Accounts[address] = &Account{ + Balance: Accounts[address].Balance, + Code: bytes, + CodeHash: common.BytesToHash(bytes), + } + }, + SetNonceFunc: func(address common.Address, v uint64) { + panic("mock out the SetNonce method") + }, + SetStateFunc: func(address common.Address, hash1 common.Hash, hash2 common.Hash) { + panic("mock out the SetState method") + }, + SnapshotFunc: func() int { + return 0 + }, + SubBalanceFunc: func(address common.Address, intMoqParam *big.Int) { + if _, ok := Accounts[address]; !ok { + panic("acct doesnt exist") + } + Accounts[address] = &Account{ + Balance: Accounts[address].Balance.Sub(Accounts[address].Balance, intMoqParam), + } + }, + TransferBalanceFunc: func(address1 common.Address, address2 common.Address, intMoqParam *big.Int) { + panic("mock out the TransferBalance method") + }, + } +} diff --git a/eth/core/state/plugin/mock/state.mock.go b/eth/core/state/plugin/mock/state.mock.go new file mode 100644 index 000000000..f204fe5a9 --- /dev/null +++ b/eth/core/state/plugin/mock/state.mock.go @@ -0,0 +1,1002 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package mock + +import ( + "github.com/berachain/stargazer/eth/core/state" + "github.com/ethereum/go-ethereum/common" + "math/big" + "sync" +) + +// Ensure, that StatePluginMock does implement state.StatePlugin. +// If this is not the case, regenerate this file with moq. +var _ state.StatePlugin = &StatePluginMock{} + +// StatePluginMock is a mock implementation of state.StatePlugin. +// +// func TestSomethingThatUsesStatePlugin(t *testing.T) { +// +// // make and configure a mocked state.StatePlugin +// mockedStatePlugin := &StatePluginMock{ +// AddBalanceFunc: func(address common.Address, intMoqParam *big.Int) { +// panic("mock out the AddBalance method") +// }, +// CreateAccountFunc: func(address common.Address) { +// panic("mock out the CreateAccount method") +// }, +// DeleteSuicidesFunc: func(addresss []common.Address) { +// panic("mock out the DeleteSuicides method") +// }, +// ExistFunc: func(address common.Address) bool { +// panic("mock out the Exist method") +// }, +// FinalizeFunc: func() { +// panic("mock out the Finalize method") +// }, +// ForEachStorageFunc: func(address common.Address, fn func(common.Hash, common.Hash) bool) error { +// panic("mock out the ForEachStorage method") +// }, +// GetBalanceFunc: func(address common.Address) *big.Int { +// panic("mock out the GetBalance method") +// }, +// GetCodeFunc: func(address common.Address) []byte { +// panic("mock out the GetCode method") +// }, +// GetCodeHashFunc: func(address common.Address) common.Hash { +// panic("mock out the GetCodeHash method") +// }, +// GetCodeSizeFunc: func(address common.Address) int { +// panic("mock out the GetCodeSize method") +// }, +// GetCommittedStateFunc: func(address common.Address, hash common.Hash) common.Hash { +// panic("mock out the GetCommittedState method") +// }, +// GetNonceFunc: func(address common.Address) uint64 { +// panic("mock out the GetNonce method") +// }, +// GetStateFunc: func(address common.Address, hash common.Hash) common.Hash { +// panic("mock out the GetState method") +// }, +// RegistryKeyFunc: func() string { +// panic("mock out the RegistryKey method") +// }, +// RevertToSnapshotFunc: func(n int) { +// panic("mock out the RevertToSnapshot method") +// }, +// SetCodeFunc: func(address common.Address, bytes []byte) { +// panic("mock out the SetCode method") +// }, +// SetNonceFunc: func(address common.Address, v uint64) { +// panic("mock out the SetNonce method") +// }, +// SetStateFunc: func(address common.Address, hash1 common.Hash, hash2 common.Hash) { +// panic("mock out the SetState method") +// }, +// SnapshotFunc: func() int { +// panic("mock out the Snapshot method") +// }, +// SubBalanceFunc: func(address common.Address, intMoqParam *big.Int) { +// panic("mock out the SubBalance method") +// }, +// TransferBalanceFunc: func(address1 common.Address, address2 common.Address, intMoqParam *big.Int) { +// panic("mock out the TransferBalance method") +// }, +// } +// +// // use mockedStatePlugin in code that requires state.StatePlugin +// // and then make assertions. +// +// } +type StatePluginMock struct { + // AddBalanceFunc mocks the AddBalance method. + AddBalanceFunc func(address common.Address, intMoqParam *big.Int) + + // CreateAccountFunc mocks the CreateAccount method. + CreateAccountFunc func(address common.Address) + + // DeleteSuicidesFunc mocks the DeleteSuicides method. + DeleteSuicidesFunc func(addresss []common.Address) + + // ExistFunc mocks the Exist method. + ExistFunc func(address common.Address) bool + + // FinalizeFunc mocks the Finalize method. + FinalizeFunc func() + + // ForEachStorageFunc mocks the ForEachStorage method. + ForEachStorageFunc func(address common.Address, fn func(common.Hash, common.Hash) bool) error + + // GetBalanceFunc mocks the GetBalance method. + GetBalanceFunc func(address common.Address) *big.Int + + // GetCodeFunc mocks the GetCode method. + GetCodeFunc func(address common.Address) []byte + + // GetCodeHashFunc mocks the GetCodeHash method. + GetCodeHashFunc func(address common.Address) common.Hash + + // GetCodeSizeFunc mocks the GetCodeSize method. + GetCodeSizeFunc func(address common.Address) int + + // GetCommittedStateFunc mocks the GetCommittedState method. + GetCommittedStateFunc func(address common.Address, hash common.Hash) common.Hash + + // GetNonceFunc mocks the GetNonce method. + GetNonceFunc func(address common.Address) uint64 + + // GetStateFunc mocks the GetState method. + GetStateFunc func(address common.Address, hash common.Hash) common.Hash + + // RegistryKeyFunc mocks the RegistryKey method. + RegistryKeyFunc func() string + + // RevertToSnapshotFunc mocks the RevertToSnapshot method. + RevertToSnapshotFunc func(n int) + + // SetCodeFunc mocks the SetCode method. + SetCodeFunc func(address common.Address, bytes []byte) + + // SetNonceFunc mocks the SetNonce method. + SetNonceFunc func(address common.Address, v uint64) + + // SetStateFunc mocks the SetState method. + SetStateFunc func(address common.Address, hash1 common.Hash, hash2 common.Hash) + + // SnapshotFunc mocks the Snapshot method. + SnapshotFunc func() int + + // SubBalanceFunc mocks the SubBalance method. + SubBalanceFunc func(address common.Address, intMoqParam *big.Int) + + // TransferBalanceFunc mocks the TransferBalance method. + TransferBalanceFunc func(address1 common.Address, address2 common.Address, intMoqParam *big.Int) + + // calls tracks calls to the methods. + calls struct { + // AddBalance holds details about calls to the AddBalance method. + AddBalance []struct { + // Address is the address argument value. + Address common.Address + // IntMoqParam is the intMoqParam argument value. + IntMoqParam *big.Int + } + // CreateAccount holds details about calls to the CreateAccount method. + CreateAccount []struct { + // Address is the address argument value. + Address common.Address + } + // DeleteSuicides holds details about calls to the DeleteSuicides method. + DeleteSuicides []struct { + // Addresss is the addresss argument value. + Addresss []common.Address + } + // Exist holds details about calls to the Exist method. + Exist []struct { + // Address is the address argument value. + Address common.Address + } + // Finalize holds details about calls to the Finalize method. + Finalize []struct { + } + // ForEachStorage holds details about calls to the ForEachStorage method. + ForEachStorage []struct { + // Address is the address argument value. + Address common.Address + // Fn is the fn argument value. + Fn func(common.Hash, common.Hash) bool + } + // GetBalance holds details about calls to the GetBalance method. + GetBalance []struct { + // Address is the address argument value. + Address common.Address + } + // GetCode holds details about calls to the GetCode method. + GetCode []struct { + // Address is the address argument value. + Address common.Address + } + // GetCodeHash holds details about calls to the GetCodeHash method. + GetCodeHash []struct { + // Address is the address argument value. + Address common.Address + } + // GetCodeSize holds details about calls to the GetCodeSize method. + GetCodeSize []struct { + // Address is the address argument value. + Address common.Address + } + // GetCommittedState holds details about calls to the GetCommittedState method. + GetCommittedState []struct { + // Address is the address argument value. + Address common.Address + // Hash is the hash argument value. + Hash common.Hash + } + // GetNonce holds details about calls to the GetNonce method. + GetNonce []struct { + // Address is the address argument value. + Address common.Address + } + // GetState holds details about calls to the GetState method. + GetState []struct { + // Address is the address argument value. + Address common.Address + // Hash is the hash argument value. + Hash common.Hash + } + // RegistryKey holds details about calls to the RegistryKey method. + RegistryKey []struct { + } + // RevertToSnapshot holds details about calls to the RevertToSnapshot method. + RevertToSnapshot []struct { + // N is the n argument value. + N int + } + // SetCode holds details about calls to the SetCode method. + SetCode []struct { + // Address is the address argument value. + Address common.Address + // Bytes is the bytes argument value. + Bytes []byte + } + // SetNonce holds details about calls to the SetNonce method. + SetNonce []struct { + // Address is the address argument value. + Address common.Address + // V is the v argument value. + V uint64 + } + // SetState holds details about calls to the SetState method. + SetState []struct { + // Address is the address argument value. + Address common.Address + // Hash1 is the hash1 argument value. + Hash1 common.Hash + // Hash2 is the hash2 argument value. + Hash2 common.Hash + } + // Snapshot holds details about calls to the Snapshot method. + Snapshot []struct { + } + // SubBalance holds details about calls to the SubBalance method. + SubBalance []struct { + // Address is the address argument value. + Address common.Address + // IntMoqParam is the intMoqParam argument value. + IntMoqParam *big.Int + } + // TransferBalance holds details about calls to the TransferBalance method. + TransferBalance []struct { + // Address1 is the address1 argument value. + Address1 common.Address + // Address2 is the address2 argument value. + Address2 common.Address + // IntMoqParam is the intMoqParam argument value. + IntMoqParam *big.Int + } + } + lockAddBalance sync.RWMutex + lockCreateAccount sync.RWMutex + lockDeleteSuicides sync.RWMutex + lockExist sync.RWMutex + lockFinalize sync.RWMutex + lockForEachStorage sync.RWMutex + lockGetBalance sync.RWMutex + lockGetCode sync.RWMutex + lockGetCodeHash sync.RWMutex + lockGetCodeSize sync.RWMutex + lockGetCommittedState sync.RWMutex + lockGetNonce sync.RWMutex + lockGetState sync.RWMutex + lockRegistryKey sync.RWMutex + lockRevertToSnapshot sync.RWMutex + lockSetCode sync.RWMutex + lockSetNonce sync.RWMutex + lockSetState sync.RWMutex + lockSnapshot sync.RWMutex + lockSubBalance sync.RWMutex + lockTransferBalance sync.RWMutex +} + +// AddBalance calls AddBalanceFunc. +func (mock *StatePluginMock) AddBalance(address common.Address, intMoqParam *big.Int) { + if mock.AddBalanceFunc == nil { + panic("StatePluginMock.AddBalanceFunc: method is nil but StatePlugin.AddBalance was just called") + } + callInfo := struct { + Address common.Address + IntMoqParam *big.Int + }{ + Address: address, + IntMoqParam: intMoqParam, + } + mock.lockAddBalance.Lock() + mock.calls.AddBalance = append(mock.calls.AddBalance, callInfo) + mock.lockAddBalance.Unlock() + mock.AddBalanceFunc(address, intMoqParam) +} + +// AddBalanceCalls gets all the calls that were made to AddBalance. +// Check the length with: +// +// len(mockedStatePlugin.AddBalanceCalls()) +func (mock *StatePluginMock) AddBalanceCalls() []struct { + Address common.Address + IntMoqParam *big.Int +} { + var calls []struct { + Address common.Address + IntMoqParam *big.Int + } + mock.lockAddBalance.RLock() + calls = mock.calls.AddBalance + mock.lockAddBalance.RUnlock() + return calls +} + +// CreateAccount calls CreateAccountFunc. +func (mock *StatePluginMock) CreateAccount(address common.Address) { + if mock.CreateAccountFunc == nil { + panic("StatePluginMock.CreateAccountFunc: method is nil but StatePlugin.CreateAccount was just called") + } + callInfo := struct { + Address common.Address + }{ + Address: address, + } + mock.lockCreateAccount.Lock() + mock.calls.CreateAccount = append(mock.calls.CreateAccount, callInfo) + mock.lockCreateAccount.Unlock() + mock.CreateAccountFunc(address) +} + +// CreateAccountCalls gets all the calls that were made to CreateAccount. +// Check the length with: +// +// len(mockedStatePlugin.CreateAccountCalls()) +func (mock *StatePluginMock) CreateAccountCalls() []struct { + Address common.Address +} { + var calls []struct { + Address common.Address + } + mock.lockCreateAccount.RLock() + calls = mock.calls.CreateAccount + mock.lockCreateAccount.RUnlock() + return calls +} + +// DeleteSuicides calls DeleteSuicidesFunc. +func (mock *StatePluginMock) DeleteSuicides(addresss []common.Address) { + if mock.DeleteSuicidesFunc == nil { + panic("StatePluginMock.DeleteSuicidesFunc: method is nil but StatePlugin.DeleteSuicides was just called") + } + callInfo := struct { + Addresss []common.Address + }{ + Addresss: addresss, + } + mock.lockDeleteSuicides.Lock() + mock.calls.DeleteSuicides = append(mock.calls.DeleteSuicides, callInfo) + mock.lockDeleteSuicides.Unlock() + mock.DeleteSuicidesFunc(addresss) +} + +// DeleteSuicidesCalls gets all the calls that were made to DeleteSuicides. +// Check the length with: +// +// len(mockedStatePlugin.DeleteSuicidesCalls()) +func (mock *StatePluginMock) DeleteSuicidesCalls() []struct { + Addresss []common.Address +} { + var calls []struct { + Addresss []common.Address + } + mock.lockDeleteSuicides.RLock() + calls = mock.calls.DeleteSuicides + mock.lockDeleteSuicides.RUnlock() + return calls +} + +// Exist calls ExistFunc. +func (mock *StatePluginMock) Exist(address common.Address) bool { + if mock.ExistFunc == nil { + panic("StatePluginMock.ExistFunc: method is nil but StatePlugin.Exist was just called") + } + callInfo := struct { + Address common.Address + }{ + Address: address, + } + mock.lockExist.Lock() + mock.calls.Exist = append(mock.calls.Exist, callInfo) + mock.lockExist.Unlock() + return mock.ExistFunc(address) +} + +// ExistCalls gets all the calls that were made to Exist. +// Check the length with: +// +// len(mockedStatePlugin.ExistCalls()) +func (mock *StatePluginMock) ExistCalls() []struct { + Address common.Address +} { + var calls []struct { + Address common.Address + } + mock.lockExist.RLock() + calls = mock.calls.Exist + mock.lockExist.RUnlock() + return calls +} + +// Finalize calls FinalizeFunc. +func (mock *StatePluginMock) Finalize() { + if mock.FinalizeFunc == nil { + panic("StatePluginMock.FinalizeFunc: method is nil but StatePlugin.Finalize was just called") + } + callInfo := struct { + }{} + mock.lockFinalize.Lock() + mock.calls.Finalize = append(mock.calls.Finalize, callInfo) + mock.lockFinalize.Unlock() + mock.FinalizeFunc() +} + +// FinalizeCalls gets all the calls that were made to Finalize. +// Check the length with: +// +// len(mockedStatePlugin.FinalizeCalls()) +func (mock *StatePluginMock) FinalizeCalls() []struct { +} { + var calls []struct { + } + mock.lockFinalize.RLock() + calls = mock.calls.Finalize + mock.lockFinalize.RUnlock() + return calls +} + +// ForEachStorage calls ForEachStorageFunc. +func (mock *StatePluginMock) ForEachStorage(address common.Address, fn func(common.Hash, common.Hash) bool) error { + if mock.ForEachStorageFunc == nil { + panic("StatePluginMock.ForEachStorageFunc: method is nil but StatePlugin.ForEachStorage was just called") + } + callInfo := struct { + Address common.Address + Fn func(common.Hash, common.Hash) bool + }{ + Address: address, + Fn: fn, + } + mock.lockForEachStorage.Lock() + mock.calls.ForEachStorage = append(mock.calls.ForEachStorage, callInfo) + mock.lockForEachStorage.Unlock() + return mock.ForEachStorageFunc(address, fn) +} + +// ForEachStorageCalls gets all the calls that were made to ForEachStorage. +// Check the length with: +// +// len(mockedStatePlugin.ForEachStorageCalls()) +func (mock *StatePluginMock) ForEachStorageCalls() []struct { + Address common.Address + Fn func(common.Hash, common.Hash) bool +} { + var calls []struct { + Address common.Address + Fn func(common.Hash, common.Hash) bool + } + mock.lockForEachStorage.RLock() + calls = mock.calls.ForEachStorage + mock.lockForEachStorage.RUnlock() + return calls +} + +// GetBalance calls GetBalanceFunc. +func (mock *StatePluginMock) GetBalance(address common.Address) *big.Int { + if mock.GetBalanceFunc == nil { + panic("StatePluginMock.GetBalanceFunc: method is nil but StatePlugin.GetBalance was just called") + } + callInfo := struct { + Address common.Address + }{ + Address: address, + } + mock.lockGetBalance.Lock() + mock.calls.GetBalance = append(mock.calls.GetBalance, callInfo) + mock.lockGetBalance.Unlock() + return mock.GetBalanceFunc(address) +} + +// GetBalanceCalls gets all the calls that were made to GetBalance. +// Check the length with: +// +// len(mockedStatePlugin.GetBalanceCalls()) +func (mock *StatePluginMock) GetBalanceCalls() []struct { + Address common.Address +} { + var calls []struct { + Address common.Address + } + mock.lockGetBalance.RLock() + calls = mock.calls.GetBalance + mock.lockGetBalance.RUnlock() + return calls +} + +// GetCode calls GetCodeFunc. +func (mock *StatePluginMock) GetCode(address common.Address) []byte { + if mock.GetCodeFunc == nil { + panic("StatePluginMock.GetCodeFunc: method is nil but StatePlugin.GetCode was just called") + } + callInfo := struct { + Address common.Address + }{ + Address: address, + } + mock.lockGetCode.Lock() + mock.calls.GetCode = append(mock.calls.GetCode, callInfo) + mock.lockGetCode.Unlock() + return mock.GetCodeFunc(address) +} + +// GetCodeCalls gets all the calls that were made to GetCode. +// Check the length with: +// +// len(mockedStatePlugin.GetCodeCalls()) +func (mock *StatePluginMock) GetCodeCalls() []struct { + Address common.Address +} { + var calls []struct { + Address common.Address + } + mock.lockGetCode.RLock() + calls = mock.calls.GetCode + mock.lockGetCode.RUnlock() + return calls +} + +// GetCodeHash calls GetCodeHashFunc. +func (mock *StatePluginMock) GetCodeHash(address common.Address) common.Hash { + if mock.GetCodeHashFunc == nil { + panic("StatePluginMock.GetCodeHashFunc: method is nil but StatePlugin.GetCodeHash was just called") + } + callInfo := struct { + Address common.Address + }{ + Address: address, + } + mock.lockGetCodeHash.Lock() + mock.calls.GetCodeHash = append(mock.calls.GetCodeHash, callInfo) + mock.lockGetCodeHash.Unlock() + return mock.GetCodeHashFunc(address) +} + +// GetCodeHashCalls gets all the calls that were made to GetCodeHash. +// Check the length with: +// +// len(mockedStatePlugin.GetCodeHashCalls()) +func (mock *StatePluginMock) GetCodeHashCalls() []struct { + Address common.Address +} { + var calls []struct { + Address common.Address + } + mock.lockGetCodeHash.RLock() + calls = mock.calls.GetCodeHash + mock.lockGetCodeHash.RUnlock() + return calls +} + +// GetCodeSize calls GetCodeSizeFunc. +func (mock *StatePluginMock) GetCodeSize(address common.Address) int { + if mock.GetCodeSizeFunc == nil { + panic("StatePluginMock.GetCodeSizeFunc: method is nil but StatePlugin.GetCodeSize was just called") + } + callInfo := struct { + Address common.Address + }{ + Address: address, + } + mock.lockGetCodeSize.Lock() + mock.calls.GetCodeSize = append(mock.calls.GetCodeSize, callInfo) + mock.lockGetCodeSize.Unlock() + return mock.GetCodeSizeFunc(address) +} + +// GetCodeSizeCalls gets all the calls that were made to GetCodeSize. +// Check the length with: +// +// len(mockedStatePlugin.GetCodeSizeCalls()) +func (mock *StatePluginMock) GetCodeSizeCalls() []struct { + Address common.Address +} { + var calls []struct { + Address common.Address + } + mock.lockGetCodeSize.RLock() + calls = mock.calls.GetCodeSize + mock.lockGetCodeSize.RUnlock() + return calls +} + +// GetCommittedState calls GetCommittedStateFunc. +func (mock *StatePluginMock) GetCommittedState(address common.Address, hash common.Hash) common.Hash { + if mock.GetCommittedStateFunc == nil { + panic("StatePluginMock.GetCommittedStateFunc: method is nil but StatePlugin.GetCommittedState was just called") + } + callInfo := struct { + Address common.Address + Hash common.Hash + }{ + Address: address, + Hash: hash, + } + mock.lockGetCommittedState.Lock() + mock.calls.GetCommittedState = append(mock.calls.GetCommittedState, callInfo) + mock.lockGetCommittedState.Unlock() + return mock.GetCommittedStateFunc(address, hash) +} + +// GetCommittedStateCalls gets all the calls that were made to GetCommittedState. +// Check the length with: +// +// len(mockedStatePlugin.GetCommittedStateCalls()) +func (mock *StatePluginMock) GetCommittedStateCalls() []struct { + Address common.Address + Hash common.Hash +} { + var calls []struct { + Address common.Address + Hash common.Hash + } + mock.lockGetCommittedState.RLock() + calls = mock.calls.GetCommittedState + mock.lockGetCommittedState.RUnlock() + return calls +} + +// GetNonce calls GetNonceFunc. +func (mock *StatePluginMock) GetNonce(address common.Address) uint64 { + if mock.GetNonceFunc == nil { + panic("StatePluginMock.GetNonceFunc: method is nil but StatePlugin.GetNonce was just called") + } + callInfo := struct { + Address common.Address + }{ + Address: address, + } + mock.lockGetNonce.Lock() + mock.calls.GetNonce = append(mock.calls.GetNonce, callInfo) + mock.lockGetNonce.Unlock() + return mock.GetNonceFunc(address) +} + +// GetNonceCalls gets all the calls that were made to GetNonce. +// Check the length with: +// +// len(mockedStatePlugin.GetNonceCalls()) +func (mock *StatePluginMock) GetNonceCalls() []struct { + Address common.Address +} { + var calls []struct { + Address common.Address + } + mock.lockGetNonce.RLock() + calls = mock.calls.GetNonce + mock.lockGetNonce.RUnlock() + return calls +} + +// GetState calls GetStateFunc. +func (mock *StatePluginMock) GetState(address common.Address, hash common.Hash) common.Hash { + if mock.GetStateFunc == nil { + panic("StatePluginMock.GetStateFunc: method is nil but StatePlugin.GetState was just called") + } + callInfo := struct { + Address common.Address + Hash common.Hash + }{ + Address: address, + Hash: hash, + } + mock.lockGetState.Lock() + mock.calls.GetState = append(mock.calls.GetState, callInfo) + mock.lockGetState.Unlock() + return mock.GetStateFunc(address, hash) +} + +// GetStateCalls gets all the calls that were made to GetState. +// Check the length with: +// +// len(mockedStatePlugin.GetStateCalls()) +func (mock *StatePluginMock) GetStateCalls() []struct { + Address common.Address + Hash common.Hash +} { + var calls []struct { + Address common.Address + Hash common.Hash + } + mock.lockGetState.RLock() + calls = mock.calls.GetState + mock.lockGetState.RUnlock() + return calls +} + +// RegistryKey calls RegistryKeyFunc. +func (mock *StatePluginMock) RegistryKey() string { + if mock.RegistryKeyFunc == nil { + panic("StatePluginMock.RegistryKeyFunc: method is nil but StatePlugin.RegistryKey was just called") + } + callInfo := struct { + }{} + mock.lockRegistryKey.Lock() + mock.calls.RegistryKey = append(mock.calls.RegistryKey, callInfo) + mock.lockRegistryKey.Unlock() + return mock.RegistryKeyFunc() +} + +// RegistryKeyCalls gets all the calls that were made to RegistryKey. +// Check the length with: +// +// len(mockedStatePlugin.RegistryKeyCalls()) +func (mock *StatePluginMock) RegistryKeyCalls() []struct { +} { + var calls []struct { + } + mock.lockRegistryKey.RLock() + calls = mock.calls.RegistryKey + mock.lockRegistryKey.RUnlock() + return calls +} + +// RevertToSnapshot calls RevertToSnapshotFunc. +func (mock *StatePluginMock) RevertToSnapshot(n int) { + if mock.RevertToSnapshotFunc == nil { + panic("StatePluginMock.RevertToSnapshotFunc: method is nil but StatePlugin.RevertToSnapshot was just called") + } + callInfo := struct { + N int + }{ + N: n, + } + mock.lockRevertToSnapshot.Lock() + mock.calls.RevertToSnapshot = append(mock.calls.RevertToSnapshot, callInfo) + mock.lockRevertToSnapshot.Unlock() + mock.RevertToSnapshotFunc(n) +} + +// RevertToSnapshotCalls gets all the calls that were made to RevertToSnapshot. +// Check the length with: +// +// len(mockedStatePlugin.RevertToSnapshotCalls()) +func (mock *StatePluginMock) RevertToSnapshotCalls() []struct { + N int +} { + var calls []struct { + N int + } + mock.lockRevertToSnapshot.RLock() + calls = mock.calls.RevertToSnapshot + mock.lockRevertToSnapshot.RUnlock() + return calls +} + +// SetCode calls SetCodeFunc. +func (mock *StatePluginMock) SetCode(address common.Address, bytes []byte) { + if mock.SetCodeFunc == nil { + panic("StatePluginMock.SetCodeFunc: method is nil but StatePlugin.SetCode was just called") + } + callInfo := struct { + Address common.Address + Bytes []byte + }{ + Address: address, + Bytes: bytes, + } + mock.lockSetCode.Lock() + mock.calls.SetCode = append(mock.calls.SetCode, callInfo) + mock.lockSetCode.Unlock() + mock.SetCodeFunc(address, bytes) +} + +// SetCodeCalls gets all the calls that were made to SetCode. +// Check the length with: +// +// len(mockedStatePlugin.SetCodeCalls()) +func (mock *StatePluginMock) SetCodeCalls() []struct { + Address common.Address + Bytes []byte +} { + var calls []struct { + Address common.Address + Bytes []byte + } + mock.lockSetCode.RLock() + calls = mock.calls.SetCode + mock.lockSetCode.RUnlock() + return calls +} + +// SetNonce calls SetNonceFunc. +func (mock *StatePluginMock) SetNonce(address common.Address, v uint64) { + if mock.SetNonceFunc == nil { + panic("StatePluginMock.SetNonceFunc: method is nil but StatePlugin.SetNonce was just called") + } + callInfo := struct { + Address common.Address + V uint64 + }{ + Address: address, + V: v, + } + mock.lockSetNonce.Lock() + mock.calls.SetNonce = append(mock.calls.SetNonce, callInfo) + mock.lockSetNonce.Unlock() + mock.SetNonceFunc(address, v) +} + +// SetNonceCalls gets all the calls that were made to SetNonce. +// Check the length with: +// +// len(mockedStatePlugin.SetNonceCalls()) +func (mock *StatePluginMock) SetNonceCalls() []struct { + Address common.Address + V uint64 +} { + var calls []struct { + Address common.Address + V uint64 + } + mock.lockSetNonce.RLock() + calls = mock.calls.SetNonce + mock.lockSetNonce.RUnlock() + return calls +} + +// SetState calls SetStateFunc. +func (mock *StatePluginMock) SetState(address common.Address, hash1 common.Hash, hash2 common.Hash) { + if mock.SetStateFunc == nil { + panic("StatePluginMock.SetStateFunc: method is nil but StatePlugin.SetState was just called") + } + callInfo := struct { + Address common.Address + Hash1 common.Hash + Hash2 common.Hash + }{ + Address: address, + Hash1: hash1, + Hash2: hash2, + } + mock.lockSetState.Lock() + mock.calls.SetState = append(mock.calls.SetState, callInfo) + mock.lockSetState.Unlock() + mock.SetStateFunc(address, hash1, hash2) +} + +// SetStateCalls gets all the calls that were made to SetState. +// Check the length with: +// +// len(mockedStatePlugin.SetStateCalls()) +func (mock *StatePluginMock) SetStateCalls() []struct { + Address common.Address + Hash1 common.Hash + Hash2 common.Hash +} { + var calls []struct { + Address common.Address + Hash1 common.Hash + Hash2 common.Hash + } + mock.lockSetState.RLock() + calls = mock.calls.SetState + mock.lockSetState.RUnlock() + return calls +} + +// Snapshot calls SnapshotFunc. +func (mock *StatePluginMock) Snapshot() int { + if mock.SnapshotFunc == nil { + panic("StatePluginMock.SnapshotFunc: method is nil but StatePlugin.Snapshot was just called") + } + callInfo := struct { + }{} + mock.lockSnapshot.Lock() + mock.calls.Snapshot = append(mock.calls.Snapshot, callInfo) + mock.lockSnapshot.Unlock() + return mock.SnapshotFunc() +} + +// SnapshotCalls gets all the calls that were made to Snapshot. +// Check the length with: +// +// len(mockedStatePlugin.SnapshotCalls()) +func (mock *StatePluginMock) SnapshotCalls() []struct { +} { + var calls []struct { + } + mock.lockSnapshot.RLock() + calls = mock.calls.Snapshot + mock.lockSnapshot.RUnlock() + return calls +} + +// SubBalance calls SubBalanceFunc. +func (mock *StatePluginMock) SubBalance(address common.Address, intMoqParam *big.Int) { + if mock.SubBalanceFunc == nil { + panic("StatePluginMock.SubBalanceFunc: method is nil but StatePlugin.SubBalance was just called") + } + callInfo := struct { + Address common.Address + IntMoqParam *big.Int + }{ + Address: address, + IntMoqParam: intMoqParam, + } + mock.lockSubBalance.Lock() + mock.calls.SubBalance = append(mock.calls.SubBalance, callInfo) + mock.lockSubBalance.Unlock() + mock.SubBalanceFunc(address, intMoqParam) +} + +// SubBalanceCalls gets all the calls that were made to SubBalance. +// Check the length with: +// +// len(mockedStatePlugin.SubBalanceCalls()) +func (mock *StatePluginMock) SubBalanceCalls() []struct { + Address common.Address + IntMoqParam *big.Int +} { + var calls []struct { + Address common.Address + IntMoqParam *big.Int + } + mock.lockSubBalance.RLock() + calls = mock.calls.SubBalance + mock.lockSubBalance.RUnlock() + return calls +} + +// TransferBalance calls TransferBalanceFunc. +func (mock *StatePluginMock) TransferBalance(address1 common.Address, address2 common.Address, intMoqParam *big.Int) { + if mock.TransferBalanceFunc == nil { + panic("StatePluginMock.TransferBalanceFunc: method is nil but StatePlugin.TransferBalance was just called") + } + callInfo := struct { + Address1 common.Address + Address2 common.Address + IntMoqParam *big.Int + }{ + Address1: address1, + Address2: address2, + IntMoqParam: intMoqParam, + } + mock.lockTransferBalance.Lock() + mock.calls.TransferBalance = append(mock.calls.TransferBalance, callInfo) + mock.lockTransferBalance.Unlock() + mock.TransferBalanceFunc(address1, address2, intMoqParam) +} + +// TransferBalanceCalls gets all the calls that were made to TransferBalance. +// Check the length with: +// +// len(mockedStatePlugin.TransferBalanceCalls()) +func (mock *StatePluginMock) TransferBalanceCalls() []struct { + Address1 common.Address + Address2 common.Address + IntMoqParam *big.Int +} { + var calls []struct { + Address1 common.Address + Address2 common.Address + IntMoqParam *big.Int + } + mock.lockTransferBalance.RLock() + calls = mock.calls.TransferBalance + mock.lockTransferBalance.RUnlock() + return calls +} diff --git a/eth/core/state/plugin/plugin_test.go b/eth/core/state/plugin/plugin_test.go index 37dc75a83..03d4b9124 100644 --- a/eth/core/state/plugin/plugin_test.go +++ b/eth/core/state/plugin/plugin_test.go @@ -12,8 +12,6 @@ // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - package plugin import ( diff --git a/x/evm/plugins/state/state_test.go b/eth/core/state/state_test.go similarity index 96% rename from x/evm/plugins/state/state_test.go rename to eth/core/state/state_test.go index d7a5cf30d..3567c69cd 100644 --- a/x/evm/plugins/state/state_test.go +++ b/eth/core/state/state_test.go @@ -23,5 +23,5 @@ import ( func TestState(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "x/evm/plugins/state") + RunSpecs(t, "eth/core/state") } diff --git a/eth/core/state/statedb.go b/eth/core/state/statedb.go index 8fc621984..f5d517a4e 100644 --- a/eth/core/state/statedb.go +++ b/eth/core/state/statedb.go @@ -15,9 +15,158 @@ package state import ( + coretypes "github.com/berachain/stargazer/eth/core/types" "github.com/berachain/stargazer/eth/core/vm" + "github.com/berachain/stargazer/lib/common" + "github.com/berachain/stargazer/lib/crypto" + "github.com/berachain/stargazer/lib/snapshot" + libtypes "github.com/berachain/stargazer/lib/types" ) -type StateDB struct { //nolint:revive // StateDB is a struct that holds the state of the blockchain. - vm.StargazerStateDB +var ( + // `emptyCodeHash` is the Keccak256 Hash of empty code + // 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470. + emptyCodeHash = crypto.Keccak256Hash(nil) +) + +// `stateDB` is a struct that holds the plugins and controller to manage Ethereum state. +type stateDB struct { + // References to the plugins in the controller. + StatePlugin + RefundPlugin + LogsPlugin + + // The controller is used to manage the plugins + ctrl libtypes.Controller[string, libtypes.Controllable[string]] + + // Dirty tracking of suicided accounts, we have to keep track of these manually, in order + // for the code and state to still be accessible even after the account has been deleted. + // We chose to keep track of them in a separate slice, rather than a map, because the + // number of accounts that will be suicided in a single transaction is expected to be + // very low. + suicides []common.Address +} + +// `NewStateDB` returns a `StargazerStateDB` with the given plugins. +func NewStateDB(sp StatePlugin, lp LogsPlugin, rp RefundPlugin) (vm.StargazerStateDB, error) { + // Build the controller and register the plugins + ctrl := snapshot.NewController[string, libtypes.Controllable[string]]() + _ = ctrl.Register(lp) + _ = ctrl.Register(rp) + _ = ctrl.Register(sp) + + // Create the `stateDB` and populate the developer provided plugins. + return &stateDB{ + StatePlugin: sp, + LogsPlugin: lp, + RefundPlugin: rp, + ctrl: ctrl, + suicides: make([]common.Address, 1), // very rare to suicide, so we alloc 1 slot. + }, nil +} + +// ============================================================================= +// Suicide +// ============================================================================= + +// Suicide implements the StargazerStateDB interface by marking the given address as suicided. +// This clears the account balance, but the code and state of the address remains available +// until after Commit is called. +func (sdb *stateDB) Suicide(addr common.Address) bool { + // only smart contracts can commit suicide + ch := sdb.GetCodeHash(addr) + if (ch == common.Hash{}) || ch == emptyCodeHash { + return false + } + + // Reduce it's balance to 0. + sdb.SubBalance(addr, sdb.GetBalance(addr)) + + // Mark the underlying account for deletion in `Commit()`. + sdb.suicides = append(sdb.suicides, addr) + return true +} + +// `HasSuicided` implements the `StargazerStateDB` interface by returning if the contract was suicided +// in current transaction. +func (sdb *stateDB) HasSuicided(addr common.Address) bool { + for _, suicide := range sdb.suicides { + if addr == suicide { + return true + } + } + return false +} + +// `Empty` implements the `StargazerStateDB` interface by returning whether the state object +// is either non-existent or empty according to the EIP161 epecification +// (balance = nonce = code = 0) +// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md +func (sdb *stateDB) Empty(addr common.Address) bool { + ch := sdb.GetCodeHash(addr) + return sdb.GetNonce(addr) == 0 && + (ch == emptyCodeHash || ch == common.Hash{}) && + sdb.GetBalance(addr).Sign() == 0 +} + +// ============================================================================= +// Snapshot +// ============================================================================= + +// `Snapshot` implements `stateDB`. +func (sdb *stateDB) Snapshot() int { + return sdb.ctrl.Snapshot() } + +// `RevertToSnapshot` implements `stateDB`. +func (sdb *stateDB) RevertToSnapshot(id int) { + sdb.ctrl.RevertToSnapshot(id) +} + +// ============================================================================= +// Finalize +// ============================================================================= + +// `Finalize` deletes the suicided accounts, clears the suicides list, and finalizes all plugins. +func (sdb *stateDB) Finalize() { + sdb.DeleteSuicides(sdb.suicides) + sdb.suicides = make([]common.Address, 1) + sdb.ctrl.Finalize() +} + +// ============================================================================= +// AccessList +// ============================================================================= + +func (sdb *stateDB) PrepareAccessList( + sender common.Address, + dst *common.Address, + precompiles []common.Address, + list coretypes.AccessList, +) { + panic("not supported by Stargazer") +} + +func (sdb *stateDB) AddAddressToAccessList(addr common.Address) { + panic("not supported by Stargazer") +} + +func (sdb *stateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) { + panic("not supported by Stargazer") +} + +func (sdb *stateDB) AddressInAccessList(addr common.Address) bool { + return false +} + +func (sdb *stateDB) SlotInAccessList(addr common.Address, slot common.Hash) (bool, bool) { + return false, false +} + +// ============================================================================= +// PreImage +// ============================================================================= + +// AddPreimage implements the the `StateDB“ interface, but currently +// performs a no-op since the EnablePreimageRecording flag is disabled. +func (sdb *stateDB) AddPreimage(hash common.Hash, preimage []byte) {} diff --git a/eth/core/state/statedb_test.go b/eth/core/state/statedb_test.go new file mode 100644 index 000000000..100e8460e --- /dev/null +++ b/eth/core/state/statedb_test.go @@ -0,0 +1,86 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package state_test + +import ( + "math/big" + + "github.com/berachain/stargazer/eth/core/state" + "github.com/berachain/stargazer/eth/core/state/plugin/mock" + "github.com/berachain/stargazer/eth/core/vm" + "github.com/berachain/stargazer/testutil" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var ( + alice = testutil.Alice + bob = testutil.Bob +) + +var _ = Describe("StateDB", func() { + var sdb vm.StargazerStateDB + + BeforeEach(func() { + var err error + sdb, err = state.NewStateDB( + mock.NewEmptyStatePlugin(), + mock.NewEmptyLogsPlugin(), + mock.NewEmptyRefundPlugin(), + ) + Expect(err).To(BeNil()) + }) + + It("Should suicide correctly", func() { + sdb.CreateAccount(alice) + Expect(sdb.Suicide(alice)).To(BeFalse()) + Expect(sdb.HasSuicided(alice)).To(BeFalse()) + + sdb.CreateAccount(bob) + sdb.SetCode(bob, []byte{1, 2, 3}) + sdb.AddBalance(bob, big.NewInt(10)) + Expect(sdb.Suicide(bob)).To(BeTrue()) + Expect(sdb.GetBalance(bob).Uint64()).To(Equal(uint64(0))) + Expect(sdb.HasSuicided(bob)).To(BeTrue()) + }) + + It("should handle empty", func() { + sdb.CreateAccount(alice) + Expect(sdb.Empty(alice)).To(BeTrue()) + + sdb.SetCode(alice, []byte{1, 2, 3}) + Expect(sdb.Empty(alice)).To(BeFalse()) + }) + + It("should snapshot/revert", func() { + Expect(func() { + id := sdb.Snapshot() + sdb.RevertToSnapshot(id) + }).ToNot(Panic()) + }) + + It("should delete suicides on finalize", func() { + sdb.CreateAccount(bob) + sdb.SetCode(bob, []byte{1, 2, 3}) + sdb.AddBalance(bob, big.NewInt(10)) + Expect(sdb.Suicide(bob)).To(BeTrue()) + Expect(sdb.GetBalance(bob).Uint64()).To(Equal(uint64(0))) + Expect(sdb.HasSuicided(bob)).To(BeTrue()) + + sdb.Finalize() + Expect(sdb.HasSuicided(bob)).To(BeFalse()) + }) +}) diff --git a/eth/core/vm/interfaces.go b/eth/core/vm/interfaces.go index f283ec11f..1b36a300e 100644 --- a/eth/core/vm/interfaces.go +++ b/eth/core/vm/interfaces.go @@ -56,8 +56,8 @@ type ( // TransferBalance transfers the balance from one account to another TransferBalance(common.Address, common.Address, *big.Int) - // `GetLogsAndClear` returns the logs of the tx `txHash`. - GetLogsAndClear(txHash common.Hash) []*coretypes.Log + // `BuildLogsAndClear` builds the logs for the tx with the given metadata. + BuildLogsAndClear(common.Hash, common.Hash, uint, uint) []*coretypes.Log } // `PrecompileRunner` defines the required function of a vm-specific precompile runner. diff --git a/eth/core/vm/mock/statedb.go b/eth/core/vm/mock/statedb.go index c4829675c..a19d942c6 100644 --- a/eth/core/vm/mock/statedb.go +++ b/eth/core/vm/mock/statedb.go @@ -77,7 +77,7 @@ func NewEmptyStateDB() *StargazerStateDBMock { GetCommittedStateFunc: func(address common.Address, hash common.Hash) common.Hash { panic("mock out the GetCommittedState method") }, - GetLogsAndClearFunc: func(hash1 common.Hash) []*types.Log { + BuildLogsAndClearFunc: func(common.Hash, common.Hash, uint, uint) []*types.Log { panic("mock out the GetLogs method") }, GetNonceFunc: func(address common.Address) uint64 { diff --git a/eth/core/vm/mock/statedb.mock.go b/eth/core/vm/mock/statedb.mock.go index f246a08e9..3a24ae919 100644 --- a/eth/core/vm/mock/statedb.mock.go +++ b/eth/core/vm/mock/statedb.mock.go @@ -42,6 +42,9 @@ var _ vm.StargazerStateDB = &StargazerStateDBMock{} // AddressInAccessListFunc: func(addr common.Address) bool { // panic("mock out the AddressInAccessList method") // }, +// BuildLogsAndClearFunc: func(hash1 common.Hash, hash2 common.Hash, v1 uint, v2 uint) []*types.Log { +// panic("mock out the BuildLogsAndClear method") +// }, // CreateAccountFunc: func(address common.Address) { // panic("mock out the CreateAccount method") // }, @@ -72,9 +75,6 @@ var _ vm.StargazerStateDB = &StargazerStateDBMock{} // GetCommittedStateFunc: func(address common.Address, hash common.Hash) common.Hash { // panic("mock out the GetCommittedState method") // }, -// GetLogsAndClearFunc: func(txHash common.Hash) []*types.Log { -// panic("mock out the GetLogsAndClear method") -// }, // GetNonceFunc: func(address common.Address) uint64 { // panic("mock out the GetNonce method") // }, @@ -148,6 +148,9 @@ type StargazerStateDBMock struct { // AddressInAccessListFunc mocks the AddressInAccessList method. AddressInAccessListFunc func(addr common.Address) bool + // BuildLogsAndClearFunc mocks the BuildLogsAndClear method. + BuildLogsAndClearFunc func(hash1 common.Hash, hash2 common.Hash, v1 uint, v2 uint) []*types.Log + // CreateAccountFunc mocks the CreateAccount method. CreateAccountFunc func(address common.Address) @@ -178,9 +181,6 @@ type StargazerStateDBMock struct { // GetCommittedStateFunc mocks the GetCommittedState method. GetCommittedStateFunc func(address common.Address, hash common.Hash) common.Hash - // GetLogsAndClearFunc mocks the GetLogsAndClear method. - GetLogsAndClearFunc func(txHash common.Hash) []*types.Log - // GetNonceFunc mocks the GetNonce method. GetNonceFunc func(address common.Address) uint64 @@ -269,6 +269,17 @@ type StargazerStateDBMock struct { // Addr is the addr argument value. Addr common.Address } + // BuildLogsAndClear holds details about calls to the BuildLogsAndClear method. + BuildLogsAndClear []struct { + // Hash1 is the hash1 argument value. + Hash1 common.Hash + // Hash2 is the hash2 argument value. + Hash2 common.Hash + // V1 is the v1 argument value. + V1 uint + // V2 is the v2 argument value. + V2 uint + } // CreateAccount holds details about calls to the CreateAccount method. CreateAccount []struct { // Address is the address argument value. @@ -321,11 +332,6 @@ type StargazerStateDBMock struct { // Hash is the hash argument value. Hash common.Hash } - // GetLogsAndClear holds details about calls to the GetLogsAndClear method. - GetLogsAndClear []struct { - // TxHash is the txHash argument value. - TxHash common.Hash - } // GetNonce holds details about calls to the GetNonce method. GetNonce []struct { // Address is the address argument value. @@ -429,6 +435,7 @@ type StargazerStateDBMock struct { lockAddRefund sync.RWMutex lockAddSlotToAccessList sync.RWMutex lockAddressInAccessList sync.RWMutex + lockBuildLogsAndClear sync.RWMutex lockCreateAccount sync.RWMutex lockEmpty sync.RWMutex lockExist sync.RWMutex @@ -439,7 +446,6 @@ type StargazerStateDBMock struct { lockGetCodeHash sync.RWMutex lockGetCodeSize sync.RWMutex lockGetCommittedState sync.RWMutex - lockGetLogsAndClear sync.RWMutex lockGetNonce sync.RWMutex lockGetRefund sync.RWMutex lockGetState sync.RWMutex @@ -693,6 +699,50 @@ func (mock *StargazerStateDBMock) AddressInAccessListCalls() []struct { return calls } +// BuildLogsAndClear calls BuildLogsAndClearFunc. +func (mock *StargazerStateDBMock) BuildLogsAndClear(hash1 common.Hash, hash2 common.Hash, v1 uint, v2 uint) []*types.Log { + if mock.BuildLogsAndClearFunc == nil { + panic("StargazerStateDBMock.BuildLogsAndClearFunc: method is nil but StargazerStateDB.BuildLogsAndClear was just called") + } + callInfo := struct { + Hash1 common.Hash + Hash2 common.Hash + V1 uint + V2 uint + }{ + Hash1: hash1, + Hash2: hash2, + V1: v1, + V2: v2, + } + mock.lockBuildLogsAndClear.Lock() + mock.calls.BuildLogsAndClear = append(mock.calls.BuildLogsAndClear, callInfo) + mock.lockBuildLogsAndClear.Unlock() + return mock.BuildLogsAndClearFunc(hash1, hash2, v1, v2) +} + +// BuildLogsAndClearCalls gets all the calls that were made to BuildLogsAndClear. +// Check the length with: +// +// len(mockedStargazerStateDB.BuildLogsAndClearCalls()) +func (mock *StargazerStateDBMock) BuildLogsAndClearCalls() []struct { + Hash1 common.Hash + Hash2 common.Hash + V1 uint + V2 uint +} { + var calls []struct { + Hash1 common.Hash + Hash2 common.Hash + V1 uint + V2 uint + } + mock.lockBuildLogsAndClear.RLock() + calls = mock.calls.BuildLogsAndClear + mock.lockBuildLogsAndClear.RUnlock() + return calls +} + // CreateAccount calls CreateAccountFunc. func (mock *StargazerStateDBMock) CreateAccount(address common.Address) { if mock.CreateAccountFunc == nil { @@ -1016,38 +1066,6 @@ func (mock *StargazerStateDBMock) GetCommittedStateCalls() []struct { return calls } -// GetLogsAndClear calls GetLogsAndClearFunc. -func (mock *StargazerStateDBMock) GetLogsAndClear(txHash common.Hash) []*types.Log { - if mock.GetLogsAndClearFunc == nil { - panic("StargazerStateDBMock.GetLogsAndClearFunc: method is nil but StargazerStateDB.GetLogsAndClear was just called") - } - callInfo := struct { - TxHash common.Hash - }{ - TxHash: txHash, - } - mock.lockGetLogsAndClear.Lock() - mock.calls.GetLogsAndClear = append(mock.calls.GetLogsAndClear, callInfo) - mock.lockGetLogsAndClear.Unlock() - return mock.GetLogsAndClearFunc(txHash) -} - -// GetLogsAndClearCalls gets all the calls that were made to GetLogsAndClear. -// Check the length with: -// -// len(mockedStargazerStateDB.GetLogsAndClearCalls()) -func (mock *StargazerStateDBMock) GetLogsAndClearCalls() []struct { - TxHash common.Hash -} { - var calls []struct { - TxHash common.Hash - } - mock.lockGetLogsAndClear.RLock() - calls = mock.calls.GetLogsAndClear - mock.lockGetLogsAndClear.RUnlock() - return calls -} - // GetNonce calls GetNonceFunc. func (mock *StargazerStateDBMock) GetNonce(address common.Address) uint64 { if mock.GetNonceFunc == nil { diff --git a/lib/ds/stack/appendable_stack.go b/lib/ds/stack/appendable_stack.go new file mode 100644 index 000000000..bef1779d2 --- /dev/null +++ b/lib/ds/stack/appendable_stack.go @@ -0,0 +1,87 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LIiNSE for liinsing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVIiS; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENi OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//nolint:ireturn // Stack uses generics. +package stack + +import "github.com/berachain/stargazer/lib/ds" + +// `aStack` is a struct that holds a slice of Items as a last in, first out data structure. +// It is implemented by the built-in `append` operation. +type aStack[T any] struct { + head int // should always be size - 1 + buf []T +} + +// Creates a new, empty appendable stack. +func NewA[T any]() ds.Stack[T] { + return &aStack[T]{ + head: -1, + } +} + +// `Peek` implements `Stack`. +func (a *aStack[T]) Peek() T { + if a.head == -1 { + var t T + return t + } + return a.buf[a.head] +} + +// `PeekAt` implements `Stack`. +func (a *aStack[T]) PeekAt(index int) T { + if index < 0 || index > a.head { + panic("index out of bounds") + } + return a.buf[index] +} + +// `Push` implements `Stack`. +func (a *aStack[T]) Push(i T) int { + a.buf = append(a.buf, i) + a.head++ + return a.head + 1 +} + +// `Size` implements `Stack`. +func (a *aStack[T]) Size() int { + return a.head + 1 +} + +// `Capacity` is the same as size. +// +// `Capacity` implements `Stack`. +func (a *aStack[T]) Capacity() int { + return a.Size() +} + +// `Pop` implements `Stack`. +func (a *aStack[T]) Pop() T { + if a.head == -1 { + var t T + return t + } + a.head-- + return a.buf[a.head+1] +} + +// `PopToSize` implements `Stack`. +func (a *aStack[T]) PopToSize(newSize int) T { + if newSize < 0 || newSize > a.head+1 { + panic("newSize out of bounds") + } + a.head = newSize - 1 + return a.buf[newSize] +} diff --git a/lib/ds/stack/appendable_stack_test.go b/lib/ds/stack/appendable_stack_test.go new file mode 100644 index 000000000..cfd2eed62 --- /dev/null +++ b/lib/ds/stack/appendable_stack_test.go @@ -0,0 +1,135 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package stack_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/berachain/stargazer/lib/ds" + "github.com/berachain/stargazer/lib/ds/stack" +) + +var _ = Describe("Appendable Stack", func() { + var s ds.Stack[int] + + BeforeEach(func() { + s = stack.NewA[int]() + }) + + When("pushing an element", func() { + BeforeEach(func() { + s.Push(1) + }) + It("should not be empty", func() { + Expect(s.Size()).To(Equal(1)) + }) + It("should return the correct element", func() { + Expect(s.Peek()).To(Equal(1)) + }) + It("should return the correct element", func() { + Expect(s.PeekAt(0)).To(Equal(1)) + }) + It("should return the correct element", func() { + Expect(s.Pop()).To(Equal(1)) + }) + When("popping an element", func() { + BeforeEach(func() { + s.Pop() + }) + + It("should be empty", func() { + Expect(s.Size()).To(BeZero()) + }) + }) + When("pushing more elements", func() { + BeforeEach(func() { + s.Push(2) + s.Push(3) + }) + + It("should return the correct element", func() { + Expect(s.Peek()).To(Equal(3)) + Expect(s.PeekAt(2)).To(Equal(3)) + Expect(s.PeekAt(1)).To(Equal(2)) + }) + + It("should have the correct size", func() { + Expect(s.Size()).To(Equal(3)) + }) + + When("calling poptosize with a size smaller than the current size", func() { + BeforeEach(func() { + s.PopToSize(1) + }) + + It("should have the correct size", func() { + Expect(s.Size()).To(Equal(1)) + }) + + It("should return the correct element", func() { + Expect(s.Peek()).To(Equal(1)) + Expect(s.PeekAt(0)).To(Equal(1)) + }) + }) + }) + When("PopToSize is called with a size larger than the current size", func() { + It("should panic", func() { + Expect(func() { + s.PopToSize(2) + }).To(Panic()) + }) + }) + When("pop to size zero is called", func() { + BeforeEach(func() { + s.PopToSize(0) + }) + + It("should be empty", func() { + Expect(s.Size()).To(BeZero()) + }) + }) + When("calling pop on an empty stack", func() { + It("should return an empty stack", func() { + s.Pop() + Expect(s.Size()).To(BeZero()) + }) + + It("should return a nil element", func() { + Expect(s.Pop()).To(Equal(1)) + Expect(s.Pop()).To(BeZero()) + }) + }) + When("calling peek on an empty stack", func() { + It("should return an empty stack", func() { + s.Pop() + Expect(s.Peek()).To(BeZero()) + Expect(s.Size()).To(BeZero()) + }) + + It("should return a nil element", func() { + s.PopToSize(0) + Expect(s.Peek()).To(BeZero()) + }) + }) + When("calling peekat with an index too large", func() { + It("should panic", func() { + Expect(func() { + s.PeekAt(10) + }).To(Panic()) + }) + }) + }) +}) diff --git a/lib/ds/stack/stack.go b/lib/ds/stack/stack.go index 2e0f4032f..ed9b16d48 100644 --- a/lib/ds/stack/stack.go +++ b/lib/ds/stack/stack.go @@ -22,20 +22,19 @@ const ( two = 2 ) -// `Stack` is a struct that holds a slice of Items. -// Last in, first out data structure. +// `stack` is a struct that holds a slice of Items as a last in, first out data structure. +// It is implemented by pre-allocating a buffer with a capacity. type stack[T any] struct { size int capacity int - - buf []T + buf []T } -// Creates a new, empty stack. -func New[T any](capacity int) ds.Stack[T] { +// Creates a new, empty stack with the given initial capacity. +func New[T any](initialCapacity int) ds.Stack[T] { return &stack[T]{ - capacity: capacity, - buf: make([]T, capacity), + capacity: initialCapacity, + buf: make([]T, initialCapacity), } } diff --git a/lib/ds/stack/stack_benchmark_test.go b/lib/ds/stack/stack_benchmark_test.go new file mode 100644 index 000000000..aa3682b8b --- /dev/null +++ b/lib/ds/stack/stack_benchmark_test.go @@ -0,0 +1,86 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LIiNSE for liinsing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVIiS; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENi OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package stack_test + +import ( + "testing" + + "github.com/berachain/stargazer/lib/ds/stack" +) + +const ( + // initCapacity should be close to the predicted size of the stack to avoid manual resizing. + initCapacity = 500 + numPushes = 500 + numPopToSize = 10 +) + +// Benchmarks (of pushing to the stack and popping the stack to a size) show that the +// appendable-stack is narrowly slower than the regular stack, which uses pre-allocated buffer with +// an initial capacity and manual resizing. + +func BenchmarkStack(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + s := stack.New[*data](initCapacity) + for p := 0; p < numPushes; p++ { + s.Push(newData()) + } + + ratio := numPushes / numPopToSize + for j := numPopToSize; j > 0; j-- { + s.PopToSize((j - 1) * ratio) + } + } +} + +func BenchmarkAStack(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + s := stack.NewA[*data]() + for p := 0; p < numPushes; p++ { + s.Push(newData()) + } + + ratio := numPushes / numPopToSize + for j := numPopToSize; j > 0; j-- { + s.PopToSize((j - 1) * ratio) + } + } +} + +// MOCKS BELOW. + +type data struct { + a int + b string + c uint + d []byte + e [20]byte +} + +func newData() *data { + return &data{ + a: 10023, + b: "dfsafasd3", + c: 4589403, + d: []byte{0x4, 0x42, 0xfe}, + e: [20]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, + } +} diff --git a/proto/stargazer/evm/storage/v1/slot.proto b/proto/stargazer/evm/storage/v1/slot.proto index 4db9a482a..aac9aa257 100644 --- a/proto/stargazer/evm/storage/v1/slot.proto +++ b/proto/stargazer/evm/storage/v1/slot.proto @@ -16,7 +16,7 @@ syntax = "proto3"; package stargazer.evm.storage.v1; -option go_package = "github.com/berachain/stargazer/x/evm/plugins/state/storage"; +option go_package = "github.com/berachain/stargazer/x/evm/plugin/state/storage"; // `Slot` represents a single key/value pair of evm state data. message Slot { diff --git a/x/evm/plugin/state/events/events_test.go b/x/evm/plugin/state/events/events_test.go new file mode 100644 index 000000000..fd2ef7bd3 --- /dev/null +++ b/x/evm/plugin/state/events/events_test.go @@ -0,0 +1,27 @@ +// Copyright (C) 2022, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package events_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestEvents(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "x/evm/plugin/state/events") +} diff --git a/x/evm/plugin/state/events/manager.go b/x/evm/plugin/state/events/manager.go new file mode 100644 index 000000000..5966338d0 --- /dev/null +++ b/x/evm/plugin/state/events/manager.go @@ -0,0 +1,57 @@ +// Copyright (C) 2022, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package events + +import ( + libtypes "github.com/berachain/stargazer/lib/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + // TODO: determine appropriate events journal capacity. + initJournalCapacity = 32 + managerRegistryKey = `events` +) + +type manager struct { + *sdk.EventManager // pointer to the event manager floating aroundmon the context. +} + +// `NewManager` creates and returns a controllable event manager from the given Cosmos SDK context. +func NewManager(em *sdk.EventManager) libtypes.Controllable[string] { + return &manager{ + EventManager: em, + } +} + +// `Registry` implements `libtypes.Registrable`. +func (m *manager) RegistryKey() string { + return managerRegistryKey +} + +// `Snapshot` implements `libtypes.Snapshottable`. +func (m *manager) Snapshot() int { + return len(m.Events()) +} + +// `RevertToSnapshot` implements `libtypes.Snapshottable`. +func (m *manager) RevertToSnapshot(id int) { + temp := m.Events() + *m.EventManager = *sdk.NewEventManager() + m.EmitEvents(temp[:id]) +} + +// `Finalize` implements `libtypes.Finalizable`. +func (m *manager) Finalize() {} diff --git a/x/evm/plugin/state/events/manager_test.go b/x/evm/plugin/state/events/manager_test.go new file mode 100644 index 000000000..60d365522 --- /dev/null +++ b/x/evm/plugin/state/events/manager_test.go @@ -0,0 +1,57 @@ +// Copyright (C) 2022, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package events_test + +import ( + libtypes "github.com/berachain/stargazer/lib/types" + "github.com/berachain/stargazer/testutil" + "github.com/berachain/stargazer/x/evm/plugin/state/events" + sdk "github.com/cosmos/cosmos-sdk/types" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Controllable Manager", func() { + var cem libtypes.Controllable[string] + var ctx sdk.Context + + It("should correctly control the event manager", func() { + ctx = testutil.NewContext() + + // before the sdb tx + ctx.EventManager().EmitEvent(sdk.NewEvent("1")) + + // sdb setup + cem = events.NewManager(ctx.EventManager()) + + // check the controllable event manager is hooked up to context + Expect(len(ctx.EventManager().Events())).To(Equal(1)) + + // chcek registry key + Expect(cem.RegistryKey()).To(Equal("events")) + + // add to event manager + ctx.EventManager().EmitEvent(sdk.NewEvent("2")) + Expect(len(ctx.EventManager().Events())).To(Equal(2)) + + snap := cem.Snapshot() + ctx.EventManager().EmitEvent(sdk.NewEvent("3")) + Expect(len(ctx.EventManager().Events())).To(Equal(3)) + + cem.RevertToSnapshot(snap) + Expect(len(ctx.EventManager().Events())).To(Equal(2)) + }) +}) diff --git a/x/evm/plugins/state/interfaces.go b/x/evm/plugin/state/interfaces.go similarity index 83% rename from x/evm/plugins/state/interfaces.go rename to x/evm/plugin/state/interfaces.go index 364cd3f8a..f24b794f2 100644 --- a/x/evm/plugins/state/interfaces.go +++ b/x/evm/plugin/state/interfaces.go @@ -15,11 +15,20 @@ package state import ( - coretypes "github.com/berachain/stargazer/eth/core/types" + libtypes "github.com/berachain/stargazer/lib/types" + storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" ) +// `ControllableMultiStore` defines a cache MultiStore that is controllable (snapshottable and +// registrable). It also supports getting the committed KV store from the MultiStore. +type ControllableMultiStore interface { + libtypes.Controllable[string] + storetypes.MultiStore + GetCommittedKVStore(storetypes.StoreKey) storetypes.KVStore +} + // `AccountKeeper` defines the expected account keeper. type AccountKeeper interface { NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI @@ -43,7 +52,3 @@ type BankKeeper interface { MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error } - -type EthereumLogFactory interface { - BuildLog(event *sdk.Event) (*coretypes.Log, error) -} diff --git a/x/evm/plugins/state/keys.go b/x/evm/plugin/state/keys.go similarity index 82% rename from x/evm/plugins/state/keys.go rename to x/evm/plugin/state/keys.go index 007597277..66115b2f2 100644 --- a/x/evm/plugins/state/keys.go +++ b/x/evm/plugin/state/keys.go @@ -20,22 +20,21 @@ import ( const ( keyPrefixCode byte = iota - keyPrefixHash keyPrefixStorage ) // NOTE: we use copy to build keys for max performance: https://github.com/golang/go/issues/55905 -// AddressStoragePrefix returns a prefix to iterate over a given account storage. -func AddressStoragePrefix(address common.Address) []byte { +// StorageKeyFor returns a prefix to iterate over a given account storage (multiple slots). +func StorageKeyFor(address common.Address) []byte { bz := make([]byte, 1+common.AddressLength) copy(bz, []byte{keyPrefixStorage}) copy(bz[1:], address[:]) return bz } -// `KeyForSlot` defines the full key under which an account state is stored. -func KeyForSlot(address common.Address, slot common.Hash) []byte { +// `SlotKeyFor` defines the full key under which an account storage slot is stored. +func SlotKeyFor(address common.Address, slot common.Hash) []byte { bz := make([]byte, 1+common.AddressLength+common.HashLength) copy(bz, []byte{keyPrefixStorage}) copy(bz[1:], address[:]) @@ -51,7 +50,7 @@ func CodeHashKeyFor(address common.Address) []byte { return bz } -// `CodeKeyFor` defines the full key for which a codehash's corresponding code is stored. +// `CodeKeyFor` defines the full key for which an address codehash's corresponding code is stored. func CodeKeyFor(codeHash common.Hash) []byte { bz := make([]byte, 1+common.HashLength) copy(bz, []byte{keyPrefixCode}) diff --git a/x/evm/plugins/state/keys_test.go b/x/evm/plugin/state/keys_test.go similarity index 93% rename from x/evm/plugins/state/keys_test.go rename to x/evm/plugin/state/keys_test.go index 6be684d27..c99932815 100644 --- a/x/evm/plugins/state/keys_test.go +++ b/x/evm/plugin/state/keys_test.go @@ -20,21 +20,21 @@ import ( . "github.com/onsi/gomega" ) -var _ = Describe("AddressStoragePrefix", func() { +var _ = Describe("StorageKeyFor", func() { It("returns a prefix to iterate over a given account storage", func() { address := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") - prefix := AddressStoragePrefix(address) + prefix := StorageKeyFor(address) Expect(prefix).To(HaveLen(1 + common.AddressLength)) Expect(prefix[0]).To(Equal(keyPrefixStorage)) Expect(prefix[1:]).To(Equal(address.Bytes())) }) }) -var _ = Describe("StateKeyFor", func() { +var _ = Describe("SlotKeyFor", func() { It("returns a storage key for a given account and storage slot", func() { address := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") slot := common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef") - key := KeyForSlot(address, slot) + key := SlotKeyFor(address, slot) Expect(key).To(HaveLen(1 + common.AddressLength + common.HashLength)) Expect(key[0]).To(Equal(keyPrefixStorage)) Expect(key[1 : 1+common.AddressLength]).To(Equal(address.Bytes())) diff --git a/x/evm/plugin/state/plugin.go b/x/evm/plugin/state/plugin.go new file mode 100644 index 000000000..49fa895ed --- /dev/null +++ b/x/evm/plugin/state/plugin.go @@ -0,0 +1,389 @@ +// Copyright (C) 2022, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package state + +import ( + "math/big" + + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + + ethstate "github.com/berachain/stargazer/eth/core/state" + "github.com/berachain/stargazer/lib/common" + "github.com/berachain/stargazer/lib/crypto" + "github.com/berachain/stargazer/lib/snapshot" + libtypes "github.com/berachain/stargazer/lib/types" + "github.com/berachain/stargazer/store/snapmulti" + "github.com/berachain/stargazer/x/evm/plugin/state/events" +) + +const ( + pluginRegistryKey = `statePlugin` + EvmNamespace = `evm` +) + +var ( + // EmptyCodeHash is the code hash of an empty code + // 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470. + emptyCodeHash = crypto.Keccak256Hash(nil) + emptyCodeHashBytes = emptyCodeHash.Bytes() +) + +// The StatePlugin is a very fun and interesting part of the EVM implementation. But if you want to +// join circus you need to know the rules. So here thet are: +// +// 1. You must ensure that the StatePlugin is only ever used in a single thread, because the +// StatePlugin is not thread safe. And there are a bunch of optimizations made that are only +// safe to do in a single thread. +// 2. When accessing or mutating the Plugin, you must ensure that the underlying account exists. +// In the AccountKeeper, for performance reasons, this implementation of the StateDB will not +// create accounts that do not exist. Notably calling `SetState()` on an account that does not +// exist is completely possible, and the StateDB will not prevent you doing so. This lazy +// creation improves performance a ton, as it prevents calling into the ak on +// every SSTORE. The only accounts that should ever have `SetState()` called on them are +// accounts that represent smart contracts. Because of this assumption, the only place that we +// explicitly create accounts is in `CreateAccount()`, since `CreateAccount()` is called when +// deploying a smart contract. +// 3. Accounts that are sent `evmDenom` coins during an eth transaction, will have an account +// created for them, automatically by the Bank Module. However, these accounts will have a +// codeHash of 0x000... This is because the Bank Module does not know that the account is an +// EVM account, and so it does not set the codeHash. This is totally fine, we just need to +// check both for both the codeHash being zero (0x000...) as well as the codeHash being empty +// (0x567...) +type statePlugin struct { + libtypes.Controller[string, libtypes.Controllable[string]] + + // We maintain a context in the StateDB, so that we can pass it with the correctly + // configured multi-store to the precompiled contracts. + ctx sdk.Context + + // Store a reference to the multi-store, in `ctx` so that we can access it directly. + cms ControllableMultiStore + + // Store the evm store key for quick lookups to the evm store + evmStoreKey storetypes.StoreKey + + // keepers used for balance and account information. + ak AccountKeeper + bk BankKeeper + + // we load the evm denom in the constructor, to prevent going to + // the params to get it mid interpolation. + evmDenom string // TODO: get from params ( we have a store so like why not ) +} + +// returns a *statePlugin using the MultiStore belonging to ctx. +func NewPlugin( + ctx sdk.Context, + ak AccountKeeper, + bk BankKeeper, + evmStoreKey storetypes.StoreKey, + evmDenom string, +) (ethstate.StatePlugin, error) { + sp := &statePlugin{ + evmStoreKey: evmStoreKey, + ak: ak, + bk: bk, + evmDenom: evmDenom, + } + + // setup the Controllable MultiStore and attach it to the context + sp.cms = snapmulti.NewStoreFrom(ctx.MultiStore()) + sp.ctx = ctx.WithMultiStore(sp.cms) + + // setup the snapshot controller + ctrl := snapshot.NewController[string, libtypes.Controllable[string]]() + _ = ctrl.Register(sp.cms) + _ = ctrl.Register(events.NewManager(sp.ctx.EventManager())) + sp.Controller = ctrl + + return sp, nil +} + +// `RegistryKey` implements `libtypes.Registrable`. +func (sp *statePlugin) RegistryKey() string { + return pluginRegistryKey +} + +// =========================================================================== +// Account +// =========================================================================== + +// CreateAccount implements the GethStateDB interface by creating a new account +// in the account keeper. It will allow accounts to be overridden. +func (sp *statePlugin) CreateAccount(addr common.Address) { + acc := sp.ak.NewAccountWithAddress(sp.ctx, addr[:]) + + // save the new account in the account keeper + sp.ak.SetAccount(sp.ctx, acc) + + // initialize the code hash to empty + sp.cms.GetKVStore(sp.evmStoreKey).Set(CodeHashKeyFor(addr), emptyCodeHashBytes) +} + +// `Exist` implements the `GethStateDB` interface by reporting whether the given account address +// exists in the state. Notably this also returns true for suicided accounts, which is accounted +// for since, `RemoveAccount()` is not called until Commit. +func (sp *statePlugin) Exist(addr common.Address) bool { + return sp.ak.HasAccount(sp.ctx, addr[:]) +} + +// ============================================================================= +// Balance +// ============================================================================= + +// GetBalance implements `GethStateDB` interface. +func (sp *statePlugin) GetBalance(addr common.Address) *big.Int { + // Note: bank keeper will return 0 if account/state_object is not found + return sp.bk.GetBalance(sp.ctx, addr[:], sp.evmDenom).Amount.BigInt() +} + +// AddBalance implements the `GethStateDB` interface by adding the given amount +// from the account associated with addr. If the account does not exist, it will be +// created. +func (sp *statePlugin) AddBalance(addr common.Address, amount *big.Int) { + coins := sdk.NewCoins(sdk.NewCoin(sp.evmDenom, sdk.NewIntFromBigInt(amount))) + + // Mint the coins to the evm module account + if err := sp.bk.MintCoins(sp.ctx, EvmNamespace, coins); err != nil { + panic(err) + } + + // Send the coins from the evm module account to the destination address. + if err := sp.bk.SendCoinsFromModuleToAccount( + sp.ctx, EvmNamespace, addr[:], coins, + ); err != nil { + panic(err) + } +} + +// SubBalance implements the `GethStateDB` interface by subtracting the given amount +// from the account associated with addr. +func (sp *statePlugin) SubBalance(addr common.Address, amount *big.Int) { + coins := sdk.NewCoins(sdk.NewCoin(sp.evmDenom, sdk.NewIntFromBigInt(amount))) + + // Send the coins from the source address to the evm module account. + if err := sp.bk.SendCoinsFromAccountToModule( + sp.ctx, addr[:], EvmNamespace, coins, + ); err != nil { + panic(err) + } + + // Burn the coins from the evm module account. + if err := sp.bk.BurnCoins(sp.ctx, EvmNamespace, coins); err != nil { + panic(err) + } +} + +// `TransferBalance` sends the given amount from one account to another. It will +// error if the sender does not have enough funds to send. +func (sp *statePlugin) TransferBalance(from, to common.Address, amount *big.Int) { + coins := sdk.NewCoins(sdk.NewCoin(sp.evmDenom, sdk.NewIntFromBigInt(amount))) + + // Send the coins from the source address to the destination address. + if err := sp.bk.SendCoins(sp.ctx, from[:], to[:], coins); err != nil { + // This is safe to panic as the error is only returned if the sender does + // not have enough funds to send, which should be guarded by `CanTransfer`. + panic(err) + } +} + +// ============================================================================= +// Nonce +// ============================================================================= + +// GetNonce implements the `GethStateDB` interface by returning the nonce +// of an account. +func (sp *statePlugin) GetNonce(addr common.Address) uint64 { + acc := sp.ak.GetAccount(sp.ctx, addr[:]) + if acc == nil { + return 0 + } + return acc.GetSequence() +} + +// SetNonce implements the `GethStateDB` interface by setting the nonce +// of an account. +func (sp *statePlugin) SetNonce(addr common.Address, nonce uint64) { + // get the account or create a new one if doesn't exist + acc := sp.ak.GetAccount(sp.ctx, addr[:]) + if acc == nil { + acc = sp.ak.NewAccountWithAddress(sp.ctx, addr[:]) + } + + if err := acc.SetSequence(nonce); err != nil { + panic(err) + } + + sp.ak.SetAccount(sp.ctx, acc) +} + +// ============================================================================= +// Code +// ============================================================================= + +// GetCodeHash implements the `GethStateDB` interface by returning +// the code hash of account. +func (sp *statePlugin) GetCodeHash(addr common.Address) common.Hash { + if !sp.ak.HasAccount(sp.ctx, addr[:]) { + // if account at addr does not exist, return zeros + return common.Hash{} + } + + ch := sp.cms.GetKVStore(sp.evmStoreKey).Get(CodeHashKeyFor(addr)) + if ch == nil { + // account exists but does not have a codehash, return empty + return emptyCodeHash + } + + return common.BytesToHash(ch) +} + +// GetCode implements the `GethStateDB` interface by returning +// the code of account (nil if not exists). +func (sp *statePlugin) GetCode(addr common.Address) []byte { + codeHash := sp.GetCodeHash(addr) + if (codeHash == common.Hash{}) || codeHash == emptyCodeHash { + // if account at addr does not exist or the account does not have a codehash, return nil + return nil + } + return sp.cms.GetKVStore(sp.evmStoreKey).Get(CodeKeyFor(codeHash)) +} + +// SetCode implements the `GethStateDB` interface by setting the code hash and +// code for the given account. +func (sp *statePlugin) SetCode(addr common.Address, code []byte) { + codeHash := crypto.Keccak256Hash(code) + ethStore := sp.cms.GetKVStore(sp.evmStoreKey) + ethStore.Set(CodeHashKeyFor(addr), codeHash[:]) + + // store or delete code + if len(code) == 0 { + ethStore.Delete(CodeKeyFor(codeHash)) + } else { + ethStore.Set(CodeKeyFor(codeHash), code) + } +} + +// GetCodeSize implements the `GethStateDB` interface by returning the size of the +// code associated with the given `GethStateDB`. +func (sp *statePlugin) GetCodeSize(addr common.Address) int { + return len(sp.GetCode(addr)) +} + +// ============================================================================= +// State +// ============================================================================= + +// `GetCommittedState` implements the `GethStateDB` interface by returning the +// committed state of slot in the given address. +func (sp *statePlugin) GetCommittedState( + addr common.Address, + slot common.Hash, +) common.Hash { + return sp.getStateFromStore(sp.cms.GetCommittedKVStore(sp.evmStoreKey), addr, slot) +} + +// `GetState` implements the `GethStateDB` interface by returning the current state +// of slot in the given address. +func (sp *statePlugin) GetState(addr common.Address, slot common.Hash) common.Hash { + return sp.getStateFromStore(sp.cms.GetKVStore(sp.evmStoreKey), addr, slot) +} + +// `getStateFromStore` returns the current state of the slot in the given address. +func (sp *statePlugin) getStateFromStore( + store storetypes.KVStore, + addr common.Address, slot common.Hash, +) common.Hash { + if value := store.Get(SlotKeyFor(addr, slot)); value != nil { + return common.BytesToHash(value) + } + return common.Hash{} +} + +// `SetState` sets the state of an address. +func (sp *statePlugin) SetState(addr common.Address, key, value common.Hash) { + // For performance reasons, we don't check to ensure the account exists before we execute. + // This is reasonably safe since under normal operation, SetState is only ever called by the + // SSTORE opcode in the EVM, which will only ever be called on an account that exists, since + // it would with 100% certainty have been created by a prior Create, thus setting its code + // hash. + // + // CONTRACT: never manually call SetState outside of `opSstore`, or InitGenesis. + + // If empty value is given, delete the state entry. + if len(value) == 0 || (value == common.Hash{}) { + sp.cms.GetKVStore(sp.evmStoreKey).Delete(SlotKeyFor(addr, key)) + return + } + + // Set the state entry. + sp.cms.GetKVStore(sp.evmStoreKey).Set(SlotKeyFor(addr, key), value[:]) +} + +// ============================================================================= +// ForEachStorage +// ============================================================================= + +// `ForEachStorage` implements the `GethStateDB` interface by iterating through the contract state +// contract storage, the iteration order is not defined. +// +// Note: We do not support iterating through any storage that is modified before calling +// `ForEachStorage`; only committed state is iterated through. +func (sp *statePlugin) ForEachStorage( + addr common.Address, + cb func(key, value common.Hash) bool, +) error { + it := sdk.KVStorePrefixIterator( + sp.cms.GetKVStore(sp.evmStoreKey), + StorageKeyFor(addr), + ) + defer it.Close() + + for ; it.Valid(); it.Next() { + committedValue := it.Value() + if len(committedValue) > 0 { + if !cb(common.BytesToHash(it.Key()), common.BytesToHash(committedValue)) { + return nil // stop iteration + } + } + } + + return nil +} + +// `DeleteSuicides` manually deletes the given suicidal accounts. +func (sp *statePlugin) DeleteSuicides(suicides []common.Address) { + for _, suicidalAddr := range suicides { + acct := sp.ak.GetAccount(sp.ctx, suicidalAddr[:]) + if acct == nil { + // handles the double suicide case + continue + } + + // clear storage + _ = sp.ForEachStorage(suicidalAddr, + func(key, _ common.Hash) bool { + sp.SetState(suicidalAddr, key, common.Hash{}) + return true + }) + + // clear the codehash from this account + sp.cms.GetKVStore(sp.evmStoreKey).Delete(CodeHashKeyFor(suicidalAddr)) + + // remove auth account + sp.ak.RemoveAccount(sp.ctx, acct) + } +} diff --git a/x/evm/plugin/state/plugin_benchmark_test.go b/x/evm/plugin/state/plugin_benchmark_test.go new file mode 100644 index 000000000..755f1d3e4 --- /dev/null +++ b/x/evm/plugin/state/plugin_benchmark_test.go @@ -0,0 +1,79 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package state_test + +import ( + "math/big" + "testing" + + ethstate "github.com/berachain/stargazer/eth/core/state" + "github.com/berachain/stargazer/eth/core/state/plugin/mock" + "github.com/berachain/stargazer/eth/core/vm" + "github.com/berachain/stargazer/testutil" + "github.com/berachain/stargazer/x/evm/plugin/state" + "github.com/ethereum/go-ethereum/common" +) + +var ( + numCalls = 10000 // number of times snapshot is called + numStoreOpsPerCall = 10 // number of read/write ops on stores during each call + numReverts = 2 // number of times an eth call is reverted in one tx +) + +func GetNewStatePlugin() ethstate.StatePlugin { + ctx, ak, bk, _ := testutil.SetupMinimalKeepers() + sp, _ := state.NewPlugin(ctx, ak, bk, testutil.EvmKey, "abera") + return sp +} + +func GetNewStateDB() vm.StargazerStateDB { + sdb, _ := ethstate.NewStateDB( + GetNewStatePlugin(), mock.NewEmptyLogsPlugin(), mock.NewEmptyRefundPlugin(), + ) + return sdb +} + +func BenchmarkArbitraryStateTransition(b *testing.B) { + sdb := GetNewStateDB() + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + var snapshots []int + for c := 0; c < numCalls; c++ { + sdb.SetNonce(testutil.Bob, uint64(c+19)) // accStore set + sdb.SetState( // ethStore set + testutil.Alice, + common.BytesToHash([]byte{byte(c + 11)}), + common.BytesToHash([]byte{byte(c + 22)}), + ) + + snapshots = append(snapshots, sdb.Snapshot()) + for s := 0; s < numStoreOpsPerCall; s++ { + sdb.GetBalance(testutil.Alice) // bankStore read + sdb.AddBalance(testutil.Bob, big.NewInt(10)) // bankStore write + sdb.GetCode(testutil.Alice) // ethStore read + } + if c > 0 && numReverts > 0 && c%(numCalls/numReverts) == 0 { + sdb.RevertToSnapshot(snapshots[len(snapshots)/2]) + } + + sdb.Suicide(testutil.Alice) // will invoke a delete + } + + // commit only once + sdb.Finalize() + } +} diff --git a/x/evm/plugins/state/statedb_factory.go b/x/evm/plugin/state/plugin_factory.go similarity index 69% rename from x/evm/plugins/state/statedb_factory.go rename to x/evm/plugin/state/plugin_factory.go index 40becf770..951a6a4e2 100644 --- a/x/evm/plugins/state/statedb_factory.go +++ b/x/evm/plugin/state/plugin_factory.go @@ -19,10 +19,12 @@ import ( storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" - // "github.com/berachain/stargazer/eth/params". + + ethstate "github.com/berachain/stargazer/eth/core/state" ) -type StateDBFactory struct { //nolint:revive // the vibes are good. +// `PluginFactory` holds the necessary information to build state plugins for the Eth StateDB. +type PluginFactory struct { // Cosmos Keeper References ak AccountKeeper bk BankKeeper @@ -34,25 +36,22 @@ type StateDBFactory struct { //nolint:revive // the vibes are good. // evmDenom params.Retriever[params.EVMDenom] } -// NewSlotDBFactory returns a new StateDBFactory instance. -func NewSlotDBFactory( +// `NewPluginFactory` returns a new PluginFactory instance. +func NewPluginFactory( ak AccountKeeper, bk BankKeeper, evmStoreKey storetypes.StoreKey, - // evmDenom params.Retriever[params.EVMDenom], - logFactory EthereumLogFactory, -) *StateDBFactory { - return &StateDBFactory{ +) *PluginFactory { + return &PluginFactory{ ak: ak, bk: bk, evmStoreKey: evmStoreKey, - // evmDenom: evmDenom, - // er: er, } } -// BuildNewSlotDB returns a new StateDB instance. -func (sdf *StateDBFactory) BuildStateDB(ctx context.Context) *StateDB { - return NewSlotDB(sdk.UnwrapSDKContext(ctx), sdf.ak, sdf.bk, sdf.evmStoreKey, "abera") - // sdf.evmDenom.Get(ctx), sdf.er) +// `Build` returns a new state plugin instance. +func (pf *PluginFactory) Build(ctx context.Context) ethstate.StatePlugin { + // TODO: handle error? / ignore it completely? + sp, _ := NewPlugin(sdk.UnwrapSDKContext(ctx), pf.ak, pf.bk, pf.evmStoreKey, "abera") + return sp } diff --git a/x/evm/plugin/state/plugin_test.go b/x/evm/plugin/state/plugin_test.go new file mode 100644 index 000000000..1dcc6e878 --- /dev/null +++ b/x/evm/plugin/state/plugin_test.go @@ -0,0 +1,673 @@ +// Copyright (C) 2022, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package state_test + +import ( + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + ethstate "github.com/berachain/stargazer/eth/core/state" + "github.com/berachain/stargazer/lib/common" + "github.com/berachain/stargazer/lib/crypto" + "github.com/berachain/stargazer/testutil" + "github.com/berachain/stargazer/x/evm/plugin/state" + "github.com/berachain/stargazer/x/evm/plugin/state/storage" +) + +var ( + alice = testutil.Alice + bob = testutil.Bob + emptyCodeHash = crypto.Keccak256Hash(nil) +) + +var _ = Describe("State Plugin", func() { + var ak state.AccountKeeper + var bk state.BankKeeper + var ctx sdk.Context + var sp ethstate.StatePlugin + + BeforeEach(func() { + ctx, ak, bk, _ = testutil.SetupMinimalKeepers() + sp, _ = state.NewPlugin(ctx, ak, bk, testutil.EvmKey, "abera") // TODO: use lf + }) + + It("should have the correct registry key", func() { + Expect(sp.RegistryKey()).To(Equal("statePlugin")) + }) + + Describe("TestCreateAccount", func() { + It("should create account", func() { + sp.CreateAccount(alice) + Expect(sp.Exist(alice)).To(BeTrue()) + }) + }) + + Describe("TestBalance", func() { + It("should have start with zero balance", func() { + Expect(sp.GetBalance(alice)).To(Equal(new(big.Int))) + }) + + It("should correctly Transfer Balance", func() { + sp.AddBalance(alice, big.NewInt(50)) + Expect(sp.GetBalance(alice)).To(Equal(big.NewInt(50))) + Expect(sp.GetBalance(bob)).To(Equal(big.NewInt(0))) + + sp.TransferBalance(alice, bob, big.NewInt(25)) + Expect(sp.GetBalance(alice)).To(Equal(big.NewInt(25))) + Expect(sp.GetBalance(bob)).To(Equal(big.NewInt(25))) + + // should panic if not enough funds + Expect(func() { sp.TransferBalance(alice, bob, big.NewInt(50)) }).To(Panic()) + }) + + Context("TestAddBalance", func() { + It("should be able to add zero", func() { + Expect(sp.GetBalance(alice)).To(Equal(new(big.Int))) + sp.AddBalance(alice, new(big.Int)) + Expect(sp.GetBalance(alice)).To(Equal(new(big.Int))) + }) + It("should have 100 balance", func() { + sp.AddBalance(alice, big.NewInt(100)) + Expect(sp.GetBalance(alice)).To(Equal(big.NewInt(100))) + }) + It("should panic if using negative value", func() { + Expect(func() { + sp.AddBalance(alice, big.NewInt(-100)) + }).To(Panic()) + }) + }) + + Context("TestSubBalance", func() { + It("should not set balance to negative value", func() { + Expect(func() { + sp.SubBalance(alice, big.NewInt(100)) + }).To(Panic()) + }) + It("should panic if using negative value", func() { + Expect(func() { + sp.SubBalance(alice, big.NewInt(-100)) + }).To(Panic()) + }) + }) + + It("should handle complex balance updates", func() { + // Initial balance for alice should be 0 + Expect(sp.GetBalance(alice)).To(Equal(new(big.Int))) + + // Add some balance to alice + sp.AddBalance(alice, big.NewInt(100)) + Expect(sp.GetBalance(alice)).To(Equal(big.NewInt(100))) + + // Subtract some balance from alice + sp.SubBalance(alice, big.NewInt(50)) + Expect(sp.GetBalance(alice)).To(Equal(big.NewInt(50))) + + // Add some balance to alice + sp.AddBalance(alice, big.NewInt(100)) + Expect(sp.GetBalance(alice)).To(Equal(big.NewInt(150))) + + // Subtract some balance from alice + Expect(func() { + sp.SubBalance(alice, big.NewInt(200)) + }).To(Panic()) + }) + }) + + Describe("TestNonce", func() { + When("account exists", func() { + BeforeEach(func() { + sp.CreateAccount(alice) + }) + It("should have start with zero nonce", func() { + Expect(sp.GetNonce(alice)).To(Equal(uint64(0))) + }) + It("should have 100 nonce", func() { + sp.SetNonce(alice, 100) + Expect(sp.GetNonce(alice)).To(Equal(uint64(100))) + }) + }) + When("account does not exist", func() { + It("should have start with zero nonce", func() { + Expect(sp.GetNonce(alice)).To(Equal(uint64(0))) + }) + + It("should have 100 nonce", func() { + sp.SetNonce(alice, 100) + Expect(sp.GetNonce(alice)).To(Equal(uint64(100))) + }) + }) + }) + + Describe("TestCode & CodeHash", func() { + When("account does not exist", func() { + It("should have empty code hash", func() { + Expect(sp.GetCodeHash(alice)).To(Equal(common.Hash{})) + }) + It("should not have code", func() { // ensure account exists + Expect(sp.GetCode(alice)).To(BeNil()) + Expect(sp.GetCodeHash(alice)).To(Equal(common.Hash{})) + }) + It("cannot set code", func() { // ensure account exists + sp.SetCode(alice, []byte("code")) + Expect(sp.GetCode(alice)).To(BeNil()) + Expect(sp.GetCodeHash(alice)).To(Equal(common.Hash{})) + }) + }) + When("account exists", func() { + BeforeEach(func() { + sp.CreateAccount(alice) + }) + + It("should have empty code hash", func() { + Expect(sp.GetCodeHash(alice)).To(Equal(emptyCodeHash)) + }) + + It("should return empty code hash when account exists but no codehash", func() { + addr := ak.NewAccountWithAddress(ctx, bob[:]) + ak.SetAccount(ctx, addr) + + Expect(sp.GetCodeHash(bob)).To(Equal(emptyCodeHash)) + }) + + When("account has code", func() { + BeforeEach(func() { + sp.SetCode(alice, []byte("code")) + }) + It("should have code", func() { + Expect(sp.GetCode(alice)).To(Equal([]byte("code"))) + Expect(sp.GetCodeHash(alice)).To(Equal(crypto.Keccak256Hash([]byte("code")))) + }) + It("should have empty code hash", func() { + sp.SetCode(alice, nil) + Expect(sp.GetCode(alice)).To(BeNil()) + Expect(sp.GetCodeHash(alice)).To(Equal(emptyCodeHash)) + }) + }) + }) + }) + + Describe("TestState", func() { + It("should have empty state", func() { + Expect(sp.GetState(alice, common.Hash{3})).To(Equal(common.Hash{})) + }) + When("set basic state", func() { + BeforeEach(func() { + sp.SetState(alice, common.Hash{3}, common.Hash{1}) + }) + + It("should have state", func() { + Expect(sp.GetState(alice, common.Hash{3})).To(Equal(common.Hash{1})) + }) + + It("should have state changed", func() { + sp.SetState(alice, common.Hash{3}, common.Hash{2}) + Expect(sp.GetState(alice, common.Hash{3})).To(Equal(common.Hash{2})) + Expect(sp.GetCommittedState(alice, common.Hash{3})).To(Equal(common.Hash{})) + }) + + When("state is committed", func() { + BeforeEach(func() { + sp.Finalize() + It("should have committed state", func() { + Expect(sp.GetCommittedState(alice, common.Hash{3})).To(Equal(common.Hash{1})) + }) + It("should maintain committed state", func() { + sp.SetState(alice, common.Hash{3}, common.Hash{4}) + Expect(sp.GetCommittedState(alice, common.Hash{3})). + To(Equal(common.Hash{1})) + Expect(sp.GetState(alice, common.Hash{3})).To(Equal(common.Hash{4})) + }) + }) + }) + }) + + Describe("TestExist", func() { + It("should not exist", func() { + Expect(sp.Exist(alice)).To(BeFalse()) + }) + When("account is created", func() { + BeforeEach(func() { + sp.CreateAccount(alice) + }) + It("should exist", func() { + Expect(sp.Exist(alice)).To(BeTrue()) + }) + // When("suicided", func() { + // BeforeEach(func() { + // // Only contracts can be suicided + // sp.SetCode(alice, []byte("code")) + // Expect(sp.Suicide(alice)).To(BeTrue()) + // }) + // It("should still exist", func() { + // Expect(sp.Exist(alice)).To(BeTrue()) + // }) + // When("commit", func() { + // BeforeEach(func() { + // Expect(sp.Finalize()).To(BeNil()) + // }) + // It("should not exist", func() { + // Expect(sp.Exist(alice)).To(BeFalse()) + // }) + // }) + // }) + }) + }) + + // Describe("TestReset", func() { + // BeforeEach(func() { + // sp.AddRefund(1000) + // sp.AddLog(&coretypes.Log{}) + // sp.Prepare(common.Hash{1}, 3) + + // sp.CreateAccount(alice) + // sp.SetCode(alice, []byte("code")) + // sp.Suicide(alice) + // }) + // It("should have reset state", func() { + // sp.Reset(ctx) + // Expect(sp.GetNonce(alice)).To(Equal(uint64(0))) + // Expect(sp.Logs()).To(BeNil()) + // Expect(sp.GetRefund()).To(Equal(uint64(0))) + // Expect(sp.GetSavedErr()).To(BeNil()) + // Expect(sp.HasSuicided(alice)).To(BeFalse()) + // // TODO: check the txhash and blockhash stuff + // Expect(sp, state.NewStateDB(ctx, ak, bk, testutil.EvmKey, "bera")) + // }) + // }) + + // Describe("TestEmpty", func() { + // When("account does not exist", func() { + // It("should return true", func() { + // Expect(sp.Empty(alice)).To(BeTrue()) + // }) + // }) + // When("account exists", func() { + // BeforeEach(func() { + // sp.CreateAccount(alice) + // }) + // It("new account", func() { + // Expect(sp.Empty(alice)).To(BeTrue()) + // }) + // It("has a balance", func() { + // sp.AddBalance(alice, big.NewInt(1)) + // Expect(sp.Empty(alice)).To(BeFalse()) + // }) + // It("has a nonce", func() { + // sp.SetNonce(alice, 1) + // Expect(sp.Empty(alice)).To(BeFalse()) + // }) + // It("has code", func() { + // sp.SetCode(alice, []byte{0x69}) + // Expect(sp.Empty(alice)).To(BeFalse()) + // }) + // }) + // }) + + Describe("Test ForEachStorage", func() { + // initialAliceBal := big.NewInt(69) + // initialBobBal := big.NewInt(420) + // bobCode := []byte("bobcode") + + BeforeEach(func() { + sp.CreateAccount(alice) + sp.CreateAccount(bob) + }) + + // It("cannot suicide eoa", func() { + // Expect(sp.Suicide(alice)).To(BeFalse()) + // Expect(sp.HasSuicided(alice)).To(BeFalse()) + // }) + + It("should iterate through storage correctly", func() { + Expect(sp.GetCode(alice)).To(BeNil()) + var aliceStorage storage.Storage + err := sp.ForEachStorage(alice, + func(key, value common.Hash) bool { + aliceStorage = append(aliceStorage, + storage.NewSlot(key, value)) + return true + }) + Expect(err).To(BeNil()) + Expect(len(aliceStorage)).To(BeZero()) + + sp.SetState(bob, common.BytesToHash([]byte{1}), common.BytesToHash([]byte{2})) + var bobStorage storage.Storage + err = sp.ForEachStorage(bob, + func(key, value common.Hash) bool { + bobStorage = append(bobStorage, storage.NewSlot(key, value)) + return true + }) + Expect(err).To(BeNil()) + Expect(len(bobStorage)).To(Equal(1)) + Expect(bobStorage[0].Key). + To(Equal("0x0000000000000000000000000000000000000000000000000000000000000001")) + Expect(bobStorage[0].Value). + To(Equal("0x0000000000000000000000000000000000000000000000000000000000000002")) + + sp.SetState(bob, common.BytesToHash([]byte{3}), common.BytesToHash([]byte{4})) + var bobStorage2 storage.Storage + i := 0 + err = sp.ForEachStorage(bob, + func(key, value common.Hash) bool { + if i > 0 { + return false + } + + bobStorage2 = append(bobStorage2, storage.NewSlot(key, value)) + i++ + return true + }, + ) + Expect(err).To(BeNil()) + Expect(len(bobStorage2)).To(Equal(1)) + }) + }) + + Describe("Test Delete Suicides", func() { + aliceCode := []byte("alicecode") + + BeforeEach(func() { + sp.CreateAccount(alice) + sp.SetCode(alice, aliceCode) + sp.SetState(alice, common.BytesToHash([]byte{1}), common.BytesToHash([]byte{2})) + }) + + It("should remove storage/codehash/acct", func() { + sp.DeleteSuicides([]common.Address{alice, alice}) + Expect(ak.HasAccount(ctx, alice[:])).To(BeFalse()) + Expect(sp.GetCode(alice)).To(BeNil()) + Expect(sp.GetState(alice, common.BytesToHash([]byte{1}))).To(Equal(common.Hash{})) + }) + }) + + // When("address has code and balance", func() { + // BeforeEach(func() { + // sp.SetCode(alice, aliceCode) + // sp.SetCode(bob, bobCode) + // sp.AddBalance(alice, initialAliceBal) + // sp.AddBalance(bob, initialBobBal) + // // Give Alice some state + // for i := 0; i < 5; i++ { + // sp.SetState(alice, common.BytesToHash([]byte(fmt.Sprintf("key%d", i))), + // common.BytesToHash([]byte(fmt.Sprintf("value%d", i)))) + // } + // // Give Bob some state + // for i := 5; i < 15; i++ { + // sp.SetState(bob, common.BytesToHash([]byte(fmt.Sprintf("key%d", i))), + // common.BytesToHash([]byte(fmt.Sprintf("value%d", i)))) + // } + // }) + // When("alice commits suicide", func() { + // BeforeEach(func() { + // Expect(sp.Suicide(alice)).To(BeTrue()) + // Expect(sp.HasSuicided(alice)).To(BeTrue()) + // }) + // It("alice should be marked as suicidal, but not bob", func() { + // Expect(sp.HasSuicided(alice)).To(BeTrue()) + // Expect(sp.HasSuicided(bob)).To(BeFalse()) + // }) + // It("alice should have her balance set to 0", func() { + // Expect(sp.GetBalance(alice)).To(Equal(new(big.Int))) + // Expect(sp.GetBalance(bob)).To(Equal(initialBobBal)) + // }) + // It("both alice and bob should have their code and state untouched", func() { + // Expect(sp.GetCode(alice)).To(Equal(aliceCode)) + // Expect(sp.GetCode(bob)).To(Equal(bobCode)) + // for i := 0; i < 5; i++ { + // Expect(sp.GetState(alice, + // common.BytesToHash([]byte(fmt.Sprintf("key%d", i))))). + // To(Equal(common.BytesToHash([]byte(fmt.Sprintf("value%d", i))))) + // } + + // for i := 5; i < 15; i++ { + // Expect(sp.GetState(bob, + // common.BytesToHash([]byte(fmt.Sprintf("key%d", i))))). + // To(Equal(common.BytesToHash([]byte(fmt.Sprintf("value%d", i))))) + // } + // }) + // }) + // }) + + Describe("TestAccount", func() { + It("account does not exist", func() { + Expect(sp.Exist(alice)).To(BeFalse()) + // Expect(sp.Empty(alice)).To(BeTrue()) + Expect(sp.GetBalance(alice)).To(Equal(new(big.Int))) + Expect(sp.GetNonce(alice)).To(BeZero()) + Expect(sp.GetCodeHash(alice)).To(Equal(common.Hash{})) + Expect(sp.GetCode(alice)).To(BeNil()) + Expect(sp.GetCodeSize(alice)).To(BeZero()) + Expect(sp.GetState(alice, common.Hash{})).To(Equal(common.Hash{})) + // Expect(sp.GetRefund()).To(BeZero()) + Expect(sp.GetCommittedState(alice, common.Hash{})).To(Equal(common.Hash{})) + }) + When("account exists", func() { + BeforeEach(func() { + sp.AddBalance(alice, big.NewInt(56)) + sp.SetNonce(alice, 59) + }) + It("accidental override account", func() { + // override + sp.CreateAccount(alice) + + // check balance is not reset + Expect(sp.GetBalance(alice)).To(Equal(big.NewInt(56))) + }) + }) + }) + + Describe("TestSnapshot", func() { + key := common.BytesToHash([]byte("key")) + value1 := common.BytesToHash([]byte("value1")) + value2 := common.BytesToHash([]byte("value2")) + It("simple revert", func() { + revision := sp.Snapshot() + Expect(revision).To(Equal(0)) + sp.SetState(alice, key, value1) + Expect(sp.GetState(alice, key)).To(Equal(value1)) + sp.RevertToSnapshot(revision) + Expect(sp.GetState(alice, key)).To(Equal(common.Hash{})) + }) + It("nested snapshot & revert", func() { + revision1 := sp.Snapshot() + Expect(revision1).To(Equal(0)) + sp.SetState(alice, key, value1) + revision2 := sp.Snapshot() + Expect(revision2).To(Equal(1)) + sp.SetState(alice, key, value2) + Expect(sp.GetState(alice, key)).To(Equal(value2)) + + sp.RevertToSnapshot(revision2) + Expect(sp.GetState(alice, key)).To(Equal(value1)) + + sp.RevertToSnapshot(revision1) + Expect(sp.GetState(alice, key)).To(Equal(common.Hash{})) + }) + It("jump revert", func() { + revision1 := sp.Snapshot() + Expect(revision1).To(Equal(0)) + sp.SetState(alice, key, value1) + sp.Snapshot() + sp.SetState(alice, key, value2) + Expect(sp.GetState(alice, key)).To(Equal(value2)) + sp.RevertToSnapshot(revision1) + Expect(sp.GetState(alice, key)).To(Equal(common.Hash{})) + }) + }) + + // Describe("Test Refund", func() { + // It("simple refund", func() { + // sp.AddRefund(10) + // Expect(sp.GetRefund()).To(Equal(uint64(10))) + // sp.AddRefund(200) + // Expect(sp.GetRefund()).To(Equal(uint64(210))) + // }) + + // It("nested refund", func() { + // sp.AddRefund(uint64(10)) + // sp.SubRefund(uint64(5)) + // Expect(sp.GetRefund()).To(Equal(uint64(5))) + // }) + + // It("negative refund", func() { + // sp.AddRefund(5) + // Expect(func() { sp.SubRefund(10) }).To(Panic()) + // }) + // }) + // Describe("Test Log", func() { + // txHash := common.BytesToHash([]byte("tx")) + // blockHash := common.BytesToHash([]byte("block")) + // data := []byte("bing bong bananas") + // blockNumber := uint64(13) + + // BeforeEach(func() { + // sp.Prepare(txHash, 0) + // }) + // When("We add a log to the state", func() { + // BeforeEach(func() { + + // sp.AddLog(&coretypes.Log{ + // Address: alice, + // Topics: []common.Hash{}, + // Data: data, + // BlockNumber: blockNumber, + // TxHash: txHash, + // TxIndex: 0, + // BlockHash: blockHash, + // Index: 0, + // Removed: false, + // }) + // }) + // It("should have the correct log", func() { + // logs := sp.GetLogs(txHash, blockHash) + // Expect(logs).To(HaveLen(1)) + // Expect(logs[0].Address).To(Equal(alice)) + // Expect(logs[0].Data).To(Equal(data)) + // Expect(logs[0].BlockNumber).To(Equal(blockNumber)) + // Expect(logs[0].TxHash).To(Equal(txHash)) + // Expect(logs[0].TxIndex).To(Equal(uint(0))) + // Expect(logs[0].BlockHash).To(Equal(blockHash)) + // Expect(logs[0].Index).To(Equal(uint(0))) + // Expect(logs[0].Removed).To(BeFalse()) + // }) + // When("we add a second log", func() { + // BeforeEach(func() { + // sp.AddLog(&coretypes.Log{ + // Address: alice, + // Topics: []common.Hash{}, + // Data: data, + // BlockNumber: blockNumber, + // TxHash: txHash, + // TxIndex: 0, + // BlockHash: blockHash, + // Index: 1, + // Removed: false, + // }) + // }) + // It("should have the correct logs", func() { + // logs := sp.GetLogs(txHash, blockHash) + // Expect(logs).To(HaveLen(2)) + // Expect(logs[1].Address).To(Equal(alice)) + // Expect(logs[1].Data).To(Equal(data)) + // Expect(logs[1].BlockNumber).To(Equal(blockNumber)) + // Expect(logs[1].TxHash).To(Equal(txHash)) + // Expect(logs[1].TxIndex).To(Equal(uint(0))) + // Expect(logs[1].BlockHash).To(Equal(blockHash)) + // Expect(logs[1].Index).To(Equal(uint(1))) + // Expect(logs[1].Removed).To(BeFalse()) + // }) + // }) + // }) + + Describe("TestRevertSnapshot", func() { + key := common.BytesToHash([]byte("key")) + value := common.BytesToHash([]byte("value")) + + When("We make a bunch of arbitrary changes", func() { + BeforeEach(func() { + sp.SetNonce(alice, 1) + sp.AddBalance(alice, big.NewInt(100)) + sp.SetCode(alice, []byte("hello world")) + sp.SetState(alice, key, value) + sp.SetNonce(bob, 1) + }) + When("we take a snapshot", func() { + var revision int + BeforeEach(func() { + revision = sp.Snapshot() + }) + When("we do more changes", func() { + AfterEach(func() { + sp.RevertToSnapshot(revision) + Expect(sp.GetNonce(alice)).To(Equal(uint64(1))) + Expect(sp.GetBalance(alice)).To(Equal(big.NewInt(100))) + Expect(sp.GetCode(alice)).To(Equal([]byte("hello world"))) + Expect(sp.GetState(alice, key)).To(Equal(value)) + Expect(sp.GetNonce(bob)).To(Equal(uint64(1))) + }) + + It("if we change balance", func() { + sp.AddBalance(alice, big.NewInt(100)) + }) + + It("if we change nonce", func() { + sp.SetNonce(alice, 2) + }) + + It("if we change code", func() { + sp.SetCode(alice, []byte("goodbye world")) + }) + + It("if we change state", func() { + sp.SetState(alice, key, common.Hash{}) + }) + + It("if we change nonce of another account", func() { + sp.SetNonce(bob, 2) + }) + }) + + When("we make a nested snapshot", func() { + var revision2 int + BeforeEach(func() { + sp.SetState(alice, key, common.Hash{2}) + revision2 = sp.Snapshot() + }) + When("we revert to snapshot ", (func() { + It("revision 2", func() { + sp.RevertToSnapshot(revision2) + Expect(sp.GetState(alice, key)).To(Equal(common.Hash{2})) + }) + It("revision 1", func() { + sp.RevertToSnapshot(revision) + Expect(sp.GetState(alice, key)).To(Equal(value)) + }) + })) + }) + }) + When("we revert to an invalid snapshot", func() { + It("should panic", func() { + Expect(func() { + sp.RevertToSnapshot(100) + }).To(Panic()) + }) + }) + }) + }) + }) +}) diff --git a/x/evm/plugins/state/store/journal/cache_entry.go b/x/evm/plugin/state/state_test.go similarity index 75% rename from x/evm/plugins/state/store/journal/cache_entry.go rename to x/evm/plugin/state/state_test.go index dfbaa43a4..862c058de 100644 --- a/x/evm/plugins/state/store/journal/cache_entry.go +++ b/x/evm/plugin/state/state_test.go @@ -12,15 +12,16 @@ // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -package journal +package state_test -import libtypes "github.com/berachain/stargazer/lib/types" +import ( + "testing" -// `CacheEntry` is an interface for journal entries. -type CacheEntry interface { - // `CacheEntry` implements `Cloneable`. - libtypes.Cloneable[CacheEntry] + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) - // `Revert` undoes the changes made by the entry. - Revert() +func TestState(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "x/evm/plugin/state") } diff --git a/x/evm/plugins/state/storage/errors.go b/x/evm/plugin/state/storage/errors.go similarity index 100% rename from x/evm/plugins/state/storage/errors.go rename to x/evm/plugin/state/storage/errors.go diff --git a/x/evm/plugins/state/storage/slot.go b/x/evm/plugin/state/storage/slot.go similarity index 86% rename from x/evm/plugins/state/storage/slot.go rename to x/evm/plugin/state/storage/slot.go index 0da7bad43..41e7af8a9 100644 --- a/x/evm/plugins/state/storage/slot.go +++ b/x/evm/plugin/state/storage/slot.go @@ -24,19 +24,21 @@ import ( ) // Compile-time interface assertions. -var _ libtypes.Cloneable[Slot] = &Slot{} -var _ fmt.Stringer = Slots{} +var ( + _ libtypes.Cloneable[*Slot] = (*Slot)(nil) + _ fmt.Stringer = (*Slot)(nil) +) // `NewSlot` creates a new State instance. -func NewSlot(key, value common.Hash) Slot { - return Slot{ +func NewSlot(key, value common.Hash) *Slot { + return &Slot{ Key: key.Hex(), Value: value.Hex(), } } // `ValidateBasic` checks to make sure the key is not empty. -func (s Slot) ValidateBasic() error { +func (s *Slot) ValidateBasic() error { if strings.TrimSpace(s.Key) == "" { return errors.Wrapf(ErrInvalidState, "key cannot be empty %s", s.Key) } @@ -45,8 +47,8 @@ func (s Slot) ValidateBasic() error { } // `Clone` implements `types.Cloneable`. -func (s Slot) Clone() Slot { - return Slot{ +func (s *Slot) Clone() *Slot { + return &Slot{ Key: s.Key, Value: s.Value, } diff --git a/x/evm/plugins/state/storage/slot.pb.go b/x/evm/plugin/state/storage/slot.pb.go similarity index 100% rename from x/evm/plugins/state/storage/slot.pb.go rename to x/evm/plugin/state/storage/slot.pb.go diff --git a/x/evm/plugins/state/storage/slot_test.go b/x/evm/plugin/state/storage/slot_test.go similarity index 94% rename from x/evm/plugins/state/storage/slot_test.go rename to x/evm/plugin/state/storage/slot_test.go index 18e1961a6..656f5a619 100644 --- a/x/evm/plugins/state/storage/slot_test.go +++ b/x/evm/plugin/state/storage/slot_test.go @@ -18,13 +18,13 @@ import ( "math/rand" "github.com/berachain/stargazer/lib/common" - "github.com/berachain/stargazer/x/evm/plugins/state/storage" + "github.com/berachain/stargazer/x/evm/plugin/state/storage" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) -var _ = Describe("x/evm/plugins/state/storage", func() { - var slot storage.Slot +var _ = Describe("x/evm/plugin/state/storage", func() { + var slot *storage.Slot key := common.Hash{}.Bytes() value := common.Hash{}.Bytes() diff --git a/x/evm/plugins/state/storage/storage.go b/x/evm/plugin/state/storage/storage.go similarity index 84% rename from x/evm/plugins/state/storage/storage.go rename to x/evm/plugin/state/storage/storage.go index f88cef578..355d8865c 100644 --- a/x/evm/plugins/state/storage/storage.go +++ b/x/evm/plugin/state/storage/storage.go @@ -22,17 +22,17 @@ import ( ) // Compile-time type assertions. -var _ libtypes.Cloneable[Slots] = Slots{} -var _ fmt.Stringer = Slots{} +var _ libtypes.Cloneable[Storage] = Storage{} +var _ fmt.Stringer = Storage{} -// `Storage` represents the account Storage map as a slice of single key value -// State pairs. This helps to ensure that the Storage map can be iterated over +// `Storage` represents the account Storage map as a slice of single key-value +// Slot pairs. This helps to ensure that the Storage map can be iterated over // deterministically. -type Slots []Slot +type Storage []*Slot // `ValidateBasic` performs basic validation of the Storage data structure. // It checks for duplicate keys and calls `ValidateBasic` on each `State`. -func (s Slots) ValidateBasic() error { +func (s Storage) ValidateBasic() error { seenSlots := make(map[string]bool) for i, slot := range s { if seenSlots[slot.Key] { @@ -49,7 +49,7 @@ func (s Slots) ValidateBasic() error { } // `String` implements `fmt.Stringer`. -func (s Slots) String() string { +func (s Storage) String() string { var str string for _, slot := range s { str += fmt.Sprintf("%s\n", slot.String()) @@ -59,8 +59,8 @@ func (s Slots) String() string { } // `Clone` implements `types.Cloneable`. -func (s Slots) Clone() Slots { - cpy := make(Slots, len(s)) +func (s Storage) Clone() Storage { + cpy := make(Storage, len(s)) copy(cpy, s) return cpy diff --git a/x/evm/plugins/state/storage/storage_test.go b/x/evm/plugin/state/storage/storage_test.go similarity index 90% rename from x/evm/plugins/state/storage/storage_test.go rename to x/evm/plugin/state/storage/storage_test.go index c260cdea0..3025e26df 100644 --- a/x/evm/plugins/state/storage/storage_test.go +++ b/x/evm/plugin/state/storage/storage_test.go @@ -15,24 +15,31 @@ package storage_test import ( + "testing" + "github.com/berachain/stargazer/lib/common" - "github.com/berachain/stargazer/x/evm/plugins/state/storage" + "github.com/berachain/stargazer/x/evm/plugin/state/storage" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) +func TestStorage(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "x/evm/plugin/state/storage") +} + var _ = Describe("StorageTest", func() { When("storage is empty", func() { It("should not return an error", func() { - slots := storage.Slots{} + slots := storage.Storage{} Expect(slots.ValidateBasic()).To(BeNil()) }) }) When("storage is not empty", func() { - var slots storage.Slots + var slots storage.Storage BeforeEach(func() { - slots = storage.Slots{ + slots = storage.Storage{ storage.NewSlot(common.BytesToHash([]byte{1, 2, 3}), common.BytesToHash([]byte{1, 2, 3})), } }) diff --git a/x/evm/plugins/state/cache_entries.go b/x/evm/plugins/state/cache_entries.go deleted file mode 100644 index 1779e2b3e..000000000 --- a/x/evm/plugins/state/cache_entries.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (C) 2022, Berachain Foundation. All rights reserved. -// See the file LICENSE for licensing terms. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -//nolint:ireturn // all `CacheEntries` must adhere to the same interface. -package state - -import ( - "github.com/berachain/stargazer/x/evm/plugins/state/store/journal" -) - -var ( - _ journal.CacheEntry = &RefundChange{} - _ journal.CacheEntry = &AddLogChange{} -) - -type ( - AddLogChange struct { - sdb *StateDB - } - RefundChange struct { - sdb *StateDB - prev uint64 - } -) - -// ============================================================================== -// AddLogChange -// ============================================================================== - -// `Revert` implements `journal.CacheEntry`. -func (ce *AddLogChange) Revert() { - ce.sdb.logs = ce.sdb.logs[:len(ce.sdb.logs)-1] -} - -// `Clone` implements `journal.CacheEntry`. -func (ce *AddLogChange) Clone() journal.CacheEntry { - return &AddLogChange{ - sdb: ce.sdb, - } -} - -// ============================================================================== -// RefundChange -// ============================================================================== - -// `Revert` implements `journal.CacheEntry`. -func (ce *RefundChange) Revert() { - ce.sdb.refund = ce.prev -} - -// `Clone` implements `journal.CacheEntry`. -func (ce *RefundChange) Clone() journal.CacheEntry { - return &RefundChange{ - sdb: ce.sdb, - prev: ce.prev, - } -} diff --git a/x/evm/plugins/state/cache_entries_test.go b/x/evm/plugins/state/cache_entries_test.go deleted file mode 100644 index d80f15800..000000000 --- a/x/evm/plugins/state/cache_entries_test.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (C) 2023, Berachain Foundation. All rights reserved. -// See the file LICENSE for licensing terms. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package state - -import ( - coretypes "github.com/berachain/stargazer/eth/core/types" - "github.com/berachain/stargazer/x/evm/plugins/state/store/journal" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("AddLogChange", func() { - var ( - ce *AddLogChange - sdb *StateDB - ) - BeforeEach(func() { - sdb = &StateDB{ - logs: []*coretypes.Log{}, - } - ce = &AddLogChange{ - sdb: sdb, - } - }) - It("implements journal.CacheEntry", func() { - var _ journal.CacheEntry = ce - Expect(ce).To(BeAssignableToTypeOf(&AddLogChange{})) - }) - It("Revert should remove the last log", func() { - sdb.logs = append(sdb.logs, &coretypes.Log{}) - ce.Revert() - Expect(len(sdb.logs)).To(Equal(0)) - }) - It("Clone should return a new AddLogChange with the same sdb", func() { - cloned, ok := ce.Clone().(*AddLogChange) - Expect(ok).To(BeTrue()) - Expect(cloned.sdb).To(Equal(sdb)) - Expect(cloned).ToNot(BeIdenticalTo(ce)) - }) -}) - -var _ = Describe("RefundChange", func() { - var ( - ce *RefundChange - sdb *StateDB - ) - - BeforeEach(func() { - sdb = &StateDB{ - refund: 0, - } - ce = &RefundChange{ - sdb: sdb, - prev: 0, - } - }) - It("implements journal.CacheEntry", func() { - var _ journal.CacheEntry = ce - Expect(ce).To(BeAssignableToTypeOf(&RefundChange{})) - }) - It("Revert should restore the previous refund value", func() { - sdb.refund = 100 - ce.prev = 50 - ce.Revert() - Expect(sdb.refund).To(Equal(uint64(50))) - }) - It("Clone should return a new RefundChange with the same sdb and prev", func() { - cloned, ok := ce.Clone().(*RefundChange) - Expect(ok).To(BeTrue()) - Expect(cloned.sdb).To(Equal(sdb)) - Expect(cloned.prev).To(Equal(ce.prev)) - Expect(cloned).ToNot(BeIdenticalTo(ce)) - }) -}) diff --git a/x/evm/plugins/state/statedb.go b/x/evm/plugins/state/statedb.go deleted file mode 100644 index 75a960cf1..000000000 --- a/x/evm/plugins/state/statedb.go +++ /dev/null @@ -1,617 +0,0 @@ -// Copyright (C) 2022, Berachain Foundation. All rights reserved. -// See the file LICENSE for licensing terms. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package state - -import ( - "bytes" - "fmt" - "math/big" - - storetypes "github.com/cosmos/cosmos-sdk/store/types" - sdk "github.com/cosmos/cosmos-sdk/types" - - coretypes "github.com/berachain/stargazer/eth/core/types" - "github.com/berachain/stargazer/lib/common" - "github.com/berachain/stargazer/lib/crypto" - "github.com/berachain/stargazer/lib/utils" - "github.com/berachain/stargazer/x/evm/constants" - "github.com/berachain/stargazer/x/evm/plugins/state/store/cachekv" - "github.com/berachain/stargazer/x/evm/plugins/state/store/cachemulti" -) - -var ( - // EmptyCodeHash is the code hash of an empty code - // 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470. - emptyCodeHash = crypto.Keccak256Hash(nil) - emptyCodeHashBytes = emptyCodeHash.Bytes() -) - -// Compile-time assertion to ensure StateDB adheres to StargazerStateDB. -// var _ vm.StargazerStateDB = (*StateDB)(nil) - -// The StateDB is a very fun and interesting part of the EVM implementation. But if you want to -// join circus you need to know the rules. So here thet are: -// -// 1. You must ensure that the StateDB is only ever used in a single thread. This is because the -// StateDB is not thread safe. And there are a bunch of optimizations made that are only safe -// to do in a single thread. -// 2. You must discard the StateDB after use. -// 3. When accessing or mutating the StateDB, you must ensure that the underlying account exists. -// in the AccountKeeper, for performance reasons, this implementation of the StateDB will not -// create accounts that do not exist. Notably calling `SetState()` on an account that does not -// exist is completely possible, and the StateDB will not prevent you doing so. This lazy -// creation improves performance a ton, as it prevents calling into the ak on -// every SSTORE. The only accounts that should ever have `SetState()` called on them are -// accounts that represent smart contracts. Because of this assumption, the only place that we -// explicitly create accounts is in `CreateAccount()`, since `CreateAccount()` is called when -// deploying a smart contract. -// 4. Accounts that are sent `evmDenom` coins during an eth transaction, will have an account -// created for them, automatically by the Bank Module. However, these accounts will have a -// codeHash of 0x000... This is because the Bank Module does not know that the account is an -// EVM account, and so it does not set the codeHash. This is totally fine, we just need to -// check both for both the codeHash being 0x000... as well as the codeHash being 0x567... -type StateDB struct { //nolint: revive // we like the vibe. - // We maintain a context in the StateDB, so that we can pass it with the correctly - // configured multi-store to the precompiled contracts. - ctx sdk.Context - - // Store a reference to the multi-store, in `ctx` so that we can access it directly. - cms *cachemulti.Store - - // eth state stores: required for vm.StateDB - // We store references to these stores, so that we can access them - // directly, without having to go through the MultiStore interface. - ethStore cachekv.StateDBCacheKVStore - - // keepers used for balance and account information. - ak AccountKeeper - bk BankKeeper - - // Any error that occurs during an sdk module read or write is - // memoized here and is eventually be returned by `Commit`. - savedErr error - - // we load the evm denom in the constructor, to prevent going to - // the params to get it mid interpolation. - evmDenom string // TODO: get from params ( we have a store so like why not ) - - // The refund counter, also used by state transitioning. - refund uint64 - - // The storekey used during execution - storeKey storetypes.StoreKey - - // Per-transaction logs - logs []*coretypes.Log - - // Transaction and logging bookkeeping - txHash common.Hash - blockHash common.Hash - txIndex uint - logIndex uint - - // Dirty tracking of suicided accounts, we have to keep track of these manually, in order - // for the code and state to still be accessible even after the account has been deleted. - // We chose to keep track of them in a separate slice, rather than a map, because the - // number of accounts that will be suicided in a single transaction is expected to be - // very low. - suicides []common.Address -} - -// returns a *StateDB using the MultiStore belonging to ctx. -func NewSlotDB( - ctx sdk.Context, - ak AccountKeeper, - bk BankKeeper, - storeKey storetypes.StoreKey, - evmDenom string, -) *StateDB { - sdb := &StateDB{ - ak: ak, - bk: bk, - evmDenom: evmDenom, - storeKey: storeKey, - } - - // Wire up the `CacheMultiStore` & `sdk.Context`. - sdb.cms = cachemulti.NewStoreFrom(ctx.MultiStore()) - sdb.ctx = ctx.WithMultiStore(sdb.cms) - - // Store a reference to the EVM state store for performance reasons. - sdb.ethStore, _ = utils.GetAs[cachekv.StateDBCacheKVStore](sdb.cms.GetKVStore(sdb.storeKey)) - - return sdb -} - -// =========================================================================== -// Account -// =========================================================================== - -// CreateAccount implements the GethStateDB interface by creating a new account -// in the account keeper. It will allow accounts to be overridden. -func (sdb *StateDB) CreateAccount(addr common.Address) { - acc := sdb.ak.NewAccountWithAddress(sdb.ctx, addr[:]) - - // save the new account in the account keeper - sdb.ak.SetAccount(sdb.ctx, acc) - - // initialize the code hash to empty - sdb.ethStore.Set(CodeHashKeyFor(addr), emptyCodeHashBytes) -} - -// ============================================================================= -// Transaction Handling -// ============================================================================= - -func (sdb *StateDB) Prepare(txHash common.Hash, ti uint) {} - -// PrepareForTransition sets the current transaction hash and index and block hash which is -// used for logging events. -func (sdb *StateDB) PrepareForTransition(blockHash, txHash common.Hash, ti, li uint) { - sdb.blockHash = blockHash - sdb.txHash = txHash - sdb.txIndex = ti - sdb.logIndex = li -} - -// Reset clears the journal and other state objects. It also clears the -// refund counter and the access list. -func (sdb *StateDB) Reset(ctx sdk.Context) { - // TODO: figure out why not fully reallocating the object causes - // the gas shit to fail - // sdb.MultiStore = cachemulti.NewStoreFrom(ctx.MultiStore()) - // sdb.ctx = ctx.WithMultiStore(sdb.MultiStore) - // // // Must support directly accessing the parent store. - // // sdb.ethStore = sdb.ctx.cms. - // // GetKVStore(sdb.storeKey).(cachekv.StateDBCacheKVStore) - // sdb.savedErr = nil - // sdb.refund = 0 - - // sdb.logs = make([]*coretypes.Log, 0) - // sdb.accessList = newAccessList() - // sdb.suicides = make([]common.Address, 0) - // TODO: unghetto this - *sdb = *NewSlotDB(ctx, sdb.ak, sdb.bk, sdb.storeKey, sdb.evmDenom) -} - -// ============================================================================= -// Balance -// ============================================================================= - -// GetBalance implements `GethStateDB` interface. -func (sdb *StateDB) GetBalance(addr common.Address) *big.Int { - // Note: bank keeper will return 0 if account/state_object is not found - return sdb.bk.GetBalance(sdb.ctx, addr[:], sdb.evmDenom).Amount.BigInt() -} - -// AddBalance implements the `GethStateDB` interface by adding the given amount -// from the account associated with addr. If the account does not exist, it will be -// created. -func (sdb *StateDB) AddBalance(addr common.Address, amount *big.Int) { - coins := sdk.NewCoins(sdk.NewCoin(sdb.evmDenom, sdk.NewIntFromBigInt(amount))) - - // Mint the coins to the evm module account - if err := sdb.bk.MintCoins(sdb.ctx, constants.EvmNamespace, coins); err != nil { - sdb.setErrorUnsafe(err) - return - } - - // Send the coins from the evm module account to the destination address. - if err := sdb.bk.SendCoinsFromModuleToAccount(sdb.ctx, constants.EvmNamespace, addr[:], coins); err != nil { - sdb.setErrorUnsafe(err) - } -} - -// SubBalance implements the `GethStateDB` interface by subtracting the given amount -// from the account associated with addr. -func (sdb *StateDB) SubBalance(addr common.Address, amount *big.Int) { - coins := sdk.NewCoins(sdk.NewCoin(sdb.evmDenom, sdk.NewIntFromBigInt(amount))) - - // Send the coins from the source address to the evm module account. - if err := sdb.bk.SendCoinsFromAccountToModule(sdb.ctx, addr[:], constants.EvmNamespace, coins); err != nil { - sdb.setErrorUnsafe(err) - return - } - - // Burn the coins from the evm module account. - if err := sdb.bk.BurnCoins(sdb.ctx, constants.EvmNamespace, coins); err != nil { - sdb.setErrorUnsafe(err) - return - } -} - -// `TransferBalance` sends the given amount from one account to another. It will -// error if the sender does not have enough funds to send. -func (sdb *StateDB) TransferBalance(from, to common.Address, amount *big.Int) { - coins := sdk.NewCoins(sdk.NewCoin(sdb.evmDenom, sdk.NewIntFromBigInt(amount))) - - // Send the coins from the source address to the destination address. - if err := sdb.bk.SendCoins(sdb.ctx, from[:], to[:], coins); err != nil { - sdb.setErrorUnsafe(err) - } -} - -// ============================================================================= -// Nonce -// ============================================================================= - -// GetNonce implements the `GethStateDB` interface by returning the nonce -// of an account. -func (sdb *StateDB) GetNonce(addr common.Address) uint64 { - acc := sdb.ak.GetAccount(sdb.ctx, addr[:]) - if acc == nil { - return 0 - } - return acc.GetSequence() -} - -// SetNonce implements the `GethStateDB` interface by setting the nonce -// of an account. -func (sdb *StateDB) SetNonce(addr common.Address, nonce uint64) { - // get the account or create a new one if doesn't exist - acc := sdb.ak.GetAccount(sdb.ctx, addr[:]) - if acc == nil { - acc = sdb.ak.NewAccountWithAddress(sdb.ctx, addr[:]) - } - - if err := acc.SetSequence(nonce); err != nil { - sdb.setErrorUnsafe(err) - } - - sdb.ak.SetAccount(sdb.ctx, acc) -} - -// ============================================================================= -// Code -// ============================================================================= - -// GetCodeHash implements the `GethStateDB` interface by returning -// the code hash of account. -func (sdb *StateDB) GetCodeHash(addr common.Address) common.Hash { - if sdb.ak.HasAccount(sdb.ctx, addr[:]) { - if ch := sdb.ethStore.Get(CodeHashKeyFor(addr)); ch != nil { - return common.BytesToHash(ch) - } - return emptyCodeHash - } - // if account at addr does not exist, return ZeroCodeHash - return common.Hash{} -} - -// GetCode implements the `GethStateDB` interface by returning -// the code of account (nil if not exists). -func (sdb *StateDB) GetCode(addr common.Address) []byte { - codeHash := sdb.GetCodeHash(addr) - // if account at addr does not exist, GetCodeHash returns ZeroCodeHash so return nil - // if codeHash is empty, i.e. crypto.Keccak256(nil), also return nil - if (codeHash == common.Hash{}) || codeHash == emptyCodeHash { - return nil - } - return sdb.ethStore.Get(CodeKeyFor(codeHash)) -} - -// SetCode implements the `GethStateDB` interface by setting the code hash and -// code for the given account. -func (sdb *StateDB) SetCode(addr common.Address, code []byte) { - codeHash := crypto.Keccak256Hash(code) - - sdb.ethStore.Set(CodeHashKeyFor(addr), codeHash[:]) - // store or delete code - if len(code) == 0 { - sdb.ethStore.Delete(CodeKeyFor(codeHash)) - } else { - sdb.ethStore.Set(CodeKeyFor(codeHash), code) - } -} - -// GetCodeSize implements the `GethStateDB` interface by returning the size of the -// code associated with the given `GethStateDB`. -func (sdb *StateDB) GetCodeSize(addr common.Address) int { - return len(sdb.GetCode(addr)) -} - -// ============================================================================= -// Refund -// ============================================================================= - -// `AddRefund` implements the `GethStateDB` interface by adding gas to the -// refund counter. -func (sdb *StateDB) AddRefund(gas uint64) { - sdb.cms.JournalMgr.Push(&RefundChange{sdb, sdb.refund}) - sdb.refund += gas -} - -// `SubRefund` implements the `GethStateDB` interface by subtracting gas from the -// refund counter. If the gas is greater than the refund counter, it will panic. -func (sdb *StateDB) SubRefund(gas uint64) { - sdb.cms.JournalMgr.Push(&RefundChange{sdb, sdb.refund}) - if gas > sdb.refund { - panic(fmt.Sprintf("Refund counter below zero (gas: %d > refund: %d)", gas, sdb.refund)) - } - sdb.refund -= gas -} - -// `GetRefund` implements the `GethStateDB` interface by returning the current -// value of the refund counter. -func (sdb *StateDB) GetRefund() uint64 { - return sdb.refund -} - -// ============================================================================= -// State -// ============================================================================= - -// `GetCommittedState` implements the `GethStateDB` interface by returning the -// committed state of slot in the given address. -func (sdb *StateDB) GetCommittedState( - addr common.Address, - slot common.Hash, -) common.Hash { - return sdb.getStateFromStore(sdb.ethStore.GetParent(), addr, slot) -} - -// `GetState` implements the `GethStateDB` interface by returning the current state -// of slot in the given address. -func (sdb *StateDB) GetState(addr common.Address, slot common.Hash) common.Hash { - return sdb.getStateFromStore(sdb.ethStore, addr, slot) -} - -// `getStateFromStore` returns the current state of the slot in the given address. -func (sdb *StateDB) getStateFromStore( - store storetypes.KVStore, - addr common.Address, slot common.Hash, -) common.Hash { - if value := store.Get(KeyForSlot(addr, slot)); value != nil { - return common.BytesToHash(value) - } - return common.Hash{} -} - -// `SetState` implements the `GethStateDB` interface by setting the state of an -// address. -func (sdb *StateDB) SetState(addr common.Address, slot, value common.Hash) { - // For performance reasons, we don't check to ensure the account exists before we execute. - // This is reasonably safe since under normal operation, SetState is only ever called by the - // SSTORE opcode in the EVM, which will only ever be called on an account that exists, since - // it would with 100% certainty have been created by a prior Create, thus setting its code - // hash. - // - // CONTRACT: never manually call SetState outside of `opSstore`, or InitGenesis. - - // If empty value is given, delete the state entry. - if len(value) == 0 || (value == common.Hash{}) { - sdb.ethStore.Delete(KeyForSlot(addr, slot)) - return - } - - // Set the state entry. - sdb.ethStore.Set(KeyForSlot(addr, slot), value[:]) -} - -// ============================================================================= -// Suicide -// ============================================================================= - -// Suicide implements the GethStateDB interface by marking the given address as suicided. -// This clears the account balance, but the code and state of the address remains available -// until after Commit is called. -func (sdb *StateDB) Suicide(addr common.Address) bool { - // only smart contracts can commit suicide - ch := sdb.GetCodeHash(addr) - if (ch == common.Hash{}) || ch == emptyCodeHash { - return false - } - - // Reduce it's balance to 0. - bal := sdb.bk.GetBalance(sdb.ctx, addr[:], sdb.evmDenom) - sdb.SubBalance(addr, bal.Amount.BigInt()) - - // Mark the underlying account for deletion in `Commit()`. - sdb.suicides = append(sdb.suicides, addr) - return true -} - -// `HasSuicided` implements the `GethStateDB` interface by returning if the contract was suicided -// in current transaction. -func (sdb *StateDB) HasSuicided(addr common.Address) bool { - for _, suicide := range sdb.suicides { - if bytes.Equal(suicide[:], addr[:]) { - return true - } - } - return false -} - -// ============================================================================= -// Exist & Empty -// ============================================================================= - -// `Exist` implements the `GethStateDB` interface by reporting whether the given account address -// exists in the state. Notably this also returns true for suicided accounts, which is accounted -// for since, `RemoveAccount()` is not called until Commit. -func (sdb *StateDB) Exist(addr common.Address) bool { - return sdb.ak.HasAccount(sdb.ctx, addr[:]) -} - -// `Empty` implements the `GethStateDB` interface by returning whether the state object -// is either non-existent or empty according to the EIP161 specification -// (balance = nonce = code = 0) -// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md -func (sdb *StateDB) Empty(addr common.Address) bool { - ch := sdb.GetCodeHash(addr) - return sdb.GetNonce(addr) == 0 && - (ch == emptyCodeHash || ch == common.Hash{}) && - sdb.GetBalance(addr).Sign() == 0 -} - -// ============================================================================= -// Snapshot -// ============================================================================= - -// `RevertToSnapshot` implements `StateDB`. -func (sdb *StateDB) RevertToSnapshot(id int) { - // revert and discard all journal entries after snapshot id - sdb.cms.JournalMgr.PopToSize(id) -} - -// `Snapshot` implements `StateDB`. -func (sdb *StateDB) Snapshot() int { - return sdb.cms.JournalMgr.Size() -} - -// ============================================================================= -// Logs -// ============================================================================= - -// AddLog implements the GethStateDB interface by adding the given log to the current transaction. -func (sdb *StateDB) AddLog(log *coretypes.Log) { - sdb.cms.JournalMgr.Push(&AddLogChange{sdb}) - log.TxHash = sdb.txHash - log.BlockHash = sdb.blockHash - log.TxIndex = sdb.txIndex - log.Index = sdb.logIndex - sdb.logs = append(sdb.logs, log) - sdb.logIndex++ // erigon intra -} - -// Logs returns the logs of current transaction. -func (sdb *StateDB) GetLogs(_, _ common.Hash) []*coretypes.Log { - return sdb.logs -} - -// ============================================================================= -// ForEachStorage -// ============================================================================= - -// `ForEachStorage` implements the `GethStateDB` interface by iterating through the contract state -// contract storage, the iteration order is not defined. -// -// Note: We do not support iterating through any storage that is modified before calling -// `ForEachStorage`; only committed state is iterated through. -func (sdb *StateDB) ForEachStorage( - addr common.Address, - cb func(key, value common.Hash) bool, -) error { - it := sdk.KVStorePrefixIterator(sdb.ethStore, AddressStoragePrefix(addr)) - defer it.Close() - - for ; it.Valid(); it.Next() { - committedValue := it.Value() - if len(committedValue) > 0 { - if !cb(common.BytesToHash(it.Key()), common.BytesToHash(committedValue)) { - // stop iteration - return nil - } - } - } - - return nil -} - -// `Commit` is called when we are complete with the state transition and want to commit the changes -// to the underlying store. -func (sdb *StateDB) Commit() error { - // If we saw an error during the execution, we return it here. - if sdb.savedErr != nil { - return sdb.savedErr - } - - // Manually delete all suicidal accounts. - for _, suicidalAddr := range sdb.suicides { - acct := sdb.ak.GetAccount(sdb.ctx, suicidalAddr[:]) - if acct == nil { - // handles the double suicide case - continue - } - - // clear storage - if err := sdb.ForEachStorage(suicidalAddr, - func(key, _ common.Hash) bool { - sdb.SetState(suicidalAddr, key, common.Hash{}) - return true - }); err != nil { - return err - } - - // clear the codehash from this account - sdb.ethStore.Delete(CodeHashKeyFor(suicidalAddr)) - - // remove auth account - sdb.ak.RemoveAccount(sdb.ctx, acct) - } - - // write all cache stores to parent stores, effectively writing temporary state in ctx to - // the underlying parent store. - sdb.cms.CacheMultiStore().Write() - return nil -} - -func (sdb *StateDB) Finalize() { - _ = sdb.Commit() -} - -// ============================================================================= -// Saved Errors -// ============================================================================= - -// Any errors that pop up during store operations should be checked here. -// Called upon the conclusion. -func (sdb *StateDB) GetSavedErr() error { - return sdb.savedErr -} - -// `setErrorUnsafe` sets error but should be called in medhods that already have locks. -func (sdb *StateDB) setErrorUnsafe(err error) { - if sdb.savedErr == nil { - sdb.savedErr = err - } -} - -// ============================================================================= -// AccessList -// ============================================================================= - -func (sdb *StateDB) PrepareAccessList( - sender common.Address, - dst *common.Address, - precompiles []common.Address, - list coretypes.AccessList, -) { - panic("not implemented, as accesslists are not valuable in the Cosmos-SDK context") -} - -func (sdb *StateDB) AddAddressToAccessList(addr common.Address) { - panic("not implemented, as accesslists are not valuable in the Cosmos-SDK context") -} - -func (sdb *StateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) { - panic("not implemented, as accesslists are not valuable in the Cosmos-SDK context") -} - -func (sdb *StateDB) AddressInAccessList(addr common.Address) bool { - return false -} - -func (sdb *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (bool, bool) { - return false, false -} - -// ============================================================================= -// PreImage -// ============================================================================= - -// AddPreimage implements the the `StateDB“ interface, but currently -// performs a no-op since the EnablePreimageRecording flag is disabled. -func (sdb *StateDB) AddPreimage(hash common.Hash, preimage []byte) {} diff --git a/x/evm/plugins/state/statedb_test.go b/x/evm/plugins/state/statedb_test.go deleted file mode 100644 index 390089454..000000000 --- a/x/evm/plugins/state/statedb_test.go +++ /dev/null @@ -1,651 +0,0 @@ -// Copyright (C) 2022, Berachain Foundation. All rights reserved. -// See the file LICENSE for licensing terms. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package state_test - -import ( - "errors" - "fmt" - "math/big" - - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - coretypes "github.com/berachain/stargazer/eth/core/types" - "github.com/berachain/stargazer/lib/common" - "github.com/berachain/stargazer/lib/crypto" - "github.com/berachain/stargazer/testutil" - "github.com/berachain/stargazer/x/evm/plugins/state" - "github.com/berachain/stargazer/x/evm/plugins/state/storage" -) - -var alice = testutil.Alice -var bob = testutil.Bob - -var _ = Describe("StateDB", func() { - var ak state.AccountKeeper - var bk state.BankKeeper - var ctx sdk.Context - var sdb *state.StateDB - - BeforeEach(func() { - ctx, ak, bk, _ = testutil.SetupMinimalKeepers() - sdb = state.NewSlotDB(ctx, ak, bk, testutil.EvmKey, "abera") // TODO: use lf - }) - - Describe("TestCreateAccount", func() { - AfterEach(func() { - Expect(sdb.GetSavedErr()).To(BeNil()) - }) - It("should create account", func() { - sdb.CreateAccount(alice) - Expect(sdb.Exist(alice)).To(BeTrue()) - }) - }) - - Describe("TestBalance", func() { - It("should have start with zero balance", func() { - Expect(sdb.GetBalance(alice)).To(Equal(new(big.Int))) - }) - Context("TestAddBalance", func() { - It("should be able to add zero", func() { - Expect(sdb.GetBalance(alice)).To(Equal(new(big.Int))) - sdb.AddBalance(alice, new(big.Int)) - Expect(sdb.GetBalance(alice)).To(Equal(new(big.Int))) - }) - It("should have 100 balance", func() { - sdb.AddBalance(alice, big.NewInt(100)) - Expect(sdb.GetBalance(alice)).To(Equal(big.NewInt(100))) - }) - It("should panic if using negative value", func() { - Expect(func() { - sdb.AddBalance(alice, big.NewInt(-100)) - }).To(Panic()) - }) - }) - - Context("TestSubBalance", func() { - It("should not set balance to negative value", func() { - sdb.SubBalance(alice, big.NewInt(100)) - Expect(sdb.GetSavedErr()).To(HaveOccurred()) - Expect(errors.Unwrap(errors.Unwrap(sdb.GetSavedErr()))).To( - Equal(sdkerrors.ErrInsufficientFunds)) - Expect(sdb.GetBalance(alice)).To(Equal(new(big.Int))) - }) - It("should panic if using negative value", func() { - Expect(func() { - sdb.SubBalance(alice, big.NewInt(-100)) - }).To(Panic()) - }) - }) - - It("should handle complex balance updates", func() { - // Initial balance for alice should be 0 - Expect(sdb.GetBalance(alice)).To(Equal(new(big.Int))) - - // Add some balance to alice - sdb.AddBalance(alice, big.NewInt(100)) - Expect(sdb.GetBalance(alice)).To(Equal(big.NewInt(100))) - - // Subtract some balance from alice - sdb.SubBalance(alice, big.NewInt(50)) - Expect(sdb.GetBalance(alice)).To(Equal(big.NewInt(50))) - - // Add some balance to alice - sdb.AddBalance(alice, big.NewInt(100)) - Expect(sdb.GetBalance(alice)).To(Equal(big.NewInt(150))) - - // Subtract some balance from alice - sdb.SubBalance(alice, big.NewInt(200)) - Expect(sdb.GetSavedErr()).To(HaveOccurred()) - Expect(errors.Unwrap(errors.Unwrap(sdb.GetSavedErr()))).To( - Equal(sdkerrors.ErrInsufficientFunds)) - Expect(sdb.GetBalance(alice)).To(Equal(big.NewInt(150))) - - }) - }) - - Describe("TestNonce", func() { - When("account exists", func() { - BeforeEach(func() { - sdb.CreateAccount(alice) - }) - It("should have start with zero nonce", func() { - Expect(sdb.GetNonce(alice)).To(Equal(uint64(0))) - }) - It("should have 100 nonce", func() { - sdb.SetNonce(alice, 100) - Expect(sdb.GetNonce(alice)).To(Equal(uint64(100))) - }) - }) - When("account does not exist", func() { - It("should have start with zero nonce", func() { - Expect(sdb.GetNonce(alice)).To(Equal(uint64(0))) - }) - - It("should have 100 nonce", func() { - sdb.SetNonce(alice, 100) - Expect(sdb.GetNonce(alice)).To(Equal(uint64(100))) - }) - }) - }) - - Describe("TestCode & CodeHash", func() { - When("account does not exist", func() { - It("should have empty code hash", func() { - Expect(sdb.GetCodeHash(alice)).To(Equal(common.Hash{})) - }) - It("should not have code", func() { // ensure account exists - Expect(sdb.GetCode(alice)).To(BeNil()) - Expect(sdb.GetCodeHash(alice)).To(Equal(common.Hash{})) - }) - It("cannot set code", func() { // ensure account exists - sdb.SetCode(alice, []byte("code")) - Expect(sdb.GetCode(alice)).To(BeNil()) - Expect(sdb.GetCodeHash(alice)).To(Equal(common.Hash{})) - }) - }) - When("account exists", func() { - BeforeEach(func() { - sdb.CreateAccount(alice) - }) - It("should have zero code hash", func() { - Expect(sdb.GetCodeHash(alice)).To(Equal(crypto.Keccak256Hash(nil))) - }) - When("account has code", func() { - BeforeEach(func() { - sdb.SetCode(alice, []byte("code")) - }) - It("should have code", func() { - Expect(sdb.GetCode(alice)).To(Equal([]byte("code"))) - Expect(sdb.GetCodeHash(alice)).To(Equal(crypto.Keccak256Hash([]byte("code")))) - }) - It("should have empty code hash", func() { - sdb.SetCode(alice, nil) - Expect(sdb.GetCode(alice)).To(BeNil()) - Expect(sdb.GetCodeHash(alice)).To(Equal(crypto.Keccak256Hash(nil))) - }) - }) - }) - }) - - Describe("TestRefund", func() { - It("should have 0 refund", func() { - Expect(sdb.GetRefund()).To(Equal(uint64(0))) - }) - It("should have 100 refund", func() { - sdb.AddRefund(100) - Expect(sdb.GetRefund()).To(Equal(uint64(100))) - }) - It("should have 0 refund", func() { - sdb.AddRefund(100) - sdb.SubRefund(100) - Expect(sdb.GetRefund()).To(Equal(uint64(0))) - }) - It("should panic and over refund", func() { - Expect(func() { - sdb.SubRefund(200) - }).To(Panic()) - }) - }) - - Describe("TestState", func() { - It("should have empty state", func() { - Expect(sdb.GetState(alice, common.Hash{3})).To(Equal(common.Hash{})) - }) - When("set basic state", func() { - BeforeEach(func() { - sdb.SetState(alice, common.Hash{3}, common.Hash{1}) - }) - - It("should have state", func() { - Expect(sdb.GetState(alice, common.Hash{3})).To(Equal(common.Hash{1})) - }) - - It("should have state changed", func() { - sdb.SetState(alice, common.Hash{3}, common.Hash{2}) - Expect(sdb.GetState(alice, common.Hash{3})).To(Equal(common.Hash{2})) - Expect(sdb.GetCommittedState(alice, common.Hash{3})).To(Equal(common.Hash{})) - }) - - When("state is committed", func() { - BeforeEach(func() { - Expect(sdb.Commit()).Should(BeNil()) - It("should have committed state", func() { - Expect(sdb.GetCommittedState(alice, common.Hash{3})).To(Equal(common.Hash{1})) - }) - It("should maintain committed state", func() { - sdb.SetState(alice, common.Hash{3}, common.Hash{4}) - Expect(sdb.GetCommittedState(alice, common.Hash{3})). - To(Equal(common.Hash{1})) - Expect(sdb.GetState(alice, common.Hash{3})).To(Equal(common.Hash{4})) - }) - }) - }) - }) - - Describe("TestExist", func() { - It("should not exist", func() { - Expect(sdb.Exist(alice)).To(BeFalse()) - }) - When("account is created", func() { - BeforeEach(func() { - sdb.CreateAccount(alice) - }) - It("should exist", func() { - Expect(sdb.Exist(alice)).To(BeTrue()) - }) - When("suicided", func() { - BeforeEach(func() { - // Only contracts can be suicided - sdb.SetCode(alice, []byte("code")) - Expect(sdb.Suicide(alice)).To(BeTrue()) - }) - It("should still exist", func() { - Expect(sdb.Exist(alice)).To(BeTrue()) - }) - When("commit", func() { - BeforeEach(func() { - Expect(sdb.Commit()).To(BeNil()) - }) - It("should not exist", func() { - Expect(sdb.Exist(alice)).To(BeFalse()) - }) - }) - }) - }) - }) - - Describe("TestReset", func() { - BeforeEach(func() { - sdb.AddRefund(1000) - sdb.AddLog(&coretypes.Log{}) - sdb.PrepareForTransition(common.Hash{1}, common.Hash{2}, 3, 4) - - sdb.CreateAccount(alice) - sdb.SetCode(alice, []byte("code")) - sdb.Suicide(alice) - }) - It("should have reset state", func() { - sdb.Reset(ctx) - Expect(sdb.GetNonce(alice)).To(Equal(uint64(0))) - Expect(sdb.GetLogs(common.Hash{}, common.Hash{})).To(BeNil()) - Expect(sdb.GetRefund()).To(Equal(uint64(0))) - Expect(sdb.GetSavedErr()).To(BeNil()) - Expect(sdb.HasSuicided(alice)).To(BeFalse()) - // TODO: check the txhash and blockhash stuff - Expect(sdb, state.NewSlotDB(ctx, ak, bk, testutil.EvmKey, "bera")) - }) - }) - - Describe("TestEmpty", func() { - When("account does not exist", func() { - It("should return true", func() { - Expect(sdb.Empty(alice)).To(BeTrue()) - }) - }) - When("account exists", func() { - BeforeEach(func() { - sdb.CreateAccount(alice) - }) - It("new account", func() { - Expect(sdb.Empty(alice)).To(BeTrue()) - }) - It("has a balance", func() { - sdb.AddBalance(alice, big.NewInt(1)) - Expect(sdb.Empty(alice)).To(BeFalse()) - }) - It("has a nonce", func() { - sdb.SetNonce(alice, 1) - Expect(sdb.Empty(alice)).To(BeFalse()) - }) - It("has code", func() { - sdb.SetCode(alice, []byte{0x69}) - Expect(sdb.Empty(alice)).To(BeFalse()) - }) - }) - }) - - Describe("TestSuicide", func() { - BeforeEach(func() { - sdb.CreateAccount(alice) - }) - It("cannot suicide eoa", func() { - Expect(sdb.Suicide(alice)).To(BeFalse()) - Expect(sdb.HasSuicided(alice)).To(BeFalse()) - }) - - initialAliceBal := big.NewInt(69) - initialBobBal := big.NewInt(420) - aliceCode := []byte("alicecode") - bobCode := []byte("bobcode") - - When("address has code and balance", func() { - BeforeEach(func() { - sdb.SetCode(alice, aliceCode) - sdb.SetCode(bob, bobCode) - sdb.AddBalance(alice, initialAliceBal) - sdb.AddBalance(bob, initialBobBal) - // Give Alice some state - for i := 0; i < 5; i++ { - sdb.SetState(alice, common.BytesToHash([]byte(fmt.Sprintf("key%d", i))), - common.BytesToHash([]byte(fmt.Sprintf("value%d", i)))) - } - // Give Bob some state - for i := 5; i < 15; i++ { - sdb.SetState(bob, common.BytesToHash([]byte(fmt.Sprintf("key%d", i))), - common.BytesToHash([]byte(fmt.Sprintf("value%d", i)))) - } - }) - When("alice commits suicide", func() { - BeforeEach(func() { - Expect(sdb.Suicide(alice)).To(BeTrue()) - Expect(sdb.HasSuicided(alice)).To(BeTrue()) - }) - It("alice should be marked as suicidal, but not bob", func() { - Expect(sdb.HasSuicided(alice)).To(BeTrue()) - Expect(sdb.HasSuicided(bob)).To(BeFalse()) - }) - It("alice should have her balance set to 0", func() { - Expect(sdb.GetBalance(alice)).To(Equal(new(big.Int))) - Expect(sdb.GetBalance(bob)).To(Equal(initialBobBal)) - }) - It("both alice and bob should have their code and state untouched", func() { - Expect(sdb.GetCode(alice)).To(Equal(aliceCode)) - Expect(sdb.GetCode(bob)).To(Equal(bobCode)) - for i := 0; i < 5; i++ { - Expect(sdb.GetState(alice, - common.BytesToHash([]byte(fmt.Sprintf("key%d", i))))). - To(Equal(common.BytesToHash([]byte(fmt.Sprintf("value%d", i))))) - } - - for i := 5; i < 15; i++ { - Expect(sdb.GetState(bob, - common.BytesToHash([]byte(fmt.Sprintf("key%d", i))))). - To(Equal(common.BytesToHash([]byte(fmt.Sprintf("value%d", i))))) - } - }) - When("commit is called", func() { - BeforeEach(func() { - _ = sdb.Commit() - }) - It("alice should have her code and state wiped, but not bob", func() { - Expect(sdb.GetCode(alice)).To(BeNil()) - Expect(sdb.GetCode(bob)).To(Equal(bobCode)) - var aliceSlots storage.Slots - err := sdb.ForEachStorage(alice, - func(key, value common.Hash) bool { - aliceSlots = append(aliceSlots, - storage.NewSlot(key, value)) - return true - }) - Expect(err).To(BeNil()) - Expect(len(aliceSlots)).To(BeZero()) - - var bobSlots storage.Slots - err = sdb.ForEachStorage(bob, - func(key, value common.Hash) bool { - bobSlots = append(bobSlots, storage.NewSlot(key, value)) - return true - }) - Expect(err).To(BeNil()) - Expect(len(bobSlots)).To(Equal(10)) - }) - }) - - }) - }) - }) - Describe("TestAccount", func() { - It("account does not exist", func() { - Expect(sdb.Exist(alice)).To(BeFalse()) - Expect(sdb.Empty(alice)).To(BeTrue()) - Expect(sdb.GetBalance(alice)).To(Equal(new(big.Int))) - Expect(sdb.GetNonce(alice)).To(BeZero()) - Expect(sdb.GetCodeHash(alice)).To(Equal(common.Hash{})) - Expect(sdb.GetCode(alice)).To(BeNil()) - Expect(sdb.GetCodeSize(alice)).To(BeZero()) - Expect(sdb.GetState(alice, common.Hash{})).To(Equal(common.Hash{})) - Expect(sdb.GetRefund()).To(BeZero()) - Expect(sdb.GetCommittedState(alice, common.Hash{})).To(Equal(common.Hash{})) - }) - When("account exists", func() { - BeforeEach(func() { - sdb.AddBalance(alice, big.NewInt(56)) - sdb.SetNonce(alice, 59) - }) - It("accidental override account", func() { - // override - sdb.CreateAccount(alice) - - // check balance is not reset - Expect(sdb.GetBalance(alice)).To(Equal(big.NewInt(56))) - }) - }) - - }) - - Describe("TestSnapshot", func() { - key := common.BytesToHash([]byte("key")) - value1 := common.BytesToHash([]byte("value1")) - value2 := common.BytesToHash([]byte("value2")) - It("simple revert", func() { - revision := sdb.Snapshot() - Expect(revision).To(Equal(0)) - sdb.SetState(alice, key, value1) - Expect(sdb.GetState(alice, key)).To(Equal(value1)) - sdb.RevertToSnapshot(revision) - Expect(sdb.GetState(alice, key)).To(Equal(common.Hash{})) - }) - It("nested snapshot & revert", func() { - revision1 := sdb.Snapshot() - Expect(revision1).To(Equal(0)) - sdb.SetState(alice, key, value1) - revision2 := sdb.Snapshot() - Expect(revision2).To(Equal(1)) - sdb.SetState(alice, key, value2) - Expect(sdb.GetState(alice, key)).To(Equal(value2)) - - sdb.RevertToSnapshot(revision2) - Expect(sdb.GetState(alice, key)).To(Equal(value1)) - - sdb.RevertToSnapshot(revision1) - Expect(sdb.GetState(alice, key)).To(Equal(common.Hash{})) - }) - It("jump revert", func() { - revision1 := sdb.Snapshot() - Expect(revision1).To(Equal(0)) - sdb.SetState(alice, key, value1) - sdb.Snapshot() - sdb.SetState(alice, key, value2) - Expect(sdb.GetState(alice, key)).To(Equal(value2)) - sdb.RevertToSnapshot(revision1) - Expect(sdb.GetState(alice, key)).To(Equal(common.Hash{})) - }) - }) - - Describe("Test Refund", func() { - It("simple refund", func() { - sdb.AddRefund(10) - Expect(sdb.GetRefund()).To(Equal(uint64(10))) - sdb.AddRefund(200) - Expect(sdb.GetRefund()).To(Equal(uint64(210))) - }) - - It("nested refund", func() { - sdb.AddRefund(uint64(10)) - sdb.SubRefund(uint64(5)) - Expect(sdb.GetRefund()).To(Equal(uint64(5))) - }) - - It("negative refund", func() { - sdb.AddRefund(5) - Expect(func() { sdb.SubRefund(10) }).To(Panic()) - }) - }) - Describe("Test Log", func() { - txHash := common.BytesToHash([]byte("tx")) - blockHash := common.BytesToHash([]byte("block")) - data := []byte("bing bong bananas") - blockNumber := uint64(13) - - BeforeEach(func() { - sdb.PrepareForTransition(blockHash, txHash, 0, 0) - }) - When("We add a log to the state", func() { - BeforeEach(func() { - - sdb.AddLog(&coretypes.Log{ - Address: alice, - Topics: []common.Hash{}, - Data: data, - BlockNumber: blockNumber, - TxHash: txHash, - TxIndex: 0, - BlockHash: blockHash, - Index: 0, - Removed: false, - }) - }) - It("should have the correct log", func() { - logs := sdb.GetLogs(common.Hash{}, common.Hash{}) - Expect(logs).To(HaveLen(1)) - Expect(logs[0].Address).To(Equal(alice)) - Expect(logs[0].Data).To(Equal(data)) - Expect(logs[0].BlockNumber).To(Equal(blockNumber)) - Expect(logs[0].TxHash).To(Equal(txHash)) - Expect(logs[0].TxIndex).To(Equal(uint(0))) - Expect(logs[0].BlockHash).To(Equal(blockHash)) - Expect(logs[0].Index).To(Equal(uint(0))) - Expect(logs[0].Removed).To(BeFalse()) - }) - When("we add a second log", func() { - BeforeEach(func() { - sdb.AddLog(&coretypes.Log{ - Address: alice, - Topics: []common.Hash{}, - Data: data, - BlockNumber: blockNumber, - TxHash: txHash, - TxIndex: 0, - BlockHash: blockHash, - Index: 1, - Removed: false, - }) - }) - It("should have the correct logs", func() { - logs := sdb.GetLogs(common.Hash{}, common.Hash{}) - Expect(logs).To(HaveLen(2)) - Expect(logs[1].Address).To(Equal(alice)) - Expect(logs[1].Data).To(Equal(data)) - Expect(logs[1].BlockNumber).To(Equal(blockNumber)) - Expect(logs[1].TxHash).To(Equal(txHash)) - Expect(logs[1].TxIndex).To(Equal(uint(0))) - Expect(logs[1].BlockHash).To(Equal(blockHash)) - Expect(logs[1].Index).To(Equal(uint(1))) - Expect(logs[1].Removed).To(BeFalse()) - }) - }) - }) - }) - - Describe("TestSavedErr", func() { - When("if we see an error", func() { - It("should have an error", func() { - sdb.TransferBalance(alice, bob, big.NewInt(100)) - Expect(sdb.GetSavedErr()).To(HaveOccurred()) - }) - }) - - }) - - Describe("TestRevertSnapshot", func() { - key := common.BytesToHash([]byte("key")) - value := common.BytesToHash([]byte("value")) - - When("We make a bunch of arbitrary changes", func() { - BeforeEach(func() { - sdb.SetNonce(alice, 1) - sdb.AddBalance(alice, big.NewInt(100)) - sdb.SetCode(alice, []byte("hello world")) - sdb.SetState(alice, key, value) - sdb.SetNonce(bob, 1) - }) - When("we take a snapshot", func() { - var revision int - BeforeEach(func() { - revision = sdb.Snapshot() - }) - When("we do more changes", func() { - AfterEach(func() { - sdb.RevertToSnapshot(revision) - Expect(sdb.GetNonce(alice)).To(Equal(uint64(1))) - Expect(sdb.GetBalance(alice)).To(Equal(big.NewInt(100))) - Expect(sdb.GetCode(alice)).To(Equal([]byte("hello world"))) - Expect(sdb.GetState(alice, key)).To(Equal(value)) - Expect(sdb.GetNonce(bob)).To(Equal(uint64(1))) - }) - - It("if we change balance", func() { - sdb.AddBalance(alice, big.NewInt(100)) - }) - - It("if we change nonce", func() { - sdb.SetNonce(alice, 2) - }) - - It("if we change code", func() { - sdb.SetCode(alice, []byte("goodbye world")) - }) - - It("if we change state", func() { - sdb.SetState(alice, key, common.Hash{}) - }) - - It("if we change nonce of another account", func() { - sdb.SetNonce(bob, 2) - }) - }) - - When("we make a nested snapshot", func() { - var revision2 int - BeforeEach(func() { - sdb.SetState(alice, key, common.Hash{2}) - revision2 = sdb.Snapshot() - }) - When("we revert to snapshot ", (func() { - It("revision 2", func() { - sdb.RevertToSnapshot(revision2) - Expect(sdb.GetState(alice, key)).To(Equal(common.Hash{2})) - }) - It("revision 1", func() { - sdb.RevertToSnapshot(revision) - Expect(sdb.GetState(alice, key)).To(Equal(value)) - }) - })) - }) - }) - When("we revert to an invalid snapshot", func() { - It("should panic", func() { - Expect(func() { - sdb.RevertToSnapshot(100) - }).To(Panic()) - }) - }) - }) - }) - }) -}) diff --git a/x/evm/plugins/state/store/cachekv/cache_entry.go b/x/evm/plugins/state/store/cachekv/cache_entry.go deleted file mode 100644 index 6582ade83..000000000 --- a/x/evm/plugins/state/store/cachekv/cache_entry.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (C) 2022, Berachain Foundation. All rights reserved. -// See the file LICENSE for licensing terms. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -package cachekv - -import "github.com/berachain/stargazer/x/evm/plugins/state/store/journal" - -// Compile-time check to ensure `cacheEntry` implements `journal.CacheEntry`. -var _ journal.CacheEntry = (*cacheEntry)(nil) - -// `cacheEntry` is a struct that contains information needed to set a value in a cache. -type cacheEntry struct { - Store *Store // Pointer to the cache store. - Key string // Key of the value to be set. - Prev *cacheValue // Deep copy of object in cache map. -} - -// `newCacheEntry` creates a new `cacheEntry` object for the given `store`, `key`, and `prev` -// cache value. -func newCacheEntry(store *Store, key string, prev *cacheValue) *cacheEntry { - // create a deep copy of the Prev field, if it is not nil. - if prev != nil { - prev = prev.Clone() - } - - return &cacheEntry{ - Store: store, - Key: key, - Prev: prev, - } -} - -// `Revert` reverts a set operation on a cache entry by setting the previous value of the entry as -// the current value in the cache map. -// -// `Revert` implements journal.cacheEntry. -func (ce *cacheEntry) Revert() { - // If there was a previous value, set it as the current value in the cache map - if ce.Prev == nil { - // If there was no previous value, remove the Key from the - // cache map and the unsorted cache set - delete(ce.Store.Cache, ce.Key) - delete(ce.Store.UnsortedCache, ce.Key) - return - } - - // If there was a previous value, set it sas the current value in the cache map. - ce.Store.Cache[ce.Key] = ce.Prev - - // If the previous value was not dirty, remove the Key from the unsorted cache set - if !ce.Prev.dirty { - delete(ce.Store.UnsortedCache, ce.Key) - } -} - -// `Clone` creates a deep copy of the cacheEntry object. -// The deep copy contains the same Store and Key fields as the original, -// and a deep copy of the Prev field, if it is not nil.s -// -// `Clone` implements `journal.cacheEntry`. -// -//nolint:nolintlint,ireturn // by design. -func (ce *cacheEntry) Clone() journal.CacheEntry { - // Return a new cacheEntry object with the same Store and Key fields as the original, - // and the Prev field set to the deep copy of the original Prev field (or nil if the original - // was nil). - return newCacheEntry(ce.Store, ce.Key, ce.Prev) -} diff --git a/x/evm/plugins/state/store/cachekv/cache_entry_test.go b/x/evm/plugins/state/store/cachekv/cache_entry_test.go deleted file mode 100644 index 8c769329a..000000000 --- a/x/evm/plugins/state/store/cachekv/cache_entry_test.go +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (C) 2022, Berachain Foundation. All rights reserved. -// See the file LICENSE for licensing terms. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package cachekv - -import ( - "reflect" - "testing" - - sdkcachekv "github.com/cosmos/cosmos-sdk/store/cachekv" - "github.com/cosmos/cosmos-sdk/store/dbadapter" - "github.com/stretchr/testify/suite" - dbm "github.com/tendermint/tm-db" - - "github.com/berachain/stargazer/lib/utils" - "github.com/berachain/stargazer/x/evm/plugins/state/store/journal" -) - -var ( - byte0 = []byte{0} - byte1 = []byte{1} - byte0Str = utils.UnsafeBytesToStr(byte0) - byte1Str = utils.UnsafeBytesToStr(byte1) -) - -type CacheValueSuite struct { - suite.Suite - cacheKVStore *Store -} - -func TestCacheValueSuite(t *testing.T) { - suite.Run(t, new(CacheValueSuite)) -} - -func (s *CacheValueSuite) SetupTest() { - parent := sdkcachekv.NewStore(dbadapter.Store{DB: dbm.NewMemDB()}) - parent.Set(byte0, byte0) - s.cacheKVStore = NewStore(parent, journal.NewManager()) -} - -func (s *CacheValueSuite) TestRevertDeleteAfterNothing() { - // delete after nothing happened to key - snapshot := s.cacheKVStore.JournalMgr().Size() - // delete key: 0 - s.cacheKVStore.Delete(byte0) - s.Require().Equal(([]byte)(nil), s.cacheKVStore.Cache[byte0Str].value) - s.Require().True(s.cacheKVStore.Cache[byte0Str].dirty) - s.Require().Contains(s.cacheKVStore.UnsortedCache, byte0Str) - // revert delete key: 0 - s.cacheKVStore.JournalMgr().PopToSize(snapshot) - s.Require().NotContains(s.cacheKVStore.Cache, byte0Str) - s.Require().NotContains(s.cacheKVStore.UnsortedCache, byte0Str) -} - -func (s *CacheValueSuite) TestRevertDeleteAfterGet() { - // delete after Get called on key - _ = s.cacheKVStore.Get(byte0) - snapshot := s.cacheKVStore.JournalMgr().Size() - // delete key: 0 - s.cacheKVStore.Delete(byte0) - // revert delete key: 0 - s.cacheKVStore.JournalMgr().PopToSize(snapshot) - s.Require().Equal(byte0, s.cacheKVStore.Cache[byte0Str].value) - s.Require().False(s.cacheKVStore.Cache[byte0Str].dirty) - s.Require().NotContains(s.cacheKVStore.UnsortedCache, byte0Str) -} - -func (s *CacheValueSuite) TestRevertDeleteAfterSet() { - // delete after Set called on key - s.cacheKVStore.Set(byte1, byte1) - snapshot := s.cacheKVStore.JournalMgr().Size() - // delete key: 1 - s.cacheKVStore.Delete(byte1) - // revert delete key: 1 - s.cacheKVStore.JournalMgr().PopToSize(snapshot) - s.Require().Equal(byte1, s.cacheKVStore.Cache[byte1Str].value) - s.Require().True(s.cacheKVStore.Cache[byte1Str].dirty) - s.Require().Contains(s.cacheKVStore.UnsortedCache, byte1Str) -} - -func (s *CacheValueSuite) TestRevertDeleteAfterDelete() { - // delete after Delete called on key - s.cacheKVStore.Delete(byte0) - snapshot := s.cacheKVStore.JournalMgr().Size() - // delete key: 0 - s.cacheKVStore.Delete(byte0) - // revert delete key: 0 - s.cacheKVStore.JournalMgr().PopToSize(snapshot) - s.Require().Equal(([]byte)(nil), s.cacheKVStore.Cache[byte0Str].value) - s.Require().True(s.cacheKVStore.Cache[byte0Str].dirty) - s.Require().Contains(s.cacheKVStore.UnsortedCache, byte0Str) -} - -func (s *CacheValueSuite) TestRevertSetAfterNothing() { - // set after nothing happened to key - snapshot := s.cacheKVStore.JournalMgr().Size() - // set key: 1 - s.cacheKVStore.Set(byte1, byte1) - s.Require().Equal(byte1, s.cacheKVStore.Cache[byte1Str].value) - s.Require().True(s.cacheKVStore.Cache[byte1Str].dirty) - s.Require().Contains(s.cacheKVStore.UnsortedCache, byte1Str) - // revert set key: 1 - s.cacheKVStore.JournalMgr().PopToSize(snapshot) - s.Require().NotContains(s.cacheKVStore.Cache, byte1Str) - s.Require().NotContains(s.cacheKVStore.UnsortedCache, byte1Str) -} - -func (s *CacheValueSuite) TestRevertSetAfterGet() { - // set after get called on key - _ = s.cacheKVStore.Get(byte0) - snapshot := s.cacheKVStore.JournalMgr().Size() - // set key: 0 to val: 1 - s.cacheKVStore.Set(byte0, byte1) - // revert set key: 1 to val: 1 - s.cacheKVStore.JournalMgr().PopToSize(snapshot) - s.Require().Equal(byte0, s.cacheKVStore.Cache[byte0Str].value) - s.Require().False(s.cacheKVStore.Cache[byte0Str].dirty) - s.Require().NotContains(s.cacheKVStore.UnsortedCache, byte0Str) -} - -func (s *CacheValueSuite) TestRevertSetAfterDelete() { - // set after delete called on key - s.cacheKVStore.Delete(byte0) - snapshot := s.cacheKVStore.JournalMgr().Size() - // set key: 0 to val: 0 - s.cacheKVStore.Set(byte0, byte0) - // revert set key: 0 to val: 0 - s.cacheKVStore.JournalMgr().PopToSize(snapshot) - s.Require().Nil(s.cacheKVStore.Cache[byte0Str].value) - s.Require().True(s.cacheKVStore.Cache[byte0Str].dirty) - s.Require().Contains(s.cacheKVStore.UnsortedCache, byte0Str) -} - -func (s *CacheValueSuite) TestRevertSetAfterSet() { - // set after set called on key - s.cacheKVStore.Set(byte1, byte1) - snapshot := s.cacheKVStore.JournalMgr().Size() - // set key: 1 to val: 0 - s.cacheKVStore.Set(byte1, byte0) - // revert set key: 1 to val: 0 - s.cacheKVStore.JournalMgr().PopToSize(snapshot) - s.Require().Equal(byte1, s.cacheKVStore.Cache[byte1Str].value) - s.Require().True(s.cacheKVStore.Cache[byte1Str].dirty) - s.Require().Contains(s.cacheKVStore.UnsortedCache, byte1Str) -} - -func (s *CacheValueSuite) TestCloneSet() { - dcvNonNil := newCacheEntry(s.cacheKVStore, byte1Str, newCacheValue(byte1, true)) - dcvNonNilClone, ok := dcvNonNil.Clone().(*cacheEntry) - s.Require().True(ok) - s.Require().Equal(byte1Str, dcvNonNilClone.Key) - s.Require().True(dcvNonNilClone.Prev.dirty) - s.Require().Equal(byte1, dcvNonNilClone.Prev.value) - - dcvNil := newCacheEntry(s.cacheKVStore, "", nil) - dcvNilClone, ok := dcvNil.Clone().(*cacheEntry) - s.Require().True(ok) - s.Require().Equal("", dcvNilClone.Key) - s.Require().Equal(dcvNil.Prev, dcvNilClone.Prev) - s.Require().True(reflect.ValueOf(dcvNilClone.Prev).IsNil()) -} diff --git a/x/evm/plugins/state/store/cachekv/evm_store.go b/x/evm/plugins/state/store/cachekv/evm_store.go deleted file mode 100644 index 2b44c3e01..000000000 --- a/x/evm/plugins/state/store/cachekv/evm_store.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (C) 2022, Berachain Foundation. All rights reserved. -// See the file LICENSE for licensing terms. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package cachekv - -import ( - storetypes "github.com/cosmos/cosmos-sdk/store/types" - - "github.com/berachain/stargazer/lib/utils" - "github.com/berachain/stargazer/x/evm/plugins/state/store/journal" -) - -// Compile-time check to ensure `EvmStore` implements `storetypes.CacheKVStore`. -var _ storetypes.CacheKVStore = (*EvmStore)(nil) - -// `EVMStore` is a cache kv store that avoids the mutex lock for EVM stores (codes/storage). -// Writes to the EVM are thread-safe because the EVM interpreter is guaranteed to be single -// threaded. All entry points to the EVM check that only a single execution context is running. -type EvmStore struct { - *Store -} - -// `NewEvmStore` creates a new Store object. -func NewEvmStore(parent storetypes.KVStore, journalMgr *journal.Manager) *EvmStore { - return &EvmStore{ - NewStore(parent, journalMgr), - } -} - -// `Get` shadows Store.Get -// This function retrieves a value associated with the specified key in the store. -func (store *EvmStore) Get(key []byte) []byte { - var bz []byte - // Check if the key is in the store's cache. - if cacheValue, found := store.Cache[utils.UnsafeBytesToStr(key)]; found { - // If the key is in the cache, return the value. - return cacheValue.value - } - - // If the key is not found in the cache, query the parent store. - bz = store.Parent.Get(key) - - // Add the key-value pair to the cache. - store.setCacheValue(key, bz, false) - - return bz -} - -// `Set` shadows Store.Set. -func (store *EvmStore) Set(key []byte, value []byte) { - store.setCacheValue(key, value, true) -} - -// `Delete` shadows Store.Delete. -func (store *EvmStore) Delete(key []byte) { - store.setCacheValue(key, nil, true) -} diff --git a/x/evm/plugins/state/store/cachekv/evm_store_test.go b/x/evm/plugins/state/store/cachekv/evm_store_test.go deleted file mode 100644 index 86b85a3e4..000000000 --- a/x/evm/plugins/state/store/cachekv/evm_store_test.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (C) 2023, Berachain Foundation. All rights reserved. -// See the file LICENSE for licensing terms. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package cachekv_test - -import ( - "testing" - - "github.com/berachain/stargazer/lib/common" - "github.com/berachain/stargazer/x/evm/plugins/state/store/cachekv" - "github.com/berachain/stargazer/x/evm/plugins/state/store/journal" - sdkcachekv "github.com/cosmos/cosmos-sdk/store/cachekv" - "github.com/cosmos/cosmos-sdk/store/dbadapter" - storetypes "github.com/cosmos/cosmos-sdk/store/types" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - dbm "github.com/tendermint/tm-db" -) - -func TestCacheKv(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "x/evm/plugins/state/store/cachekv") -} - -var _ = Describe("CacheMulti", func() { - var ( - byte0 = []byte{0} - byte1 = []byte{1} - nonZeroCodeHash = common.BytesToHash([]byte{0x05}) - zeroCodeHash = common.Hash{} - parent storetypes.KVStore - evmStore *cachekv.EvmStore - ) - - BeforeEach(func() { - parent = sdkcachekv.NewStore(dbadapter.Store{DB: dbm.NewMemDB()}) - evmStore = cachekv.NewEvmStore(parent, journal.NewManager()) - }) - - It("TestWarmSlotVia0", func() { - evmStore.Set(byte1, zeroCodeHash.Bytes()) - evmStore.Write() - Expect(parent.Get(byte1)).To(Equal(zeroCodeHash.Bytes())) - }) - It("TestWriteZeroValParentNotNil", func() { - evmStore.Set(byte0, zeroCodeHash.Bytes()) - evmStore.Write() - Expect(parent.Get(byte0)).To(Equal(zeroCodeHash.Bytes())) - }) - It("TestWriteNonZeroValParentNil", func() { - evmStore.Set(byte0, nonZeroCodeHash.Bytes()) - Expect(parent.Get(byte0)).To(BeNil()) - evmStore.Write() - Expect(parent.Get(byte0)).To(Equal(nonZeroCodeHash.Bytes())) - }) - It("TestWriteNonZeroValParentNotNil", func() { - evmStore.Set(byte0, nonZeroCodeHash.Bytes()) - Expect(parent.Get(byte0)).To(BeNil()) - evmStore.Write() - Expect(parent.Get(byte0)).To(Equal(nonZeroCodeHash.Bytes())) - }) - It("TestWriteAfterDelete", func() { - evmStore.Set(byte1, zeroCodeHash.Bytes()) - Expect(evmStore.Get(byte1)).To(Equal(zeroCodeHash.Bytes())) - evmStore.Delete(byte1) - Expect(evmStore.Get(byte1)).To(BeNil()) - evmStore.Write() - Expect(parent.Get(byte1)).To(BeNil()) - }) -}) diff --git a/x/evm/plugins/state/store/cachekv/mergeiterator.go b/x/evm/plugins/state/store/cachekv/mergeiterator.go deleted file mode 100644 index 66f5937f1..000000000 --- a/x/evm/plugins/state/store/cachekv/mergeiterator.go +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright (C) 2023, Berachain Foundation. All rights reserved. -// See the file LICENSE for licensing terms. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package cachekv - -import ( - "bytes" - "errors" - - "github.com/cosmos/cosmos-sdk/store/types" -) - -// `cacheMergeIterator` merges a parent Iterator and a cache Iterator. -// The cache iterator may return nil keys to signal that an item -// had been deleted (but not deleted in the parent). -// If the cache iterator has the same key as the parent, the -// cache shadows (overrides) the parent. -// -// TODO: Optimize by memoizing. -type cacheMergeIterator struct { - parent types.Iterator - cache types.Iterator - ascending bool - - valid bool -} - -var _ types.Iterator = (*cacheMergeIterator)(nil) - -// `NewCacheMergeIterator` creates a new cacheMergeIterator. -func newCacheMergeIterator(parent, cache types.Iterator, ascending bool) *cacheMergeIterator { - iter := &cacheMergeIterator{ - parent: parent, - cache: cache, - ascending: ascending, - } - - iter.valid = iter.skipUntilExistsOrInvalid() - return iter -} - -// Domain implements Iterator. -// Returns parent domain because cache and parent domains are the same. -func (iter *cacheMergeIterator) Domain() ([]byte, []byte) { - return iter.parent.Domain() -} - -// Valid implements Iterator. -func (iter *cacheMergeIterator) Valid() bool { - return iter.valid -} - -// Next implements Iterator. -func (iter *cacheMergeIterator) Next() { - iter.assertValid() - - switch { - case !iter.parent.Valid(): - // If parent is invalid, get the next cache item. - iter.cache.Next() - case !iter.cache.Valid(): - // If cache is invalid, get the next parent item. - iter.parent.Next() - default: - // Both are valid. Compare keys. - keyP, keyC := iter.parent.Key(), iter.cache.Key() - switch iter.compare(keyP, keyC) { - case -1: // parent < cache - iter.parent.Next() - case 0: // parent == cache - iter.parent.Next() - iter.cache.Next() - case 1: // parent > cache - iter.cache.Next() - } - } - iter.valid = iter.skipUntilExistsOrInvalid() -} - -// Key implements Iterator. -func (iter *cacheMergeIterator) Key() []byte { - iter.assertValid() - - // If parent is invalid, get the cache key. - if !iter.parent.Valid() { - return iter.cache.Key() - } - - // If cache is invalid, get the parent key. - if !iter.cache.Valid() { - return iter.parent.Key() - } - - // Both are valid. Compare keys. - keyP, keyC := iter.parent.Key(), iter.cache.Key() - - cmp := iter.compare(keyP, keyC) - switch cmp { - case -1: // parent < cache - return keyP - case 0: // parent == cache - return keyP - case 1: // parent > cache - return keyC - default: - panic("invalid compare result") - } -} - -// Value implements Iterator. -func (iter *cacheMergeIterator) Value() []byte { - iter.assertValid() - - // If parent is invalid, get the cache value. - if !iter.parent.Valid() { - return iter.cache.Value() - } - - // If cache is invalid, get the parent value. - if !iter.cache.Valid() { - return iter.parent.Value() - } - - // Both are valid. Compare keys. - keyP, keyC := iter.parent.Key(), iter.cache.Key() - - cmp := iter.compare(keyP, keyC) - switch cmp { - case -1: // parent < cache - return iter.parent.Value() - case 0: // parent == cache - return iter.cache.Value() - case 1: // parent > cache - return iter.cache.Value() - default: - panic("invalid comparison result") - } -} - -// Close implements Iterator. -func (iter *cacheMergeIterator) Close() error { - err1 := iter.cache.Close() - if err := iter.parent.Close(); err != nil { - return err - } - - return err1 -} - -// Error returns an error if the cacheMergeIterator is invalid defined by the -// Valid method. -func (iter *cacheMergeIterator) Error() error { - if !iter.Valid() { - return errors.New("invalid cacheMergeIterator") - } - - return nil -} - -// If not valid, panics. -// NOTE: May have side-effect of iterating over cache. -func (iter *cacheMergeIterator) assertValid() { - if err := iter.Error(); err != nil { - panic(err) - } -} - -// Like bytes.Compare but opposite if not ascending. -func (iter *cacheMergeIterator) compare(a, b []byte) int { - if iter.ascending { - return bytes.Compare(a, b) - } - - return bytes.Compare(a, b) * -1 -} - -// Skip all delete-items from the cache w/ `key < until`. After this function, -// current cache item is a non-delete-item, or `until <= key`. -// If the current cache item is not a delete item, does nothing. -// If `until` is nil, there is no limit, and cache may end up invalid. -// CONTRACT: cache is valid. -func (iter *cacheMergeIterator) skipCacheDeletes(until []byte) { - for iter.cache.Valid() && - iter.cache.Value() == nil && - (until == nil || iter.compare(iter.cache.Key(), until) < 0) { - iter.cache.Next() - } -} - -// Fast forwards cache (or parent+cache in case of deleted items) until current -// item exists, or until iterator becomes invalid. -// Returns whether the iterator is valid. -func (iter *cacheMergeIterator) skipUntilExistsOrInvalid() bool { - for { - // If parent is invalid, fast-forward cache. - if !iter.parent.Valid() { - iter.skipCacheDeletes(nil) - return iter.cache.Valid() - } - // Parent is valid. - - if !iter.cache.Valid() { - return true - } - // Parent is valid, cache is valid. - - // Compare parent and cache. - keyP := iter.parent.Key() - keyC := iter.cache.Key() - - switch iter.compare(keyP, keyC) { - case -1: // parent < cache. - return true - - case 0: // parent == cache. - // Skip over if cache item is a delete. - valueC := iter.cache.Value() - if valueC == nil { - iter.parent.Next() - iter.cache.Next() - - continue - } - // Cache is not a delete. - - return true // cache exists. - case 1: // cache < parent - // Skip over if cache item is a delete. - valueC := iter.cache.Value() - if valueC == nil { - iter.skipCacheDeletes(keyP) - continue - } - // Cache is not a delete. - - return true // cache exists. - } - } -} diff --git a/x/evm/plugins/state/store/cachekv/store.go b/x/evm/plugins/state/store/cachekv/store.go deleted file mode 100644 index 5d82a7241..000000000 --- a/x/evm/plugins/state/store/cachekv/store.go +++ /dev/null @@ -1,434 +0,0 @@ -// Copyright (C) 2022, Berachain Foundation. All rights reserved. -// See the file LICENSE for licensing terms. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -package cachekv - -import ( - "bytes" - "io" - "sort" - "sync" - - sdkcachekv "github.com/cosmos/cosmos-sdk/store/cachekv" - "github.com/cosmos/cosmos-sdk/store/listenkv" - "github.com/cosmos/cosmos-sdk/store/tracekv" - storetypes "github.com/cosmos/cosmos-sdk/store/types" - "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/kv" - "github.com/tendermint/tendermint/libs/math" - dbm "github.com/tendermint/tm-db" - - "github.com/berachain/stargazer/lib/ds" - "github.com/berachain/stargazer/lib/ds/trees" - "github.com/berachain/stargazer/lib/utils" - "github.com/berachain/stargazer/x/evm/plugins/state/store/journal" -) - -type StateDBCacheKVStore interface { - storetypes.CacheKVStore - GetParent() storetypes.KVStore -} - -var _ StateDBCacheKVStore = (*Store)(nil) - -// Store wraps an in-memory cache around an underlying storetypes.KVStore. -// If a cached value is nil but deleted is defined for the corresponding key, -// it means the parent doesn't have the key. (No need to delete upon Write()). -type Store struct { - mtx sync.RWMutex - Cache map[string]*cacheValue - UnsortedCache map[string]struct{} - SortedCache ds.BTree // always ascending sorted - Parent storetypes.KVStore - journalMgr *journal.Manager -} - -// NewStore creates a new Store object. -func NewStore(parent storetypes.KVStore, journalMgr *journal.Manager) *Store { - return &Store{ - Cache: make(map[string]*cacheValue), - UnsortedCache: make(map[string]struct{}), - SortedCache: trees.NewBTree(), - Parent: parent, - journalMgr: journalMgr, - } -} - -func (store *Store) JournalMgr() *journal.Manager { - return store.journalMgr -} - -// GetStoreType implements storetypes.KVStore. -func (store *Store) GetStoreType() storetypes.StoreType { - return store.Parent.GetStoreType() -} - -// Get implements storetypes.KVStore. -// This function retrieves a value associated with the specified key in the store. -func (store *Store) Get(key []byte) []byte { - var bz []byte - store.mtx.RLock() - defer store.mtx.RUnlock() - // Assert that the key is valid. - storetypes.AssertValidKey(key) - - // Check if the key is in the store's cache. - if cacheValue, found := store.Cache[utils.UnsafeBytesToStr(key)]; !found { - bz = store.Parent.Get(key) - store.setCacheValue(key, bz, false) - } else { - bz = cacheValue.value - } - - return bz -} - -func (store *Store) GetParent() storetypes.KVStore { - return store.Parent -} - -// Set implements storetypes.KVStore. -func (store *Store) Set(key []byte, value []byte) { - store.mtx.Lock() - defer store.mtx.Unlock() - storetypes.AssertValidKey(key) - storetypes.AssertValidValue(value) - store.setCacheValue(key, value, true) -} - -// Has implements storetypes.KVStore. -func (store *Store) Has(key []byte) bool { - value := store.Get(key) - return value != nil -} - -// Delete implements storetypes.KVStore. -func (store *Store) Delete(key []byte) { - storetypes.AssertValidKey(key) - store.mtx.Lock() - defer store.mtx.Unlock() - store.setCacheValue(key, nil, true) -} - -// Implements Cachetypes.KVStore. -func (store *Store) Write() { - store.mtx.Lock() - defer store.mtx.Unlock() - - if len(store.Cache) == 0 && len(store.UnsortedCache) == 0 { - store.SortedCache = trees.NewBTree() - return - } - - // We need a copy of all of the keys. - // Not the best, but probably not a bottleneck depending. - keys := make([]string, 0, len(store.Cache)) - - for key, dbValue := range store.Cache { - if dbValue.dirty { - keys = append(keys, key) - } - } - - sort.Strings(keys) - - // TODO: Consider allowing usage of Batch, which would allow the write to - // at least happen atomically. - for _, key := range keys { - // We use []byte(key) instead of utils.UnsafeStrToBytes because we cannot - // be sure if the underlying store might do a save with the byteslice or - // not. Once we get confirmation that .Delete is guaranteed not to - // save the byteslice, then we can assume only a read-only copy is sufficient. - cacheValue := store.Cache[key] - if cacheValue.value != nil { - // It already exists in the parent, hence update it. - store.Parent.Set([]byte(key), cacheValue.value) - } else { - store.Parent.Delete([]byte(key)) - } - } - - // Clear the journal entries - store.journalMgr = journal.NewManager() - - // Clear the cache using the map clearing idiom - // and not allocating fresh objects. - // Please see https://bencher.orijtech.com/perfclinic/mapclearing/ - for key := range store.Cache { - delete(store.Cache, key) - } - for key := range store.UnsortedCache { - delete(store.UnsortedCache, key) - } - - store.SortedCache = trees.NewBTree() -} - -// CacheWrap implements CacheWrapper. -func (store *Store) CacheWrap() storetypes.CacheWrap { - return sdkcachekv.NewStore(store) -} - -// CacheWrapWithTrace implements the CacheWrapper interface. -func (store *Store) CacheWrapWithTrace( - w io.Writer, - tc storetypes.TraceContext, -) storetypes.CacheWrap { - return sdkcachekv.NewStore(tracekv.NewStore(store, w, tc)) -} - -// CacheWrapWithListeners implements the CacheWrapper interface. -func (store *Store) CacheWrapWithListeners( - storeKey storetypes.StoreKey, - listeners []storetypes.WriteListener, -) storetypes.CacheWrap { - return sdkcachekv.NewStore(listenkv.NewStore(store, storeKey, listeners)) -} - -// ================================================ -// Iteration - -// Iterator implements storetypes.KVStore. -func (store *Store) Iterator(start, end []byte) storetypes.Iterator { - return store.iterator(start, end, true) -} - -// ReverseIterator implements storetypes.KVStore. -func (store *Store) ReverseIterator(start, end []byte) storetypes.Iterator { - return store.iterator(start, end, false) -} - -func (store *Store) iterator(start, end []byte, ascending bool) types.Iterator { - store.mtx.Lock() - defer store.mtx.Unlock() - - store.dirtyItems(start, end) - isoSortedCache := store.SortedCache.Copy() - - var ( - err error - parent, cache types.Iterator - ) - - if ascending { - parent = store.Parent.Iterator(start, end) - cache, err = isoSortedCache.Iterator(start, end) - } else { - parent = store.Parent.ReverseIterator(start, end) - cache, err = isoSortedCache.ReverseIterator(start, end) - } - if err != nil { - panic(err) - } - - return newCacheMergeIterator(parent, cache, ascending) -} - -func findStartIndex(strL []string, startQ string) int { - // Modified binary search to find the very first element in >=startQ. - if len(strL) == 0 { - return -1 - } - - var left, right, mid int - right = len(strL) - 1 - for left <= right { - mid = (left + right) >> 1 - midStr := strL[mid] - if midStr == startQ { - // Handle condition where there might be multiple values equal to startQ. - // We are looking for the very first value < midStL, that i+1 will be the first - // element >= midStr. - for i := mid - 1; i >= 0; i-- { - if strL[i] != midStr { - return i + 1 - } - } - return 0 - } - if midStr < startQ { - left = mid + 1 - } else { // midStrL > startQ - right = mid - 1 - } - } - if left >= 0 && left < len(strL) && strL[left] >= startQ { - return left - } - return -1 -} - -func findEndIndex(strL []string, endQ string) int { - if len(strL) == 0 { - return -1 - } - - // Modified binary search to find the very first element > 1 - midStr := strL[mid] - if midStr == endQ { - // Handle condition where there might be multiple values equal to startQ. - // We are looking for the very first value < midStL, that i+1 will be the first - // element >= midStr. - for i := mid - 1; i >= 0; i-- { - if strL[i] < midStr { - return i + 1 - } - } - return 0 - } - if midStr < endQ { - left = mid + 1 - } else { // midStrL > startQ - right = mid - 1 - } - } - - // Binary search failed, now let's find a value less than endQ. - for i := right; i >= 0; i-- { - if strL[i] < endQ { - return i - } - } - - return -1 -} - -type sortState int - -const ( - stateUnsorted sortState = iota - stateAlreadySorted -) - -const minSortSize = 1024 - -// Constructs a slice of dirty items, to use w/ memIterator. -func (store *Store) dirtyItems(start, end []byte) { - startStr, endStr := utils.UnsafeBytesToStr(start), utils.UnsafeBytesToStr(end) - if end != nil && startStr > endStr { - // Nothing to do here. - return - } - - n := len(store.UnsortedCache) - unsorted := make([]*kv.Pair, 0) - // If the unsortedCache is too big, its costs too much to determine - // whats in the subset we are concerned about. - // If you are interleaving iterator calls with writes, this can easily become an - // O(N^2) overhead. - // Even without that, too many range checks eventually becomes more expensive - // than just not having the cache. - if n < minSortSize { - for key := range store.UnsortedCache { - // dbm.IsKeyInDomain is nil safe and returns true iff key is greater than start - if dbm.IsKeyInDomain(utils.UnsafeStrToBytes(key), start, end) { - cacheValue := store.Cache[key] - unsorted = append(unsorted, &kv.Pair{Key: []byte(key), Value: cacheValue.value}) - } - } - store.clearUnsortedCacheSubset(unsorted, stateUnsorted) - return - } - - // Otherwise it is large so perform a modified binary search to find - // the target ranges for the keys that we should be looking for. - strL := make([]string, 0, n) - for key := range store.UnsortedCache { - strL = append(strL, key) - } - sort.Strings(strL) - - // Now find the values within the domain - // [start, end) - startIndex := findStartIndex(strL, startStr) - if startIndex < 0 { - startIndex = 0 - } - - var endIndex int - if end == nil { - endIndex = len(strL) - 1 - } else { - endIndex = findEndIndex(strL, endStr) - } - if endIndex < 0 { - endIndex = len(strL) - 1 - } - - // Since we spent cycles to sort the values, we should process and remove a reasonable amount - // ensure start to end is at least minSortSize in size - // if below minSortSize, expand it to cover additional values - // this amortizes the cost of processing elements across multiple calls - if endIndex-startIndex < minSortSize { - endIndex = math.MinInt(startIndex+minSortSize, len(strL)-1) - if endIndex-startIndex < minSortSize { - startIndex = math.MaxInt(endIndex-minSortSize, 0) - } - } - - kvL := make([]*kv.Pair, 0, 1+endIndex-startIndex) - for i := startIndex; i <= endIndex; i++ { - key := strL[i] - cacheValue := store.Cache[key] - kvL = append(kvL, &kv.Pair{Key: []byte(key), Value: cacheValue.value}) - } - - // kvL was already sorted so pass it in as is. - store.clearUnsortedCacheSubset(kvL, stateAlreadySorted) -} - -func (store *Store) clearUnsortedCacheSubset(unsorted []*kv.Pair, sortState sortState) { - n := len(store.UnsortedCache) - if len(unsorted) == n { // This pattern allows the Go compiler to emit the map clearing idiom for the entire map. - for key := range store.UnsortedCache { - delete(store.UnsortedCache, key) - } - } else { // Otherwise, normally delete the unsorted keys from the map. - for _, kv := range unsorted { - delete(store.UnsortedCache, utils.UnsafeBytesToStr(kv.Key)) - } - } - - if sortState == stateUnsorted { - sort.Slice(unsorted, func(i, j int) bool { - return bytes.Compare(unsorted[i].Key, unsorted[j].Key) < 0 - }) - } - - for _, item := range unsorted { - // sortedCache is able to store `nil` value to represent deleted items. - store.SortedCache.Set(item.Key, item.Value) - } -} - -// ================================================ -// etc - -// Only entrypoint to mutate store.Cache. -func (store *Store) setCacheValue(key, value []byte, dirty bool) { - keyStr := utils.UnsafeBytesToStr(key) - - // Append a new journal entry if the value is dirty, in order to remember the previous state. - // Also add the key to the unsorted cache. - if dirty { - store.journalMgr.Push(newCacheEntry(store, keyStr, store.Cache[keyStr])) - store.UnsortedCache[keyStr] = struct{}{} - } - - // Cache the value for the key. - store.Cache[keyStr] = newCacheValue(value, dirty) -} diff --git a/x/evm/plugins/state/store/cachekv/store_benchmark_test.go b/x/evm/plugins/state/store/cachekv/store_benchmark_test.go deleted file mode 100644 index 44276df11..000000000 --- a/x/evm/plugins/state/store/cachekv/store_benchmark_test.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (C) 2022, Berachain Foundation. All rights reserved. -// See the file LICENSE for licensing terms. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package cachekv_test - -import ( - "testing" - - "github.com/cosmos/cosmos-sdk/store" - storetypes "github.com/cosmos/cosmos-sdk/store/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/tendermint/tendermint/libs/log" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - dbm "github.com/tendermint/tm-db" - - "github.com/berachain/stargazer/x/evm/plugins/state/store/cachemulti" -) - -func DoBenchmarkGet(b *testing.B, custom bool, keyStr string) { - key := storetypes.NewKVStoreKey(keyStr) - db := dbm.NewMemDB() - cms := store.NewCommitMultiStore(db) - cms.MountStoreWithDB(key, storetypes.StoreTypeIAVL, db) - _ = cms.LoadLatestVersion() - ctx := sdk.NewContext(cms, tmproto.Header{}, false, log.NewNopLogger()) - if custom { - ctx = ctx.WithMultiStore(cachemulti.NewStoreFrom(cms)) - } else { - ctx = ctx.WithMultiStore(cms.CacheMultiStore()) - } - store := ctx.KVStore(key) - - for i := 0; i < b.N; i++ { - store.Set([]byte("key"), []byte("value")) - } - - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - store.Get([]byte("key")) - } -} - -func BenchmarkGetStandardSdkCache(b *testing.B) { - DoBenchmarkGet(b, false, "test") -} - -func BenchmarkGetCustomCache(b *testing.B) { - DoBenchmarkGet(b, true, "test") -} - -func BenchmarkGetCustomEvmCache(b *testing.B) { - DoBenchmarkGet(b, true, "evm") -} diff --git a/x/evm/plugins/state/store/cachekv/store_test.go b/x/evm/plugins/state/store/cachekv/store_test.go deleted file mode 100644 index 7a22c7312..000000000 --- a/x/evm/plugins/state/store/cachekv/store_test.go +++ /dev/null @@ -1,611 +0,0 @@ -// Copyright (C) 2022, Berachain Foundation. All rights reserved. -// See the file LICENSE for licensing terms. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package cachekv_test - -import ( - "bytes" - "fmt" - "testing" - - sdkcachekv "github.com/cosmos/cosmos-sdk/store/cachekv" - "github.com/cosmos/cosmos-sdk/store/dbadapter" - "github.com/cosmos/cosmos-sdk/store/types" - "github.com/stretchr/testify/require" - tmrand "github.com/tendermint/tendermint/libs/rand" - dbm "github.com/tendermint/tm-db" - - "github.com/berachain/stargazer/x/evm/plugins/state/store/cachekv" - "github.com/berachain/stargazer/x/evm/plugins/state/store/journal" -) - -func newParent() types.CacheKVStore { - return sdkcachekv.NewStore(dbadapter.Store{DB: dbm.NewMemDB()}) -} - -func newCacheKVStoreFromParent(parent types.CacheKVStore) types.CacheKVStore { - return cachekv.NewStore(parent, journal.NewManager()) -} - -func TestSdkConsistency(t *testing.T) { - sdkParent := dbadapter.Store{DB: dbm.NewMemDB()} - sdkCacheKV := sdkcachekv.NewStore(sdkParent) - parent := newParent() - cacheKV := newCacheKVStoreFromParent(parent) - - // do an op, test the iterator - for i := 0; i < 2000; i++ { - doRandomOp(t, cacheKV, sdkCacheKV, 1000) - assertIterateDomainCompare(t, cacheKV, sdkCacheKV) - } -} - -func TestGetStoreType(t *testing.T) { - parent := newParent() - st := cachekv.NewStore(parent, journal.NewManager()) - require.Equal(t, parent.GetStoreType(), st.GetStoreType()) -} - -func TestHas(t *testing.T) { - parent := newParent() - st := cachekv.NewStore(parent, journal.NewManager()) - st.Set(keyFmt(1), keyFmt(1)) - require.True(t, st.Has(keyFmt(1))) - require.False(t, parent.Has(keyFmt(1))) - st.Write() - require.True(t, parent.Has(keyFmt(1))) -} - -func TestCacheKVReverseIterator(t *testing.T) { - parent := newParent() - st := cachekv.NewStore(parent, journal.NewManager()) - - // Use the parent to check values on the merge iterator - setRange(t, st, parent, 0, 40) - st.Write() - - itr1 := st.ReverseIterator(nil, nil) - itr2 := parent.ReverseIterator(nil, nil) - checkIterators(t, itr1, itr2) -} - -func TestCacheWrap(t *testing.T) { - st := cachekv.NewStore(newParent(), journal.NewManager()) - - // test before initializing cache wraps - st.Set(keyFmt(1), valFmt(1)) - require.Equal(t, valFmt(1), st.Get(keyFmt(1))) - - stWrap, ok := st.CacheWrap().(types.KVStore) - require.True(t, ok) - stTrace, ok := st.CacheWrapWithTrace( - bytes.NewBuffer(nil), - types.TraceContext(map[string]interface{}{"blockHeight": 64}), - ).(types.KVStore) - require.True(t, ok) - stListeners, ok := st.CacheWrapWithListeners( - types.NewKVStoreKey("acc"), - []types.WriteListener{}, - ).(types.KVStore) - require.True(t, ok) - - // test after initializing cache wraps - st.Set(keyFmt(2), valFmt(2)) - require.Equal(t, valFmt(2), st.Get(keyFmt(2))) - - // both keys 1 and 2 should be set in all cache wraps - require.Equal(t, valFmt(1), stWrap.Get(keyFmt(1))) - require.Equal(t, valFmt(1), stTrace.Get(keyFmt(1))) - require.Equal(t, valFmt(1), stListeners.Get(keyFmt(1))) - require.Equal(t, valFmt(2), stWrap.Get(keyFmt(2))) - require.Equal(t, valFmt(2), stTrace.Get(keyFmt(2))) - require.Equal(t, valFmt(2), stListeners.Get(keyFmt(2))) -} - -// Tests below taken from Cosmos SDK and adapted to our custom CacheKVStore to ensure -// the same logical behavior - -func bz(s string) []byte { return []byte(s) } - -func keyFmt(i int) []byte { return bz(fmt.Sprintf("key%0.8d", i)) } - -func valFmt(i int) []byte { return bz(fmt.Sprintf("value%0.8d", i)) } - -func TestCacheKVStore(t *testing.T) { - parent := newParent() - st := newCacheKVStoreFromParent(parent) - - require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") - - // put something in parent and in cache - parent.Set(keyFmt(1), valFmt(1)) - st.Set(keyFmt(1), valFmt(1)) - require.Equal(t, valFmt(1), st.Get(keyFmt(1))) - - // update it in cache, shoudn't change parent - st.Set(keyFmt(1), valFmt(2)) - require.Equal(t, valFmt(2), st.Get(keyFmt(1))) - require.Equal(t, valFmt(1), parent.Get(keyFmt(1))) - - // write it. should change parent - st.Write() - require.Equal(t, valFmt(2), parent.Get(keyFmt(1))) - require.Equal(t, valFmt(2), st.Get(keyFmt(1))) - - // more writes and checks - st.Write() - st.Write() - require.Equal(t, valFmt(2), parent.Get(keyFmt(1))) - require.Equal(t, valFmt(2), st.Get(keyFmt(1))) - - // make a new one, check it - st = newCacheKVStoreFromParent(parent) - require.Equal(t, valFmt(2), st.Get(keyFmt(1))) - - // make a new one and delete - should not be removed from parent - st = newCacheKVStoreFromParent(parent) - st.Delete(keyFmt(1)) - require.Empty(t, st.Get(keyFmt(1))) - require.Equal(t, parent.Get(keyFmt(1)), valFmt(2)) - - // Write. should now be removed from both - st.Write() - require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") - require.Empty(t, parent.Get(keyFmt(1)), "Expected `key1` to be empty") -} - -func TestCacheKVStoreNoNilSet(t *testing.T) { - parent := newParent() - st := newCacheKVStoreFromParent(parent) - require.Panics(t, func() { st.Set([]byte("key"), nil) }, "setting a nil value should panic") - require.Panics(t, func() { st.Set(nil, []byte("value")) }, "setting a nil key should panic") - require.Panics( - t, - func() { st.Set([]byte(""), []byte("value")) }, - "setting an empty key should panic", - ) -} - -func TestCacheKVStoreNested(t *testing.T) { - parent := newParent() - st := newCacheKVStoreFromParent(parent) - - // set. check its there on st and not on parent. - st.Set(keyFmt(1), valFmt(1)) - require.Empty(t, parent.Get(keyFmt(1))) - require.Equal(t, valFmt(1), st.Get(keyFmt(1))) - - // make a new from st and check - st2 := sdkcachekv.NewStore(st) - require.Equal(t, valFmt(1), st2.Get(keyFmt(1))) - - // update the value on st2, check it only effects st2 - st2.Set(keyFmt(1), valFmt(3)) - require.Equal(t, []byte(nil), parent.Get(keyFmt(1))) - require.Equal(t, valFmt(1), st.Get(keyFmt(1))) - require.Equal(t, valFmt(3), st2.Get(keyFmt(1))) - - // st2 writes to its parent, st. doesnt effect parent - st2.Write() - require.Equal(t, []byte(nil), parent.Get(keyFmt(1))) - require.Equal(t, valFmt(3), st.Get(keyFmt(1))) - - // updates parent - st.Write() - require.Equal(t, valFmt(3), parent.Get(keyFmt(1))) -} - -func TestCacheKVIteratorBounds(t *testing.T) { - parent := newParent() - st := cachekv.NewStore(parent, journal.NewManager()) - - // set some items - nItems := 5 - for i := 0; i < nItems; i++ { - st.Set(keyFmt(i), valFmt(i)) - } - - // iterate over all of them - itr := st.Iterator(nil, nil) - i := 0 - for ; itr.Valid(); itr.Next() { - k, v := itr.Key(), itr.Value() - require.Equal(t, keyFmt(i), k) - require.Equal(t, valFmt(i), v) - i++ - } - require.Equal(t, nItems, i) - - // iterate over none - itr = st.Iterator(bz("money"), nil) - i = 0 - for ; itr.Valid(); itr.Next() { - i++ - } - require.Equal(t, 0, i) - - // iterate over lower - itr = st.Iterator(keyFmt(0), keyFmt(3)) - i = 0 - for ; itr.Valid(); itr.Next() { - k, v := itr.Key(), itr.Value() - require.Equal(t, keyFmt(i), k) - require.Equal(t, valFmt(i), v) - i++ - } - require.Equal(t, 3, i) - - // iterate over upper - itr = st.Iterator(keyFmt(2), keyFmt(4)) - i = 2 - for ; itr.Valid(); itr.Next() { - k, v := itr.Key(), itr.Value() - require.Equal(t, keyFmt(i), k) - require.Equal(t, valFmt(i), v) - i++ - } - require.Equal(t, 4, i) -} - -func TestCacheKVMergeIteratorBasics(t *testing.T) { - parent := newParent() - st := cachekv.NewStore(parent, journal.NewManager()) - - // set and delete an item in the cache, iterator should be empty - k, v := keyFmt(0), valFmt(0) - st.Set(k, v) - st.Delete(k) - assertIterateDomain(t, st, 0) - - // now set it and assert its there - st.Set(k, v) - assertIterateDomain(t, st, 1) - - // write it and assert its there - st.Write() - assertIterateDomain(t, st, 1) - - // remove it in cache and assert its not - st.Delete(k) - assertIterateDomain(t, st, 0) - - // write the delete and assert its not there - st.Write() - assertIterateDomain(t, st, 0) - - // add two keys and assert theyre there - k1, v1 := keyFmt(1), valFmt(1) - st.Set(k, v) - st.Set(k1, v1) - assertIterateDomain(t, st, 2) - - // write it and assert theyre there - st.Write() - assertIterateDomain(t, st, 2) - - // remove one in cache and assert its not - st.Delete(k1) - assertIterateDomain(t, st, 1) - - // write the delete and assert its not there - st.Write() - assertIterateDomain(t, st, 1) - - // delete the other key in cache and asserts its empty - st.Delete(k) - assertIterateDomain(t, st, 0) -} - -func TestCacheKVMergeIteratorDeleteLast(t *testing.T) { - parent := newParent() - st := cachekv.NewStore(parent, journal.NewManager()) - - // set some items and write them - nItems := 5 - for i := 0; i < nItems; i++ { - st.Set(keyFmt(i), valFmt(i)) - } - st.Write() - - // set some more items and leave dirty - for i := nItems; i < nItems*2; i++ { - st.Set(keyFmt(i), valFmt(i)) - } - - // iterate over all of them - assertIterateDomain(t, st, nItems*2) - - // delete them all - for i := 0; i < nItems*2; i++ { - last := nItems*2 - 1 - i - st.Delete(keyFmt(last)) - assertIterateDomain(t, st, last) - } -} - -func TestCacheKVMergeIteratorDeletes(t *testing.T) { - parent := newParent() - st := newCacheKVStoreFromParent(parent) - - // set some items and write them - nItems := 10 - for i := 0; i < nItems; i++ { - doOp(t, st, parent, opSet, i) - } - st.Write() - - // delete every other item, starting from 0 - for i := 0; i < nItems; i += 2 { - doOp(t, st, parent, opDel, i) - assertIterateDomainCompare(t, st, parent) - } - - // reset - parent = newParent() - st = newCacheKVStoreFromParent(parent) - - // set some items and write them - for i := 0; i < nItems; i++ { - doOp(t, st, parent, opSet, i) - } - st.Write() - - // delete every other item, starting from 1 - for i := 1; i < nItems; i += 2 { - doOp(t, st, parent, opDel, i) - assertIterateDomainCompare(t, st, parent) - } -} - -func TestCacheKVMergeIteratorChunks(t *testing.T) { - parent := newParent() - st := cachekv.NewStore(parent, journal.NewManager()) - - // Use the parent to check values on the merge iterator - // sets to the parent - setRange(t, st, parent, 0, 20) - setRange(t, st, parent, 40, 60) - st.Write() - - // sets to the cache - setRange(t, st, parent, 20, 40) - setRange(t, st, parent, 60, 80) - assertIterateDomainCheck(t, st, parent, []keyRange{{0, 80}}) - - // remove some parents and some cache - deleteRange(t, st, parent, 15, 25) - assertIterateDomainCheck(t, st, parent, []keyRange{{0, 15}, {25, 80}}) - - // remove some parents and some cache - deleteRange(t, st, parent, 35, 45) - assertIterateDomainCheck(t, st, parent, []keyRange{{0, 15}, {25, 35}, {45, 80}}) - - // write, add more to the cache, and delete some cache - st.Write() - setRange(t, st, parent, 38, 42) - deleteRange(t, st, parent, 40, 43) - assertIterateDomainCheck(t, st, parent, []keyRange{{0, 15}, {25, 35}, {38, 40}, {45, 80}}) -} - -func TestCacheKVMergeIteratorRandom(t *testing.T) { - parent := newParent() - st := cachekv.NewStore(parent, journal.NewManager()) - - start, end := 25, 975 - max := 1000 - setRange(t, st, parent, start, end) - - // do an op, test the iterator - for i := 0; i < 2000; i++ { - doRandomOp(t, st, parent, max) - assertIterateDomainCompare(t, st, parent) - } -} - -// ============================================================- -// do some random ops - -const ( - opSet = 0 - opSetRange = 1 - opDel = 2 - opDelRange = 3 - opWrite = 4 - - totalOps = 5 // number of possible operations -) - -func randInt(n int) int { - return tmrand.NewRand().Int() % n -} - -// useful for replaying a error case if we find one. -func doOp(t *testing.T, st types.CacheKVStore, truth types.KVStore, op int, args ...int) { - switch op { - case opSet: - k := args[0] - st.Set(keyFmt(k), valFmt(k)) - truth.Set(keyFmt(k), valFmt(k)) - case opSetRange: - start := args[0] - end := args[1] - setRange(t, st, truth, start, end) - case opDel: - k := args[0] - st.Delete(keyFmt(k)) - truth.Delete(keyFmt(k)) - case opDelRange: - start := args[0] - end := args[1] - deleteRange(t, st, truth, start, end) - case opWrite: - st.Write() - } -} - -func doRandomOp(t *testing.T, st types.CacheKVStore, truth types.KVStore, maxKey int) { - r := randInt(totalOps) - switch r { - case opSet: - k := randInt(maxKey) - st.Set(keyFmt(k), valFmt(k)) - truth.Set(keyFmt(k), valFmt(k)) - case opSetRange: - start := randInt(maxKey - 2) - end := randInt(maxKey-start) + start - setRange(t, st, truth, start, end) - case opDel: - k := randInt(maxKey) - st.Delete(keyFmt(k)) - truth.Delete(keyFmt(k)) - case opDelRange: - start := randInt(maxKey - 2) - end := randInt(maxKey-start) + start - deleteRange(t, st, truth, start, end) - case opWrite: - st.Write() - } -} - -// ============================================================- - -// iterate over whole domain. -func assertIterateDomain(t *testing.T, st types.KVStore, expectedN int) { - itr := st.Iterator(nil, nil) - i := 0 - for ; itr.Valid(); itr.Next() { - k, v := itr.Key(), itr.Value() - require.Equal(t, keyFmt(i), k) - require.Equal(t, valFmt(i), v) - i++ - } - require.Equal(t, expectedN, i) -} - -func assertIterateDomainCheck(t *testing.T, st types.KVStore, mem types.KVStore, r []keyRange) { - // iterate over each and check they match the other - itr := st.Iterator(nil, nil) - itr2 := mem.Iterator(nil, nil) // ground truth - - krc := newKeyRangeCounter(r) - i := 0 - - for ; krc.valid(); krc.next() { - require.True(t, itr.Valid()) - require.True(t, itr2.Valid()) - - // check the key/val matches the ground truth - k, v := itr.Key(), itr.Value() - k2, v2 := itr2.Key(), itr2.Value() - require.Equal(t, k, k2) - require.Equal(t, v, v2) - - // check they match the counter - require.Equal(t, k, keyFmt(krc.key())) - - itr.Next() - itr2.Next() - i++ - } - - require.False(t, itr.Valid()) - require.False(t, itr2.Valid()) -} - -func assertIterateDomainCompare(t *testing.T, st types.KVStore, mem types.KVStore) { - // iterate over each and check they match the other - itr := st.Iterator(nil, nil) - itr2 := mem.Iterator(nil, nil) // ground truth - checkIterators(t, itr, itr2) - checkIterators(t, itr2, itr) -} - -func checkIterators(t *testing.T, itr, itr2 types.Iterator) { - for ; itr.Valid(); itr.Next() { - require.True(t, itr2.Valid()) - k, v := itr.Key(), itr.Value() - k2, v2 := itr2.Key(), itr2.Value() - require.Equal(t, k, k2) - require.Equal(t, v, v2) - itr2.Next() - } - require.False(t, itr.Valid()) - require.False(t, itr2.Valid()) -} - -// ====================================-- - -func setRange(_ *testing.T, st types.KVStore, mem types.KVStore, start, end int) { - for i := start; i < end; i++ { - st.Set(keyFmt(i), valFmt(i)) - mem.Set(keyFmt(i), valFmt(i)) - } -} - -func deleteRange(_ *testing.T, st types.KVStore, mem types.KVStore, start, end int) { - for i := start; i < end; i++ { - st.Delete(keyFmt(i)) - mem.Delete(keyFmt(i)) - } -} - -// ====================================-- - -type keyRange struct { - start int - end int -} - -func (kr keyRange) len() int { - return kr.end - kr.start -} - -func newKeyRangeCounter(kr []keyRange) *keyRangeCounter { - return &keyRangeCounter{keyRanges: kr} -} - -// we can iterate over this and make sure our real iterators have all the right keys. -type keyRangeCounter struct { - rangeIdx int - idx int - keyRanges []keyRange -} - -func (krc *keyRangeCounter) valid() bool { - maxRangeIdx := len(krc.keyRanges) - 1 - maxRange := krc.keyRanges[maxRangeIdx] - - // if we're not in the max range, we're valid - if krc.rangeIdx <= maxRangeIdx && - krc.idx < maxRange.len() { - return true - } - - return false -} - -func (krc *keyRangeCounter) next() { - thisKeyRange := krc.keyRanges[krc.rangeIdx] - if krc.idx == thisKeyRange.len()-1 { - krc.rangeIdx++ - krc.idx = 0 - } else { - krc.idx++ - } -} - -func (krc *keyRangeCounter) key() int { - thisKeyRange := krc.keyRanges[krc.rangeIdx] - return thisKeyRange.start + krc.idx -} diff --git a/x/evm/plugins/state/store/cachemulti/store.go b/x/evm/plugins/state/store/cachemulti/store.go deleted file mode 100644 index 4ca517bd8..000000000 --- a/x/evm/plugins/state/store/cachemulti/store.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (C) 2022, Berachain Foundation. All rights reserved. -// See the file LICENSE for licensing terms. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -package cachemulti - -import ( - storetypes "github.com/cosmos/cosmos-sdk/store/types" - - "github.com/berachain/stargazer/x/evm/constants" - "github.com/berachain/stargazer/x/evm/plugins/state/store/cachekv" - "github.com/berachain/stargazer/x/evm/plugins/state/store/journal" -) - -// Compile-time check to ensure `Store` implements `storetypes.CacheMultiStore`. -var _ storetypes.CacheMultiStore = (*Store)(nil) - -// `Store` is a wrapper around the Cosmos SDK `MultiStore` which injects a custom EVM CacheKVStore. -type Store struct { - storetypes.MultiStore - stores map[storetypes.StoreKey]storetypes.CacheKVStore - JournalMgr *journal.Manager -} - -// `NewStoreFrom` creates and returns a new `Store` from a given MultiStore. -func NewStoreFrom(ms storetypes.MultiStore) *Store { - return &Store{ - MultiStore: ms, - stores: make(map[storetypes.StoreKey]storetypes.CacheKVStore), - JournalMgr: journal.NewManager(), - } -} - -// `GetKVStore` shadows the SDK's `storetypes.MultiStore` function. Routes native module calls to -// read the dirty state during an eth tx. Any state that is modified by evm statedb, and using the -// context passed in to StateDB, will be routed to a tx-specific cache kv store. -func (s *Store) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { - // check if cache kv store already used - if cacheKVStore, exists := s.stores[key]; exists { - return cacheKVStore - } - // get kvstore from cachemultistore and set cachekv to memory - kvstore := s.MultiStore.GetKVStore(key) - s.stores[key] = s.newCacheKVStore(key, kvstore) - return s.stores[key] -} - -// `Write` commits each of the individual cachekv stores to its corresponding parent kv stores. -// -// `Write` implements Cosmos SDK `storetypes.CacheMultiStore`. -func (s *Store) Write() { - // Safe from non-determinism, since order in which - // we write to the parent kv stores does not matter. - // - //#nosec:G705 - for _, cacheKVStore := range s.stores { - cacheKVStore.Write() - } - - // Clear the journal entries - s.JournalMgr = journal.NewManager() -} - -// `newCacheKVStore` returns a new CacheKVStore. If the `key` is an EVM storekey, it will return -// an EVM CacheKVStore. -func (s *Store) newCacheKVStore( - key storetypes.StoreKey, - kvstore storetypes.KVStore, -) storetypes.CacheKVStore { - if key.Name() == constants.EvmStoreKey { - return cachekv.NewEvmStore(kvstore, s.JournalMgr) - } - return cachekv.NewStore(kvstore, s.JournalMgr) -} diff --git a/x/evm/plugins/state/store/cachemulti/store_test.go b/x/evm/plugins/state/store/cachemulti/store_test.go deleted file mode 100644 index f3ec7b90c..000000000 --- a/x/evm/plugins/state/store/cachemulti/store_test.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (C) 2022, Berachain Foundation. All rights reserved. -// See the file LICENSE for licensing terms. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package cachemulti_test - -import ( - "reflect" - "testing" - - "github.com/berachain/stargazer/x/evm/plugins/state/store/cachekv" - "github.com/berachain/stargazer/x/evm/plugins/state/store/cachemulti" - sdkcachemulti "github.com/cosmos/cosmos-sdk/store/cachemulti" - "github.com/cosmos/cosmos-sdk/store/dbadapter" - storetypes "github.com/cosmos/cosmos-sdk/store/types" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - dbm "github.com/tendermint/tm-db" -) - -func TestCacheMulti(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "x/evm/plugins/state/store/cachemulti") -} - -var _ = Describe("CacheMulti", func() { - var ( - byte1 = []byte{1} - cms storetypes.CacheMultiStore - ms storetypes.MultiStore - accStoreParent storetypes.KVStore - accStoreCache storetypes.KVStore - accStoreKey = storetypes.NewKVStoreKey("acc") - evmStoreParent storetypes.KVStore - evmStoreCache storetypes.KVStore - evmStoreKey = storetypes.NewKVStoreKey("evm") - ) - - BeforeEach(func() { - stores := map[storetypes.StoreKey]storetypes.CacheWrapper{ - evmStoreKey: dbadapter.Store{DB: dbm.NewMemDB()}, - accStoreKey: dbadapter.Store{DB: dbm.NewMemDB()}, - } - ms = sdkcachemulti.NewStore( - dbm.NewMemDB(), - stores, map[string]storetypes.StoreKey{}, - nil, - nil, - ) - accStoreParent = ms.GetKVStore(accStoreKey) - evmStoreParent = ms.GetKVStore(evmStoreKey) - cms = cachemulti.NewStoreFrom(ms) - accStoreCache = cms.GetKVStore(accStoreKey) - evmStoreCache = cms.GetKVStore(evmStoreKey) - }) - - It("CorrectStoreType", func() { - // Test that the correct store type is returned - Expect(reflect.TypeOf(cms.GetKVStore(evmStoreKey))).To(Equal(reflect.TypeOf(&cachekv.EvmStore{}))) - Expect(reflect.TypeOf(cms.GetKVStore(accStoreKey))).To(Equal(reflect.TypeOf(&cachekv.Store{}))) - }) - - It("TestWrite", func() { - // Test that the cache multi store writes to the underlying stores - evmStoreCache.Set(byte1, byte1) - accStoreCache.Set(byte1, byte1) - Expect(evmStoreParent.Get(byte1)).To(BeNil()) - Expect(accStoreParent.Get(byte1)).To(BeNil()) - Expect(evmStoreCache.Get(byte1)).To(Equal(byte1)) - Expect(accStoreCache.Get(byte1)).To(Equal(byte1)) - - cms.Write() - - Expect(evmStoreParent.Get(byte1)).To(Equal(byte1)) - Expect(evmStoreParent.Get(byte1)).To(Equal(byte1)) - Expect(evmStoreCache.Get(byte1)).To(Equal(byte1)) - Expect(accStoreCache.Get(byte1)).To(Equal(byte1)) - }) - - It("TestWriteCacheMultiStore", func() { - // check that accStoreCache is not equal to accStoreParent - accStoreCache.Set(byte1, byte1) - Expect(accStoreCache.Has(byte1)).To(BeTrue()) - Expect(accStoreParent.Has(byte1)).To(BeFalse()) - - // check that getting accStore from cms is not the same as parent - accStoreCache2 := cms.GetKVStore(accStoreKey) - Expect(accStoreCache2.Has(byte1)).To(BeTrue()) - }) -}) diff --git a/x/evm/plugins/state/store/journal/manager.go b/x/evm/plugins/state/store/journal/manager.go deleted file mode 100644 index be9e07e97..000000000 --- a/x/evm/plugins/state/store/journal/manager.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (C) 2023, Berachain Foundation. All rights reserved. -// See the file LICENSE for licensing terms. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -package journal - -import ( - "github.com/berachain/stargazer/lib/ds" - "github.com/berachain/stargazer/lib/ds/stack" -) - -// Compile-time check to ensure `Manager` implements `ManagerI`. -var _ ds.CloneableStack[CacheEntry] = (*Manager)(nil) - -// `Manager` is a struct that holds a slice of CacheEntry instances. -type Manager struct { - // The journal manager is a stack. - ds.Stack[CacheEntry] -} - -// TODO: determine optimal initial capacity -const initialJournalCapacity = 512 - -// `NewManager` creates and returns a new Manager instance with an empty journal. -func NewManager() *Manager { - return &Manager{ - stack.New[CacheEntry](initialJournalCapacity), - } -} - -// `PopToSize` implements `StackI`. -func (jm *Manager) PopToSize(newSize int) CacheEntry { - // Revert and discard all journal entries after and including newSize. - for i := jm.Size() - 1; i >= newSize; i-- { - jm.Stack.PeekAt(i).Revert() - } - // Call parent. - return jm.Stack.PopToSize(newSize) -} - -// `Clone` returns a cloned journal by deep copying each CacheEntry. -// `Clone` implements `ManagerI[*Manager]`. -func (jm *Manager) Clone() ds.CloneableStack[CacheEntry] { - newManager := NewManager() - for i := 0; i < jm.Size(); i++ { - newManager.Push(jm.PeekAt(i).Clone()) - } - return newManager -} diff --git a/x/evm/plugins/state/store/journal/manager_test.go b/x/evm/plugins/state/store/journal/manager_test.go deleted file mode 100644 index 0c0ec673d..000000000 --- a/x/evm/plugins/state/store/journal/manager_test.go +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (C) 2023, Berachain Foundation. All rights reserved. -// See the file LICENSE for licensing terms. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package journal_test - -import ( - "fmt" - "math/rand" - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/berachain/stargazer/lib/ds" - "github.com/berachain/stargazer/x/evm/plugins/state/store/journal" - "github.com/berachain/stargazer/x/evm/plugins/state/store/journal/mock" -) - -func TestJournalManager(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "store/journal") -} - -var _ = Describe("Journal", func() { - var jm ds.CloneableStack[journal.CacheEntry] - var entries []*mock.CacheEntry - - BeforeEach(func() { - entries = make([]*mock.CacheEntry, 10) - jm = journal.NewManager() - for i := 0; i < 10; i++ { - entries[i] = mock.NewCacheEntry() - } - }) - - When("the journal is pushed to", func() { - BeforeEach(func() { - jm.Push(entries[0]) - }) - - It("should have a size of 1", func() { - Expect(jm.Size()).To(Equal(1)) - }) - - When("the journal is reverted to size 0", func() { - BeforeEach(func() { - jm.PopToSize(0) - }) - - It("should have a size of 0", func() { - Expect(jm.Size()).To(Equal(0)) - }) - }) - - When("the journal is pushed to 9 more times", func() { - BeforeEach(func() { - for i := 1; i <= 9; i++ { - jm.Push(entries[i]) - } - }) - - It(fmt.Sprintf("should have a size of %d", 10), func() { - Expect(jm.Size()).To(Equal(10)) - }) - - size := rand.Int() % 10 - When(fmt.Sprintf("the journal is reverted to size, %d", size), func() { - BeforeEach(func() { - jm.PopToSize(size) - }) - - It(fmt.Sprintf("should have a size of %d", size), func() { - Expect(jm.Size()).To(Equal(size)) - }) - }) - - When("the journal is reverted to size 5", func() { - BeforeEach(func() { - jm.PopToSize(5) - }) - - It("should have a size of 5", func() { - Expect(jm.Size()).To(Equal(5)) - }) - - It("should have called revert on last 5 entries", func() { - for i := len(entries) - 1; i >= 5; i-- { - Expect(entries[i].RevertCallCount()).To(Equal(1)) - } - }) - - It("should not have called revert on the first 5 entries", func() { - for i := 4; i >= 0; i-- { - Expect(entries[i].RevertCallCount()).To(Equal(0)) - } - }) - - When("the journal is cloned", func() { - var jm2 ds.CloneableStack[journal.CacheEntry] - BeforeEach(func() { - jm2 = jm.Clone() - }) - - It("should have a size of 5", func() { - Expect(jm2.Size()).To(Equal(5)) - }) - - It("should be a deep copy", func() { - for i := 0; i < 5; i++ { - Expect(jm2.PeekAt(i)).To(Equal(jm.PeekAt(i))) - Expect(jm2.PeekAt(0)).ToNot(BeIdenticalTo(jm.PeekAt(0))) - } - }) - - When("the original journal is reverted to size 0", func() { - BeforeEach(func() { - jm.PopToSize(0) - }) - - It("the clone should stillhave a size of 5", func() { - Expect(jm2.Size()).To(Equal(5)) - }) - }) - }) - }) - }) - }) -})