From 295a2d92eb63b49ecf30e017f9b1cc4058b6cae2 Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Fri, 25 Oct 2024 03:06:40 -0500 Subject: [PATCH 01/20] 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 02/20] 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 03/20] 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 04/20] 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 efbd52e7f3d5ca12d6f84be969aeafa0f2eb24ea Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Mon, 28 Oct 2024 18:46:01 -0500 Subject: [PATCH 05/20] 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 | 5 ++ x/evm/statedb/statedb_test.go | 36 +++++----- 37 files changed, 124 insertions(+), 224 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..d0f41b8a2 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, 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 a8c100e92a3df893c29a2494c90ca2079a06d4e7 Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Mon, 28 Oct 2024 18:59:09 -0500 Subject: [PATCH 06/20] chore changelog --- CHANGELOG.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac2dba69c..75dd2c2c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,13 +79,15 @@ fixes. 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. - +- [#2095](https://github.com/NibiruChain/nibiru/pull/2095) - fix(evm): This +change records NIBI (ether) transfers on the `StateDB` during precompiled +contract calls using the `NibiruBankKeeper`, which is struct extension of +the `bankkeeper.BaseKeeper` that is used throughout Nibiru. +The `NibiruBankKeeper` holds a reference to the current EVM `StateDB` and records +balance changes in wei as journal changes automatically. This guarantees that +commits and reversions of the `StateDB` do not misalign with the state of the +Bank module. This code change uses the `NibiruBankKeeper` on all modules that +depend on x/bank, such as the EVM and Wasm modules. #### Nibiru EVM | Before Audit 1 - 2024-10-18 From 0ba8c0f318cee81802bc040fd629a06311b828a5 Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Mon, 28 Oct 2024 19:11:48 -0500 Subject: [PATCH 07/20] reset cache --- x/evm/evmtest/tx.go | 58 --------------------------------------------- 1 file changed, 58 deletions(-) diff --git a/x/evm/evmtest/tx.go b/x/evm/evmtest/tx.go index 5bfa99a50..0de08602e 100644 --- a/x/evm/evmtest/tx.go +++ b/x/evm/evmtest/tx.go @@ -24,64 +24,6 @@ import ( "github.com/NibiruChain/nibiru/v2/x/evm/embeds" ) -type GethTxType = uint8 - -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.NewStateDB().GetNonce(deps.Sender.EthAddr) From 08a73ee6c86fbf1449a10f32c7b0eecdfb5caa7f Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Mon, 28 Oct 2024 18:46:01 -0500 Subject: [PATCH 08/20] 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 09/20] 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 10/20] 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 17df4481f16b07a276ca13f442f73222ff5e5458 Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Mon, 28 Oct 2024 19:28:22 -0500 Subject: [PATCH 11/20] fix strange ignored file issue --- x/evm/evmtest/tx.go | 111 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/x/evm/evmtest/tx.go b/x/evm/evmtest/tx.go index 0de08602e..de1c88115 100644 --- a/x/evm/evmtest/tx.go +++ b/x/evm/evmtest/tx.go @@ -8,6 +8,7 @@ import ( "testing" "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -236,3 +237,113 @@ func TransferWei( } return err } + +// -------------------------------------------------- +// Templates +// -------------------------------------------------- + +// 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{}, + } +} + +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 12/20] 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 13/20] 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() } } From 80563285be37df67d35c09447c514846c25336e5 Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Mon, 28 Oct 2024 20:29:22 -0500 Subject: [PATCH 14/20] chore: changelo g --- CHANGELOG.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75dd2c2c5..7aa957dbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,15 +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. +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. - [#2095](https://github.com/NibiruChain/nibiru/pull/2095) - fix(evm): This change records NIBI (ether) transfers on the `StateDB` during precompiled contract calls using the `NibiruBankKeeper`, which is struct extension of From c4ca8a20ef7c402fedc0c444186089a443d4d71d Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Tue, 29 Oct 2024 11:12:22 -0500 Subject: [PATCH 15/20] fix(deps): update bank module to remove concrete type assertion on AppModule.RegisterServices This makes it possible to plug in an alternative bankkeeper.Keeper implementation instead of bankkeeper.BaseKeeper. --- app/keepers.go | 20 +++++++++----------- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/app/keepers.go b/app/keepers.go index 80d4966b7..f02bbe3bf 100644 --- a/app/keepers.go +++ b/app/keepers.go @@ -140,7 +140,6 @@ type AppKeepers struct { } type privateKeepers struct { - bankBaseKeeper bankkeeper.BaseKeeper capabilityKeeper *capabilitykeeper.Keeper slashingKeeper slashingkeeper.Keeper crisisKeeper crisiskeeper.Keeper @@ -264,16 +263,15 @@ func (app *NibiruApp) InitKeepers( govModuleAddr, ) - app.bankBaseKeeper = bankkeeper.NewBaseKeeper( - appCodec, - keys[banktypes.StoreKey], - app.AccountKeeper, - BlockedAddresses(), - govModuleAddr, - ) nibiruBankKeeper := &evmkeeper.NibiruBankKeeper{ - BaseKeeper: app.bankBaseKeeper, - StateDB: nil, + BaseKeeper: bankkeeper.NewBaseKeeper( + appCodec, + keys[banktypes.StoreKey], + app.AccountKeeper, + BlockedAddresses(), + govModuleAddr, + ), + StateDB: nil, } app.BankKeeper = nibiruBankKeeper @@ -614,7 +612,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.bankBaseKeeper, app.AccountKeeper, app.GetSubspace(banktypes.ModuleName)), + bank.NewAppModule(appCodec, app.BankKeeper, 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/go.mod b/go.mod index bc14078f3..212c99e6b 100644 --- a/go.mod +++ b/go.mod @@ -245,7 +245,7 @@ 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/cosmos-sdk => github.com/NibiruChain/cosmos-sdk v0.47.11-nibiru.2 github.com/cosmos/iavl => github.com/cosmos/iavl v0.20.0 diff --git a/go.sum b/go.sum index 213ee9c84..ab3cd42aa 100644 --- a/go.sum +++ b/go.sum @@ -235,8 +235,8 @@ 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/cosmos-sdk v0.47.11-nibiru.2 h1:HtLNrkp0HhgxtbpdNwsasOod4uUNFpuwYpyceFtbj3E= +github.com/NibiruChain/cosmos-sdk v0.47.11-nibiru.2/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= From 70b6e5efc53b2e26c653bb01518f573a033c2a8e Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Wed, 30 Oct 2024 04:13:02 -0500 Subject: [PATCH 16/20] chore: merge conflicts --- app/evmante/evmante_can_transfer.go | 3 +- .../TestNativeSendThenPrecompileSend.json | 4 +- .../TestNativeSendThenPrecompileSend.sol | 21 +-- x/evm/evmtest/erc20.go | 16 +++ x/evm/keeper/bank_extension.go | 25 ++-- x/evm/keeper/erc20.go | 4 +- x/evm/keeper/funtoken_from_coin_test.go | 125 ++++++++++++------ x/evm/keeper/msg_server.go | 1 - x/evm/keeper/statedb.go | 2 +- x/evm/precompile/funtoken.go | 80 +++++++++-- x/evm/statedb/statedb.go | 4 - 11 files changed, 198 insertions(+), 87 deletions(-) diff --git a/app/evmante/evmante_can_transfer.go b/app/evmante/evmante_can_transfer.go index 8af24d2f2..0f8cd0d06 100644 --- a/app/evmante/evmante_can_transfer.go +++ b/app/evmante/evmante_can_transfer.go @@ -72,9 +72,8 @@ func (ctd CanTransferDecorator) AnteHandle( BaseFeeWei: baseFeeWeiPerGas, } - stateDB := statedb.New( + stateDB := ctd.NewStateDB( ctx, - ctd.EVMKeeper, statedb.NewEmptyTxConfig(gethcommon.BytesToHash(ctx.HeaderHash().Bytes())), ) evmInstance := ctd.EVMKeeper.NewEVM(ctx, coreMsg, cfg, evm.NewNoOpTracer(), stateDB) diff --git a/x/evm/embeds/artifacts/contracts/TestNativeSendThenPrecompileSend.sol/TestNativeSendThenPrecompileSend.json b/x/evm/embeds/artifacts/contracts/TestNativeSendThenPrecompileSend.sol/TestNativeSendThenPrecompileSend.json index 5b0046040..e4a2ccfb1 100644 --- a/x/evm/embeds/artifacts/contracts/TestNativeSendThenPrecompileSend.sol/TestNativeSendThenPrecompileSend.json +++ b/x/evm/embeds/artifacts/contracts/TestNativeSendThenPrecompileSend.sol/TestNativeSendThenPrecompileSend.json @@ -43,8 +43,8 @@ "type": "function" } ], - "bytecode": "0x608060405234801561001057600080fd5b50604051610877380380610877833981810160405281019061003291906100db565b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610108565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100a88261007d565b9050919050565b6100b88161009d565b81146100c357600080fd5b50565b6000815190506100d5816100af565b92915050565b6000602082840312156100f1576100f0610078565b5b60006100ff848285016100c6565b91505092915050565b610760806101176000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063a4de557414610030575b600080fd5b61004a6004803603810190610045919061043c565b61004c565b005b60008473ffffffffffffffffffffffffffffffffffffffff166108fc859081150290604051600060405180830381858888f193505050509050806100c5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bc9061051c565b60405180910390fd5b600061080073ffffffffffffffffffffffffffffffffffffffff1660008054906101000a900473ffffffffffffffffffffffffffffffffffffffff168486604051602401610115939291906105da565b6040516020818303038152906040527f03003bc5000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161019f919061065f565b6000604051808303816000865af19150503d80600081146101dc576040519150601f19603f3d011682016040523d82523d6000602084013e6101e1565b606091505b50509050806040516020016101f5906106f3565b60405160208183030381529060405290610245576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161023c9190610708565b60405180910390fd5b50505050505050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061028d82610262565b9050919050565b61029d81610282565b81146102a857600080fd5b50565b6000813590506102ba81610294565b92915050565b6000819050919050565b6102d3816102c0565b81146102de57600080fd5b50565b6000813590506102f0816102ca565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61034982610300565b810181811067ffffffffffffffff8211171561036857610367610311565b5b80604052505050565b600061037b61024e565b90506103878282610340565b919050565b600067ffffffffffffffff8211156103a7576103a6610311565b5b6103b082610300565b9050602081019050919050565b82818337600083830152505050565b60006103df6103da8461038c565b610371565b9050828152602081018484840111156103fb576103fa6102fb565b5b6104068482856103bd565b509392505050565b600082601f830112610423576104226102f6565b5b81356104338482602086016103cc565b91505092915050565b6000806000806080858703121561045657610455610258565b5b6000610464878288016102ab565b9450506020610475878288016102e1565b935050604085013567ffffffffffffffff8111156104965761049561025d565b5b6104a28782880161040e565b92505060606104b3878288016102e1565b91505092959194509250565b600082825260208201905092915050565b7f4661696c656420746f2073656e64206e617469766520746f6b656e0000000000600082015250565b6000610506601b836104bf565b9150610511826104d0565b602082019050919050565b60006020820190508181036000830152610535816104f9565b9050919050565b600061054782610262565b9050919050565b6105578161053c565b82525050565b610566816102c0565b82525050565b600081519050919050565b60005b8381101561059557808201518184015260208101905061057a565b60008484015250505050565b60006105ac8261056c565b6105b681856104bf565b93506105c6818560208601610577565b6105cf81610300565b840191505092915050565b60006060820190506105ef600083018661054e565b6105fc602083018561055d565b818103604083015261060e81846105a1565b9050949350505050565b600081519050919050565b600081905092915050565b600061063982610618565b6106438185610623565b9350610653818560208601610577565b80840191505092915050565b600061066b828461062e565b915081905092915050565b600081905092915050565b7f4661696c656420746f2063616c6c20707265636f6d70696c652062616e6b536560008201527f6e64000000000000000000000000000000000000000000000000000000000000602082015250565b60006106dd602283610676565b91506106e882610681565b602282019050919050565b60006106fe826106d0565b9150819050919050565b6000602082019050818103600083015261072281846105a1565b90509291505056fea2646970667358221220bd148fba67bf9e1966835ecfba5be560625fcf8c88f7890050149168488a782364736f6c63430008180033", - "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063a4de557414610030575b600080fd5b61004a6004803603810190610045919061043c565b61004c565b005b60008473ffffffffffffffffffffffffffffffffffffffff166108fc859081150290604051600060405180830381858888f193505050509050806100c5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bc9061051c565b60405180910390fd5b600061080073ffffffffffffffffffffffffffffffffffffffff1660008054906101000a900473ffffffffffffffffffffffffffffffffffffffff168486604051602401610115939291906105da565b6040516020818303038152906040527f03003bc5000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161019f919061065f565b6000604051808303816000865af19150503d80600081146101dc576040519150601f19603f3d011682016040523d82523d6000602084013e6101e1565b606091505b50509050806040516020016101f5906106f3565b60405160208183030381529060405290610245576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161023c9190610708565b60405180910390fd5b50505050505050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061028d82610262565b9050919050565b61029d81610282565b81146102a857600080fd5b50565b6000813590506102ba81610294565b92915050565b6000819050919050565b6102d3816102c0565b81146102de57600080fd5b50565b6000813590506102f0816102ca565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61034982610300565b810181811067ffffffffffffffff8211171561036857610367610311565b5b80604052505050565b600061037b61024e565b90506103878282610340565b919050565b600067ffffffffffffffff8211156103a7576103a6610311565b5b6103b082610300565b9050602081019050919050565b82818337600083830152505050565b60006103df6103da8461038c565b610371565b9050828152602081018484840111156103fb576103fa6102fb565b5b6104068482856103bd565b509392505050565b600082601f830112610423576104226102f6565b5b81356104338482602086016103cc565b91505092915050565b6000806000806080858703121561045657610455610258565b5b6000610464878288016102ab565b9450506020610475878288016102e1565b935050604085013567ffffffffffffffff8111156104965761049561025d565b5b6104a28782880161040e565b92505060606104b3878288016102e1565b91505092959194509250565b600082825260208201905092915050565b7f4661696c656420746f2073656e64206e617469766520746f6b656e0000000000600082015250565b6000610506601b836104bf565b9150610511826104d0565b602082019050919050565b60006020820190508181036000830152610535816104f9565b9050919050565b600061054782610262565b9050919050565b6105578161053c565b82525050565b610566816102c0565b82525050565b600081519050919050565b60005b8381101561059557808201518184015260208101905061057a565b60008484015250505050565b60006105ac8261056c565b6105b681856104bf565b93506105c6818560208601610577565b6105cf81610300565b840191505092915050565b60006060820190506105ef600083018661054e565b6105fc602083018561055d565b818103604083015261060e81846105a1565b9050949350505050565b600081519050919050565b600081905092915050565b600061063982610618565b6106438185610623565b9350610653818560208601610577565b80840191505092915050565b600061066b828461062e565b915081905092915050565b600081905092915050565b7f4661696c656420746f2063616c6c20707265636f6d70696c652062616e6b536560008201527f6e64000000000000000000000000000000000000000000000000000000000000602082015250565b60006106dd602283610676565b91506106e882610681565b602282019050919050565b60006106fe826106d0565b9150819050919050565b6000602082019050818103600083015261072281846105a1565b90509291505056fea2646970667358221220bd148fba67bf9e1966835ecfba5be560625fcf8c88f7890050149168488a782364736f6c63430008180033", + "bytecode": "0x608060405234801561001057600080fd5b50604051610afc380380610afc833981810160405281019061003291906100db565b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610108565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100a88261007d565b9050919050565b6100b88161009d565b81146100c357600080fd5b50565b6000815190506100d5816100af565b92915050565b6000602082840312156100f1576100f0610078565b5b60006100ff848285016100c6565b91505092915050565b6109e5806101176000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063a4de557414610030575b600080fd5b61004a600480360381019061004591906105f9565b61004c565b005b60008473ffffffffffffffffffffffffffffffffffffffff166108fc859081150290604051600060405180830381858888f193505050509050806100c5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bc906106d9565b60405180910390fd5b600061080073ffffffffffffffffffffffffffffffffffffffff166303003bc560008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1685876040518463ffffffff1660e01b815260040161012693929190610797565b6020604051808303816000875af1158015610145573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061016991906107ea565b9050828114610177826101ea565b610180856101ea565b604051602001610191929190610911565b604051602081830303815290604052906101e1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101d8919061095e565b60405180910390fd5b50505050505050565b6060600060016101f9846102b8565b01905060008167ffffffffffffffff811115610218576102176104ce565b5b6040519080825280601f01601f19166020018201604052801561024a5781602001600182028036833780820191505090505b509050600082602001820190505b6001156102ad578080600190039150507f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a85816102a1576102a0610980565b5b04945060008503610258575b819350505050919050565b600080600090507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008310610316577a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000838161030c5761030b610980565b5b0492506040810190505b6d04ee2d6d415b85acef81000000008310610353576d04ee2d6d415b85acef8100000000838161034957610348610980565b5b0492506020810190505b662386f26fc10000831061038257662386f26fc10000838161037857610377610980565b5b0492506010810190505b6305f5e10083106103ab576305f5e10083816103a1576103a0610980565b5b0492506008810190505b61271083106103d05761271083816103c6576103c5610980565b5b0492506004810190505b606483106103f357606483816103e9576103e8610980565b5b0492506002810190505b600a8310610402576001810190505b80915050919050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061044a8261041f565b9050919050565b61045a8161043f565b811461046557600080fd5b50565b60008135905061047781610451565b92915050565b6000819050919050565b6104908161047d565b811461049b57600080fd5b50565b6000813590506104ad81610487565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b610506826104bd565b810181811067ffffffffffffffff82111715610525576105246104ce565b5b80604052505050565b600061053861040b565b905061054482826104fd565b919050565b600067ffffffffffffffff821115610564576105636104ce565b5b61056d826104bd565b9050602081019050919050565b82818337600083830152505050565b600061059c61059784610549565b61052e565b9050828152602081018484840111156105b8576105b76104b8565b5b6105c384828561057a565b509392505050565b600082601f8301126105e0576105df6104b3565b5b81356105f0848260208601610589565b91505092915050565b6000806000806080858703121561061357610612610415565b5b600061062187828801610468565b94505060206106328782880161049e565b935050604085013567ffffffffffffffff8111156106535761065261041a565b5b61065f878288016105cb565b92505060606106708782880161049e565b91505092959194509250565b600082825260208201905092915050565b7f4661696c656420746f2073656e64206e617469766520746f6b656e0000000000600082015250565b60006106c3601b8361067c565b91506106ce8261068d565b602082019050919050565b600060208201905081810360008301526106f2816106b6565b9050919050565b60006107048261041f565b9050919050565b610714816106f9565b82525050565b6107238161047d565b82525050565b600081519050919050565b60005b83811015610752578082015181840152602081019050610737565b60008484015250505050565b600061076982610729565b610773818561067c565b9350610783818560208601610734565b61078c816104bd565b840191505092915050565b60006060820190506107ac600083018661070b565b6107b9602083018561071a565b81810360408301526107cb818461075e565b9050949350505050565b6000815190506107e481610487565b92915050565b600060208284031215610800576107ff610415565b5b600061080e848285016107d5565b91505092915050565b600081905092915050565b7f4946756e546f6b656e2e62616e6b53656e64207375636365656465642062757460008201527f207472616e73666572726564207468652077726f6e6720616d6f756e74000000602082015250565b600061087e603d83610817565b915061088982610822565b603d82019050919050565b7f73656e74416d6f756e7420000000000000000000000000000000000000000000815250565b60006108c582610729565b6108cf8185610817565b93506108df818560208601610734565b80840191505092915050565b7f6578706563746564200000000000000000000000000000000000000000000000815250565b600061091c82610871565b915061092782610894565b600b8201915061093782856108ba565b9150610942826108eb565b60098201915061095282846108ba565b91508190509392505050565b60006020820190508181036000830152610978818461075e565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fdfea2646970667358221220df7092cb424bd549df3b9d958db66a63c029a1e42d78f468fb93c7574ccea23864736f6c63430008180033", + "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063a4de557414610030575b600080fd5b61004a600480360381019061004591906105f9565b61004c565b005b60008473ffffffffffffffffffffffffffffffffffffffff166108fc859081150290604051600060405180830381858888f193505050509050806100c5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bc906106d9565b60405180910390fd5b600061080073ffffffffffffffffffffffffffffffffffffffff166303003bc560008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1685876040518463ffffffff1660e01b815260040161012693929190610797565b6020604051808303816000875af1158015610145573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061016991906107ea565b9050828114610177826101ea565b610180856101ea565b604051602001610191929190610911565b604051602081830303815290604052906101e1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101d8919061095e565b60405180910390fd5b50505050505050565b6060600060016101f9846102b8565b01905060008167ffffffffffffffff811115610218576102176104ce565b5b6040519080825280601f01601f19166020018201604052801561024a5781602001600182028036833780820191505090505b509050600082602001820190505b6001156102ad578080600190039150507f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a85816102a1576102a0610980565b5b04945060008503610258575b819350505050919050565b600080600090507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008310610316577a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000838161030c5761030b610980565b5b0492506040810190505b6d04ee2d6d415b85acef81000000008310610353576d04ee2d6d415b85acef8100000000838161034957610348610980565b5b0492506020810190505b662386f26fc10000831061038257662386f26fc10000838161037857610377610980565b5b0492506010810190505b6305f5e10083106103ab576305f5e10083816103a1576103a0610980565b5b0492506008810190505b61271083106103d05761271083816103c6576103c5610980565b5b0492506004810190505b606483106103f357606483816103e9576103e8610980565b5b0492506002810190505b600a8310610402576001810190505b80915050919050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061044a8261041f565b9050919050565b61045a8161043f565b811461046557600080fd5b50565b60008135905061047781610451565b92915050565b6000819050919050565b6104908161047d565b811461049b57600080fd5b50565b6000813590506104ad81610487565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b610506826104bd565b810181811067ffffffffffffffff82111715610525576105246104ce565b5b80604052505050565b600061053861040b565b905061054482826104fd565b919050565b600067ffffffffffffffff821115610564576105636104ce565b5b61056d826104bd565b9050602081019050919050565b82818337600083830152505050565b600061059c61059784610549565b61052e565b9050828152602081018484840111156105b8576105b76104b8565b5b6105c384828561057a565b509392505050565b600082601f8301126105e0576105df6104b3565b5b81356105f0848260208601610589565b91505092915050565b6000806000806080858703121561061357610612610415565b5b600061062187828801610468565b94505060206106328782880161049e565b935050604085013567ffffffffffffffff8111156106535761065261041a565b5b61065f878288016105cb565b92505060606106708782880161049e565b91505092959194509250565b600082825260208201905092915050565b7f4661696c656420746f2073656e64206e617469766520746f6b656e0000000000600082015250565b60006106c3601b8361067c565b91506106ce8261068d565b602082019050919050565b600060208201905081810360008301526106f2816106b6565b9050919050565b60006107048261041f565b9050919050565b610714816106f9565b82525050565b6107238161047d565b82525050565b600081519050919050565b60005b83811015610752578082015181840152602081019050610737565b60008484015250505050565b600061076982610729565b610773818561067c565b9350610783818560208601610734565b61078c816104bd565b840191505092915050565b60006060820190506107ac600083018661070b565b6107b9602083018561071a565b81810360408301526107cb818461075e565b9050949350505050565b6000815190506107e481610487565b92915050565b600060208284031215610800576107ff610415565b5b600061080e848285016107d5565b91505092915050565b600081905092915050565b7f4946756e546f6b656e2e62616e6b53656e64207375636365656465642062757460008201527f207472616e73666572726564207468652077726f6e6720616d6f756e74000000602082015250565b600061087e603d83610817565b915061088982610822565b603d82019050919050565b7f73656e74416d6f756e7420000000000000000000000000000000000000000000815250565b60006108c582610729565b6108cf8185610817565b93506108df818560208601610734565b80840191505092915050565b7f6578706563746564200000000000000000000000000000000000000000000000815250565b600061091c82610871565b915061092782610894565b600b8201915061093782856108ba565b9150610942826108eb565b60098201915061095282846108ba565b91508190509392505050565b60006020820190508181036000830152610978818461075e565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fdfea2646970667358221220df7092cb424bd549df3b9d958db66a63c029a1e42d78f468fb93c7574ccea23864736f6c63430008180033", "linkReferences": {}, "deployedLinkReferences": {} } diff --git a/x/evm/embeds/contracts/TestNativeSendThenPrecompileSend.sol b/x/evm/embeds/contracts/TestNativeSendThenPrecompileSend.sol index da9cb471b..45ec757b9 100644 --- a/x/evm/embeds/contracts/TestNativeSendThenPrecompileSend.sol +++ b/x/evm/embeds/contracts/TestNativeSendThenPrecompileSend.sol @@ -20,15 +20,20 @@ contract TestNativeSendThenPrecompileSend { bool isSent = nativeRecipient.send(nativeAmount); require(isSent, "Failed to send native token"); - (bool success, ) = FUNTOKEN_PRECOMPILE_ADDRESS.call( - abi.encodeWithSignature( - "bankSend(address,uint256,string)", - erc20, - precompileAmount, - precompileRecipient + uint256 sentAmount = FUNTOKEN_PRECOMPILE.bankSend( + erc20, + precompileAmount, + precompileRecipient + ); + require( + sentAmount == precompileAmount, + string.concat( + "IFunToken.bankSend succeeded but transferred the wrong amount", + "sentAmount ", + Strings.toString(sentAmount), + "expected ", + Strings.toString(precompileAmount) ) ); - - require(success, string.concat("Failed to call precompile bankSend")); } } diff --git a/x/evm/evmtest/erc20.go b/x/evm/evmtest/erc20.go index d8798f71d..176861192 100644 --- a/x/evm/evmtest/erc20.go +++ b/x/evm/evmtest/erc20.go @@ -105,3 +105,19 @@ func BigPow(amount *big.Int, pow10Exp uint8) (powAmount *big.Int) { pow10 := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(pow10Exp)), nil) return new(big.Int).Mul(amount, pow10) } + +type FunTokenBalanceAssert struct { + FunToken evm.FunToken + Account gethcommon.Address + BalanceBank *big.Int + BalanceERC20 *big.Int +} + +func (bals FunTokenBalanceAssert) Assert(t *testing.T, deps TestDeps) { + AssertERC20BalanceEqual( + t, deps, bals.FunToken.Erc20Addr.Address, bals.Account, bals.BalanceERC20, + ) + AssertBankBalanceEqual( + t, deps, bals.FunToken.BankDenom, bals.Account, bals.BalanceBank, + ) +} diff --git a/x/evm/keeper/bank_extension.go b/x/evm/keeper/bank_extension.go index ed9ae45ba..79bafa8b3 100644 --- a/x/evm/keeper/bank_extension.go +++ b/x/evm/keeper/bank_extension.go @@ -17,14 +17,24 @@ var ( type NibiruBankKeeper struct { bankkeeper.BaseKeeper - StateDB *statedb.StateDB + StateDB *statedb.StateDB + TxStateDB *statedb.StateDB } func (evmKeeper *Keeper) NewStateDB( ctx sdk.Context, txConfig statedb.TxConfig, ) *statedb.StateDB { stateDB := statedb.New(ctx, evmKeeper, txConfig) - evmKeeper.Bank.ResetStateDB(stateDB) + evmKeeper.Bank.StateDB = stateDB + return stateDB +} + +func (evmKeeper *Keeper) NewTxStateDB( + ctx sdk.Context, txConfig statedb.TxConfig, +) *statedb.StateDB { + stateDB := statedb.New(ctx, evmKeeper, txConfig) + evmKeeper.Bank.StateDB = stateDB + evmKeeper.Bank.TxStateDB = stateDB return stateDB } @@ -44,17 +54,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, diff --git a/x/evm/keeper/erc20.go b/x/evm/keeper/erc20.go index 06d2e5ab3..79c189f1e 100644 --- a/x/evm/keeper/erc20.go +++ b/x/evm/keeper/erc20.go @@ -180,7 +180,9 @@ func (k Keeper) CallContract( if err != nil { return nil, fmt.Errorf("failed to pack ABI args: %w", err) } - evmResp, _, err = k.CallContractWithInput(ctx, fromAcc, contract, commit, contractInput) + evmResp, _, err = k.CallContractWithInput( + ctx, fromAcc, contract, commit, contractInput, + ) return evmResp, err } diff --git a/x/evm/keeper/funtoken_from_coin_test.go b/x/evm/keeper/funtoken_from_coin_test.go index b252affe0..f69143772 100644 --- a/x/evm/keeper/funtoken_from_coin_test.go +++ b/x/evm/keeper/funtoken_from_coin_test.go @@ -172,7 +172,7 @@ func (s *FunTokenFromCoinSuite) TestConvertCoinToEvmAndBack() { bankDenom := evm.EVMBankDenom // Initial setup - funTokenErc20Addr := s.fundAndCreateFunToken(deps, 100) + funToken := s.fundAndCreateFunToken(deps, 100) s.T().Log("Convert bank coin to erc-20") _, err := deps.EvmKeeper.ConvertCoinToEvm( @@ -193,7 +193,7 @@ func (s *FunTokenFromCoinSuite) TestConvertCoinToEvmAndBack() { deps.Ctx, &evm.EventConvertCoinToEvm{ Sender: deps.Sender.NibiruAddr.String(), - Erc20ContractAddress: funTokenErc20Addr.String(), + Erc20ContractAddress: funToken.Erc20Addr.String(), ToEthAddr: alice.EthAddr.String(), BankCoin: sdk.NewCoin(bankDenom, sdk.NewInt(10)), }, @@ -208,7 +208,7 @@ func (s *FunTokenFromCoinSuite) TestConvertCoinToEvmAndBack() { s.Require().Equal(sdk.NewInt(90), senderBalance.Amount) // Check 3: erc-20 balance - balance, err := deps.EvmKeeper.ERC20().BalanceOf(funTokenErc20Addr.Address, alice.EthAddr, deps.Ctx) + balance, err := deps.EvmKeeper.ERC20().BalanceOf(funToken.Erc20Addr.Address, alice.EthAddr, deps.Ctx) s.Require().NoError(err) s.Require().Zero(balance.Cmp(big.NewInt(10))) @@ -233,7 +233,7 @@ func (s *FunTokenFromCoinSuite) TestConvertCoinToEvmAndBack() { &precompile.PrecompileAddr_FunToken, true, "bankSend", - funTokenErc20Addr.Address, + funToken.Erc20Addr.Address, big.NewInt(10), deps.Sender.NibiruAddr.String(), ) @@ -248,7 +248,7 @@ func (s *FunTokenFromCoinSuite) TestConvertCoinToEvmAndBack() { s.Require().Equal(sdk.NewInt(100), senderBalance.Amount) // Check 3: erc-20 balance - balance, err = deps.EvmKeeper.ERC20().BalanceOf(funTokenErc20Addr.Address, alice.EthAddr, deps.Ctx) + balance, err = deps.EvmKeeper.ERC20().BalanceOf(funToken.Erc20Addr.Address, alice.EthAddr, deps.Ctx) s.Require().NoError(err) s.Require().Equal("0", balance.String()) @@ -260,7 +260,7 @@ func (s *FunTokenFromCoinSuite) TestConvertCoinToEvmAndBack() { &precompile.PrecompileAddr_FunToken, true, "bankSend", - funTokenErc20Addr.Address, + funToken.Erc20Addr.Address, big.NewInt(10), deps.Sender.NibiruAddr.String(), ) @@ -286,13 +286,14 @@ func (s *FunTokenFromCoinSuite) TestNativeSendThenPrecompileSend() { bankDenom := evm.EVMBankDenom // Initial setup - funTokenErc20Addr := s.fundAndCreateFunToken(deps, 10e6) + sendAmt := big.NewInt(10) + funtoken := s.fundAndCreateFunToken(deps, sendAmt.Int64()) s.T().Log("Deploy Test Contract") deployResp, err := evmtest.DeployContract( &deps, embeds.SmartContract_TestNativeSendThenPrecompileSendJson, - funTokenErc20Addr.Address, + funtoken.Erc20Addr.Address, ) s.Require().NoError(err) @@ -304,7 +305,13 @@ func (s *FunTokenFromCoinSuite) TestNativeSendThenPrecompileSend() { deps.App.BankKeeper, deps.Ctx, testContractNibiAddr, - sdk.NewCoins(sdk.NewCoin(bankDenom, sdk.NewInt(10e6)))), + sdk.NewCoins(sdk.NewCoin(bankDenom, sdk.NewIntFromBigInt(sendAmt)))), + ) + evmtest.AssertBankBalanceEqual( + s.T(), deps, bankDenom, testContractAddr, sendAmt, + ) + evmtest.AssertBankBalanceEqual( + s.T(), deps, bankDenom, evm.EVM_MODULE_ADDRESS, big.NewInt(0), ) s.T().Log("Convert bank coin to erc-20: give test contract 10 WNIBI (erc20)") @@ -312,18 +319,36 @@ func (s *FunTokenFromCoinSuite) TestNativeSendThenPrecompileSend() { sdk.WrapSDKContext(deps.Ctx), &evm.MsgConvertCoinToEvm{ Sender: deps.Sender.NibiruAddr.String(), - BankCoin: sdk.NewCoin(bankDenom, sdk.NewInt(10e6)), + BankCoin: sdk.NewCoin(bankDenom, sdk.NewIntFromBigInt(sendAmt)), ToEthAddr: eth.EIP55Addr{Address: testContractAddr}, }, ) s.Require().NoError(err) + evmtest.FunTokenBalanceAssert{ + FunToken: funtoken, + Account: testContractAddr, + BalanceBank: sendAmt, + BalanceERC20: sendAmt, + }.Assert(s.T(), deps) + evmtest.FunTokenBalanceAssert{ + FunToken: funtoken, + Account: evm.EVM_MODULE_ADDRESS, + BalanceBank: sendAmt, + BalanceERC20: big.NewInt(0), + }.Assert(s.T(), deps) // Alice hex and Alice bech32 is the same address in different representation, // so funds are expected to be available in Alice's bank wallet alice := evmtest.NewEthPrivAcc() + evmtest.FunTokenBalanceAssert{ + FunToken: funtoken, + Account: alice.EthAddr, + BalanceBank: big.NewInt(0), + BalanceERC20: big.NewInt(0), + }.Assert(s.T(), deps) s.T().Log("call test contract") - _, err = deps.EvmKeeper.CallContract( + evmResp, err := deps.EvmKeeper.CallContract( deps.Ctx, embeds.SmartContract_TestNativeSendThenPrecompileSendJson.ABI, deps.Sender.EthAddr, @@ -332,34 +357,34 @@ func (s *FunTokenFromCoinSuite) TestNativeSendThenPrecompileSend() { "nativeSendThenPrecompileSend", []any{ alice.EthAddr, - evm.NativeToWei(big.NewInt(10e6)), // for native evm send: 18 decimals + evm.NativeToWei(sendAmt), // native send uses wei units alice.NibiruAddr.String(), - big.NewInt(10e6), // for precompile bankSend: 6 decimals + sendAmt, // amount for precompile bankSend }..., ) s.Require().NoError(err) - - // Check 1: Alice has 20 NIBI in bank - aliceBankBalance := deps.App.BankKeeper.GetBalance(deps.Ctx, alice.NibiruAddr, bankDenom) - s.Require().Equal(sdk.NewInt(20e6), aliceBankBalance.Amount) - - // Check 2: Alice has 0 WNIBI on ERC20 - aliceERC20Balance, err := deps.EvmKeeper.ERC20().BalanceOf(funTokenErc20Addr.Address, alice.EthAddr, deps.Ctx) - s.Require().NoError(err) - s.Require().Zero(big.NewInt(0).Cmp(aliceERC20Balance)) - - // Check 3: test contract has 0 NIBI in bank - testContractBankBalance := deps.App.BankKeeper.GetBalance(deps.Ctx, testContractNibiAddr, bankDenom) - s.Require().Equal(sdk.NewInt(0), testContractBankBalance.Amount) - - // Check 4: test contract has 0 WNIBI on ERC20 - testContractERC20Balance, err := deps.EvmKeeper.ERC20().BalanceOf(funTokenErc20Addr.Address, testContractAddr, deps.Ctx) - s.Require().NoError(err) - s.Require().Zero(big.NewInt(0).Cmp(testContractERC20Balance)) - - // Check 5: module balance has 0 NIBI escrowed - moduleBalance := deps.App.BankKeeper.GetBalance(deps.Ctx, authtypes.NewModuleAddress(evm.ModuleName), bankDenom) - s.Require().Equal(sdk.NewInt(0), moduleBalance.Amount) + s.Empty(evmResp.VmError) + + evmtest.FunTokenBalanceAssert{ + FunToken: funtoken, + Account: alice.EthAddr, + BalanceBank: new(big.Int).Mul(sendAmt, big.NewInt(2)), + BalanceERC20: big.NewInt(0), + }.Assert(s.T(), deps) + + evmtest.FunTokenBalanceAssert{ + FunToken: funtoken, + Account: testContractAddr, + BalanceBank: big.NewInt(0), + BalanceERC20: big.NewInt(0), + }.Assert(s.T(), deps) + + evmtest.FunTokenBalanceAssert{ + FunToken: funtoken, + Account: evm.EVM_MODULE_ADDRESS, + BalanceBank: big.NewInt(0), + BalanceERC20: big.NewInt(0), + }.Assert(s.T(), deps) } // TestERC20TransferThenPrecompileSend @@ -381,13 +406,13 @@ func (s *FunTokenFromCoinSuite) TestERC20TransferThenPrecompileSend() { bankDenom := evm.EVMBankDenom // Initial setup - funTokenErc20Addr := s.fundAndCreateFunToken(deps, 10e6) + funToken := s.fundAndCreateFunToken(deps, 10e6) s.T().Log("Deploy Test Contract") deployResp, err := evmtest.DeployContract( &deps, embeds.SmartContract_TestERC20TransferThenPrecompileSend, - funTokenErc20Addr.Address, + funToken.Erc20Addr.Address, ) s.Require().NoError(err) @@ -427,12 +452,12 @@ func (s *FunTokenFromCoinSuite) TestERC20TransferThenPrecompileSend() { s.Require().Equal(sdk.NewInt(9e6), aliceBankBalance.Amount) // Check 2: Alice has 1 WNIBI on ERC20 - aliceERC20Balance, err := deps.EvmKeeper.ERC20().BalanceOf(funTokenErc20Addr.Address, alice.EthAddr, deps.Ctx) + aliceERC20Balance, err := deps.EvmKeeper.ERC20().BalanceOf(funToken.Erc20Addr.Address, alice.EthAddr, deps.Ctx) s.Require().NoError(err) s.Require().Zero(big.NewInt(1e6).Cmp(aliceERC20Balance)) // Check 3: test contract has 0 WNIBI on ERC20 - testContractERC20Balance, err := deps.EvmKeeper.ERC20().BalanceOf(funTokenErc20Addr.Address, testContractAddr, deps.Ctx) + testContractERC20Balance, err := deps.EvmKeeper.ERC20().BalanceOf(funToken.Erc20Addr.Address, testContractAddr, deps.Ctx) s.Require().NoError(err) s.Require().Zero(big.NewInt(0).Cmp(testContractERC20Balance)) @@ -442,7 +467,7 @@ func (s *FunTokenFromCoinSuite) TestERC20TransferThenPrecompileSend() { } // fundAndCreateFunToken creates initial setup for tests -func (s *FunTokenFromCoinSuite) fundAndCreateFunToken(deps evmtest.TestDeps, unibiAmount int64) eth.EIP55Addr { +func (s *FunTokenFromCoinSuite) fundAndCreateFunToken(deps evmtest.TestDeps, unibiAmount int64) evm.FunToken { bankDenom := evm.EVMBankDenom s.T().Log("Setup: Create a coin in the bank state") @@ -451,12 +476,15 @@ func (s *FunTokenFromCoinSuite) fundAndCreateFunToken(deps evmtest.TestDeps, uni { Denom: bankDenom, Exponent: 0, - Aliases: nil, + }, + { + Denom: "NIBI", + Exponent: 6, }, }, Base: bankDenom, - Display: bankDenom, - Name: bankDenom, + Display: "NIBI", + Name: "NIBI", Symbol: "NIBI", }) @@ -477,7 +505,16 @@ func (s *FunTokenFromCoinSuite) fundAndCreateFunToken(deps evmtest.TestDeps, uni }, ) s.Require().NoError(err) - return createFunTokenResp.FuntokenMapping.Erc20Addr + + erc20Decimals, err := deps.EvmKeeper.LoadERC20Decimals( + deps.Ctx, + embeds.SmartContract_ERC20Minter.ABI, + createFunTokenResp.FuntokenMapping.Erc20Addr.Address, + ) + s.Require().NoError(err) + s.Require().Equal(erc20Decimals, uint8(6)) + + return createFunTokenResp.FuntokenMapping } type FunTokenFromCoinSuite struct { diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go index 60660acd1..57f3cc54a 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) } diff --git a/x/evm/keeper/statedb.go b/x/evm/keeper/statedb.go index 209539305..b85a595a5 100644 --- a/x/evm/keeper/statedb.go +++ b/x/evm/keeper/statedb.go @@ -73,7 +73,7 @@ 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() + balance := k.Bank.GetBalance(ctx, addrBech32, evm.EVMBankDenom).Amount.BigInt() delta := new(big.Int).Sub(amountEvmDenom, balance) bk := k.Bank.BaseKeeper diff --git a/x/evm/precompile/funtoken.go b/x/evm/precompile/funtoken.go index 8fb126f96..92b6701ac 100644 --- a/x/evm/precompile/funtoken.go +++ b/x/evm/precompile/funtoken.go @@ -3,18 +3,20 @@ package precompile import ( "fmt" "math/big" - "sync" "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" + auth "github.com/cosmos/cosmos-sdk/x/auth/types" 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) @@ -54,6 +56,7 @@ func (p precompileFunToken) Run( if err != nil { return nil, err } + p.evmKeeper.Bank.StateDB = start.StateDB method := start.Method switch PrecompileMethod(method.Name) { @@ -73,18 +76,14 @@ func (p precompileFunToken) Run( func PrecompileFunToken(keepers keepers.PublicKeepers) vm.PrecompiledContract { return precompileFunToken{ - bankKeeper: keepers.EvmKeeper.Bank, - evmKeeper: keepers.EvmKeeper, + evmKeeper: keepers.EvmKeeper, } } type precompileFunToken struct { - bankKeeper *evmkeeper.NibiruBankKeeper - evmKeeper *evmkeeper.Keeper + evmKeeper *evmkeeper.Keeper } -var executionGuard sync.Mutex - // bankSend: Implements "IFunToken.bankSend" // // The "args" populate the following function signature in Solidity: @@ -106,10 +105,6 @@ func (p precompileFunToken) bankSend( err = e return } - if !executionGuard.TryLock() { - return nil, fmt.Errorf("bankSend is already in progress") - } - defer executionGuard.Unlock() erc20, amount, to, err := p.decomposeBankSendArgs(args) if err != nil { @@ -157,7 +152,9 @@ func (p precompileFunToken) bankSend( return } } else { + p.evmKeeper.Bank.StateDB = start.StateDB err = p.evmKeeper.Bank.MintCoins(ctx, evm.ModuleName, sdk.NewCoins(coinToSend)) + // err = SafeMintCoins(ctx, evm.ModuleName, coinToSend, p.evmKeeper, 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, @@ -166,12 +163,21 @@ func (p precompileFunToken) bankSend( } // Transfer the bank coin + p.evmKeeper.Bank.StateDB = start.StateDB err = p.evmKeeper.Bank.SendCoinsFromModuleToAccount( ctx, evm.ModuleName, toAddr, sdk.NewCoins(coinToSend), ) + // err = SafeSendCoinFromModuleToAccount( + // ctx, + // evm.ModuleName, + // toAddr, + // coinToSend, + // p.evmKeeper, + // start.StateDB, + // ) if err != nil { return nil, fmt.Errorf("send failed for module \"%s\" (%s): contract caller %s: %w", evm.ModuleName, evm.EVM_MODULE_ADDRESS.Hex(), caller.Hex(), err, @@ -214,3 +220,55 @@ func (p precompileFunToken) decomposeBankSendArgs(args []any) ( return } +func SafeMintCoins( + ctx sdk.Context, + moduleName string, + amt sdk.Coin, + ek *evmkeeper.Keeper, + db *statedb.StateDB, +) error { + bk := ek.Bank + 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, + ek *evmkeeper.Keeper, + db *statedb.StateDB, +) error { + bk := ek.Bank + 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 +} diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go index a4594770f..a5059c039 100644 --- a/x/evm/statedb/statedb.go +++ b/x/evm/statedb/statedb.go @@ -84,7 +84,6 @@ func FromVM(evmObj *vm.EVM) *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,9 +484,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 From 53e8c59da4a5e2734ffb0b927aeb7f1482996f2e Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Wed, 30 Oct 2024 22:50:44 -0500 Subject: [PATCH 17/20] revert experiment --- x/evm/keeper/bank_extension.go | 12 +----- x/evm/keeper/msg_server.go | 7 ++-- x/evm/precompile/funtoken.go | 73 +++++----------------------------- x/evm/precompile/wasm.go | 8 ++++ 4 files changed, 22 insertions(+), 78 deletions(-) diff --git a/x/evm/keeper/bank_extension.go b/x/evm/keeper/bank_extension.go index 79bafa8b3..9272ff590 100644 --- a/x/evm/keeper/bank_extension.go +++ b/x/evm/keeper/bank_extension.go @@ -17,8 +17,7 @@ var ( type NibiruBankKeeper struct { bankkeeper.BaseKeeper - StateDB *statedb.StateDB - TxStateDB *statedb.StateDB + StateDB *statedb.StateDB } func (evmKeeper *Keeper) NewStateDB( @@ -29,15 +28,6 @@ func (evmKeeper *Keeper) NewStateDB( return stateDB } -func (evmKeeper *Keeper) NewTxStateDB( - ctx sdk.Context, txConfig statedb.TxConfig, -) *statedb.StateDB { - stateDB := statedb.New(ctx, evmKeeper, txConfig) - evmKeeper.Bank.StateDB = stateDB - evmKeeper.Bank.TxStateDB = stateDB - return stateDB -} - func (bk NibiruBankKeeper) MintCoins( ctx sdk.Context, moduleName string, diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go index 57f3cc54a..dbc1e8f74 100644 --- a/x/evm/keeper/msg_server.go +++ b/x/evm/keeper/msg_server.go @@ -202,9 +202,10 @@ func (k Keeper) GetHashFn(ctx sdk.Context) vm.GetHashFunc { } } -// ApplyEvmMsg computes the new state by applying the given message against the existing state. -// If the message fails, the VM execution error with the reason will be returned to the client -// and the transaction won't be committed to the store. +// ApplyEvmMsg computes the new state by applying the given message against the +// existing state. If the message fails, the VM execution error with the reason +// will be returned to the client and the transaction won't be committed to the +// store. // // # Reverted state // diff --git a/x/evm/precompile/funtoken.go b/x/evm/precompile/funtoken.go index 92b6701ac..ced70e9ad 100644 --- a/x/evm/precompile/funtoken.go +++ b/x/evm/precompile/funtoken.go @@ -6,17 +6,14 @@ import ( "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" - auth "github.com/cosmos/cosmos-sdk/x/auth/types" 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) @@ -152,9 +149,12 @@ func (p precompileFunToken) bankSend( return } } else { + // NOTE: The NibiruBankKeeper needs to reference the current [vm.StateDB] before + // any operation that has the potential to use Bank send methods. This will + // guarantee that [evmkeeper.Keeper.SetAccBalance] journal changes are + // recorded if wei (NIBI) is transferred. p.evmKeeper.Bank.StateDB = start.StateDB err = p.evmKeeper.Bank.MintCoins(ctx, evm.ModuleName, sdk.NewCoins(coinToSend)) - // err = SafeMintCoins(ctx, evm.ModuleName, coinToSend, p.evmKeeper, 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, @@ -163,6 +163,11 @@ func (p precompileFunToken) bankSend( } // Transfer the bank coin + // + // NOTE: The NibiruBankKeeper needs to reference the current [vm.StateDB] before + // any operation that has the potential to use Bank send methods. This will + // guarantee that [evmkeeper.Keeper.SetAccBalance] journal changes are + // recorded if wei (NIBI) is transferred. p.evmKeeper.Bank.StateDB = start.StateDB err = p.evmKeeper.Bank.SendCoinsFromModuleToAccount( ctx, @@ -170,14 +175,6 @@ func (p precompileFunToken) bankSend( toAddr, sdk.NewCoins(coinToSend), ) - // err = SafeSendCoinFromModuleToAccount( - // ctx, - // evm.ModuleName, - // toAddr, - // coinToSend, - // p.evmKeeper, - // start.StateDB, - // ) if err != nil { return nil, fmt.Errorf("send failed for module \"%s\" (%s): contract caller %s: %w", evm.ModuleName, evm.EVM_MODULE_ADDRESS.Hex(), caller.Hex(), err, @@ -220,55 +217,3 @@ func (p precompileFunToken) decomposeBankSendArgs(args []any) ( return } -func SafeMintCoins( - ctx sdk.Context, - moduleName string, - amt sdk.Coin, - ek *evmkeeper.Keeper, - db *statedb.StateDB, -) error { - bk := ek.Bank - 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, - ek *evmkeeper.Keeper, - db *statedb.StateDB, -) error { - bk := ek.Bank - 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 -} diff --git a/x/evm/precompile/wasm.go b/x/evm/precompile/wasm.go index a7b21684c..7f18fb244 100644 --- a/x/evm/precompile/wasm.go +++ b/x/evm/precompile/wasm.go @@ -8,6 +8,7 @@ import ( "github.com/NibiruChain/nibiru/v2/app/keepers" "github.com/NibiruChain/nibiru/v2/eth" "github.com/NibiruChain/nibiru/v2/x/evm/embeds" + evmkeeper "github.com/NibiruChain/nibiru/v2/x/evm/keeper" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" gethabi "github.com/ethereum/go-ethereum/accounts/abi" @@ -42,6 +43,11 @@ func (p precompileWasm) Run( } method := start.Method + // The NibiruBankKeeper needs to reference the current [vm.StateDB] before + // any operation that has the potential to use Bank send methods. This will + // guarantee that [evmkeeper.Keeper.SetAccBalance] journal changes are + // recorded if wei (NIBI) is transferred. + p.Bank.StateDB = start.StateDB switch PrecompileMethod(method.Name) { case WasmMethod_execute: bz, err = p.execute(start, contract.CallerAddress, readonly) @@ -66,6 +72,7 @@ func (p precompileWasm) Run( } type precompileWasm struct { + *evmkeeper.Keeper Wasm Wasm } @@ -91,6 +98,7 @@ type Wasm struct { func PrecompileWasm(keepers keepers.PublicKeepers) vm.PrecompiledContract { return precompileWasm{ + Keeper: keepers.EvmKeeper, Wasm: Wasm{ wasmkeeper.NewDefaultPermissionKeeper(keepers.WasmKeeper), keepers.WasmKeeper, From a04fd06f4ffedf21c6a1ab5e9effb4b3b47ce4a6 Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Thu, 31 Oct 2024 03:29:46 -0500 Subject: [PATCH 18/20] finishing touch --- x/evm/evmtest/eth_test.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/x/evm/evmtest/eth_test.go b/x/evm/evmtest/eth_test.go index 612beb05d..1d5feee47 100644 --- a/x/evm/evmtest/eth_test.go +++ b/x/evm/evmtest/eth_test.go @@ -36,11 +36,10 @@ func (s *Suite) TestSampleFns() { func (s *Suite) TestERC20Helpers() { deps := evmtest.NewTestDeps() funtoken := evmtest.CreateFunTokenForBankCoin(&deps, "token", &s.Suite) - - evmtest.AssertERC20BalanceEqual( - s.T(), deps, - funtoken.Erc20Addr.Address, - deps.Sender.EthAddr, - big.NewInt(0), - ) + evmtest.FunTokenBalanceAssert{ + FunToken: funtoken, + Account: deps.Sender.EthAddr, + BalanceBank: big.NewInt(0), + BalanceERC20: big.NewInt(0), + }.Assert(s.T(), deps) } From 9aa3a6406517c3e479904ba4ce0302c387fd2fc3 Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Thu, 31 Oct 2024 13:23:41 -0500 Subject: [PATCH 19/20] fix incorrect arugmetn --- x/evm/keeper/bank_extension.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/evm/keeper/bank_extension.go b/x/evm/keeper/bank_extension.go index 9272ff590..da5221cbb 100644 --- a/x/evm/keeper/bank_extension.go +++ b/x/evm/keeper/bank_extension.go @@ -38,7 +38,7 @@ func (bk NibiruBankKeeper) MintCoins( return err } if findEtherBalanceChangeFromCoins(coins) { - moduleBech32Addr := auth.NewModuleAddress(evm.ModuleName) + moduleBech32Addr := auth.NewModuleAddress(moduleName) bk.SyncStateDBWithAccount(ctx, moduleBech32Addr) } return nil @@ -54,7 +54,7 @@ func (bk NibiruBankKeeper) BurnCoins( return err } if findEtherBalanceChangeFromCoins(coins) { - moduleBech32Addr := auth.NewModuleAddress(evm.ModuleName) + moduleBech32Addr := auth.NewModuleAddress(moduleName) bk.SyncStateDBWithAccount(ctx, moduleBech32Addr) } return nil From 18892d2eaa2b9ffd8633b5b62da9c8027f0d9029 Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Thu, 31 Oct 2024 13:52:10 -0500 Subject: [PATCH 20/20] feat(evmtest-erc20: Add optional descriptions to the assertions --- x/evm/evmtest/erc20.go | 58 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/x/evm/evmtest/erc20.go b/x/evm/evmtest/erc20.go index 176861192..3d7dd4d76 100644 --- a/x/evm/evmtest/erc20.go +++ b/x/evm/evmtest/erc20.go @@ -21,9 +21,7 @@ func AssertERC20BalanceEqual( erc20, account gethcommon.Address, expectedBalance *big.Int, ) { - actualBalance, err := deps.EvmKeeper.ERC20().BalanceOf(erc20, account, deps.Ctx) - assert.NoError(t, err) - assert.Equal(t, expectedBalance.String(), actualBalance.String(), "expected %s, got %s", expectedBalance, actualBalance) + AssertERC20BalanceEqualWithDescription(t, deps, erc20, account, expectedBalance, "") } // CreateFunTokenForBankCoin: Uses the "TestDeps.Sender" account to create a @@ -95,9 +93,9 @@ func AssertBankBalanceEqual( account gethcommon.Address, expectedBalance *big.Int, ) { - bech32Addr := eth.EthAddrToNibiruAddr(account) - actualBalance := deps.App.BankKeeper.GetBalance(deps.Ctx, bech32Addr, denom).Amount.BigInt() - assert.Zero(t, expectedBalance.Cmp(actualBalance), "expected %s, got %s", expectedBalance, actualBalance) + AssertBankBalanceEqualWithDescription( + t, deps, denom, account, expectedBalance, "", + ) } // BigPow multiplies "amount" by 10 to the "pow10Exp". @@ -111,13 +109,57 @@ type FunTokenBalanceAssert struct { Account gethcommon.Address BalanceBank *big.Int BalanceERC20 *big.Int + Description string } func (bals FunTokenBalanceAssert) Assert(t *testing.T, deps TestDeps) { - AssertERC20BalanceEqual( + AssertERC20BalanceEqualWithDescription( t, deps, bals.FunToken.Erc20Addr.Address, bals.Account, bals.BalanceERC20, + bals.Description, ) - AssertBankBalanceEqual( + AssertBankBalanceEqualWithDescription( t, deps, bals.FunToken.BankDenom, bals.Account, bals.BalanceBank, + bals.Description, ) } + +func AssertERC20BalanceEqualWithDescription( + t *testing.T, + deps TestDeps, + erc20, account gethcommon.Address, + expectedBalance *big.Int, + description string, +) { + actualBalance, err := deps.EvmKeeper.ERC20().BalanceOf(erc20, account, deps.Ctx) + var errSuffix string + if description == "" { + errSuffix = description + } else { + errSuffix = ": " + description + } + assert.NoError(t, err, errSuffix) + assert.Equalf(t, expectedBalance.String(), actualBalance.String(), + "expected %s, got %s", expectedBalance, actualBalance, + errSuffix, + ) +} + +func AssertBankBalanceEqualWithDescription( + t *testing.T, + deps TestDeps, + denom string, + account gethcommon.Address, + expectedBalance *big.Int, + description string, +) { + bech32Addr := eth.EthAddrToNibiruAddr(account) + actualBalance := deps.App.BankKeeper.GetBalance(deps.Ctx, bech32Addr, denom).Amount.BigInt() + var errSuffix string + if description == "" { + errSuffix = description + } else { + errSuffix = ": " + description + } + assert.Equalf(t, expectedBalance.String(), actualBalance.String(), + "expected %s, got %s", expectedBalance, actualBalance, errSuffix) +}