From 295a2d92eb63b49ecf30e017f9b1cc4058b6cae2 Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Fri, 25 Oct 2024 03:06:40 -0500 Subject: [PATCH 01/21] 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/21] 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/21] 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/21] chore: changelog --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 821b29e13..ac2dba69c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,23 @@ consistent setup and dynamic gas calculations, addressing the following tickets. - [#2088](https://github.com/NibiruChain/nibiru/pull/2088) - refactor(evm): remove outdated comment and improper error message text - [#2089](https://github.com/NibiruChain/nibiru/pull/2089) - better handling of gas consumption within erc20 contract execution - [#2091](https://github.com/NibiruChain/nibiru/pull/2091) - feat(evm): add fun token creation fee validation +- [#2094](https://github.com/NibiruChain/nibiru/pull/2094) - fix(evm): Following +from the changs in #2086, this pull request implements two critical security +fixes. + 1. First, we add new `JournalChange` struct that saves a deep copy of the + state multi store before each state-modifying, Nibiru-specific precompiled + contract is called (`OnRunStart`). Additionally, we commit the `StateDB` there + as well. This guarantees that the non-EVM and EVM state will be in sync even + if there are complex, multi-step Ethereum transactions, such as in the case of + an EthereumTx that influences the `StateDB`, then calls a precompile that also + changes non-EVM state, and then EVM reverts inside of a try-catch. + 2. Second, the solution from #2086 that records NIBI (ether) transfers on the + `StateDB` during precompiled contract calls is generalized as + `NibiruBankKeeper`, which is struct extension of the `bankkeeper.BaseKeeper` + that is used throughout the Nibiru base application. The `NibiruBankKeeper` + holds a reference to the current EVM `StateDB` if there is one and records + balance changes in wei as journal changes automatically. + #### Nibiru EVM | Before Audit 1 - 2024-10-18 From 08a73ee6c86fbf1449a10f32c7b0eecdfb5caa7f Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Mon, 28 Oct 2024 18:46:01 -0500 Subject: [PATCH 05/21] 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 06/21] 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 07/21] fix strange ignored file issue --- x/evm/evmtest/tx.go | 182 ++++++++++++++++++++++---------------------- 1 file changed, 93 insertions(+), 89 deletions(-) diff --git a/x/evm/evmtest/tx.go b/x/evm/evmtest/tx.go index dde679851..b2bfe3e34 100644 --- a/x/evm/evmtest/tx.go +++ b/x/evm/evmtest/tx.go @@ -26,95 +26,6 @@ import ( "github.com/NibiruChain/nibiru/v2/x/evm/embeds" ) -type GethTxType = uint8 - -func TxTemplateAccessListTx() *gethcore.AccessListTx { - return &gethcore.AccessListTx{ - GasPrice: big.NewInt(1), - Gas: gethparams.TxGas, - To: &gethcommon.Address{}, - Value: big.NewInt(0), - Data: []byte{}, - } -} - -func TxTemplateLegacyTx() *gethcore.LegacyTx { - return &gethcore.LegacyTx{ - GasPrice: big.NewInt(1), - Gas: gethparams.TxGas, - To: &gethcommon.Address{}, - Value: big.NewInt(0), - Data: []byte{}, - } -} - -func TxTemplateDynamicFeeTx() *gethcore.DynamicFeeTx { - return &gethcore.DynamicFeeTx{ - GasFeeCap: big.NewInt(10), - GasTipCap: big.NewInt(2), - Gas: gethparams.TxGas, - To: &gethcommon.Address{}, - Value: big.NewInt(0), - Data: []byte{}, - } -} - -func NewEthTxMsgFromTxData( - deps *TestDeps, - txType GethTxType, - innerTxData []byte, - nonce uint64, - to *gethcommon.Address, - value *big.Int, - gas uint64, - accessList gethcore.AccessList, -) (*evm.MsgEthereumTx, error) { - if innerTxData == nil { - innerTxData = []byte{} - } - - var ethCoreTx *gethcore.Transaction - switch txType { - case gethcore.LegacyTxType: - innerTx := TxTemplateLegacyTx() - innerTx.Nonce = nonce - innerTx.Data = innerTxData - innerTx.To = to - innerTx.Value = value - innerTx.Gas = gas - ethCoreTx = gethcore.NewTx(innerTx) - case gethcore.AccessListTxType: - innerTx := TxTemplateAccessListTx() - innerTx.Nonce = nonce - innerTx.Data = innerTxData - innerTx.AccessList = accessList - innerTx.To = to - innerTx.Value = value - innerTx.Gas = gas - ethCoreTx = gethcore.NewTx(innerTx) - case gethcore.DynamicFeeTxType: - innerTx := TxTemplateDynamicFeeTx() - innerTx.Nonce = nonce - innerTx.Data = innerTxData - innerTx.To = to - innerTx.Value = value - innerTx.Gas = gas - innerTx.AccessList = accessList - ethCoreTx = gethcore.NewTx(innerTx) - default: - return nil, fmt.Errorf( - "received unknown tx type (%v) in NewEthTxMsgFromTxData", txType) - } - - ethTxMsg := new(evm.MsgEthereumTx) - if err := ethTxMsg.FromEthereumTx(ethCoreTx); err != nil { - return ethTxMsg, err - } - - ethTxMsg.From = deps.Sender.EthAddr.Hex() - return ethTxMsg, ethTxMsg.Sign(deps.GethSigner(), deps.Sender.KeyringSigner) -} - // ExecuteNibiTransfer executes nibi transfer func ExecuteNibiTransfer(deps *TestDeps, t *testing.T) *evm.MsgEthereumTx { nonce := deps.StateDB().GetNonce(deps.Sender.EthAddr) @@ -326,6 +237,10 @@ func TransferWei( return err } +// -------------------------------------------------- +// Templates +// -------------------------------------------------- + // ValidLegacyTx: Useful initial condition for tests // Exported only for use in tests. func ValidLegacyTx() *evm.LegacyTx { @@ -342,3 +257,92 @@ func ValidLegacyTx() *evm.LegacyTx { S: []byte{}, } } + +type GethTxType = uint8 + +func TxTemplateAccessListTx() *gethcore.AccessListTx { + return &gethcore.AccessListTx{ + GasPrice: big.NewInt(1), + Gas: gethparams.TxGas, + To: &gethcommon.Address{}, + Value: big.NewInt(0), + Data: []byte{}, + } +} + +func TxTemplateLegacyTx() *gethcore.LegacyTx { + return &gethcore.LegacyTx{ + GasPrice: big.NewInt(1), + Gas: gethparams.TxGas, + To: &gethcommon.Address{}, + Value: big.NewInt(0), + Data: []byte{}, + } +} + +func TxTemplateDynamicFeeTx() *gethcore.DynamicFeeTx { + return &gethcore.DynamicFeeTx{ + GasFeeCap: big.NewInt(10), + GasTipCap: big.NewInt(2), + Gas: gethparams.TxGas, + To: &gethcommon.Address{}, + Value: big.NewInt(0), + Data: []byte{}, + } +} + +func NewEthTxMsgFromTxData( + deps *TestDeps, + txType GethTxType, + innerTxData []byte, + nonce uint64, + to *gethcommon.Address, + value *big.Int, + gas uint64, + accessList gethcore.AccessList, +) (*evm.MsgEthereumTx, error) { + if innerTxData == nil { + innerTxData = []byte{} + } + + var ethCoreTx *gethcore.Transaction + switch txType { + case gethcore.LegacyTxType: + innerTx := TxTemplateLegacyTx() + innerTx.Nonce = nonce + innerTx.Data = innerTxData + innerTx.To = to + innerTx.Value = value + innerTx.Gas = gas + ethCoreTx = gethcore.NewTx(innerTx) + case gethcore.AccessListTxType: + innerTx := TxTemplateAccessListTx() + innerTx.Nonce = nonce + innerTx.Data = innerTxData + innerTx.AccessList = accessList + innerTx.To = to + innerTx.Value = value + innerTx.Gas = gas + ethCoreTx = gethcore.NewTx(innerTx) + case gethcore.DynamicFeeTxType: + innerTx := TxTemplateDynamicFeeTx() + innerTx.Nonce = nonce + innerTx.Data = innerTxData + innerTx.To = to + innerTx.Value = value + innerTx.Gas = gas + innerTx.AccessList = accessList + ethCoreTx = gethcore.NewTx(innerTx) + default: + return nil, fmt.Errorf( + "received unknown tx type (%v) in NewEthTxMsgFromTxData", txType) + } + + ethTxMsg := new(evm.MsgEthereumTx) + if err := ethTxMsg.FromEthereumTx(ethCoreTx); err != nil { + return ethTxMsg, err + } + + ethTxMsg.From = deps.Sender.EthAddr.Hex() + return ethTxMsg, ethTxMsg.Sign(deps.GethSigner(), deps.Sender.KeyringSigner) +} From 7c34423434f7f2768ce92759e5c2c1d24d68e9f0 Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Mon, 28 Oct 2024 19:49:45 -0500 Subject: [PATCH 08/21] 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 09/21] 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 90fecf7b3f90737042d7eb217e0e06cce049f213 Mon Sep 17 00:00:00 2001 From: Kevin Yang <5478483+k-yang@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:29:12 -0700 Subject: [PATCH 10/21] refactor: clean up test code --- x/evm/precompile/test/export.go | 63 ++++++++++++--------------------- x/evm/statedb/journal_test.go | 27 +++++++------- 2 files changed, 35 insertions(+), 55 deletions(-) diff --git a/x/evm/precompile/test/export.go b/x/evm/precompile/test/export.go index 28670e3e0..24700ce85 100644 --- a/x/evm/precompile/test/export.go +++ b/x/evm/precompile/test/export.go @@ -8,20 +8,17 @@ import ( "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" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/core/vm" "github.com/stretchr/testify/suite" "github.com/NibiruChain/nibiru/v2/app" + serverconfig "github.com/NibiruChain/nibiru/v2/app/server/config" "github.com/NibiruChain/nibiru/v2/x/evm/embeds" "github.com/NibiruChain/nibiru/v2/x/evm/evmtest" "github.com/NibiruChain/nibiru/v2/x/evm/precompile" - "github.com/NibiruChain/nibiru/v2/x/evm/statedb" ) // SetupWasmContracts stores all Wasm bytecode and has the "deps.Sender" @@ -31,68 +28,52 @@ func SetupWasmContracts(deps *evmtest.TestDeps, s *suite.Suite) ( ) { wasmCodes := DeployWasmBytecode(s, deps.Ctx, deps.Sender.NibiruAddr, deps.App) - otherArgs := []struct { - InstMsg []byte - Label string + instantiateArgs := []struct { + InstantiateMsg []byte + Label string }{ { - InstMsg: []byte("{}"), - Label: "https://github.com/NibiruChain/nibiru-wasm/blob/main/contracts/nibi-stargate/src/contract.rs", + InstantiateMsg: []byte("{}"), + Label: "https://github.com/NibiruChain/nibiru-wasm/blob/main/contracts/nibi-stargate/src/contract.rs", }, { - InstMsg: []byte(`{"count": 0}`), - Label: "https://github.com/NibiruChain/nibiru-wasm/tree/ec3ab9f09587a11fbdfbd4021c7617eca3912044/contracts/00-hello-world-counter", + InstantiateMsg: []byte(`{"count": 0}`), + Label: "https://github.com/NibiruChain/nibiru-wasm/tree/ec3ab9f09587a11fbdfbd4021c7617eca3912044/contracts/00-hello-world-counter", }, } - for wasmCodeIdx, wasmCode := range wasmCodes { + for i, wasmCode := range wasmCodes { s.T().Logf("Instantiate using Wasm precompile: %s", wasmCode.binPath) codeId := wasmCode.codeId m := wasm.MsgInstantiateContract{ Admin: "", CodeID: codeId, - Label: otherArgs[wasmCodeIdx].Label, - Msg: otherArgs[wasmCodeIdx].InstMsg, - Funds: []sdk.Coin{}, + Label: instantiateArgs[i].Label, + Msg: instantiateArgs[i].InstantiateMsg, } msgArgsBz, err := json.Marshal(m.Msg) s.NoError(err) - var funds []precompile.WasmBankCoin - fundsJson, err := m.Funds.MarshalJSON() - s.NoErrorf(err, "fundsJson: %s", fundsJson) - err = json.Unmarshal(fundsJson, &funds) - s.Require().NoError(err) - - callArgs := []any{m.Admin, m.CodeID, msgArgsBz, m.Label, funds} + callArgs := []any{m.Admin, m.CodeID, msgArgsBz, m.Label, []precompile.WasmBankCoin{}} input, err := embeds.SmartContract_Wasm.ABI.Pack( string(precompile.WasmMethod_instantiate), callArgs..., ) s.Require().NoError(err) - ethTxResp, evmObj, err := deps.EvmKeeper.CallContractWithInput( + ethTxResp, _, err := deps.EvmKeeper.CallContractWithInput( deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input, ) s.Require().NoError(err) s.Require().NotEmpty(ethTxResp.Ret) - // Finalize transaction - err = evmObj.StateDB.(*statedb.StateDB).Commit() - s.Require().NoError(err) - s.T().Log("Parse the response contract addr and response bytes") - var contractAddrStr string - var data []byte - err = embeds.SmartContract_Wasm.ABI.UnpackIntoInterface( - &[]any{&contractAddrStr, &data}, - string(precompile.WasmMethod_instantiate), - ethTxResp.Ret, - ) + vals, err := embeds.SmartContract_Wasm.ABI.Unpack(string(precompile.WasmMethod_instantiate), ethTxResp.Ret) s.Require().NoError(err) - contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + + contractAddr, err := sdk.AccAddressFromBech32(vals[0].(string)) s.NoError(err) contracts = append(contracts, contractAddr) } @@ -118,7 +99,7 @@ func DeployWasmBytecode( rootPathBz, err := exec.Command("go", "list", "-m", "-f", "{{.Dir}}").Output() s.Require().NoError(err) rootPath := strings.Trim(string(rootPathBz), "\n") - for _, pathToWasmBin := range []string{ + for _, wasmFile := range []string{ // nibi_stargate.wasm is a compiled version of: // https://github.com/NibiruChain/nibiru-wasm/blob/main/contracts/nibi-stargate/src/contract.rs "x/tokenfactory/fixture/nibi_stargate.wasm", @@ -129,11 +110,11 @@ func DeployWasmBytecode( // Add other wasm bytecode here if needed... } { - pathToWasmBin = path.Join(string(rootPath), pathToWasmBin) - wasmBytecode, err := os.ReadFile(pathToWasmBin) + binPath := path.Join(rootPath, wasmFile) + wasmBytecode, err := os.ReadFile(binPath) s.Require().NoErrorf( err, - "rootPath %s, pathToWasmBin %s", rootPath, pathToWasmBin, + "path %s, pathToWasmBin %s", binPath, ) // The "Create" fn is private on the nibiru.WasmKeeper. By placing it as the @@ -150,7 +131,7 @@ func DeployWasmBytecode( codeIds = append(codeIds, struct { codeId uint64 binPath string - }{codeId, pathToWasmBin}) + }{codeId, binPath}) } return codeIds diff --git a/x/evm/statedb/journal_test.go b/x/evm/statedb/journal_test.go index 6390b640f..c1c74110c 100644 --- a/x/evm/statedb/journal_test.go +++ b/x/evm/statedb/journal_test.go @@ -31,13 +31,12 @@ func (s *Suite) TestComplexJournalChanges() { )) s.T().Log("Set up helloworldcounter.wasm") - - wasmContract := test.SetupWasmContracts(&deps, &s.Suite)[1] - fmt.Printf("wasmContract: %s\n", wasmContract) + helloWorldCounterWasm := test.SetupWasmContracts(&deps, &s.Suite)[1] + fmt.Printf("wasmContract: %s\n", helloWorldCounterWasm) s.T().Log("Assert before transition") test.AssertWasmCounterState( - &s.Suite, deps, wasmContract, 0, + &s.Suite, deps, helloWorldCounterWasm, 0, ) deployArgs := []any{"name", "SYMBOL", uint8(18)} @@ -129,11 +128,11 @@ func (s *Suite) TestComplexJournalChanges() { s.T().Log("commitEvmTx=true, expect 0 dirty journal entries") commitEvmTx := true evmObj = test.IncrementWasmCounterWithExecuteMulti( - &s.Suite, &deps, wasmContract, 7, commitEvmTx, + &s.Suite, &deps, helloWorldCounterWasm, 7, commitEvmTx, ) // assertions after run test.AssertWasmCounterState( - &s.Suite, deps, wasmContract, 7, + &s.Suite, deps, helloWorldCounterWasm, 7, ) stateDB, ok := evmObj.StateDB.(*statedb.StateDB) s.Require().True(ok, "error retrieving StateDB from the EVM") @@ -145,7 +144,7 @@ func (s *Suite) TestComplexJournalChanges() { s.T().Log("commitEvmTx=false, expect dirty journal entries") commitEvmTx = false evmObj = test.IncrementWasmCounterWithExecuteMulti( - &s.Suite, &deps, wasmContract, 5, commitEvmTx, + &s.Suite, &deps, helloWorldCounterWasm, 5, commitEvmTx, ) stateDB, ok = evmObj.StateDB.(*statedb.StateDB) s.Require().True(ok, "error retrieving StateDB from the EVM") @@ -158,7 +157,7 @@ func (s *Suite) TestComplexJournalChanges() { s.T().Log("Expect no change since the StateDB has not been committed") test.AssertWasmCounterState( - &s.Suite, deps, wasmContract, 7, // 7 = 7 + 0 + &s.Suite, deps, helloWorldCounterWasm, 7, // 7 = 7 + 0 ) s.T().Log("Expect change to persist on the StateDB cacheCtx") @@ -166,14 +165,14 @@ func (s *Suite) TestComplexJournalChanges() { s.NotNil(cacheCtx) deps.Ctx = *cacheCtx test.AssertWasmCounterState( - &s.Suite, deps, wasmContract, 12, // 12 = 7 + 5 + &s.Suite, deps, helloWorldCounterWasm, 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, + &s.Suite, &deps, helloWorldCounterWasm, 50, commitEvmTx, evmObj, ) stateDBPtr := evmObj.StateDB.(*statedb.StateDB) s.Require().Equal(stateDB, stateDBPtr) @@ -185,7 +184,7 @@ snapshots and see the prior states.`)) cacheCtx = stateDB.GetCacheContext() deps.Ctx = *cacheCtx test.AssertWasmCounterState( - &s.Suite, deps, wasmContract, 7+5+50, + &s.Suite, deps, helloWorldCounterWasm, 7+5+50, ) errFn := common.TryCatch(func() { @@ -201,7 +200,7 @@ snapshots and see the prior states.`)) s.NotNil(cacheCtx) deps.Ctx = *cacheCtx test.AssertWasmCounterState( - &s.Suite, deps, wasmContract, 7+5, + &s.Suite, deps, helloWorldCounterWasm, 7+5, ) stateDB.RevertToSnapshot(0) @@ -209,13 +208,13 @@ snapshots and see the prior states.`)) s.NotNil(cacheCtx) deps.Ctx = *cacheCtx test.AssertWasmCounterState( - &s.Suite, deps, wasmContract, 7, // state before precompile called + &s.Suite, deps, helloWorldCounterWasm, 7, // state before precompile called ) err = stateDB.Commit() deps.Ctx = stateDB.GetEvmTxContext() test.AssertWasmCounterState( - &s.Suite, deps, wasmContract, 7, // state before precompile called + &s.Suite, deps, helloWorldCounterWasm, 7, // state before precompile called ) }) } From 5fcb36167ae444e24dfe1215f2bc649967d2abfd Mon Sep 17 00:00:00 2001 From: Kevin Yang <5478483+k-yang@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:36:15 -0700 Subject: [PATCH 11/21] refactor: clean up parse fund args --- x/evm/precompile/wasm_parse.go | 57 +++++++++++++--------------------- 1 file changed, 22 insertions(+), 35 deletions(-) diff --git a/x/evm/precompile/wasm_parse.go b/x/evm/precompile/wasm_parse.go index 80d950622..5a3014569 100644 --- a/x/evm/precompile/wasm_parse.go +++ b/x/evm/precompile/wasm_parse.go @@ -16,51 +16,38 @@ type WasmBankCoin struct { Amount *big.Int `json:"amount"` } -func parseSdkCoins(unparsed []struct { - Denom string `json:"denom"` - Amount *big.Int `json:"amount"` -}, -) sdk.Coins { - parsed := sdk.Coins{} - for _, coin := range unparsed { - parsed = append( - parsed, - // Favor the sdk.Coin constructor over sdk.NewCoin because sdk.NewCoin - // is not panic-safe. Validation will be handled when the coin is used - // as an argument during the execution of a transaction. - sdk.Coin{ - Denom: coin.Denom, - Amount: sdk.NewIntFromBigInt(coin.Amount), - }, - ) - } - return parsed -} - // Parses [sdk.Coins] from a "BankCoin[]" solidity argument: // // ```solidity // BankCoin[] memory funds // ``` func parseFundsArg(arg any) (funds sdk.Coins, err error) { - bankCoinsUnparsed, ok := arg.([]struct { + if arg == nil { + return funds, nil + } + + raw, ok := arg.([]struct { Denom string `json:"denom"` Amount *big.Int `json:"amount"` }) - switch { - case arg == nil: - bankCoinsUnparsed = []struct { - Denom string `json:"denom"` - Amount *big.Int `json:"amount"` - }{} - case !ok: - err = ErrArgTypeValidation("BankCoin[] funds", arg) - return - case ok: - // Type assertion succeeded + + if !ok { + return funds, ErrArgTypeValidation("BankCoin[] funds", arg) + } + + for _, coin := range raw { + funds = append( + funds, + // Favor the sdk.Coin constructor over sdk.NewCoin because sdk.NewCoin + // is not panic-safe. Validation will be handled when the coin is used + // as an argument during the execution of a transaction. + sdk.Coin{ + Denom: coin.Denom, + Amount: sdk.NewIntFromBigInt(coin.Amount), + }, + ) } - funds = parseSdkCoins(bankCoinsUnparsed) - return + return funds, nil } // Parses [sdk.AccAddress] from a "string" solidity argument: From c1bb21adc422e1264bf5e2b3c2b0fed14a4e1083 Mon Sep 17 00:00:00 2001 From: Kevin Yang <5478483+k-yang@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:40:39 -0700 Subject: [PATCH 12/21] refactor: clean up common precompile functions --- x/evm/precompile/precompile.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/x/evm/precompile/precompile.go b/x/evm/precompile/precompile.go index 40d0c74b4..9c136b56f 100644 --- a/x/evm/precompile/precompile.go +++ b/x/evm/precompile/precompile.go @@ -85,15 +85,15 @@ func methodById(abi *gethabi.ABI, sigdata []byte) (*gethabi.Method, error) { return nil, fmt.Errorf("no method with id: %#x", sigdata[:4]) } -func DecomposeInput( +func decomposeInput( abi *gethabi.ABI, input []byte, ) (method *gethabi.Method, args []interface{}, err error) { // ABI method IDs are exactly 4 bytes according to "gethabi.ABI.MethodByID". if len(input) < 4 { - readableBz := collections.HumanizeBytes(input) - err = fmt.Errorf("input \"%s\" too short to extract method ID (less than 4 bytes)", readableBz) + err = fmt.Errorf("input \"%s\" too short to extract method ID (less than 4 bytes)", collections.HumanizeBytes(input)) return } + method, err = methodById(abi, input[:4]) if err != nil { err = fmt.Errorf("unable to parse ABI method by its 4-byte ID: %w", err) @@ -110,7 +110,7 @@ func DecomposeInput( } func RequiredGas(input []byte, abi *gethabi.ABI) uint64 { - method, _, err := DecomposeInput(abi, input) + method, err := methodById(abi, input[:4]) if err != nil { // It's appropriate to return a reasonable default here // because the error from DecomposeInput will be handled automatically by @@ -122,16 +122,15 @@ func RequiredGas(input []byte, abi *gethabi.ABI) uint64 { // Map access could panic. We know that it won't panic because all methods // are in the map, which is verified by unit tests. - methodIsTx := precompileMethodIsTxMap[PrecompileMethod(method.Name)] var costPerByte, costFlat uint64 - if methodIsTx { + if isMutation[PrecompileMethod(method.Name)] { costPerByte, costFlat = gasCfg.WriteCostPerByte, gasCfg.WriteCostFlat } else { costPerByte, costFlat = gasCfg.ReadCostPerByte, gasCfg.ReadCostFlat } - argsBzLen := uint64(len(input[4:])) - return (costPerByte * argsBzLen) + costFlat + // Calculate the total gas required based on the input size and flat cost + return (costPerByte * uint64(len(input[4:]))) + costFlat } type OnRunStartResult struct { @@ -180,7 +179,7 @@ type OnRunStartResult struct { func OnRunStart( evm *vm.EVM, contract *vm.Contract, abi *gethabi.ABI, ) (res OnRunStartResult, err error) { - method, args, err := DecomposeInput(abi, contract.Input) + method, args, err := decomposeInput(abi, contract.Input) if err != nil { return res, err } @@ -210,7 +209,7 @@ func OnRunStart( }, nil } -var precompileMethodIsTxMap map[PrecompileMethod]bool = map[PrecompileMethod]bool{ +var isMutation map[PrecompileMethod]bool = map[PrecompileMethod]bool{ WasmMethod_execute: true, WasmMethod_instantiate: true, WasmMethod_executeMulti: true, From 10885f10ec1fe9a507c671801a167f5586a4bc27 Mon Sep 17 00:00:00 2001 From: Kevin Yang <5478483+k-yang@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:46:02 -0700 Subject: [PATCH 13/21] refactor: clean up precompile onrunstart --- x/evm/precompile/errors.go | 9 --------- x/evm/precompile/funtoken.go | 7 +++---- x/evm/precompile/oracle.go | 2 +- x/evm/precompile/precompile.go | 6 +++--- x/evm/precompile/wasm.go | 20 ++++++++++---------- x/evm/statedb/statedb.go | 7 +++---- 6 files changed, 20 insertions(+), 31 deletions(-) diff --git a/x/evm/precompile/errors.go b/x/evm/precompile/errors.go index a95989b71..498ba8406 100644 --- a/x/evm/precompile/errors.go +++ b/x/evm/precompile/errors.go @@ -1,7 +1,6 @@ package precompile import ( - "errors" "fmt" "reflect" @@ -34,14 +33,6 @@ func ErrMethodCalled(method *gethabi.Method, wrapped error) error { return fmt.Errorf("%s method called: %w", method.Name, wrapped) } -// Check required for transactions but not needed for queries -func assertNotReadonlyTx(readOnly bool, isTx bool) error { - if readOnly && isTx { - return errors.New("cannot write state from staticcall (a read-only call)") - } - return nil -} - // assertContractQuery checks if a contract call is a valid query operation. This // function verifies that no funds (wei) are being sent with the query, as query // operations should be read-only and not involve any value transfer. diff --git a/x/evm/precompile/funtoken.go b/x/evm/precompile/funtoken.go index 69e756ed3..6a9c5e76e 100644 --- a/x/evm/precompile/funtoken.go +++ b/x/evm/precompile/funtoken.go @@ -38,7 +38,7 @@ func (p precompileFunToken) ABI() *gethabi.ABI { // RequiredGas calculates the cost of calling the precompile in gas units. func (p precompileFunToken) RequiredGas(input []byte) (gasCost uint64) { - return RequiredGas(input, p.ABI()) + return requiredGas(input, p.ABI()) } const ( @@ -106,9 +106,8 @@ func (p precompileFunToken) bankSend( readOnly bool, ) (bz []byte, err error) { ctx, method, args := start.Ctx, start.Method, start.Args - if e := assertNotReadonlyTx(readOnly, true); e != nil { - err = e - return + if readOnly { + return nil, fmt.Errorf("bankSend cannot be called in read-only mode") } if !executionGuard.TryLock() { return nil, fmt.Errorf("bankSend is already in progress") diff --git a/x/evm/precompile/oracle.go b/x/evm/precompile/oracle.go index fb0b2981b..fd809df50 100644 --- a/x/evm/precompile/oracle.go +++ b/x/evm/precompile/oracle.go @@ -24,7 +24,7 @@ func (p precompileOracle) Address() gethcommon.Address { } func (p precompileOracle) RequiredGas(input []byte) (gasPrice uint64) { - return RequiredGas(input, embeds.SmartContract_Oracle.ABI) + return requiredGas(input, embeds.SmartContract_Oracle.ABI) } const ( diff --git a/x/evm/precompile/precompile.go b/x/evm/precompile/precompile.go index 9c136b56f..5a729a5b3 100644 --- a/x/evm/precompile/precompile.go +++ b/x/evm/precompile/precompile.go @@ -109,7 +109,7 @@ func decomposeInput( return method, args, nil } -func RequiredGas(input []byte, abi *gethabi.ABI) uint64 { +func requiredGas(input []byte, abi *gethabi.ABI) uint64 { method, err := methodById(abi, input[:4]) if err != nil { // It's appropriate to return a reasonable default here @@ -193,8 +193,8 @@ func OnRunStart( // 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 { + cacheCtx, journalEntry := stateDB.CacheCtxForPrecompile() + if err = stateDB.SavePrecompileCalledJournalChange(journalEntry); err != nil { return res, err } if err = stateDB.CommitCacheCtx(); err != nil { diff --git a/x/evm/precompile/wasm.go b/x/evm/precompile/wasm.go index a7b21684c..01106c9d8 100644 --- a/x/evm/precompile/wasm.go +++ b/x/evm/precompile/wasm.go @@ -1,6 +1,7 @@ package precompile import ( + "errors" "fmt" sdk "github.com/cosmos/cosmos-sdk/types" @@ -79,7 +80,7 @@ func (p precompileWasm) ABI() *gethabi.ABI { // RequiredGas calculates the cost of calling the precompile in gas units. func (p precompileWasm) RequiredGas(input []byte) (gasCost uint64) { - return RequiredGas(input, p.ABI()) + return requiredGas(input, p.ABI()) } // Wasm: A struct embedding keepers for read and write operations in Wasm, such @@ -128,17 +129,16 @@ func (p precompileWasm) execute( err = ErrMethodCalled(method, err) } }() - - if err := assertNotReadonlyTx(readOnly, true); err != nil { - return bz, err + if readOnly { + return nil, errors.New("wasm execute cannot be called in read-only mode") } + wasmContract, msgArgs, funds, err := p.parseExecuteArgs(args) if err != nil { err = ErrInvalidArgs(err) return } - callerBech32 := eth.EthAddrToNibiruAddr(caller) - data, err := p.Wasm.Execute(ctx, wasmContract, callerBech32, msgArgs, funds) + data, err := p.Wasm.Execute(ctx, wasmContract, eth.EthAddrToNibiruAddr(caller), msgArgs, funds) if err != nil { return } @@ -212,8 +212,8 @@ func (p precompileWasm) instantiate( err = ErrMethodCalled(method, err) } }() - if err := assertNotReadonlyTx(readOnly, true); err != nil { - return bz, err + if readOnly { + return nil, errors.New("wasm instantiate cannot be called in read-only mode") } callerBech32 := eth.EthAddrToNibiruAddr(caller) @@ -263,8 +263,8 @@ func (p precompileWasm) executeMulti( err = ErrMethodCalled(method, err) } }() - if err := assertNotReadonlyTx(readOnly, true); err != nil { - return bz, err + if readOnly { + return nil, errors.New("wasm executeMulti cannot be called in read-only mode") } wasmExecMsgs, err := p.parseExecuteMultiArgs(args) diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go index 4c79e61af..6112ce868 100644 --- a/x/evm/statedb/statedb.go +++ b/x/evm/statedb/statedb.go @@ -572,7 +572,7 @@ func (s *StateDB) commitCtx(ctx sdk.Context) error { return nil } -func (s *StateDB) CacheCtxForPrecompile(precompileAddr common.Address) ( +func (s *StateDB) CacheCtxForPrecompile() ( sdk.Context, PrecompileCalled, ) { if s.writeToCommitCtxFromCacheCtx == nil { @@ -593,15 +593,14 @@ func (s *StateDB) CacheCtxForPrecompile(precompileAddr common.Address) ( // // See [PrecompileCalled] for more info. func (s *StateDB) SavePrecompileCalledJournalChange( - precompileAddr common.Address, journalChange PrecompileCalled, ) error { 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(), + "exceeded maximum number Nibiru-specific precompiled contract calls in one transaction (%d).", + maxMultistoreCacheCount, ) } return nil From f04b1e595579d1df6e96b6d12ea55dbd8159bb7e Mon Sep 17 00:00:00 2001 From: Kevin Yang <5478483+k-yang@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:56:13 -0700 Subject: [PATCH 14/21] refactor: clean up precompile onrunstart --- x/evm/precompile/funtoken.go | 22 ++++--------------- x/evm/precompile/oracle.go | 6 ++--- x/evm/precompile/precompile.go | 22 +++++++++---------- x/evm/precompile/test/export.go | 10 ++++----- x/evm/precompile/wasm.go | 39 ++++++++++++++------------------- x/evm/precompile/wasm_parse.go | 19 +++++++++++----- 6 files changed, 53 insertions(+), 65 deletions(-) diff --git a/x/evm/precompile/funtoken.go b/x/evm/precompile/funtoken.go index 6a9c5e76e..9ad906479 100644 --- a/x/evm/precompile/funtoken.go +++ b/x/evm/precompile/funtoken.go @@ -3,13 +3,11 @@ 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" 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" @@ -32,21 +30,15 @@ func (p precompileFunToken) Address() gethcommon.Address { return PrecompileAddr_FunToken } -func (p precompileFunToken) ABI() *gethabi.ABI { - return embeds.SmartContract_FunToken.ABI -} - // RequiredGas calculates the cost of calling the precompile in gas units. func (p precompileFunToken) RequiredGas(input []byte) (gasCost uint64) { - return requiredGas(input, p.ABI()) + return requiredGas(input, embeds.SmartContract_FunToken.ABI) } const ( FunTokenMethod_BankSend PrecompileMethod = "bankSend" ) -type PrecompileMethod string - // Run runs the precompiled contract func (p precompileFunToken) Run( evm *vm.EVM, contract *vm.Contract, readonly bool, @@ -54,7 +46,7 @@ func (p precompileFunToken) Run( defer func() { err = ErrPrecompileRun(err, p) }() - start, err := OnRunStart(evm, contract, p.ABI()) + start, err := OnRunStart(evm, contract.Input, embeds.SmartContract_FunToken.ABI) if err != nil { return nil, err } @@ -87,8 +79,6 @@ type precompileFunToken struct { evmKeeper evmkeeper.Keeper } -var executionGuard sync.Mutex - // bankSend: Implements "IFunToken.bankSend" // // The "args" populate the following function signature in Solidity: @@ -105,14 +95,10 @@ func (p precompileFunToken) bankSend( caller gethcommon.Address, readOnly bool, ) (bz []byte, err error) { - ctx, method, args := start.Ctx, start.Method, start.Args + ctx, method, args := start.CacheCtx, start.Method, start.Args if readOnly { return nil, fmt.Errorf("bankSend cannot be called in read-only mode") } - 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 { @@ -241,7 +227,7 @@ func SafeSendCoinFromModuleToAccount( return nil } -func (p precompileFunToken) decomposeBankSendArgs(args []any) ( +func (p precompileFunToken) decomposeBankSendArgs(args []interface{}) ( erc20 gethcommon.Address, amount *big.Int, to string, diff --git a/x/evm/precompile/oracle.go b/x/evm/precompile/oracle.go index fd809df50..81f19255a 100644 --- a/x/evm/precompile/oracle.go +++ b/x/evm/precompile/oracle.go @@ -38,11 +38,11 @@ func (p precompileOracle) Run( defer func() { err = ErrPrecompileRun(err, p) }() - res, err := OnRunStart(evm, contract, embeds.SmartContract_Oracle.ABI) + res, err := OnRunStart(evm, contract.Input, embeds.SmartContract_Oracle.ABI) if err != nil { return nil, err } - method, args, ctx := res.Method, res.Args, res.Ctx + method, args, ctx := res.Method, res.Args, res.CacheCtx switch PrecompileMethod(method.Name) { case OracleMethod_queryExchangeRate: @@ -88,7 +88,7 @@ func (p precompileOracle) queryExchangeRate( return method.Outputs.Pack(price.String()) } -func (p precompileOracle) decomposeQueryExchangeRateArgs(args []any) ( +func (p precompileOracle) decomposeQueryExchangeRateArgs(args []interface{}) ( pair string, err error, ) { diff --git a/x/evm/precompile/precompile.go b/x/evm/precompile/precompile.go index 5a729a5b3..8f1a94f7f 100644 --- a/x/evm/precompile/precompile.go +++ b/x/evm/precompile/precompile.go @@ -133,21 +133,21 @@ func requiredGas(input []byte, abi *gethabi.ABI) uint64 { return (costPerByte * uint64(len(input[4:]))) + costFlat } +type PrecompileMethod string + type OnRunStartResult struct { // Args contains the decoded (ABI unpacked) arguments passed to the contract // as input. - Args []any + Args []interface{} - // Ctx is a cached SDK context that allows isolated state + // CacheCtx is a cached SDK context that allows isolated state // operations to occur that can be reverted by the EVM's [statedb.StateDB]. - Ctx sdk.Context + CacheCtx sdk.Context // Method is the ABI method for the precompiled contract call. Method *gethabi.Method StateDB *statedb.StateDB - - PrecompileJournalEntry statedb.PrecompileCalled } // OnRunStart prepares the execution environment for a precompiled contract call. @@ -177,9 +177,9 @@ type OnRunStartResult struct { // } // ``` func OnRunStart( - evm *vm.EVM, contract *vm.Contract, abi *gethabi.ABI, + evm *vm.EVM, contractInput []byte, abi *gethabi.ABI, ) (res OnRunStartResult, err error) { - method, args, err := decomposeInput(abi, contract.Input) + method, args, err := decomposeInput(abi, contractInput) if err != nil { return res, err } @@ -202,10 +202,10 @@ func OnRunStart( } return OnRunStartResult{ - Args: args, - Ctx: cacheCtx, - Method: method, - StateDB: stateDB, + Args: args, + CacheCtx: cacheCtx, + Method: method, + StateDB: stateDB, }, nil } diff --git a/x/evm/precompile/test/export.go b/x/evm/precompile/test/export.go index 24700ce85..ca60a9c73 100644 --- a/x/evm/precompile/test/export.go +++ b/x/evm/precompile/test/export.go @@ -56,7 +56,7 @@ func SetupWasmContracts(deps *evmtest.TestDeps, s *suite.Suite) ( msgArgsBz, err := json.Marshal(m.Msg) s.NoError(err) - callArgs := []any{m.Admin, m.CodeID, msgArgsBz, m.Label, []precompile.WasmBankCoin{}} + callArgs := []interface{}{m.Admin, m.CodeID, msgArgsBz, m.Label, []precompile.WasmBankCoin{}} input, err := embeds.SmartContract_Wasm.ABI.Pack( string(precompile.WasmMethod_instantiate), callArgs..., @@ -157,7 +157,7 @@ func AssertWasmCounterState( } `) - callArgs := []any{ + callArgs := []interface{}{ // string memory contractAddr wasmContract.String(), // bytes memory req @@ -181,7 +181,7 @@ func AssertWasmCounterState( err = embeds.SmartContract_Wasm.ABI.UnpackIntoInterface( // Since there's only one return value, don't unpack as a slice. // If there were two or more return values, we'd use - // &[]any{...} + // &[]interface{}{...} &queryResp, string(precompile.WasmMethod_query), ethTxResp.Ret, @@ -283,7 +283,7 @@ func IncrementWasmCounterWithExecuteMulti( } s.Require().Len(executeMsgs, int(times)) // sanity check assertion - callArgs := []any{ + callArgs := []interface{}{ executeMsgs, } input, err := embeds.SmartContract_Wasm.ABI.Pack( @@ -338,7 +338,7 @@ func IncrementWasmCounterWithExecuteMultiViaVMCall( } s.Require().Len(executeMsgs, int(times)) // sanity check assertion - callArgs := []any{ + callArgs := []interface{}{ executeMsgs, } input, err := embeds.SmartContract_Wasm.ABI.Pack( diff --git a/x/evm/precompile/wasm.go b/x/evm/precompile/wasm.go index 01106c9d8..228b965e9 100644 --- a/x/evm/precompile/wasm.go +++ b/x/evm/precompile/wasm.go @@ -11,7 +11,6 @@ import ( "github.com/NibiruChain/nibiru/v2/x/evm/embeds" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" - gethabi "github.com/ethereum/go-ethereum/accounts/abi" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" ) @@ -37,27 +36,26 @@ func (p precompileWasm) Run( defer func() { err = ErrPrecompileRun(err, p) }() - start, err := OnRunStart(evm, contract, p.ABI()) + startResult, err := OnRunStart(evm, contract.Input, embeds.SmartContract_Wasm.ABI) if err != nil { return nil, err } - method := start.Method - switch PrecompileMethod(method.Name) { + switch PrecompileMethod(startResult.Method.Name) { case WasmMethod_execute: - bz, err = p.execute(start, contract.CallerAddress, readonly) + bz, err = p.execute(startResult, contract.CallerAddress, readonly) case WasmMethod_query: - bz, err = p.query(start, contract) + bz, err = p.query(startResult, contract) case WasmMethod_instantiate: - bz, err = p.instantiate(start, contract.CallerAddress, readonly) + bz, err = p.instantiate(startResult, contract.CallerAddress, readonly) case WasmMethod_executeMulti: - bz, err = p.executeMulti(start, contract.CallerAddress, readonly) + bz, err = p.executeMulti(startResult, contract.CallerAddress, readonly) case WasmMethod_queryRaw: - bz, err = p.queryRaw(start, contract) + bz, err = p.queryRaw(startResult, contract) default: // Note that this code path should be impossible to reach since // "DecomposeInput" parses methods directly from the ABI. - err = fmt.Errorf("invalid method called with name \"%s\"", method.Name) + err = fmt.Errorf("invalid method called with name \"%s\"", startResult.Method.Name) return } if err != nil { @@ -74,13 +72,9 @@ func (p precompileWasm) Address() gethcommon.Address { return PrecompileAddr_Wasm } -func (p precompileWasm) ABI() *gethabi.ABI { - return embeds.SmartContract_Wasm.ABI -} - // RequiredGas calculates the cost of calling the precompile in gas units. func (p precompileWasm) RequiredGas(input []byte) (gasCost uint64) { - return requiredGas(input, p.ABI()) + return requiredGas(input, embeds.SmartContract_Wasm.ABI) } // Wasm: A struct embedding keepers for read and write operations in Wasm, such @@ -123,7 +117,7 @@ func (p precompileWasm) execute( caller gethcommon.Address, readOnly bool, ) (bz []byte, err error) { - method, args, ctx := start.Method, start.Args, start.Ctx + method, args, ctx := start.Method, start.Args, start.CacheCtx defer func() { if err != nil { err = ErrMethodCalled(method, err) @@ -133,12 +127,12 @@ func (p precompileWasm) execute( return nil, errors.New("wasm execute cannot be called in read-only mode") } - wasmContract, msgArgs, funds, err := p.parseExecuteArgs(args) + wasmContract, msgArgsBz, funds, err := p.parseExecuteArgs(args) if err != nil { err = ErrInvalidArgs(err) return } - data, err := p.Wasm.Execute(ctx, wasmContract, eth.EthAddrToNibiruAddr(caller), msgArgs, funds) + data, err := p.Wasm.Execute(ctx, wasmContract, eth.EthAddrToNibiruAddr(caller), msgArgsBz, funds) if err != nil { return } @@ -161,7 +155,7 @@ func (p precompileWasm) query( start OnRunStartResult, contract *vm.Contract, ) (bz []byte, err error) { - method, args, ctx := start.Method, start.Args, start.Ctx + method, args, ctx := start.Method, start.Args, start.CacheCtx defer func() { if err != nil { err = ErrMethodCalled(method, err) @@ -170,6 +164,7 @@ func (p precompileWasm) query( if err := assertContractQuery(contract); err != nil { return bz, err } + wasmContract, req, err := p.parseQueryArgs(args) if err != nil { err = ErrInvalidArgs(err) @@ -206,7 +201,7 @@ func (p precompileWasm) instantiate( caller gethcommon.Address, readOnly bool, ) (bz []byte, err error) { - method, args, ctx := start.Method, start.Args, start.Ctx + method, args, ctx := start.Method, start.Args, start.CacheCtx defer func() { if err != nil { err = ErrMethodCalled(method, err) @@ -257,7 +252,7 @@ func (p precompileWasm) executeMulti( caller gethcommon.Address, readOnly bool, ) (bz []byte, err error) { - method, args, ctx := start.Method, start.Args, start.Ctx + method, args, ctx := start.Method, start.Args, start.CacheCtx defer func() { if err != nil { err = ErrMethodCalled(method, err) @@ -321,7 +316,7 @@ func (p precompileWasm) queryRaw( start OnRunStartResult, contract *vm.Contract, ) (bz []byte, err error) { - method, args, ctx := start.Method, start.Args, start.Ctx + method, args, ctx := start.Method, start.Args, start.CacheCtx defer func() { if err != nil { err = ErrMethodCalled(method, err) diff --git a/x/evm/precompile/wasm_parse.go b/x/evm/precompile/wasm_parse.go index 5a3014569..6e8047eaa 100644 --- a/x/evm/precompile/wasm_parse.go +++ b/x/evm/precompile/wasm_parse.go @@ -21,7 +21,7 @@ type WasmBankCoin struct { // ```solidity // BankCoin[] memory funds // ``` -func parseFundsArg(arg any) (funds sdk.Coins, err error) { +func parseFundsArg(arg interface{}) (funds sdk.Coins, err error) { if arg == nil { return funds, nil } @@ -67,7 +67,7 @@ func parseContractAddrArg(arg any) (addr sdk.AccAddress, err error) { return addr, nil } -func (p precompileWasm) parseInstantiateArgs(args []any, sender string) ( +func (p precompileWasm) parseInstantiateArgs(args []interface{}, sender string) ( txMsg wasm.MsgInstantiateContract, err error, ) { @@ -124,7 +124,7 @@ func (p precompileWasm) parseInstantiateArgs(args []any, sender string) ( return txMsg, txMsg.ValidateBasic() } -func (p precompileWasm) parseExecuteArgs(args []any) ( +func (p precompileWasm) parseExecuteArgs(args []interface{}) ( wasmContract sdk.AccAddress, msgArgs []byte, funds sdk.Coins, @@ -135,6 +135,7 @@ func (p precompileWasm) parseExecuteArgs(args []any) ( return } + // contract address argIdx := 0 contractAddrStr, ok := args[argIdx].(string) if !ok { @@ -149,6 +150,7 @@ func (p precompileWasm) parseExecuteArgs(args []any) ( return } + // msg args argIdx++ msgArgs, ok = args[argIdx].([]byte) if !ok { @@ -161,6 +163,7 @@ func (p precompileWasm) parseExecuteArgs(args []any) ( return } + // funds argIdx++ funds, e := parseFundsArg(args[argIdx]) if e != nil { @@ -171,7 +174,7 @@ func (p precompileWasm) parseExecuteArgs(args []any) ( return contractAddr, msgArgs, funds, nil } -func (p precompileWasm) parseQueryArgs(args []any) ( +func (p precompileWasm) parseQueryArgs(args []interface{}) ( wasmContract sdk.AccAddress, req wasm.RawContractMessage, err error, @@ -189,7 +192,11 @@ func (p precompileWasm) parseQueryArgs(args []any) ( } argsIdx++ - reqBz := args[argsIdx].([]byte) + reqBz, ok := args[argsIdx].([]byte) + if !ok { + err = ErrArgTypeValidation("bytes req", args[argsIdx]) + return + } req = wasm.RawContractMessage(reqBz) if e := req.ValidateBasic(); e != nil { err = e @@ -199,7 +206,7 @@ func (p precompileWasm) parseQueryArgs(args []any) ( return wasmContract, req, nil } -func (p precompileWasm) parseExecuteMultiArgs(args []any) ( +func (p precompileWasm) parseExecuteMultiArgs(args []interface{}) ( wasmExecMsgs []struct { ContractAddr string `json:"contractAddr"` MsgArgs []byte `json:"msgArgs"` From 78c835ac45709f0d219c202ef09c1ff012173ef1 Mon Sep 17 00:00:00 2001 From: Kevin Yang <5478483+k-yang@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:00:30 -0700 Subject: [PATCH 15/21] refactor: clean up oracle precompile --- x/evm/precompile/oracle.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/x/evm/precompile/oracle.go b/x/evm/precompile/oracle.go index 81f19255a..2545f4eea 100644 --- a/x/evm/precompile/oracle.go +++ b/x/evm/precompile/oracle.go @@ -38,21 +38,19 @@ func (p precompileOracle) Run( defer func() { err = ErrPrecompileRun(err, p) }() - res, err := OnRunStart(evm, contract.Input, embeds.SmartContract_Oracle.ABI) + startResult, err := OnRunStart(evm, contract.Input, embeds.SmartContract_Oracle.ABI) if err != nil { return nil, err } - method, args, ctx := res.Method, res.Args, res.CacheCtx + method, args, ctx := startResult.Method, startResult.Args, startResult.CacheCtx switch PrecompileMethod(method.Name) { case OracleMethod_queryExchangeRate: - bz, err = p.queryExchangeRate(ctx, method, args, readonly) + return p.queryExchangeRate(ctx, method, args) default: err = fmt.Errorf("invalid method called with name \"%s\"", method.Name) return } - - return } func PrecompileOracle(keepers keepers.PublicKeepers) vm.PrecompiledContract { @@ -69,9 +67,8 @@ func (p precompileOracle) queryExchangeRate( ctx sdk.Context, method *gethabi.Method, args []interface{}, - readOnly bool, ) (bz []byte, err error) { - pair, err := p.decomposeQueryExchangeRateArgs(args) + pair, err := p.parseQueryExchangeRateArgs(args) if err != nil { return nil, err } @@ -88,7 +85,7 @@ func (p precompileOracle) queryExchangeRate( return method.Outputs.Pack(price.String()) } -func (p precompileOracle) decomposeQueryExchangeRateArgs(args []interface{}) ( +func (p precompileOracle) parseQueryExchangeRateArgs(args []interface{}) ( pair string, err error, ) { From d7db81b5b5dc2e278a0419c0461c0f46aaba3de4 Mon Sep 17 00:00:00 2001 From: Kevin Yang <5478483+k-yang@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:04:45 -0700 Subject: [PATCH 16/21] fix: import --- x/evm/precompile/test/export.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/x/evm/precompile/test/export.go b/x/evm/precompile/test/export.go index 9acba7932..ca60a9c73 100644 --- a/x/evm/precompile/test/export.go +++ b/x/evm/precompile/test/export.go @@ -8,8 +8,6 @@ import ( "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" sdk "github.com/cosmos/cosmos-sdk/types" From 7236e22cee7134dba2daeba10d9032be86c75d3e Mon Sep 17 00:00:00 2001 From: Kevin Yang <5478483+k-yang@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:15:37 -0700 Subject: [PATCH 17/21] Delete .gitignore --- x/evm/precompile/.gitignore | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 x/evm/precompile/.gitignore diff --git a/x/evm/precompile/.gitignore b/x/evm/precompile/.gitignore deleted file mode 100644 index e8c12ff4f..000000000 --- a/x/evm/precompile/.gitignore +++ /dev/null @@ -1,17 +0,0 @@ -node_modules -.env - -# Hardhat files -/cache -/artifacts - -# TypeChain files -/typechain -/typechain-types - -# solidity-coverage files -/coverage -/coverage.json - -# Hardhat Ignition default folder for deployments against a local node -ignition/deployments/chain-31337 From f6cccc755d5ac8ec711439688e0822c7bfc8f128 Mon Sep 17 00:00:00 2001 From: Kevin Yang <5478483+k-yang@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:16:23 -0700 Subject: [PATCH 18/21] Update CHANGELOG.md --- CHANGELOG.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c99138718..0e8fc50c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,9 +63,9 @@ committed as expected, fixes the `StateDB.Commit` to follow its guidelines more closely, and solves for a critical state inconsistency producible from the FunToken.sol precompiled contract. It also aligns the precompiles to use consistent setup and dynamic gas calculations, addressing the following tickets. - - https://github.com/NibiruChain/nibiru/issues/2083 - - https://github.com/code-423n4/2024-10-nibiru-zenith/issues/43 - - https://github.com/code-423n4/2024-10-nibiru-zenith/issues/47 + - + - + - - [#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 @@ -158,6 +158,7 @@ reverts inside of a try-catch. - [#2060](https://github.com/NibiruChain/nibiru/pull/2060) - fix(evm-precompiles): add assertNumArgs validation - [#2056](https://github.com/NibiruChain/nibiru/pull/2056) - feat(evm): add oracle precompile - [#2065](https://github.com/NibiruChain/nibiru/pull/2065) - refactor(evm)!: Refactor out dead code from the evm.Params +- [#2100](https://github.com/NibiruChain/nibiru/pull/2100) - refactor: cleanup statedb and precompile sections ### State Machine Breaking (Other) From 2dc5646681bd5e24165cfd66acfb75240a680a70 Mon Sep 17 00:00:00 2001 From: Kevin Yang <5478483+k-yang@users.noreply.github.com> Date: Wed, 30 Oct 2024 08:59:55 -0700 Subject: [PATCH 19/21] refactor: replace interface{} with any --- app/server/config/server_config.go | 2 +- cmd/nibid/cmd/base64.go | 2 +- cmd/nibid/cmd/decode_base64.go | 8 +++--- eth/eip712/eip712_legacy.go | 10 ++++---- eth/eip712/encoding_legacy.go | 4 +-- eth/eip712/message.go | 4 +-- eth/rpc/backend/blocks.go | 8 +++--- eth/rpc/backend/node_info.go | 4 +-- eth/rpc/backend/tracing.go | 8 +++--- eth/rpc/backend/utils.go | 2 +- eth/rpc/rpc.go | 6 ++--- eth/rpc/rpcapi/debugapi/api.go | 4 +-- eth/rpc/rpcapi/eth_api.go | 20 +++++++-------- eth/rpc/rpcapi/eth_filters_api.go | 2 +- eth/rpc/rpcapi/websockets.go | 34 ++++++++++++------------- x/common/testutil/nullify.go | 2 +- x/common/testutil/testnetwork/logger.go | 4 +-- x/devgas/v1/types/params.go | 6 ++--- x/epochs/types/identifier.go | 2 +- x/evm/errors.go | 2 +- x/evm/keeper/grpc_query.go | 4 +-- x/evm/params.go | 4 +-- x/evm/precompile/funtoken.go | 2 +- x/evm/precompile/oracle.go | 4 +-- x/evm/precompile/precompile.go | 4 +-- x/evm/precompile/test/export.go | 10 ++++---- x/evm/precompile/wasm_parse.go | 10 ++++---- x/evm/vmtracer.go | 4 +-- x/inflation/types/params.go | 12 ++++----- x/oracle/types/hash.go | 2 +- x/tokenfactory/types/codec.go | 2 +- 31 files changed, 96 insertions(+), 96 deletions(-) diff --git a/app/server/config/server_config.go b/app/server/config/server_config.go index b72638ed2..f393983a9 100644 --- a/app/server/config/server_config.go +++ b/app/server/config/server_config.go @@ -174,7 +174,7 @@ type TLSConfig struct { // AppConfig helps to override default appConfig template and configs. // return "", nil if no custom configuration is required for the application. -func AppConfig(denom string) (string, interface{}) { +func AppConfig(denom string) (string, any) { // Optionally allow the chain developer to overwrite the SDK's default // server config. customAppConfig := DefaultConfig() diff --git a/cmd/nibid/cmd/base64.go b/cmd/nibid/cmd/base64.go index 5f235d3ea..ba5685aff 100644 --- a/cmd/nibid/cmd/base64.go +++ b/cmd/nibid/cmd/base64.go @@ -33,7 +33,7 @@ func GetBuildWasmMsg() *cobra.Command { Value string `json:"value,omitempty"` } - js, err := json.Marshal(map[string]interface{}{ + js, err := json.Marshal(map[string]any{ "stargate": stargateMessage{ TypeURL: anyMsg.TypeUrl, Value: base64.StdEncoding.EncodeToString(anyMsg.Value), diff --git a/cmd/nibid/cmd/decode_base64.go b/cmd/nibid/cmd/decode_base64.go index 3de3706f2..8625070c3 100644 --- a/cmd/nibid/cmd/decode_base64.go +++ b/cmd/nibid/cmd/decode_base64.go @@ -27,7 +27,7 @@ import ( // from the provided JSON data. // - err error: An error object, which is nil if the operation is successful. func YieldStargateMsgs(jsonBz []byte) (sgMsgs []wasmvm.StargateMsg, err error) { - var data interface{} + var data any if err := json.Unmarshal(jsonBz, &data); err != nil { return sgMsgs, err } @@ -50,7 +50,7 @@ func YieldStargateMsgs(jsonBz []byte) (sgMsgs []wasmvm.StargateMsg, err error) { // encoded base 64 string. func parseStargateMsgs(jsonData any, msgs *[]wasmvm.StargateMsg) { switch v := jsonData.(type) { - case map[string]interface{}: + case map[string]any: if typeURL, ok := v["type_url"].(string); ok { if value, ok := v["value"].(string); ok { *msgs = append(*msgs, wasmvm.StargateMsg{ @@ -62,7 +62,7 @@ func parseStargateMsgs(jsonData any, msgs *[]wasmvm.StargateMsg) { for _, value := range v { parseStargateMsgs(value, msgs) } - case []interface{}: + case []any: for _, value := range v { parseStargateMsgs(value, msgs) } @@ -93,7 +93,7 @@ func DecodeBase64StargateMsgs( ) (newSgMsgs []StargateMsgDecoded, err error) { codec := clientCtx.Codec - var data interface{} + var data any if err := json.Unmarshal(jsonBz, &data); err != nil { return newSgMsgs, fmt.Errorf( "failed to decode stargate msgs due to invalid JSON: %w", err) diff --git a/eth/eip712/eip712_legacy.go b/eth/eip712/eip712_legacy.go index c1d0b1b30..b50f10e5f 100644 --- a/eth/eip712/eip712_legacy.go +++ b/eth/eip712/eip712_legacy.go @@ -38,7 +38,7 @@ func LegacyWrapTxToTypedData( data []byte, feeDelegation *FeeDelegationOptions, ) (apitypes.TypedData, error) { - txData := make(map[string]interface{}) + txData := make(map[string]any) if err := json.Unmarshal(data, &txData); err != nil { return apitypes.TypedData{}, errorsmod.Wrap(errortypes.ErrJSONUnmarshal, "failed to JSON unmarshal data") @@ -58,7 +58,7 @@ func LegacyWrapTxToTypedData( } if feeDelegation != nil { - feeInfo, ok := txData["fee"].(map[string]interface{}) + feeInfo, ok := txData["fee"].(map[string]any) if !ok { return apitypes.TypedData{}, errorsmod.Wrap(errortypes.ErrInvalidType, "cannot parse fee from tx data") } @@ -139,7 +139,7 @@ func extractMsgTypes(cdc codectypes.AnyUnpacker, msgTypeName string, msg sdk.Msg return rootTypes, nil } -func walkFields(cdc codectypes.AnyUnpacker, typeMap apitypes.Types, rootType string, in interface{}) (err error) { +func walkFields(cdc codectypes.AnyUnpacker, typeMap apitypes.Types, rootType string, in any) (err error) { defer doRecover(&err) t := reflect.TypeOf(in) @@ -161,8 +161,8 @@ func walkFields(cdc codectypes.AnyUnpacker, typeMap apitypes.Types, rootType str } type CosmosAnyWrapper struct { - Type string `json:"type"` - Value interface{} `json:"value"` + Type string `json:"type"` + Value any `json:"value"` } // legacyTraverseFields: Recursively inspects the fields of a given diff --git a/eth/eip712/encoding_legacy.go b/eth/eip712/encoding_legacy.go index 805eb48dd..23f943eaf 100644 --- a/eth/eip712/encoding_legacy.go +++ b/eth/eip712/encoding_legacy.go @@ -17,8 +17,8 @@ import ( ) type aminoMessage struct { - Type string `json:"type"` - Value interface{} `json:"value"` + Type string `json:"type"` + Value any `json:"value"` } // LegacyGetEIP712BytesForMsg returns the EIP-712 object bytes for the given SignDoc bytes by decoding the bytes into diff --git a/eth/eip712/message.go b/eth/eip712/message.go index 6a8577c22..176d90b56 100644 --- a/eth/eip712/message.go +++ b/eth/eip712/message.go @@ -14,7 +14,7 @@ import ( type eip712MessagePayload struct { payload gjson.Result numPayloadMsgs int - message map[string]interface{} + message map[string]any } const ( @@ -34,7 +34,7 @@ func createEIP712MessagePayload(data []byte) (eip712MessagePayload, error) { return eip712MessagePayload{}, errorsmod.Wrap(err, "failed to flatten payload JSON messages") } - message, ok := payload.Value().(map[string]interface{}) + message, ok := payload.Value().(map[string]any) if !ok { return eip712MessagePayload{}, errorsmod.Wrap(errortypes.ErrInvalidType, "failed to parse JSON as map") } diff --git a/eth/rpc/backend/blocks.go b/eth/rpc/backend/blocks.go index a64e73e2a..7bc5f4b97 100644 --- a/eth/rpc/backend/blocks.go +++ b/eth/rpc/backend/blocks.go @@ -57,7 +57,7 @@ func (b *Backend) BlockNumber() (hexutil.Uint64, error) { // GetBlockByNumber returns the JSON-RPC compatible Ethereum block identified by // block number. Depending on fullTx it either returns the full transaction // objects or if false only the hashes of the transactions. -func (b *Backend) GetBlockByNumber(blockNum rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { +func (b *Backend) GetBlockByNumber(blockNum rpc.BlockNumber, fullTx bool) (map[string]any, error) { resBlock, err := b.TendermintBlockByNumber(blockNum) if err != nil { return nil, nil @@ -85,7 +85,7 @@ func (b *Backend) GetBlockByNumber(blockNum rpc.BlockNumber, fullTx bool) (map[s // GetBlockByHash returns the JSON-RPC compatible Ethereum block identified by // hash. -func (b *Backend) GetBlockByHash(hash gethcommon.Hash, fullTx bool) (map[string]interface{}, error) { +func (b *Backend) GetBlockByHash(hash gethcommon.Hash, fullTx bool) (map[string]any, error) { resBlock, err := b.TendermintBlockByHash(hash) if err != nil { return nil, err @@ -348,8 +348,8 @@ func (b *Backend) RPCBlockFromTendermintBlock( resBlock *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults, fullTx bool, -) (map[string]interface{}, error) { - ethRPCTxs := []interface{}{} +) (map[string]any, error) { + ethRPCTxs := []any{} block := resBlock.Block baseFeeWei, err := b.BaseFeeWei(blockRes) diff --git a/eth/rpc/backend/node_info.go b/eth/rpc/backend/node_info.go index 402578dd5..e95488b2a 100644 --- a/eth/rpc/backend/node_info.go +++ b/eth/rpc/backend/node_info.go @@ -39,7 +39,7 @@ func (b *Backend) Accounts() ([]gethcommon.Address, error) { // - highestBlock: block number of the highest block header this node has received from peers // - pulledStates: number of state entries processed until now // - knownStates: number of known state entries that still need to be pulled -func (b *Backend) Syncing() (interface{}, error) { +func (b *Backend) Syncing() (any, error) { status, err := b.clientCtx.Client.Status(b.ctx) if err != nil { return false, err @@ -49,7 +49,7 @@ func (b *Backend) Syncing() (interface{}, error) { return false, nil } - return map[string]interface{}{ + return map[string]any{ "startingBlock": hexutil.Uint64(status.SyncInfo.EarliestBlockHeight), "currentBlock": hexutil.Uint64(status.SyncInfo.LatestBlockHeight), // "highestBlock": nil, // NA diff --git a/eth/rpc/backend/tracing.go b/eth/rpc/backend/tracing.go index 4a20557cd..94bb8fbba 100644 --- a/eth/rpc/backend/tracing.go +++ b/eth/rpc/backend/tracing.go @@ -18,7 +18,7 @@ import ( // TraceTransaction returns the structured logs created during the execution of EVM // and returns them as a JSON object. -func (b *Backend) TraceTransaction(hash gethcommon.Hash, config *evm.TraceConfig) (interface{}, error) { +func (b *Backend) TraceTransaction(hash gethcommon.Hash, config *evm.TraceConfig) (any, error) { // Get transaction by hash transaction, err := b.GetTxByEthHash(hash) if err != nil { @@ -124,7 +124,7 @@ func (b *Backend) TraceTransaction(hash gethcommon.Hash, config *evm.TraceConfig // Response format is unknown due to custom tracer config param // More information can be found here https://geth.ethereum.org/docs/dapp/tracing-filtered - var decodedResult interface{} + var decodedResult any err = json.Unmarshal(traceResult.Data, &decodedResult) if err != nil { return nil, err @@ -219,7 +219,7 @@ func (b *Backend) TraceCall( txArgs evm.JsonTxArgs, contextBlock rpc.BlockNumber, config *evm.TraceConfig, -) (interface{}, error) { +) (any, error) { blk, err := b.TendermintBlockByNumber(contextBlock) if err != nil { b.logger.Debug("block not found", "contextBlock", contextBlock) @@ -253,7 +253,7 @@ func (b *Backend) TraceCall( if err != nil { return nil, err } - var decodedResult interface{} + var decodedResult any err = json.Unmarshal(traceResult.Data, &decodedResult) if err != nil { return nil, err diff --git a/eth/rpc/backend/utils.go b/eth/rpc/backend/utils.go index 23ce2f410..bb689bcce 100644 --- a/eth/rpc/backend/utils.go +++ b/eth/rpc/backend/utils.go @@ -109,7 +109,7 @@ func (b *Backend) getAccountNonce(accAddr common.Address, pending bool, height i // See eth_feeHistory method for more details of the return format. func (b *Backend) retrieveEVMTxFeesFromBlock( tendermintBlock *tmrpctypes.ResultBlock, - ethBlock *map[string]interface{}, + ethBlock *map[string]any, rewardPercentiles []float64, tendermintBlockResult *tmrpctypes.ResultBlockResults, targetOneFeeHistory *rpc.OneFeeHistory, diff --git a/eth/rpc/rpc.go b/eth/rpc/rpc.go index de5a97991..c6c0891de 100644 --- a/eth/rpc/rpc.go +++ b/eth/rpc/rpc.go @@ -114,9 +114,9 @@ func BlockMaxGasFromConsensusParams( // transactions. func FormatBlock( header tmtypes.Header, size int, gasLimit int64, - gasUsed *big.Int, transactions []interface{}, bloom gethcore.Bloom, + gasUsed *big.Int, transactions []any, bloom gethcore.Bloom, validatorAddr gethcommon.Address, baseFee *big.Int, -) map[string]interface{} { +) map[string]any { var transactionsRoot gethcommon.Hash if len(transactions) == 0 { transactionsRoot = gethcore.EmptyRootHash @@ -124,7 +124,7 @@ func FormatBlock( transactionsRoot = gethcommon.BytesToHash(header.DataHash) } - result := map[string]interface{}{ + result := map[string]any{ "number": hexutil.Uint64(header.Height), "hash": hexutil.Bytes(header.Hash()), "parentHash": gethcommon.BytesToHash(header.LastBlockID.Hash.Bytes()), diff --git a/eth/rpc/rpcapi/debugapi/api.go b/eth/rpc/rpcapi/debugapi/api.go index c70673273..7ce3fa345 100644 --- a/eth/rpc/rpcapi/debugapi/api.go +++ b/eth/rpc/rpcapi/debugapi/api.go @@ -65,7 +65,7 @@ func NewImplDebugAPI( // TraceTransaction returns the structured logs created during the execution of EVM // and returns them as a JSON object. -func (a *DebugAPI) TraceTransaction(hash common.Hash, config *evm.TraceConfig) (interface{}, error) { +func (a *DebugAPI) TraceTransaction(hash common.Hash, config *evm.TraceConfig) (any, error) { a.logger.Debug("debug_traceTransaction", "hash", hash) return a.backend.TraceTransaction(hash, config) } @@ -115,7 +115,7 @@ func (a *DebugAPI) TraceCall( args evm.JsonTxArgs, blockNrOrHash rpc.BlockNumberOrHash, config *evm.TraceConfig, -) (interface{}, error) { +) (any, error) { a.logger.Debug("debug_traceCall", args.String(), "block number or hash", blockNrOrHash) // Get Tendermint Block diff --git a/eth/rpc/rpcapi/eth_api.go b/eth/rpc/rpcapi/eth_api.go index 62590347f..3ada30358 100644 --- a/eth/rpc/rpcapi/eth_api.go +++ b/eth/rpc/rpcapi/eth_api.go @@ -30,8 +30,8 @@ type IEthAPI interface { // // Retrieves information from a particular block in the blockchain. BlockNumber() (hexutil.Uint64, error) - GetBlockByNumber(ethBlockNum rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) - GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) + GetBlockByNumber(ethBlockNum rpc.BlockNumber, fullTx bool) (map[string]any, error) + GetBlockByHash(hash common.Hash, fullTx bool) (map[string]any, error) GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Uint GetBlockTransactionCountByNumber(blockNum rpc.BlockNumber) *hexutil.Uint @@ -97,15 +97,15 @@ type IEthAPI interface { // and replaced by a canonical block instead. GetUncleByBlockHashAndIndex( hash common.Hash, idx hexutil.Uint, - ) map[string]interface{} + ) map[string]any GetUncleByBlockNumberAndIndex( number, idx hexutil.Uint, - ) map[string]interface{} + ) map[string]any GetUncleCountByBlockHash(hash common.Hash) hexutil.Uint GetUncleCountByBlockNumber(blockNum rpc.BlockNumber) hexutil.Uint // Other - Syncing() (interface{}, error) + Syncing() (any, error) GetTransactionLogs(txHash common.Hash) ([]*gethcore.Log, error) FillTransaction( args evm.JsonTxArgs, @@ -144,13 +144,13 @@ func (e *EthAPI) BlockNumber() (hexutil.Uint64, error) { } // GetBlockByNumber returns the block identified by number. -func (e *EthAPI) GetBlockByNumber(ethBlockNum rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { +func (e *EthAPI) GetBlockByNumber(ethBlockNum rpc.BlockNumber, fullTx bool) (map[string]any, error) { e.logger.Debug("eth_getBlockByNumber", "number", ethBlockNum, "full", fullTx) return e.backend.GetBlockByNumber(ethBlockNum, fullTx) } // GetBlockByHash returns the block identified by hash. -func (e *EthAPI) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) { +func (e *EthAPI) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]any, error) { e.logger.Debug("eth_getBlockByHash", "hash", hash.Hex(), "full", fullTx) return e.backend.GetBlockByHash(hash, fullTx) } @@ -359,7 +359,7 @@ func (e *EthAPI) ChainId() (*hexutil.Big, error) { //nolint // Always returns nil. func (e *EthAPI) GetUncleByBlockHashAndIndex( _ common.Hash, _ hexutil.Uint, -) map[string]interface{} { +) map[string]any { return nil } @@ -367,7 +367,7 @@ func (e *EthAPI) GetUncleByBlockHashAndIndex( // index. Always returns nil. func (e *EthAPI) GetUncleByBlockNumberAndIndex( _, _ hexutil.Uint, -) map[string]interface{} { +) map[string]any { return nil } @@ -396,7 +396,7 @@ func (e *EthAPI) GetUncleCountByBlockNumber(_ rpc.BlockNumber) hexutil.Uint { // - highestBlock: block number of the highest block header this node has received from peers // - pulledStates: number of state entries processed until now // - knownStates: number of known state entries that still need to be pulled -func (e *EthAPI) Syncing() (interface{}, error) { +func (e *EthAPI) Syncing() (any, error) { e.logger.Debug("eth_syncing") return e.backend.Syncing() } diff --git a/eth/rpc/rpcapi/eth_filters_api.go b/eth/rpc/rpcapi/eth_filters_api.go index 782014bb5..eca8dd27c 100644 --- a/eth/rpc/rpcapi/eth_filters_api.go +++ b/eth/rpc/rpcapi/eth_filters_api.go @@ -621,7 +621,7 @@ func (api *FiltersAPI) GetFilterLogs(ctx context.Context, id gethrpc.ID) ([]*get // // This function implements the "eth_getFilterChanges" JSON-RPC service method. // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterchanges -func (api *FiltersAPI) GetFilterChanges(id gethrpc.ID) (interface{}, error) { +func (api *FiltersAPI) GetFilterChanges(id gethrpc.ID) (any, error) { api.logger.Debug("eth_getFilterChanges") api.filtersMu.Lock() defer api.filtersMu.Unlock() diff --git a/eth/rpc/rpcapi/websockets.go b/eth/rpc/rpcapi/websockets.go index 68fb7eb9e..530dd39d7 100644 --- a/eth/rpc/rpcapi/websockets.go +++ b/eth/rpc/rpcapi/websockets.go @@ -41,9 +41,9 @@ type WebsocketsServer interface { } type SubscriptionResponseJSON struct { - Jsonrpc string `json:"jsonrpc"` - Result interface{} `json:"result"` - ID float64 `json:"id"` + Jsonrpc string `json:"jsonrpc"` + Result any `json:"result"` + ID float64 `json:"id"` } type SubscriptionNotification struct { @@ -53,8 +53,8 @@ type SubscriptionNotification struct { } type SubscriptionResult struct { - Subscription gethrpc.ID `json:"subscription"` - Result interface{} `json:"result"` + Subscription gethrpc.ID `json:"subscription"` + Result any `json:"result"` } type ErrorResponseJSON struct { @@ -157,7 +157,7 @@ type wsConn struct { mux *sync.Mutex } -func (w *wsConn) WriteJSON(v interface{}) error { +func (w *wsConn) WriteJSON(v any) error { w.mux.Lock() defer w.mux.Unlock() @@ -203,7 +203,7 @@ func (s *websocketsServer) readLoop(wsConn *wsConn) { continue } - var msg map[string]interface{} + var msg map[string]any if err = json.Unmarshal(mb, &msg); err != nil { s.sendErrResponse(wsConn, err.Error()) continue @@ -299,8 +299,8 @@ func (s *websocketsServer) readLoop(wsConn *wsConn) { } // tcpGetAndSendResponse sends error response to client if params is invalid -func (s *websocketsServer) getParamsAndCheckValid(msg map[string]interface{}, wsConn *wsConn) ([]interface{}, bool) { - params, ok := msg["params"].([]interface{}) +func (s *websocketsServer) getParamsAndCheckValid(msg map[string]any, wsConn *wsConn) ([]any, bool) { + params, ok := msg["params"].([]any) if !ok { s.sendErrResponse(wsConn, "invalid parameters") return nil, false @@ -336,7 +336,7 @@ func (s *websocketsServer) tcpGetAndSendResponse(wsConn *wsConn, mb []byte) erro return errors.Wrap(err, "could not read body from response") } - var wsSend interface{} + var wsSend any err = json.Unmarshal(body, &wsSend) if err != nil { return errors.Wrap(err, "failed to unmarshal rest-server response") @@ -362,7 +362,7 @@ func newPubSubAPI(clientCtx client.Context, logger log.Logger, tmWSClient *rpccl } } -func (api *pubSubAPI) subscribe(wsConn *wsConn, subID gethrpc.ID, params []interface{}) (pubsub.UnsubscribeFunc, error) { +func (api *pubSubAPI) subscribe(wsConn *wsConn, subID gethrpc.ID, params []any) (pubsub.UnsubscribeFunc, error) { method, ok := params[0].(string) if !ok { return nil, errors.New("invalid parameters") @@ -462,11 +462,11 @@ func try(fn func(), l log.Logger, desc string) { fn() } -func (api *pubSubAPI) subscribeLogs(wsConn *wsConn, subID gethrpc.ID, extra interface{}) (pubsub.UnsubscribeFunc, error) { +func (api *pubSubAPI) subscribeLogs(wsConn *wsConn, subID gethrpc.ID, extra any) (pubsub.UnsubscribeFunc, error) { crit := filters.FilterCriteria{} if extra != nil { - params, ok := extra.(map[string]interface{}) + params, ok := extra.(map[string]any) if !ok { err := errors.New("invalid criteria") api.logger.Debug("invalid criteria", "type", fmt.Sprintf("%T", extra)) @@ -475,7 +475,7 @@ func (api *pubSubAPI) subscribeLogs(wsConn *wsConn, subID gethrpc.ID, extra inte if params["address"] != nil { address, isString := params["address"].(string) - addresses, isSlice := params["address"].([]interface{}) + addresses, isSlice := params["address"].([]any) if !isString && !isSlice { err := errors.New("invalid addresses; must be address or array of addresses") api.logger.Debug("invalid addresses", "type", fmt.Sprintf("%T", params["address"])) @@ -502,7 +502,7 @@ func (api *pubSubAPI) subscribeLogs(wsConn *wsConn, subID gethrpc.ID, extra inte } if params["topics"] != nil { - topics, ok := params["topics"].([]interface{}) + topics, ok := params["topics"].([]any) if !ok { err := errors.Errorf("invalid topics: %s", topics) api.logger.Error("invalid topics", "type", fmt.Sprintf("%T", topics)) @@ -511,7 +511,7 @@ func (api *pubSubAPI) subscribeLogs(wsConn *wsConn, subID gethrpc.ID, extra inte crit.Topics = make([][]common.Hash, len(topics)) - addCritTopic := func(topicIdx int, topic interface{}) error { + addCritTopic := func(topicIdx int, topic any) error { tstr, ok := topic.(string) if !ok { err := errors.Errorf("invalid topic: %s", topic) @@ -538,7 +538,7 @@ func (api *pubSubAPI) subscribeLogs(wsConn *wsConn, subID gethrpc.ID, extra inte } // in case we actually have a list of subtopics - subtopicsList, ok := subtopics.([]interface{}) + subtopicsList, ok := subtopics.([]any) if !ok { err := errors.New("invalid subtopics") api.logger.Error("invalid subtopic", "type", fmt.Sprintf("%T", subtopics)) diff --git a/x/common/testutil/nullify.go b/x/common/testutil/nullify.go index 202b59e6f..96fc340f6 100644 --- a/x/common/testutil/nullify.go +++ b/x/common/testutil/nullify.go @@ -16,7 +16,7 @@ var ( // Fill analyze all struct fields and slices with // reflection and initialize the nil and empty slices, // structs, and pointers. -func Fill(x interface{}) interface{} { +func Fill(x any) any { v := reflect.Indirect(reflect.ValueOf(x)) switch v.Kind() { case reflect.Slice: diff --git a/x/common/testutil/testnetwork/logger.go b/x/common/testutil/testnetwork/logger.go index dfdf56d97..7b1206a94 100644 --- a/x/common/testutil/testnetwork/logger.go +++ b/x/common/testutil/testnetwork/logger.go @@ -11,8 +11,8 @@ import ( // Typically, a `testing.T` struct is used as the logger for both the "Network" // and corresponding "Validators". type Logger interface { - Log(args ...interface{}) - Logf(format string, args ...interface{}) + Log(args ...any) + Logf(format string, args ...any) } var _ Logger = (*testing.T)(nil) diff --git a/x/devgas/v1/types/params.go b/x/devgas/v1/types/params.go index 433aa4d43..532436c73 100644 --- a/x/devgas/v1/types/params.go +++ b/x/devgas/v1/types/params.go @@ -28,7 +28,7 @@ func DefaultParams() ModuleParams { } } -func validateBool(i interface{}) error { +func validateBool(i any) error { _, ok := i.(bool) if !ok { return fmt.Errorf("invalid parameter type: %T", i) @@ -37,7 +37,7 @@ func validateBool(i interface{}) error { return nil } -func validateShares(i interface{}) error { +func validateShares(i any) error { v, ok := i.(sdk.Dec) if !ok { @@ -59,7 +59,7 @@ func validateShares(i interface{}) error { return nil } -func validateArray(i interface{}) error { +func validateArray(i any) error { _, ok := i.([]string) if !ok { return fmt.Errorf("invalid parameter type: %T", i) diff --git a/x/epochs/types/identifier.go b/x/epochs/types/identifier.go index 3dd3012cb..714b9dd66 100644 --- a/x/epochs/types/identifier.go +++ b/x/epochs/types/identifier.go @@ -20,7 +20,7 @@ const ( // ValidateEpochIdentifierInterface performs a stateless // validation of the epoch ID interface. -func ValidateEpochIdentifierInterface(i interface{}) error { +func ValidateEpochIdentifierInterface(i any) error { v, ok := i.(string) if !ok { return fmt.Errorf("invalid parameter type: %T", i) diff --git a/x/evm/errors.go b/x/evm/errors.go index 9fc6722ac..87d5c747b 100644 --- a/x/evm/errors.go +++ b/x/evm/errors.go @@ -106,6 +106,6 @@ func (e *RevertError) ErrorCode() int { } // ErrorData returns the hex encoded revert reason. -func (e *RevertError) ErrorData() interface{} { +func (e *RevertError) ErrorData() any { return e.reason } diff --git a/x/evm/keeper/grpc_query.go b/x/evm/keeper/grpc_query.go index 9cc9290e9..4e13254a9 100644 --- a/x/evm/keeper/grpc_query.go +++ b/x/evm/keeper/grpc_query.go @@ -736,7 +736,7 @@ func (k *Keeper) TraceEthTxMsg( traceConfig *evm.TraceConfig, commitMessage bool, tracerJSONConfig json.RawMessage, -) (*interface{}, uint, error) { +) (*any, uint, error) { // Assemble the structured logger or the JavaScript tracer var ( tracer tracers.Tracer @@ -805,7 +805,7 @@ func (k *Keeper) TraceEthTxMsg( return nil, 0, grpcstatus.Error(grpccodes.Internal, err.Error()) } - var result interface{} + var result any result, err = tracer.GetResult() if err != nil { return nil, 0, grpcstatus.Error(grpccodes.Internal, err.Error()) diff --git a/x/evm/params.go b/x/evm/params.go index 66d31e035..e0211d0bb 100644 --- a/x/evm/params.go +++ b/x/evm/params.go @@ -35,7 +35,7 @@ func DefaultParams() Params { } // validateChannels checks if channels ids are valid -func validateChannels(i interface{}) error { +func validateChannels(i any) error { channels, ok := i.([]string) if !ok { return fmt.Errorf("invalid parameter type: %T", i) @@ -76,7 +76,7 @@ func (p Params) IsEVMChannel(channel string) bool { return slices.Contains(p.EVMChannels, channel) } -func validateEIPs(i interface{}) error { +func validateEIPs(i any) error { eips, ok := i.([]int64) if !ok { return fmt.Errorf("invalid EIP slice type: %T", i) diff --git a/x/evm/precompile/funtoken.go b/x/evm/precompile/funtoken.go index 9ad906479..5e364d604 100644 --- a/x/evm/precompile/funtoken.go +++ b/x/evm/precompile/funtoken.go @@ -227,7 +227,7 @@ func SafeSendCoinFromModuleToAccount( return nil } -func (p precompileFunToken) decomposeBankSendArgs(args []interface{}) ( +func (p precompileFunToken) decomposeBankSendArgs(args []any) ( erc20 gethcommon.Address, amount *big.Int, to string, diff --git a/x/evm/precompile/oracle.go b/x/evm/precompile/oracle.go index 2545f4eea..860fa335c 100644 --- a/x/evm/precompile/oracle.go +++ b/x/evm/precompile/oracle.go @@ -66,7 +66,7 @@ type precompileOracle struct { func (p precompileOracle) queryExchangeRate( ctx sdk.Context, method *gethabi.Method, - args []interface{}, + args []any, ) (bz []byte, err error) { pair, err := p.parseQueryExchangeRateArgs(args) if err != nil { @@ -85,7 +85,7 @@ func (p precompileOracle) queryExchangeRate( return method.Outputs.Pack(price.String()) } -func (p precompileOracle) parseQueryExchangeRateArgs(args []interface{}) ( +func (p precompileOracle) parseQueryExchangeRateArgs(args []any) ( pair string, err error, ) { diff --git a/x/evm/precompile/precompile.go b/x/evm/precompile/precompile.go index 90788665f..234a27f4b 100644 --- a/x/evm/precompile/precompile.go +++ b/x/evm/precompile/precompile.go @@ -87,7 +87,7 @@ func methodById(abi *gethabi.ABI, sigdata []byte) (*gethabi.Method, error) { func decomposeInput( abi *gethabi.ABI, input []byte, -) (method *gethabi.Method, args []interface{}, err error) { +) (method *gethabi.Method, args []any, err error) { // ABI method IDs are exactly 4 bytes according to "gethabi.ABI.MethodByID". if len(input) < 4 { err = fmt.Errorf("input \"%s\" too short to extract method ID (less than 4 bytes)", collections.HumanizeBytes(input)) @@ -138,7 +138,7 @@ type PrecompileMethod string type OnRunStartResult struct { // Args contains the decoded (ABI unpacked) arguments passed to the contract // as input. - Args []interface{} + Args []any // CacheCtx is a cached SDK context that allows isolated state // operations to occur that can be reverted by the EVM's [statedb.StateDB]. diff --git a/x/evm/precompile/test/export.go b/x/evm/precompile/test/export.go index ca60a9c73..24700ce85 100644 --- a/x/evm/precompile/test/export.go +++ b/x/evm/precompile/test/export.go @@ -56,7 +56,7 @@ func SetupWasmContracts(deps *evmtest.TestDeps, s *suite.Suite) ( msgArgsBz, err := json.Marshal(m.Msg) s.NoError(err) - callArgs := []interface{}{m.Admin, m.CodeID, msgArgsBz, m.Label, []precompile.WasmBankCoin{}} + callArgs := []any{m.Admin, m.CodeID, msgArgsBz, m.Label, []precompile.WasmBankCoin{}} input, err := embeds.SmartContract_Wasm.ABI.Pack( string(precompile.WasmMethod_instantiate), callArgs..., @@ -157,7 +157,7 @@ func AssertWasmCounterState( } `) - callArgs := []interface{}{ + callArgs := []any{ // string memory contractAddr wasmContract.String(), // bytes memory req @@ -181,7 +181,7 @@ func AssertWasmCounterState( err = embeds.SmartContract_Wasm.ABI.UnpackIntoInterface( // Since there's only one return value, don't unpack as a slice. // If there were two or more return values, we'd use - // &[]interface{}{...} + // &[]any{...} &queryResp, string(precompile.WasmMethod_query), ethTxResp.Ret, @@ -283,7 +283,7 @@ func IncrementWasmCounterWithExecuteMulti( } s.Require().Len(executeMsgs, int(times)) // sanity check assertion - callArgs := []interface{}{ + callArgs := []any{ executeMsgs, } input, err := embeds.SmartContract_Wasm.ABI.Pack( @@ -338,7 +338,7 @@ func IncrementWasmCounterWithExecuteMultiViaVMCall( } s.Require().Len(executeMsgs, int(times)) // sanity check assertion - callArgs := []interface{}{ + callArgs := []any{ executeMsgs, } input, err := embeds.SmartContract_Wasm.ABI.Pack( diff --git a/x/evm/precompile/wasm_parse.go b/x/evm/precompile/wasm_parse.go index 6e8047eaa..97a532753 100644 --- a/x/evm/precompile/wasm_parse.go +++ b/x/evm/precompile/wasm_parse.go @@ -21,7 +21,7 @@ type WasmBankCoin struct { // ```solidity // BankCoin[] memory funds // ``` -func parseFundsArg(arg interface{}) (funds sdk.Coins, err error) { +func parseFundsArg(arg any) (funds sdk.Coins, err error) { if arg == nil { return funds, nil } @@ -67,7 +67,7 @@ func parseContractAddrArg(arg any) (addr sdk.AccAddress, err error) { return addr, nil } -func (p precompileWasm) parseInstantiateArgs(args []interface{}, sender string) ( +func (p precompileWasm) parseInstantiateArgs(args []any, sender string) ( txMsg wasm.MsgInstantiateContract, err error, ) { @@ -124,7 +124,7 @@ func (p precompileWasm) parseInstantiateArgs(args []interface{}, sender string) return txMsg, txMsg.ValidateBasic() } -func (p precompileWasm) parseExecuteArgs(args []interface{}) ( +func (p precompileWasm) parseExecuteArgs(args []any) ( wasmContract sdk.AccAddress, msgArgs []byte, funds sdk.Coins, @@ -174,7 +174,7 @@ func (p precompileWasm) parseExecuteArgs(args []interface{}) ( return contractAddr, msgArgs, funds, nil } -func (p precompileWasm) parseQueryArgs(args []interface{}) ( +func (p precompileWasm) parseQueryArgs(args []any) ( wasmContract sdk.AccAddress, req wasm.RawContractMessage, err error, @@ -206,7 +206,7 @@ func (p precompileWasm) parseQueryArgs(args []interface{}) ( return wasmContract, req, nil } -func (p precompileWasm) parseExecuteMultiArgs(args []interface{}) ( +func (p precompileWasm) parseExecuteMultiArgs(args []any) ( wasmExecMsgs []struct { ContractAddr string `json:"contractAddr"` MsgArgs []byte `json:"msgArgs"` diff --git a/x/evm/vmtracer.go b/x/evm/vmtracer.go index 2a20f2b01..2078bdfe7 100644 --- a/x/evm/vmtracer.go +++ b/x/evm/vmtracer.go @@ -51,8 +51,8 @@ func NewTracer(tracer string, msg core.Message, cfg *params.ChainConfig, height // TxTraceResult is the result of a single transaction trace during a block trace. type TxTraceResult struct { - Result interface{} `json:"result,omitempty"` // Trace results produced by the tracer - Error string `json:"error,omitempty"` // Trace failure produced by the tracer + Result any `json:"result,omitempty"` // Trace results produced by the tracer + Error string `json:"error,omitempty"` // Trace failure produced by the tracer } var _ vm.EVMLogger = &NoOpTracer{} diff --git a/x/inflation/types/params.go b/x/inflation/types/params.go index 1ca46623b..6b95bbb7c 100644 --- a/x/inflation/types/params.go +++ b/x/inflation/types/params.go @@ -71,7 +71,7 @@ func DefaultParams() Params { } } -func validatePolynomialFactors(i interface{}) error { +func validatePolynomialFactors(i any) error { v, ok := i.([]sdk.Dec) if !ok { return fmt.Errorf("invalid parameter type: %T", i) @@ -83,7 +83,7 @@ func validatePolynomialFactors(i interface{}) error { return nil } -func validateInflationDistribution(i interface{}) error { +func validateInflationDistribution(i any) error { v, ok := i.(InflationDistribution) if !ok { return fmt.Errorf("invalid parameter type: %T", i) @@ -109,7 +109,7 @@ func validateInflationDistribution(i interface{}) error { return nil } -func validateBool(i interface{}) error { +func validateBool(i any) error { _, ok := i.(bool) if !ok { return fmt.Errorf("invalid parameter type: %T", i) @@ -118,7 +118,7 @@ func validateBool(i interface{}) error { return nil } -func validateUint64(i interface{}) error { +func validateUint64(i any) error { _, ok := i.(uint64) if !ok { return fmt.Errorf("invalid genesis state type: %T", i) @@ -126,7 +126,7 @@ func validateUint64(i interface{}) error { return nil } -func validateEpochsPerPeriod(i interface{}) error { +func validateEpochsPerPeriod(i any) error { val, ok := i.(uint64) if !ok { return fmt.Errorf("invalid parameter type: %T", i) @@ -139,7 +139,7 @@ func validateEpochsPerPeriod(i interface{}) error { return nil } -func validatePeriodsPerYear(i interface{}) error { +func validatePeriodsPerYear(i any) error { val, ok := i.(uint64) if !ok { return fmt.Errorf("invalid parameter type: %T", i) diff --git a/x/oracle/types/hash.go b/x/oracle/types/hash.go index b9d816ecc..a828a6414 100644 --- a/x/oracle/types/hash.go +++ b/x/oracle/types/hash.go @@ -99,7 +99,7 @@ func (h AggregateVoteHash) MarshalJSON() ([]byte, error) { } // MarshalYAML marshals to YAML using Bech32. -func (h AggregateVoteHash) MarshalYAML() (interface{}, error) { +func (h AggregateVoteHash) MarshalYAML() (any, error) { return h.String(), nil } diff --git a/x/tokenfactory/types/codec.go b/x/tokenfactory/types/codec.go index e0e639c0d..c20e55ad4 100644 --- a/x/tokenfactory/types/codec.go +++ b/x/tokenfactory/types/codec.go @@ -58,7 +58,7 @@ func TX_MSG_TYPE_URLS() []string { // Amino JSON serialization and EIP-712 compatibility. func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { for _, ele := range []struct { - MsgType interface{} + MsgType any Name string }{ {&MsgCreateDenom{}, "nibiru/tokenfactory/create-denom"}, From 5bae79e762eb8ac015dfdcb89bb378971f93a34a Mon Sep 17 00:00:00 2001 From: Kevin Yang <5478483+k-yang@users.noreply.github.com> Date: Wed, 30 Oct 2024 09:02:52 -0700 Subject: [PATCH 20/21] refactor: add ABI() precompile fn --- x/evm/precompile/funtoken.go | 9 +++++++-- x/evm/precompile/oracle.go | 8 ++++++-- x/evm/precompile/wasm.go | 9 +++++++-- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/x/evm/precompile/funtoken.go b/x/evm/precompile/funtoken.go index 5e364d604..bc2981baa 100644 --- a/x/evm/precompile/funtoken.go +++ b/x/evm/precompile/funtoken.go @@ -8,6 +8,7 @@ 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" + gethabi "github.com/ethereum/go-ethereum/accounts/abi" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" @@ -32,7 +33,11 @@ func (p precompileFunToken) Address() gethcommon.Address { // RequiredGas calculates the cost of calling the precompile in gas units. func (p precompileFunToken) RequiredGas(input []byte) (gasCost uint64) { - return requiredGas(input, embeds.SmartContract_FunToken.ABI) + return requiredGas(input, p.ABI()) +} + +func (p precompileFunToken) ABI() *gethabi.ABI { + return embeds.SmartContract_FunToken.ABI } const ( @@ -46,7 +51,7 @@ func (p precompileFunToken) Run( defer func() { err = ErrPrecompileRun(err, p) }() - start, err := OnRunStart(evm, contract.Input, embeds.SmartContract_FunToken.ABI) + start, err := OnRunStart(evm, contract.Input, p.ABI()) if err != nil { return nil, err } diff --git a/x/evm/precompile/oracle.go b/x/evm/precompile/oracle.go index 860fa335c..f59b93822 100644 --- a/x/evm/precompile/oracle.go +++ b/x/evm/precompile/oracle.go @@ -24,7 +24,11 @@ func (p precompileOracle) Address() gethcommon.Address { } func (p precompileOracle) RequiredGas(input []byte) (gasPrice uint64) { - return requiredGas(input, embeds.SmartContract_Oracle.ABI) + return requiredGas(input, p.ABI()) +} + +func (p precompileOracle) ABI() *gethabi.ABI { + return embeds.SmartContract_Oracle.ABI } const ( @@ -38,7 +42,7 @@ func (p precompileOracle) Run( defer func() { err = ErrPrecompileRun(err, p) }() - startResult, err := OnRunStart(evm, contract.Input, embeds.SmartContract_Oracle.ABI) + startResult, err := OnRunStart(evm, contract.Input, p.ABI()) if err != nil { return nil, err } diff --git a/x/evm/precompile/wasm.go b/x/evm/precompile/wasm.go index 228b965e9..412607e39 100644 --- a/x/evm/precompile/wasm.go +++ b/x/evm/precompile/wasm.go @@ -11,6 +11,7 @@ import ( "github.com/NibiruChain/nibiru/v2/x/evm/embeds" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + gethabi "github.com/ethereum/go-ethereum/accounts/abi" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" ) @@ -36,7 +37,7 @@ func (p precompileWasm) Run( defer func() { err = ErrPrecompileRun(err, p) }() - startResult, err := OnRunStart(evm, contract.Input, embeds.SmartContract_Wasm.ABI) + startResult, err := OnRunStart(evm, contract.Input, p.ABI()) if err != nil { return nil, err } @@ -74,7 +75,11 @@ func (p precompileWasm) Address() gethcommon.Address { // RequiredGas calculates the cost of calling the precompile in gas units. func (p precompileWasm) RequiredGas(input []byte) (gasCost uint64) { - return requiredGas(input, embeds.SmartContract_Wasm.ABI) + return requiredGas(input, p.ABI()) +} + +func (p precompileWasm) ABI() *gethabi.ABI { + return embeds.SmartContract_Wasm.ABI } // Wasm: A struct embedding keepers for read and write operations in Wasm, such From 94121a7d1a01bf4f01719c02033192fd36bcc471 Mon Sep 17 00:00:00 2001 From: Kevin Yang <5478483+k-yang@users.noreply.github.com> Date: Wed, 30 Oct 2024 09:08:01 -0700 Subject: [PATCH 21/21] refactor: replace assertNotReadonlyTx --- x/evm/precompile/errors.go | 7 +++++++ x/evm/precompile/funtoken.go | 4 ++-- x/evm/precompile/wasm.go | 13 ++++++------- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/x/evm/precompile/errors.go b/x/evm/precompile/errors.go index 498ba8406..438ffa750 100644 --- a/x/evm/precompile/errors.go +++ b/x/evm/precompile/errors.go @@ -8,6 +8,13 @@ import ( "github.com/ethereum/go-ethereum/core/vm" ) +func assertNotReadonlyTx(readOnly bool, method *gethabi.Method) error { + if readOnly { + return fmt.Errorf("method %s cannot be called in a read-only context (e.g. staticcall)", method.Name) + } + return nil +} + // ErrPrecompileRun is error function intended for use in a `defer` pattern, // which modifies the input error in the event that its value becomes non-nil. // This creates a concise way to prepend extra information to the original error. diff --git a/x/evm/precompile/funtoken.go b/x/evm/precompile/funtoken.go index bc2981baa..695a23333 100644 --- a/x/evm/precompile/funtoken.go +++ b/x/evm/precompile/funtoken.go @@ -101,8 +101,8 @@ func (p precompileFunToken) bankSend( readOnly bool, ) (bz []byte, err error) { ctx, method, args := start.CacheCtx, start.Method, start.Args - if readOnly { - return nil, fmt.Errorf("bankSend cannot be called in read-only mode") + if err := assertNotReadonlyTx(readOnly, method); err != nil { + return nil, err } erc20, amount, to, err := p.decomposeBankSendArgs(args) diff --git a/x/evm/precompile/wasm.go b/x/evm/precompile/wasm.go index 412607e39..63b5862a8 100644 --- a/x/evm/precompile/wasm.go +++ b/x/evm/precompile/wasm.go @@ -1,7 +1,6 @@ package precompile import ( - "errors" "fmt" sdk "github.com/cosmos/cosmos-sdk/types" @@ -128,8 +127,8 @@ func (p precompileWasm) execute( err = ErrMethodCalled(method, err) } }() - if readOnly { - return nil, errors.New("wasm execute cannot be called in read-only mode") + if err := assertNotReadonlyTx(readOnly, method); err != nil { + return nil, err } wasmContract, msgArgsBz, funds, err := p.parseExecuteArgs(args) @@ -212,8 +211,8 @@ func (p precompileWasm) instantiate( err = ErrMethodCalled(method, err) } }() - if readOnly { - return nil, errors.New("wasm instantiate cannot be called in read-only mode") + if err := assertNotReadonlyTx(readOnly, method); err != nil { + return nil, err } callerBech32 := eth.EthAddrToNibiruAddr(caller) @@ -263,8 +262,8 @@ func (p precompileWasm) executeMulti( err = ErrMethodCalled(method, err) } }() - if readOnly { - return nil, errors.New("wasm executeMulti cannot be called in read-only mode") + if err := assertNotReadonlyTx(readOnly, method); err != nil { + return nil, err } wasmExecMsgs, err := p.parseExecuteMultiArgs(args)