From 295a2d92eb63b49ecf30e017f9b1cc4058b6cae2 Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Fri, 25 Oct 2024 03:06:40 -0500 Subject: [PATCH 1/9] statedb: add cacheing for multistore before precompile runs --- x/evm/keeper/erc20.go | 6 +-- x/evm/keeper/precompiles.go | 4 ++ x/evm/precompile/funtoken.go | 3 +- x/evm/precompile/precompile.go | 38 ++++++++++--- x/evm/precompile/test/export.go | 3 +- x/evm/precompile/wasm.go | 4 +- x/evm/precompile/wasm_test.go | 4 +- x/evm/statedb/interfaces.go | 2 + x/evm/statedb/journal.go | 41 ++++++++++++++ x/evm/statedb/journal_test.go | 61 +++++++++++++-------- x/evm/statedb/state_object.go | 5 +- x/evm/statedb/statedb.go | 94 +++++++++++++++++++++++++++++++-- 12 files changed, 219 insertions(+), 46 deletions(-) diff --git a/x/evm/keeper/erc20.go b/x/evm/keeper/erc20.go index 10404bea4..328452ecf 100644 --- a/x/evm/keeper/erc20.go +++ b/x/evm/keeper/erc20.go @@ -218,11 +218,8 @@ func (k Keeper) CallContractWithInput( // sent by a user txConfig := k.TxConfig(ctx, gethcommon.BigToHash(big.NewInt(0))) - // Using tmp context to not modify the state in case of evm revert - tmpCtx, commitCtx := ctx.CacheContext() - evmResp, evmObj, err = k.ApplyEvmMsg( - tmpCtx, evmMsg, evm.NewNoOpTracer(), commit, evmCfg, txConfig, + ctx, evmMsg, evm.NewNoOpTracer(), commit, evmCfg, txConfig, ) if err != nil { // We don't know the actual gas used, so consuming the gas limit @@ -245,7 +242,6 @@ func (k Keeper) CallContractWithInput( } else { // Success, committing the state to ctx if commit { - commitCtx() totalGasUsed, err := k.AddToBlockGasUsed(ctx, evmResp.GasUsed) if err != nil { k.ResetGasMeterAndConsumeGas(ctx, ctx.GasMeter().Limit()) diff --git a/x/evm/keeper/precompiles.go b/x/evm/keeper/precompiles.go index 965866660..3b1a0e480 100644 --- a/x/evm/keeper/precompiles.go +++ b/x/evm/keeper/precompiles.go @@ -21,3 +21,7 @@ func (k *Keeper) AddPrecompiles( } } } + +func (k *Keeper) IsPrecompile(addr gethcommon.Address) bool { + return k.precompiles.Has(addr) +} diff --git a/x/evm/precompile/funtoken.go b/x/evm/precompile/funtoken.go index 5c585c2e9..b95fd0084 100644 --- a/x/evm/precompile/funtoken.go +++ b/x/evm/precompile/funtoken.go @@ -72,8 +72,7 @@ func (p precompileFunToken) Run( if err != nil { return nil, err } - // Dirty journal entries in `StateDB` must be committed - return bz, start.StateDB.Commit() + return bz, OnRunEnd(start.StateDB, start.SnapshotBeforeRun, p.Address()) } func PrecompileFunToken(keepers keepers.PublicKeepers) vm.PrecompiledContract { diff --git a/x/evm/precompile/precompile.go b/x/evm/precompile/precompile.go index a6bbfefc4..428af9775 100644 --- a/x/evm/precompile/precompile.go +++ b/x/evm/precompile/precompile.go @@ -147,6 +147,11 @@ type OnRunStartResult struct { Method *gethabi.Method 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.PrecompileSnapshotBeforeRun } // OnRunStart prepares the execution environment for a precompiled contract call. @@ -188,19 +193,40 @@ func OnRunStart( err = fmt.Errorf("failed to load the sdk.Context from the EVM StateDB") return } - ctx := stateDB.GetContext() - if err = stateDB.Commit(); err != nil { + cacheCtx, snapshot := stateDB.CacheCtxForPrecompile(contract.Address()) + if err = stateDB.CommitCacheCtx(); err != nil { return res, fmt.Errorf("error committing dirty journal entries: %w", err) } return OnRunStartResult{ - Args: args, - Ctx: ctx, - Method: method, - StateDB: stateDB, + Args: args, + Ctx: cacheCtx, + Method: method, + StateDB: stateDB, + SnapshotBeforeRun: snapshot, }, 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.PrecompileSnapshotBeforeRun, + precompileAddr gethcommon.Address, +) error { + return stateDB.SavePrecompileSnapshotToJournal(precompileAddr, snapshot) +} + var precompileMethodIsTxMap map[PrecompileMethod]bool = map[PrecompileMethod]bool{ WasmMethod_execute: true, WasmMethod_instantiate: true, diff --git a/x/evm/precompile/test/export.go b/x/evm/precompile/test/export.go index 966dd3359..05405c730 100644 --- a/x/evm/precompile/test/export.go +++ b/x/evm/precompile/test/export.go @@ -267,6 +267,7 @@ func IncrementWasmCounterWithExecuteMulti( deps *evmtest.TestDeps, wasmContract sdk.AccAddress, times uint, + finalizeTx bool, ) (evmObj *vm.EVM) { msgArgsBz := []byte(` { @@ -308,7 +309,7 @@ func IncrementWasmCounterWithExecuteMulti( s.Require().NoError(err) ethTxResp, evmObj, err := deps.EvmKeeper.CallContractWithInput( - deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input, + deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, finalizeTx, input, ) s.Require().NoError(err) s.Require().NotEmpty(ethTxResp.Ret) diff --git a/x/evm/precompile/wasm.go b/x/evm/precompile/wasm.go index 10817c673..49fe40f87 100644 --- a/x/evm/precompile/wasm.go +++ b/x/evm/precompile/wasm.go @@ -62,9 +62,7 @@ func (p precompileWasm) Run( if err != nil { return nil, err } - - // Dirty journal entries in `StateDB` must be committed - return bz, start.StateDB.Commit() + return bz, OnRunEnd(start.StateDB, start.SnapshotBeforeRun, p.Address()) } type precompileWasm struct { diff --git a/x/evm/precompile/wasm_test.go b/x/evm/precompile/wasm_test.go index d796f8b89..dbe35b839 100644 --- a/x/evm/precompile/wasm_test.go +++ b/x/evm/precompile/wasm_test.go @@ -100,13 +100,13 @@ func (s *WasmSuite) TestExecuteMultiHappy() { test.AssertWasmCounterState(&s.Suite, deps, wasmContract, 0) // count += 2 test.IncrementWasmCounterWithExecuteMulti( - &s.Suite, &deps, wasmContract, 2) + &s.Suite, &deps, wasmContract, 2, true) // count = 2 test.AssertWasmCounterState(&s.Suite, deps, wasmContract, 2) s.assertWasmCounterStateRaw(deps, wasmContract, 2) // count += 67 test.IncrementWasmCounterWithExecuteMulti( - &s.Suite, &deps, wasmContract, 67) + &s.Suite, &deps, wasmContract, 67, true) // count = 69 test.AssertWasmCounterState(&s.Suite, deps, wasmContract, 69) s.assertWasmCounterStateRaw(deps, wasmContract, 69) diff --git a/x/evm/statedb/interfaces.go b/x/evm/statedb/interfaces.go index a4c1c3b59..a9a0f37b4 100644 --- a/x/evm/statedb/interfaces.go +++ b/x/evm/statedb/interfaces.go @@ -38,4 +38,6 @@ type Keeper interface { // DeleteAccount handles contract's suicide call, clearing the balance, // contract bytecode, contract state, and its native account. DeleteAccount(ctx sdk.Context, addr common.Address) error + + IsPrecompile(addr common.Address) bool } diff --git a/x/evm/statedb/journal.go b/x/evm/statedb/journal.go index ac041b617..40a8cc3af 100644 --- a/x/evm/statedb/journal.go +++ b/x/evm/statedb/journal.go @@ -21,6 +21,8 @@ import ( "math/big" "sort" + store "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" ) @@ -335,3 +337,42 @@ func (ch accessListAddSlotChange) Revert(s *StateDB) { func (ch accessListAddSlotChange) Dirtied() *common.Address { return nil } + +// ------------------------------------------------------ +// PrecompileSnapshotBeforeRun + +// PrecompileSnapshotBeforeRun: Precompiles can alter persistent storage of other +// modules. These changes to persistent storage are not reverted by a `Revert` of +// [JournalChange] by default, as it generally manages only changes to accounts +// and Bank balances for ether (NIBI). +// +// As a workaround to make state changes from precompiles reversible, we store +// [PrecompileSnapshotBeforeRun] snapshots that sync and record the prior state +// of the other modules, allowing precompile calls to truly be reverted. +// +// As a simple example, suppose that a transaction calls a precompile. +// 1. If the precompile changes the state in the Bank Module or Wasm module +// 2. The call gets reverted (`revert()` in Solidity), which shoud restore the +// state to a in-memory snapshot recorded on the StateDB journal. +// 3. This could cause a problem where changes to the rest of the blockchain state +// are still in effect following the reversion in the EVM state DB. +type PrecompileSnapshotBeforeRun struct { + MultiStore store.CacheMultiStore + Events sdk.Events + Precompile common.Address +} + +var _ JournalChange = PrecompileSnapshotBeforeRun{} + +func (ch PrecompileSnapshotBeforeRun) 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) + ch.MultiStore.Write() + } +} + +func (ch PrecompileSnapshotBeforeRun) Dirtied() *common.Address { + return &ch.Precompile +} diff --git a/x/evm/statedb/journal_test.go b/x/evm/statedb/journal_test.go index 5863face5..0297e8999 100644 --- a/x/evm/statedb/journal_test.go +++ b/x/evm/statedb/journal_test.go @@ -18,7 +18,7 @@ import ( "github.com/NibiruChain/nibiru/v2/x/evm/statedb" ) -func (s *Suite) TestPrecompileSnapshots() { +func (s *Suite) TestComplexJournalChanges() { deps := evmtest.NewTestDeps() bankDenom := evm.EVMBankDenom s.Require().NoError(testapp.FundAccount( @@ -32,25 +32,11 @@ func (s *Suite) TestPrecompileSnapshots() { wasmContract := test.SetupWasmContracts(&deps, &s.Suite)[1] fmt.Printf("wasmContract: %s\n", wasmContract) - assertionsBeforeRun := func(deps *evmtest.TestDeps) { - test.AssertWasmCounterState( - &s.Suite, *deps, wasmContract, 0, - ) - } - run := func(deps *evmtest.TestDeps) *vm.EVM { - return test.IncrementWasmCounterWithExecuteMulti( - &s.Suite, deps, wasmContract, 7, - ) - } - assertionsAfterRun := func(deps *evmtest.TestDeps) { - test.AssertWasmCounterState( - &s.Suite, *deps, wasmContract, 7, - ) - } s.T().Log("Assert before transition") - - assertionsBeforeRun(&deps) + test.AssertWasmCounterState( + &s.Suite, deps, wasmContract, 0, + ) deployArgs := []any{"name", "SYMBOL", uint8(18)} deployResp, err := evmtest.DeployContract( @@ -136,15 +122,48 @@ func (s *Suite) TestPrecompileSnapshots() { s.Require().ErrorContains(err, vm.ErrExecutionReverted.Error()) }) - s.Run("Precompile calls also start and end clean (no dirty changes)", func() { - evmObj = run(&deps) - assertionsAfterRun(&deps) + s.Run("Precompile calls populate snapshots", func() { + s.T().Log("commitEvmTx=true, expect 0 dirty journal entries") + commitEvmTx := true + evmObj = test.IncrementWasmCounterWithExecuteMulti( + &s.Suite, &deps, wasmContract, 7, commitEvmTx, + ) + // assertions after run + test.AssertWasmCounterState( + &s.Suite, deps, wasmContract, 7, + ) stateDB, ok := evmObj.StateDB.(*statedb.StateDB) s.Require().True(ok, "error retrieving StateDB from the EVM") if stateDB.DirtiesCount() != 0 { debugDirtiesCountMismatch(stateDB, s.T()) s.FailNow("expected 0 dirty journal changes") } + + s.T().Log("commitEvmTx=false, expect dirty journal entries") + commitEvmTx = false + evmObj = test.IncrementWasmCounterWithExecuteMulti( + &s.Suite, &deps, wasmContract, 5, commitEvmTx, + ) + stateDB, ok = evmObj.StateDB.(*statedb.StateDB) + s.Require().True(ok, "error retrieving StateDB from the EVM") + + s.T().Log("Expect exactly 1 dirty journal entry for the precompile snapshot") + if stateDB.DirtiesCount() != 1 { + debugDirtiesCountMismatch(stateDB, s.T()) + s.FailNow("expected 1 dirty journal changes") + } + + s.T().Log("Expect no change since the StateDB has not been committed") + test.AssertWasmCounterState( + &s.Suite, deps, wasmContract, 7, // 7 = 7 + 0 + ) + + s.T().Log("Expect change after the StateDB gets committed") + err = stateDB.Commit() + s.Require().NoError(err) + test.AssertWasmCounterState( + &s.Suite, deps, wasmContract, 12, // 12 = 7 + 5 + ) }) } diff --git a/x/evm/statedb/state_object.go b/x/evm/statedb/state_object.go index e371beae0..3e546362b 100644 --- a/x/evm/statedb/state_object.go +++ b/x/evm/statedb/state_object.go @@ -121,8 +121,9 @@ type stateObject struct { address common.Address // flags - DirtyCode bool - Suicided bool + DirtyCode bool + Suicided bool + IsPrecompile bool } // newObject creates a state object. diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go index 223e92edb..839b40815 100644 --- a/x/evm/statedb/statedb.go +++ b/x/evm/statedb/statedb.go @@ -43,6 +43,21 @@ type StateDB struct { txConfig TxConfig + // cacheCtx: An sdk.Context produced from the [StateDB.ctx] with the + // multi-store cached and a new event manager. The cached context + // (`cacheCtx`) is written to the persistent context (`ctx`) when + // `writeCacheCtx` is called. + 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 + // of the two contexts. + writeToCommitCtxFromCacheCtx func() + + // The number of precompiled contract calls within the current transaction + precompileSnapshotsCount uint8 + // The refund counter, also used by state transitioning. refund uint64 @@ -212,6 +227,16 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject { if obj := s.stateObjects[addr]; obj != nil { 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) if account == nil { @@ -461,13 +486,38 @@ func errorf(format string, args ...any) error { // StateDB object cannot be reused after [Commit] has completed. A new // object needs to be created from the EVM. func (s *StateDB) Commit() error { - ctx := s.GetContext() + 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()) +} + +// 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). +// 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. +func (s *StateDB) CommitCacheCtx() error { + return s.commitCtx(s.cacheCtx) +} + +// commitCtx writes the dirty journal state changes to the EVM Keeper. The +// StateDB object cannot be reused after [commitCtx] has completed. A new +// object needs to be created from the EVM. +func (s *StateDB) commitCtx(ctx sdk.Context) error { for _, addr := range s.Journal.sortedDirties() { obj := s.getStateObject(addr) if obj == nil { continue } - if obj.Suicided { + if obj.IsPrecompile { + s.Journal.dirties[addr] = 0 + continue + } else if obj.Suicided { // Invariant: After [StateDB.Suicide] for some address, the // corresponding account's state object is only available until the // state is committed. @@ -493,13 +543,49 @@ func (s *StateDB) Commit() error { obj.OriginStorage[key] = dirtyVal } } - // Clear the dirty counts because all state changes have been - // committed. + // Reset the dirty count to 0 because all state changes for this dirtied + // address in the journal have been committed. s.Journal.dirties[addr] = 0 } return nil } +func (s *StateDB) CacheCtxForPrecompile(precompileAddr common.Address) ( + sdk.Context, PrecompileSnapshotBeforeRun, +) { + if s.writeToCommitCtxFromCacheCtx == nil { + s.cacheCtx, s.writeToCommitCtxFromCacheCtx = s.ctx.CacheContext() + } + return s.cacheCtx, PrecompileSnapshotBeforeRun{ + MultiStore: s.cacheCtx.MultiStore().CacheMultiStore(), + Events: s.cacheCtx.EventManager().Events(), + Precompile: precompileAddr, + } +} + +// SavePrecompileSnapshotToJournal adds a snapshot of the commit multistore +// ([PrecompileSnapshotBeforeRun]) 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 +// precompile and an inconsistency occurs between the EVM module and other +// modules. +// +// See [PrecompileSnapshotBeforeRun] for more info. +func (s *StateDB) SavePrecompileSnapshotToJournal( + precompileAddr common.Address, + snapshot PrecompileSnapshotBeforeRun, +) 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) + } + return nil +} + +const maxPrecompileCalls uint8 = 10 + // StateObjects: Returns a copy of the [StateDB.stateObjects] map. func (s *StateDB) StateObjects() map[common.Address]*stateObject { copyOfMap := make(map[common.Address]*stateObject) From 80dd2d70eb928f7dda9e85aa6e69150e54366800 Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Fri, 25 Oct 2024 20:48:05 -0500 Subject: [PATCH 2/9] messy, working first version that allows for precompile reversion --- go.mod | 3 ++ go.sum | 8 ++-- x/evm/precompile/precompile.go | 9 +++-- x/evm/precompile/test/export.go | 62 +++++++++++++++++++++++++++++ x/evm/statedb/journal.go | 22 ++++++++--- x/evm/statedb/journal_test.go | 70 ++++++++++++++++++++++++++++++--- x/evm/statedb/statedb.go | 26 +++++++++--- 7 files changed, 175 insertions(+), 25 deletions(-) diff --git a/go.mod b/go.mod index 158dc9b80..bc14078f3 100644 --- a/go.mod +++ b/go.mod @@ -244,6 +244,9 @@ require ( replace ( cosmossdk.io/api => cosmossdk.io/api v0.3.1 + github.com/CosmWasm/wasmd => github.com/NibiruChain/wasmd v0.44.0-nibiru + github.com/cosmos/cosmos-sdk => github.com/NibiruChain/cosmos-sdk v0.47.11-nibiru + github.com/cosmos/iavl => github.com/cosmos/iavl v0.20.0 github.com/ethereum/go-ethereum => github.com/NibiruChain/go-ethereum v1.10.27-nibiru diff --git a/go.sum b/go.sum index 2c789f74f..213ee9c84 100644 --- a/go.sum +++ b/go.sum @@ -221,8 +221,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/ChainSafe/go-schnorrkel v1.0.0 h1:3aDA67lAykLaG1y3AOjs88dMxC88PgUuHRrLeDnvGIM= github.com/ChainSafe/go-schnorrkel v1.0.0/go.mod h1:dpzHYVxLZcp8pjlV+O+UR8K0Hp/z7vcchBSbMBEhCw4= -github.com/CosmWasm/wasmd v0.44.0 h1:2sbcoCAvfjCs1O0SWt53xULKjkV06dbSFthEViIC6Zg= -github.com/CosmWasm/wasmd v0.44.0/go.mod h1:tDyYN050qUcdd7LOxGeo2e185sEShyO3nJGl2Cf59+k= github.com/CosmWasm/wasmvm v1.5.5 h1:XlZI3xO5iUhiBqMiyzsrWEfUtk5gcBMNYIdHnsTB+NI= github.com/CosmWasm/wasmvm v1.5.5/go.mod h1:Q0bSEtlktzh7W2hhEaifrFp1Erx11ckQZmjq8FLCyys= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= @@ -237,8 +235,12 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/NibiruChain/collections v0.5.0 h1:33pXpVTe1PK/tfdZlAJF1JF7AdzGNARG+iL9G/z3X7k= github.com/NibiruChain/collections v0.5.0/go.mod h1:43L6yjuF0BMre/mw4gqn/kUOZz1c2Y3huZ/RQfBFrOQ= +github.com/NibiruChain/cosmos-sdk v0.47.11-nibiru h1:PgFpxDe+7+OzWHs4zXlml5j2i9sGq2Zpd3ndYQG29/0= +github.com/NibiruChain/cosmos-sdk v0.47.11-nibiru/go.mod h1:ADjORYzUQqQv/FxDi0H0K5gW/rAk1CiDR3ZKsExfJV0= github.com/NibiruChain/go-ethereum v1.10.27-nibiru h1:o6lRFt57izoYwzN5cG8tnnBtJcaO3X7MjjN7PGGNCFg= github.com/NibiruChain/go-ethereum v1.10.27-nibiru/go.mod h1:kvvL3nDceUcB+1qGUBAsVf5dW23RBR77fqxgx2PGNrQ= +github.com/NibiruChain/wasmd v0.44.0-nibiru h1:b+stNdbMFsl0+o4KedXyF83qRnEpB/jCiTGZZgv2h2U= +github.com/NibiruChain/wasmd v0.44.0-nibiru/go.mod h1:inrbdsixQ0Kdu4mFUg1u7fn3XPOEkzqieGv0H/gR0ck= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= @@ -426,8 +428,6 @@ github.com/cosmos/cosmos-db v1.0.2 h1:hwMjozuY1OlJs/uh6vddqnk9j7VamLv+0DBlbEXbAK github.com/cosmos/cosmos-db v1.0.2/go.mod h1:Z8IXcFJ9PqKK6BIsVOB3QXtkKoqUOp1vRvPT39kOXEA= github.com/cosmos/cosmos-proto v1.0.0-beta.5 h1:eNcayDLpip+zVLRLYafhzLvQlSmyab+RC5W7ZfmxJLA= github.com/cosmos/cosmos-proto v1.0.0-beta.5/go.mod h1:hQGLpiIUloJBMdQMMWb/4wRApmI9hjHH05nefC0Ojec= -github.com/cosmos/cosmos-sdk v0.47.11 h1:0Qx7eORw0RJqPv+mvDuU8NQ1LV3nJJKJnPoYblWHolc= -github.com/cosmos/cosmos-sdk v0.47.11/go.mod h1:ADjORYzUQqQv/FxDi0H0K5gW/rAk1CiDR3ZKsExfJV0= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= diff --git a/x/evm/precompile/precompile.go b/x/evm/precompile/precompile.go index 428af9775..ecf116f16 100644 --- a/x/evm/precompile/precompile.go +++ b/x/evm/precompile/precompile.go @@ -151,7 +151,7 @@ type OnRunStartResult struct { // 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.PrecompileSnapshotBeforeRun + SnapshotBeforeRun statedb.PrecompileCalled } // OnRunStart prepares the execution environment for a precompiled contract call. @@ -194,6 +194,7 @@ func OnRunStart( return } cacheCtx, snapshot := stateDB.CacheCtxForPrecompile(contract.Address()) + stateDB.SavePrecompileSnapshotToJournal(contract.Address(), snapshot) if err = stateDB.CommitCacheCtx(); err != nil { return res, fmt.Errorf("error committing dirty journal entries: %w", err) } @@ -221,10 +222,12 @@ func OnRunStart( // - Multiple precompiles are called within a single transaction func OnRunEnd( stateDB *statedb.StateDB, - snapshot statedb.PrecompileSnapshotBeforeRun, + snapshot statedb.PrecompileCalled, precompileAddr gethcommon.Address, ) error { - return stateDB.SavePrecompileSnapshotToJournal(precompileAddr, snapshot) + // 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{ diff --git a/x/evm/precompile/test/export.go b/x/evm/precompile/test/export.go index 05405c730..28670e3e0 100644 --- a/x/evm/precompile/test/export.go +++ b/x/evm/precompile/test/export.go @@ -2,11 +2,14 @@ package test import ( "encoding/json" + "math/big" "os" "os/exec" "path" "strings" + serverconfig "github.com/NibiruChain/nibiru/v2/app/server/config" + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" wasm "github.com/CosmWasm/wasmd/x/wasm/types" "github.com/ethereum/go-ethereum/core/vm" @@ -315,3 +318,62 @@ func IncrementWasmCounterWithExecuteMulti( s.Require().NotEmpty(ethTxResp.Ret) return evmObj } + +func IncrementWasmCounterWithExecuteMultiViaVMCall( + s *suite.Suite, + deps *evmtest.TestDeps, + wasmContract sdk.AccAddress, + times uint, + finalizeTx bool, + evmObj *vm.EVM, +) error { + msgArgsBz := []byte(` + { + "increment": {} + } + `) + + // Parse funds argument. + var funds []precompile.WasmBankCoin // blank funds + fundsJson, err := json.Marshal(funds) + s.NoErrorf(err, "fundsJson: %s", fundsJson) + err = json.Unmarshal(fundsJson, &funds) + s.Require().NoError(err, "fundsJson %s, funds %s", fundsJson, funds) + + // The "times" arg determines the number of messages in the executeMsgs slice + executeMsgs := []struct { + ContractAddr string `json:"contractAddr"` + MsgArgs []byte `json:"msgArgs"` + Funds []precompile.WasmBankCoin `json:"funds"` + }{ + {wasmContract.String(), msgArgsBz, funds}, + } + if times == 0 { + executeMsgs = executeMsgs[:0] // force empty + } else { + for i := uint(1); i < times; i++ { + executeMsgs = append(executeMsgs, executeMsgs[0]) + } + } + s.Require().Len(executeMsgs, int(times)) // sanity check assertion + + callArgs := []any{ + executeMsgs, + } + input, err := embeds.SmartContract_Wasm.ABI.Pack( + string(precompile.WasmMethod_executeMulti), + callArgs..., + ) + s.Require().NoError(err) + + contract := precompile.PrecompileAddr_Wasm + leftoverGas := serverconfig.DefaultEthCallGasLimit + _, _, err = evmObj.Call( + vm.AccountRef(deps.Sender.EthAddr), + contract, + input, + leftoverGas, + big.NewInt(0), + ) + return err +} diff --git a/x/evm/statedb/journal.go b/x/evm/statedb/journal.go index 40a8cc3af..d5af3c479 100644 --- a/x/evm/statedb/journal.go +++ b/x/evm/statedb/journal.go @@ -341,13 +341,13 @@ func (ch accessListAddSlotChange) Dirtied() *common.Address { // ------------------------------------------------------ // PrecompileSnapshotBeforeRun -// PrecompileSnapshotBeforeRun: Precompiles can alter persistent storage of other +// PrecompileCalled: Precompiles can alter persistent storage of other // modules. These changes to persistent storage are not reverted by a `Revert` of // [JournalChange] by default, as it generally manages only changes to accounts // and Bank balances for ether (NIBI). // // As a workaround to make state changes from precompiles reversible, we store -// [PrecompileSnapshotBeforeRun] snapshots that sync and record the prior state +// [PrecompileCalled] snapshots that sync and record the prior state // of the other modules, allowing precompile calls to truly be reverted. // // As a simple example, suppose that a transaction calls a precompile. @@ -356,23 +356,33 @@ func (ch accessListAddSlotChange) Dirtied() *common.Address { // state to a in-memory snapshot recorded on the StateDB journal. // 3. This could cause a problem where changes to the rest of the blockchain state // are still in effect following the reversion in the EVM state DB. -type PrecompileSnapshotBeforeRun struct { +type PrecompileCalled struct { MultiStore store.CacheMultiStore Events sdk.Events Precompile common.Address } -var _ JournalChange = PrecompileSnapshotBeforeRun{} +var _ JournalChange = PrecompileCalled{} -func (ch PrecompileSnapshotBeforeRun) Revert(s *StateDB) { +func (ch PrecompileCalled) Revert(s *StateDB) { + // TEMP: trying something + // If the wasm state is not in the cacheCtx, + // s.CommitCacheCtx() + + // Old Code 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) + // TODO: UD-DEBUG: Overwriting events might fix an issue with + // appending too many + // s.ctx.WithEventManager( + // sdk.NewEventManager().EmitEvents(ch.Events), + // ) ch.MultiStore.Write() } } -func (ch PrecompileSnapshotBeforeRun) Dirtied() *common.Address { +func (ch PrecompileCalled) Dirtied() *common.Address { return &ch.Precompile } diff --git a/x/evm/statedb/journal_test.go b/x/evm/statedb/journal_test.go index 0297e8999..6be5571f9 100644 --- a/x/evm/statedb/journal_test.go +++ b/x/evm/statedb/journal_test.go @@ -6,10 +6,12 @@ import ( "strings" "testing" + "github.com/MakeNowJust/heredoc/v2" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/core/vm" serverconfig "github.com/NibiruChain/nibiru/v2/app/server/config" + "github.com/NibiruChain/nibiru/v2/x/common" "github.com/NibiruChain/nibiru/v2/x/common/testutil/testapp" "github.com/NibiruChain/nibiru/v2/x/evm" "github.com/NibiruChain/nibiru/v2/x/evm/embeds" @@ -147,10 +149,10 @@ func (s *Suite) TestComplexJournalChanges() { stateDB, ok = evmObj.StateDB.(*statedb.StateDB) s.Require().True(ok, "error retrieving StateDB from the EVM") - s.T().Log("Expect exactly 1 dirty journal entry for the precompile snapshot") - if stateDB.DirtiesCount() != 1 { + s.T().Log("Expect exactly 0 dirty journal entry for the precompile snapshot") + if stateDB.DirtiesCount() != 0 { debugDirtiesCountMismatch(stateDB, s.T()) - s.FailNow("expected 1 dirty journal changes") + s.FailNow("expected 0 dirty journal changes") } s.T().Log("Expect no change since the StateDB has not been committed") @@ -158,12 +160,68 @@ func (s *Suite) TestComplexJournalChanges() { &s.Suite, deps, wasmContract, 7, // 7 = 7 + 0 ) - s.T().Log("Expect change after the StateDB gets committed") - err = stateDB.Commit() - s.Require().NoError(err) + s.T().Log("Expect change to persist on the StateDB cacheCtx") + cacheCtx := stateDB.GetCacheContext() + s.NotNil(cacheCtx) + deps.Ctx = *cacheCtx test.AssertWasmCounterState( &s.Suite, deps, wasmContract, 12, // 12 = 7 + 5 ) + // NOTE: that the [StateDB.Commit] fn has not been called yet. We're still + // mid-transaction. + + s.T().Log("EVM revert operation should bring about the old state") + err = test.IncrementWasmCounterWithExecuteMultiViaVMCall( + &s.Suite, &deps, wasmContract, 50, commitEvmTx, evmObj, + ) + stateDBPtr := evmObj.StateDB.(*statedb.StateDB) + s.Require().Equal(stateDB, stateDBPtr) + s.Require().NoError(err) + s.T().Log(heredoc.Doc(`At this point, 2 precompile calls have succeeded. +One that increments the counter to 7 + 5, and another for +50. +The StateDB has not been committed. We expect to be able to revert to both +snapshots and see the prior states.`)) + cacheCtx = stateDB.GetCacheContext() + deps.Ctx = *cacheCtx + test.AssertWasmCounterState( + &s.Suite, deps, wasmContract, 7+5+50, + ) + + errFn := common.TryCatch(func() { + // There were only two EVM calls. + // Thus, there are only 2 snapshots: 0 and 1. + // We should not be able to revert to a third one. + stateDB.RevertToSnapshot(2) + }) + s.Require().ErrorContains(errFn(), "revision id 2 cannot be reverted") + + stateDB.RevertToSnapshot(1) + cacheCtx = stateDB.GetCacheContext() + s.NotNil(cacheCtx) + deps.Ctx = *cacheCtx + test.AssertWasmCounterState( + &s.Suite, deps, wasmContract, 7+5, + ) + + stateDB.RevertToSnapshot(0) + cacheCtx = stateDB.GetCacheContext() + s.NotNil(cacheCtx) + deps.Ctx = *cacheCtx + test.AssertWasmCounterState( + &s.Suite, deps, wasmContract, 7, // state before precompile called + ) + + err = stateDB.Commit() + deps.Ctx = stateDB.GetContext() + test.AssertWasmCounterState( + &s.Suite, deps, wasmContract, 7, // state before precompile called + ) + }) + + s.Run("too many precompile calls in one tx will fail", func() { + // currently + // evmObj + }) } diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go index 839b40815..81a4ce9e8 100644 --- a/x/evm/statedb/statedb.go +++ b/x/evm/statedb/statedb.go @@ -6,6 +6,7 @@ import ( "math/big" "sort" + store "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" gethcore "github.com/ethereum/go-ethereum/core/types" @@ -91,6 +92,14 @@ func (s *StateDB) GetContext() sdk.Context { return s.ctx } +// GetCacheContext: Getter for testing purposes. +func (s *StateDB) GetCacheContext() *sdk.Context { + if s.writeToCommitCtxFromCacheCtx == nil { + return nil + } + return &s.cacheCtx +} + // AddLog adds a log, called by evm. func (s *StateDB) AddLog(log *gethcore.Log) { s.Journal.append(addLogChange{}) @@ -463,6 +472,9 @@ func (s *StateDB) Snapshot() int { // RevertToSnapshot reverts all state changes made since the given revision. func (s *StateDB) RevertToSnapshot(revid int) { + fmt.Printf("len(s.validRevisions): %d\n", len(s.validRevisions)) + fmt.Printf("s.validRevisions: %v\n", s.validRevisions) + // Find the snapshot in the stack of valid snapshots. idx := sort.Search(len(s.validRevisions), func(i int) bool { return s.validRevisions[i].id >= revid @@ -515,6 +527,7 @@ func (s *StateDB) commitCtx(ctx sdk.Context) error { continue } if obj.IsPrecompile { + // TODO: UD-DEBUG: Assume clean to pretend for tests s.Journal.dirties[addr] = 0 continue } else if obj.Suicided { @@ -543,6 +556,7 @@ func (s *StateDB) commitCtx(ctx sdk.Context) error { obj.OriginStorage[key] = dirtyVal } } + // TODO: UD-DEBUG: Assume clean to pretend for tests // Reset the dirty count to 0 because all state changes for this dirtied // address in the journal have been committed. s.Journal.dirties[addr] = 0 @@ -551,29 +565,29 @@ func (s *StateDB) commitCtx(ctx sdk.Context) error { } func (s *StateDB) CacheCtxForPrecompile(precompileAddr common.Address) ( - sdk.Context, PrecompileSnapshotBeforeRun, + sdk.Context, PrecompileCalled, ) { if s.writeToCommitCtxFromCacheCtx == nil { s.cacheCtx, s.writeToCommitCtxFromCacheCtx = s.ctx.CacheContext() } - return s.cacheCtx, PrecompileSnapshotBeforeRun{ - MultiStore: s.cacheCtx.MultiStore().CacheMultiStore(), + 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 -// ([PrecompileSnapshotBeforeRun]) to the [StateDB] journal at the end of +// ([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 // precompile and an inconsistency occurs between the EVM module and other // modules. // -// See [PrecompileSnapshotBeforeRun] for more info. +// See [PrecompileCalled] for more info. func (s *StateDB) SavePrecompileSnapshotToJournal( precompileAddr common.Address, - snapshot PrecompileSnapshotBeforeRun, + snapshot PrecompileCalled, ) error { obj := s.getOrNewStateObject(precompileAddr) obj.db.Journal.append(snapshot) From c62491262eeef1ecb1d179dc7f0ab95fc375ef6b Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Sat, 26 Oct 2024 04:45:55 -0500 Subject: [PATCH 3/9] wip!: Save checkpoint. 1. Created NibiruBankKeeper with safety around NIBI transfers inside of EthereumTx. 2. The "PrecompileCalled" JournalChange now has a propery implementation and a strong test case to show that reverting the precompile calls works as intended. 3. Remove unneeded functions created for testing with low-level struct fields. --- app/keepers.go | 20 +++- x/evm/deps.go | 13 --- x/evm/evmtest/test_deps.go | 3 +- x/evm/keeper/bank_extension.go | 163 +++++++++++++++++++++++++++++++++ x/evm/keeper/keeper.go | 5 +- x/evm/keeper/msg_server.go | 2 +- x/evm/keeper/statedb.go | 15 +-- x/evm/precompile/funtoken.go | 2 +- x/evm/precompile/precompile.go | 46 +++------- x/evm/precompile/wasm.go | 2 +- x/evm/statedb/access_list.go | 4 +- x/evm/statedb/config.go | 3 +- x/evm/statedb/interfaces.go | 14 +-- x/evm/statedb/journal.go | 13 +-- x/evm/statedb/journal_test.go | 6 +- x/evm/statedb/state_object.go | 12 +-- x/evm/statedb/statedb.go | 109 ++++++++++++---------- 17 files changed, 289 insertions(+), 143 deletions(-) create mode 100644 x/evm/keeper/bank_extension.go 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 { From 7f904a07ac4d5555d8c088411024fc50ff65d085 Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Sat, 26 Oct 2024 05:03:17 -0500 Subject: [PATCH 4/9] chore: changelog --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 821b29e13..ac2dba69c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,23 @@ consistent setup and dynamic gas calculations, addressing the following tickets. - [#2088](https://github.com/NibiruChain/nibiru/pull/2088) - refactor(evm): remove outdated comment and improper error message text - [#2089](https://github.com/NibiruChain/nibiru/pull/2089) - better handling of gas consumption within erc20 contract execution - [#2091](https://github.com/NibiruChain/nibiru/pull/2091) - feat(evm): add fun token creation fee validation +- [#2094](https://github.com/NibiruChain/nibiru/pull/2094) - fix(evm): Following +from the changs in #2086, this pull request implements two critical security +fixes. + 1. First, we add new `JournalChange` struct that saves a deep copy of the + state multi store before each state-modifying, Nibiru-specific precompiled + contract is called (`OnRunStart`). Additionally, we commit the `StateDB` there + as well. This guarantees that the non-EVM and EVM state will be in sync even + if there are complex, multi-step Ethereum transactions, such as in the case of + an EthereumTx that influences the `StateDB`, then calls a precompile that also + changes non-EVM state, and then EVM reverts inside of a try-catch. + 2. Second, the solution from #2086 that records NIBI (ether) transfers on the + `StateDB` during precompiled contract calls is generalized as + `NibiruBankKeeper`, which is struct extension of the `bankkeeper.BaseKeeper` + that is used throughout the Nibiru base application. The `NibiruBankKeeper` + holds a reference to the current EVM `StateDB` if there is one and records + balance changes in wei as journal changes automatically. + #### Nibiru EVM | Before Audit 1 - 2024-10-18 From 08a73ee6c86fbf1449a10f32c7b0eecdfb5caa7f Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Mon, 28 Oct 2024 18:46:01 -0500 Subject: [PATCH 5/9] finalize bank keeper changes --- app/ante/gas_wanted_test.go | 2 +- app/ante/handler_opts.go | 2 +- app/evmante/evmante_can_transfer_test.go | 4 +- app/evmante/evmante_emit_event_test.go | 4 +- app/evmante/evmante_gas_consume_test.go | 4 +- app/evmante/evmante_handler.go | 18 ++--- app/evmante/evmante_handler_test.go | 2 +- .../evmante_increment_sender_seq_test.go | 4 +- app/evmante/evmante_mempool_fees_test.go | 2 +- app/evmante/evmante_setup_ctx_test.go | 4 +- app/evmante/evmante_sigverify_test.go | 4 +- app/evmante/evmante_validate_basic_test.go | 4 +- app/evmante/evmante_verify_eth_acc_test.go | 4 +- app/keepers.go | 5 +- app/keepers/all_keepers.go | 2 +- x/evm/evmmodule/genesis_test.go | 4 +- x/evm/evmtest/smart_contract_test.go | 4 +- x/evm/evmtest/test_deps.go | 4 +- x/evm/evmtest/tx.go | 62 ++-------------- x/evm/keeper/bank_extension.go | 23 +++--- x/evm/keeper/funtoken_from_coin.go | 2 +- x/evm/keeper/funtoken_from_coin_test.go | 2 +- x/evm/keeper/funtoken_from_erc20.go | 4 +- x/evm/keeper/funtoken_from_erc20_test.go | 2 +- x/evm/keeper/gas_fees.go | 4 +- x/evm/keeper/grpc_query_test.go | 4 +- x/evm/keeper/keeper.go | 6 +- x/evm/keeper/msg_ethereum_tx_test.go | 8 +-- x/evm/keeper/msg_server.go | 11 +-- x/evm/keeper/statedb.go | 12 ++-- x/evm/keeper/statedb_test.go | 6 +- x/evm/precompile/funtoken.go | 70 ++----------------- x/evm/precompile/funtoken_test.go | 4 +- x/evm/statedb/journal.go | 5 +- x/evm/statedb/journal_test.go | 5 -- x/evm/statedb/statedb.go | 8 ++- x/evm/statedb/statedb_test.go | 36 +++++----- 37 files changed, 124 insertions(+), 227 deletions(-) diff --git a/app/ante/gas_wanted_test.go b/app/ante/gas_wanted_test.go index d892a8217..92b5d1343 100644 --- a/app/ante/gas_wanted_test.go +++ b/app/ante/gas_wanted_test.go @@ -83,7 +83,7 @@ func (s *AnteTestSuite) TestGasWantedDecorator() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - stateDB := deps.StateDB() + stateDB := deps.NewStateDB() anteDec := ante.AnteDecoratorGasWanted{} tx := tc.txSetup(&deps) diff --git a/app/ante/handler_opts.go b/app/ante/handler_opts.go index b516653c8..9c1d88301 100644 --- a/app/ante/handler_opts.go +++ b/app/ante/handler_opts.go @@ -20,7 +20,7 @@ type AnteHandlerOptions struct { IBCKeeper *ibckeeper.Keeper DevGasKeeper *devgaskeeper.Keeper DevGasBankKeeper devgasante.BankKeeper - EvmKeeper evmkeeper.Keeper + EvmKeeper *evmkeeper.Keeper AccountKeeper authkeeper.AccountKeeper TxCounterStoreKey types.StoreKey diff --git a/app/evmante/evmante_can_transfer_test.go b/app/evmante/evmante_can_transfer_test.go index 2fa71c674..381597624 100644 --- a/app/evmante/evmante_can_transfer_test.go +++ b/app/evmante/evmante_can_transfer_test.go @@ -88,8 +88,8 @@ func (s *TestSuite) TestCanTransferDecorator() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - stateDB := deps.StateDB() - anteDec := evmante.CanTransferDecorator{&deps.App.AppKeepers.EvmKeeper} + stateDB := deps.NewStateDB() + anteDec := evmante.CanTransferDecorator{deps.App.AppKeepers.EvmKeeper} tx := tc.txSetup(&deps) if tc.ctxSetup != nil { diff --git a/app/evmante/evmante_emit_event_test.go b/app/evmante/evmante_emit_event_test.go index 855165450..20ff36f5d 100644 --- a/app/evmante/evmante_emit_event_test.go +++ b/app/evmante/evmante_emit_event_test.go @@ -41,8 +41,8 @@ func (s *TestSuite) TestEthEmitEventDecorator() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - stateDB := deps.StateDB() - anteDec := evmante.NewEthEmitEventDecorator(&deps.App.AppKeepers.EvmKeeper) + stateDB := deps.NewStateDB() + anteDec := evmante.NewEthEmitEventDecorator(deps.App.AppKeepers.EvmKeeper) tx := tc.txSetup(&deps) s.Require().NoError(stateDB.Commit()) diff --git a/app/evmante/evmante_gas_consume_test.go b/app/evmante/evmante_gas_consume_test.go index 1e3c6b1fe..3291c3349 100644 --- a/app/evmante/evmante_gas_consume_test.go +++ b/app/evmante/evmante_gas_consume_test.go @@ -59,9 +59,9 @@ func (s *TestSuite) TestAnteDecEthGasConsume() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - stateDB := deps.StateDB() + stateDB := deps.NewStateDB() anteDec := evmante.NewAnteDecEthGasConsume( - &deps.App.AppKeepers.EvmKeeper, tc.maxGasWanted, + deps.App.AppKeepers.EvmKeeper, tc.maxGasWanted, ) tc.beforeTxSetup(&deps, stateDB) diff --git a/app/evmante/evmante_handler.go b/app/evmante/evmante_handler.go index 787be312e..a9c2f7d0f 100644 --- a/app/evmante/evmante_handler.go +++ b/app/evmante/evmante_handler.go @@ -13,16 +13,16 @@ func NewAnteHandlerEVM( ) sdk.AnteHandler { return sdk.ChainAnteDecorators( // outermost AnteDecorator. SetUpContext must be called first - NewEthSetUpContextDecorator(&options.EvmKeeper), - NewMempoolGasPriceDecorator(&options.EvmKeeper), - NewEthValidateBasicDecorator(&options.EvmKeeper), - NewEthSigVerificationDecorator(&options.EvmKeeper), - NewAnteDecVerifyEthAcc(&options.EvmKeeper, options.AccountKeeper), - CanTransferDecorator{&options.EvmKeeper}, - NewAnteDecEthGasConsume(&options.EvmKeeper, options.MaxTxGasWanted), - NewAnteDecEthIncrementSenderSequence(&options.EvmKeeper, options.AccountKeeper), + NewEthSetUpContextDecorator(options.EvmKeeper), + NewMempoolGasPriceDecorator(options.EvmKeeper), + NewEthValidateBasicDecorator(options.EvmKeeper), + NewEthSigVerificationDecorator(options.EvmKeeper), + NewAnteDecVerifyEthAcc(options.EvmKeeper, options.AccountKeeper), + CanTransferDecorator{options.EvmKeeper}, + NewAnteDecEthGasConsume(options.EvmKeeper, options.MaxTxGasWanted), + NewAnteDecEthIncrementSenderSequence(options.EvmKeeper, options.AccountKeeper), ante.AnteDecoratorGasWanted{}, // emit eth tx hash and index at the very last ante handler. - NewEthEmitEventDecorator(&options.EvmKeeper), + NewEthEmitEventDecorator(options.EvmKeeper), ) } diff --git a/app/evmante/evmante_handler_test.go b/app/evmante/evmante_handler_test.go index 62e7afb0d..5ab7e8a1f 100644 --- a/app/evmante/evmante_handler_test.go +++ b/app/evmante/evmante_handler_test.go @@ -69,7 +69,7 @@ func (s *TestSuite) TestAnteHandlerEVM() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - stateDB := deps.StateDB() + stateDB := deps.NewStateDB() anteHandlerEVM := evmante.NewAnteHandlerEVM( ante.AnteHandlerOptions{ diff --git a/app/evmante/evmante_increment_sender_seq_test.go b/app/evmante/evmante_increment_sender_seq_test.go index ac358cbb0..b4503e675 100644 --- a/app/evmante/evmante_increment_sender_seq_test.go +++ b/app/evmante/evmante_increment_sender_seq_test.go @@ -66,8 +66,8 @@ func (s *TestSuite) TestAnteDecEthIncrementSenderSequence() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - stateDB := deps.StateDB() - anteDec := evmante.NewAnteDecEthIncrementSenderSequence(&deps.App.EvmKeeper, deps.App.AccountKeeper) + stateDB := deps.NewStateDB() + anteDec := evmante.NewAnteDecEthIncrementSenderSequence(deps.App.EvmKeeper, deps.App.AccountKeeper) if tc.beforeTxSetup != nil { tc.beforeTxSetup(&deps, stateDB) diff --git a/app/evmante/evmante_mempool_fees_test.go b/app/evmante/evmante_mempool_fees_test.go index ef7b34e64..892bd9e57 100644 --- a/app/evmante/evmante_mempool_fees_test.go +++ b/app/evmante/evmante_mempool_fees_test.go @@ -82,7 +82,7 @@ func (s *TestSuite) TestMempoolGasFeeDecorator() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - anteDec := evmante.NewMempoolGasPriceDecorator(&deps.App.AppKeepers.EvmKeeper) + anteDec := evmante.NewMempoolGasPriceDecorator(deps.App.AppKeepers.EvmKeeper) tx := tc.txSetup(&deps) diff --git a/app/evmante/evmante_setup_ctx_test.go b/app/evmante/evmante_setup_ctx_test.go index 9df86ba17..028fceb52 100644 --- a/app/evmante/evmante_setup_ctx_test.go +++ b/app/evmante/evmante_setup_ctx_test.go @@ -12,8 +12,8 @@ import ( func (s *TestSuite) TestEthSetupContextDecorator() { deps := evmtest.NewTestDeps() - stateDB := deps.StateDB() - anteDec := evmante.NewEthSetUpContextDecorator(&deps.App.EvmKeeper) + stateDB := deps.NewStateDB() + anteDec := evmante.NewEthSetUpContextDecorator(deps.App.EvmKeeper) s.Require().NoError(stateDB.Commit()) tx := evmtest.HappyCreateContractTx(&deps) diff --git a/app/evmante/evmante_sigverify_test.go b/app/evmante/evmante_sigverify_test.go index 63b290140..d6a7998b1 100644 --- a/app/evmante/evmante_sigverify_test.go +++ b/app/evmante/evmante_sigverify_test.go @@ -66,8 +66,8 @@ func (s *TestSuite) TestEthSigVerificationDecorator() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - stateDB := deps.StateDB() - anteDec := evmante.NewEthSigVerificationDecorator(&deps.App.AppKeepers.EvmKeeper) + stateDB := deps.NewStateDB() + anteDec := evmante.NewEthSigVerificationDecorator(deps.App.AppKeepers.EvmKeeper) tx := tc.txSetup(&deps) s.Require().NoError(stateDB.Commit()) diff --git a/app/evmante/evmante_validate_basic_test.go b/app/evmante/evmante_validate_basic_test.go index 3f1263dee..2aa7910dd 100644 --- a/app/evmante/evmante_validate_basic_test.go +++ b/app/evmante/evmante_validate_basic_test.go @@ -198,8 +198,8 @@ func (s *TestSuite) TestEthValidateBasicDecorator() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - stateDB := deps.StateDB() - anteDec := evmante.NewEthValidateBasicDecorator(&deps.App.AppKeepers.EvmKeeper) + stateDB := deps.NewStateDB() + anteDec := evmante.NewEthValidateBasicDecorator(deps.App.AppKeepers.EvmKeeper) tx := tc.txSetup(&deps) s.Require().NoError(stateDB.Commit()) diff --git a/app/evmante/evmante_verify_eth_acc_test.go b/app/evmante/evmante_verify_eth_acc_test.go index 6d7f9aeda..2af951aa5 100644 --- a/app/evmante/evmante_verify_eth_acc_test.go +++ b/app/evmante/evmante_verify_eth_acc_test.go @@ -64,8 +64,8 @@ func (s *TestSuite) TestAnteDecoratorVerifyEthAcc_CheckTx() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - stateDB := deps.StateDB() - anteDec := evmante.NewAnteDecVerifyEthAcc(&deps.App.AppKeepers.EvmKeeper, &deps.App.AppKeepers.AccountKeeper) + stateDB := deps.NewStateDB() + anteDec := evmante.NewAnteDecVerifyEthAcc(deps.App.AppKeepers.EvmKeeper, &deps.App.AppKeepers.AccountKeeper) tc.beforeTxSetup(&deps, stateDB) tx := tc.txSetup(&deps) diff --git a/app/keepers.go b/app/keepers.go index be75b2357..173c035d0 100644 --- a/app/keepers.go +++ b/app/keepers.go @@ -378,7 +378,7 @@ func (app *NibiruApp) InitKeepers( ), ) - app.EvmKeeper = evmkeeper.NewKeeper( + evmKeeper := evmkeeper.NewKeeper( appCodec, keys[evm.StoreKey], tkeys[evm.TransientKey], @@ -388,6 +388,7 @@ func (app *NibiruApp) InitKeepers( app.StakingKeeper, cast.ToString(appOpts.Get("evm.tracer")), ) + app.EvmKeeper = &evmKeeper // ---------------------------------- IBC keepers @@ -644,7 +645,7 @@ func (app *NibiruApp) initAppModules( ibcfee.NewAppModule(app.ibcFeeKeeper), ica.NewAppModule(&app.icaControllerKeeper, &app.icaHostKeeper), - evmmodule.NewAppModule(&app.EvmKeeper, app.AccountKeeper), + evmmodule.NewAppModule(app.EvmKeeper, app.AccountKeeper), // wasm wasm.NewAppModule( diff --git a/app/keepers/all_keepers.go b/app/keepers/all_keepers.go index 7bbdc9c10..4692905c7 100644 --- a/app/keepers/all_keepers.go +++ b/app/keepers/all_keepers.go @@ -63,7 +63,7 @@ type PublicKeepers struct { SudoKeeper keeper.Keeper DevGasKeeper devgaskeeper.Keeper TokenFactoryKeeper tokenfactorykeeper.Keeper - EvmKeeper evmkeeper.Keeper + EvmKeeper *evmkeeper.Keeper // WASM keepers WasmKeeper wasmkeeper.Keeper diff --git a/x/evm/evmmodule/genesis_test.go b/x/evm/evmmodule/genesis_test.go index 690745e84..72b884082 100644 --- a/x/evm/evmmodule/genesis_test.go +++ b/x/evm/evmmodule/genesis_test.go @@ -89,13 +89,13 @@ func (s *Suite) TestExportInitGenesis() { s.Require().NoError(err) // Export genesis - evmGenesisState := evmmodule.ExportGenesis(deps.Ctx, &deps.EvmKeeper, deps.App.AccountKeeper) + evmGenesisState := evmmodule.ExportGenesis(deps.Ctx, deps.EvmKeeper, deps.App.AccountKeeper) authGenesisState := deps.App.AccountKeeper.ExportGenesis(deps.Ctx) // Init genesis from the exported state deps = evmtest.NewTestDeps() deps.App.AccountKeeper.InitGenesis(deps.Ctx, *authGenesisState) - evmmodule.InitGenesis(deps.Ctx, &deps.EvmKeeper, deps.App.AccountKeeper, *evmGenesisState) + evmmodule.InitGenesis(deps.Ctx, deps.EvmKeeper, deps.App.AccountKeeper, *evmGenesisState) // Verify erc20 balances for users A, B and sender balance, err := deps.EvmKeeper.ERC20().BalanceOf(erc20Addr, toUserA, deps.Ctx) diff --git a/x/evm/evmtest/smart_contract_test.go b/x/evm/evmtest/smart_contract_test.go index d5ca3434a..a78dbcf8e 100644 --- a/x/evm/evmtest/smart_contract_test.go +++ b/x/evm/evmtest/smart_contract_test.go @@ -17,7 +17,7 @@ func (s *Suite) TestCreateContractTxMsg() { EthAcc: ethAcc, EthChainIDInt: deps.EvmKeeper.EthChainID(deps.Ctx), GasPrice: big.NewInt(1), - Nonce: deps.StateDB().GetNonce(ethAcc.EthAddr), + Nonce: deps.NewStateDB().GetNonce(ethAcc.EthAddr), } ethTxMsg, err := evmtest.CreateContractMsgEthereumTx(args) @@ -33,7 +33,7 @@ func (s *Suite) TestExecuteContractTxMsg() { EthAcc: ethAcc, EthChainIDInt: deps.EvmKeeper.EthChainID(deps.Ctx), GasPrice: big.NewInt(1), - Nonce: deps.StateDB().GetNonce(ethAcc.EthAddr), + Nonce: deps.NewStateDB().GetNonce(ethAcc.EthAddr), ContractAddress: &contractAddress, Data: nil, } diff --git a/x/evm/evmtest/test_deps.go b/x/evm/evmtest/test_deps.go index 44fb34c31..6d2e830af 100644 --- a/x/evm/evmtest/test_deps.go +++ b/x/evm/evmtest/test_deps.go @@ -22,7 +22,7 @@ type TestDeps struct { App *app.NibiruApp Ctx sdk.Context EncCfg codec.EncodingConfig - EvmKeeper keeper.Keeper + EvmKeeper *keeper.Keeper GenState *evm.GenesisState Sender EthPrivKeyAcc } @@ -45,7 +45,7 @@ func NewTestDeps() TestDeps { } } -func (deps TestDeps) StateDB() *statedb.StateDB { +func (deps TestDeps) NewStateDB() *statedb.StateDB { return deps.EvmKeeper.NewStateDB( deps.Ctx, statedb.NewEmptyTxConfig( diff --git a/x/evm/evmtest/tx.go b/x/evm/evmtest/tx.go index dde679851..5bfa99a50 100644 --- a/x/evm/evmtest/tx.go +++ b/x/evm/evmtest/tx.go @@ -7,8 +7,6 @@ import ( "math/big" "testing" - sdkmath "cosmossdk.io/math" - "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" gethcommon "github.com/ethereum/go-ethereum/common" @@ -28,37 +26,6 @@ import ( type GethTxType = uint8 -func TxTemplateAccessListTx() *gethcore.AccessListTx { - return &gethcore.AccessListTx{ - GasPrice: big.NewInt(1), - Gas: gethparams.TxGas, - To: &gethcommon.Address{}, - Value: big.NewInt(0), - Data: []byte{}, - } -} - -func TxTemplateLegacyTx() *gethcore.LegacyTx { - return &gethcore.LegacyTx{ - GasPrice: big.NewInt(1), - Gas: gethparams.TxGas, - To: &gethcommon.Address{}, - Value: big.NewInt(0), - Data: []byte{}, - } -} - -func TxTemplateDynamicFeeTx() *gethcore.DynamicFeeTx { - return &gethcore.DynamicFeeTx{ - GasFeeCap: big.NewInt(10), - GasTipCap: big.NewInt(2), - Gas: gethparams.TxGas, - To: &gethcommon.Address{}, - Value: big.NewInt(0), - Data: []byte{}, - } -} - func NewEthTxMsgFromTxData( deps *TestDeps, txType GethTxType, @@ -117,7 +84,7 @@ func NewEthTxMsgFromTxData( // ExecuteNibiTransfer executes nibi transfer func ExecuteNibiTransfer(deps *TestDeps, t *testing.T) *evm.MsgEthereumTx { - nonce := deps.StateDB().GetNonce(deps.Sender.EthAddr) + nonce := deps.NewStateDB().GetNonce(deps.Sender.EthAddr) recipient := NewEthPrivAcc().EthAddr txArgs := evm.JsonTxArgs{ @@ -156,7 +123,7 @@ func DeployContract( } bytecodeForCall := append(contract.Bytecode, packedArgs...) - nonce := deps.StateDB().GetNonce(deps.Sender.EthAddr) + nonce := deps.NewStateDB().GetNonce(deps.Sender.EthAddr) ethTxMsg, gethSigner, krSigner, err := GenerateEthTxMsgAndSigner( evm.JsonTxArgs{ Nonce: (*hexutil.Uint64)(&nonce), @@ -213,7 +180,7 @@ func DeployAndExecuteERC20Transfer( "transfer", NewEthPrivAcc().EthAddr, new(big.Int).SetUint64(1000), ) require.NoError(t, err) - nonce = deps.StateDB().GetNonce(deps.Sender.EthAddr) + nonce = deps.NewStateDB().GetNonce(deps.Sender.EthAddr) txArgs := evm.JsonTxArgs{ From: &deps.Sender.EthAddr, To: &contractAddr, @@ -238,7 +205,7 @@ func CallContractTx( input []byte, sender EthPrivKeyAcc, ) (ethTxMsg *evm.MsgEthereumTx, resp *evm.MsgEthereumTxResponse, err error) { - nonce := deps.StateDB().GetNonce(sender.EthAddr) + nonce := deps.NewStateDB().GetNonce(sender.EthAddr) ethTxMsg, gethSigner, krSigner, err := GenerateEthTxMsgAndSigner(evm.JsonTxArgs{ From: &sender.EthAddr, To: &contractAddr, @@ -260,6 +227,8 @@ func CallContractTx( return ethTxMsg, resp, err } +var DefaultEthCallGasLimit = srvconfig.DefaultEthCallGasLimit + // GenerateEthTxMsgAndSigner estimates gas, sets gas limit and returns signer for // the tx. // @@ -309,7 +278,7 @@ func TransferWei( deps, gethcore.LegacyTxType, innerTxData, - deps.StateDB().GetNonce(ethAcc.EthAddr), + deps.NewStateDB().GetNonce(ethAcc.EthAddr), &to, amountWei, gethparams.TxGas, @@ -325,20 +294,3 @@ func TransferWei( } return err } - -// ValidLegacyTx: Useful initial condition for tests -// Exported only for use in tests. -func ValidLegacyTx() *evm.LegacyTx { - sdkInt := sdkmath.NewIntFromBigInt(evm.NativeToWei(big.NewInt(420))) - return &evm.LegacyTx{ - Nonce: 24, - GasLimit: 50_000, - To: gethcommon.HexToAddress("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed").Hex(), - GasPrice: &sdkInt, - Amount: &sdkInt, - Data: []byte{}, - V: []byte{}, - R: []byte{}, - S: []byte{}, - } -} diff --git a/x/evm/keeper/bank_extension.go b/x/evm/keeper/bank_extension.go index a2bcd6d27..ed9ae45ba 100644 --- a/x/evm/keeper/bank_extension.go +++ b/x/evm/keeper/bank_extension.go @@ -17,24 +17,17 @@ var ( type NibiruBankKeeper struct { bankkeeper.BaseKeeper - StateDB *statedb.StateDB - balanceChangesForStateDB uint64 + StateDB *statedb.StateDB } 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 + evmKeeper.Bank.ResetStateDB(stateDB) 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, @@ -51,6 +44,17 @@ func (bk NibiruBankKeeper) MintCoins( return nil } +func (bk *NibiruBankKeeper) ResetStateDB(db *statedb.StateDB) { + bk.StateDB = db +} + +// s.Require().Equal( +// statedb.FromVM(evmObj).GetBalance( +// eth.NibiruAddrToEthAddr(randomAcc), +// ).String(), +// "420"+strings.Repeat("0", 12), +// ) + func (bk NibiruBankKeeper) BurnCoins( ctx sdk.Context, moduleName string, @@ -95,7 +99,6 @@ func (bk *NibiruBankKeeper) SyncStateDBWithAccount( 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) { diff --git a/x/evm/keeper/funtoken_from_coin.go b/x/evm/keeper/funtoken_from_coin.go index 6f0f2efd0..8075107c6 100644 --- a/x/evm/keeper/funtoken_from_coin.go +++ b/x/evm/keeper/funtoken_from_coin.go @@ -23,7 +23,7 @@ func (k *Keeper) createFunTokenFromCoin( } // 2 | Check for denom metadata in bank state - bankMetadata, isFound := k.bankKeeper.GetDenomMetaData(ctx, bankDenom) + bankMetadata, isFound := k.Bank.GetDenomMetaData(ctx, bankDenom) if !isFound { return nil, fmt.Errorf("bank coin denom should have bank metadata for denom \"%s\"", bankDenom) } diff --git a/x/evm/keeper/funtoken_from_coin_test.go b/x/evm/keeper/funtoken_from_coin_test.go index 1f5e3a85a..92e459390 100644 --- a/x/evm/keeper/funtoken_from_coin_test.go +++ b/x/evm/keeper/funtoken_from_coin_test.go @@ -25,7 +25,7 @@ func (s *FunTokenFromCoinSuite) TestCreateFunTokenFromCoin() { deps := evmtest.NewTestDeps() // Compute contract address. FindERC20 should fail - nonce := deps.StateDB().GetNonce(deps.Sender.EthAddr) + nonce := deps.NewStateDB().GetNonce(deps.Sender.EthAddr) contractAddress := crypto.CreateAddress(deps.Sender.EthAddr, nonce) metadata, err := deps.EvmKeeper.FindERC20Metadata(deps.Ctx, contractAddress) s.Require().Error(err) diff --git a/x/evm/keeper/funtoken_from_erc20.go b/x/evm/keeper/funtoken_from_erc20.go index e10f0cca8..f1fa3b0b2 100644 --- a/x/evm/keeper/funtoken_from_erc20.go +++ b/x/evm/keeper/funtoken_from_erc20.go @@ -122,7 +122,7 @@ func (k *Keeper) createFunTokenFromERC20( bankDenom := fmt.Sprintf("erc20/%s", erc20.String()) // 3 | Coin already registered with FunToken? - _, isFound := k.bankKeeper.GetDenomMetaData(ctx, bankDenom) + _, isFound := k.Bank.GetDenomMetaData(ctx, bankDenom) if isFound { return funtoken, fmt.Errorf("bank coin denom already registered with denom \"%s\"", bankDenom) } @@ -137,7 +137,7 @@ func (k *Keeper) createFunTokenFromERC20( if err != nil { return funtoken, fmt.Errorf("failed to validate bank metadata: %w", err) } - k.bankKeeper.SetDenomMetaData(ctx, bankMetadata) + k.Bank.SetDenomMetaData(ctx, bankMetadata) // 5 | Officially create the funtoken mapping funtoken = &evm.FunToken{ diff --git a/x/evm/keeper/funtoken_from_erc20_test.go b/x/evm/keeper/funtoken_from_erc20_test.go index eb209f7ca..53db98b31 100644 --- a/x/evm/keeper/funtoken_from_erc20_test.go +++ b/x/evm/keeper/funtoken_from_erc20_test.go @@ -25,7 +25,7 @@ func (s *FunTokenFromErc20Suite) TestCreateFunTokenFromERC20() { deps := evmtest.NewTestDeps() // assert that the ERC20 contract is not deployed - expectedERC20Addr := crypto.CreateAddress(deps.Sender.EthAddr, deps.StateDB().GetNonce(deps.Sender.EthAddr)) + expectedERC20Addr := crypto.CreateAddress(deps.Sender.EthAddr, deps.NewStateDB().GetNonce(deps.Sender.EthAddr)) _, err := deps.EvmKeeper.FindERC20Metadata(deps.Ctx, expectedERC20Addr) s.Error(err) diff --git a/x/evm/keeper/gas_fees.go b/x/evm/keeper/gas_fees.go index 01c72034c..c373b06ac 100644 --- a/x/evm/keeper/gas_fees.go +++ b/x/evm/keeper/gas_fees.go @@ -60,7 +60,7 @@ func (k *Keeper) RefundGas( // Refund to sender from the fee collector module account. This account // manages the collection of gas fees. - err := k.bankKeeper.SendCoinsFromModuleToAccount( + err := k.Bank.SendCoinsFromModuleToAccount( ctx, authtypes.FeeCollectorName, // sender msgFrom.Bytes(), // recipient @@ -136,7 +136,7 @@ func (k *Keeper) DeductTxCostsFromUserBalance( } // deduct the full gas cost from the user balance - if err := authante.DeductFees(k.bankKeeper, ctx, signerAcc, fees); err != nil { + if err := authante.DeductFees(k.Bank, ctx, signerAcc, fees); err != nil { return errors.Wrapf(err, "failed to deduct full gas cost %s from the user %s balance", fees, from) } diff --git a/x/evm/keeper/grpc_query_test.go b/x/evm/keeper/grpc_query_test.go index b16bea40c..b1d8e0515 100644 --- a/x/evm/keeper/grpc_query_test.go +++ b/x/evm/keeper/grpc_query_test.go @@ -330,7 +330,7 @@ func (s *Suite) TestQueryStorage() { Key: storageKey.String(), } - stateDB := deps.StateDB() + stateDB := deps.NewStateDB() storageValue := gethcommon.BytesToHash([]byte("value")) stateDB.SetState(addr, storageKey, storageValue) @@ -404,7 +404,7 @@ func (s *Suite) TestQueryCode() { Address: addr.Hex(), } - stateDB := deps.StateDB() + stateDB := deps.NewStateDB() contractBytecode := []byte("bytecode") stateDB.SetCode(addr, contractBytecode) s.Require().NoError(stateDB.Commit()) diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index c6b0720d8..dd757ced9 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 *NibiruBankKeeper + Bank *NibiruBankKeeper accountKeeper evm.AccountKeeper stakingKeeper evm.StakingKeeper @@ -79,7 +79,7 @@ func NewKeeper( EvmState: NewEvmState(cdc, storeKey, transientKey), FunTokens: NewFunTokenState(cdc, storeKey), accountKeeper: accKeeper, - bankKeeper: bankKeeper, + Bank: bankKeeper, stakingKeeper: stakingKeeper, tracer: tracer, } @@ -90,7 +90,7 @@ func NewKeeper( // tokens for EVM execution in EVM denom units. func (k *Keeper) GetEvmGasBalance(ctx sdk.Context, addr gethcommon.Address) (balance *big.Int) { nibiruAddr := sdk.AccAddress(addr.Bytes()) - return k.bankKeeper.GetBalance(ctx, nibiruAddr, evm.EVMBankDenom).Amount.BigInt() + return k.Bank.GetBalance(ctx, nibiruAddr, evm.EVMBankDenom).Amount.BigInt() } func (k Keeper) EthChainID(ctx sdk.Context) *big.Int { diff --git a/x/evm/keeper/msg_ethereum_tx_test.go b/x/evm/keeper/msg_ethereum_tx_test.go index 0c2de7a39..4d9f16345 100644 --- a/x/evm/keeper/msg_ethereum_tx_test.go +++ b/x/evm/keeper/msg_ethereum_tx_test.go @@ -47,7 +47,7 @@ func (s *Suite) TestMsgEthereumTx_CreateContract() { EthAcc: ethAcc, EthChainIDInt: deps.EvmKeeper.EthChainID(deps.Ctx), GasPrice: big.NewInt(1), - Nonce: deps.StateDB().GetNonce(ethAcc.EthAddr), + Nonce: deps.NewStateDB().GetNonce(ethAcc.EthAddr), GasLimit: gasLimit, } ethTxMsg, err := evmtest.CreateContractMsgEthereumTx(args) @@ -87,7 +87,7 @@ func (s *Suite) TestMsgEthereumTx_CreateContract() { EthAcc: ethAcc, EthChainIDInt: deps.EvmKeeper.EthChainID(deps.Ctx), GasPrice: big.NewInt(1), - Nonce: deps.StateDB().GetNonce(ethAcc.EthAddr), + Nonce: deps.NewStateDB().GetNonce(ethAcc.EthAddr), } ethTxMsg, err := evmtest.CreateContractMsgEthereumTx(args) s.NoError(err) @@ -139,7 +139,7 @@ func (s *Suite) TestMsgEthereumTx_ExecuteContract() { EthAcc: ethAcc, EthChainIDInt: deps.EvmKeeper.EthChainID(deps.Ctx), GasPrice: big.NewInt(1), - Nonce: deps.StateDB().GetNonce(ethAcc.EthAddr), + Nonce: deps.NewStateDB().GetNonce(ethAcc.EthAddr), GasLimit: gasLimit, ContractAddress: &contractAddr, Data: input, @@ -205,7 +205,7 @@ func (s *Suite) TestMsgEthereumTx_SimpleTransfer() { &deps, tc.txType, innerTxData, - deps.StateDB().GetNonce(ethAcc.EthAddr), + deps.NewStateDB().GetNonce(ethAcc.EthAddr), &to, big.NewInt(fundedAmount), gethparams.TxGas, diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go index be8abb236..da7d79c44 100644 --- a/x/evm/keeper/msg_server.go +++ b/x/evm/keeper/msg_server.go @@ -351,6 +351,7 @@ func (k *Keeper) ApplyEvmMsg(ctx sdk.Context, // The dirty states in `StateDB` is either committed or discarded after return if commit { + fmt.Println("stateDB.Commit in ApplyEvmMsg") if err := stateDB.Commit(); err != nil { return nil, evmObj, fmt.Errorf("failed to commit stateDB: %w", err) } @@ -455,11 +456,11 @@ func (k Keeper) deductCreateFunTokenFee(ctx sdk.Context, msg *evm.MsgCreateFunTo fee := k.FeeForCreateFunToken(ctx) from := sdk.MustAccAddressFromBech32(msg.Sender) // validation in msg.ValidateBasic - if err := k.bankKeeper.SendCoinsFromAccountToModule( + if err := k.Bank.SendCoinsFromAccountToModule( ctx, from, evm.ModuleName, fee); err != nil { return fmt.Errorf("unable to pay the \"create_fun_token_fee\": %w", err) } - if err := k.bankKeeper.BurnCoins(ctx, evm.ModuleName, fee); err != nil { + if err := k.Bank.BurnCoins(ctx, evm.ModuleName, fee); err != nil { return fmt.Errorf("failed to burn the \"create_fun_token_fee\" after payment: %w", err) } return nil @@ -507,7 +508,7 @@ func (k Keeper) convertCoinNativeCoin( funTokenMapping evm.FunToken, ) (*evm.MsgConvertCoinToEvmResponse, error) { // Step 1: Escrow bank coins with EVM module account - err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, evm.ModuleName, sdk.NewCoins(coin)) + err := k.Bank.SendCoinsFromAccountToModule(ctx, sender, evm.ModuleName, sdk.NewCoins(coin)) if err != nil { return nil, errors.Wrap(err, "failed to send coins to module account") } @@ -563,7 +564,7 @@ func (k Keeper) convertCoinNativeERC20( } // Escrow Coins on module account - if err := k.bankKeeper.SendCoinsFromAccountToModule( + if err := k.Bank.SendCoinsFromAccountToModule( ctx, sender, evm.ModuleName, @@ -619,7 +620,7 @@ func (k Keeper) convertCoinNativeERC20( } // Burn escrowed Coins - err = k.bankKeeper.BurnCoins(ctx, evm.ModuleName, sdk.NewCoins(coin)) + err = k.Bank.BurnCoins(ctx, evm.ModuleName, sdk.NewCoins(coin)) if err != nil { return nil, errors.Wrap(err, "failed to burn coins") } diff --git a/x/evm/keeper/statedb.go b/x/evm/keeper/statedb.go index 575962d02..c010dd9b7 100644 --- a/x/evm/keeper/statedb.go +++ b/x/evm/keeper/statedb.go @@ -72,27 +72,27 @@ func (k *Keeper) ForEachStorage( func (k *Keeper) SetAccBalance( ctx sdk.Context, addr gethcommon.Address, amountEvmDenom *big.Int, ) error { - nativeAddr := sdk.AccAddress(addr.Bytes()) - balance := k.bankKeeper.BaseKeeper.GetBalance(ctx, nativeAddr, evm.EVMBankDenom).Amount.BigInt() + addrBech32 := eth.EthAddrToNibiruAddr(addr) + balance := k.Bank.BaseKeeper.GetBalance(ctx, addrBech32, 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.BaseKeeper.MintCoins(ctx, evm.ModuleName, coins); err != nil { + if err := k.Bank.BaseKeeper.MintCoins(ctx, evm.ModuleName, coins); err != nil { return err } - if err := k.bankKeeper.BaseKeeper.SendCoinsFromModuleToAccount(ctx, evm.ModuleName, nativeAddr, coins); err != nil { + if err := k.Bank.BaseKeeper.SendCoinsFromModuleToAccount(ctx, evm.ModuleName, addrBech32, 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.BaseKeeper.SendCoinsFromAccountToModule(ctx, nativeAddr, evm.ModuleName, coins); err != nil { + if err := k.Bank.BaseKeeper.SendCoinsFromAccountToModule(ctx, addrBech32, evm.ModuleName, coins); err != nil { return err } - if err := k.bankKeeper.BaseKeeper.BurnCoins(ctx, evm.ModuleName, coins); err != nil { + if err := k.Bank.BaseKeeper.BurnCoins(ctx, evm.ModuleName, coins); err != nil { return err } default: diff --git a/x/evm/keeper/statedb_test.go b/x/evm/keeper/statedb_test.go index a918039a8..a9fd8cede 100644 --- a/x/evm/keeper/statedb_test.go +++ b/x/evm/keeper/statedb_test.go @@ -21,7 +21,7 @@ import ( func (s *Suite) TestStateDBBalance() { deps := evmtest.NewTestDeps() { - db := deps.StateDB() + db := deps.NewStateDB() s.Equal("0", db.GetBalance(deps.Sender.EthAddr).String()) s.T().Log("fund account in unibi. See expected wei amount.") @@ -47,7 +47,7 @@ func (s *Suite) TestStateDBBalance() { { err := evmtest.TransferWei(&deps, to, evm.NativeToWei(big.NewInt(12))) s.Require().NoError(err) - db := deps.StateDB() + db := deps.NewStateDB() s.Equal( "30"+strings.Repeat("0", 12), db.GetBalance(deps.Sender.EthAddr).String(), @@ -80,7 +80,7 @@ func (s *Suite) TestStateDBBalance() { ) s.NoError(err) - db := deps.StateDB() + db := deps.NewStateDB() s.Equal( "3"+strings.Repeat("0", 12), db.GetBalance(to).String(), diff --git a/x/evm/precompile/funtoken.go b/x/evm/precompile/funtoken.go index 505c106b4..a279fc8a0 100644 --- a/x/evm/precompile/funtoken.go +++ b/x/evm/precompile/funtoken.go @@ -7,18 +7,14 @@ import ( "cosmossdk.io/math" 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" gethabi "github.com/ethereum/go-ethereum/accounts/abi" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" "github.com/NibiruChain/nibiru/v2/app/keepers" - "github.com/NibiruChain/nibiru/v2/eth" "github.com/NibiruChain/nibiru/v2/x/evm" "github.com/NibiruChain/nibiru/v2/x/evm/embeds" evmkeeper "github.com/NibiruChain/nibiru/v2/x/evm/keeper" - "github.com/NibiruChain/nibiru/v2/x/evm/statedb" ) var _ vm.PrecompiledContract = (*precompileFunToken)(nil) @@ -77,14 +73,14 @@ func (p precompileFunToken) Run( func PrecompileFunToken(keepers keepers.PublicKeepers) vm.PrecompiledContract { return precompileFunToken{ - bankKeeper: keepers.BankKeeper, + bankKeeper: keepers.EvmKeeper.Bank, evmKeeper: keepers.EvmKeeper, } } type precompileFunToken struct { - bankKeeper bankkeeper.Keeper - evmKeeper evmkeeper.Keeper + bankKeeper *evmkeeper.NibiruBankKeeper + evmKeeper *evmkeeper.Keeper } var executionGuard sync.Mutex @@ -162,7 +158,7 @@ func (p precompileFunToken) bankSend( return } } else { - err = SafeMintCoins(ctx, evm.ModuleName, coinToSend, p.bankKeeper, start.StateDB) + err = p.evmKeeper.Bank.MintCoins(ctx, evm.ModuleName, sdk.NewCoins(coinToSend)) if err != nil { return nil, fmt.Errorf("mint failed for module \"%s\" (%s): contract caller %s: %w", evm.ModuleName, evm.EVM_MODULE_ADDRESS.Hex(), caller.Hex(), err, @@ -171,13 +167,11 @@ func (p precompileFunToken) bankSend( } // Transfer the bank coin - err = SafeSendCoinFromModuleToAccount( + err = p.evmKeeper.Bank.SendCoinsFromModuleToAccount( ctx, evm.ModuleName, toAddr, - coinToSend, - p.bankKeeper, - start.StateDB, + sdk.NewCoins(coinToSend), ) if err != nil { return nil, fmt.Errorf("send failed for module \"%s\" (%s): contract caller %s: %w", @@ -190,58 +184,6 @@ func (p precompileFunToken) bankSend( return method.Outputs.Pack() } -func SafeMintCoins( - ctx sdk.Context, - moduleName string, - amt sdk.Coin, - bk bankkeeper.Keeper, - db *statedb.StateDB, -) error { - err := bk.MintCoins(ctx, evm.ModuleName, sdk.NewCoins(amt)) - if err != nil { - return err - } - if amt.Denom == evm.EVMBankDenom { - evmBech32Addr := auth.NewModuleAddress(evm.ModuleName) - balAfter := bk.GetBalance(ctx, evmBech32Addr, amt.Denom).Amount.BigInt() - db.SetBalanceWei( - evm.EVM_MODULE_ADDRESS, - evm.NativeToWei(balAfter), - ) - } - - return nil -} - -func SafeSendCoinFromModuleToAccount( - ctx sdk.Context, - senderModule string, - recipientAddr sdk.AccAddress, - amt sdk.Coin, - bk bankkeeper.Keeper, - db *statedb.StateDB, -) error { - err := bk.SendCoinsFromModuleToAccount(ctx, senderModule, recipientAddr, sdk.NewCoins(amt)) - if err != nil { - return err - } - if amt.Denom == evm.EVMBankDenom { - evmBech32Addr := auth.NewModuleAddress(evm.ModuleName) - balAfterFrom := bk.GetBalance(ctx, evmBech32Addr, amt.Denom).Amount.BigInt() - db.SetBalanceWei( - evm.EVM_MODULE_ADDRESS, - evm.NativeToWei(balAfterFrom), - ) - - balAfterTo := bk.GetBalance(ctx, recipientAddr, amt.Denom).Amount.BigInt() - db.SetBalanceWei( - eth.NibiruAddrToEthAddr(recipientAddr), - evm.NativeToWei(balAfterTo), - ) - } - return nil -} - func (p precompileFunToken) decomposeBankSendArgs(args []any) ( erc20 gethcommon.Address, amount *big.Int, diff --git a/x/evm/precompile/funtoken_test.go b/x/evm/precompile/funtoken_test.go index dd5176fb3..9c26255c4 100644 --- a/x/evm/precompile/funtoken_test.go +++ b/x/evm/precompile/funtoken_test.go @@ -120,7 +120,7 @@ func (s *FuntokenSuite) TestHappyPath() { randomAcc := testutil.AccAddress() - s.T().Log("Send using precompile") + s.T().Log("Send NIBI (FunToken) using precompile") amtToSend := int64(420) callArgs := []any{erc20, big.NewInt(amtToSend), randomAcc.String()} input, err := embeds.SmartContract_FunToken.ABI.Pack(string(precompile.FunTokenMethod_BankSend), callArgs...) @@ -134,10 +134,12 @@ func (s *FuntokenSuite) TestHappyPath() { ) s.Require().NoError(err) s.Require().Empty(resp.VmError) + s.True(deps.EvmKeeper == deps.App.EvmKeeper) evmtest.AssertERC20BalanceEqual(s.T(), deps, erc20, deps.Sender.EthAddr, big.NewInt(69_000)) evmtest.AssertERC20BalanceEqual(s.T(), deps, erc20, evm.EVM_MODULE_ADDRESS, big.NewInt(0)) s.Equal(sdk.NewInt(420).String(), deps.App.BankKeeper.GetBalance(deps.Ctx, randomAcc, funtoken.BankDenom).Amount.String(), ) + s.Require().NotNil(deps.EvmKeeper.Bank.StateDB) } diff --git a/x/evm/statedb/journal.go b/x/evm/statedb/journal.go index e684fd574..7a3c495c0 100644 --- a/x/evm/statedb/journal.go +++ b/x/evm/statedb/journal.go @@ -373,9 +373,8 @@ func (ch PrecompileCalled) Revert(s *StateDB) { // Rewrite the `writeCacheCtxFn` using the same logic as sdk.Context.CacheCtx s.writeToCommitCtxFromCacheCtx = func() { s.evmTxCtx.EventManager().EmitEvents(ch.Events) - // TODO: UD-DEBUG: Overwriting events might fix an issue with - // appending too many - // Check correctness of the emitted events + // TODO: UD-DEBUG: Overwriting events might fix an issue with appending + // too many Check correctness of the emitted events ch.MultiStore.Write() } } diff --git a/x/evm/statedb/journal_test.go b/x/evm/statedb/journal_test.go index 046fc514c..220c7ef38 100644 --- a/x/evm/statedb/journal_test.go +++ b/x/evm/statedb/journal_test.go @@ -218,11 +218,6 @@ snapshots and see the prior states.`)) &s.Suite, deps, wasmContract, 7, // state before precompile called ) }) - - s.Run("too many precompile calls in one tx will fail", func() { - // currently - // evmObj - }) } func debugDirtiesCountMismatch(db *statedb.StateDB, t *testing.T) string { diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go index 957da7888..a61c4b14e 100644 --- a/x/evm/statedb/statedb.go +++ b/x/evm/statedb/statedb.go @@ -78,8 +78,13 @@ type StateDB struct { accessList *accessList } +func FromVM(evmObj *vm.EVM) *StateDB { + return evmObj.StateDB.(*StateDB) +} + // New creates a new state from a given trie. func New(ctx sdk.Context, keeper Keeper, txConfig TxConfig) *StateDB { + fmt.Println("statedb.New called") return &StateDB{ keeper: keeper, evmTxCtx: ctx, @@ -480,9 +485,6 @@ func (s *StateDB) Snapshot() int { // RevertToSnapshot reverts all state changes made since the given revision. func (s *StateDB) RevertToSnapshot(revid int) { - fmt.Printf("len(s.validRevisions): %d\n", len(s.validRevisions)) - fmt.Printf("s.validRevisions: %v\n", s.validRevisions) - // Find the snapshot in the stack of valid snapshots. idx := sort.Search(len(s.validRevisions), func(i int) bool { return s.validRevisions[i].id >= revid diff --git a/x/evm/statedb/statedb_test.go b/x/evm/statedb/statedb_test.go index 7919d3da0..a89444042 100644 --- a/x/evm/statedb/statedb_test.go +++ b/x/evm/statedb/statedb_test.go @@ -82,7 +82,7 @@ func (s *Suite) TestAccount() { s.Require().EqualValues(statedb.NewEmptyAccount(), acct) s.Require().Empty(CollectContractStorage(db)) - db = deps.StateDB() + db = deps.NewStateDB() s.Require().Equal(true, db.Exist(address)) s.Require().Equal(true, db.Empty(address)) s.Require().Equal(big.NewInt(0), db.GetBalance(address)) @@ -104,7 +104,7 @@ func (s *Suite) TestAccount() { s.Require().NoError(db.Commit()) // suicide - db = deps.StateDB() + db = deps.NewStateDB() s.Require().False(db.HasSuicided(address)) s.Require().True(db.Suicide(address)) @@ -119,7 +119,7 @@ func (s *Suite) TestAccount() { s.Require().NoError(db.Commit()) // not accessible from StateDB anymore - db = deps.StateDB() + db = deps.NewStateDB() s.Require().False(db.Exist(address)) s.Require().Empty(CollectContractStorage(db)) }}, @@ -127,7 +127,7 @@ func (s *Suite) TestAccount() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - db := deps.StateDB() + db := deps.NewStateDB() tc.malleate(&deps, db) }) } @@ -135,7 +135,7 @@ func (s *Suite) TestAccount() { func (s *Suite) TestAccountOverride() { deps := evmtest.NewTestDeps() - db := deps.StateDB() + db := deps.NewStateDB() // test balance carry over when overwritten amount := big.NewInt(1) @@ -168,7 +168,7 @@ func (s *Suite) TestDBError() { } for _, tc := range testCases { deps := evmtest.NewTestDeps() - db := deps.StateDB() + db := deps.NewStateDB() tc.malleate(db) s.Require().NoError(db.Commit()) } @@ -201,7 +201,7 @@ func (s *Suite) TestBalance() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - db := deps.StateDB() + db := deps.NewStateDB() tc.malleate(db) // check dirty state @@ -254,7 +254,7 @@ func (s *Suite) TestState() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - db := deps.StateDB() + db := deps.NewStateDB() tc.malleate(db) s.Require().NoError(db.Commit()) @@ -264,7 +264,7 @@ func (s *Suite) TestState() { } // check ForEachStorage - db = deps.StateDB() + db = deps.NewStateDB() collected := CollectContractStorage(db) if len(tc.expStates) > 0 { s.Require().Equal(tc.expStates, collected) @@ -297,7 +297,7 @@ func (s *Suite) TestCode() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - db := deps.StateDB() + db := deps.NewStateDB() tc.malleate(db) // check dirty state @@ -308,7 +308,7 @@ func (s *Suite) TestCode() { s.Require().NoError(db.Commit()) // check again - db = deps.StateDB() + db = deps.NewStateDB() s.Require().Equal(tc.expCode, db.GetCode(address)) s.Require().Equal(len(tc.expCode), db.GetCodeSize(address)) s.Require().Equal(tc.expCodeHash, db.GetCodeHash(address)) @@ -364,7 +364,7 @@ func (s *Suite) TestRevertSnapshot() { deps := evmtest.NewTestDeps() // do some arbitrary changes to the storage - db := deps.StateDB() + db := deps.NewStateDB() db.SetNonce(address, 1) db.AddBalance(address, big.NewInt(100)) db.SetCode(address, []byte("hello world")) @@ -406,7 +406,7 @@ func (s *Suite) TestNestedSnapshot() { value2 := common.BigToHash(big.NewInt(2)) deps := evmtest.NewTestDeps() - db := deps.StateDB() + db := deps.NewStateDB() rev1 := db.Snapshot() db.SetState(address, key, value1) @@ -424,7 +424,7 @@ func (s *Suite) TestNestedSnapshot() { func (s *Suite) TestInvalidSnapshotId() { deps := evmtest.NewTestDeps() - db := deps.StateDB() + db := deps.NewStateDB() s.Require().Panics(func() { db.RevertToSnapshot(1) @@ -499,7 +499,7 @@ func (s *Suite) TestAccessList() { for _, tc := range testCases { deps := evmtest.NewTestDeps() - db := deps.StateDB() + db := deps.NewStateDB() tc.malleate(db) } } @@ -521,7 +521,7 @@ func (s *Suite) TestLog() { } deps := evmtest.NewTestDeps() - db := statedb.New(deps.Ctx, &deps.App.EvmKeeper, txConfig) + db := statedb.New(deps.Ctx, deps.App.EvmKeeper, txConfig) logData := []byte("hello world") log := &gethcore.Log{ @@ -576,7 +576,7 @@ func (s *Suite) TestRefund() { } for _, tc := range testCases { deps := evmtest.NewTestDeps() - db := deps.StateDB() + db := deps.NewStateDB() if !tc.expPanic { tc.malleate(db) s.Require().Equal(tc.expRefund, db.GetRefund()) @@ -595,7 +595,7 @@ func (s *Suite) TestIterateStorage() { value2 := common.BigToHash(big.NewInt(4)) deps := evmtest.NewTestDeps() - db := deps.StateDB() + db := deps.NewStateDB() db.SetState(address, key1, value1) db.SetState(address, key2, value2) From 717ce1cf01c0941a843df8afeac995494b480d2f Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Mon, 28 Oct 2024 19:22:54 -0500 Subject: [PATCH 6/9] revert to previous commit 7f904a07ac4d5555d8c088411024fc50ff65d085 --- app/ante/gas_wanted_test.go | 2 +- app/ante/handler_opts.go | 2 +- app/evmante/evmante_can_transfer_test.go | 4 +- app/evmante/evmante_emit_event_test.go | 4 +- app/evmante/evmante_gas_consume_test.go | 4 +- app/evmante/evmante_handler.go | 18 ++--- app/evmante/evmante_handler_test.go | 2 +- .../evmante_increment_sender_seq_test.go | 4 +- app/evmante/evmante_mempool_fees_test.go | 2 +- app/evmante/evmante_setup_ctx_test.go | 4 +- app/evmante/evmante_sigverify_test.go | 4 +- app/evmante/evmante_validate_basic_test.go | 4 +- app/evmante/evmante_verify_eth_acc_test.go | 4 +- app/keepers.go | 5 +- app/keepers/all_keepers.go | 2 +- x/evm/evmmodule/genesis_test.go | 4 +- x/evm/evmtest/smart_contract_test.go | 4 +- x/evm/evmtest/test_deps.go | 4 +- x/evm/evmtest/tx.go | 62 ++++++++++++++-- x/evm/keeper/bank_extension.go | 23 +++--- x/evm/keeper/funtoken_from_coin.go | 2 +- x/evm/keeper/funtoken_from_coin_test.go | 2 +- x/evm/keeper/funtoken_from_erc20.go | 4 +- x/evm/keeper/funtoken_from_erc20_test.go | 2 +- x/evm/keeper/gas_fees.go | 4 +- x/evm/keeper/grpc_query_test.go | 4 +- x/evm/keeper/keeper.go | 6 +- x/evm/keeper/msg_ethereum_tx_test.go | 8 +-- x/evm/keeper/msg_server.go | 11 ++- x/evm/keeper/statedb.go | 12 ++-- x/evm/keeper/statedb_test.go | 6 +- x/evm/precompile/funtoken.go | 70 +++++++++++++++++-- x/evm/precompile/funtoken_test.go | 4 +- x/evm/statedb/journal.go | 5 +- x/evm/statedb/journal_test.go | 5 ++ x/evm/statedb/statedb.go | 8 +-- x/evm/statedb/statedb_test.go | 36 +++++----- 37 files changed, 227 insertions(+), 124 deletions(-) diff --git a/app/ante/gas_wanted_test.go b/app/ante/gas_wanted_test.go index 92b5d1343..d892a8217 100644 --- a/app/ante/gas_wanted_test.go +++ b/app/ante/gas_wanted_test.go @@ -83,7 +83,7 @@ func (s *AnteTestSuite) TestGasWantedDecorator() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - stateDB := deps.NewStateDB() + stateDB := deps.StateDB() anteDec := ante.AnteDecoratorGasWanted{} tx := tc.txSetup(&deps) diff --git a/app/ante/handler_opts.go b/app/ante/handler_opts.go index 9c1d88301..b516653c8 100644 --- a/app/ante/handler_opts.go +++ b/app/ante/handler_opts.go @@ -20,7 +20,7 @@ type AnteHandlerOptions struct { IBCKeeper *ibckeeper.Keeper DevGasKeeper *devgaskeeper.Keeper DevGasBankKeeper devgasante.BankKeeper - EvmKeeper *evmkeeper.Keeper + EvmKeeper evmkeeper.Keeper AccountKeeper authkeeper.AccountKeeper TxCounterStoreKey types.StoreKey diff --git a/app/evmante/evmante_can_transfer_test.go b/app/evmante/evmante_can_transfer_test.go index 381597624..2fa71c674 100644 --- a/app/evmante/evmante_can_transfer_test.go +++ b/app/evmante/evmante_can_transfer_test.go @@ -88,8 +88,8 @@ func (s *TestSuite) TestCanTransferDecorator() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - stateDB := deps.NewStateDB() - anteDec := evmante.CanTransferDecorator{deps.App.AppKeepers.EvmKeeper} + stateDB := deps.StateDB() + anteDec := evmante.CanTransferDecorator{&deps.App.AppKeepers.EvmKeeper} tx := tc.txSetup(&deps) if tc.ctxSetup != nil { diff --git a/app/evmante/evmante_emit_event_test.go b/app/evmante/evmante_emit_event_test.go index 20ff36f5d..855165450 100644 --- a/app/evmante/evmante_emit_event_test.go +++ b/app/evmante/evmante_emit_event_test.go @@ -41,8 +41,8 @@ func (s *TestSuite) TestEthEmitEventDecorator() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - stateDB := deps.NewStateDB() - anteDec := evmante.NewEthEmitEventDecorator(deps.App.AppKeepers.EvmKeeper) + stateDB := deps.StateDB() + anteDec := evmante.NewEthEmitEventDecorator(&deps.App.AppKeepers.EvmKeeper) tx := tc.txSetup(&deps) s.Require().NoError(stateDB.Commit()) diff --git a/app/evmante/evmante_gas_consume_test.go b/app/evmante/evmante_gas_consume_test.go index 3291c3349..1e3c6b1fe 100644 --- a/app/evmante/evmante_gas_consume_test.go +++ b/app/evmante/evmante_gas_consume_test.go @@ -59,9 +59,9 @@ func (s *TestSuite) TestAnteDecEthGasConsume() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - stateDB := deps.NewStateDB() + stateDB := deps.StateDB() anteDec := evmante.NewAnteDecEthGasConsume( - deps.App.AppKeepers.EvmKeeper, tc.maxGasWanted, + &deps.App.AppKeepers.EvmKeeper, tc.maxGasWanted, ) tc.beforeTxSetup(&deps, stateDB) diff --git a/app/evmante/evmante_handler.go b/app/evmante/evmante_handler.go index a9c2f7d0f..787be312e 100644 --- a/app/evmante/evmante_handler.go +++ b/app/evmante/evmante_handler.go @@ -13,16 +13,16 @@ func NewAnteHandlerEVM( ) sdk.AnteHandler { return sdk.ChainAnteDecorators( // outermost AnteDecorator. SetUpContext must be called first - NewEthSetUpContextDecorator(options.EvmKeeper), - NewMempoolGasPriceDecorator(options.EvmKeeper), - NewEthValidateBasicDecorator(options.EvmKeeper), - NewEthSigVerificationDecorator(options.EvmKeeper), - NewAnteDecVerifyEthAcc(options.EvmKeeper, options.AccountKeeper), - CanTransferDecorator{options.EvmKeeper}, - NewAnteDecEthGasConsume(options.EvmKeeper, options.MaxTxGasWanted), - NewAnteDecEthIncrementSenderSequence(options.EvmKeeper, options.AccountKeeper), + NewEthSetUpContextDecorator(&options.EvmKeeper), + NewMempoolGasPriceDecorator(&options.EvmKeeper), + NewEthValidateBasicDecorator(&options.EvmKeeper), + NewEthSigVerificationDecorator(&options.EvmKeeper), + NewAnteDecVerifyEthAcc(&options.EvmKeeper, options.AccountKeeper), + CanTransferDecorator{&options.EvmKeeper}, + NewAnteDecEthGasConsume(&options.EvmKeeper, options.MaxTxGasWanted), + NewAnteDecEthIncrementSenderSequence(&options.EvmKeeper, options.AccountKeeper), ante.AnteDecoratorGasWanted{}, // emit eth tx hash and index at the very last ante handler. - NewEthEmitEventDecorator(options.EvmKeeper), + NewEthEmitEventDecorator(&options.EvmKeeper), ) } diff --git a/app/evmante/evmante_handler_test.go b/app/evmante/evmante_handler_test.go index 5ab7e8a1f..62e7afb0d 100644 --- a/app/evmante/evmante_handler_test.go +++ b/app/evmante/evmante_handler_test.go @@ -69,7 +69,7 @@ func (s *TestSuite) TestAnteHandlerEVM() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - stateDB := deps.NewStateDB() + stateDB := deps.StateDB() anteHandlerEVM := evmante.NewAnteHandlerEVM( ante.AnteHandlerOptions{ diff --git a/app/evmante/evmante_increment_sender_seq_test.go b/app/evmante/evmante_increment_sender_seq_test.go index b4503e675..ac358cbb0 100644 --- a/app/evmante/evmante_increment_sender_seq_test.go +++ b/app/evmante/evmante_increment_sender_seq_test.go @@ -66,8 +66,8 @@ func (s *TestSuite) TestAnteDecEthIncrementSenderSequence() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - stateDB := deps.NewStateDB() - anteDec := evmante.NewAnteDecEthIncrementSenderSequence(deps.App.EvmKeeper, deps.App.AccountKeeper) + stateDB := deps.StateDB() + anteDec := evmante.NewAnteDecEthIncrementSenderSequence(&deps.App.EvmKeeper, deps.App.AccountKeeper) if tc.beforeTxSetup != nil { tc.beforeTxSetup(&deps, stateDB) diff --git a/app/evmante/evmante_mempool_fees_test.go b/app/evmante/evmante_mempool_fees_test.go index 892bd9e57..ef7b34e64 100644 --- a/app/evmante/evmante_mempool_fees_test.go +++ b/app/evmante/evmante_mempool_fees_test.go @@ -82,7 +82,7 @@ func (s *TestSuite) TestMempoolGasFeeDecorator() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - anteDec := evmante.NewMempoolGasPriceDecorator(deps.App.AppKeepers.EvmKeeper) + anteDec := evmante.NewMempoolGasPriceDecorator(&deps.App.AppKeepers.EvmKeeper) tx := tc.txSetup(&deps) diff --git a/app/evmante/evmante_setup_ctx_test.go b/app/evmante/evmante_setup_ctx_test.go index 028fceb52..9df86ba17 100644 --- a/app/evmante/evmante_setup_ctx_test.go +++ b/app/evmante/evmante_setup_ctx_test.go @@ -12,8 +12,8 @@ import ( func (s *TestSuite) TestEthSetupContextDecorator() { deps := evmtest.NewTestDeps() - stateDB := deps.NewStateDB() - anteDec := evmante.NewEthSetUpContextDecorator(deps.App.EvmKeeper) + stateDB := deps.StateDB() + anteDec := evmante.NewEthSetUpContextDecorator(&deps.App.EvmKeeper) s.Require().NoError(stateDB.Commit()) tx := evmtest.HappyCreateContractTx(&deps) diff --git a/app/evmante/evmante_sigverify_test.go b/app/evmante/evmante_sigverify_test.go index d6a7998b1..63b290140 100644 --- a/app/evmante/evmante_sigverify_test.go +++ b/app/evmante/evmante_sigverify_test.go @@ -66,8 +66,8 @@ func (s *TestSuite) TestEthSigVerificationDecorator() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - stateDB := deps.NewStateDB() - anteDec := evmante.NewEthSigVerificationDecorator(deps.App.AppKeepers.EvmKeeper) + stateDB := deps.StateDB() + anteDec := evmante.NewEthSigVerificationDecorator(&deps.App.AppKeepers.EvmKeeper) tx := tc.txSetup(&deps) s.Require().NoError(stateDB.Commit()) diff --git a/app/evmante/evmante_validate_basic_test.go b/app/evmante/evmante_validate_basic_test.go index 2aa7910dd..3f1263dee 100644 --- a/app/evmante/evmante_validate_basic_test.go +++ b/app/evmante/evmante_validate_basic_test.go @@ -198,8 +198,8 @@ func (s *TestSuite) TestEthValidateBasicDecorator() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - stateDB := deps.NewStateDB() - anteDec := evmante.NewEthValidateBasicDecorator(deps.App.AppKeepers.EvmKeeper) + stateDB := deps.StateDB() + anteDec := evmante.NewEthValidateBasicDecorator(&deps.App.AppKeepers.EvmKeeper) tx := tc.txSetup(&deps) s.Require().NoError(stateDB.Commit()) diff --git a/app/evmante/evmante_verify_eth_acc_test.go b/app/evmante/evmante_verify_eth_acc_test.go index 2af951aa5..6d7f9aeda 100644 --- a/app/evmante/evmante_verify_eth_acc_test.go +++ b/app/evmante/evmante_verify_eth_acc_test.go @@ -64,8 +64,8 @@ func (s *TestSuite) TestAnteDecoratorVerifyEthAcc_CheckTx() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - stateDB := deps.NewStateDB() - anteDec := evmante.NewAnteDecVerifyEthAcc(deps.App.AppKeepers.EvmKeeper, &deps.App.AppKeepers.AccountKeeper) + stateDB := deps.StateDB() + anteDec := evmante.NewAnteDecVerifyEthAcc(&deps.App.AppKeepers.EvmKeeper, &deps.App.AppKeepers.AccountKeeper) tc.beforeTxSetup(&deps, stateDB) tx := tc.txSetup(&deps) diff --git a/app/keepers.go b/app/keepers.go index 173c035d0..be75b2357 100644 --- a/app/keepers.go +++ b/app/keepers.go @@ -378,7 +378,7 @@ func (app *NibiruApp) InitKeepers( ), ) - evmKeeper := evmkeeper.NewKeeper( + app.EvmKeeper = evmkeeper.NewKeeper( appCodec, keys[evm.StoreKey], tkeys[evm.TransientKey], @@ -388,7 +388,6 @@ func (app *NibiruApp) InitKeepers( app.StakingKeeper, cast.ToString(appOpts.Get("evm.tracer")), ) - app.EvmKeeper = &evmKeeper // ---------------------------------- IBC keepers @@ -645,7 +644,7 @@ func (app *NibiruApp) initAppModules( ibcfee.NewAppModule(app.ibcFeeKeeper), ica.NewAppModule(&app.icaControllerKeeper, &app.icaHostKeeper), - evmmodule.NewAppModule(app.EvmKeeper, app.AccountKeeper), + evmmodule.NewAppModule(&app.EvmKeeper, app.AccountKeeper), // wasm wasm.NewAppModule( diff --git a/app/keepers/all_keepers.go b/app/keepers/all_keepers.go index 4692905c7..7bbdc9c10 100644 --- a/app/keepers/all_keepers.go +++ b/app/keepers/all_keepers.go @@ -63,7 +63,7 @@ type PublicKeepers struct { SudoKeeper keeper.Keeper DevGasKeeper devgaskeeper.Keeper TokenFactoryKeeper tokenfactorykeeper.Keeper - EvmKeeper *evmkeeper.Keeper + EvmKeeper evmkeeper.Keeper // WASM keepers WasmKeeper wasmkeeper.Keeper diff --git a/x/evm/evmmodule/genesis_test.go b/x/evm/evmmodule/genesis_test.go index 72b884082..690745e84 100644 --- a/x/evm/evmmodule/genesis_test.go +++ b/x/evm/evmmodule/genesis_test.go @@ -89,13 +89,13 @@ func (s *Suite) TestExportInitGenesis() { s.Require().NoError(err) // Export genesis - evmGenesisState := evmmodule.ExportGenesis(deps.Ctx, deps.EvmKeeper, deps.App.AccountKeeper) + evmGenesisState := evmmodule.ExportGenesis(deps.Ctx, &deps.EvmKeeper, deps.App.AccountKeeper) authGenesisState := deps.App.AccountKeeper.ExportGenesis(deps.Ctx) // Init genesis from the exported state deps = evmtest.NewTestDeps() deps.App.AccountKeeper.InitGenesis(deps.Ctx, *authGenesisState) - evmmodule.InitGenesis(deps.Ctx, deps.EvmKeeper, deps.App.AccountKeeper, *evmGenesisState) + evmmodule.InitGenesis(deps.Ctx, &deps.EvmKeeper, deps.App.AccountKeeper, *evmGenesisState) // Verify erc20 balances for users A, B and sender balance, err := deps.EvmKeeper.ERC20().BalanceOf(erc20Addr, toUserA, deps.Ctx) diff --git a/x/evm/evmtest/smart_contract_test.go b/x/evm/evmtest/smart_contract_test.go index a78dbcf8e..d5ca3434a 100644 --- a/x/evm/evmtest/smart_contract_test.go +++ b/x/evm/evmtest/smart_contract_test.go @@ -17,7 +17,7 @@ func (s *Suite) TestCreateContractTxMsg() { EthAcc: ethAcc, EthChainIDInt: deps.EvmKeeper.EthChainID(deps.Ctx), GasPrice: big.NewInt(1), - Nonce: deps.NewStateDB().GetNonce(ethAcc.EthAddr), + Nonce: deps.StateDB().GetNonce(ethAcc.EthAddr), } ethTxMsg, err := evmtest.CreateContractMsgEthereumTx(args) @@ -33,7 +33,7 @@ func (s *Suite) TestExecuteContractTxMsg() { EthAcc: ethAcc, EthChainIDInt: deps.EvmKeeper.EthChainID(deps.Ctx), GasPrice: big.NewInt(1), - Nonce: deps.NewStateDB().GetNonce(ethAcc.EthAddr), + Nonce: deps.StateDB().GetNonce(ethAcc.EthAddr), ContractAddress: &contractAddress, Data: nil, } diff --git a/x/evm/evmtest/test_deps.go b/x/evm/evmtest/test_deps.go index 6d2e830af..44fb34c31 100644 --- a/x/evm/evmtest/test_deps.go +++ b/x/evm/evmtest/test_deps.go @@ -22,7 +22,7 @@ type TestDeps struct { App *app.NibiruApp Ctx sdk.Context EncCfg codec.EncodingConfig - EvmKeeper *keeper.Keeper + EvmKeeper keeper.Keeper GenState *evm.GenesisState Sender EthPrivKeyAcc } @@ -45,7 +45,7 @@ func NewTestDeps() TestDeps { } } -func (deps TestDeps) NewStateDB() *statedb.StateDB { +func (deps TestDeps) StateDB() *statedb.StateDB { return deps.EvmKeeper.NewStateDB( deps.Ctx, statedb.NewEmptyTxConfig( diff --git a/x/evm/evmtest/tx.go b/x/evm/evmtest/tx.go index 5bfa99a50..dde679851 100644 --- a/x/evm/evmtest/tx.go +++ b/x/evm/evmtest/tx.go @@ -7,6 +7,8 @@ import ( "math/big" "testing" + sdkmath "cosmossdk.io/math" + "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" gethcommon "github.com/ethereum/go-ethereum/common" @@ -26,6 +28,37 @@ import ( type GethTxType = uint8 +func TxTemplateAccessListTx() *gethcore.AccessListTx { + return &gethcore.AccessListTx{ + GasPrice: big.NewInt(1), + Gas: gethparams.TxGas, + To: &gethcommon.Address{}, + Value: big.NewInt(0), + Data: []byte{}, + } +} + +func TxTemplateLegacyTx() *gethcore.LegacyTx { + return &gethcore.LegacyTx{ + GasPrice: big.NewInt(1), + Gas: gethparams.TxGas, + To: &gethcommon.Address{}, + Value: big.NewInt(0), + Data: []byte{}, + } +} + +func TxTemplateDynamicFeeTx() *gethcore.DynamicFeeTx { + return &gethcore.DynamicFeeTx{ + GasFeeCap: big.NewInt(10), + GasTipCap: big.NewInt(2), + Gas: gethparams.TxGas, + To: &gethcommon.Address{}, + Value: big.NewInt(0), + Data: []byte{}, + } +} + func NewEthTxMsgFromTxData( deps *TestDeps, txType GethTxType, @@ -84,7 +117,7 @@ func NewEthTxMsgFromTxData( // ExecuteNibiTransfer executes nibi transfer func ExecuteNibiTransfer(deps *TestDeps, t *testing.T) *evm.MsgEthereumTx { - nonce := deps.NewStateDB().GetNonce(deps.Sender.EthAddr) + nonce := deps.StateDB().GetNonce(deps.Sender.EthAddr) recipient := NewEthPrivAcc().EthAddr txArgs := evm.JsonTxArgs{ @@ -123,7 +156,7 @@ func DeployContract( } bytecodeForCall := append(contract.Bytecode, packedArgs...) - nonce := deps.NewStateDB().GetNonce(deps.Sender.EthAddr) + nonce := deps.StateDB().GetNonce(deps.Sender.EthAddr) ethTxMsg, gethSigner, krSigner, err := GenerateEthTxMsgAndSigner( evm.JsonTxArgs{ Nonce: (*hexutil.Uint64)(&nonce), @@ -180,7 +213,7 @@ func DeployAndExecuteERC20Transfer( "transfer", NewEthPrivAcc().EthAddr, new(big.Int).SetUint64(1000), ) require.NoError(t, err) - nonce = deps.NewStateDB().GetNonce(deps.Sender.EthAddr) + nonce = deps.StateDB().GetNonce(deps.Sender.EthAddr) txArgs := evm.JsonTxArgs{ From: &deps.Sender.EthAddr, To: &contractAddr, @@ -205,7 +238,7 @@ func CallContractTx( input []byte, sender EthPrivKeyAcc, ) (ethTxMsg *evm.MsgEthereumTx, resp *evm.MsgEthereumTxResponse, err error) { - nonce := deps.NewStateDB().GetNonce(sender.EthAddr) + nonce := deps.StateDB().GetNonce(sender.EthAddr) ethTxMsg, gethSigner, krSigner, err := GenerateEthTxMsgAndSigner(evm.JsonTxArgs{ From: &sender.EthAddr, To: &contractAddr, @@ -227,8 +260,6 @@ func CallContractTx( return ethTxMsg, resp, err } -var DefaultEthCallGasLimit = srvconfig.DefaultEthCallGasLimit - // GenerateEthTxMsgAndSigner estimates gas, sets gas limit and returns signer for // the tx. // @@ -278,7 +309,7 @@ func TransferWei( deps, gethcore.LegacyTxType, innerTxData, - deps.NewStateDB().GetNonce(ethAcc.EthAddr), + deps.StateDB().GetNonce(ethAcc.EthAddr), &to, amountWei, gethparams.TxGas, @@ -294,3 +325,20 @@ func TransferWei( } return err } + +// ValidLegacyTx: Useful initial condition for tests +// Exported only for use in tests. +func ValidLegacyTx() *evm.LegacyTx { + sdkInt := sdkmath.NewIntFromBigInt(evm.NativeToWei(big.NewInt(420))) + return &evm.LegacyTx{ + Nonce: 24, + GasLimit: 50_000, + To: gethcommon.HexToAddress("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed").Hex(), + GasPrice: &sdkInt, + Amount: &sdkInt, + Data: []byte{}, + V: []byte{}, + R: []byte{}, + S: []byte{}, + } +} diff --git a/x/evm/keeper/bank_extension.go b/x/evm/keeper/bank_extension.go index ed9ae45ba..a2bcd6d27 100644 --- a/x/evm/keeper/bank_extension.go +++ b/x/evm/keeper/bank_extension.go @@ -17,17 +17,24 @@ var ( type NibiruBankKeeper struct { bankkeeper.BaseKeeper - StateDB *statedb.StateDB + StateDB *statedb.StateDB + balanceChangesForStateDB uint64 } func (evmKeeper *Keeper) NewStateDB( ctx sdk.Context, txConfig statedb.TxConfig, ) *statedb.StateDB { stateDB := statedb.New(ctx, evmKeeper, txConfig) - evmKeeper.Bank.ResetStateDB(stateDB) + 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, @@ -44,17 +51,6 @@ func (bk NibiruBankKeeper) MintCoins( return nil } -func (bk *NibiruBankKeeper) ResetStateDB(db *statedb.StateDB) { - bk.StateDB = db -} - -// s.Require().Equal( -// statedb.FromVM(evmObj).GetBalance( -// eth.NibiruAddrToEthAddr(randomAcc), -// ).String(), -// "420"+strings.Repeat("0", 12), -// ) - func (bk NibiruBankKeeper) BurnCoins( ctx sdk.Context, moduleName string, @@ -99,6 +95,7 @@ func (bk *NibiruBankKeeper) SyncStateDBWithAccount( 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) { diff --git a/x/evm/keeper/funtoken_from_coin.go b/x/evm/keeper/funtoken_from_coin.go index 8075107c6..6f0f2efd0 100644 --- a/x/evm/keeper/funtoken_from_coin.go +++ b/x/evm/keeper/funtoken_from_coin.go @@ -23,7 +23,7 @@ func (k *Keeper) createFunTokenFromCoin( } // 2 | Check for denom metadata in bank state - bankMetadata, isFound := k.Bank.GetDenomMetaData(ctx, bankDenom) + bankMetadata, isFound := k.bankKeeper.GetDenomMetaData(ctx, bankDenom) if !isFound { return nil, fmt.Errorf("bank coin denom should have bank metadata for denom \"%s\"", bankDenom) } diff --git a/x/evm/keeper/funtoken_from_coin_test.go b/x/evm/keeper/funtoken_from_coin_test.go index 92e459390..1f5e3a85a 100644 --- a/x/evm/keeper/funtoken_from_coin_test.go +++ b/x/evm/keeper/funtoken_from_coin_test.go @@ -25,7 +25,7 @@ func (s *FunTokenFromCoinSuite) TestCreateFunTokenFromCoin() { deps := evmtest.NewTestDeps() // Compute contract address. FindERC20 should fail - nonce := deps.NewStateDB().GetNonce(deps.Sender.EthAddr) + nonce := deps.StateDB().GetNonce(deps.Sender.EthAddr) contractAddress := crypto.CreateAddress(deps.Sender.EthAddr, nonce) metadata, err := deps.EvmKeeper.FindERC20Metadata(deps.Ctx, contractAddress) s.Require().Error(err) diff --git a/x/evm/keeper/funtoken_from_erc20.go b/x/evm/keeper/funtoken_from_erc20.go index f1fa3b0b2..e10f0cca8 100644 --- a/x/evm/keeper/funtoken_from_erc20.go +++ b/x/evm/keeper/funtoken_from_erc20.go @@ -122,7 +122,7 @@ func (k *Keeper) createFunTokenFromERC20( bankDenom := fmt.Sprintf("erc20/%s", erc20.String()) // 3 | Coin already registered with FunToken? - _, isFound := k.Bank.GetDenomMetaData(ctx, bankDenom) + _, isFound := k.bankKeeper.GetDenomMetaData(ctx, bankDenom) if isFound { return funtoken, fmt.Errorf("bank coin denom already registered with denom \"%s\"", bankDenom) } @@ -137,7 +137,7 @@ func (k *Keeper) createFunTokenFromERC20( if err != nil { return funtoken, fmt.Errorf("failed to validate bank metadata: %w", err) } - k.Bank.SetDenomMetaData(ctx, bankMetadata) + k.bankKeeper.SetDenomMetaData(ctx, bankMetadata) // 5 | Officially create the funtoken mapping funtoken = &evm.FunToken{ diff --git a/x/evm/keeper/funtoken_from_erc20_test.go b/x/evm/keeper/funtoken_from_erc20_test.go index 53db98b31..eb209f7ca 100644 --- a/x/evm/keeper/funtoken_from_erc20_test.go +++ b/x/evm/keeper/funtoken_from_erc20_test.go @@ -25,7 +25,7 @@ func (s *FunTokenFromErc20Suite) TestCreateFunTokenFromERC20() { deps := evmtest.NewTestDeps() // assert that the ERC20 contract is not deployed - expectedERC20Addr := crypto.CreateAddress(deps.Sender.EthAddr, deps.NewStateDB().GetNonce(deps.Sender.EthAddr)) + expectedERC20Addr := crypto.CreateAddress(deps.Sender.EthAddr, deps.StateDB().GetNonce(deps.Sender.EthAddr)) _, err := deps.EvmKeeper.FindERC20Metadata(deps.Ctx, expectedERC20Addr) s.Error(err) diff --git a/x/evm/keeper/gas_fees.go b/x/evm/keeper/gas_fees.go index c373b06ac..01c72034c 100644 --- a/x/evm/keeper/gas_fees.go +++ b/x/evm/keeper/gas_fees.go @@ -60,7 +60,7 @@ func (k *Keeper) RefundGas( // Refund to sender from the fee collector module account. This account // manages the collection of gas fees. - err := k.Bank.SendCoinsFromModuleToAccount( + err := k.bankKeeper.SendCoinsFromModuleToAccount( ctx, authtypes.FeeCollectorName, // sender msgFrom.Bytes(), // recipient @@ -136,7 +136,7 @@ func (k *Keeper) DeductTxCostsFromUserBalance( } // deduct the full gas cost from the user balance - if err := authante.DeductFees(k.Bank, ctx, signerAcc, fees); err != nil { + if err := authante.DeductFees(k.bankKeeper, ctx, signerAcc, fees); err != nil { return errors.Wrapf(err, "failed to deduct full gas cost %s from the user %s balance", fees, from) } diff --git a/x/evm/keeper/grpc_query_test.go b/x/evm/keeper/grpc_query_test.go index b1d8e0515..b16bea40c 100644 --- a/x/evm/keeper/grpc_query_test.go +++ b/x/evm/keeper/grpc_query_test.go @@ -330,7 +330,7 @@ func (s *Suite) TestQueryStorage() { Key: storageKey.String(), } - stateDB := deps.NewStateDB() + stateDB := deps.StateDB() storageValue := gethcommon.BytesToHash([]byte("value")) stateDB.SetState(addr, storageKey, storageValue) @@ -404,7 +404,7 @@ func (s *Suite) TestQueryCode() { Address: addr.Hex(), } - stateDB := deps.NewStateDB() + stateDB := deps.StateDB() contractBytecode := []byte("bytecode") stateDB.SetCode(addr, contractBytecode) s.Require().NoError(stateDB.Commit()) diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index dd757ced9..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 - Bank *NibiruBankKeeper + bankKeeper *NibiruBankKeeper accountKeeper evm.AccountKeeper stakingKeeper evm.StakingKeeper @@ -79,7 +79,7 @@ func NewKeeper( EvmState: NewEvmState(cdc, storeKey, transientKey), FunTokens: NewFunTokenState(cdc, storeKey), accountKeeper: accKeeper, - Bank: bankKeeper, + bankKeeper: bankKeeper, stakingKeeper: stakingKeeper, tracer: tracer, } @@ -90,7 +90,7 @@ func NewKeeper( // tokens for EVM execution in EVM denom units. func (k *Keeper) GetEvmGasBalance(ctx sdk.Context, addr gethcommon.Address) (balance *big.Int) { nibiruAddr := sdk.AccAddress(addr.Bytes()) - return k.Bank.GetBalance(ctx, nibiruAddr, evm.EVMBankDenom).Amount.BigInt() + return k.bankKeeper.GetBalance(ctx, nibiruAddr, evm.EVMBankDenom).Amount.BigInt() } func (k Keeper) EthChainID(ctx sdk.Context) *big.Int { diff --git a/x/evm/keeper/msg_ethereum_tx_test.go b/x/evm/keeper/msg_ethereum_tx_test.go index 4d9f16345..0c2de7a39 100644 --- a/x/evm/keeper/msg_ethereum_tx_test.go +++ b/x/evm/keeper/msg_ethereum_tx_test.go @@ -47,7 +47,7 @@ func (s *Suite) TestMsgEthereumTx_CreateContract() { EthAcc: ethAcc, EthChainIDInt: deps.EvmKeeper.EthChainID(deps.Ctx), GasPrice: big.NewInt(1), - Nonce: deps.NewStateDB().GetNonce(ethAcc.EthAddr), + Nonce: deps.StateDB().GetNonce(ethAcc.EthAddr), GasLimit: gasLimit, } ethTxMsg, err := evmtest.CreateContractMsgEthereumTx(args) @@ -87,7 +87,7 @@ func (s *Suite) TestMsgEthereumTx_CreateContract() { EthAcc: ethAcc, EthChainIDInt: deps.EvmKeeper.EthChainID(deps.Ctx), GasPrice: big.NewInt(1), - Nonce: deps.NewStateDB().GetNonce(ethAcc.EthAddr), + Nonce: deps.StateDB().GetNonce(ethAcc.EthAddr), } ethTxMsg, err := evmtest.CreateContractMsgEthereumTx(args) s.NoError(err) @@ -139,7 +139,7 @@ func (s *Suite) TestMsgEthereumTx_ExecuteContract() { EthAcc: ethAcc, EthChainIDInt: deps.EvmKeeper.EthChainID(deps.Ctx), GasPrice: big.NewInt(1), - Nonce: deps.NewStateDB().GetNonce(ethAcc.EthAddr), + Nonce: deps.StateDB().GetNonce(ethAcc.EthAddr), GasLimit: gasLimit, ContractAddress: &contractAddr, Data: input, @@ -205,7 +205,7 @@ func (s *Suite) TestMsgEthereumTx_SimpleTransfer() { &deps, tc.txType, innerTxData, - deps.NewStateDB().GetNonce(ethAcc.EthAddr), + deps.StateDB().GetNonce(ethAcc.EthAddr), &to, big.NewInt(fundedAmount), gethparams.TxGas, diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go index da7d79c44..be8abb236 100644 --- a/x/evm/keeper/msg_server.go +++ b/x/evm/keeper/msg_server.go @@ -351,7 +351,6 @@ func (k *Keeper) ApplyEvmMsg(ctx sdk.Context, // The dirty states in `StateDB` is either committed or discarded after return if commit { - fmt.Println("stateDB.Commit in ApplyEvmMsg") if err := stateDB.Commit(); err != nil { return nil, evmObj, fmt.Errorf("failed to commit stateDB: %w", err) } @@ -456,11 +455,11 @@ func (k Keeper) deductCreateFunTokenFee(ctx sdk.Context, msg *evm.MsgCreateFunTo fee := k.FeeForCreateFunToken(ctx) from := sdk.MustAccAddressFromBech32(msg.Sender) // validation in msg.ValidateBasic - if err := k.Bank.SendCoinsFromAccountToModule( + if err := k.bankKeeper.SendCoinsFromAccountToModule( ctx, from, evm.ModuleName, fee); err != nil { return fmt.Errorf("unable to pay the \"create_fun_token_fee\": %w", err) } - if err := k.Bank.BurnCoins(ctx, evm.ModuleName, fee); err != nil { + if err := k.bankKeeper.BurnCoins(ctx, evm.ModuleName, fee); err != nil { return fmt.Errorf("failed to burn the \"create_fun_token_fee\" after payment: %w", err) } return nil @@ -508,7 +507,7 @@ func (k Keeper) convertCoinNativeCoin( funTokenMapping evm.FunToken, ) (*evm.MsgConvertCoinToEvmResponse, error) { // Step 1: Escrow bank coins with EVM module account - err := k.Bank.SendCoinsFromAccountToModule(ctx, sender, evm.ModuleName, sdk.NewCoins(coin)) + err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, evm.ModuleName, sdk.NewCoins(coin)) if err != nil { return nil, errors.Wrap(err, "failed to send coins to module account") } @@ -564,7 +563,7 @@ func (k Keeper) convertCoinNativeERC20( } // Escrow Coins on module account - if err := k.Bank.SendCoinsFromAccountToModule( + if err := k.bankKeeper.SendCoinsFromAccountToModule( ctx, sender, evm.ModuleName, @@ -620,7 +619,7 @@ func (k Keeper) convertCoinNativeERC20( } // Burn escrowed Coins - err = k.Bank.BurnCoins(ctx, evm.ModuleName, sdk.NewCoins(coin)) + err = k.bankKeeper.BurnCoins(ctx, evm.ModuleName, sdk.NewCoins(coin)) if err != nil { return nil, errors.Wrap(err, "failed to burn coins") } diff --git a/x/evm/keeper/statedb.go b/x/evm/keeper/statedb.go index c010dd9b7..575962d02 100644 --- a/x/evm/keeper/statedb.go +++ b/x/evm/keeper/statedb.go @@ -72,27 +72,27 @@ func (k *Keeper) ForEachStorage( func (k *Keeper) SetAccBalance( ctx sdk.Context, addr gethcommon.Address, amountEvmDenom *big.Int, ) error { - addrBech32 := eth.EthAddrToNibiruAddr(addr) - balance := k.Bank.BaseKeeper.GetBalance(ctx, addrBech32, evm.EVMBankDenom).Amount.BigInt() + nativeAddr := sdk.AccAddress(addr.Bytes()) + 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.Bank.BaseKeeper.MintCoins(ctx, evm.ModuleName, coins); err != nil { + if err := k.bankKeeper.BaseKeeper.MintCoins(ctx, evm.ModuleName, coins); err != nil { return err } - if err := k.Bank.BaseKeeper.SendCoinsFromModuleToAccount(ctx, evm.ModuleName, addrBech32, 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.Bank.BaseKeeper.SendCoinsFromAccountToModule(ctx, addrBech32, evm.ModuleName, coins); err != nil { + if err := k.bankKeeper.BaseKeeper.SendCoinsFromAccountToModule(ctx, nativeAddr, evm.ModuleName, coins); err != nil { return err } - if err := k.Bank.BaseKeeper.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/keeper/statedb_test.go b/x/evm/keeper/statedb_test.go index a9fd8cede..a918039a8 100644 --- a/x/evm/keeper/statedb_test.go +++ b/x/evm/keeper/statedb_test.go @@ -21,7 +21,7 @@ import ( func (s *Suite) TestStateDBBalance() { deps := evmtest.NewTestDeps() { - db := deps.NewStateDB() + db := deps.StateDB() s.Equal("0", db.GetBalance(deps.Sender.EthAddr).String()) s.T().Log("fund account in unibi. See expected wei amount.") @@ -47,7 +47,7 @@ func (s *Suite) TestStateDBBalance() { { err := evmtest.TransferWei(&deps, to, evm.NativeToWei(big.NewInt(12))) s.Require().NoError(err) - db := deps.NewStateDB() + db := deps.StateDB() s.Equal( "30"+strings.Repeat("0", 12), db.GetBalance(deps.Sender.EthAddr).String(), @@ -80,7 +80,7 @@ func (s *Suite) TestStateDBBalance() { ) s.NoError(err) - db := deps.NewStateDB() + db := deps.StateDB() s.Equal( "3"+strings.Repeat("0", 12), db.GetBalance(to).String(), diff --git a/x/evm/precompile/funtoken.go b/x/evm/precompile/funtoken.go index a279fc8a0..505c106b4 100644 --- a/x/evm/precompile/funtoken.go +++ b/x/evm/precompile/funtoken.go @@ -7,14 +7,18 @@ import ( "cosmossdk.io/math" 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" gethabi "github.com/ethereum/go-ethereum/accounts/abi" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" "github.com/NibiruChain/nibiru/v2/app/keepers" + "github.com/NibiruChain/nibiru/v2/eth" "github.com/NibiruChain/nibiru/v2/x/evm" "github.com/NibiruChain/nibiru/v2/x/evm/embeds" evmkeeper "github.com/NibiruChain/nibiru/v2/x/evm/keeper" + "github.com/NibiruChain/nibiru/v2/x/evm/statedb" ) var _ vm.PrecompiledContract = (*precompileFunToken)(nil) @@ -73,14 +77,14 @@ func (p precompileFunToken) Run( func PrecompileFunToken(keepers keepers.PublicKeepers) vm.PrecompiledContract { return precompileFunToken{ - bankKeeper: keepers.EvmKeeper.Bank, + bankKeeper: keepers.BankKeeper, evmKeeper: keepers.EvmKeeper, } } type precompileFunToken struct { - bankKeeper *evmkeeper.NibiruBankKeeper - evmKeeper *evmkeeper.Keeper + bankKeeper bankkeeper.Keeper + evmKeeper evmkeeper.Keeper } var executionGuard sync.Mutex @@ -158,7 +162,7 @@ func (p precompileFunToken) bankSend( return } } else { - err = p.evmKeeper.Bank.MintCoins(ctx, evm.ModuleName, sdk.NewCoins(coinToSend)) + err = SafeMintCoins(ctx, evm.ModuleName, coinToSend, p.bankKeeper, start.StateDB) if err != nil { return nil, fmt.Errorf("mint failed for module \"%s\" (%s): contract caller %s: %w", evm.ModuleName, evm.EVM_MODULE_ADDRESS.Hex(), caller.Hex(), err, @@ -167,11 +171,13 @@ func (p precompileFunToken) bankSend( } // Transfer the bank coin - err = p.evmKeeper.Bank.SendCoinsFromModuleToAccount( + err = SafeSendCoinFromModuleToAccount( ctx, evm.ModuleName, toAddr, - sdk.NewCoins(coinToSend), + coinToSend, + p.bankKeeper, + start.StateDB, ) if err != nil { return nil, fmt.Errorf("send failed for module \"%s\" (%s): contract caller %s: %w", @@ -184,6 +190,58 @@ func (p precompileFunToken) bankSend( return method.Outputs.Pack() } +func SafeMintCoins( + ctx sdk.Context, + moduleName string, + amt sdk.Coin, + bk bankkeeper.Keeper, + db *statedb.StateDB, +) error { + err := bk.MintCoins(ctx, evm.ModuleName, sdk.NewCoins(amt)) + if err != nil { + return err + } + if amt.Denom == evm.EVMBankDenom { + evmBech32Addr := auth.NewModuleAddress(evm.ModuleName) + balAfter := bk.GetBalance(ctx, evmBech32Addr, amt.Denom).Amount.BigInt() + db.SetBalanceWei( + evm.EVM_MODULE_ADDRESS, + evm.NativeToWei(balAfter), + ) + } + + return nil +} + +func SafeSendCoinFromModuleToAccount( + ctx sdk.Context, + senderModule string, + recipientAddr sdk.AccAddress, + amt sdk.Coin, + bk bankkeeper.Keeper, + db *statedb.StateDB, +) error { + err := bk.SendCoinsFromModuleToAccount(ctx, senderModule, recipientAddr, sdk.NewCoins(amt)) + if err != nil { + return err + } + if amt.Denom == evm.EVMBankDenom { + evmBech32Addr := auth.NewModuleAddress(evm.ModuleName) + balAfterFrom := bk.GetBalance(ctx, evmBech32Addr, amt.Denom).Amount.BigInt() + db.SetBalanceWei( + evm.EVM_MODULE_ADDRESS, + evm.NativeToWei(balAfterFrom), + ) + + balAfterTo := bk.GetBalance(ctx, recipientAddr, amt.Denom).Amount.BigInt() + db.SetBalanceWei( + eth.NibiruAddrToEthAddr(recipientAddr), + evm.NativeToWei(balAfterTo), + ) + } + return nil +} + func (p precompileFunToken) decomposeBankSendArgs(args []any) ( erc20 gethcommon.Address, amount *big.Int, diff --git a/x/evm/precompile/funtoken_test.go b/x/evm/precompile/funtoken_test.go index 9c26255c4..dd5176fb3 100644 --- a/x/evm/precompile/funtoken_test.go +++ b/x/evm/precompile/funtoken_test.go @@ -120,7 +120,7 @@ func (s *FuntokenSuite) TestHappyPath() { randomAcc := testutil.AccAddress() - s.T().Log("Send NIBI (FunToken) using precompile") + s.T().Log("Send using precompile") amtToSend := int64(420) callArgs := []any{erc20, big.NewInt(amtToSend), randomAcc.String()} input, err := embeds.SmartContract_FunToken.ABI.Pack(string(precompile.FunTokenMethod_BankSend), callArgs...) @@ -134,12 +134,10 @@ func (s *FuntokenSuite) TestHappyPath() { ) s.Require().NoError(err) s.Require().Empty(resp.VmError) - s.True(deps.EvmKeeper == deps.App.EvmKeeper) evmtest.AssertERC20BalanceEqual(s.T(), deps, erc20, deps.Sender.EthAddr, big.NewInt(69_000)) evmtest.AssertERC20BalanceEqual(s.T(), deps, erc20, evm.EVM_MODULE_ADDRESS, big.NewInt(0)) s.Equal(sdk.NewInt(420).String(), deps.App.BankKeeper.GetBalance(deps.Ctx, randomAcc, funtoken.BankDenom).Amount.String(), ) - s.Require().NotNil(deps.EvmKeeper.Bank.StateDB) } diff --git a/x/evm/statedb/journal.go b/x/evm/statedb/journal.go index 7a3c495c0..e684fd574 100644 --- a/x/evm/statedb/journal.go +++ b/x/evm/statedb/journal.go @@ -373,8 +373,9 @@ func (ch PrecompileCalled) Revert(s *StateDB) { // Rewrite the `writeCacheCtxFn` using the same logic as sdk.Context.CacheCtx s.writeToCommitCtxFromCacheCtx = func() { s.evmTxCtx.EventManager().EmitEvents(ch.Events) - // TODO: UD-DEBUG: Overwriting events might fix an issue with appending - // too many Check correctness of the emitted events + // TODO: UD-DEBUG: Overwriting events might fix an issue with + // appending too many + // Check correctness of the emitted events ch.MultiStore.Write() } } diff --git a/x/evm/statedb/journal_test.go b/x/evm/statedb/journal_test.go index 220c7ef38..046fc514c 100644 --- a/x/evm/statedb/journal_test.go +++ b/x/evm/statedb/journal_test.go @@ -218,6 +218,11 @@ snapshots and see the prior states.`)) &s.Suite, deps, wasmContract, 7, // state before precompile called ) }) + + s.Run("too many precompile calls in one tx will fail", func() { + // currently + // evmObj + }) } func debugDirtiesCountMismatch(db *statedb.StateDB, t *testing.T) string { diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go index a61c4b14e..957da7888 100644 --- a/x/evm/statedb/statedb.go +++ b/x/evm/statedb/statedb.go @@ -78,13 +78,8 @@ type StateDB struct { accessList *accessList } -func FromVM(evmObj *vm.EVM) *StateDB { - return evmObj.StateDB.(*StateDB) -} - // New creates a new state from a given trie. func New(ctx sdk.Context, keeper Keeper, txConfig TxConfig) *StateDB { - fmt.Println("statedb.New called") return &StateDB{ keeper: keeper, evmTxCtx: ctx, @@ -485,6 +480,9 @@ func (s *StateDB) Snapshot() int { // RevertToSnapshot reverts all state changes made since the given revision. func (s *StateDB) RevertToSnapshot(revid int) { + fmt.Printf("len(s.validRevisions): %d\n", len(s.validRevisions)) + fmt.Printf("s.validRevisions: %v\n", s.validRevisions) + // Find the snapshot in the stack of valid snapshots. idx := sort.Search(len(s.validRevisions), func(i int) bool { return s.validRevisions[i].id >= revid diff --git a/x/evm/statedb/statedb_test.go b/x/evm/statedb/statedb_test.go index a89444042..7919d3da0 100644 --- a/x/evm/statedb/statedb_test.go +++ b/x/evm/statedb/statedb_test.go @@ -82,7 +82,7 @@ func (s *Suite) TestAccount() { s.Require().EqualValues(statedb.NewEmptyAccount(), acct) s.Require().Empty(CollectContractStorage(db)) - db = deps.NewStateDB() + db = deps.StateDB() s.Require().Equal(true, db.Exist(address)) s.Require().Equal(true, db.Empty(address)) s.Require().Equal(big.NewInt(0), db.GetBalance(address)) @@ -104,7 +104,7 @@ func (s *Suite) TestAccount() { s.Require().NoError(db.Commit()) // suicide - db = deps.NewStateDB() + db = deps.StateDB() s.Require().False(db.HasSuicided(address)) s.Require().True(db.Suicide(address)) @@ -119,7 +119,7 @@ func (s *Suite) TestAccount() { s.Require().NoError(db.Commit()) // not accessible from StateDB anymore - db = deps.NewStateDB() + db = deps.StateDB() s.Require().False(db.Exist(address)) s.Require().Empty(CollectContractStorage(db)) }}, @@ -127,7 +127,7 @@ func (s *Suite) TestAccount() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - db := deps.NewStateDB() + db := deps.StateDB() tc.malleate(&deps, db) }) } @@ -135,7 +135,7 @@ func (s *Suite) TestAccount() { func (s *Suite) TestAccountOverride() { deps := evmtest.NewTestDeps() - db := deps.NewStateDB() + db := deps.StateDB() // test balance carry over when overwritten amount := big.NewInt(1) @@ -168,7 +168,7 @@ func (s *Suite) TestDBError() { } for _, tc := range testCases { deps := evmtest.NewTestDeps() - db := deps.NewStateDB() + db := deps.StateDB() tc.malleate(db) s.Require().NoError(db.Commit()) } @@ -201,7 +201,7 @@ func (s *Suite) TestBalance() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - db := deps.NewStateDB() + db := deps.StateDB() tc.malleate(db) // check dirty state @@ -254,7 +254,7 @@ func (s *Suite) TestState() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - db := deps.NewStateDB() + db := deps.StateDB() tc.malleate(db) s.Require().NoError(db.Commit()) @@ -264,7 +264,7 @@ func (s *Suite) TestState() { } // check ForEachStorage - db = deps.NewStateDB() + db = deps.StateDB() collected := CollectContractStorage(db) if len(tc.expStates) > 0 { s.Require().Equal(tc.expStates, collected) @@ -297,7 +297,7 @@ func (s *Suite) TestCode() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - db := deps.NewStateDB() + db := deps.StateDB() tc.malleate(db) // check dirty state @@ -308,7 +308,7 @@ func (s *Suite) TestCode() { s.Require().NoError(db.Commit()) // check again - db = deps.NewStateDB() + db = deps.StateDB() s.Require().Equal(tc.expCode, db.GetCode(address)) s.Require().Equal(len(tc.expCode), db.GetCodeSize(address)) s.Require().Equal(tc.expCodeHash, db.GetCodeHash(address)) @@ -364,7 +364,7 @@ func (s *Suite) TestRevertSnapshot() { deps := evmtest.NewTestDeps() // do some arbitrary changes to the storage - db := deps.NewStateDB() + db := deps.StateDB() db.SetNonce(address, 1) db.AddBalance(address, big.NewInt(100)) db.SetCode(address, []byte("hello world")) @@ -406,7 +406,7 @@ func (s *Suite) TestNestedSnapshot() { value2 := common.BigToHash(big.NewInt(2)) deps := evmtest.NewTestDeps() - db := deps.NewStateDB() + db := deps.StateDB() rev1 := db.Snapshot() db.SetState(address, key, value1) @@ -424,7 +424,7 @@ func (s *Suite) TestNestedSnapshot() { func (s *Suite) TestInvalidSnapshotId() { deps := evmtest.NewTestDeps() - db := deps.NewStateDB() + db := deps.StateDB() s.Require().Panics(func() { db.RevertToSnapshot(1) @@ -499,7 +499,7 @@ func (s *Suite) TestAccessList() { for _, tc := range testCases { deps := evmtest.NewTestDeps() - db := deps.NewStateDB() + db := deps.StateDB() tc.malleate(db) } } @@ -521,7 +521,7 @@ func (s *Suite) TestLog() { } deps := evmtest.NewTestDeps() - db := statedb.New(deps.Ctx, deps.App.EvmKeeper, txConfig) + db := statedb.New(deps.Ctx, &deps.App.EvmKeeper, txConfig) logData := []byte("hello world") log := &gethcore.Log{ @@ -576,7 +576,7 @@ func (s *Suite) TestRefund() { } for _, tc := range testCases { deps := evmtest.NewTestDeps() - db := deps.NewStateDB() + db := deps.StateDB() if !tc.expPanic { tc.malleate(db) s.Require().Equal(tc.expRefund, db.GetRefund()) @@ -595,7 +595,7 @@ func (s *Suite) TestIterateStorage() { value2 := common.BigToHash(big.NewInt(4)) deps := evmtest.NewTestDeps() - db := deps.NewStateDB() + db := deps.StateDB() db.SetState(address, key1, value1) db.SetState(address, key2, value2) From 04a6897d618ec4ff5155f182d9413fdb45e6f1c8 Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Mon, 28 Oct 2024 19:28:22 -0500 Subject: [PATCH 7/9] fix strange ignored file issue --- x/evm/evmtest/tx.go | 182 ++++++++++++++++++++++---------------------- 1 file changed, 93 insertions(+), 89 deletions(-) diff --git a/x/evm/evmtest/tx.go b/x/evm/evmtest/tx.go index dde679851..b2bfe3e34 100644 --- a/x/evm/evmtest/tx.go +++ b/x/evm/evmtest/tx.go @@ -26,95 +26,6 @@ import ( "github.com/NibiruChain/nibiru/v2/x/evm/embeds" ) -type GethTxType = uint8 - -func TxTemplateAccessListTx() *gethcore.AccessListTx { - return &gethcore.AccessListTx{ - GasPrice: big.NewInt(1), - Gas: gethparams.TxGas, - To: &gethcommon.Address{}, - Value: big.NewInt(0), - Data: []byte{}, - } -} - -func TxTemplateLegacyTx() *gethcore.LegacyTx { - return &gethcore.LegacyTx{ - GasPrice: big.NewInt(1), - Gas: gethparams.TxGas, - To: &gethcommon.Address{}, - Value: big.NewInt(0), - Data: []byte{}, - } -} - -func TxTemplateDynamicFeeTx() *gethcore.DynamicFeeTx { - return &gethcore.DynamicFeeTx{ - GasFeeCap: big.NewInt(10), - GasTipCap: big.NewInt(2), - Gas: gethparams.TxGas, - To: &gethcommon.Address{}, - Value: big.NewInt(0), - Data: []byte{}, - } -} - -func NewEthTxMsgFromTxData( - deps *TestDeps, - txType GethTxType, - innerTxData []byte, - nonce uint64, - to *gethcommon.Address, - value *big.Int, - gas uint64, - accessList gethcore.AccessList, -) (*evm.MsgEthereumTx, error) { - if innerTxData == nil { - innerTxData = []byte{} - } - - var ethCoreTx *gethcore.Transaction - switch txType { - case gethcore.LegacyTxType: - innerTx := TxTemplateLegacyTx() - innerTx.Nonce = nonce - innerTx.Data = innerTxData - innerTx.To = to - innerTx.Value = value - innerTx.Gas = gas - ethCoreTx = gethcore.NewTx(innerTx) - case gethcore.AccessListTxType: - innerTx := TxTemplateAccessListTx() - innerTx.Nonce = nonce - innerTx.Data = innerTxData - innerTx.AccessList = accessList - innerTx.To = to - innerTx.Value = value - innerTx.Gas = gas - ethCoreTx = gethcore.NewTx(innerTx) - case gethcore.DynamicFeeTxType: - innerTx := TxTemplateDynamicFeeTx() - innerTx.Nonce = nonce - innerTx.Data = innerTxData - innerTx.To = to - innerTx.Value = value - innerTx.Gas = gas - innerTx.AccessList = accessList - ethCoreTx = gethcore.NewTx(innerTx) - default: - return nil, fmt.Errorf( - "received unknown tx type (%v) in NewEthTxMsgFromTxData", txType) - } - - ethTxMsg := new(evm.MsgEthereumTx) - if err := ethTxMsg.FromEthereumTx(ethCoreTx); err != nil { - return ethTxMsg, err - } - - ethTxMsg.From = deps.Sender.EthAddr.Hex() - return ethTxMsg, ethTxMsg.Sign(deps.GethSigner(), deps.Sender.KeyringSigner) -} - // ExecuteNibiTransfer executes nibi transfer func ExecuteNibiTransfer(deps *TestDeps, t *testing.T) *evm.MsgEthereumTx { nonce := deps.StateDB().GetNonce(deps.Sender.EthAddr) @@ -326,6 +237,10 @@ func TransferWei( return err } +// -------------------------------------------------- +// Templates +// -------------------------------------------------- + // ValidLegacyTx: Useful initial condition for tests // Exported only for use in tests. func ValidLegacyTx() *evm.LegacyTx { @@ -342,3 +257,92 @@ func ValidLegacyTx() *evm.LegacyTx { S: []byte{}, } } + +type GethTxType = uint8 + +func TxTemplateAccessListTx() *gethcore.AccessListTx { + return &gethcore.AccessListTx{ + GasPrice: big.NewInt(1), + Gas: gethparams.TxGas, + To: &gethcommon.Address{}, + Value: big.NewInt(0), + Data: []byte{}, + } +} + +func TxTemplateLegacyTx() *gethcore.LegacyTx { + return &gethcore.LegacyTx{ + GasPrice: big.NewInt(1), + Gas: gethparams.TxGas, + To: &gethcommon.Address{}, + Value: big.NewInt(0), + Data: []byte{}, + } +} + +func TxTemplateDynamicFeeTx() *gethcore.DynamicFeeTx { + return &gethcore.DynamicFeeTx{ + GasFeeCap: big.NewInt(10), + GasTipCap: big.NewInt(2), + Gas: gethparams.TxGas, + To: &gethcommon.Address{}, + Value: big.NewInt(0), + Data: []byte{}, + } +} + +func NewEthTxMsgFromTxData( + deps *TestDeps, + txType GethTxType, + innerTxData []byte, + nonce uint64, + to *gethcommon.Address, + value *big.Int, + gas uint64, + accessList gethcore.AccessList, +) (*evm.MsgEthereumTx, error) { + if innerTxData == nil { + innerTxData = []byte{} + } + + var ethCoreTx *gethcore.Transaction + switch txType { + case gethcore.LegacyTxType: + innerTx := TxTemplateLegacyTx() + innerTx.Nonce = nonce + innerTx.Data = innerTxData + innerTx.To = to + innerTx.Value = value + innerTx.Gas = gas + ethCoreTx = gethcore.NewTx(innerTx) + case gethcore.AccessListTxType: + innerTx := TxTemplateAccessListTx() + innerTx.Nonce = nonce + innerTx.Data = innerTxData + innerTx.AccessList = accessList + innerTx.To = to + innerTx.Value = value + innerTx.Gas = gas + ethCoreTx = gethcore.NewTx(innerTx) + case gethcore.DynamicFeeTxType: + innerTx := TxTemplateDynamicFeeTx() + innerTx.Nonce = nonce + innerTx.Data = innerTxData + innerTx.To = to + innerTx.Value = value + innerTx.Gas = gas + innerTx.AccessList = accessList + ethCoreTx = gethcore.NewTx(innerTx) + default: + return nil, fmt.Errorf( + "received unknown tx type (%v) in NewEthTxMsgFromTxData", txType) + } + + ethTxMsg := new(evm.MsgEthereumTx) + if err := ethTxMsg.FromEthereumTx(ethCoreTx); err != nil { + return ethTxMsg, err + } + + ethTxMsg.From = deps.Sender.EthAddr.Hex() + return ethTxMsg, ethTxMsg.Sign(deps.GethSigner(), deps.Sender.KeyringSigner) +} From 7c34423434f7f2768ce92759e5c2c1d24d68e9f0 Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Mon, 28 Oct 2024 19:49:45 -0500 Subject: [PATCH 8/9] remove new bank keeper --- CHANGELOG.md | 23 ++--- app/keepers.go | 15 +-- x/evm/keeper/bank_extension.go | 163 --------------------------------- x/evm/keeper/keeper.go | 5 +- x/evm/keeper/statedb.go | 17 +++- x/evm/statedb/debug.go | 39 ++++++++ x/evm/statedb/journal.go | 19 ---- x/evm/statedb/journal_test.go | 21 ++--- x/evm/statedb/statedb.go | 9 -- 9 files changed, 72 insertions(+), 239 deletions(-) delete mode 100644 x/evm/keeper/bank_extension.go create mode 100644 x/evm/statedb/debug.go diff --git a/CHANGELOG.md b/CHANGELOG.md index ac2dba69c..473fdaf98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,21 +70,14 @@ consistent setup and dynamic gas calculations, addressing the following tickets. - [#2089](https://github.com/NibiruChain/nibiru/pull/2089) - better handling of gas consumption within erc20 contract execution - [#2091](https://github.com/NibiruChain/nibiru/pull/2091) - feat(evm): add fun token creation fee validation - [#2094](https://github.com/NibiruChain/nibiru/pull/2094) - fix(evm): Following -from the changs in #2086, this pull request implements two critical security -fixes. - 1. First, we add new `JournalChange` struct that saves a deep copy of the - state multi store before each state-modifying, Nibiru-specific precompiled - contract is called (`OnRunStart`). Additionally, we commit the `StateDB` there - as well. This guarantees that the non-EVM and EVM state will be in sync even - if there are complex, multi-step Ethereum transactions, such as in the case of - an EthereumTx that influences the `StateDB`, then calls a precompile that also - changes non-EVM state, and then EVM reverts inside of a try-catch. - 2. Second, the solution from #2086 that records NIBI (ether) transfers on the - `StateDB` during precompiled contract calls is generalized as - `NibiruBankKeeper`, which is struct extension of the `bankkeeper.BaseKeeper` - that is used throughout the Nibiru base application. The `NibiruBankKeeper` - holds a reference to the current EVM `StateDB` if there is one and records - balance changes in wei as journal changes automatically. +from the changs in #2086, this pull request implements a new `JournalChange` +struct that saves a deep copy of the state multi store before each +state-modifying, Nibiru-specific precompiled contract is called (`OnRunStart`). +Additionally, we commit the `StateDB` there as well. This guarantees that the +non-EVM and EVM state will be in sync even if there are complex, multi-step +Ethereum transactions, such as in the case of an EthereumTx that influences the +`StateDB`, then calls a precompile that also changes non-EVM state, and then EVM +reverts inside of a try-catch. #### Nibiru EVM | Before Audit 1 - 2024-10-18 diff --git a/app/keepers.go b/app/keepers.go index be75b2357..4c5721e83 100644 --- a/app/keepers.go +++ b/app/keepers.go @@ -271,18 +271,7 @@ func (app *NibiruApp) InitKeepers( BlockedAddresses(), govModuleAddr, ) - nibiruBankKeeper := evmkeeper.NibiruBankKeeper{ - BaseKeeper: bankkeeper.NewBaseKeeper( - appCodec, - keys[banktypes.StoreKey], - app.AccountKeeper, - BlockedAddresses(), - govModuleAddr, - ), - StateDB: nil, - } - app.BankKeeper = nibiruBankKeeper - + app.BankKeeper = app.bankBaseKeeper app.StakingKeeper = stakingkeeper.NewKeeper( appCodec, keys[stakingtypes.StoreKey], @@ -384,7 +373,7 @@ func (app *NibiruApp) InitKeepers( tkeys[evm.TransientKey], authtypes.NewModuleAddress(govtypes.ModuleName), app.AccountKeeper, - &nibiruBankKeeper, + app.BankKeeper, app.StakingKeeper, cast.ToString(appOpts.Get("evm.tracer")), ) diff --git a/x/evm/keeper/bank_extension.go b/x/evm/keeper/bank_extension.go deleted file mode 100644 index a2bcd6d27..000000000 --- a/x/evm/keeper/bank_extension.go +++ /dev/null @@ -1,163 +0,0 @@ -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 c6b0720d8..dd31229fd 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -15,6 +15,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/NibiruChain/nibiru/v2/app/appconst" @@ -40,7 +41,7 @@ type Keeper struct { // this should be the x/gov module account. authority sdk.AccAddress - bankKeeper *NibiruBankKeeper + bankKeeper bankkeeper.Keeper accountKeeper evm.AccountKeeper stakingKeeper evm.StakingKeeper @@ -63,7 +64,7 @@ func NewKeeper( storeKey, transientKey storetypes.StoreKey, authority sdk.AccAddress, accKeeper evm.AccountKeeper, - bankKeeper *NibiruBankKeeper, + bankKeeper bankkeeper.Keeper, stakingKeeper evm.StakingKeeper, tracer string, ) Keeper { diff --git a/x/evm/keeper/statedb.go b/x/evm/keeper/statedb.go index 575962d02..7aff8c02d 100644 --- a/x/evm/keeper/statedb.go +++ b/x/evm/keeper/statedb.go @@ -17,6 +17,13 @@ import ( var _ statedb.Keeper = &Keeper{} +func (k *Keeper) NewStateDB( + ctx sdk.Context, + txConfig statedb.TxConfig, +) *statedb.StateDB { + return statedb.New(ctx, k, txConfig) +} + // ---------------------------------------------------------------------------- // StateDB Keeper implementation // ---------------------------------------------------------------------------- @@ -73,26 +80,26 @@ func (k *Keeper) SetAccBalance( ctx sdk.Context, addr gethcommon.Address, amountEvmDenom *big.Int, ) error { nativeAddr := sdk.AccAddress(addr.Bytes()) - balance := k.bankKeeper.BaseKeeper.GetBalance(ctx, nativeAddr, evm.EVMBankDenom).Amount.BigInt() + balance := k.bankKeeper.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.BaseKeeper.MintCoins(ctx, evm.ModuleName, coins); err != nil { + if err := k.bankKeeper.MintCoins(ctx, evm.ModuleName, coins); err != nil { return err } - if err := k.bankKeeper.BaseKeeper.SendCoinsFromModuleToAccount(ctx, evm.ModuleName, nativeAddr, coins); err != nil { + if err := k.bankKeeper.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.BaseKeeper.SendCoinsFromAccountToModule(ctx, nativeAddr, evm.ModuleName, coins); err != nil { + if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, nativeAddr, evm.ModuleName, coins); err != nil { return err } - if err := k.bankKeeper.BaseKeeper.BurnCoins(ctx, evm.ModuleName, coins); err != nil { + if err := k.bankKeeper.BurnCoins(ctx, evm.ModuleName, coins); err != nil { return err } default: diff --git a/x/evm/statedb/debug.go b/x/evm/statedb/debug.go new file mode 100644 index 000000000..c2b5fb968 --- /dev/null +++ b/x/evm/statedb/debug.go @@ -0,0 +1,39 @@ +package statedb + +// Copyright (c) 2023-2024 Nibi, Inc. + +import ( + "github.com/ethereum/go-ethereum/common" +) + +// DebugDirtiesCount is a test helper to inspect how many entries in the journal +// are still dirty (uncommitted). After calling [StateDB.Commit], this function +// should return zero. +func (s *StateDB) DebugDirtiesCount() int { + dirtiesCount := 0 + for _, dirtyCount := range s.Journal.dirties { + dirtiesCount += dirtyCount + } + return dirtiesCount +} + +// DebugDirties is a test helper that returns the journal's dirty account changes map. +func (s *StateDB) DebugDirties() map[common.Address]int { + return s.Journal.dirties +} + +// DebugEntries is a test helper that returns the sequence of [JournalChange] +// objects added during execution. +func (s *StateDB) DebugEntries() []JournalChange { + return s.Journal.entries +} + +// DebugStateObjects is a test helper that returns returns a copy of the +// [StateDB.stateObjects] map. +func (s *StateDB) DebugStateObjects() map[common.Address]*stateObject { + copyOfMap := make(map[common.Address]*stateObject) + for key, val := range s.stateObjects { + copyOfMap[key] = val + } + return copyOfMap +} diff --git a/x/evm/statedb/journal.go b/x/evm/statedb/journal.go index e684fd574..dd42a16f1 100644 --- a/x/evm/statedb/journal.go +++ b/x/evm/statedb/journal.go @@ -93,25 +93,6 @@ func (j *journal) Length() int { return len(j.entries) } -// DirtiesCount is a test helper to inspect how many entries in the journal are -// still dirty (uncommitted). After calling [StateDB.Commit], this function should -// return zero. -func (s *StateDB) DirtiesCount() int { - dirtiesCount := 0 - for _, dirtyCount := range s.Journal.dirties { - dirtiesCount += dirtyCount - } - return dirtiesCount -} - -func (s *StateDB) Dirties() map[common.Address]int { - return s.Journal.dirties -} - -func (s *StateDB) Entries() []JournalChange { - return s.Journal.entries -} - // ------------------------------------------------------ // createObjectChange diff --git a/x/evm/statedb/journal_test.go b/x/evm/statedb/journal_test.go index 046fc514c..6390b640f 100644 --- a/x/evm/statedb/journal_test.go +++ b/x/evm/statedb/journal_test.go @@ -59,7 +59,7 @@ func (s *Suite) TestComplexJournalChanges() { s.Run("Populate dirty journal entries. Remove with Commit", func() { stateDB := evmObj.StateDB.(*statedb.StateDB) - s.Equal(0, stateDB.DirtiesCount()) + s.Equal(0, stateDB.DebugDirtiesCount()) randomAcc := evmtest.NewEthPrivAcc().EthAddr balDelta := evm.NativeToWei(big.NewInt(4)) @@ -69,7 +69,7 @@ func (s *Suite) TestComplexJournalChanges() { stateDB.AddBalance(randomAcc, balDelta) // 1 dirties from [balanceChange] stateDB.SubBalance(randomAcc, balDelta) - if stateDB.DirtiesCount() != 4 { + if stateDB.DebugDirtiesCount() != 4 { debugDirtiesCountMismatch(stateDB, s.T()) s.FailNow("expected 4 dirty journal changes") } @@ -77,7 +77,7 @@ func (s *Suite) TestComplexJournalChanges() { s.T().Log("StateDB.Commit, then Dirties should be gone") err = stateDB.Commit() s.NoError(err) - if stateDB.DirtiesCount() != 0 { + if stateDB.DebugDirtiesCount() != 0 { debugDirtiesCountMismatch(stateDB, s.T()) s.FailNow("expected 0 dirty journal changes") } @@ -99,7 +99,7 @@ func (s *Suite) TestComplexJournalChanges() { ) s.Require().NoError(err) stateDB := evmObj.StateDB.(*statedb.StateDB) - if stateDB.DirtiesCount() != 2 { + if stateDB.DebugDirtiesCount() != 2 { debugDirtiesCountMismatch(stateDB, s.T()) s.FailNow("expected 2 dirty journal changes") } @@ -137,7 +137,7 @@ func (s *Suite) TestComplexJournalChanges() { ) stateDB, ok := evmObj.StateDB.(*statedb.StateDB) s.Require().True(ok, "error retrieving StateDB from the EVM") - if stateDB.DirtiesCount() != 0 { + if stateDB.DebugDirtiesCount() != 0 { debugDirtiesCountMismatch(stateDB, s.T()) s.FailNow("expected 0 dirty journal changes") } @@ -151,7 +151,7 @@ func (s *Suite) TestComplexJournalChanges() { s.Require().True(ok, "error retrieving StateDB from the EVM") s.T().Log("Expect exactly 0 dirty journal entry for the precompile snapshot") - if stateDB.DirtiesCount() != 0 { + if stateDB.DebugDirtiesCount() != 0 { debugDirtiesCountMismatch(stateDB, s.T()) s.FailNow("expected 0 dirty journal changes") } @@ -218,17 +218,12 @@ snapshots and see the prior states.`)) &s.Suite, deps, wasmContract, 7, // state before precompile called ) }) - - s.Run("too many precompile calls in one tx will fail", func() { - // currently - // evmObj - }) } func debugDirtiesCountMismatch(db *statedb.StateDB, t *testing.T) string { lines := []string{} - dirties := db.Dirties() - stateObjects := db.StateObjects() + dirties := db.DebugDirties() + stateObjects := db.DebugStateObjects() for addr, dirtyCountForAddr := range dirties { lines = append(lines, fmt.Sprintf("Dirty addr: %s, dirtyCountForAddr=%d", addr, dirtyCountForAddr)) diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go index 957da7888..4c79e61af 100644 --- a/x/evm/statedb/statedb.go +++ b/x/evm/statedb/statedb.go @@ -608,12 +608,3 @@ func (s *StateDB) SavePrecompileCalledJournalChange( } const maxMultistoreCacheCount uint8 = 10 - -// StateObjects: Returns a copy of the [StateDB.stateObjects] map. -func (s *StateDB) StateObjects() map[common.Address]*stateObject { - copyOfMap := make(map[common.Address]*stateObject) - for key, val := range s.stateObjects { - copyOfMap[key] = val - } - return copyOfMap -} From 4e3bf3c4f8411599530ac8bbd3f1808defbe02c4 Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Mon, 28 Oct 2024 20:21:29 -0500 Subject: [PATCH 9/9] chore: comments from self-review --- x/evm/evmtest/tx.go | 21 +++++++++++++++++++++ x/evm/statedb/journal.go | 13 +++++-------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/x/evm/evmtest/tx.go b/x/evm/evmtest/tx.go index b2bfe3e34..6b876f16d 100644 --- a/x/evm/evmtest/tx.go +++ b/x/evm/evmtest/tx.go @@ -258,6 +258,8 @@ func ValidLegacyTx() *evm.LegacyTx { } } +// GethTxType represents different Ethereum transaction types as defined in +// go-ethereum, such as Legacy, AccessList, and DynamicFee transactions. type GethTxType = uint8 func TxTemplateAccessListTx() *gethcore.AccessListTx { @@ -291,6 +293,25 @@ func TxTemplateDynamicFeeTx() *gethcore.DynamicFeeTx { } } +// NewEthTxMsgFromTxData creates an Ethereum transaction message based on +// the specified txType (Legacy, AccessList, or DynamicFee). This function +// populates transaction fields like nonce, recipient, value, and gas, with +// an optional access list for AccessList and DynamicFee types. The transaction +// is signed using the provided dependencies. +// +// Parameters: +// - deps: Required dependencies including the sender address and signer. +// - txType: Transaction type (Legacy, AccessList, or DynamicFee). +// - innerTxData: Byte slice of transaction data (input). +// - nonce: Transaction nonce. +// - to: Recipient address. +// - value: ETH value (in wei) to transfer. +// - gas: Gas limit for the transaction. +// - accessList: Access list for AccessList and DynamicFee types. +// +// Returns: +// - *evm.MsgEthereumTx: Ethereum transaction message ready for submission. +// - error: Any error encountered during creation or signing. func NewEthTxMsgFromTxData( deps *TestDeps, txType GethTxType, diff --git a/x/evm/statedb/journal.go b/x/evm/statedb/journal.go index dd42a16f1..acbba5eab 100644 --- a/x/evm/statedb/journal.go +++ b/x/evm/statedb/journal.go @@ -344,19 +344,16 @@ type PrecompileCalled struct { var _ JournalChange = PrecompileCalled{} +// Revert rolls back the [StateDB] cache context to the state it was in prior to +// the precompile call. Modifications to this cache context are pushed to the +// commit context (s.evmTxCtx) when [StateDB.Commit] is executed. func (ch PrecompileCalled) Revert(s *StateDB) { - // TEMP: trying something - // If the wasm state is not in the cacheCtx, - // s.CommitCacheCtx() - - // Old Code s.cacheCtx = s.cacheCtx.WithMultiStore(ch.MultiStore) // Rewrite the `writeCacheCtxFn` using the same logic as sdk.Context.CacheCtx s.writeToCommitCtxFromCacheCtx = func() { s.evmTxCtx.EventManager().EmitEvents(ch.Events) - // TODO: UD-DEBUG: Overwriting events might fix an issue with - // appending too many - // Check correctness of the emitted events + // TODO: Check correctness of the emitted events + // https://github.com/NibiruChain/nibiru/issues/2096 ch.MultiStore.Write() } }