diff --git a/app/keepers.go b/app/keepers.go
index 6695a6194..be75b2357 100644
--- a/app/keepers.go
+++ b/app/keepers.go
@@ -140,6 +140,7 @@ type AppKeepers struct {
}
type privateKeepers struct {
+ bankBaseKeeper bankkeeper.BaseKeeper
capabilityKeeper *capabilitykeeper.Keeper
slashingKeeper slashingkeeper.Keeper
crisisKeeper crisiskeeper.Keeper
@@ -262,13 +263,26 @@ func (app *NibiruApp) InitKeepers(
sdk.GetConfig().GetBech32AccountAddrPrefix(),
govModuleAddr,
)
- app.BankKeeper = bankkeeper.NewBaseKeeper(
+
+ app.bankBaseKeeper = bankkeeper.NewBaseKeeper(
appCodec,
keys[banktypes.StoreKey],
app.AccountKeeper,
BlockedAddresses(),
govModuleAddr,
)
+ nibiruBankKeeper := evmkeeper.NibiruBankKeeper{
+ BaseKeeper: bankkeeper.NewBaseKeeper(
+ appCodec,
+ keys[banktypes.StoreKey],
+ app.AccountKeeper,
+ BlockedAddresses(),
+ govModuleAddr,
+ ),
+ StateDB: nil,
+ }
+ app.BankKeeper = nibiruBankKeeper
+
app.StakingKeeper = stakingkeeper.NewKeeper(
appCodec,
keys[stakingtypes.StoreKey],
@@ -370,7 +384,7 @@ func (app *NibiruApp) InitKeepers(
tkeys[evm.TransientKey],
authtypes.NewModuleAddress(govtypes.ModuleName),
app.AccountKeeper,
- app.BankKeeper,
+ &nibiruBankKeeper,
app.StakingKeeper,
cast.ToString(appOpts.Get("evm.tracer")),
)
@@ -605,7 +619,7 @@ func (app *NibiruApp) initAppModules(
),
auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, app.GetSubspace(authtypes.ModuleName)),
vesting.NewAppModule(app.AccountKeeper, app.BankKeeper),
- bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper, app.GetSubspace(banktypes.ModuleName)),
+ bank.NewAppModule(appCodec, app.bankBaseKeeper, app.AccountKeeper, app.GetSubspace(banktypes.ModuleName)),
capability.NewAppModule(appCodec, *app.capabilityKeeper, false),
feegrantmodule.NewAppModule(appCodec, app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, app.interfaceRegistry),
gov.NewAppModule(appCodec, &app.GovKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(govtypes.ModuleName)),
diff --git a/x/evm/deps.go b/x/evm/deps.go
index 04327db06..2325def18 100644
--- a/x/evm/deps.go
+++ b/x/evm/deps.go
@@ -4,7 +4,6 @@ package evm
import (
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
- bank "github.com/cosmos/cosmos-sdk/x/bank/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)
@@ -32,18 +31,6 @@ type AccountKeeper interface {
SetModuleAccount(ctx sdk.Context, macc authtypes.ModuleAccountI)
}
-// BankKeeper defines the expected interface needed to retrieve account balances.
-type BankKeeper interface {
- authtypes.BankKeeper
- GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin
- SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
- MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
- BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
-
- GetDenomMetaData(ctx sdk.Context, denom string) (metadata bank.Metadata, isFound bool)
- SetDenomMetaData(ctx sdk.Context, denomMetaData bank.Metadata)
-}
-
// StakingKeeper returns the historical headers kept in store.
type StakingKeeper interface {
GetHistoricalInfo(ctx sdk.Context, height int64) (stakingtypes.HistoricalInfo, bool)
diff --git a/x/evm/evmtest/test_deps.go b/x/evm/evmtest/test_deps.go
index 1810b1c8f..44fb34c31 100644
--- a/x/evm/evmtest/test_deps.go
+++ b/x/evm/evmtest/test_deps.go
@@ -46,7 +46,8 @@ func NewTestDeps() TestDeps {
}
func (deps TestDeps) StateDB() *statedb.StateDB {
- return statedb.New(deps.Ctx, &deps.App.EvmKeeper,
+ return deps.EvmKeeper.NewStateDB(
+ deps.Ctx,
statedb.NewEmptyTxConfig(
gethcommon.BytesToHash(deps.Ctx.HeaderHash().Bytes()),
),
diff --git a/x/evm/keeper/bank_extension.go b/x/evm/keeper/bank_extension.go
new file mode 100644
index 000000000..a2bcd6d27
--- /dev/null
+++ b/x/evm/keeper/bank_extension.go
@@ -0,0 +1,163 @@
+package keeper
+
+import (
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ auth "github.com/cosmos/cosmos-sdk/x/auth/types"
+ bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
+
+ "github.com/NibiruChain/nibiru/v2/eth"
+ "github.com/NibiruChain/nibiru/v2/x/evm"
+ "github.com/NibiruChain/nibiru/v2/x/evm/statedb"
+)
+
+var (
+ _ bankkeeper.Keeper = &NibiruBankKeeper{}
+ _ bankkeeper.SendKeeper = &NibiruBankKeeper{}
+)
+
+type NibiruBankKeeper struct {
+ bankkeeper.BaseKeeper
+ StateDB *statedb.StateDB
+ balanceChangesForStateDB uint64
+}
+
+func (evmKeeper *Keeper) NewStateDB(
+ ctx sdk.Context, txConfig statedb.TxConfig,
+) *statedb.StateDB {
+ stateDB := statedb.New(ctx, evmKeeper, txConfig)
+ bk := evmKeeper.bankKeeper
+ bk.StateDB = stateDB
+ bk.balanceChangesForStateDB = 0
+ return stateDB
+}
+
+// BalanceChangesForStateDB returns the count of [statedb.JournalChange] entries
+// that were added to the current [statedb.StateDB]
+func (bk *NibiruBankKeeper) BalanceChangesForStateDB() uint64 { return bk.balanceChangesForStateDB }
+
+func (bk NibiruBankKeeper) MintCoins(
+ ctx sdk.Context,
+ moduleName string,
+ coins sdk.Coins,
+) error {
+ // Use the embedded function from [bankkeeper.Keeper]
+ if err := bk.BaseKeeper.MintCoins(ctx, moduleName, coins); err != nil {
+ return err
+ }
+ if findEtherBalanceChangeFromCoins(coins) {
+ moduleBech32Addr := auth.NewModuleAddress(evm.ModuleName)
+ bk.SyncStateDBWithAccount(ctx, moduleBech32Addr)
+ }
+ return nil
+}
+
+func (bk NibiruBankKeeper) BurnCoins(
+ ctx sdk.Context,
+ moduleName string,
+ coins sdk.Coins,
+) error {
+ // Use the embedded function from [bankkeeper.Keeper]
+ if err := bk.BaseKeeper.BurnCoins(ctx, moduleName, coins); err != nil {
+ return err
+ }
+ if findEtherBalanceChangeFromCoins(coins) {
+ moduleBech32Addr := auth.NewModuleAddress(evm.ModuleName)
+ bk.SyncStateDBWithAccount(ctx, moduleBech32Addr)
+ }
+ return nil
+}
+
+func (bk NibiruBankKeeper) SendCoins(
+ ctx sdk.Context,
+ fromAddr sdk.AccAddress,
+ toAddr sdk.AccAddress,
+ coins sdk.Coins,
+) error {
+ // Use the embedded function from [bankkeeper.Keeper]
+ if err := bk.BaseKeeper.SendCoins(ctx, fromAddr, toAddr, coins); err != nil {
+ return err
+ }
+ if findEtherBalanceChangeFromCoins(coins) {
+ bk.SyncStateDBWithAccount(ctx, fromAddr)
+ bk.SyncStateDBWithAccount(ctx, toAddr)
+ }
+ return nil
+}
+
+func (bk *NibiruBankKeeper) SyncStateDBWithAccount(
+ ctx sdk.Context, acc sdk.AccAddress,
+) {
+ // If there's no StateDB set, it means we're not in an EthereumTx.
+ if bk.StateDB == nil {
+ return
+ }
+ balanceWei := evm.NativeToWei(
+ bk.GetBalance(ctx, acc, evm.EVMBankDenom).Amount.BigInt(),
+ )
+ bk.StateDB.SetBalanceWei(eth.NibiruAddrToEthAddr(acc), balanceWei)
+ bk.balanceChangesForStateDB += 1
+}
+
+func findEtherBalanceChangeFromCoins(coins sdk.Coins) (found bool) {
+ for _, c := range coins {
+ if c.Denom == evm.EVMBankDenom {
+ return true
+ }
+ }
+ return false
+}
+
+func (bk NibiruBankKeeper) SendCoinsFromAccountToModule(
+ ctx sdk.Context,
+ senderAddr sdk.AccAddress,
+ recipientModule string,
+ coins sdk.Coins,
+) error {
+ // Use the embedded function from [bankkeeper.Keeper]
+ if err := bk.BaseKeeper.SendCoinsFromAccountToModule(ctx, senderAddr, recipientModule, coins); err != nil {
+ return err
+ }
+ if findEtherBalanceChangeFromCoins(coins) {
+ bk.SyncStateDBWithAccount(ctx, senderAddr)
+ moduleBech32Addr := auth.NewModuleAddress(recipientModule)
+ bk.SyncStateDBWithAccount(ctx, moduleBech32Addr)
+ }
+ return nil
+}
+
+func (bk NibiruBankKeeper) SendCoinsFromModuleToAccount(
+ ctx sdk.Context,
+ senderModule string,
+ recipientAddr sdk.AccAddress,
+ coins sdk.Coins,
+) error {
+ // Use the embedded function from [bankkeeper.Keeper]
+ if err := bk.BaseKeeper.SendCoinsFromModuleToAccount(ctx, senderModule, recipientAddr, coins); err != nil {
+ return err
+ }
+ if findEtherBalanceChangeFromCoins(coins) {
+ moduleBech32Addr := auth.NewModuleAddress(senderModule)
+ bk.SyncStateDBWithAccount(ctx, moduleBech32Addr)
+ bk.SyncStateDBWithAccount(ctx, recipientAddr)
+ }
+ return nil
+}
+
+func (bk NibiruBankKeeper) SendCoinsFromModuleToModule(
+ ctx sdk.Context,
+ senderModule string,
+ recipientModule string,
+ coins sdk.Coins,
+) error {
+ // Use the embedded function from [bankkeeper.Keeper]
+ if err := bk.BaseKeeper.SendCoinsFromModuleToModule(ctx, senderModule, recipientModule, coins); err != nil {
+ return err
+ }
+ if findEtherBalanceChangeFromCoins(coins) {
+ senderBech32Addr := auth.NewModuleAddress(senderModule)
+ recipientBech32Addr := auth.NewModuleAddress(recipientModule)
+ bk.SyncStateDBWithAccount(ctx, senderBech32Addr)
+ bk.SyncStateDBWithAccount(ctx, recipientBech32Addr)
+ }
+ return nil
+}
diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go
index 49ea0c9bf..c6b0720d8 100644
--- a/x/evm/keeper/keeper.go
+++ b/x/evm/keeper/keeper.go
@@ -40,7 +40,7 @@ type Keeper struct {
// this should be the x/gov module account.
authority sdk.AccAddress
- bankKeeper evm.BankKeeper
+ bankKeeper *NibiruBankKeeper
accountKeeper evm.AccountKeeper
stakingKeeper evm.StakingKeeper
@@ -63,13 +63,14 @@ func NewKeeper(
storeKey, transientKey storetypes.StoreKey,
authority sdk.AccAddress,
accKeeper evm.AccountKeeper,
- bankKeeper evm.BankKeeper,
+ bankKeeper *NibiruBankKeeper,
stakingKeeper evm.StakingKeeper,
tracer string,
) Keeper {
if err := sdk.VerifyAddressFormat(authority); err != nil {
panic(err)
}
+
return Keeper{
cdc: cdc,
storeKey: storeKey,
diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go
index 89a249dd3..be8abb236 100644
--- a/x/evm/keeper/msg_server.go
+++ b/x/evm/keeper/msg_server.go
@@ -252,7 +252,7 @@ func (k *Keeper) ApplyEvmMsg(ctx sdk.Context,
vmErr error // vm errors do not effect consensus and are therefore not assigned to err
)
- stateDB := statedb.New(ctx, k, txConfig)
+ stateDB := k.NewStateDB(ctx, txConfig)
evmObj = k.NewEVM(ctx, msg, evmConfig, tracer, stateDB)
leftoverGas := msg.Gas()
diff --git a/x/evm/keeper/statedb.go b/x/evm/keeper/statedb.go
index 6eb46f990..575962d02 100644
--- a/x/evm/keeper/statedb.go
+++ b/x/evm/keeper/statedb.go
@@ -65,31 +65,34 @@ func (k *Keeper) ForEachStorage(
}
}
-// SetAccBalance update account's balance, compare with current balance first, then decide to mint or burn.
+// SetAccBalance update account's balance, compare with current balance first,
+// then decide to mint or burn.
+// Implements the `statedb.Keeper` interface.
+// Only called by `StateDB.Commit()`.
func (k *Keeper) SetAccBalance(
ctx sdk.Context, addr gethcommon.Address, amountEvmDenom *big.Int,
) error {
nativeAddr := sdk.AccAddress(addr.Bytes())
- balance := k.bankKeeper.GetBalance(ctx, nativeAddr, evm.EVMBankDenom).Amount.BigInt()
+ balance := k.bankKeeper.BaseKeeper.GetBalance(ctx, nativeAddr, evm.EVMBankDenom).Amount.BigInt()
delta := new(big.Int).Sub(amountEvmDenom, balance)
switch delta.Sign() {
case 1:
// mint
coins := sdk.NewCoins(sdk.NewCoin(evm.EVMBankDenom, sdkmath.NewIntFromBigInt(delta)))
- if err := k.bankKeeper.MintCoins(ctx, evm.ModuleName, coins); err != nil {
+ if err := k.bankKeeper.BaseKeeper.MintCoins(ctx, evm.ModuleName, coins); err != nil {
return err
}
- if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, evm.ModuleName, nativeAddr, coins); err != nil {
+ if err := k.bankKeeper.BaseKeeper.SendCoinsFromModuleToAccount(ctx, evm.ModuleName, nativeAddr, coins); err != nil {
return err
}
case -1:
// burn
coins := sdk.NewCoins(sdk.NewCoin(evm.EVMBankDenom, sdkmath.NewIntFromBigInt(new(big.Int).Neg(delta))))
- if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, nativeAddr, evm.ModuleName, coins); err != nil {
+ if err := k.bankKeeper.BaseKeeper.SendCoinsFromAccountToModule(ctx, nativeAddr, evm.ModuleName, coins); err != nil {
return err
}
- if err := k.bankKeeper.BurnCoins(ctx, evm.ModuleName, coins); err != nil {
+ if err := k.bankKeeper.BaseKeeper.BurnCoins(ctx, evm.ModuleName, coins); err != nil {
return err
}
default:
diff --git a/x/evm/precompile/funtoken.go b/x/evm/precompile/funtoken.go
index b95fd0084..505c106b4 100644
--- a/x/evm/precompile/funtoken.go
+++ b/x/evm/precompile/funtoken.go
@@ -72,7 +72,7 @@ func (p precompileFunToken) Run(
if err != nil {
return nil, err
}
- return bz, OnRunEnd(start.StateDB, start.SnapshotBeforeRun, p.Address())
+ return bz, err
}
func PrecompileFunToken(keepers keepers.PublicKeepers) vm.PrecompiledContract {
diff --git a/x/evm/precompile/precompile.go b/x/evm/precompile/precompile.go
index ecf116f16..40d0c74b4 100644
--- a/x/evm/precompile/precompile.go
+++ b/x/evm/precompile/precompile.go
@@ -148,10 +148,7 @@ type OnRunStartResult struct {
StateDB *statedb.StateDB
- // SnapshotBeforeRun captures the state before precompile execution to enable
- // proper state reversal if the call fails or if [statedb.JournalChange]
- // is reverted in general.
- SnapshotBeforeRun statedb.PrecompileCalled
+ PrecompileJournalEntry statedb.PrecompileCalled
}
// OnRunStart prepares the execution environment for a precompiled contract call.
@@ -193,43 +190,26 @@ func OnRunStart(
err = fmt.Errorf("failed to load the sdk.Context from the EVM StateDB")
return
}
- cacheCtx, snapshot := stateDB.CacheCtxForPrecompile(contract.Address())
- stateDB.SavePrecompileSnapshotToJournal(contract.Address(), snapshot)
+
+ // journalEntry captures the state before precompile execution to enable
+ // proper state reversal if the call fails or if [statedb.JournalChange]
+ // is reverted in general.
+ cacheCtx, journalEntry := stateDB.CacheCtxForPrecompile(contract.Address())
+ if err = stateDB.SavePrecompileCalledJournalChange(contract.Address(), journalEntry); err != nil {
+ return res, err
+ }
if err = stateDB.CommitCacheCtx(); err != nil {
return res, fmt.Errorf("error committing dirty journal entries: %w", err)
}
return OnRunStartResult{
- Args: args,
- Ctx: cacheCtx,
- Method: method,
- StateDB: stateDB,
- SnapshotBeforeRun: snapshot,
+ Args: args,
+ Ctx: cacheCtx,
+ Method: method,
+ StateDB: stateDB,
}, nil
}
-// OnRunEnd finalizes a precompile execution by saving its state snapshot to the
-// journal. This ensures that any state changes can be properly reverted if needed.
-//
-// Args:
-// - stateDB: The EVM state database
-// - snapshot: The state snapshot taken before the precompile executed
-// - precompileAddr: The address of the precompiled contract
-//
-// The snapshot is critical for maintaining state consistency when:
-// - The operation gets reverted ([statedb.JournalChange] Revert).
-// - The precompile modifies state in other modules (e.g., bank, wasm)
-// - Multiple precompiles are called within a single transaction
-func OnRunEnd(
- stateDB *statedb.StateDB,
- snapshot statedb.PrecompileCalled,
- precompileAddr gethcommon.Address,
-) error {
- // TODO: UD-DEBUG: Not needed because it's been added to start.
- // return stateDB.SavePrecompileSnapshotToJournal(precompileAddr, snapshot)
- return nil
-}
-
var precompileMethodIsTxMap map[PrecompileMethod]bool = map[PrecompileMethod]bool{
WasmMethod_execute: true,
WasmMethod_instantiate: true,
diff --git a/x/evm/precompile/wasm.go b/x/evm/precompile/wasm.go
index 49fe40f87..a7b21684c 100644
--- a/x/evm/precompile/wasm.go
+++ b/x/evm/precompile/wasm.go
@@ -62,7 +62,7 @@ func (p precompileWasm) Run(
if err != nil {
return nil, err
}
- return bz, OnRunEnd(start.StateDB, start.SnapshotBeforeRun, p.Address())
+ return bz, err
}
type precompileWasm struct {
diff --git a/x/evm/statedb/access_list.go b/x/evm/statedb/access_list.go
index 4513a9164..f62b45171 100644
--- a/x/evm/statedb/access_list.go
+++ b/x/evm/statedb/access_list.go
@@ -1,3 +1,5 @@
+package statedb
+
// Copyright 2020 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
@@ -14,8 +16,6 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package statedb
-
import (
"github.com/ethereum/go-ethereum/common"
)
diff --git a/x/evm/statedb/config.go b/x/evm/statedb/config.go
index 887f591c5..417e480ac 100644
--- a/x/evm/statedb/config.go
+++ b/x/evm/statedb/config.go
@@ -1,6 +1,7 @@
-// Copyright (c) 2023-2024 Nibi, Inc.
package statedb
+// Copyright (c) 2023-2024 Nibi, Inc.
+
import (
"math/big"
diff --git a/x/evm/statedb/interfaces.go b/x/evm/statedb/interfaces.go
index a9a0f37b4..c242771ca 100644
--- a/x/evm/statedb/interfaces.go
+++ b/x/evm/statedb/interfaces.go
@@ -1,22 +1,12 @@
-// Copyright (c) 2023-2024 Nibi, Inc.
package statedb
+// Copyright (c) 2023-2024 Nibi, Inc.
+
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/vm"
)
-// ExtStateDB defines an extension to the interface provided by the go-ethereum
-// codebase to support additional state transition functionalities. In particular
-// it supports appending a new entry to the state journal through
-// AppendJournalEntry so that the state can be reverted after running
-// stateful precompiled contracts.
-type ExtStateDB interface {
- vm.StateDB
- AppendJournalEntry(JournalChange)
-}
-
// Keeper provide underlying storage of StateDB
type Keeper interface {
// GetAccount: Ethereum account getter for a [statedb.Account].
diff --git a/x/evm/statedb/journal.go b/x/evm/statedb/journal.go
index d5af3c479..e684fd574 100644
--- a/x/evm/statedb/journal.go
+++ b/x/evm/statedb/journal.go
@@ -1,3 +1,5 @@
+package statedb
+
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
@@ -14,8 +16,6 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package statedb
-
import (
"bytes"
"math/big"
@@ -359,7 +359,6 @@ func (ch accessListAddSlotChange) Dirtied() *common.Address {
type PrecompileCalled struct {
MultiStore store.CacheMultiStore
Events sdk.Events
- Precompile common.Address
}
var _ JournalChange = PrecompileCalled{}
@@ -373,16 +372,14 @@ func (ch PrecompileCalled) Revert(s *StateDB) {
s.cacheCtx = s.cacheCtx.WithMultiStore(ch.MultiStore)
// Rewrite the `writeCacheCtxFn` using the same logic as sdk.Context.CacheCtx
s.writeToCommitCtxFromCacheCtx = func() {
- s.ctx.EventManager().EmitEvents(ch.Events)
+ s.evmTxCtx.EventManager().EmitEvents(ch.Events)
// TODO: UD-DEBUG: Overwriting events might fix an issue with
// appending too many
- // s.ctx.WithEventManager(
- // sdk.NewEventManager().EmitEvents(ch.Events),
- // )
+ // Check correctness of the emitted events
ch.MultiStore.Write()
}
}
func (ch PrecompileCalled) Dirtied() *common.Address {
- return &ch.Precompile
+ return nil
}
diff --git a/x/evm/statedb/journal_test.go b/x/evm/statedb/journal_test.go
index 6be5571f9..046fc514c 100644
--- a/x/evm/statedb/journal_test.go
+++ b/x/evm/statedb/journal_test.go
@@ -74,7 +74,8 @@ func (s *Suite) TestComplexJournalChanges() {
s.FailNow("expected 4 dirty journal changes")
}
- err = stateDB.Commit() // Dirties should be gone
+ s.T().Log("StateDB.Commit, then Dirties should be gone")
+ err = stateDB.Commit()
s.NoError(err)
if stateDB.DirtiesCount() != 0 {
debugDirtiesCountMismatch(stateDB, s.T())
@@ -212,7 +213,7 @@ snapshots and see the prior states.`))
)
err = stateDB.Commit()
- deps.Ctx = stateDB.GetContext()
+ deps.Ctx = stateDB.GetEvmTxContext()
test.AssertWasmCounterState(
&s.Suite, deps, wasmContract, 7, // state before precompile called
)
@@ -221,7 +222,6 @@ snapshots and see the prior states.`))
s.Run("too many precompile calls in one tx will fail", func() {
// currently
// evmObj
-
})
}
diff --git a/x/evm/statedb/state_object.go b/x/evm/statedb/state_object.go
index 3e546362b..28ba2d85a 100644
--- a/x/evm/statedb/state_object.go
+++ b/x/evm/statedb/state_object.go
@@ -1,6 +1,7 @@
-// Copyright (c) 2023-2024 Nibi, Inc.
package statedb
+// Copyright (c) 2023-2024 Nibi, Inc.
+
import (
"bytes"
"math/big"
@@ -121,9 +122,8 @@ type stateObject struct {
address common.Address
// flags
- DirtyCode bool
- Suicided bool
- IsPrecompile bool
+ DirtyCode bool
+ Suicided bool
}
// newObject creates a state object.
@@ -199,7 +199,7 @@ func (s *stateObject) Code() []byte {
if bytes.Equal(s.CodeHash(), emptyCodeHash) {
return nil
}
- code := s.db.keeper.GetCode(s.db.ctx, common.BytesToHash(s.CodeHash()))
+ code := s.db.keeper.GetCode(s.db.evmTxCtx, common.BytesToHash(s.CodeHash()))
s.code = code
return code
}
@@ -261,7 +261,7 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
return value
}
// If no live objects are available, load it from keeper
- value := s.db.keeper.GetState(s.db.ctx, s.Address(), key)
+ value := s.db.keeper.GetState(s.db.evmTxCtx, s.Address(), key)
s.OriginStorage[key] = value
return value
}
diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go
index 81a4ce9e8..957da7888 100644
--- a/x/evm/statedb/statedb.go
+++ b/x/evm/statedb/statedb.go
@@ -1,6 +1,22 @@
-// Copyright (c) 2023-2024 Nibi, Inc.
+// The "evm/statedb" package implements a go-ethereum [vm.StateDB] with state
+// management and journal changes specific to the Nibiru EVM.
+//
+// This package plays a critical role in managing the state of accounts,
+// contracts, and storage while handling atomicity, caching, and state
+// modifications. It ensures that state transitions made during the
+// execution of smart contracts are either committed or reverted based
+// on transaction outcomes.
+//
+// StateDB structs used to store anything within the state tree, including
+// accounts, contracts, and contract storage.
+// Note that Nibiru's state tree is an IAVL tree, which differs from the Merkle
+// Patricia Trie structure seen on Ethereum mainnet.
+//
+// StateDBs also take care of caching and handling nested states.
package statedb
+// Copyright (c) 2023-2024 Nibi, Inc.
+
import (
"fmt"
"math/big"
@@ -14,14 +30,6 @@ import (
"github.com/ethereum/go-ethereum/crypto"
)
-// revision is the identifier of a version of state.
-// it consists of an auto-increment id and a journal index.
-// it's safer to use than using journal index alone.
-type revision struct {
- id int
- journalIndex int
-}
-
var _ vm.StateDB = &StateDB{}
// StateDB structs within the ethereum protocol are used to store anything
@@ -31,8 +39,9 @@ var _ vm.StateDB = &StateDB{}
// * Accounts
type StateDB struct {
keeper Keeper
- // ctx is the persistent context used for official `StateDB.Commit` calls.
- ctx sdk.Context
+
+ // evmTxCtx is the persistent context used for official `StateDB.Commit` calls.
+ evmTxCtx sdk.Context
// Journal of state modifications. This is the backbone of
// Snapshot and RevertToSnapshot.
@@ -51,13 +60,13 @@ type StateDB struct {
cacheCtx sdk.Context
// writeToCommitCtxFromCacheCtx is the "write" function received from
- // `s.ctx.CacheContext()`. It saves mutations on s.cacheCtx to the StateDB's
- // commit context (s.ctx). This synchronizes the multistore and event manager
+ // `s.evmTxCtx.CacheContext()`. It saves mutations on s.cacheCtx to the StateDB's
+ // commit context (s.evmTxCtx). This synchronizes the multistore and event manager
// of the two contexts.
writeToCommitCtxFromCacheCtx func()
// The number of precompiled contract calls within the current transaction
- precompileSnapshotsCount uint8
+ multistoreCacheCount uint8
// The refund counter, also used by state transitioning.
refund uint64
@@ -73,7 +82,7 @@ type StateDB struct {
func New(ctx sdk.Context, keeper Keeper, txConfig TxConfig) *StateDB {
return &StateDB{
keeper: keeper,
- ctx: ctx,
+ evmTxCtx: ctx,
stateObjects: make(map[common.Address]*stateObject),
Journal: newJournal(),
accessList: newAccessList(),
@@ -82,14 +91,22 @@ func New(ctx sdk.Context, keeper Keeper, txConfig TxConfig) *StateDB {
}
}
+// revision is the identifier of a version of state.
+// it consists of an auto-increment id and a journal index.
+// it's safer to use than using journal index alone.
+type revision struct {
+ id int
+ journalIndex int
+}
+
// Keeper returns the underlying `Keeper`
func (s *StateDB) Keeper() Keeper {
return s.keeper
}
-// GetContext returns the transaction Context.
-func (s *StateDB) GetContext() sdk.Context {
- return s.ctx
+// GetEvmTxContext returns the EVM transaction context.
+func (s *StateDB) GetEvmTxContext() sdk.Context {
+ return s.evmTxCtx
}
// GetCacheContext: Getter for testing purposes.
@@ -237,17 +254,8 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
return obj
}
- if s.keeper.IsPrecompile(addr) {
- obj := newObject(s, addr, Account{
- Nonce: 0,
- })
- obj.IsPrecompile = true
- s.setStateObject(obj)
- return obj
- }
-
// If no live objects are available, load it from keeper
- account := s.keeper.GetAccount(s.ctx, addr)
+ account := s.keeper.GetAccount(s.evmTxCtx, addr)
if account == nil {
return nil
}
@@ -308,7 +316,7 @@ func (s *StateDB) ForEachStorage(addr common.Address, cb func(key, value common.
if so == nil {
return nil
}
- s.keeper.ForEachStorage(s.ctx, addr, func(key, value common.Hash) bool {
+ s.keeper.ForEachStorage(s.evmTxCtx, addr, func(key, value common.Hash) bool {
if value, dirty := so.DirtyStorage[key]; dirty {
return cb(key, value)
}
@@ -497,19 +505,22 @@ func errorf(format string, args ...any) error {
// Commit writes the dirty journal state changes to the EVM Keeper. The
// StateDB object cannot be reused after [Commit] has completed. A new
// object needs to be created from the EVM.
+//
+// cacheCtxSyncNeeded: If one of the [Nibiru-Specific Precompiled Contracts] was
+// called, a [JournalChange] of type [PrecompileSnapshotBeforeRun] gets added and
+// we branch off a cache of the commit context (s.evmTxCtx).
+//
+// [Nibiru-Specific Precompiled Contracts]: https://nibiru.fi/docs/evm/precompiles/nibiru.html
func (s *StateDB) Commit() error {
if s.writeToCommitCtxFromCacheCtx != nil {
- // cacheCtxSyncNeeded: If a precompile was called, a [JournalChange]
- // of type [PrecompileSnapshotBeforeRun] gets added and we branch off a
- // cache of the commit context (s.ctx).
s.writeToCommitCtxFromCacheCtx()
}
- return s.commitCtx(s.GetContext())
+ return s.commitCtx(s.GetEvmTxContext())
}
// CommitCacheCtx is identical to [StateDB.Commit], except it:
// (1) uses the cacheCtx of the [StateDB] and
-// (2) does not save mutations of the cacheCtx to the commit context (s.ctx).
+// (2) does not save mutations of the cacheCtx to the commit context (s.evmTxCtx).
// The reason for (2) is that the overall EVM transaction (block, not internal)
// is only finalized when [Commit] is called, not when [CommitCacheCtx] is
// called.
@@ -524,13 +535,10 @@ func (s *StateDB) commitCtx(ctx sdk.Context) error {
for _, addr := range s.Journal.sortedDirties() {
obj := s.getStateObject(addr)
if obj == nil {
- continue
- }
- if obj.IsPrecompile {
- // TODO: UD-DEBUG: Assume clean to pretend for tests
s.Journal.dirties[addr] = 0
continue
- } else if obj.Suicided {
+ }
+ if obj.Suicided {
// Invariant: After [StateDB.Suicide] for some address, the
// corresponding account's state object is only available until the
// state is committed.
@@ -568,16 +576,15 @@ func (s *StateDB) CacheCtxForPrecompile(precompileAddr common.Address) (
sdk.Context, PrecompileCalled,
) {
if s.writeToCommitCtxFromCacheCtx == nil {
- s.cacheCtx, s.writeToCommitCtxFromCacheCtx = s.ctx.CacheContext()
+ s.cacheCtx, s.writeToCommitCtxFromCacheCtx = s.evmTxCtx.CacheContext()
}
return s.cacheCtx, PrecompileCalled{
MultiStore: s.cacheCtx.MultiStore().(store.CacheMultiStore).Copy(),
Events: s.cacheCtx.EventManager().Events(),
- Precompile: precompileAddr,
}
}
-// SavePrecompileSnapshotToJournal adds a snapshot of the commit multistore
+// SavePrecompileCalledJournalChange adds a snapshot of the commit multistore
// ([PrecompileCalled]) to the [StateDB] journal at the end of
// successful invocation of a precompiled contract. This is necessary to revert
// intermediate states where an EVM contract augments the multistore with a
@@ -585,20 +592,22 @@ func (s *StateDB) CacheCtxForPrecompile(precompileAddr common.Address) (
// modules.
//
// See [PrecompileCalled] for more info.
-func (s *StateDB) SavePrecompileSnapshotToJournal(
+func (s *StateDB) SavePrecompileCalledJournalChange(
precompileAddr common.Address,
- snapshot PrecompileCalled,
+ journalChange PrecompileCalled,
) error {
- obj := s.getOrNewStateObject(precompileAddr)
- obj.db.Journal.append(snapshot)
- s.precompileSnapshotsCount++
- if s.precompileSnapshotsCount > maxPrecompileCalls {
- return fmt.Errorf("exceeded maximum allowed number of precompiled contract calls in one transaction (%d)", maxPrecompileCalls)
+ s.Journal.append(journalChange)
+ s.multistoreCacheCount++
+ if s.multistoreCacheCount > maxMultistoreCacheCount {
+ return fmt.Errorf(
+ "exceeded maximum number Nibiru-specific precompiled contract calls in one transaction (%d). Called address %s",
+ maxMultistoreCacheCount, precompileAddr.Hex(),
+ )
}
return nil
}
-const maxPrecompileCalls uint8 = 10
+const maxMultistoreCacheCount uint8 = 10
// StateObjects: Returns a copy of the [StateDB.stateObjects] map.
func (s *StateDB) StateObjects() map[common.Address]*stateObject {