diff --git a/CHANGELOG.md b/CHANGELOG.md
index a316c53f3..68f7f9cc8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,17 +40,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
-### State Machine Breaking
+### Nibiru EVM
 
-#### For next mainnet version
+#### Nibiru EVM | Before Audit 2 [Nov, 2024]
 
-- [#1766](https://github.com/NibiruChain/nibiru/pull/1766) - refactor(app-wasmext)!: remove wasmbinding `CosmosMsg::Custom` bindings.
-- [#1776](https://github.com/NibiruChain/nibiru/pull/1776) - feat(inflation): make inflation params a collection and add commands to update them
-- [#1872](https://github.com/NibiruChain/nibiru/pull/1872) - chore(math): use cosmossdk.io/math to replace sdk types
-- [#1874](https://github.com/NibiruChain/nibiru/pull/1874) - chore(proto): remove the proto stringer as per Cosmos SDK migration guidelines
-- [#1932](https://github.com/NibiruChain/nibiru/pull/1932) - fix(gosdk): fix keyring import functions
+The codebase went through a third-party [Code4rena
+Zenith](https://code4rena.com/zenith) Audit, running from 2024-10-07 until
+2024-11-01 and including both a primary review period and mitigation/remission
+period. This section describes code changes that occured after that audit in
+preparation for a second audit starting in November 2024.
+
+- [#2074](https://github.com/NibiruChain/nibiru/pull/2074) - fix(evm-keeper): better utilize ERC20 metadata during FunToken creation. The bank metadata for a new FunToken mapping ties a connection between the Bank Coin's `DenomUnit` and the ERC20 contract metadata like the name, decimals, and symbol.  This change brings parity between EVM wallets, such as MetaMask, and Interchain wallets like Keplr and Leap.
+- [#2076](https://github.com/NibiruChain/nibiru/pull/2076) - fix(evm-gas-fees):
+Use effective gas price in RefundGas and make sure that units are properly
+reflected on all occurences of "base fee" in the codebase. This fixes [#2059](https://github.com/NibiruChain/nibiru/issues/2059)
+and the [related comments from @Unique-Divine and @berndartmueller](https://github.com/NibiruChain/nibiru/issues/2059#issuecomment-2408625724).
+- [#2084](https://github.com/NibiruChain/nibiru/pull/2084) - feat(evm-forge): foundry support and template for Nibiru EVM develoment
+- [#2086](https://github.com/NibiruChain/nibiru/pull/2086) - fix(evm-precomples):
+Fix state consistency in precompile execution by ensuring proper journaling of
+state changes in the StateDB. This pull request makes sure that state is
+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
 
-#### Nibiru EVM
+#### Nibiru EVM | Before Audit 1 - 2024-10-18
 
 - [#1837](https://github.com/NibiruChain/nibiru/pull/1837) - feat(eth): protos, eth types, and evm module types
 - [#1838](https://github.com/NibiruChain/nibiru/pull/1838) - feat(eth): Go-ethereum, crypto, encoding, and unit tests for evm/types
@@ -70,7 +90,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - [#1909](https://github.com/NibiruChain/nibiru/pull/1909) - chore(evm): set is_london true by default and removed from config
 - [#1911](https://github.com/NibiruChain/nibiru/pull/1911) - chore(evm): simplified config by removing old eth forks
 - [#1912](https://github.com/NibiruChain/nibiru/pull/1912) - test(evm): unit tests for evm_ante
-- [#1914](https://github.com/NibiruChain/nibiru/pull/1914) - refactor(evm): Remove dead code and document non-EVM ante handler- [#1917](https://github.com/NibiruChain/nibiru/pull/1917) - test(e2e-evm): TypeScript support. Type generation from compiled contracts. Formatter for TS code.
+- [#1914](https://github.com/NibiruChain/nibiru/pull/1914) - refactor(evm): Remove dead code and document non-EVM ante handler
 - [#1917](https://github.com/NibiruChain/nibiru/pull/1917) - test(e2e-evm): TypeScript support. Type generation from compiled contracts. Formatter for TS code.
 - [#1922](https://github.com/NibiruChain/nibiru/pull/1922) - feat(evm): tracer option is read from the config.
 - [#1936](https://github.com/NibiruChain/nibiru/pull/1936) - feat(evm): EVM fungible token protobufs and encoding tests
@@ -108,7 +128,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - [#2002](https://github.com/NibiruChain/nibiru/pull/2002) - feat(evm): Add the account query to the EVM command. Cover the CLI with tests.
 - [#2003](https://github.com/NibiruChain/nibiru/pull/2003) - fix(evm): fix FunToken conversions between Cosmos and EVM
 - [#2004](https://github.com/NibiruChain/nibiru/pull/2004) - refactor(evm)!: replace `HexAddr` with `EIP55Addr`
-- [#2006](https://github.com/NibiruChain/nibiru/pull/2006) - test(evm): e2e tests for eth_* endpoints
+- [#2006](https://github.com/NibiruChain/nibiru/pull/2006) - test(evm): e2e tests for eth\_\* endpoints
 - [#2008](https://github.com/NibiruChain/nibiru/pull/2008) - refactor(evm): clean up precompile setups
 - [#2013](https://github.com/NibiruChain/nibiru/pull/2013) - chore(evm): Set appropriate gas value for the required gas of the "IFunToken.sol" precompile.
 - [#2014](https://github.com/NibiruChain/nibiru/pull/2014) - feat(evm): Emit block bloom event in EndBlock hook.
@@ -124,18 +144,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - [#2044](https://github.com/NibiruChain/nibiru/pull/2044) - feat(evm): evm tx indexer service implemented
 - [#2045](https://github.com/NibiruChain/nibiru/pull/2045) - test(evm): backend tests with test network and real txs
 - [#2053](https://github.com/NibiruChain/nibiru/pull/2053) - refactor(evm): converted untyped event to typed and cleaned up
-- [#2054](https://github.com/NibiruChain/nibiru/pull/2054) - feat(evm-precompile): Precompile for one-way EVM calls to invoke/execute Wasm contracts. 
+- [#2054](https://github.com/NibiruChain/nibiru/pull/2054) - feat(evm-precompile): Precompile for one-way EVM calls to invoke/execute Wasm contracts.
 - [#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
-- [#2073](https://github.com/NibiruChain/nibiru/pull/2073) - fix(evm-keeper): better utilize ERC20 metadata during FunToken creation
-- [#2076](https://github.com/NibiruChain/nibiru/pull/2076) - fix(evm-gas-fees):
-Use effective gas price in RefundGas and make sure that units are properly
-reflected on all occurences of "base fee" in the codebase. This fixes [#2059](https://github.com/NibiruChain/nibiru/issues/2059)
-and the [related comments from @Unique-Divine and @berndartmueller](https://github.com/NibiruChain/nibiru/issues/2059#issuecomment-2408625724).
-- [#2084](https://github.com/NibiruChain/nibiru/pull/2084) - feat(evm-forge): foundry support and template for Nibiru EVM develoment
-- [#2088](https://github.com/NibiruChain/nibiru/pull/2088) - refactor(evm): remove outdated comment and improper error message text
 
+### State Machine Breaking (Other)
+
+#### For next mainnet version
+
+- [#1766](https://github.com/NibiruChain/nibiru/pull/1766) - refactor(app-wasmext)!: remove wasmbinding `CosmosMsg::Custom` bindings.
+- [#1776](https://github.com/NibiruChain/nibiru/pull/1776) - feat(inflation): make inflation params a collection and add commands to update them
+- [#1872](https://github.com/NibiruChain/nibiru/pull/1872) - chore(math): use cosmossdk.io/math to replace sdk types
+- [#1874](https://github.com/NibiruChain/nibiru/pull/1874) - chore(proto): remove the proto stringer as per Cosmos SDK migration guidelines
+- [#1932](https://github.com/NibiruChain/nibiru/pull/1932) - fix(gosdk): fix keyring import functions
 
 #### Dapp modules: perp, spot, oracle, etc
 
diff --git a/app/evmante/evmante_validate_basic_test.go b/app/evmante/evmante_validate_basic_test.go
index 4f0e136ff..3f1263dee 100644
--- a/app/evmante/evmante_validate_basic_test.go
+++ b/app/evmante/evmante_validate_basic_test.go
@@ -36,6 +36,18 @@ func (s *TestSuite) TestEthValidateBasicDecorator() {
 			},
 			wantErr: "",
 		},
+		{
+			name: "sad: fail to set params",
+			txSetup: func(deps *evmtest.TestDeps) sdk.Tx {
+				return evmtest.HappyCreateContractTx(deps)
+			},
+			paramsSetup: func(deps *evmtest.TestDeps) evm.Params {
+				return evm.Params{
+					CreateFuntokenFee: sdk.NewInt(-1),
+				}
+			},
+			wantErr: "createFuntokenFee cannot be negative: -1",
+		},
 		{
 			name: "happy: ctx recheck should ignore validation",
 			ctxSetup: func(deps *evmtest.TestDeps) {
@@ -195,12 +207,16 @@ func (s *TestSuite) TestEthValidateBasicDecorator() {
 			if tc.ctxSetup != nil {
 				tc.ctxSetup(&deps)
 			}
+			var err error
 			if tc.paramsSetup != nil {
-				deps.EvmKeeper.SetParams(deps.Ctx, tc.paramsSetup(&deps))
+				err = deps.EvmKeeper.SetParams(deps.Ctx, tc.paramsSetup(&deps))
+			}
+
+			if err == nil {
+				_, err = anteDec.AnteHandle(
+					deps.Ctx, tx, false, evmtest.NextNoOpAnteHandler,
+				)
 			}
-			_, err := anteDec.AnteHandle(
-				deps.Ctx, tx, false, evmtest.NextNoOpAnteHandler,
-			)
 			if tc.wantErr != "" {
 				s.Require().ErrorContains(err, tc.wantErr)
 				return
diff --git a/eth/indexer.pb.go b/eth/indexer.pb.go
index 31ba6aa32..5fbf5d2af 100644
--- a/eth/indexer.pb.go
+++ b/eth/indexer.pb.go
@@ -27,14 +27,16 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
 type TxResult struct {
 	// height of the blockchain
 	Height int64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"`
-	// tx_index of the cosmos transaction
+	// tx_index is the index of the block transaction. It is not the index of an
+	// "internal transaction"
 	TxIndex uint32 `protobuf:"varint,2,opt,name=tx_index,json=txIndex,proto3" json:"tx_index,omitempty"`
 	// msg_index in a batch transaction
 	MsgIndex uint32 `protobuf:"varint,3,opt,name=msg_index,json=msgIndex,proto3" json:"msg_index,omitempty"`
-	// eth_tx_index is the index in the list of valid eth tx in the block,
-	// aka. the transaction list returned by eth_getBlock api.
+	// eth_tx_index is the index in the list of valid eth tx in the block. Said
+	// another way, it is the index of the transaction list returned by
+	// eth_getBlock API.
 	EthTxIndex int32 `protobuf:"varint,4,opt,name=eth_tx_index,json=ethTxIndex,proto3" json:"eth_tx_index,omitempty"`
-	// failed is true if the eth transaction did not go succeed
+	// failed is true if the eth transaction did not succeed
 	Failed bool `protobuf:"varint,5,opt,name=failed,proto3" json:"failed,omitempty"`
 	// gas_used by the transaction. If it exceeds the block gas limit,
 	// it's set to gas limit, which is what's actually deducted by ante handler.
diff --git a/proto/eth/evm/v1/evm.proto b/proto/eth/evm/v1/evm.proto
index 6720afff9..46893edcc 100644
--- a/proto/eth/evm/v1/evm.proto
+++ b/proto/eth/evm/v1/evm.proto
@@ -6,8 +6,8 @@ import "gogoproto/gogo.proto";
 
 option go_package = "github.com/NibiruChain/nibiru/v2/x/evm";
 
-// FunToken is a fungible token mapping between a bank coin and a corresponding
-// ERC-20 smart contract. Bank coins here refer to tokens like NIBI, IBC
+// FunToken is a fungible token mapping between a Bank Coin and a corresponding
+// ERC-20 smart contract. Bank Coins here refer to tokens like NIBI, IBC
 // coins (ICS-20), and token factory coins, which are each represented by the
 // "Coin" type in Golang.
 message FunToken {
@@ -20,7 +20,7 @@ message FunToken {
   // bank_denom: Coin denomination in the Bank Module.
   string bank_denom = 2;
 
-  // True if the `FunToken` mapping was created from an existing bank coin and
+  // True if the `FunToken` mapping was created from an existing Bank Coin and
   // the ERC-20 contract gets deployed by the module account. False if the
   // mapping was created from an externally owned ERC-20 contract.
   bool is_made_from_coin = 3;
diff --git a/proto/eth/evm/v1/query.proto b/proto/eth/evm/v1/query.proto
index a745ae506..bef574508 100644
--- a/proto/eth/evm/v1/query.proto
+++ b/proto/eth/evm/v1/query.proto
@@ -314,6 +314,6 @@ message QueryFunTokenMappingResponse {
   option (gogoproto.equal) = false;
   option (gogoproto.goproto_getters) = false;  
 
-  // fun_token is a mapping between the Cosmos native coin and the ERC20 contract address
+  // fun_token is a mapping between the Bank Coin and the ERC20 contract address
   eth.evm.v1.FunToken fun_token = 1;
 }
diff --git a/proto/eth/evm/v1/tx.proto b/proto/eth/evm/v1/tx.proto
index dd336183f..af185d368 100644
--- a/proto/eth/evm/v1/tx.proto
+++ b/proto/eth/evm/v1/tx.proto
@@ -18,13 +18,13 @@ service Msg {
   rpc EthereumTx(MsgEthereumTx) returns (MsgEthereumTxResponse) {
     option (google.api.http).post = "/nibiru/evm/v1/ethereum_tx";
   };
-  // UpdateParams defined a governance operation for updating the x/evm module parameters.
-  // The authority is hard-coded to the Cosmos SDK x/gov module account
+  // UpdateParams defined a governance operation for updating the x/evm module
+  // parameters. The authority is hard-coded to the x/gov module account
   rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse);
 
   // CreateFunToken: Create a "FunToken" mapping. Either the ERC20 contract
-  // address can be given to create the mapping to a bank coin, or the
-  // denomination for a bank coin can be given to create the mapping to an ERC20.
+  // address can be given to create the mapping to a Bank Coin, or the
+  // denomination for a Bank Coin can be given to create the mapping to an ERC20.
   rpc CreateFunToken(MsgCreateFunToken) returns (MsgCreateFunTokenResponse);
 
   // ConvertCoinToEvm: Sends a coin with a valid "FunToken" mapping to the
@@ -229,8 +229,8 @@ message MsgUpdateParams {
 message MsgUpdateParamsResponse {}
 
 // MsgCreateFunToken: Arguments to create a "FunToken" mapping. Either the ERC20
-// contract address can be given to create the mapping to a bank coin, or the
-// denomination for a bank coin can be given to create the mapping to an ERC20.
+// contract address can be given to create the mapping to a Bank Coin, or the
+// denomination for a Bank Coin can be given to create the mapping to an ERC20.
 message MsgCreateFunToken {
   // Hexadecimal address of the ERC20 token to which the `FunToken` maps
   string from_erc20 = 1 [
@@ -250,7 +250,7 @@ message MsgCreateFunTokenResponse {
   eth.evm.v1.FunToken funtoken_mapping = 1 [(gogoproto.nullable) = false];
 }
 
-// MsgConvertCoinToEvm: Arguments to send a bank coin to ERC-20 representation
+// MsgConvertCoinToEvm: Arguments to send a Bank Coin to ERC-20 representation
 message MsgConvertCoinToEvm {
   // Hexadecimal address of the ERC20 token to which the `FunToken` maps
   string to_eth_addr = 1 [
@@ -261,7 +261,7 @@ message MsgConvertCoinToEvm {
   // Sender: Address for the signer of the transaction.
   string sender = 2;
 
-  // Bank coin to get converted to ERC20
+  // Bank Coin to get converted to ERC20
   cosmos.base.v1beta1.Coin bank_coin = 3 [
     (gogoproto.moretags) = "yaml:\"bank_coin\"",
     (gogoproto.nullable) = false
diff --git a/proto/eth/types/v1/indexer.proto b/proto/eth/types/v1/indexer.proto
index 9840795e5..df17c31f0 100644
--- a/proto/eth/types/v1/indexer.proto
+++ b/proto/eth/types/v1/indexer.proto
@@ -12,15 +12,17 @@ message TxResult {
 
   // height of the blockchain
   int64 height = 1;
-  // tx_index of the cosmos transaction
+  // tx_index is the index of the block transaction. It is not the index of an
+  // "internal transaction"
   uint32 tx_index = 2;
   // msg_index in a batch transaction
   uint32 msg_index = 3;
 
-  // eth_tx_index is the index in the list of valid eth tx in the block,
-  // aka. the transaction list returned by eth_getBlock api.
+  // eth_tx_index is the index in the list of valid eth tx in the block. Said
+  // another way, it is the index of the transaction list returned by
+  // eth_getBlock API.
   int32 eth_tx_index = 4;
-  // failed is true if the eth transaction did not go succeed
+  // failed is true if the eth transaction did not succeed
   bool failed = 5;
   // gas_used by the transaction. If it exceeds the block gas limit,
   // it's set to gas limit, which is what's actually deducted by ante handler.
diff --git a/x/evm/embeds/artifacts/contracts/FunToken.sol/IFunToken.json b/x/evm/embeds/artifacts/contracts/FunToken.sol/IFunToken.json
index a2bebf939..9f006c50f 100644
--- a/x/evm/embeds/artifacts/contracts/FunToken.sol/IFunToken.json
+++ b/x/evm/embeds/artifacts/contracts/FunToken.sol/IFunToken.json
@@ -1,6 +1,6 @@
 {
   "_format": "hh-sol-artifact-1",
-  "contractName": "FunToken",
+  "contractName": "IFunToken",
   "sourceName": "contracts/FunToken.sol",
   "abi": [
     {
diff --git a/x/evm/embeds/artifacts/contracts/TestERC20MaliciousName.sol/TestERC20MaliciousName.json b/x/evm/embeds/artifacts/contracts/TestERC20MaliciousName.sol/TestERC20MaliciousName.json
new file mode 100644
index 000000000..766929c8f
--- /dev/null
+++ b/x/evm/embeds/artifacts/contracts/TestERC20MaliciousName.sol/TestERC20MaliciousName.json
@@ -0,0 +1,302 @@
+{
+  "_format": "hh-sol-artifact-1",
+  "contractName": "TestERC20MaliciousName",
+  "sourceName": "contracts/TestERC20MaliciousName.sol",
+  "abi": [
+    {
+      "inputs": [
+        {
+          "internalType": "string",
+          "name": "name",
+          "type": "string"
+        },
+        {
+          "internalType": "string",
+          "name": "symbol",
+          "type": "string"
+        },
+        {
+          "internalType": "uint8",
+          "name": "decimals_",
+          "type": "uint8"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "constructor"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "owner",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        }
+      ],
+      "name": "Approval",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "from",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        }
+      ],
+      "name": "Transfer",
+      "type": "event"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "owner",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        }
+      ],
+      "name": "allowance",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        }
+      ],
+      "name": "approve",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "account",
+          "type": "address"
+        }
+      ],
+      "name": "balanceOf",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "decimals",
+      "outputs": [
+        {
+          "internalType": "uint8",
+          "name": "",
+          "type": "uint8"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "subtractedValue",
+          "type": "uint256"
+        }
+      ],
+      "name": "decreaseAllowance",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "addedValue",
+          "type": "uint256"
+        }
+      ],
+      "name": "increaseAllowance",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "name",
+      "outputs": [
+        {
+          "internalType": "string",
+          "name": "",
+          "type": "string"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "symbol",
+      "outputs": [
+        {
+          "internalType": "string",
+          "name": "",
+          "type": "string"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "totalSupply",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        }
+      ],
+      "name": "transfer",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "from",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        }
+      ],
+      "name": "transferFrom",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    }
+  ],
+  "bytecode": "0x60806040523480156200001157600080fd5b5060405162001c5638038062001c568339818101604052810190620000379190620003cc565b828281600390816200004a9190620006b1565b5080600490816200005c9190620006b1565b5050506200007b3369d3c21bcecceda10000006200008460201b60201c565b505050620008b3565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603620000f6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620000ed90620007f9565b60405180910390fd5b6200010a60008383620001f160201b60201c565b80600260008282546200011e91906200084a565b92505081905550806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051620001d1919062000896565b60405180910390a3620001ed60008383620001f660201b60201c565b5050565b505050565b505050565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b620002648262000219565b810181811067ffffffffffffffff821117156200028657620002856200022a565b5b80604052505050565b60006200029b620001fb565b9050620002a9828262000259565b919050565b600067ffffffffffffffff821115620002cc57620002cb6200022a565b5b620002d78262000219565b9050602081019050919050565b60005b8381101562000304578082015181840152602081019050620002e7565b60008484015250505050565b6000620003276200032184620002ae565b6200028f565b90508281526020810184848401111562000346576200034562000214565b5b62000353848285620002e4565b509392505050565b600082601f8301126200037357620003726200020f565b5b81516200038584826020860162000310565b91505092915050565b600060ff82169050919050565b620003a6816200038e565b8114620003b257600080fd5b50565b600081519050620003c6816200039b565b92915050565b600080600060608486031215620003e857620003e762000205565b5b600084015167ffffffffffffffff8111156200040957620004086200020a565b5b62000417868287016200035b565b935050602084015167ffffffffffffffff8111156200043b576200043a6200020a565b5b62000449868287016200035b565b92505060406200045c86828701620003b5565b9150509250925092565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680620004b957607f821691505b602082108103620004cf57620004ce62000471565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b600060088302620005397fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82620004fa565b620005458683620004fa565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b6000620005926200058c62000586846200055d565b62000567565b6200055d565b9050919050565b6000819050919050565b620005ae8362000571565b620005c6620005bd8262000599565b84845462000507565b825550505050565b600090565b620005dd620005ce565b620005ea818484620005a3565b505050565b5b81811015620006125762000606600082620005d3565b600181019050620005f0565b5050565b601f82111562000661576200062b81620004d5565b6200063684620004ea565b8101602085101562000646578190505b6200065e6200065585620004ea565b830182620005ef565b50505b505050565b600082821c905092915050565b6000620006866000198460080262000666565b1980831691505092915050565b6000620006a1838362000673565b9150826002028217905092915050565b620006bc8262000466565b67ffffffffffffffff811115620006d857620006d76200022a565b5b620006e48254620004a0565b620006f182828562000616565b600060209050601f83116001811462000729576000841562000714578287015190505b62000720858262000693565b86555062000790565b601f1984166200073986620004d5565b60005b8281101562000763578489015182556001820191506020850194506020810190506200073c565b868310156200078357848901516200077f601f89168262000673565b8355505b6001600288020188555050505b505050505050565b600082825260208201905092915050565b7f45524332303a206d696e7420746f20746865207a65726f206164647265737300600082015250565b6000620007e1601f8362000798565b9150620007ee82620007a9565b602082019050919050565b600060208201905081810360008301526200081481620007d2565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600062000857826200055d565b915062000864836200055d565b92508282019050808211156200087f576200087e6200081b565b5b92915050565b62000890816200055d565b82525050565b6000602082019050620008ad600083018462000885565b92915050565b61139380620008c36000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610b9f565b60405180910390f35b6100e660048036038101906100e19190610c5a565b610293565b6040516100f39190610cb5565b60405180910390f35b6101046102b6565b6040516101119190610cdf565b60405180910390f35b610134600480360381019061012f9190610cfa565b6102c0565b6040516101419190610cb5565b60405180910390f35b6101526102ef565b60405161015f9190610d69565b60405180910390f35b610182600480360381019061017d9190610c5a565b6102f8565b60405161018f9190610cb5565b60405180910390f35b6101b260048036038101906101ad9190610d84565b61032f565b6040516101bf9190610cdf565b60405180910390f35b6101d0610377565b6040516101dd9190610b9f565b60405180910390f35b61020060048036038101906101fb9190610c5a565b610409565b60405161020d9190610cb5565b60405180910390f35b610230600480360381019061022b9190610c5a565b610480565b60405161023d9190610cb5565b60405180910390f35b610260600480360381019061025b9190610db1565b6104a3565b60405161026d9190610cdf565b60405180910390f35b6060600061028261052a565b905061028c6105bc565b8091505090565b60008061029e610632565b90506102ab81858561063a565b600191505092915050565b6000600254905090565b6000806102cb610632565b90506102d8858285610803565b6102e385858561088f565b60019150509392505050565b60006012905090565b600080610303610632565b905061032481858561031585896104a3565b61031f9190610e20565b61063a565b600191505092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60606004805461038690610e83565b80601f01602080910402602001604051908101604052809291908181526020018280546103b290610e83565b80156103ff5780601f106103d4576101008083540402835291602001916103ff565b820191906000526020600020905b8154815290600101906020018083116103e257829003601f168201915b5050505050905090565b600080610414610632565b9050600061042282866104a3565b905083811015610467576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161045e90610f26565b60405180910390fd5b610474828686840361063a565b60019250505092915050565b60008061048b610632565b905061049881858561088f565b600191505092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b60606003805461053990610e83565b80601f016020809104026020016040519081016040528092919081815260200182805461056590610e83565b80156105b25780601f10610587576101008083540402835291602001916105b2565b820191906000526020600020905b81548152906001019060200180831161059557829003601f168201915b5050505050905090565b60006001905060005b620186a081101561061d5760016002836105df9190610f46565b6105e99190610e20565b91506002826105f89190610fb7565b9150600182901b8218915067ffffffffffffffff8216915080806001019150506105c5565b506000810361062f5761062e610fe8565b5b50565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036106a9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106a090611089565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610718576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161070f9061111b565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040516107f69190610cdf565b60405180910390a3505050565b600061080f84846104a3565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114610889578181101561087b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161087290611187565b60405180910390fd5b610888848484840361063a565b5b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036108fe576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108f590611219565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361096d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610964906112ab565b60405180910390fd5b610978838383610b05565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050818110156109fe576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109f59061133d565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610aec9190610cdf565b60405180910390a3610aff848484610b0a565b50505050565b505050565b505050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610b49578082015181840152602081019050610b2e565b60008484015250505050565b6000601f19601f8301169050919050565b6000610b7182610b0f565b610b7b8185610b1a565b9350610b8b818560208601610b2b565b610b9481610b55565b840191505092915050565b60006020820190508181036000830152610bb98184610b66565b905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610bf182610bc6565b9050919050565b610c0181610be6565b8114610c0c57600080fd5b50565b600081359050610c1e81610bf8565b92915050565b6000819050919050565b610c3781610c24565b8114610c4257600080fd5b50565b600081359050610c5481610c2e565b92915050565b60008060408385031215610c7157610c70610bc1565b5b6000610c7f85828601610c0f565b9250506020610c9085828601610c45565b9150509250929050565b60008115159050919050565b610caf81610c9a565b82525050565b6000602082019050610cca6000830184610ca6565b92915050565b610cd981610c24565b82525050565b6000602082019050610cf46000830184610cd0565b92915050565b600080600060608486031215610d1357610d12610bc1565b5b6000610d2186828701610c0f565b9350506020610d3286828701610c0f565b9250506040610d4386828701610c45565b9150509250925092565b600060ff82169050919050565b610d6381610d4d565b82525050565b6000602082019050610d7e6000830184610d5a565b92915050565b600060208284031215610d9a57610d99610bc1565b5b6000610da884828501610c0f565b91505092915050565b60008060408385031215610dc857610dc7610bc1565b5b6000610dd685828601610c0f565b9250506020610de785828601610c0f565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610e2b82610c24565b9150610e3683610c24565b9250828201905080821115610e4e57610e4d610df1565b5b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680610e9b57607f821691505b602082108103610eae57610ead610e54565b5b50919050565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b6000610f10602583610b1a565b9150610f1b82610eb4565b604082019050919050565b60006020820190508181036000830152610f3f81610f03565b9050919050565b6000610f5182610c24565b9150610f5c83610c24565b9250828202610f6a81610c24565b91508282048414831517610f8157610f80610df1565b5b5092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b6000610fc282610c24565b9150610fcd83610c24565b925082610fdd57610fdc610f88565b5b828204905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052600160045260246000fd5b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b6000611073602483610b1a565b915061107e82611017565b604082019050919050565b600060208201905081810360008301526110a281611066565b9050919050565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b6000611105602283610b1a565b9150611110826110a9565b604082019050919050565b60006020820190508181036000830152611134816110f8565b9050919050565b7f45524332303a20696e73756666696369656e7420616c6c6f77616e6365000000600082015250565b6000611171601d83610b1a565b915061117c8261113b565b602082019050919050565b600060208201905081810360008301526111a081611164565b9050919050565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b6000611203602583610b1a565b915061120e826111a7565b604082019050919050565b60006020820190508181036000830152611232816111f6565b9050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b6000611295602383610b1a565b91506112a082611239565b604082019050919050565b600060208201905081810360008301526112c481611288565b9050919050565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b6000611327602683610b1a565b9150611332826112cb565b604082019050919050565b600060208201905081810360008301526113568161131a565b905091905056fea26469706673582212205d0c992a81a43cd8431857189c1e4ae58ba867bc7888b0cc0c2e0031d6d9f3c664736f6c63430008180033",
+  "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610b9f565b60405180910390f35b6100e660048036038101906100e19190610c5a565b610293565b6040516100f39190610cb5565b60405180910390f35b6101046102b6565b6040516101119190610cdf565b60405180910390f35b610134600480360381019061012f9190610cfa565b6102c0565b6040516101419190610cb5565b60405180910390f35b6101526102ef565b60405161015f9190610d69565b60405180910390f35b610182600480360381019061017d9190610c5a565b6102f8565b60405161018f9190610cb5565b60405180910390f35b6101b260048036038101906101ad9190610d84565b61032f565b6040516101bf9190610cdf565b60405180910390f35b6101d0610377565b6040516101dd9190610b9f565b60405180910390f35b61020060048036038101906101fb9190610c5a565b610409565b60405161020d9190610cb5565b60405180910390f35b610230600480360381019061022b9190610c5a565b610480565b60405161023d9190610cb5565b60405180910390f35b610260600480360381019061025b9190610db1565b6104a3565b60405161026d9190610cdf565b60405180910390f35b6060600061028261052a565b905061028c6105bc565b8091505090565b60008061029e610632565b90506102ab81858561063a565b600191505092915050565b6000600254905090565b6000806102cb610632565b90506102d8858285610803565b6102e385858561088f565b60019150509392505050565b60006012905090565b600080610303610632565b905061032481858561031585896104a3565b61031f9190610e20565b61063a565b600191505092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60606004805461038690610e83565b80601f01602080910402602001604051908101604052809291908181526020018280546103b290610e83565b80156103ff5780601f106103d4576101008083540402835291602001916103ff565b820191906000526020600020905b8154815290600101906020018083116103e257829003601f168201915b5050505050905090565b600080610414610632565b9050600061042282866104a3565b905083811015610467576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161045e90610f26565b60405180910390fd5b610474828686840361063a565b60019250505092915050565b60008061048b610632565b905061049881858561088f565b600191505092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b60606003805461053990610e83565b80601f016020809104026020016040519081016040528092919081815260200182805461056590610e83565b80156105b25780601f10610587576101008083540402835291602001916105b2565b820191906000526020600020905b81548152906001019060200180831161059557829003601f168201915b5050505050905090565b60006001905060005b620186a081101561061d5760016002836105df9190610f46565b6105e99190610e20565b91506002826105f89190610fb7565b9150600182901b8218915067ffffffffffffffff8216915080806001019150506105c5565b506000810361062f5761062e610fe8565b5b50565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036106a9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106a090611089565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610718576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161070f9061111b565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040516107f69190610cdf565b60405180910390a3505050565b600061080f84846104a3565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114610889578181101561087b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161087290611187565b60405180910390fd5b610888848484840361063a565b5b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036108fe576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108f590611219565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361096d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610964906112ab565b60405180910390fd5b610978838383610b05565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050818110156109fe576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109f59061133d565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610aec9190610cdf565b60405180910390a3610aff848484610b0a565b50505050565b505050565b505050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610b49578082015181840152602081019050610b2e565b60008484015250505050565b6000601f19601f8301169050919050565b6000610b7182610b0f565b610b7b8185610b1a565b9350610b8b818560208601610b2b565b610b9481610b55565b840191505092915050565b60006020820190508181036000830152610bb98184610b66565b905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610bf182610bc6565b9050919050565b610c0181610be6565b8114610c0c57600080fd5b50565b600081359050610c1e81610bf8565b92915050565b6000819050919050565b610c3781610c24565b8114610c4257600080fd5b50565b600081359050610c5481610c2e565b92915050565b60008060408385031215610c7157610c70610bc1565b5b6000610c7f85828601610c0f565b9250506020610c9085828601610c45565b9150509250929050565b60008115159050919050565b610caf81610c9a565b82525050565b6000602082019050610cca6000830184610ca6565b92915050565b610cd981610c24565b82525050565b6000602082019050610cf46000830184610cd0565b92915050565b600080600060608486031215610d1357610d12610bc1565b5b6000610d2186828701610c0f565b9350506020610d3286828701610c0f565b9250506040610d4386828701610c45565b9150509250925092565b600060ff82169050919050565b610d6381610d4d565b82525050565b6000602082019050610d7e6000830184610d5a565b92915050565b600060208284031215610d9a57610d99610bc1565b5b6000610da884828501610c0f565b91505092915050565b60008060408385031215610dc857610dc7610bc1565b5b6000610dd685828601610c0f565b9250506020610de785828601610c0f565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610e2b82610c24565b9150610e3683610c24565b9250828201905080821115610e4e57610e4d610df1565b5b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680610e9b57607f821691505b602082108103610eae57610ead610e54565b5b50919050565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b6000610f10602583610b1a565b9150610f1b82610eb4565b604082019050919050565b60006020820190508181036000830152610f3f81610f03565b9050919050565b6000610f5182610c24565b9150610f5c83610c24565b9250828202610f6a81610c24565b91508282048414831517610f8157610f80610df1565b5b5092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b6000610fc282610c24565b9150610fcd83610c24565b925082610fdd57610fdc610f88565b5b828204905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052600160045260246000fd5b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b6000611073602483610b1a565b915061107e82611017565b604082019050919050565b600060208201905081810360008301526110a281611066565b9050919050565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b6000611105602283610b1a565b9150611110826110a9565b604082019050919050565b60006020820190508181036000830152611134816110f8565b9050919050565b7f45524332303a20696e73756666696369656e7420616c6c6f77616e6365000000600082015250565b6000611171601d83610b1a565b915061117c8261113b565b602082019050919050565b600060208201905081810360008301526111a081611164565b9050919050565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b6000611203602583610b1a565b915061120e826111a7565b604082019050919050565b60006020820190508181036000830152611232816111f6565b9050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b6000611295602383610b1a565b91506112a082611239565b604082019050919050565b600060208201905081810360008301526112c481611288565b9050919050565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b6000611327602683610b1a565b9150611332826112cb565b604082019050919050565b600060208201905081810360008301526113568161131a565b905091905056fea26469706673582212205d0c992a81a43cd8431857189c1e4ae58ba867bc7888b0cc0c2e0031d6d9f3c664736f6c63430008180033",
+  "linkReferences": {},
+  "deployedLinkReferences": {}
+}
diff --git a/x/evm/embeds/artifacts/contracts/TestERC20MaliciousTransfer.sol/TestERC20MaliciousTransfer.json b/x/evm/embeds/artifacts/contracts/TestERC20MaliciousTransfer.sol/TestERC20MaliciousTransfer.json
new file mode 100644
index 000000000..48019e35d
--- /dev/null
+++ b/x/evm/embeds/artifacts/contracts/TestERC20MaliciousTransfer.sol/TestERC20MaliciousTransfer.json
@@ -0,0 +1,302 @@
+{
+  "_format": "hh-sol-artifact-1",
+  "contractName": "TestERC20MaliciousTransfer",
+  "sourceName": "contracts/TestERC20MaliciousTransfer.sol",
+  "abi": [
+    {
+      "inputs": [
+        {
+          "internalType": "string",
+          "name": "name",
+          "type": "string"
+        },
+        {
+          "internalType": "string",
+          "name": "symbol",
+          "type": "string"
+        },
+        {
+          "internalType": "uint8",
+          "name": "decimals_",
+          "type": "uint8"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "constructor"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "owner",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        }
+      ],
+      "name": "Approval",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "from",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        }
+      ],
+      "name": "Transfer",
+      "type": "event"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "owner",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        }
+      ],
+      "name": "allowance",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        }
+      ],
+      "name": "approve",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "account",
+          "type": "address"
+        }
+      ],
+      "name": "balanceOf",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "decimals",
+      "outputs": [
+        {
+          "internalType": "uint8",
+          "name": "",
+          "type": "uint8"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "subtractedValue",
+          "type": "uint256"
+        }
+      ],
+      "name": "decreaseAllowance",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "addedValue",
+          "type": "uint256"
+        }
+      ],
+      "name": "increaseAllowance",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "name",
+      "outputs": [
+        {
+          "internalType": "string",
+          "name": "",
+          "type": "string"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "symbol",
+      "outputs": [
+        {
+          "internalType": "string",
+          "name": "",
+          "type": "string"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "totalSupply",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "recipient",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        }
+      ],
+      "name": "transfer",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "from",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        }
+      ],
+      "name": "transferFrom",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    }
+  ],
+  "bytecode": "0x60806040523480156200001157600080fd5b5060405162001c5538038062001c558339818101604052810190620000379190620003cc565b828281600390816200004a9190620006b1565b5080600490816200005c9190620006b1565b5050506200007b3369d3c21bcecceda10000006200008460201b60201c565b505050620008b3565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603620000f6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620000ed90620007f9565b60405180910390fd5b6200010a60008383620001f160201b60201c565b80600260008282546200011e91906200084a565b92505081905550806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051620001d1919062000896565b60405180910390a3620001ed60008383620001f660201b60201c565b5050565b505050565b505050565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b620002648262000219565b810181811067ffffffffffffffff821117156200028657620002856200022a565b5b80604052505050565b60006200029b620001fb565b9050620002a9828262000259565b919050565b600067ffffffffffffffff821115620002cc57620002cb6200022a565b5b620002d78262000219565b9050602081019050919050565b60005b8381101562000304578082015181840152602081019050620002e7565b60008484015250505050565b6000620003276200032184620002ae565b6200028f565b90508281526020810184848401111562000346576200034562000214565b5b62000353848285620002e4565b509392505050565b600082601f8301126200037357620003726200020f565b5b81516200038584826020860162000310565b91505092915050565b600060ff82169050919050565b620003a6816200038e565b8114620003b257600080fd5b50565b600081519050620003c6816200039b565b92915050565b600080600060608486031215620003e857620003e762000205565b5b600084015167ffffffffffffffff8111156200040957620004086200020a565b5b62000417868287016200035b565b935050602084015167ffffffffffffffff8111156200043b576200043a6200020a565b5b62000449868287016200035b565b92505060406200045c86828701620003b5565b9150509250925092565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680620004b957607f821691505b602082108103620004cf57620004ce62000471565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b600060088302620005397fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82620004fa565b620005458683620004fa565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b6000620005926200058c62000586846200055d565b62000567565b6200055d565b9050919050565b6000819050919050565b620005ae8362000571565b620005c6620005bd8262000599565b84845462000507565b825550505050565b600090565b620005dd620005ce565b620005ea818484620005a3565b505050565b5b81811015620006125762000606600082620005d3565b600181019050620005f0565b5050565b601f82111562000661576200062b81620004d5565b6200063684620004ea565b8101602085101562000646578190505b6200065e6200065585620004ea565b830182620005ef565b50505b505050565b600082821c905092915050565b6000620006866000198460080262000666565b1980831691505092915050565b6000620006a1838362000673565b9150826002028217905092915050565b620006bc8262000466565b67ffffffffffffffff811115620006d857620006d76200022a565b5b620006e48254620004a0565b620006f182828562000616565b600060209050601f83116001811462000729576000841562000714578287015190505b62000720858262000693565b86555062000790565b601f1984166200073986620004d5565b60005b8281101562000763578489015182556001820191506020850194506020810190506200073c565b868310156200078357848901516200077f601f89168262000673565b8355505b6001600288020188555050505b505050505050565b600082825260208201905092915050565b7f45524332303a206d696e7420746f20746865207a65726f206164647265737300600082015250565b6000620007e1601f8362000798565b9150620007ee82620007a9565b602082019050919050565b600060208201905081810360008301526200081481620007d2565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600062000857826200055d565b915062000864836200055d565b92508282019050808211156200087f576200087e6200081b565b5b92915050565b62000890816200055d565b82525050565b6000602082019050620008ad600083018462000885565b92915050565b61139280620008c36000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610b9e565b60405180910390f35b6100e660048036038101906100e19190610c59565b610308565b6040516100f39190610cb4565b60405180910390f35b61010461032b565b6040516101119190610cde565b60405180910390f35b610134600480360381019061012f9190610cf9565b610335565b6040516101419190610cb4565b60405180910390f35b610152610364565b60405161015f9190610d68565b60405180910390f35b610182600480360381019061017d9190610c59565b61036d565b60405161018f9190610cb4565b60405180910390f35b6101b260048036038101906101ad9190610d83565b6103a4565b6040516101bf9190610cde565b60405180910390f35b6101d06103ec565b6040516101dd9190610b9e565b60405180910390f35b61020060048036038101906101fb9190610c59565b61047e565b60405161020d9190610cb4565b60405180910390f35b610230600480360381019061022b9190610c59565b6104f5565b60405161023d9190610cb4565b60405180910390f35b610260600480360381019061025b9190610db0565b610511565b60405161026d9190610cde565b60405180910390f35b60606003805461028590610e1f565b80601f01602080910402602001604051908101604052809291908181526020018280546102b190610e1f565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b600080610313610598565b90506103208185856105a0565b600191505092915050565b6000600254905090565b600080610340610598565b905061034d858285610769565b6103588585856107f5565b60019150509392505050565b60006012905090565b600080610378610598565b905061039981858561038a8589610511565b6103949190610e7f565b6105a0565b600191505092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6060600480546103fb90610e1f565b80601f016020809104026020016040519081016040528092919081815260200182805461042790610e1f565b80156104745780601f1061044957610100808354040283529160200191610474565b820191906000526020600020905b81548152906001019060200180831161045757829003601f168201915b5050505050905090565b600080610489610598565b905060006104978286610511565b9050838110156104dc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d390610f25565b60405180910390fd5b6104e982868684036105a0565b60019250505092915050565b60006104ff610a6b565b6105098383610ae1565b905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361060f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161060690610fb7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361067e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067590611049565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258360405161075c9190610cde565b60405180910390a3505050565b60006107758484610511565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146107ef57818110156107e1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107d8906110b5565b60405180910390fd5b6107ee84848484036105a0565b5b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610864576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161085b90611147565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036108d3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108ca906111d9565b60405180910390fd5b6108de838383610b04565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610964576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161095b9061126b565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610a529190610cde565b60405180910390a3610a65848484610b09565b50505050565b60006001905060005b620186a0811015610acc576001600283610a8e919061128b565b610a989190610e7f565b9150600282610aa791906112fc565b9150600182901b8218915067ffffffffffffffff821691508080600101915050610a74565b5060008103610ade57610add61132d565b5b50565b600080610aec610598565b9050610af98185856107f5565b600191505092915050565b505050565b505050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610b48578082015181840152602081019050610b2d565b60008484015250505050565b6000601f19601f8301169050919050565b6000610b7082610b0e565b610b7a8185610b19565b9350610b8a818560208601610b2a565b610b9381610b54565b840191505092915050565b60006020820190508181036000830152610bb88184610b65565b905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610bf082610bc5565b9050919050565b610c0081610be5565b8114610c0b57600080fd5b50565b600081359050610c1d81610bf7565b92915050565b6000819050919050565b610c3681610c23565b8114610c4157600080fd5b50565b600081359050610c5381610c2d565b92915050565b60008060408385031215610c7057610c6f610bc0565b5b6000610c7e85828601610c0e565b9250506020610c8f85828601610c44565b9150509250929050565b60008115159050919050565b610cae81610c99565b82525050565b6000602082019050610cc96000830184610ca5565b92915050565b610cd881610c23565b82525050565b6000602082019050610cf36000830184610ccf565b92915050565b600080600060608486031215610d1257610d11610bc0565b5b6000610d2086828701610c0e565b9350506020610d3186828701610c0e565b9250506040610d4286828701610c44565b9150509250925092565b600060ff82169050919050565b610d6281610d4c565b82525050565b6000602082019050610d7d6000830184610d59565b92915050565b600060208284031215610d9957610d98610bc0565b5b6000610da784828501610c0e565b91505092915050565b60008060408385031215610dc757610dc6610bc0565b5b6000610dd585828601610c0e565b9250506020610de685828601610c0e565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680610e3757607f821691505b602082108103610e4a57610e49610df0565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610e8a82610c23565b9150610e9583610c23565b9250828201905080821115610ead57610eac610e50565b5b92915050565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b6000610f0f602583610b19565b9150610f1a82610eb3565b604082019050919050565b60006020820190508181036000830152610f3e81610f02565b9050919050565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b6000610fa1602483610b19565b9150610fac82610f45565b604082019050919050565b60006020820190508181036000830152610fd081610f94565b9050919050565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b6000611033602283610b19565b915061103e82610fd7565b604082019050919050565b6000602082019050818103600083015261106281611026565b9050919050565b7f45524332303a20696e73756666696369656e7420616c6c6f77616e6365000000600082015250565b600061109f601d83610b19565b91506110aa82611069565b602082019050919050565b600060208201905081810360008301526110ce81611092565b9050919050565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b6000611131602583610b19565b915061113c826110d5565b604082019050919050565b6000602082019050818103600083015261116081611124565b9050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b60006111c3602383610b19565b91506111ce82611167565b604082019050919050565b600060208201905081810360008301526111f2816111b6565b9050919050565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b6000611255602683610b19565b9150611260826111f9565b604082019050919050565b6000602082019050818103600083015261128481611248565b9050919050565b600061129682610c23565b91506112a183610c23565b92508282026112af81610c23565b915082820484148315176112c6576112c5610e50565b5b5092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600061130782610c23565b915061131283610c23565b925082611322576113216112cd565b5b828204905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052600160045260246000fdfea2646970667358221220e34d9015349ff6af20fee44587dc5ba21b370ca2a6239a1dc6440ecaddfd47a364736f6c63430008180033",
+  "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610b9e565b60405180910390f35b6100e660048036038101906100e19190610c59565b610308565b6040516100f39190610cb4565b60405180910390f35b61010461032b565b6040516101119190610cde565b60405180910390f35b610134600480360381019061012f9190610cf9565b610335565b6040516101419190610cb4565b60405180910390f35b610152610364565b60405161015f9190610d68565b60405180910390f35b610182600480360381019061017d9190610c59565b61036d565b60405161018f9190610cb4565b60405180910390f35b6101b260048036038101906101ad9190610d83565b6103a4565b6040516101bf9190610cde565b60405180910390f35b6101d06103ec565b6040516101dd9190610b9e565b60405180910390f35b61020060048036038101906101fb9190610c59565b61047e565b60405161020d9190610cb4565b60405180910390f35b610230600480360381019061022b9190610c59565b6104f5565b60405161023d9190610cb4565b60405180910390f35b610260600480360381019061025b9190610db0565b610511565b60405161026d9190610cde565b60405180910390f35b60606003805461028590610e1f565b80601f01602080910402602001604051908101604052809291908181526020018280546102b190610e1f565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b600080610313610598565b90506103208185856105a0565b600191505092915050565b6000600254905090565b600080610340610598565b905061034d858285610769565b6103588585856107f5565b60019150509392505050565b60006012905090565b600080610378610598565b905061039981858561038a8589610511565b6103949190610e7f565b6105a0565b600191505092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6060600480546103fb90610e1f565b80601f016020809104026020016040519081016040528092919081815260200182805461042790610e1f565b80156104745780601f1061044957610100808354040283529160200191610474565b820191906000526020600020905b81548152906001019060200180831161045757829003601f168201915b5050505050905090565b600080610489610598565b905060006104978286610511565b9050838110156104dc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d390610f25565b60405180910390fd5b6104e982868684036105a0565b60019250505092915050565b60006104ff610a6b565b6105098383610ae1565b905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361060f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161060690610fb7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361067e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067590611049565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258360405161075c9190610cde565b60405180910390a3505050565b60006107758484610511565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146107ef57818110156107e1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107d8906110b5565b60405180910390fd5b6107ee84848484036105a0565b5b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610864576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161085b90611147565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036108d3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108ca906111d9565b60405180910390fd5b6108de838383610b04565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610964576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161095b9061126b565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610a529190610cde565b60405180910390a3610a65848484610b09565b50505050565b60006001905060005b620186a0811015610acc576001600283610a8e919061128b565b610a989190610e7f565b9150600282610aa791906112fc565b9150600182901b8218915067ffffffffffffffff821691508080600101915050610a74565b5060008103610ade57610add61132d565b5b50565b600080610aec610598565b9050610af98185856107f5565b600191505092915050565b505050565b505050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610b48578082015181840152602081019050610b2d565b60008484015250505050565b6000601f19601f8301169050919050565b6000610b7082610b0e565b610b7a8185610b19565b9350610b8a818560208601610b2a565b610b9381610b54565b840191505092915050565b60006020820190508181036000830152610bb88184610b65565b905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610bf082610bc5565b9050919050565b610c0081610be5565b8114610c0b57600080fd5b50565b600081359050610c1d81610bf7565b92915050565b6000819050919050565b610c3681610c23565b8114610c4157600080fd5b50565b600081359050610c5381610c2d565b92915050565b60008060408385031215610c7057610c6f610bc0565b5b6000610c7e85828601610c0e565b9250506020610c8f85828601610c44565b9150509250929050565b60008115159050919050565b610cae81610c99565b82525050565b6000602082019050610cc96000830184610ca5565b92915050565b610cd881610c23565b82525050565b6000602082019050610cf36000830184610ccf565b92915050565b600080600060608486031215610d1257610d11610bc0565b5b6000610d2086828701610c0e565b9350506020610d3186828701610c0e565b9250506040610d4286828701610c44565b9150509250925092565b600060ff82169050919050565b610d6281610d4c565b82525050565b6000602082019050610d7d6000830184610d59565b92915050565b600060208284031215610d9957610d98610bc0565b5b6000610da784828501610c0e565b91505092915050565b60008060408385031215610dc757610dc6610bc0565b5b6000610dd585828601610c0e565b9250506020610de685828601610c0e565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680610e3757607f821691505b602082108103610e4a57610e49610df0565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610e8a82610c23565b9150610e9583610c23565b9250828201905080821115610ead57610eac610e50565b5b92915050565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b6000610f0f602583610b19565b9150610f1a82610eb3565b604082019050919050565b60006020820190508181036000830152610f3e81610f02565b9050919050565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b6000610fa1602483610b19565b9150610fac82610f45565b604082019050919050565b60006020820190508181036000830152610fd081610f94565b9050919050565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b6000611033602283610b19565b915061103e82610fd7565b604082019050919050565b6000602082019050818103600083015261106281611026565b9050919050565b7f45524332303a20696e73756666696369656e7420616c6c6f77616e6365000000600082015250565b600061109f601d83610b19565b91506110aa82611069565b602082019050919050565b600060208201905081810360008301526110ce81611092565b9050919050565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b6000611131602583610b19565b915061113c826110d5565b604082019050919050565b6000602082019050818103600083015261116081611124565b9050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b60006111c3602383610b19565b91506111ce82611167565b604082019050919050565b600060208201905081810360008301526111f2816111b6565b9050919050565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b6000611255602683610b19565b9150611260826111f9565b604082019050919050565b6000602082019050818103600083015261128481611248565b9050919050565b600061129682610c23565b91506112a183610c23565b92508282026112af81610c23565b915082820484148315176112c6576112c5610e50565b5b5092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600061130782610c23565b915061131283610c23565b925082611322576113216112cd565b5b828204905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052600160045260246000fdfea2646970667358221220e34d9015349ff6af20fee44587dc5ba21b370ca2a6239a1dc6440ecaddfd47a364736f6c63430008180033",
+  "linkReferences": {},
+  "deployedLinkReferences": {}
+}
diff --git a/x/evm/embeds/contracts/TestERC20MaliciousName.sol b/x/evm/embeds/contracts/TestERC20MaliciousName.sol
new file mode 100644
index 000000000..e6be7270a
--- /dev/null
+++ b/x/evm/embeds/contracts/TestERC20MaliciousName.sol
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+
+contract TestERC20MaliciousName is ERC20 {
+    constructor(string memory name, string memory symbol, uint8 decimals_)
+    ERC20(name, symbol) {
+        _mint(msg.sender, 1000000 * 10**18);
+    }
+
+    function name() public view virtual override returns (string memory) {
+        string memory actualName = super.name();
+        _gasIntensiveOperation();
+        return actualName;
+    }
+
+    // Gas-intensive operation to simulate high computational cost
+    function _gasIntensiveOperation() internal pure {
+        uint256 result = 1;
+        for (uint256 i = 0; i < 100000; i++) {
+            result = result * 2 + 1;
+            result = result / 2;
+            result = result ^ (result << 1);
+            result = result & 0xFFFFFFFFFFFFFFFF;
+        }
+        // The result is not used, ensuring the compiler doesn't optimize this away
+        assert(result != 0);
+    }
+}
\ No newline at end of file
diff --git a/x/evm/embeds/contracts/TestERC20MaliciousTransfer.sol b/x/evm/embeds/contracts/TestERC20MaliciousTransfer.sol
new file mode 100644
index 000000000..07fc63699
--- /dev/null
+++ b/x/evm/embeds/contracts/TestERC20MaliciousTransfer.sol
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+
+contract TestERC20MaliciousTransfer is ERC20 {
+    constructor(string memory name, string memory symbol, uint8 decimals_)
+    ERC20(name, symbol) {
+        _mint(msg.sender, 1000000 * 10**18);
+    }
+
+    function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
+        _gasIntensiveOperation();
+        return super.transfer(recipient, amount);
+    }
+
+    // Gas-intensive operation to simulate high computational cost
+    function _gasIntensiveOperation() internal pure {
+        uint256 result = 1;
+        for (uint256 i = 0; i < 100000; i++) {
+            result = result * 2 + 1;
+            result = result / 2;
+            result = result ^ (result << 1);
+            result = result & 0xFFFFFFFFFFFFFFFF;
+        }
+        // The result is not used, ensuring the compiler doesn't optimize this away
+        assert(result != 0);
+    }
+}
\ No newline at end of file
diff --git a/x/evm/embeds/embeds.go b/x/evm/embeds/embeds.go
index b2535040c..5ac65655d 100644
--- a/x/evm/embeds/embeds.go
+++ b/x/evm/embeds/embeds.go
@@ -25,6 +25,10 @@ var (
 	wasmPrecompileJSON []byte
 	//go:embed artifacts/contracts/TestERC20.sol/TestERC20.json
 	testErc20Json []byte
+	//go:embed artifacts/contracts/TestERC20MaliciousName.sol/TestERC20MaliciousName.json
+	testErc20MaliciousNameJson []byte
+	//go:embed artifacts/contracts/TestERC20MaliciousTransfer.sol/TestERC20MaliciousTransfer.json
+	testErc20MaliciousTransferJson []byte
 )
 
 var (
@@ -58,6 +62,20 @@ var (
 		Name:      "TestERC20.sol",
 		EmbedJSON: testErc20Json,
 	}
+	// SmartContract_TestERC20MaliciousName is a test contract
+	// which simulates malicious ERC20 behavior by adding gas intensive operation
+	// for function name() intended to attack funtoken creation
+	SmartContract_TestERC20MaliciousName = CompiledEvmContract{
+		Name:      "TestERC20MaliciousName.sol",
+		EmbedJSON: testErc20MaliciousNameJson,
+	}
+	// SmartContract_TestERC20MaliciousTransfer is a test contract
+	// which simulates malicious ERC20 behavior by adding gas intensive operation
+	// for function transfer() intended to attack funtoken conversion from erc20 to bank coin
+	SmartContract_TestERC20MaliciousTransfer = CompiledEvmContract{
+		Name:      "TestERC20MaliciousTransfer.sol",
+		EmbedJSON: testErc20MaliciousTransferJson,
+	}
 )
 
 func init() {
@@ -66,6 +84,8 @@ func init() {
 	SmartContract_Wasm.MustLoad()
 	SmartContract_Oracle.MustLoad()
 	SmartContract_TestERC20.MustLoad()
+	SmartContract_TestERC20MaliciousName.MustLoad()
+	SmartContract_TestERC20MaliciousTransfer.MustLoad()
 }
 
 type CompiledEvmContract struct {
diff --git a/x/evm/embeds/embeds_test.go b/x/evm/embeds/embeds_test.go
index 5aa9a020e..586ff68b9 100644
--- a/x/evm/embeds/embeds_test.go
+++ b/x/evm/embeds/embeds_test.go
@@ -14,5 +14,7 @@ func TestLoadContracts(t *testing.T) {
 		embeds.SmartContract_ERC20Minter.MustLoad()
 		embeds.SmartContract_FunToken.MustLoad()
 		embeds.SmartContract_TestERC20.MustLoad()
+		embeds.SmartContract_TestERC20MaliciousName.MustLoad()
+		embeds.SmartContract_TestERC20MaliciousTransfer.MustLoad()
 	})
 }
diff --git a/x/evm/embeds/package-lock.json b/x/evm/embeds/package-lock.json
index 257cdaa23..806b00fe2 100644
--- a/x/evm/embeds/package-lock.json
+++ b/x/evm/embeds/package-lock.json
@@ -6517,21 +6517,47 @@
       "license": "MIT"
     },
     "node_modules/secp256k1": {
-      "version": "4.0.3",
-      "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz",
-      "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==",
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.4.tgz",
+      "integrity": "sha512-6JfvwvjUOn8F/jUoBY2Q1v5WY5XS+rj8qSe0v8Y4ezH4InLgTEeOOPQsRll9OV429Pvo6BCHGavIyJfr3TAhsw==",
       "dev": true,
       "hasInstallScript": true,
-      "license": "MIT",
       "dependencies": {
-        "elliptic": "^6.5.4",
-        "node-addon-api": "^2.0.0",
+        "elliptic": "^6.5.7",
+        "node-addon-api": "^5.0.0",
         "node-gyp-build": "^4.2.0"
       },
       "engines": {
-        "node": ">=10.0.0"
+        "node": ">=18.0.0"
       }
     },
+    "node_modules/secp256k1/node_modules/bn.js": {
+      "version": "4.12.0",
+      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
+      "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
+      "dev": true
+    },
+    "node_modules/secp256k1/node_modules/elliptic": {
+      "version": "6.5.7",
+      "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz",
+      "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==",
+      "dev": true,
+      "dependencies": {
+        "bn.js": "^4.11.9",
+        "brorand": "^1.1.0",
+        "hash.js": "^1.0.0",
+        "hmac-drbg": "^1.0.1",
+        "inherits": "^2.0.4",
+        "minimalistic-assert": "^1.0.1",
+        "minimalistic-crypto-utils": "^1.0.1"
+      }
+    },
+    "node_modules/secp256k1/node_modules/node-addon-api": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
+      "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==",
+      "dev": true
+    },
     "node_modules/semver": {
       "version": "6.3.1",
       "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
diff --git a/x/evm/errors.go b/x/evm/errors.go
index a32facdcb..9fc6722ac 100644
--- a/x/evm/errors.go
+++ b/x/evm/errors.go
@@ -74,9 +74,17 @@ var (
 func NewExecErrorWithReason(revertReason []byte) *RevertError {
 	result := common.CopyBytes(revertReason)
 	reason, errUnpack := abi.UnpackRevert(result)
-	err := errors.New("execution reverted")
+
+	var err error
+	errPrefix := "execution reverted"
 	if errUnpack == nil {
-		err = fmt.Errorf("execution reverted: %v", reason)
+		reasonStr := reason
+		err = fmt.Errorf("%s with reason \"%v\"", errPrefix, reasonStr)
+	} else if string(result) != "" {
+		reasonStr := string(result)
+		err = fmt.Errorf("%s with reason \"%v\"", errPrefix, reasonStr)
+	} else {
+		err = errors.New(errPrefix)
 	}
 	return &RevertError{
 		error:  err,
diff --git a/x/evm/evm.pb.go b/x/evm/evm.pb.go
index fa9c6988e..59bdc4cbe 100644
--- a/x/evm/evm.pb.go
+++ b/x/evm/evm.pb.go
@@ -25,8 +25,8 @@ var _ = math.Inf
 // proto package needs to be updated.
 const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
 
-// FunToken is a fungible token mapping between a bank coin and a corresponding
-// ERC-20 smart contract. Bank coins here refer to tokens like NIBI, IBC
+// FunToken is a fungible token mapping between a Bank Coin and a corresponding
+// ERC-20 smart contract. Bank Coins here refer to tokens like NIBI, IBC
 // coins (ICS-20), and token factory coins, which are each represented by the
 // "Coin" type in Golang.
 type FunToken struct {
@@ -34,7 +34,7 @@ type FunToken struct {
 	Erc20Addr github_com_NibiruChain_nibiru_v2_eth.EIP55Addr `protobuf:"bytes,1,opt,name=erc20_addr,json=erc20Addr,proto3,customtype=github.com/NibiruChain/nibiru/v2/eth.EIP55Addr" json:"erc20_addr"`
 	// bank_denom: Coin denomination in the Bank Module.
 	BankDenom string `protobuf:"bytes,2,opt,name=bank_denom,json=bankDenom,proto3" json:"bank_denom,omitempty"`
-	// True if the `FunToken` mapping was created from an existing bank coin and
+	// True if the `FunToken` mapping was created from an existing Bank Coin and
 	// the ERC-20 contract gets deployed by the module account. False if the
 	// mapping was created from an externally owned ERC-20 contract.
 	IsMadeFromCoin bool `protobuf:"varint,3,opt,name=is_made_from_coin,json=isMadeFromCoin,proto3" json:"is_made_from_coin,omitempty"`
diff --git a/x/evm/evmmodule/genesis.go b/x/evm/evmmodule/genesis.go
index d67a0c18a..b552b25ea 100644
--- a/x/evm/evmmodule/genesis.go
+++ b/x/evm/evmmodule/genesis.go
@@ -23,7 +23,10 @@ func InitGenesis(
 	accountKeeper evm.AccountKeeper,
 	genState evm.GenesisState,
 ) []abci.ValidatorUpdate {
-	k.SetParams(ctx, genState.Params)
+	err := k.SetParams(ctx, genState.Params)
+	if err != nil {
+		panic(fmt.Errorf("failed to set params: %w", err))
+	}
 
 	// Note that "GetModuleAccount" initializes the module account with permissions
 	// under the hood if it did not already exist. This is important because the
diff --git a/x/evm/evmtest/erc20.go b/x/evm/evmtest/erc20.go
index ce020036f..d8798f71d 100644
--- a/x/evm/evmtest/erc20.go
+++ b/x/evm/evmtest/erc20.go
@@ -23,7 +23,7 @@ func AssertERC20BalanceEqual(
 ) {
 	actualBalance, err := deps.EvmKeeper.ERC20().BalanceOf(erc20, account, deps.Ctx)
 	assert.NoError(t, err)
-	assert.Zero(t, expectedBalance.Cmp(actualBalance), "expected %s, got %s", expectedBalance, actualBalance)
+	assert.Equal(t, expectedBalance.String(), actualBalance.String(), "expected %s, got %s", expectedBalance, actualBalance)
 }
 
 // CreateFunTokenForBankCoin: Uses the "TestDeps.Sender" account to create a
@@ -99,3 +99,9 @@ func AssertBankBalanceEqual(
 	actualBalance := deps.App.BankKeeper.GetBalance(deps.Ctx, bech32Addr, denom).Amount.BigInt()
 	assert.Zero(t, expectedBalance.Cmp(actualBalance), "expected %s, got %s", expectedBalance, actualBalance)
 }
+
+// BigPow multiplies "amount" by 10 to the "pow10Exp".
+func BigPow(amount *big.Int, pow10Exp uint8) (powAmount *big.Int) {
+	pow10 := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(pow10Exp)), nil)
+	return new(big.Int).Mul(amount, pow10)
+}
diff --git a/x/evm/evmtest/test_deps.go b/x/evm/evmtest/test_deps.go
index 9c6015933..1810b1c8f 100644
--- a/x/evm/evmtest/test_deps.go
+++ b/x/evm/evmtest/test_deps.go
@@ -1,6 +1,8 @@
 package evmtest
 
 import (
+	"context"
+
 	sdk "github.com/cosmos/cosmos-sdk/types"
 
 	gethcommon "github.com/ethereum/go-ethereum/common"
@@ -54,3 +56,7 @@ func (deps TestDeps) StateDB() *statedb.StateDB {
 func (deps *TestDeps) GethSigner() gethcore.Signer {
 	return gethcore.LatestSignerForChainID(deps.App.EvmKeeper.EthChainID(deps.Ctx))
 }
+
+func (deps TestDeps) GoCtx() context.Context {
+	return sdk.WrapSDKContext(deps.Ctx)
+}
diff --git a/x/evm/evmtest/tx.go b/x/evm/evmtest/tx.go
index 107a15593..dde679851 100644
--- a/x/evm/evmtest/tx.go
+++ b/x/evm/evmtest/tx.go
@@ -20,6 +20,8 @@ import (
 
 	srvconfig "github.com/NibiruChain/nibiru/v2/app/server/config"
 
+	"github.com/cosmos/cosmos-sdk/crypto/keyring"
+
 	"github.com/NibiruChain/nibiru/v2/x/evm"
 	"github.com/NibiruChain/nibiru/v2/x/evm/embeds"
 )
@@ -123,7 +125,9 @@ func ExecuteNibiTransfer(deps *TestDeps, t *testing.T) *evm.MsgEthereumTx {
 		To:    &recipient,
 		Nonce: (*hexutil.Uint64)(&nonce),
 	}
-	ethTxMsg, err := GenerateAndSignEthTxMsg(txArgs, deps)
+	ethTxMsg, gethSigner, krSigner, err := GenerateEthTxMsgAndSigner(txArgs, deps, deps.Sender)
+	require.NoError(t, err)
+	err = ethTxMsg.Sign(gethSigner, krSigner)
 	require.NoError(t, err)
 
 	resp, err := deps.App.EvmKeeper.EthereumTx(sdk.WrapSDKContext(deps.Ctx), ethTxMsg)
@@ -153,18 +157,20 @@ func DeployContract(
 	bytecodeForCall := append(contract.Bytecode, packedArgs...)
 
 	nonce := deps.StateDB().GetNonce(deps.Sender.EthAddr)
-	msgEthTx, err := GenerateAndSignEthTxMsg(
+	ethTxMsg, gethSigner, krSigner, err := GenerateEthTxMsgAndSigner(
 		evm.JsonTxArgs{
 			Nonce: (*hexutil.Uint64)(&nonce),
 			Input: (*hexutil.Bytes)(&bytecodeForCall),
 			From:  &deps.Sender.EthAddr,
-		}, deps,
+		}, deps, deps.Sender,
 	)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to generate and sign eth tx msg")
+	} else if err := ethTxMsg.Sign(gethSigner, krSigner); err != nil {
+		return nil, errors.Wrap(err, "failed to generate and sign eth tx msg")
 	}
 
-	resp, err := deps.App.EvmKeeper.EthereumTx(sdk.WrapSDKContext(deps.Ctx), msgEthTx)
+	resp, err := deps.App.EvmKeeper.EthereumTx(sdk.WrapSDKContext(deps.Ctx), ethTxMsg)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to execute ethereum tx")
 	}
@@ -174,7 +180,7 @@ func DeployContract(
 
 	return &DeployContractResult{
 		TxResp:       resp,
-		EthTxMsg:     msgEthTx,
+		EthTxMsg:     ethTxMsg,
 		ContractData: contract,
 		Nonce:        nonce,
 		ContractAddr: crypto.CreateAddress(deps.Sender.EthAddr, nonce),
@@ -184,7 +190,11 @@ func DeployContract(
 // DeployAndExecuteERC20Transfer deploys contract, executes transfer and returns tx hash
 func DeployAndExecuteERC20Transfer(
 	deps *TestDeps, t *testing.T,
-) (erc20Transfer *evm.MsgEthereumTx, predecessors []*evm.MsgEthereumTx) {
+) (
+	erc20Transfer *evm.MsgEthereumTx,
+	predecessors []*evm.MsgEthereumTx,
+	contractAddr gethcommon.Address,
+) {
 	// TX 1: Deploy ERC-20 contract
 	deployResp, err := DeployContract(deps, embeds.SmartContract_TestERC20)
 	require.NoError(t, err)
@@ -192,7 +202,7 @@ func DeployAndExecuteERC20Transfer(
 	nonce := deployResp.Nonce
 
 	// Contract address is deterministic
-	contractAddress := crypto.CreateAddress(deps.Sender.EthAddr, nonce)
+	contractAddr = crypto.CreateAddress(deps.Sender.EthAddr, nonce)
 	deps.App.Commit()
 	predecessors = []*evm.MsgEthereumTx{
 		deployResp.EthTxMsg,
@@ -206,27 +216,67 @@ func DeployAndExecuteERC20Transfer(
 	nonce = deps.StateDB().GetNonce(deps.Sender.EthAddr)
 	txArgs := evm.JsonTxArgs{
 		From:  &deps.Sender.EthAddr,
-		To:    &contractAddress,
+		To:    &contractAddr,
 		Nonce: (*hexutil.Uint64)(&nonce),
 		Data:  (*hexutil.Bytes)(&input),
 	}
-	erc20Transfer, err = GenerateAndSignEthTxMsg(txArgs, deps)
+	erc20Transfer, gethSigner, krSigner, err := GenerateEthTxMsgAndSigner(txArgs, deps, deps.Sender)
+	require.NoError(t, err)
+	err = erc20Transfer.Sign(gethSigner, krSigner)
 	require.NoError(t, err)
 
-	resp, err := deps.App.EvmKeeper.EthereumTx(sdk.WrapSDKContext(deps.Ctx), erc20Transfer)
+	resp, err := deps.App.EvmKeeper.EthereumTx(deps.GoCtx(), erc20Transfer)
 	require.NoError(t, err)
 	require.Empty(t, resp.VmError)
 
-	return erc20Transfer, predecessors
+	return erc20Transfer, predecessors, contractAddr
 }
 
-// GenerateAndSignEthTxMsg estimates gas, sets gas limit and sings the tx
-func GenerateAndSignEthTxMsg(
-	jsonTxArgs evm.JsonTxArgs, deps *TestDeps,
-) (*evm.MsgEthereumTx, error) {
+func CallContractTx(
+	deps *TestDeps,
+	contractAddr gethcommon.Address,
+	input []byte,
+	sender EthPrivKeyAcc,
+) (ethTxMsg *evm.MsgEthereumTx, resp *evm.MsgEthereumTxResponse, err error) {
+	nonce := deps.StateDB().GetNonce(sender.EthAddr)
+	ethTxMsg, gethSigner, krSigner, err := GenerateEthTxMsgAndSigner(evm.JsonTxArgs{
+		From:  &sender.EthAddr,
+		To:    &contractAddr,
+		Nonce: (*hexutil.Uint64)(&nonce),
+		Data:  (*hexutil.Bytes)(&input),
+	}, deps, sender)
+	if err != nil {
+		err = fmt.Errorf("CallContract error during tx generation: %w", err)
+		return
+	}
+
+	err = ethTxMsg.Sign(gethSigner, krSigner)
+	if err != nil {
+		err = fmt.Errorf("CallContract error during signature: %w", err)
+		return
+	}
+
+	resp, err = deps.EvmKeeper.EthereumTx(deps.GoCtx(), ethTxMsg)
+	return ethTxMsg, resp, err
+}
+
+// GenerateEthTxMsgAndSigner estimates gas, sets gas limit and returns signer for
+// the tx.
+//
+// Usage:
+//
+//	```go
+//	evmTxMsg, gethSigner, krSigner, _ := GenerateEthTxMsgAndSigner(
+//	    jsonTxArgs, &deps, sender,
+//	)
+//	err := evmTxMsg.Sign(gethSigner, sender.KeyringSigner)
+//	```
+func GenerateEthTxMsgAndSigner(
+	jsonTxArgs evm.JsonTxArgs, deps *TestDeps, sender EthPrivKeyAcc,
+) (evmTxMsg *evm.MsgEthereumTx, gethSigner gethcore.Signer, krSigner keyring.Signer, err error) {
 	estimateArgs, err := json.Marshal(&jsonTxArgs)
 	if err != nil {
-		return nil, err
+		return
 	}
 	res, err := deps.App.EvmKeeper.EstimateGas(
 		sdk.WrapSDKContext(deps.Ctx),
@@ -238,13 +288,13 @@ func GenerateAndSignEthTxMsg(
 		},
 	)
 	if err != nil {
-		return nil, err
+		return
 	}
 	jsonTxArgs.Gas = (*hexutil.Uint64)(&res.Gas)
 
-	msgEthTx := jsonTxArgs.ToMsgEthTx()
-	gethSigner := gethcore.LatestSignerForChainID(deps.App.EvmKeeper.EthChainID(deps.Ctx))
-	return msgEthTx, msgEthTx.Sign(gethSigner, deps.Sender.KeyringSigner)
+	evmTxMsg = jsonTxArgs.ToMsgEthTx()
+	gethSigner = gethcore.LatestSignerForChainID(deps.App.EvmKeeper.EthChainID(deps.Ctx))
+	return evmTxMsg, gethSigner, sender.KeyringSigner, nil
 }
 
 func TransferWei(
diff --git a/x/evm/evmtest/tx_test.go b/x/evm/evmtest/tx_test.go
new file mode 100644
index 000000000..e9cc956c1
--- /dev/null
+++ b/x/evm/evmtest/tx_test.go
@@ -0,0 +1,90 @@
+// Copyright (c) 2023-2024 Nibi, Inc.
+package evmtest_test
+
+import (
+	"math/big"
+
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	"github.com/ethereum/go-ethereum/crypto"
+
+	"github.com/NibiruChain/nibiru/v2/x/common/testutil/testapp"
+	"github.com/NibiruChain/nibiru/v2/x/evm"
+	"github.com/NibiruChain/nibiru/v2/x/evm/embeds"
+	"github.com/NibiruChain/nibiru/v2/x/evm/evmtest"
+)
+
+func (s *Suite) TestCallContractTx() {
+	deps := evmtest.NewTestDeps()
+
+	s.T().Log("Deploy some ERC20")
+	deployArgs := []any{"name", "SYMBOL", uint8(18)}
+	deployResp, err := evmtest.DeployContract(
+		&deps,
+		embeds.SmartContract_ERC20Minter,
+		deployArgs...,
+	)
+	s.Require().NoError(err, deployResp)
+	contractAddr := crypto.CreateAddress(deps.Sender.EthAddr, deployResp.Nonce)
+	gotContractAddr := deployResp.ContractAddr
+	s.Require().Equal(contractAddr, gotContractAddr)
+
+	s.T().Log("expect zero balance")
+	{
+		wantBal := big.NewInt(0)
+		evmtest.AssertERC20BalanceEqual(
+			s.T(), deps, contractAddr, deps.Sender.EthAddr, wantBal,
+		)
+	}
+
+	abi := deployResp.ContractData.ABI
+	s.T().Log("mint some tokens")
+	{
+		amount := big.NewInt(69_420)
+		to := deps.Sender.EthAddr
+		callArgs := []any{to, amount}
+		input, err := abi.Pack(
+			"mint", callArgs...,
+		)
+		s.Require().NoError(err)
+		_, resp, err := evmtest.CallContractTx(
+			&deps,
+			contractAddr,
+			input,
+			deps.Sender,
+		)
+		s.Require().NoError(err)
+		s.Require().Empty(resp.VmError)
+	}
+
+	s.T().Log("expect nonzero balance")
+	{
+		wantBal := big.NewInt(69_420)
+		evmtest.AssertERC20BalanceEqual(
+			s.T(), deps, contractAddr, deps.Sender.EthAddr, wantBal,
+		)
+	}
+}
+
+func (s *Suite) TestTransferWei() {
+	deps := evmtest.NewTestDeps()
+
+	s.Require().NoError(testapp.FundAccount(
+		deps.App.BankKeeper,
+		deps.Ctx,
+		deps.Sender.NibiruAddr,
+		sdk.NewCoins(sdk.NewCoin(evm.EVMBankDenom, sdk.NewInt(69_420))),
+	))
+
+	randomAcc := evmtest.NewEthPrivAcc()
+	to := randomAcc.EthAddr
+	err := evmtest.TransferWei(&deps, to, evm.NativeToWei(big.NewInt(420)))
+	s.Require().NoError(err)
+
+	evmtest.AssertBankBalanceEqual(
+		s.T(), deps, evm.EVMBankDenom, deps.Sender.EthAddr, big.NewInt(69_000),
+	)
+
+	s.Run("DeployAndExecuteERC20Transfer", func() {
+		evmtest.DeployAndExecuteERC20Transfer(&deps, s.T())
+	})
+}
diff --git a/x/evm/keeper/erc20.go b/x/evm/keeper/erc20.go
index abecd80ca..10404bea4 100644
--- a/x/evm/keeper/erc20.go
+++ b/x/evm/keeper/erc20.go
@@ -2,7 +2,6 @@
 package keeper
 
 import (
-	"encoding/json"
 	"fmt"
 	"math/big"
 
@@ -10,13 +9,13 @@ import (
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	gethabi "github.com/ethereum/go-ethereum/accounts/abi"
 	gethcommon "github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/common/hexutil"
 	gethcore "github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/core/vm"
 
 	serverconfig "github.com/NibiruChain/nibiru/v2/app/server/config"
+
 	"github.com/NibiruChain/nibiru/v2/x/evm"
 	"github.com/NibiruChain/nibiru/v2/x/evm/embeds"
-	"github.com/NibiruChain/nibiru/v2/x/evm/statedb"
 )
 
 // ERC20 returns a mutable reference to the keeper with an ERC20 contract ABI and
@@ -57,7 +56,8 @@ func (e erc20Calls) Mint(
 	if err != nil {
 		return nil, fmt.Errorf("failed to pack ABI args: %w", err)
 	}
-	return e.CallContractWithInput(ctx, from, &contract, true, input)
+	evmResp, _, err = e.CallContractWithInput(ctx, from, &contract, true, input)
+	return evmResp, err
 }
 
 /*
@@ -78,7 +78,7 @@ func (e erc20Calls) Transfer(
 	if err != nil {
 		return false, fmt.Errorf("failed to pack ABI args: %w", err)
 	}
-	resp, err := e.CallContractWithInput(ctx, from, &contract, true, input)
+	resp, _, err := e.CallContractWithInput(ctx, from, &contract, true, input)
 	if err != nil {
 		return false, err
 	}
@@ -118,7 +118,8 @@ func (e erc20Calls) Burn(
 		return
 	}
 	commit := true
-	return e.CallContractWithInput(ctx, from, &contract, commit, input)
+	evmResp, _, err = e.CallContractWithInput(ctx, from, &contract, commit, input)
+	return
 }
 
 // CallContract invokes a smart contract on the method specified by [methodName]
@@ -149,30 +150,33 @@ func (k Keeper) CallContract(
 	if err != nil {
 		return nil, fmt.Errorf("failed to pack ABI args: %w", err)
 	}
-	return k.CallContractWithInput(ctx, fromAcc, contract, commit, contractInput)
+	evmResp, _, err = k.CallContractWithInput(ctx, fromAcc, contract, commit, contractInput)
+	return evmResp, err
 }
 
-// CallContractWithInput invokes a smart contract with the given [contractInput].
+// CallContractWithInput invokes a smart contract with the given [contractInput]
+// or deploys a new contract.
 //
 // Parameters:
 //   - ctx: The SDK context for the transaction.
 //   - fromAcc: The Ethereum address of the account initiating the contract call.
-//   - contract: Pointer to the Ethereum address of the contract to be called.
-//   - commit: Boolean flag indicating whether to commit the transaction (true) or simulate it (false).
+//   - contract: Pointer to the Ethereum address of the contract. Nil if new
+//     contract is deployed.
+//   - commit: Boolean flag indicating whether to commit the transaction (true)
+//     or simulate it (false).
 //   - contractInput: Hexadecimal-encoded bytes use as input data to the call.
 //
 // Note: This function handles both contract method calls and simulations,
-// depending on the 'commit' parameter. It uses a default gas limit for
-// simulations and estimates gas for actual transactions.
+// depending on the 'commit' parameter. It uses a default gas limit.
 func (k Keeper) CallContractWithInput(
 	ctx sdk.Context,
 	fromAcc gethcommon.Address,
 	contract *gethcommon.Address,
 	commit bool,
 	contractInput []byte,
-) (evmResp *evm.MsgEthereumTxResponse, err error) {
-	// This is a `defer` pattern to add behavior that runs in the case that the error is
-	// non-nil, creating a concise way to add extra information.
+) (evmResp *evm.MsgEthereumTxResponse, evmObj *vm.EVM, err error) {
+	// This is a `defer` pattern to add behavior that runs in the case that the
+	// error is non-nil, creating a concise way to add extra information.
 	defer func() {
 		if err != nil {
 			err = fmt.Errorf("CallContractError: %w", err)
@@ -180,13 +184,9 @@ func (k Keeper) CallContractWithInput(
 	}()
 	nonce := k.GetAccNonce(ctx, fromAcc)
 
+	// Gas cap sufficient for all "honest" ERC20 calls without malicious (gas
+	// intensive) code in contracts
 	gasLimit := serverconfig.DefaultEthCallGasLimit
-	gasLimit, err = computeCommitGasLimit(
-		commit, gasLimit, &fromAcc, contract, contractInput, k, ctx,
-	)
-	if err != nil {
-		return nil, err
-	}
 
 	unusedBigInt := big.NewInt(0)
 	evmMsg := gethcore.NewMessage(
@@ -210,70 +210,58 @@ func (k Keeper) CallContractWithInput(
 		k.EthChainID(ctx),
 	)
 	if err != nil {
-		return nil, errors.Wrapf(err, "failed to load evm config")
-	}
-
-	txConfig := statedb.NewEmptyTxConfig(gethcommon.BytesToHash(ctx.HeaderHash()))
-	evmResp, err = k.ApplyEvmMsg(
-		ctx, evmMsg, evm.NewNoOpTracer(), commit, evmCfg, txConfig,
-	)
-	if err != nil {
-		return nil, errors.Wrapf(err, "failed to apply EVM message")
-	}
-
-	if evmResp.Failed() {
-		return nil, errors.Wrapf(err, "EVM execution failed: %s", evmResp.VmError)
+		err = errors.Wrapf(err, "failed to load EVM config")
+		return
 	}
 
-	return evmResp, err
-}
-
-// computeCommitGasLimit: If the transition is meant to mutate state, this
-// function computes an appopriates gas limit for the call with "contractInput"
-// bytes against the given contract address.
-//
-// ℹ️ This creates a cached context for gas estimation, which is essential
-// because state transitions can occur outside of the EVM that are triggered
-// by Ethereum transactions, like in the case of precompiled contract or
-// custom EVM hook that runs after tx execution. Gas estimation in that case
-// could mutate the "ctx" object and result in falty resulting state, so we
-// must cache here to avoid this issue.
-func computeCommitGasLimit(
-	commit bool,
-	gasLimit uint64,
-	fromAcc, contract *gethcommon.Address,
-	contractInput []byte,
-	k Keeper,
-	ctx sdk.Context,
-) (newGasLimit uint64, err error) {
-	if !commit {
-		return gasLimit, nil
-	}
+	// Generating TxConfig with an empty tx hash as there is no actual eth tx
+	// sent by a user
+	txConfig := k.TxConfig(ctx, gethcommon.BigToHash(big.NewInt(0)))
 
-	cachedCtx, _ := ctx.CacheContext() // IMPORTANT!
+	// Using tmp context to not modify the state in case of evm revert
+	tmpCtx, commitCtx := ctx.CacheContext()
 
-	jsonArgs, err := json.Marshal(evm.JsonTxArgs{
-		From: fromAcc,
-		To:   contract,
-		Data: (*hexutil.Bytes)(&contractInput),
-	})
-	if err != nil {
-		return gasLimit, fmt.Errorf("failed compute gas limit to marshal tx args: %w", err)
-	}
-
-	gasRes, err := k.EstimateGasForEvmCallType(
-		sdk.WrapSDKContext(cachedCtx),
-		&evm.EthCallRequest{
-			Args:   jsonArgs,
-			GasCap: gasLimit,
-		},
-		evm.CallTypeSmart,
+	evmResp, evmObj, err = k.ApplyEvmMsg(
+		tmpCtx, evmMsg, evm.NewNoOpTracer(), commit, evmCfg, txConfig,
 	)
 	if err != nil {
-		return gasLimit, fmt.Errorf("failed to compute gas limit: %w", err)
+		// We don't know the actual gas used, so consuming the gas limit
+		k.ResetGasMeterAndConsumeGas(ctx, gasLimit)
+		err = errors.Wrap(err, "failed to apply ethereum core message")
+		return
+	}
+	if evmResp.Failed() {
+		k.ResetGasMeterAndConsumeGas(ctx, evmResp.GasUsed)
+		if evmResp.VmError != vm.ErrOutOfGas.Error() {
+			if evmResp.VmError == vm.ErrExecutionReverted.Error() {
+				err = fmt.Errorf("VMError: %w", evm.NewExecErrorWithReason(evmResp.Ret))
+				return
+			}
+			err = fmt.Errorf("VMError: %s", evmResp.VmError)
+			return
+		}
+		err = fmt.Errorf("gas required exceeds allowance (%d)", gasLimit)
+		return
+	} 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())
+				return nil, nil, errors.Wrap(err, "error adding transient gas used to block")
+			}
+			k.ResetGasMeterAndConsumeGas(ctx, totalGasUsed)
+			k.updateBlockBloom(ctx, evmResp, uint64(txConfig.LogIndex))
+			err = k.EmitEthereumTxEvents(ctx, contract, gethcore.LegacyTxType, evmMsg, evmResp)
+			if err != nil {
+				return nil, nil, errors.Wrap(err, "error emitting ethereum tx events")
+			}
+			blockTxIdx := uint64(txConfig.TxIndex) + 1
+			k.EvmState.BlockTxIndex.Set(ctx, blockTxIdx)
+		}
+		return evmResp, evmObj, nil
 	}
-
-	return gasRes.Gas, nil
 }
 
 func (k Keeper) LoadERC20Name(
diff --git a/x/evm/keeper/erc20_test.go b/x/evm/keeper/erc20_test.go
index d328ea4e6..4b1dc10fa 100644
--- a/x/evm/keeper/erc20_test.go
+++ b/x/evm/keeper/erc20_test.go
@@ -16,7 +16,10 @@ func (s *Suite) TestERC20Calls() {
 
 	s.T().Log("Mint tokens - Fail from non-owner")
 	{
-		_, err := deps.EvmKeeper.ERC20().Mint(contract, deps.Sender.EthAddr, evm.EVM_MODULE_ADDRESS, big.NewInt(69_420), deps.Ctx)
+		_, err := deps.EvmKeeper.ERC20().Mint(
+			contract, deps.Sender.EthAddr, evm.EVM_MODULE_ADDRESS,
+			big.NewInt(69_420), deps.Ctx,
+		)
 		s.ErrorContains(err, evm.ErrOwnable)
 	}
 
diff --git a/x/evm/keeper/evm_state.go b/x/evm/keeper/evm_state.go
index 426e78d8e..9e78cc17b 100644
--- a/x/evm/keeper/evm_state.go
+++ b/x/evm/keeper/evm_state.go
@@ -2,6 +2,7 @@
 package keeper
 
 import (
+	"fmt"
 	"math/big"
 
 	"github.com/NibiruChain/collections"
@@ -116,8 +117,13 @@ func (k Keeper) GetParams(ctx sdk.Context) (params evm.Params) {
 }
 
 // SetParams: Setter for the module parameters.
-func (k Keeper) SetParams(ctx sdk.Context, params evm.Params) {
+func (k Keeper) SetParams(ctx sdk.Context, params evm.Params) (err error) {
+	if params.CreateFuntokenFee.IsNegative() {
+		return fmt.Errorf("createFuntokenFee cannot be negative: %s", params.CreateFuntokenFee)
+	}
+
 	k.EvmState.ModuleParams.Set(ctx, params)
+	return
 }
 
 // SetState updates contract storage and deletes if the value is empty.
diff --git a/x/evm/keeper/funtoken_from_coin.go b/x/evm/keeper/funtoken_from_coin.go
index 82d7017f6..6f0f2efd0 100644
--- a/x/evm/keeper/funtoken_from_coin.go
+++ b/x/evm/keeper/funtoken_from_coin.go
@@ -78,7 +78,7 @@ func (k *Keeper) deployERC20ForBankCoin(
 	bytecodeForCall := append(embeds.SmartContract_ERC20Minter.Bytecode, packedArgs...)
 
 	// nil address for contract creation
-	_, err = k.CallContractWithInput(
+	_, _, err = k.CallContractWithInput(
 		ctx, evm.EVM_MODULE_ADDRESS, nil, true, bytecodeForCall,
 	)
 	if err != nil {
diff --git a/x/evm/keeper/funtoken_from_coin_test.go b/x/evm/keeper/funtoken_from_coin_test.go
index cb9b87ffb..1f5e3a85a 100644
--- a/x/evm/keeper/funtoken_from_coin_test.go
+++ b/x/evm/keeper/funtoken_from_coin_test.go
@@ -282,7 +282,7 @@ func (s *FunTokenFromCoinSuite) TestConvertCoinToEvmAndBack() {
 	// Check 3: erc-20 balance
 	balance, err = deps.EvmKeeper.ERC20().BalanceOf(funTokenErc20Addr.Address, alice.EthAddr, deps.Ctx)
 	s.Require().NoError(err)
-	s.Require().Zero(balance.Cmp(big.NewInt(0)))
+	s.Require().Equal("0", balance.String())
 
 	s.T().Log("sad: Convert more erc-20 to back to bank coin, insufficient funds")
 	_, err = deps.EvmKeeper.CallContract(
diff --git a/x/evm/keeper/funtoken_from_erc20_test.go b/x/evm/keeper/funtoken_from_erc20_test.go
index 3eaf1462c..eb209f7ca 100644
--- a/x/evm/keeper/funtoken_from_erc20_test.go
+++ b/x/evm/keeper/funtoken_from_erc20_test.go
@@ -160,7 +160,7 @@ func (s *FunTokenFromErc20Suite) TestCreateFunTokenFromERC20() {
 	s.ErrorContains(err, "either the \"from_erc20\" or \"from_bank_denom\" must be set")
 }
 
-func (s *FunTokenFromErc20Suite) TestSendFromEvmToCosmos() {
+func (s *FunTokenFromErc20Suite) TestSendFromEvmToBank() {
 	deps := evmtest.NewTestDeps()
 
 	s.T().Log("Deploy ERC20")
@@ -210,7 +210,7 @@ func (s *FunTokenFromErc20Suite) TestSendFromEvmToCosmos() {
 
 	randomAcc := testutil.AccAddress()
 
-	s.T().Log("send erc20 tokens to cosmos")
+	s.T().Log("send erc20 tokens to Bank")
 	_, err = deps.EvmKeeper.CallContract(
 		deps.Ctx,
 		embeds.SmartContract_FunToken.ABI,
@@ -231,8 +231,8 @@ func (s *FunTokenFromErc20Suite) TestSendFromEvmToCosmos() {
 		deps.App.BankKeeper.GetBalance(deps.Ctx, randomAcc, bankDemon).Amount,
 	)
 
-	s.T().Log("sad: send too many erc20 tokens to cosmos")
-	_, err = deps.EvmKeeper.CallContract(
+	s.T().Log("sad: send too many erc20 tokens to Bank")
+	evmResp, err := deps.EvmKeeper.CallContract(
 		deps.Ctx,
 		embeds.SmartContract_FunToken.ABI,
 		deps.Sender.EthAddr,
@@ -243,9 +243,10 @@ func (s *FunTokenFromErc20Suite) TestSendFromEvmToCosmos() {
 		big.NewInt(70_000),
 		randomAcc.String(),
 	)
-	s.Require().Error(err)
+	s.T().Log("check balances")
+	s.Require().Error(err, evmResp.String())
 
-	s.T().Log("send cosmos tokens back to erc20")
+	s.T().Log("send Bank tokens back to erc20")
 	_, err = deps.EvmKeeper.ConvertCoinToEvm(sdk.WrapSDKContext(deps.Ctx),
 		&evm.MsgConvertCoinToEvm{
 			ToEthAddr: eth.EIP55Addr{
@@ -264,7 +265,7 @@ func (s *FunTokenFromErc20Suite) TestSendFromEvmToCosmos() {
 		deps.App.BankKeeper.GetBalance(deps.Ctx, randomAcc, bankDemon).Amount.Equal(sdk.NewInt(0)),
 	)
 
-	s.T().Log("sad: send too many cosmos tokens back to erc20")
+	s.T().Log("sad: send too many Bank tokens back to erc20")
 	_, err = deps.EvmKeeper.ConvertCoinToEvm(sdk.WrapSDKContext(deps.Ctx),
 		&evm.MsgConvertCoinToEvm{
 			ToEthAddr: eth.EIP55Addr{
@@ -277,6 +278,101 @@ func (s *FunTokenFromErc20Suite) TestSendFromEvmToCosmos() {
 	s.Require().Error(err)
 }
 
+// TestCreateFunTokenFromERC20MaliciousName tries to create funtoken from a contract
+// with a malicious (gas intensive) name() function.
+// Fun token should fail creation with "out of gas"
+func (s *FunTokenFromErc20Suite) TestCreateFunTokenFromERC20MaliciousName() {
+	deps := evmtest.NewTestDeps()
+
+	s.T().Log("Deploy ERC20MaliciousName")
+	metadata := keeper.ERC20Metadata{
+		Name:     "erc20name",
+		Symbol:   "TOKEN",
+		Decimals: 18,
+	}
+	deployResp, err := evmtest.DeployContract(
+		&deps, embeds.SmartContract_TestERC20MaliciousName,
+		metadata.Name, metadata.Symbol, metadata.Decimals,
+	)
+	s.Require().NoError(err)
+
+	erc20Addr := eth.EIP55Addr{
+		Address: deployResp.ContractAddr,
+	}
+
+	s.T().Log("sad: CreateFunToken for ERC20 with malicious name")
+	s.Require().NoError(testapp.FundAccount(
+		deps.App.BankKeeper,
+		deps.Ctx,
+		deps.Sender.NibiruAddr,
+		deps.EvmKeeper.FeeForCreateFunToken(deps.Ctx),
+	))
+
+	_, err = deps.EvmKeeper.CreateFunToken(
+		sdk.WrapSDKContext(deps.Ctx),
+		&evm.MsgCreateFunToken{
+			FromErc20: &erc20Addr,
+			Sender:    deps.Sender.NibiruAddr.String(),
+		},
+	)
+	s.Require().ErrorContains(err, "gas required exceeds allowance")
+}
+
+// TestFunTokenFromERC20MaliciousTransfer creates a funtoken from a contract
+// with a malicious (gas intensive) transfer() function.
+// Fun token should be created but sending from erc20 to bank should fail with out of gas
+func (s *FunTokenFromErc20Suite) TestFunTokenFromERC20MaliciousTransfer() {
+	deps := evmtest.NewTestDeps()
+
+	s.T().Log("Deploy ERC20MaliciousTransfer")
+	metadata := keeper.ERC20Metadata{
+		Name:     "erc20name",
+		Symbol:   "TOKEN",
+		Decimals: 18,
+	}
+	deployResp, err := evmtest.DeployContract(
+		&deps, embeds.SmartContract_TestERC20MaliciousTransfer,
+		metadata.Name, metadata.Symbol, metadata.Decimals,
+	)
+	s.Require().NoError(err)
+
+	erc20Addr := eth.EIP55Addr{
+		Address: deployResp.ContractAddr,
+	}
+
+	s.T().Log("happy: CreateFunToken for ERC20 with malicious transfer")
+	s.Require().NoError(testapp.FundAccount(
+		deps.App.BankKeeper,
+		deps.Ctx,
+		deps.Sender.NibiruAddr,
+		deps.EvmKeeper.FeeForCreateFunToken(deps.Ctx),
+	))
+
+	_, err = deps.EvmKeeper.CreateFunToken(
+		sdk.WrapSDKContext(deps.Ctx),
+		&evm.MsgCreateFunToken{
+			FromErc20: &erc20Addr,
+			Sender:    deps.Sender.NibiruAddr.String(),
+		},
+	)
+	s.Require().NoError(err)
+	randomAcc := testutil.AccAddress()
+
+	s.T().Log("send erc20 tokens to cosmos")
+	_, err = deps.EvmKeeper.CallContract(
+		deps.Ctx,
+		embeds.SmartContract_FunToken.ABI,
+		deps.Sender.EthAddr,
+		&precompile.PrecompileAddr_FunToken,
+		true,
+		"bankSend",
+		deployResp.ContractAddr,
+		big.NewInt(1),
+		randomAcc.String(),
+	)
+	s.Require().ErrorContains(err, "gas required exceeds allowance")
+}
+
 type FunTokenFromErc20Suite struct {
 	suite.Suite
 }
diff --git a/x/evm/keeper/grpc_query.go b/x/evm/keeper/grpc_query.go
index 1ae3c19be..9cc9290e9 100644
--- a/x/evm/keeper/grpc_query.go
+++ b/x/evm/keeper/grpc_query.go
@@ -284,7 +284,7 @@ func (k *Keeper) EthCall(
 	txConfig := statedb.NewEmptyTxConfig(gethcommon.BytesToHash(ctx.HeaderHash()))
 
 	// pass false to not commit StateDB
-	res, err := k.ApplyEvmMsg(ctx, msg, nil, false, cfg, txConfig)
+	res, _, err := k.ApplyEvmMsg(ctx, msg, nil, false, cfg, txConfig)
 	if err != nil {
 		return nil, grpcstatus.Error(grpccodes.Internal, err.Error())
 	}
@@ -422,7 +422,7 @@ func (k Keeper) EstimateGasForEvmCallType(
 				WithTransientKVGasConfig(storetypes.GasConfig{})
 		}
 		// pass false to not commit StateDB
-		rsp, err = k.ApplyEvmMsg(tmpCtx, msg, nil, false, cfg, txConfig)
+		rsp, _, err = k.ApplyEvmMsg(tmpCtx, msg, nil, false, cfg, txConfig)
 		if err != nil {
 			if errors.Is(err, core.ErrIntrinsicGas) {
 				return true, nil, nil // Special case, raise gas limit
@@ -518,7 +518,7 @@ func (k Keeper) TraceTx(
 		ctx = ctx.WithGasMeter(eth.NewInfiniteGasMeterWithLimit(msg.Gas())).
 			WithKVGasConfig(storetypes.GasConfig{}).
 			WithTransientKVGasConfig(storetypes.GasConfig{})
-		rsp, err := k.ApplyEvmMsg(ctx, msg, evm.NewNoOpTracer(), true, cfg, txConfig)
+		rsp, _, err := k.ApplyEvmMsg(ctx, msg, evm.NewNoOpTracer(), true, cfg, txConfig)
 		if err != nil {
 			continue
 		}
@@ -663,15 +663,14 @@ func (k Keeper) TraceBlock(
 		contextHeight = 1
 	}
 
-	ctx := sdk.UnwrapSDKContext(goCtx)
-	ctx = ctx.WithBlockHeight(contextHeight)
-	ctx = ctx.WithBlockTime(req.BlockTime)
-	ctx = ctx.WithHeaderHash(gethcommon.Hex2Bytes(req.BlockHash))
-
-	// to get the base fee we only need the block max gas in the consensus params
-	ctx = ctx.WithConsensusParams(&cmtproto.ConsensusParams{
-		Block: &cmtproto.BlockParams{MaxGas: req.BlockMaxGas},
-	})
+	ctx := sdk.UnwrapSDKContext(goCtx).
+		WithBlockHeight(contextHeight).
+		WithBlockTime(req.BlockTime).
+		WithHeaderHash(gethcommon.Hex2Bytes(req.BlockHash)).
+		// to get the base fee we only need the block max gas in the consensus params
+		WithConsensusParams(&cmtproto.ConsensusParams{
+			Block: &cmtproto.BlockParams{MaxGas: req.BlockMaxGas},
+		})
 
 	chainID := k.EthChainID(ctx)
 
@@ -801,7 +800,7 @@ func (k *Keeper) TraceEthTxMsg(
 	ctx = ctx.WithGasMeter(eth.NewInfiniteGasMeterWithLimit(msg.Gas())).
 		WithKVGasConfig(storetypes.GasConfig{}).
 		WithTransientKVGasConfig(storetypes.GasConfig{})
-	res, err := k.ApplyEvmMsg(ctx, msg, tracer, commitMessage, cfg, txConfig)
+	res, _, err := k.ApplyEvmMsg(ctx, msg, tracer, commitMessage, cfg, txConfig)
 	if err != nil {
 		return nil, 0, grpcstatus.Error(grpccodes.Internal, err.Error())
 	}
diff --git a/x/evm/keeper/grpc_query_test.go b/x/evm/keeper/grpc_query_test.go
index 46ce456d2..b16bea40c 100644
--- a/x/evm/keeper/grpc_query_test.go
+++ b/x/evm/keeper/grpc_query_test.go
@@ -447,7 +447,9 @@ func (s *Suite) TestQueryCode() {
 func (s *Suite) TestQueryParams() {
 	deps := evmtest.NewTestDeps()
 	want := evm.DefaultParams()
-	deps.EvmKeeper.SetParams(deps.Ctx, want)
+	err := deps.EvmKeeper.SetParams(deps.Ctx, want)
+	s.NoError(err)
+
 	gotResp, err := deps.EvmKeeper.Params(sdk.WrapSDKContext(deps.Ctx), nil)
 	s.NoError(err)
 	got := gotResp.Params
@@ -458,7 +460,8 @@ func (s *Suite) TestQueryParams() {
 
 	// Empty params to test the setter
 	want.EVMChannels = []string{"channel-420"}
-	deps.EvmKeeper.SetParams(deps.Ctx, want)
+	err = deps.EvmKeeper.SetParams(deps.Ctx, want)
+	s.NoError(err)
 	gotResp, err = deps.EvmKeeper.Params(sdk.WrapSDKContext(deps.Ctx), nil)
 	s.Require().NoError(err)
 	got = gotResp.Params
@@ -791,7 +794,7 @@ func (s *Suite) TestTraceTx() {
 		{
 			name: "happy: trace erc-20 transfer tx",
 			scenario: func(deps *evmtest.TestDeps) (req In, wantResp Out) {
-				txMsg, predecessors := evmtest.DeployAndExecuteERC20Transfer(deps, s.T())
+				txMsg, predecessors, _ := evmtest.DeployAndExecuteERC20Transfer(deps, s.T())
 
 				req = &evm.QueryTraceTxRequest{
 					Msg:          txMsg,
@@ -870,7 +873,7 @@ func (s *Suite) TestTraceBlock() {
 			name:  "happy: trace erc-20 transfer tx",
 			setup: nil,
 			scenario: func(deps *evmtest.TestDeps) (req In, wantResp Out) {
-				txMsg, _ := evmtest.DeployAndExecuteERC20Transfer(deps, s.T())
+				txMsg, _, _ := evmtest.DeployAndExecuteERC20Transfer(deps, s.T())
 				req = &evm.QueryTraceBlockRequest{
 					Txs: []*evm.MsgEthereumTx{
 						txMsg,
diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go
index 27e80a610..89a249dd3 100644
--- a/x/evm/keeper/msg_server.go
+++ b/x/evm/keeper/msg_server.go
@@ -31,8 +31,8 @@ var _ evm.MsgServer = &Keeper{}
 func (k *Keeper) EthereumTx(
 	goCtx context.Context, txMsg *evm.MsgEthereumTx,
 ) (evmResp *evm.MsgEthereumTxResponse, err error) {
-	// This is a `defer` pattern to add behavior that runs in the case that the error is
-	// non-nil, creating a concise way to add extra information.
+	// This is a `defer` pattern to add behavior that runs in the case that the
+	// error is non-nil, creating a concise way to add extra information.
 	defer func() {
 		if err != nil {
 			err = fmt.Errorf("EthereumTx error: %w", err)
@@ -54,15 +54,15 @@ func (k *Keeper) EthereumTx(
 
 	// get the signer according to the chain rules from the config and block height
 	signer := gethcore.MakeSigner(evmConfig.ChainConfig, big.NewInt(ctx.BlockHeight()))
-	msg, err := tx.AsMessage(signer, evmConfig.BaseFeeWei)
+	evmMsg, err := tx.AsMessage(signer, evmConfig.BaseFeeWei)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to return ethereum transaction as core message")
 	}
 
-	tmpCtx, commit := ctx.CacheContext()
+	tmpCtx, commitCtx := ctx.CacheContext()
 
 	// pass true to commit the StateDB
-	evmResp, err = k.ApplyEvmMsg(tmpCtx, msg, nil, true, evmConfig, txConfig)
+	evmResp, _, err = k.ApplyEvmMsg(tmpCtx, evmMsg, nil, true, evmConfig, txConfig)
 	if err != nil {
 		// when a transaction contains multiple msg, as long as one of the msg fails
 		// all gas will be deducted. so is not msg.Gas()
@@ -70,74 +70,38 @@ func (k *Keeper) EthereumTx(
 		return nil, errors.Wrap(err, "failed to apply ethereum core message")
 	}
 
-	logs := evm.LogsToEthereum(evmResp.Logs)
-
-	cumulativeGasUsed := evmResp.GasUsed
-	if ctx.BlockGasMeter() != nil {
-		limit := ctx.BlockGasMeter().Limit()
-		cumulativeGasUsed += ctx.BlockGasMeter().GasConsumed()
-		if cumulativeGasUsed > limit {
-			cumulativeGasUsed = limit
-		}
-	}
-
-	var contractAddr gethcommon.Address
-	if msg.To() == nil {
-		contractAddr = crypto.CreateAddress(msg.From(), msg.Nonce())
-	}
-
-	receipt := &gethcore.Receipt{
-		Type:              tx.Type(),
-		PostState:         nil, // TODO: intermediate state root
-		CumulativeGasUsed: cumulativeGasUsed,
-		Bloom:             k.EvmState.CalcBloomFromLogs(ctx, logs),
-		Logs:              logs,
-		TxHash:            txConfig.TxHash,
-		ContractAddress:   contractAddr,
-		GasUsed:           evmResp.GasUsed,
-		BlockHash:         txConfig.BlockHash,
-		BlockNumber:       big.NewInt(ctx.BlockHeight()),
-		TransactionIndex:  txConfig.TxIndex,
-	}
-
 	if !evmResp.Failed() {
-		receipt.Status = gethcore.ReceiptStatusSuccessful
-		commit()
+		commitCtx()
 	}
 
-	// refund gas in order to match the Ethereum gas consumption instead of the default SDK one.
+	// refund gas in order to match the Ethereum gas consumption instead of the
+	// default SDK one.
 	refundGas := uint64(0)
-	if msg.Gas() > evmResp.GasUsed {
-		refundGas = msg.Gas() - evmResp.GasUsed
+	if evmMsg.Gas() > evmResp.GasUsed {
+		refundGas = evmMsg.Gas() - evmResp.GasUsed
 	}
 	weiPerGas := txMsg.EffectiveGasPriceWeiPerGas(evmConfig.BaseFeeWei)
-	if err = k.RefundGas(ctx, msg.From(), refundGas, weiPerGas); err != nil {
-		return nil, errors.Wrapf(err, "error refunding leftover gas to sender %s", msg.From())
+	if err = k.RefundGas(ctx, evmMsg.From(), refundGas, weiPerGas); err != nil {
+		return nil, errors.Wrapf(err, "error refunding leftover gas to sender %s", evmMsg.From())
 	}
 
-	if len(receipt.Logs) > 0 {
-		// Update transient block bloom filter
-		k.EvmState.BlockBloom.Set(ctx, receipt.Bloom.Bytes())
-		blockLogSize := uint64(txConfig.LogIndex) + uint64(len(receipt.Logs))
-		k.EvmState.BlockLogSize.Set(ctx, blockLogSize)
-	}
+	k.updateBlockBloom(ctx, evmResp, uint64(txConfig.LogIndex))
 
 	totalGasUsed, err := k.AddToBlockGasUsed(ctx, evmResp.GasUsed)
 	if err != nil {
 		return nil, errors.Wrap(err, "error adding transient gas used to block")
 	}
 
-	// reset the gas meter for current cosmos transaction
+	// reset the gas meter for current TxMsg (EthereumTx)
 	k.ResetGasMeterAndConsumeGas(ctx, totalGasUsed)
 
-	err = k.EmitEthereumTxEvents(ctx, tx, msg, evmResp, contractAddr)
+	err = k.EmitEthereumTxEvents(ctx, tx.To(), tx.Type(), evmMsg, evmResp)
 	if err != nil {
 		return nil, errors.Wrap(err, "error emitting ethereum tx events")
 	}
 
 	blockTxIdx := uint64(txConfig.TxIndex) + 1
 	k.EvmState.BlockTxIndex.Set(ctx, blockTxIdx)
-
 	return evmResp, nil
 }
 
@@ -282,14 +246,14 @@ func (k *Keeper) ApplyEvmMsg(ctx sdk.Context,
 	commit bool,
 	evmConfig *statedb.EVMConfig,
 	txConfig statedb.TxConfig,
-) (*evm.MsgEthereumTxResponse, error) {
+) (resp *evm.MsgEthereumTxResponse, evmObj *vm.EVM, err error) {
 	var (
 		ret   []byte // return bytes from evm execution
 		vmErr error  // vm errors do not effect consensus and are therefore not assigned to err
 	)
 
 	stateDB := statedb.New(ctx, k, txConfig)
-	evmObj := k.NewEVM(ctx, msg, evmConfig, tracer, stateDB)
+	evmObj = k.NewEVM(ctx, msg, evmConfig, tracer, stateDB)
 
 	leftoverGas := msg.Gas()
 
@@ -308,7 +272,7 @@ func (k *Keeper) ApplyEvmMsg(ctx sdk.Context,
 	intrinsicGas, err := k.GetEthIntrinsicGas(ctx, msg, evmConfig.ChainConfig, contractCreation)
 	if err != nil {
 		// should have already been checked on Ante Handler
-		return nil, errors.Wrap(err, "intrinsic gas failed")
+		return nil, evmObj, errors.Wrap(err, "intrinsic gas failed")
 	}
 
 	// Check if the provided gas in the message is enough to cover the intrinsic
@@ -319,7 +283,7 @@ func (k *Keeper) ApplyEvmMsg(ctx sdk.Context,
 	// don't go through Ante Handler.
 	if leftoverGas < intrinsicGas {
 		// eth_estimateGas will check for this exact error
-		return nil, errors.Wrapf(
+		return nil, evmObj, errors.Wrapf(
 			core.ErrIntrinsicGas,
 			"apply message msg.Gas = %d, intrinsic gas = %d.",
 			leftoverGas, intrinsicGas,
@@ -339,7 +303,7 @@ func (k *Keeper) ApplyEvmMsg(ctx sdk.Context,
 
 	msgWei, err := ParseWeiAsMultipleOfMicronibi(msg.Value())
 	if err != nil {
-		return nil, err
+		return nil, evmObj, err
 	}
 
 	if contractCreation {
@@ -369,7 +333,7 @@ func (k *Keeper) ApplyEvmMsg(ctx sdk.Context,
 
 	// calculate gas refund
 	if msg.Gas() < leftoverGas {
-		return nil, errors.Wrap(evm.ErrGasOverflow, "apply message")
+		return nil, evmObj, errors.Wrap(evm.ErrGasOverflow, "apply message")
 	}
 	// refund gas
 	temporaryGasUsed := msg.Gas() - leftoverGas
@@ -388,7 +352,7 @@ func (k *Keeper) ApplyEvmMsg(ctx sdk.Context,
 	// The dirty states in `StateDB` is either committed or discarded after return
 	if commit {
 		if err := stateDB.Commit(); err != nil {
-			return nil, fmt.Errorf("failed to commit stateDB: %w", err)
+			return nil, evmObj, fmt.Errorf("failed to commit stateDB: %w", err)
 		}
 	}
 
@@ -397,11 +361,11 @@ func (k *Keeper) ApplyEvmMsg(ctx sdk.Context,
 	minimumGasUsed := gasLimit.Mul(minGasMultiplier)
 
 	if !minimumGasUsed.TruncateInt().IsUint64() {
-		return nil, errors.Wrapf(evm.ErrGasOverflow, "minimumGasUsed(%s) is not a uint64", minimumGasUsed.TruncateInt().String())
+		return nil, evmObj, errors.Wrapf(evm.ErrGasOverflow, "minimumGasUsed(%s) is not a uint64", minimumGasUsed.TruncateInt().String())
 	}
 
 	if msg.Gas() < leftoverGas {
-		return nil, errors.Wrapf(evm.ErrGasOverflow, "message gas limit < leftover gas (%d < %d)", msg.Gas(), leftoverGas)
+		return nil, evmObj, errors.Wrapf(evm.ErrGasOverflow, "message gas limit < leftover gas (%d < %d)", msg.Gas(), leftoverGas)
 	}
 
 	gasUsed := math.LegacyMaxDec(minimumGasUsed, math.LegacyNewDec(int64(temporaryGasUsed))).TruncateInt().Uint64()
@@ -417,7 +381,7 @@ func (k *Keeper) ApplyEvmMsg(ctx sdk.Context,
 		Ret:     ret,
 		Logs:    evm.NewLogsFromEth(stateDB.Logs()),
 		Hash:    txConfig.TxHash.Hex(),
-	}, nil
+	}, evmObj, nil
 }
 
 func ParseWeiAsMultipleOfMicronibi(weiInt *big.Int) (newWeiInt *big.Int, err error) {
@@ -673,10 +637,10 @@ func (k Keeper) convertCoinNativeERC20(
 // EmitEthereumTxEvents emits all types of EVM events applicable to a particular execution case
 func (k *Keeper) EmitEthereumTxEvents(
 	ctx sdk.Context,
-	tx *gethcore.Transaction,
+	recipient *gethcommon.Address,
+	txType uint8,
 	msg gethcore.Message,
 	evmResp *evm.MsgEthereumTxResponse,
-	contractAddr gethcommon.Address,
 ) error {
 	// Typed event: eth.evm.v1.EventEthereumTx
 	eventEthereumTx := &evm.EventEthereumTx{
@@ -687,8 +651,8 @@ func (k *Keeper) EmitEthereumTxEvents(
 	if len(ctx.TxBytes()) > 0 {
 		eventEthereumTx.Hash = tmbytes.HexBytes(tmtypes.Tx(ctx.TxBytes()).Hash()).String()
 	}
-	if to := tx.To(); to != nil {
-		eventEthereumTx.Recipient = to.Hex()
+	if recipient != nil {
+		eventEthereumTx.Recipient = recipient.Hex()
 	}
 	if evmResp.Failed() {
 		eventEthereumTx.EthTxFailed = evmResp.VmError
@@ -715,13 +679,14 @@ func (k *Keeper) EmitEthereumTxEvents(
 			sdk.EventTypeMessage,
 			sdk.NewAttribute(sdk.AttributeKeyModule, evm.ModuleName),
 			sdk.NewAttribute(sdk.AttributeKeySender, msg.From().Hex()),
-			sdk.NewAttribute(evm.MessageEventAttrTxType, fmt.Sprintf("%d", tx.Type())),
+			sdk.NewAttribute(evm.MessageEventAttrTxType, fmt.Sprintf("%d", txType)),
 		),
 	)
 
 	// Emit typed events
 	if !evmResp.Failed() {
-		if tx.To() == nil { // contract creation
+		if recipient == nil { // contract creation
+			contractAddr := crypto.CreateAddress(msg.From(), msg.Nonce())
 			_ = ctx.EventManager().EmitTypedEvent(&evm.EventContractDeployed{
 				Sender:       msg.From().Hex(),
 				ContractAddr: contractAddr.String(),
@@ -742,3 +707,17 @@ func (k *Keeper) EmitEthereumTxEvents(
 
 	return nil
 }
+
+// updateBlockBloom updates transient block bloom filter
+func (k *Keeper) updateBlockBloom(
+	ctx sdk.Context,
+	evmResp *evm.MsgEthereumTxResponse,
+	logIndex uint64,
+) {
+	logs := evm.LogsToEthereum(evmResp.Logs)
+	if len(logs) > 0 {
+		k.EvmState.BlockBloom.Set(ctx, k.EvmState.CalcBloomFromLogs(ctx, logs).Bytes())
+		blockLogSize := logIndex + uint64(len(logs))
+		k.EvmState.BlockLogSize.Set(ctx, blockLogSize)
+	}
+}
diff --git a/x/evm/keeper/msg_update_params.go b/x/evm/keeper/msg_update_params.go
index 1098138a3..12dd71fe8 100644
--- a/x/evm/keeper/msg_update_params.go
+++ b/x/evm/keeper/msg_update_params.go
@@ -18,6 +18,9 @@ func (k *Keeper) UpdateParams(
 		return nil, errors.Wrapf(govtypes.ErrInvalidSigner, "invalid authority, expected %s, got %s", k.authority.String(), req.Authority)
 	}
 	ctx := sdk.UnwrapSDKContext(goCtx)
-	k.SetParams(ctx, req.Params)
+	err = k.SetParams(ctx, req.Params)
+	if err != nil {
+		return nil, errors.Wrapf(err, "failed to set params")
+	}
 	return &evm.MsgUpdateParamsResponse{}, nil
 }
diff --git a/x/evm/keeper/vm_config.go b/x/evm/keeper/vm_config.go
index 241cae816..2f8232ad6 100644
--- a/x/evm/keeper/vm_config.go
+++ b/x/evm/keeper/vm_config.go
@@ -39,12 +39,12 @@ func (k *Keeper) GetEVMConfig(
 func (k *Keeper) TxConfig(
 	ctx sdk.Context, txHash common.Hash,
 ) statedb.TxConfig {
-	return statedb.NewTxConfig(
-		common.BytesToHash(ctx.HeaderHash()), // BlockHash
-		txHash,                               // TxHash
-		uint(k.EvmState.BlockTxIndex.GetOr(ctx, 0)), // TxIndex
-		uint(k.EvmState.BlockLogSize.GetOr(ctx, 0)), // LogIndex
-	)
+	return statedb.TxConfig{
+		BlockHash: common.BytesToHash(ctx.HeaderHash()),
+		TxHash:    txHash,
+		TxIndex:   uint(k.EvmState.BlockTxIndex.GetOr(ctx, 0)),
+		LogIndex:  uint(k.EvmState.BlockLogSize.GetOr(ctx, 0)),
+	}
 }
 
 // VMConfig creates an EVM configuration from the debug setting and the extra
diff --git a/x/evm/logs.go b/x/evm/logs.go
index 21bc74db5..3b662964f 100644
--- a/x/evm/logs.go
+++ b/x/evm/logs.go
@@ -11,14 +11,6 @@ import (
 	"github.com/NibiruChain/nibiru/v2/eth"
 )
 
-// NewTransactionLogs creates a new NewTransactionLogs instance.
-func NewTransactionLogs(hash gethcommon.Hash, logs []*Log) TransactionLogs {
-	return TransactionLogs{
-		Hash: hash.String(),
-		Logs: logs,
-	}
-}
-
 // NewTransactionLogsFromEth creates a new NewTransactionLogs instance using []*ethtypes.Log.
 func NewTransactionLogsFromEth(hash gethcommon.Hash, ethlogs []*gethcore.Log) TransactionLogs {
 	return TransactionLogs{
diff --git a/x/evm/logs_test.go b/x/evm/logs_test.go
index 34cb80a12..66dc1105c 100644
--- a/x/evm/logs_test.go
+++ b/x/evm/logs_test.go
@@ -193,7 +193,10 @@ func TestConversionFunctions(t *testing.T) {
 	conversionErr := conversionLogs.Validate()
 
 	// create new transaction logs as copy of old valid one (and validate)
-	copyLogs := evm.NewTransactionLogs(common.BytesToHash([]byte("tx_hash")), txLogs.Logs)
+	copyLogs := evm.TransactionLogs{
+		Hash: common.BytesToHash([]byte("tx_hash")).Hex(),
+		Logs: txLogs.Logs,
+	}
 	copyErr := copyLogs.Validate()
 
 	require.Nil(t, conversionErr)
diff --git a/x/evm/precompile/errors.go b/x/evm/precompile/errors.go
index f22ed9f7e..a95989b71 100644
--- a/x/evm/precompile/errors.go
+++ b/x/evm/precompile/errors.go
@@ -3,11 +3,23 @@ package precompile
 import (
 	"errors"
 	"fmt"
+	"reflect"
 
 	gethabi "github.com/ethereum/go-ethereum/accounts/abi"
 	"github.com/ethereum/go-ethereum/core/vm"
 )
 
+// 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.
+func ErrPrecompileRun(err error, p vm.PrecompiledContract) error {
+	if err != nil {
+		precompileType := reflect.TypeOf(p).Name()
+		err = fmt.Errorf("precompile error: failed to run %s: %w", precompileType, err)
+	}
+	return err
+}
+
 // Error short-hand for type validation
 func ErrArgTypeValidation(solidityHint string, arg any) error {
 	return fmt.Errorf("type validation failed for (%s) argument: %s", solidityHint, arg)
diff --git a/x/evm/precompile/funtoken.go b/x/evm/precompile/funtoken.go
index 042544269..5c585c2e9 100644
--- a/x/evm/precompile/funtoken.go
+++ b/x/evm/precompile/funtoken.go
@@ -3,18 +3,18 @@ package precompile
 import (
 	"fmt"
 	"math/big"
-	"reflect"
 	"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"
-	gethparams "github.com/ethereum/go-ethereum/params"
 
 	"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"
@@ -32,17 +32,13 @@ 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) {
-	// Since [gethparams.TxGas] is the cost per (Ethereum) transaction that does not create
-	// a contract, it's value can be used to derive an appropriate value for the
-	// precompile call. The FunToken precompile performs 3 operations, labeled 1-3
-	// below:
-	// 0 | Call the precompile (already counted in gas calculation)
-	// 1 | Send ERC20 to EVM.
-	// 2 | Convert ERC20 to coin
-	// 3 | Send coin to recipient.
-	return gethparams.TxGas * 2
+	return RequiredGas(input, p.ABI())
 }
 
 const (
@@ -55,39 +51,29 @@ type PrecompileMethod string
 func (p precompileFunToken) Run(
 	evm *vm.EVM, contract *vm.Contract, readonly bool,
 ) (bz []byte, err error) {
-	// This is a `defer` pattern to add behavior that runs in the case that the error is
-	// non-nil, creating a concise way to add extra information.
 	defer func() {
-		if err != nil {
-			precompileType := reflect.TypeOf(p).Name()
-			err = fmt.Errorf("precompile error: failed to run %s: %w", precompileType, err)
-		}
+		err = ErrPrecompileRun(err, p)
 	}()
-
-	// 1 | Get context from StateDB
-	stateDB, ok := evm.StateDB.(*statedb.StateDB)
-	if !ok {
-		err = fmt.Errorf("failed to load the sdk.Context from the EVM StateDB")
-		return
-	}
-	ctx := stateDB.GetContext()
-
-	method, args, err := DecomposeInput(embeds.SmartContract_FunToken.ABI, contract.Input)
+	start, err := OnRunStart(evm, contract, p.ABI())
 	if err != nil {
 		return nil, err
 	}
 
+	method := start.Method
 	switch PrecompileMethod(method.Name) {
 	case FunTokenMethod_BankSend:
-		bz, err = p.bankSend(ctx, contract.CallerAddress, method, args, readonly)
+		bz, err = p.bankSend(start, contract.CallerAddress, readonly)
 	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)
 		return
 	}
-
-	return
+	if err != nil {
+		return nil, err
+	}
+	// Dirty journal entries in `StateDB` must be committed
+	return bz, start.StateDB.Commit()
 }
 
 func PrecompileFunToken(keepers keepers.PublicKeepers) vm.PrecompiledContract {
@@ -116,12 +102,11 @@ var executionGuard sync.Mutex
 //	function bankSend(address erc20, uint256 amount, string memory to) external;
 //	```
 func (p precompileFunToken) bankSend(
-	ctx sdk.Context,
+	start OnRunStartResult,
 	caller gethcommon.Address,
-	method *gethabi.Method,
-	args []any,
 	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
@@ -166,7 +151,7 @@ func (p precompileFunToken) bankSend(
 
 	// EVM account mints FunToken.BankDenom to module account
 	amt := math.NewIntFromBigInt(amount)
-	coins := sdk.NewCoins(sdk.NewCoin(funtoken.BankDenom, amt))
+	coinToSend := sdk.NewCoin(funtoken.BankDenom, amt)
 	if funtoken.IsMadeFromCoin {
 		// If the FunToken mapping was created from a bank coin, then the EVM account
 		// owns the ERC20 contract and was the original minter of the ERC20 tokens.
@@ -178,7 +163,7 @@ func (p precompileFunToken) bankSend(
 			return
 		}
 	} else {
-		err = p.bankKeeper.MintCoins(ctx, evm.ModuleName, coins)
+		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,
@@ -187,7 +172,14 @@ func (p precompileFunToken) bankSend(
 	}
 
 	// Transfer the bank coin
-	err = p.bankKeeper.SendCoinsFromModuleToAccount(ctx, evm.ModuleName, toAddr, coins)
+	err = SafeSendCoinFromModuleToAccount(
+		ctx,
+		evm.ModuleName,
+		toAddr,
+		coinToSend,
+		p.bankKeeper,
+		start.StateDB,
+	)
 	if err != nil {
 		return nil, fmt.Errorf("send failed for module \"%s\" (%s): contract caller %s: %w",
 			evm.ModuleName, evm.EVM_MODULE_ADDRESS.Hex(), caller.Hex(), err,
@@ -199,6 +191,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 64be0360f..dd5176fb3 100644
--- a/x/evm/precompile/funtoken_test.go
+++ b/x/evm/precompile/funtoken_test.go
@@ -5,7 +5,7 @@ import (
 	"testing"
 
 	sdk "github.com/cosmos/cosmos-sdk/types"
-	"github.com/ethereum/go-ethereum/common"
+	gethcommon "github.com/ethereum/go-ethereum/common"
 	"github.com/stretchr/testify/suite"
 
 	"github.com/NibiruChain/nibiru/v2/eth"
@@ -49,19 +49,19 @@ func (s *FuntokenSuite) TestFailToPackABI() {
 		{
 			name:       "wrong type for amount",
 			methodName: string(precompile.FunTokenMethod_BankSend),
-			callArgs:   []any{common.HexToAddress("0x7D4B7B8CA7E1a24928Bb96D59249c7a5bd1DfBe6"), "foo", testutil.AccAddress().String()},
+			callArgs:   []any{gethcommon.HexToAddress("0x7D4B7B8CA7E1a24928Bb96D59249c7a5bd1DfBe6"), "foo", testutil.AccAddress().String()},
 			wantError:  "abi: cannot use string as type ptr as argument",
 		},
 		{
 			name:       "wrong type for recipient",
 			methodName: string(precompile.FunTokenMethod_BankSend),
-			callArgs:   []any{common.HexToAddress("0x7D4B7B8CA7E1a24928Bb96D59249c7a5bd1DfBe6"), big.NewInt(1), 111},
+			callArgs:   []any{gethcommon.HexToAddress("0x7D4B7B8CA7E1a24928Bb96D59249c7a5bd1DfBe6"), big.NewInt(1), 111},
 			wantError:  "abi: cannot use int as type string as argument",
 		},
 		{
 			name:       "invalid method name",
 			methodName: "foo",
-			callArgs:   []any{common.HexToAddress("0x7D4B7B8CA7E1a24928Bb96D59249c7a5bd1DfBe6"), big.NewInt(1), testutil.AccAddress().String()},
+			callArgs:   []any{gethcommon.HexToAddress("0x7D4B7B8CA7E1a24928Bb96D59249c7a5bd1DfBe6"), big.NewInt(1), testutil.AccAddress().String()},
 			wantError:  "method 'foo' not found",
 		},
 	}
@@ -112,7 +112,7 @@ func (s *FuntokenSuite) TestHappyPath() {
 	{
 		input, err := embeds.SmartContract_ERC20Minter.ABI.Pack("mint", deps.Sender.EthAddr, big.NewInt(69_420))
 		s.NoError(err)
-		_, err = deps.EvmKeeper.CallContractWithInput(
+		_, _, err = deps.EvmKeeper.CallContractWithInput(
 			deps.Ctx, deps.Sender.EthAddr, &erc20, true, input,
 		)
 		s.ErrorContains(err, "Ownable: caller is not the owner")
@@ -126,14 +126,18 @@ func (s *FuntokenSuite) TestHappyPath() {
 	input, err := embeds.SmartContract_FunToken.ABI.Pack(string(precompile.FunTokenMethod_BankSend), callArgs...)
 	s.NoError(err)
 
-	_, err = deps.EvmKeeper.CallContractWithInput(
-		deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_FunToken, true, input,
+	_, resp, err := evmtest.CallContractTx(
+		&deps,
+		precompile.PrecompileAddr_FunToken,
+		input,
+		deps.Sender,
 	)
 	s.Require().NoError(err)
+	s.Require().Empty(resp.VmError)
 
 	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),
-		deps.App.BankKeeper.GetBalance(deps.Ctx, randomAcc, funtoken.BankDenom).Amount,
+	s.Equal(sdk.NewInt(420).String(),
+		deps.App.BankKeeper.GetBalance(deps.Ctx, randomAcc, funtoken.BankDenom).Amount.String(),
 	)
 }
diff --git a/x/evm/precompile/oracle.go b/x/evm/precompile/oracle.go
index b7d283ed7..fb0b2981b 100644
--- a/x/evm/precompile/oracle.go
+++ b/x/evm/precompile/oracle.go
@@ -2,18 +2,15 @@ package precompile
 
 import (
 	"fmt"
-	"reflect"
 
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	gethabi "github.com/ethereum/go-ethereum/accounts/abi"
 	gethcommon "github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/core/vm"
-	gethparams "github.com/ethereum/go-ethereum/params"
 
 	"github.com/NibiruChain/nibiru/v2/app/keepers"
 	"github.com/NibiruChain/nibiru/v2/x/common/asset"
 	"github.com/NibiruChain/nibiru/v2/x/evm/embeds"
-	"github.com/NibiruChain/nibiru/v2/x/evm/statedb"
 	oraclekeeper "github.com/NibiruChain/nibiru/v2/x/oracle/keeper"
 )
 
@@ -27,45 +24,28 @@ func (p precompileOracle) Address() gethcommon.Address {
 }
 
 func (p precompileOracle) RequiredGas(input []byte) (gasPrice uint64) {
-	// Since [gethparams.TxGas] is the cost per (Ethereum) transaction that does not create
-	// a contract, it's value can be used to derive an appropriate value for the precompile call.
-	return gethparams.TxGas
+	return RequiredGas(input, embeds.SmartContract_Oracle.ABI)
 }
 
 const (
-	OracleMethod_QueryExchangeRate OracleMethod = "queryExchangeRate"
+	OracleMethod_queryExchangeRate PrecompileMethod = "queryExchangeRate"
 )
 
-type OracleMethod string
-
 // Run runs the precompiled contract
 func (p precompileOracle) Run(
 	evm *vm.EVM, contract *vm.Contract, readonly bool,
 ) (bz []byte, err error) {
-	// This is a `defer` pattern to add behavior that runs in the case that the error is
-	// non-nil, creating a concise way to add extra information.
 	defer func() {
-		if err != nil {
-			precompileType := reflect.TypeOf(p).Name()
-			err = fmt.Errorf("precompile error: failed to run %s: %w", precompileType, err)
-		}
+		err = ErrPrecompileRun(err, p)
 	}()
-
-	// 1 | Get context from StateDB
-	stateDB, ok := evm.StateDB.(*statedb.StateDB)
-	if !ok {
-		err = fmt.Errorf("failed to load the sdk.Context from the EVM StateDB")
-		return
-	}
-	ctx := stateDB.GetContext()
-
-	method, args, err := DecomposeInput(embeds.SmartContract_Oracle.ABI, contract.Input)
+	res, err := OnRunStart(evm, contract, embeds.SmartContract_Oracle.ABI)
 	if err != nil {
 		return nil, err
 	}
+	method, args, ctx := res.Method, res.Args, res.Ctx
 
-	switch OracleMethod(method.Name) {
-	case OracleMethod_QueryExchangeRate:
+	switch PrecompileMethod(method.Name) {
+	case OracleMethod_queryExchangeRate:
 		bz, err = p.queryExchangeRate(ctx, method, args, readonly)
 	default:
 		err = fmt.Errorf("invalid method called with name \"%s\"", method.Name)
diff --git a/x/evm/precompile/oracle_test.go b/x/evm/precompile/oracle_test.go
index 4d8d0116e..efab80118 100644
--- a/x/evm/precompile/oracle_test.go
+++ b/x/evm/precompile/oracle_test.go
@@ -21,13 +21,13 @@ func (s *OracleSuite) TestOracle_FailToPackABI() {
 	}{
 		{
 			name:       "wrong amount of call args",
-			methodName: string(precompile.OracleMethod_QueryExchangeRate),
+			methodName: string(precompile.OracleMethod_queryExchangeRate),
 			callArgs:   []any{"nonsense", "args here", "to see if", "precompile is", "called"},
 			wantError:  "argument count mismatch: got 5 for 1",
 		},
 		{
 			name:       "wrong type for pair",
-			methodName: string(precompile.OracleMethod_QueryExchangeRate),
+			methodName: string(precompile.OracleMethod_queryExchangeRate),
 			callArgs:   []any{common.HexToAddress("0x7D4B7B8CA7E1a24928Bb96D59249c7a5bd1DfBe6")},
 			wantError:  "abi: cannot use array as type string as argument",
 		},
@@ -58,13 +58,13 @@ func (s *OracleSuite) TestOracle_HappyPath() {
 		deps.App.OracleKeeper.SetPrice(deps.Ctx, "unibi:uusd", sdk.MustNewDecFromStr("0.067"))
 		input, err := embeds.SmartContract_Oracle.ABI.Pack("queryExchangeRate", "unibi:uusd")
 		s.NoError(err)
-		resp, err := deps.EvmKeeper.CallContractWithInput(
+		resp, _, err := deps.EvmKeeper.CallContractWithInput(
 			deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Oracle, true, input,
 		)
 		s.NoError(err)
 
 		// Check the response
-		out, err := embeds.SmartContract_Oracle.ABI.Unpack(string(precompile.OracleMethod_QueryExchangeRate), resp.Ret)
+		out, err := embeds.SmartContract_Oracle.ABI.Unpack(string(precompile.OracleMethod_queryExchangeRate), resp.Ret)
 		s.NoError(err)
 
 		// Check the response
diff --git a/x/evm/precompile/precompile.go b/x/evm/precompile/precompile.go
index 38a8744c1..a6bbfefc4 100644
--- a/x/evm/precompile/precompile.go
+++ b/x/evm/precompile/precompile.go
@@ -24,7 +24,10 @@ import (
 	"github.com/ethereum/go-ethereum/core/vm"
 	gethparams "github.com/ethereum/go-ethereum/params"
 
+	sdk "github.com/cosmos/cosmos-sdk/types"
+
 	"github.com/NibiruChain/nibiru/v2/app/keepers"
+	"github.com/NibiruChain/nibiru/v2/x/evm/statedb"
 )
 
 // InitPrecompiles initializes and returns a map of precompiled contracts for the EVM.
@@ -130,3 +133,82 @@ func RequiredGas(input []byte, abi *gethabi.ABI) uint64 {
 	argsBzLen := uint64(len(input[4:]))
 	return (costPerByte * argsBzLen) + costFlat
 }
+
+type OnRunStartResult struct {
+	// Args contains the decoded (ABI unpacked) arguments passed to the contract
+	// as input.
+	Args []any
+
+	// Ctx 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
+
+	// Method is the ABI method for the precompiled contract call.
+	Method *gethabi.Method
+
+	StateDB *statedb.StateDB
+}
+
+// OnRunStart prepares the execution environment for a precompiled contract call.
+// It handles decoding the input data according the to contract ABI, creates an
+// isolated cache context for state changes, and sets up a snapshot for potential
+// EVM "reverts".
+//
+// Args:
+//   - evm: Instance of the EVM executing the contract
+//   - contract: Precompiled contract being called
+//   - abi: [gethabi.ABI] defining the contract's invokable methods.
+//
+// Example Usage:
+//
+//	```go
+//	func (p newPrecompile) Run(
+//	  evm *vm.EVM, contract *vm.Contract, readonly bool
+//	) (bz []byte, err error) {
+//		res, err := OnRunStart(evm, contract, p.ABI())
+//		if err != nil {
+//		    return nil, err
+//		}
+//		// ...
+//		// Use res.Ctx for state changes
+//		// Use res.StateDB.Commit() before any non-EVM state changes
+//		// to guarantee the context and [statedb.StateDB] are in sync.
+//	}
+//	```
+func OnRunStart(
+	evm *vm.EVM, contract *vm.Contract, abi *gethabi.ABI,
+) (res OnRunStartResult, err error) {
+	method, args, err := DecomposeInput(abi, contract.Input)
+	if err != nil {
+		return res, err
+	}
+
+	stateDB, ok := evm.StateDB.(*statedb.StateDB)
+	if !ok {
+		err = fmt.Errorf("failed to load the sdk.Context from the EVM StateDB")
+		return
+	}
+	ctx := stateDB.GetContext()
+	if err = stateDB.Commit(); err != nil {
+		return res, fmt.Errorf("error committing dirty journal entries: %w", err)
+	}
+
+	return OnRunStartResult{
+		Args:    args,
+		Ctx:     ctx,
+		Method:  method,
+		StateDB: stateDB,
+	}, nil
+}
+
+var precompileMethodIsTxMap map[PrecompileMethod]bool = map[PrecompileMethod]bool{
+	WasmMethod_execute:      true,
+	WasmMethod_instantiate:  true,
+	WasmMethod_executeMulti: true,
+	WasmMethod_query:        false,
+	WasmMethod_queryRaw:     false,
+
+	FunTokenMethod_BankSend: true,
+
+	OracleMethod_queryExchangeRate: false,
+}
diff --git a/x/evm/precompile/test/export.go b/x/evm/precompile/test/export.go
new file mode 100644
index 000000000..966dd3359
--- /dev/null
+++ b/x/evm/precompile/test/export.go
@@ -0,0 +1,316 @@
+package test
+
+import (
+	"encoding/json"
+	"os"
+	"os/exec"
+	"path"
+	"strings"
+
+	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/stretchr/testify/suite"
+
+	"github.com/NibiruChain/nibiru/v2/app"
+	"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"
+// instantiate each Wasm contract using the precompile.
+func SetupWasmContracts(deps *evmtest.TestDeps, s *suite.Suite) (
+	contracts []sdk.AccAddress,
+) {
+	wasmCodes := DeployWasmBytecode(s, deps.Ctx, deps.Sender.NibiruAddr, deps.App)
+
+	otherArgs := []struct {
+		InstMsg []byte
+		Label   string
+	}{
+		{
+			InstMsg: []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",
+		},
+	}
+
+	for wasmCodeIdx, 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{},
+		}
+
+		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}
+		input, err := embeds.SmartContract_Wasm.ABI.Pack(
+			string(precompile.WasmMethod_instantiate),
+			callArgs...,
+		)
+		s.Require().NoError(err)
+
+		ethTxResp, evmObj, 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,
+		)
+		s.Require().NoError(err)
+		contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr)
+		s.NoError(err)
+		contracts = append(contracts, contractAddr)
+	}
+
+	return contracts
+}
+
+// DeployWasmBytecode is a setup function that stores all Wasm bytecode used in
+// the test suite.
+func DeployWasmBytecode(
+	s *suite.Suite,
+	ctx sdk.Context,
+	sender sdk.AccAddress,
+	nibiru *app.NibiruApp,
+) (codeIds []struct {
+	codeId  uint64
+	binPath string
+},
+) {
+	// rootPath, _ := exec.Command("go list -m -f {{.Dir}}").Output()
+	// Run: go list -m -f {{.Dir}}
+	// This returns the path to the root of the project.
+	rootPathBz, err := exec.Command("go", "list", "-m", "-f", "{{.Dir}}").Output()
+	s.Require().NoError(err)
+	rootPath := strings.Trim(string(rootPathBz), "\n")
+	for _, pathToWasmBin := 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",
+
+		// hello_world_counter.wasm is a compiled version of:
+		// https://github.com/NibiruChain/nibiru-wasm/tree/ec3ab9f09587a11fbdfbd4021c7617eca3912044/contracts/00-hello-world-counter
+		"x/evm/precompile/hello_world_counter.wasm",
+
+		// Add other wasm bytecode here if needed...
+	} {
+		pathToWasmBin = path.Join(string(rootPath), pathToWasmBin)
+		wasmBytecode, err := os.ReadFile(pathToWasmBin)
+		s.Require().NoErrorf(
+			err,
+			"rootPath %s, pathToWasmBin %s", rootPath, pathToWasmBin,
+		)
+
+		// The "Create" fn is private on the nibiru.WasmKeeper. By placing it as the
+		// decorated keeper in PermissionedKeeper type, we can access "Create" as a
+		// public fn.
+		wasmPermissionedKeeper := wasmkeeper.NewDefaultPermissionKeeper(nibiru.WasmKeeper)
+		instantiateAccess := &wasm.AccessConfig{
+			Permission: wasm.AccessTypeEverybody,
+		}
+		codeId, _, err := wasmPermissionedKeeper.Create(
+			ctx, sender, wasmBytecode, instantiateAccess,
+		)
+		s.Require().NoError(err)
+		codeIds = append(codeIds, struct {
+			codeId  uint64
+			binPath string
+		}{codeId, pathToWasmBin})
+	}
+
+	return codeIds
+}
+
+// From IWasm.query of Wasm.sol:
+//
+//	```solidity
+//	function query(
+//	  string memory contractAddr,
+//	  bytes memory req
+//	) external view returns (bytes memory response);
+//	```
+func AssertWasmCounterState(
+	s *suite.Suite,
+	deps evmtest.TestDeps,
+	wasmContract sdk.AccAddress,
+	wantCount int64,
+) (evmObj *vm.EVM) {
+	msgArgsBz := []byte(`
+		{ 
+		  "count": {}
+		}
+		`)
+
+	callArgs := []any{
+		// string memory contractAddr
+		wasmContract.String(),
+		// bytes memory req
+		msgArgsBz,
+	}
+	input, err := embeds.SmartContract_Wasm.ABI.Pack(
+		string(precompile.WasmMethod_query),
+		callArgs...,
+	)
+	s.Require().NoError(err)
+
+	ethTxResp, evmObj, err := deps.EvmKeeper.CallContractWithInput(
+		deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input,
+	)
+	s.Require().NoError(err)
+	s.Require().NotEmpty(ethTxResp.Ret)
+
+	s.T().Log("Parse the response contract addr and response bytes")
+	s.T().Logf("ethTxResp.Ret: %s", ethTxResp.Ret)
+	var queryResp []byte
+	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{...}
+		&queryResp,
+		string(precompile.WasmMethod_query),
+		ethTxResp.Ret,
+	)
+	s.Require().NoError(err)
+	s.T().Logf("queryResp: %s", queryResp)
+
+	s.T().Log("Response is a JSON-encoded struct from the Wasm contract")
+	var wasmMsg wasm.RawContractMessage
+	err = json.Unmarshal(queryResp, &wasmMsg)
+	s.NoError(err)
+	s.NoError(wasmMsg.ValidateBasic())
+	var typedResp QueryMsgCountResp
+	err = json.Unmarshal(wasmMsg, &typedResp)
+	s.NoError(err)
+
+	s.EqualValues(wantCount, typedResp.Count)
+	s.EqualValues(deps.Sender.NibiruAddr.String(), typedResp.Owner)
+	return evmObj
+}
+
+// Result of QueryMsg::Count from the [hello_world_counter] Wasm contract:
+//
+//	```rust
+//	#[cw_serde]
+//	pub struct State {
+//	    pub count: i64,
+//	    pub owner: Addr,
+//	}
+//	```
+//
+// [hello_world_counter]: https://github.com/NibiruChain/nibiru-wasm/tree/ec3ab9f09587a11fbdfbd4021c7617eca3912044/contracts/00-hello-world-counter
+type QueryMsgCountResp struct {
+	Count int64  `json:"count"`
+	Owner string `json:"owner"`
+}
+
+// From evm/embeds/contracts/Wasm.sol:
+//
+//	```solidity
+//	struct WasmExecuteMsg {
+//	  string contractAddr;
+//	  bytes msgArgs;
+//	  BankCoin[] funds;
+//	}
+//
+//	/// @notice Identical to "execute", except for multiple contract calls.
+//	function executeMulti(
+//	  WasmExecuteMsg[] memory executeMsgs
+//	) payable external returns (bytes[] memory responses);
+//	```
+//
+// The increment call corresponds to the ExecuteMsg from
+// the [hello_world_counter] Wasm contract:
+//
+//	```rust
+//	#[cw_serde]
+//	pub enum ExecuteMsg {
+//	    Increment {},         // Increase count by 1
+//	    Reset { count: i64 }, // Reset to any i64 value
+//	}
+//	```
+//
+// [hello_world_counter]: https://github.com/NibiruChain/nibiru-wasm/tree/ec3ab9f09587a11fbdfbd4021c7617eca3912044/contracts/00-hello-world-counter
+func IncrementWasmCounterWithExecuteMulti(
+	s *suite.Suite,
+	deps *evmtest.TestDeps,
+	wasmContract sdk.AccAddress,
+	times uint,
+) (evmObj *vm.EVM) {
+	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)
+
+	ethTxResp, evmObj, err := deps.EvmKeeper.CallContractWithInput(
+		deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input,
+	)
+	s.Require().NoError(err)
+	s.Require().NotEmpty(ethTxResp.Ret)
+	return evmObj
+}
diff --git a/x/evm/precompile/wasm.go b/x/evm/precompile/wasm.go
index 091999ee3..10817c673 100644
--- a/x/evm/precompile/wasm.go
+++ b/x/evm/precompile/wasm.go
@@ -2,7 +2,6 @@ package precompile
 
 import (
 	"fmt"
-	"reflect"
 
 	sdk "github.com/cosmos/cosmos-sdk/types"
 
@@ -14,8 +13,6 @@ import (
 	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/x/evm/statedb"
 )
 
 var _ vm.PrecompiledContract = (*precompileWasm)(nil)
@@ -32,89 +29,75 @@ const (
 	WasmMethod_queryRaw     PrecompileMethod = "queryRaw"
 )
 
-var precompileMethodIsTxMap map[PrecompileMethod]bool = map[PrecompileMethod]bool{
-	WasmMethod_execute:      true,
-	WasmMethod_instantiate:  true,
-	WasmMethod_executeMulti: true,
-	WasmMethod_query:        false,
-	WasmMethod_queryRaw:     false,
-
-	FunTokenMethod_BankSend: true,
-}
-
-// Wasm: A struct embedding keepers for read and write operations in Wasm, such
-// as execute, query, and instantiate.
-type Wasm struct {
-	*wasmkeeper.PermissionedKeeper
-	wasmkeeper.Keeper
-}
-
-func PrecompileWasm(keepers keepers.PublicKeepers) vm.PrecompiledContract {
-	return precompileWasm{
-		Wasm: Wasm{
-			wasmkeeper.NewDefaultPermissionKeeper(keepers.WasmKeeper),
-			keepers.WasmKeeper,
-		},
-	}
-}
-
-type precompileWasm struct {
-	Wasm Wasm
-}
-
-func (p precompileWasm) Address() gethcommon.Address {
-	return PrecompileAddr_Wasm
-}
-
-// 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)
-}
-
 // Run runs the precompiled contract
 func (p precompileWasm) Run(
 	evm *vm.EVM, contract *vm.Contract, readonly bool,
 ) (bz []byte, err error) {
-	// This is a `defer` pattern to add behavior that runs in the case that the error is
-	// non-nil, creating a concise way to add extra information.
 	defer func() {
-		if err != nil {
-			precompileType := reflect.TypeOf(p).Name()
-			err = fmt.Errorf("precompile error: failed to run %s: %w", precompileType, err)
-		}
+		err = ErrPrecompileRun(err, p)
 	}()
-
-	method, args, err := DecomposeInput(embeds.SmartContract_Wasm.ABI, contract.Input)
+	start, err := OnRunStart(evm, contract, p.ABI())
 	if err != nil {
 		return nil, err
 	}
-
-	stateDB, ok := evm.StateDB.(*statedb.StateDB)
-	if !ok {
-		err = fmt.Errorf("failed to load the sdk.Context from the EVM StateDB")
-		return
-	}
-	ctx := stateDB.GetContext()
+	method := start.Method
 
 	switch PrecompileMethod(method.Name) {
 	case WasmMethod_execute:
-		bz, err = p.execute(ctx, contract.CallerAddress, method, args, readonly)
+		bz, err = p.execute(start, contract.CallerAddress, readonly)
 	case WasmMethod_query:
-		bz, err = p.query(ctx, method, args, contract)
+		bz, err = p.query(start, contract)
 	case WasmMethod_instantiate:
-		bz, err = p.instantiate(ctx, contract.CallerAddress, method, args, readonly)
+		bz, err = p.instantiate(start, contract.CallerAddress, readonly)
 	case WasmMethod_executeMulti:
-		bz, err = p.executeMulti(ctx, contract.CallerAddress, method, args, readonly)
+		bz, err = p.executeMulti(start, contract.CallerAddress, readonly)
 	case WasmMethod_queryRaw:
-		bz, err = p.queryRaw(ctx, method, args, contract)
+		bz, err = p.queryRaw(start, 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)
 		return
 	}
+	if err != nil {
+		return nil, err
+	}
+
+	// Dirty journal entries in `StateDB` must be committed
+	return bz, start.StateDB.Commit()
+}
+
+type precompileWasm struct {
+	Wasm Wasm
+}
+
+func (p precompileWasm) Address() gethcommon.Address {
+	return PrecompileAddr_Wasm
+}
+
+func (p precompileWasm) ABI() *gethabi.ABI {
+	return embeds.SmartContract_Wasm.ABI
+}
 
-	return
+// RequiredGas calculates the cost of calling the precompile in gas units.
+func (p precompileWasm) RequiredGas(input []byte) (gasCost uint64) {
+	return RequiredGas(input, p.ABI())
+}
+
+// Wasm: A struct embedding keepers for read and write operations in Wasm, such
+// as execute, query, and instantiate.
+type Wasm struct {
+	*wasmkeeper.PermissionedKeeper
+	wasmkeeper.Keeper
+}
+
+func PrecompileWasm(keepers keepers.PublicKeepers) vm.PrecompiledContract {
+	return precompileWasm{
+		Wasm: Wasm{
+			wasmkeeper.NewDefaultPermissionKeeper(keepers.WasmKeeper),
+			keepers.WasmKeeper,
+		},
+	}
 }
 
 // execute invokes a Wasm contract's "ExecuteMsg", which corresponds to
@@ -137,12 +120,11 @@ func (p precompileWasm) Run(
 //   - funds: Optional funds to supply during the execute call. It's
 //     uncommon to use this field, so you'll pass an empty array most of the time.
 func (p precompileWasm) execute(
-	ctx sdk.Context,
+	start OnRunStartResult,
 	caller gethcommon.Address,
-	method *gethabi.Method,
-	args []any,
 	readOnly bool,
 ) (bz []byte, err error) {
+	method, args, ctx := start.Method, start.Args, start.Ctx
 	defer func() {
 		if err != nil {
 			err = ErrMethodCalled(method, err)
@@ -178,11 +160,10 @@ func (p precompileWasm) execute(
 //	) external view returns (bytes memory response);
 //	```
 func (p precompileWasm) query(
-	ctx sdk.Context,
-	method *gethabi.Method,
-	args []any,
+	start OnRunStartResult,
 	contract *vm.Contract,
 ) (bz []byte, err error) {
+	method, args, ctx := start.Method, start.Args, start.Ctx
 	defer func() {
 		if err != nil {
 			err = ErrMethodCalled(method, err)
@@ -223,12 +204,11 @@ func (p precompileWasm) query(
 //	) payable external returns (string memory contractAddr, bytes memory data);
 //	```
 func (p precompileWasm) instantiate(
-	ctx sdk.Context,
+	start OnRunStartResult,
 	caller gethcommon.Address,
-	method *gethabi.Method,
-	args []any,
 	readOnly bool,
 ) (bz []byte, err error) {
+	method, args, ctx := start.Method, start.Args, start.Ctx
 	defer func() {
 		if err != nil {
 			err = ErrMethodCalled(method, err)
@@ -275,12 +255,11 @@ func (p precompileWasm) instantiate(
 //	) payable external returns (bytes[] memory responses);
 //	```
 func (p precompileWasm) executeMulti(
-	ctx sdk.Context,
+	start OnRunStartResult,
 	caller gethcommon.Address,
-	method *gethabi.Method,
-	args []any,
 	readOnly bool,
 ) (bz []byte, err error) {
+	method, args, ctx := start.Method, start.Args, start.Ctx
 	defer func() {
 		if err != nil {
 			err = ErrMethodCalled(method, err)
@@ -341,11 +320,10 @@ func (p precompileWasm) executeMulti(
 //   - bz: The encoded raw data stored at the queried key
 //   - err: Any error that occurred during the query
 func (p precompileWasm) queryRaw(
-	ctx sdk.Context,
-	method *gethabi.Method,
-	args []any,
+	start OnRunStartResult,
 	contract *vm.Contract,
 ) (bz []byte, err error) {
+	method, args, ctx := start.Method, start.Args, start.Ctx
 	defer func() {
 		if err != nil {
 			err = ErrMethodCalled(method, err)
diff --git a/x/evm/precompile/wasm_test.go b/x/evm/precompile/wasm_test.go
index 6db1d7642..d796f8b89 100644
--- a/x/evm/precompile/wasm_test.go
+++ b/x/evm/precompile/wasm_test.go
@@ -4,16 +4,14 @@ import (
 	"encoding/json"
 	"fmt"
 	"math/big"
-	"os"
 
-	wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
 	wasm "github.com/CosmWasm/wasmd/x/wasm/types"
 
-	"github.com/NibiruChain/nibiru/v2/app"
 	"github.com/NibiruChain/nibiru/v2/x/common/testutil"
 	"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/precompile/test"
 	tokenfactory "github.com/NibiruChain/nibiru/v2/x/tokenfactory/types"
 
 	sdk "github.com/cosmos/cosmos-sdk/types"
@@ -24,127 +22,9 @@ type WasmSuite struct {
 	suite.Suite
 }
 
-// SetupWasmContracts stores all Wasm bytecode and has the "deps.Sender"
-// instantiate each Wasm contract using the precompile.
-func SetupWasmContracts(deps *evmtest.TestDeps, s *suite.Suite) (
-	contracts []sdk.AccAddress,
-) {
-	wasmCodes := DeployWasmBytecode(s, deps.Ctx, deps.Sender.NibiruAddr, deps.App)
-
-	otherArgs := []struct {
-		InstMsg []byte
-		Label   string
-	}{
-		{
-			InstMsg: []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",
-		},
-	}
-
-	for wasmCodeIdx, 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{},
-		}
-
-		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}
-		input, err := embeds.SmartContract_Wasm.ABI.Pack(
-			string(precompile.WasmMethod_instantiate),
-			callArgs...,
-		)
-		s.Require().NoError(err)
-
-		ethTxResp, err := deps.EvmKeeper.CallContractWithInput(
-			deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input,
-		)
-		s.Require().NoError(err)
-		s.Require().NotEmpty(ethTxResp.Ret)
-
-		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,
-		)
-		s.Require().NoError(err)
-		contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr)
-		s.NoError(err)
-		contracts = append(contracts, contractAddr)
-	}
-
-	return contracts
-}
-
-// DeployWasmBytecode is a setup function that stores all Wasm bytecode used in
-// the test suite.
-func DeployWasmBytecode(
-	s *suite.Suite,
-	ctx sdk.Context,
-	sender sdk.AccAddress,
-	nibiru *app.NibiruApp,
-) (codeIds []struct {
-	codeId  uint64
-	binPath string
-},
-) {
-	for _, pathToWasmBin := range []string{
-		// nibi_stargate.wasm is a compiled version of:
-		// https://github.com/NibiruChain/nibiru-wasm/blob/main/contracts/nibi-stargate/src/contract.rs
-		"../../tokenfactory/fixture/nibi_stargate.wasm",
-
-		// hello_world_counter.wasm is a compiled version of:
-		// https://github.com/NibiruChain/nibiru-wasm/tree/ec3ab9f09587a11fbdfbd4021c7617eca3912044/contracts/00-hello-world-counter
-		"./hello_world_counter.wasm",
-
-		// Add other wasm bytecode here if needed...
-	} {
-		wasmBytecode, err := os.ReadFile(pathToWasmBin)
-		s.Require().NoError(err)
-
-		// The "Create" fn is private on the nibiru.WasmKeeper. By placing it as the
-		// decorated keeper in PermissionedKeeper type, we can access "Create" as a
-		// public fn.
-		wasmPermissionedKeeper := wasmkeeper.NewDefaultPermissionKeeper(nibiru.WasmKeeper)
-		instantiateAccess := &wasm.AccessConfig{
-			Permission: wasm.AccessTypeEverybody,
-		}
-		codeId, _, err := wasmPermissionedKeeper.Create(
-			ctx, sender, wasmBytecode, instantiateAccess,
-		)
-		s.Require().NoError(err)
-		codeIds = append(codeIds, struct {
-			codeId  uint64
-			binPath string
-		}{codeId, pathToWasmBin})
-	}
-
-	return codeIds
-}
-
 func (s *WasmSuite) TestExecuteHappy() {
 	deps := evmtest.NewTestDeps()
-	wasmContracts := SetupWasmContracts(&deps, &s.Suite)
+	wasmContracts := test.SetupWasmContracts(&deps, &s.Suite)
 	wasmContract := wasmContracts[0] // nibi_stargate.wasm
 
 	s.T().Log("Execute: create denom")
@@ -172,7 +52,7 @@ func (s *WasmSuite) TestExecuteHappy() {
 	)
 	s.Require().NoError(err)
 
-	ethTxResp, err := deps.EvmKeeper.CallContractWithInput(
+	ethTxResp, _, err := deps.EvmKeeper.CallContractWithInput(
 		deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input,
 	)
 	s.Require().NoError(err)
@@ -201,7 +81,7 @@ func (s *WasmSuite) TestExecuteHappy() {
 		callArgs...,
 	)
 	s.Require().NoError(err)
-	ethTxResp, err = deps.EvmKeeper.CallContractWithInput(
+	ethTxResp, _, err = deps.EvmKeeper.CallContractWithInput(
 		deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input,
 	)
 	s.Require().NoError(err)
@@ -211,178 +91,27 @@ func (s *WasmSuite) TestExecuteHappy() {
 	)
 }
 
-// Result of QueryMsg::Count from the [hello_world_counter] Wasm contract:
-//
-//	```rust
-//	#[cw_serde]
-//	pub struct State {
-//	    pub count: i64,
-//	    pub owner: Addr,
-//	}
-//	```
-//
-// [hello_world_counter]: https://github.com/NibiruChain/nibiru-wasm/tree/ec3ab9f09587a11fbdfbd4021c7617eca3912044/contracts/00-hello-world-counter
-type QueryMsgCountResp struct {
-	Count int64  `json:"count"`
-	Owner string `json:"owner"`
-}
-
 func (s *WasmSuite) TestExecuteMultiHappy() {
 	deps := evmtest.NewTestDeps()
-	wasmContracts := SetupWasmContracts(&deps, &s.Suite)
+	wasmContracts := test.SetupWasmContracts(&deps, &s.Suite)
 	wasmContract := wasmContracts[1] // hello_world_counter.wasm
 
-	s.assertWasmCounterState(deps, wasmContract, 0)                // count = 0
-	s.incrementWasmCounterWithExecuteMulti(&deps, wasmContract, 2) // count += 2
-	s.assertWasmCounterState(deps, wasmContract, 2)                // count = 2
+	// count = 0
+	test.AssertWasmCounterState(&s.Suite, deps, wasmContract, 0)
+	// count += 2
+	test.IncrementWasmCounterWithExecuteMulti(
+		&s.Suite, &deps, wasmContract, 2)
+	// count = 2
+	test.AssertWasmCounterState(&s.Suite, deps, wasmContract, 2)
 	s.assertWasmCounterStateRaw(deps, wasmContract, 2)
-	s.incrementWasmCounterWithExecuteMulti(&deps, wasmContract, 67) // count += 67
-	s.assertWasmCounterState(deps, wasmContract, 69)                // count = 69
+	// count += 67
+	test.IncrementWasmCounterWithExecuteMulti(
+		&s.Suite, &deps, wasmContract, 67)
+	// count = 69
+	test.AssertWasmCounterState(&s.Suite, deps, wasmContract, 69)
 	s.assertWasmCounterStateRaw(deps, wasmContract, 69)
 }
 
-// From IWasm.query of Wasm.sol:
-//
-//	```solidity
-//	function query(
-//	  string memory contractAddr,
-//	  bytes memory req
-//	) external view returns (bytes memory response);
-//	```
-func (s *WasmSuite) assertWasmCounterState(
-	deps evmtest.TestDeps,
-	wasmContract sdk.AccAddress,
-	wantCount int64,
-) {
-	msgArgsBz := []byte(`
-		{ 
-		  "count": {}
-		}
-		`)
-
-	callArgs := []any{
-		// string memory contractAddr
-		wasmContract.String(),
-		// bytes memory req
-		msgArgsBz,
-	}
-	input, err := embeds.SmartContract_Wasm.ABI.Pack(
-		string(precompile.WasmMethod_query),
-		callArgs...,
-	)
-	s.Require().NoError(err)
-
-	ethTxResp, err := deps.EvmKeeper.CallContractWithInput(
-		deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input,
-	)
-	s.Require().NoError(err)
-	s.Require().NotEmpty(ethTxResp.Ret)
-
-	s.T().Log("Parse the response contract addr and response bytes")
-	s.T().Logf("ethTxResp.Ret: %s", ethTxResp.Ret)
-	var queryResp []byte
-	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{...}
-		&queryResp,
-		string(precompile.WasmMethod_query),
-		ethTxResp.Ret,
-	)
-	s.Require().NoError(err)
-	s.T().Logf("queryResp: %s", queryResp)
-
-	s.T().Log("Response is a JSON-encoded struct from the Wasm contract")
-	var wasmMsg wasm.RawContractMessage
-	err = json.Unmarshal(queryResp, &wasmMsg)
-	s.NoError(err)
-	s.NoError(wasmMsg.ValidateBasic())
-	var typedResp QueryMsgCountResp
-	err = json.Unmarshal(wasmMsg, &typedResp)
-	s.NoError(err)
-
-	s.EqualValues(wantCount, typedResp.Count)
-	s.EqualValues(deps.Sender.NibiruAddr.String(), typedResp.Owner)
-}
-
-// From evm/embeds/contracts/Wasm.sol:
-//
-//	```solidity
-//	struct WasmExecuteMsg {
-//	  string contractAddr;
-//	  bytes msgArgs;
-//	  BankCoin[] funds;
-//	}
-//
-//	/// @notice Identical to "execute", except for multiple contract calls.
-//	function executeMulti(
-//	  WasmExecuteMsg[] memory executeMsgs
-//	) payable external returns (bytes[] memory responses);
-//	```
-//
-// The increment call corresponds to the ExecuteMsg from
-// the [hello_world_counter] Wasm contract:
-//
-//	```rust
-//	#[cw_serde]
-//	pub enum ExecuteMsg {
-//	    Increment {},         // Increase count by 1
-//	    Reset { count: i64 }, // Reset to any i64 value
-//	}
-//	```
-//
-// [hello_world_counter]: https://github.com/NibiruChain/nibiru-wasm/tree/ec3ab9f09587a11fbdfbd4021c7617eca3912044/contracts/00-hello-world-counter
-func (s *WasmSuite) incrementWasmCounterWithExecuteMulti(
-	deps *evmtest.TestDeps,
-	wasmContract sdk.AccAddress,
-	times uint,
-) {
-	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)
-
-	ethTxResp, err := deps.EvmKeeper.CallContractWithInput(
-		deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input,
-	)
-	s.Require().NoError(err)
-	s.Require().NotEmpty(ethTxResp.Ret)
-}
-
 // From IWasm.query of Wasm.sol:
 //
 //	```solidity
@@ -407,7 +136,7 @@ func (s *WasmSuite) assertWasmCounterStateRaw(
 	)
 	s.Require().NoError(err)
 
-	ethTxResp, err := deps.EvmKeeper.CallContractWithInput(
+	ethTxResp, _, err := deps.EvmKeeper.CallContractWithInput(
 		deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input,
 	)
 	s.Require().NoError(err)
@@ -430,7 +159,7 @@ func (s *WasmSuite) assertWasmCounterStateRaw(
 	s.T().Logf("wasmMsg: %s", wasmMsg)
 	s.NoError(wasmMsg.ValidateBasic())
 
-	var typedResp QueryMsgCountResp
+	var typedResp test.QueryMsgCountResp
 	s.NoError(json.Unmarshal(wasmMsg, &typedResp))
 	s.EqualValues(wantCount, typedResp.Count)
 	s.EqualValues(deps.Sender.NibiruAddr.String(), typedResp.Owner)
@@ -577,11 +306,10 @@ func (s *WasmSuite) TestSadArgsExecute() {
 			)
 			s.Require().NoError(err)
 
-			ethTxResp, err := deps.EvmKeeper.CallContractWithInput(
+			ethTxResp, _, err := deps.EvmKeeper.CallContractWithInput(
 				deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input,
 			)
-			s.ErrorContains(err, tc.wantError)
-			s.Require().Nil(ethTxResp)
+			s.Require().ErrorContains(err, tc.wantError, "ethTxResp %v", ethTxResp)
 		})
 	}
 }
diff --git a/x/evm/query.pb.go b/x/evm/query.pb.go
index a710bd10d..26db67b42 100644
--- a/x/evm/query.pb.go
+++ b/x/evm/query.pb.go
@@ -1198,8 +1198,9 @@ func (m *QueryBaseFeeRequest) XXX_DiscardUnknown() {
 var xxx_messageInfo_QueryBaseFeeRequest proto.InternalMessageInfo
 
 // QueryBaseFeeResponse returns the EIP1559 base fee.
+// See https://github.com/ethereum/EIPs/blob/ba6c342c23164072adb500c3136e3ae6eabff306/EIPS/eip-1559.md.
 type QueryBaseFeeResponse struct {
-	// base_fee is the EIP1559 base fee
+	// base_fee is the EIP1559 base fee in units of wei.
 	BaseFee *cosmossdk_io_math.Int `protobuf:"bytes,1,opt,name=base_fee,json=baseFee,proto3,customtype=cosmossdk.io/math.Int" json:"base_fee,omitempty"`
 	// base_fee is the EIP1559 base fee in units of micronibi ("unibi").
 	BaseFeeUnibi *cosmossdk_io_math.Int `protobuf:"bytes,2,opt,name=base_fee_unibi,json=baseFeeUnibi,proto3,customtype=cosmossdk.io/math.Int" json:"base_fee_unibi,omitempty"`
@@ -1239,7 +1240,8 @@ func (m *QueryBaseFeeResponse) XXX_DiscardUnknown() {
 var xxx_messageInfo_QueryBaseFeeResponse proto.InternalMessageInfo
 
 type QueryFunTokenMappingRequest struct {
-	// either the 0x contract address of the ERC-20 token or the cosmos denom
+	// Either the hexadecimal-encoded ERC20 contract address or denomination of the
+	// Bank Coin.
 	Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
 }
 
@@ -1277,7 +1279,7 @@ func (m *QueryFunTokenMappingRequest) XXX_DiscardUnknown() {
 var xxx_messageInfo_QueryFunTokenMappingRequest proto.InternalMessageInfo
 
 type QueryFunTokenMappingResponse struct {
-	// fun_token is a mapping between the Cosmos native coin and the ERC20 contract address
+	// fun_token is a mapping between the Bank Coin and the ERC20 contract address
 	FunToken *FunToken `protobuf:"bytes,1,opt,name=fun_token,json=funToken,proto3" json:"fun_token,omitempty"`
 }
 
diff --git a/x/evm/statedb/config.go b/x/evm/statedb/config.go
index c75cdc179..887f591c5 100644
--- a/x/evm/statedb/config.go
+++ b/x/evm/statedb/config.go
@@ -18,21 +18,11 @@ type TxConfig struct {
 	LogIndex  uint            // the index of next log within current block
 }
 
-// NewTxConfig returns a TxConfig
-func NewTxConfig(bhash, thash gethcommon.Hash, txIndex, logIndex uint) TxConfig {
-	return TxConfig{
-		BlockHash: bhash,
-		TxHash:    thash,
-		TxIndex:   txIndex,
-		LogIndex:  logIndex,
-	}
-}
-
 // NewEmptyTxConfig construct an empty TxConfig,
 // used in context where there's no transaction, e.g. `eth_call`/`eth_estimateGas`.
-func NewEmptyTxConfig(bhash gethcommon.Hash) TxConfig {
+func NewEmptyTxConfig(blockHash gethcommon.Hash) TxConfig {
 	return TxConfig{
-		BlockHash: bhash,
+		BlockHash: blockHash,
 		TxHash:    gethcommon.Hash{},
 		TxIndex:   0,
 		LogIndex:  0,
diff --git a/x/evm/statedb/interfaces.go b/x/evm/statedb/interfaces.go
index 4ef8b2862..a4c1c3b59 100644
--- a/x/evm/statedb/interfaces.go
+++ b/x/evm/statedb/interfaces.go
@@ -14,7 +14,7 @@ import (
 // stateful precompiled contracts.
 type ExtStateDB interface {
 	vm.StateDB
-	AppendJournalEntry(JournalEntry)
+	AppendJournalEntry(JournalChange)
 }
 
 // Keeper provide underlying storage of StateDB
diff --git a/x/evm/statedb/journal.go b/x/evm/statedb/journal.go
index 14bb7b1df..ac041b617 100644
--- a/x/evm/statedb/journal.go
+++ b/x/evm/statedb/journal.go
@@ -24,9 +24,9 @@ import (
 	"github.com/ethereum/go-ethereum/common"
 )
 
-// JournalEntry is a modification entry in the state change journal that can be
-// Reverted on demand.
-type JournalEntry interface {
+// JournalChange, also called a "journal entry", is a modification entry in the
+// state change journal that can be reverted on demand.
+type JournalChange interface {
 	// Revert undoes the changes introduced by this journal entry.
 	Revert(*StateDB)
 
@@ -38,7 +38,7 @@ type JournalEntry interface {
 // commit. These are tracked to be able to be reverted in the case of an execution
 // exception or request for reversal.
 type journal struct {
-	entries []JournalEntry         // Current changes tracked by the journal
+	entries []JournalChange        // Current changes tracked by the journal
 	dirties map[common.Address]int // Dirty accounts and the number of changes
 }
 
@@ -62,7 +62,7 @@ func (j *journal) sortedDirties() []common.Address {
 }
 
 // append inserts a new modification entry to the end of the change journal.
-func (j *journal) append(entry JournalEntry) {
+func (j *journal) append(entry JournalChange) {
 	j.entries = append(j.entries, entry)
 	if addr := entry.Dirtied(); addr != nil {
 		j.dirties[*addr]++
@@ -86,58 +86,40 @@ func (j *journal) Revert(statedb *StateDB, snapshot int) {
 	j.entries = j.entries[:snapshot]
 }
 
-// length returns the current number of entries in the journal.
-func (j *journal) length() int {
+// Length returns the current number of entries in the journal.
+func (j *journal) Length() int {
 	return len(j.entries)
 }
 
-type (
-	// Changes to the account trie.
-	createObjectChange struct {
-		account *common.Address
-	}
-	resetObjectChange struct {
-		prev *stateObject
-	}
-	suicideChange struct {
-		account     *common.Address
-		prev        bool // whether account had already suicided
-		prevbalance *big.Int
+// 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
+}
 
-	// Changes to individual accounts.
-	balanceChange struct {
-		account *common.Address
-		prev    *big.Int
-	}
-	nonceChange struct {
-		account *common.Address
-		prev    uint64
-	}
-	storageChange struct {
-		account       *common.Address
-		key, prevalue common.Hash
-	}
-	codeChange struct {
-		account            *common.Address
-		prevcode, prevhash []byte
-	}
+func (s *StateDB) Dirties() map[common.Address]int {
+	return s.Journal.dirties
+}
 
-	// Changes to other state values.
-	refundChange struct {
-		prev uint64
-	}
-	addLogChange struct{}
+func (s *StateDB) Entries() []JournalChange {
+	return s.Journal.entries
+}
 
-	// Changes to the access list
-	accessListAddAccountChange struct {
-		address *common.Address
-	}
-	accessListAddSlotChange struct {
-		address *common.Address
-		slot    *common.Hash
-	}
-)
+// ------------------------------------------------------
+// createObjectChange
+
+// createObjectChange: [JournalChange] implementation for when
+// a new account (called an "object" in this context) is created in state.
+type createObjectChange struct {
+	account *common.Address
+}
+
+var _ JournalChange = createObjectChange{}
 
 func (ch createObjectChange) Revert(s *StateDB) {
 	delete(s.stateObjects, *ch.account)
@@ -147,6 +129,18 @@ func (ch createObjectChange) Dirtied() *common.Address {
 	return ch.account
 }
 
+// ------------------------------------------------------
+// resetObjectChange
+
+// resetObjectChange: [JournalChange] for an account that needs its
+// original state reset. This is used when an account's state is being replaced
+// and we need to revert to the previous version.
+type resetObjectChange struct {
+	prev *stateObject
+}
+
+var _ JournalChange = resetObjectChange{}
+
 func (ch resetObjectChange) Revert(s *StateDB) {
 	s.setStateObject(ch.prev)
 }
@@ -155,10 +149,21 @@ func (ch resetObjectChange) Dirtied() *common.Address {
 	return nil
 }
 
+// ------------------------------------------------------
+// suicideChange
+
+type suicideChange struct {
+	account     *common.Address
+	prev        bool // whether account had already suicided
+	prevbalance *big.Int
+}
+
+var _ JournalChange = suicideChange{}
+
 func (ch suicideChange) Revert(s *StateDB) {
 	obj := s.getStateObject(*ch.account)
 	if obj != nil {
-		obj.suicided = ch.prev
+		obj.Suicided = ch.prev
 		obj.setBalance(ch.prevbalance)
 	}
 }
@@ -167,14 +172,37 @@ func (ch suicideChange) Dirtied() *common.Address {
 	return ch.account
 }
 
+// ------------------------------------------------------
+// balanceChange
+
+// balanceChange: [JournalChange] for an update to the wei balance of an account.
+type balanceChange struct {
+	account *common.Address
+	prevWei *big.Int
+}
+
+var _ JournalChange = balanceChange{}
+
 func (ch balanceChange) Revert(s *StateDB) {
-	s.getStateObject(*ch.account).setBalance(ch.prev)
+	s.getStateObject(*ch.account).setBalance(ch.prevWei)
 }
 
 func (ch balanceChange) Dirtied() *common.Address {
 	return ch.account
 }
 
+// ------------------------------------------------------
+// nonceChange
+
+// nonceChange: [JournalChange] for an update to the nonce of an account.
+// The nonce is a counter of the number of transactions sent from an account.
+type nonceChange struct {
+	account *common.Address
+	prev    uint64
+}
+
+var _ JournalChange = nonceChange{}
+
 func (ch nonceChange) Revert(s *StateDB) {
 	s.getStateObject(*ch.account).setNonce(ch.prev)
 }
@@ -183,6 +211,19 @@ func (ch nonceChange) Dirtied() *common.Address {
 	return ch.account
 }
 
+// ------------------------------------------------------
+// codeChange
+
+// codeChange: [JournalChange] for an update to an account's code (smart contract
+// bytecode). The previous code and hash for the code are stored to enable
+// reversion.
+type codeChange struct {
+	account            *common.Address
+	prevcode, prevhash []byte
+}
+
+var _ JournalChange = codeChange{}
+
 func (ch codeChange) Revert(s *StateDB) {
 	s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode)
 }
@@ -191,6 +232,18 @@ func (ch codeChange) Dirtied() *common.Address {
 	return ch.account
 }
 
+// ------------------------------------------------------
+// storageChange
+
+// storageChange: [JournalChange] for the modification of a single key and value
+// within a contract's storage.
+type storageChange struct {
+	account       *common.Address
+	key, prevalue common.Hash
+}
+
+var _ JournalChange = storageChange{}
+
 func (ch storageChange) Revert(s *StateDB) {
 	s.getStateObject(*ch.account).setState(ch.key, ch.prevalue)
 }
@@ -199,6 +252,17 @@ func (ch storageChange) Dirtied() *common.Address {
 	return ch.account
 }
 
+// ------------------------------------------------------
+// refundChange
+
+// refundChange: [JournalChange] for the global gas refund counter.
+// This tracks changes to the gas refund value during contract execution.
+type refundChange struct {
+	prev uint64
+}
+
+var _ JournalChange = refundChange{}
+
 func (ch refundChange) Revert(s *StateDB) {
 	s.refund = ch.prev
 }
@@ -207,6 +271,15 @@ func (ch refundChange) Dirtied() *common.Address {
 	return nil
 }
 
+// ------------------------------------------------------
+// addLogChange
+
+// addLogChange represents [JournalChange] for a new log addition.
+// When reverted, it removes the last log from the accumulated logs list.
+type addLogChange struct{}
+
+var _ JournalChange = addLogChange{}
+
 func (ch addLogChange) Revert(s *StateDB) {
 	s.logs = s.logs[:len(s.logs)-1]
 }
@@ -215,16 +288,25 @@ func (ch addLogChange) Dirtied() *common.Address {
 	return nil
 }
 
+// ------------------------------------------------------
+// accessListAddAccountChange
+
+// accessListAddAccountChange represents [JournalChange] for when an address
+// is added to the access list. Access lists track warm storage slots for
+// gas cost calculations.
+type accessListAddAccountChange struct {
+	address *common.Address
+}
+
+// When an (address, slot) combination is added, it always results in two
+// journal entries if the address is not already present:
+//  1. `accessListAddAccountChange`: a journal change for the address
+//  2. `accessListAddSlotChange`: a journal change for the (address, slot)
+//     combination.
+//
+// Thus, when reverting, we can safely delete the address, as no storage slots
+// remain once the address entry is reverted.
 func (ch accessListAddAccountChange) Revert(s *StateDB) {
-	/*
-		One important invariant here, is that whenever a (addr, slot) is added, if the
-		addr is not already present, the add causes two journal entries:
-		- one for the address,
-		- one for the (address,slot)
-		Therefore, when unrolling the change, we can always blindly delete the
-		(addr) at this point, since no storage adds can remain when come upon
-		a single (addr) change.
-	*/
 	s.accessList.DeleteAddress(*ch.address)
 }
 
@@ -232,6 +314,20 @@ func (ch accessListAddAccountChange) Dirtied() *common.Address {
 	return nil
 }
 
+// ------------------------------------------------------
+// accessListAddSlotChange
+
+// accessListAddSlotChange: [JournalChange] implementations for
+type accessListAddSlotChange struct {
+	address *common.Address
+	slot    *common.Hash
+}
+
+// accessListAddSlotChange represents a [JournalChange] for when a storage slot
+// is added to an address's access list entry. This tracks individual storage
+// slots that have been accessed.
+var _ JournalChange = accessListAddSlotChange{}
+
 func (ch accessListAddSlotChange) Revert(s *StateDB) {
 	s.accessList.DeleteSlot(*ch.address, *ch.slot)
 }
diff --git a/x/evm/statedb/journal_test.go b/x/evm/statedb/journal_test.go
new file mode 100644
index 000000000..5863face5
--- /dev/null
+++ b/x/evm/statedb/journal_test.go
@@ -0,0 +1,181 @@
+package statedb_test
+
+import (
+	"fmt"
+	"math/big"
+	"strings"
+	"testing"
+
+	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/testutil/testapp"
+	"github.com/NibiruChain/nibiru/v2/x/evm"
+	"github.com/NibiruChain/nibiru/v2/x/evm/embeds"
+	"github.com/NibiruChain/nibiru/v2/x/evm/evmtest"
+	"github.com/NibiruChain/nibiru/v2/x/evm/precompile/test"
+	"github.com/NibiruChain/nibiru/v2/x/evm/statedb"
+)
+
+func (s *Suite) TestPrecompileSnapshots() {
+	deps := evmtest.NewTestDeps()
+	bankDenom := evm.EVMBankDenom
+	s.Require().NoError(testapp.FundAccount(
+		deps.App.BankKeeper,
+		deps.Ctx,
+		deps.Sender.NibiruAddr,
+		sdk.NewCoins(sdk.NewCoin(bankDenom, sdk.NewInt(69_420))),
+	))
+
+	s.T().Log("Set up helloworldcounter.wasm")
+
+	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)
+
+	deployArgs := []any{"name", "SYMBOL", uint8(18)}
+	deployResp, err := evmtest.DeployContract(
+		&deps,
+		embeds.SmartContract_ERC20Minter,
+		deployArgs...,
+	)
+	s.Require().NoError(err, deployResp)
+
+	contract := deployResp.ContractAddr
+	to, amount := deps.Sender.EthAddr, big.NewInt(69_420)
+	input, err := deps.EvmKeeper.ERC20().ABI.Pack("mint", to, amount)
+	s.Require().NoError(err)
+	_, evmObj, err := deps.EvmKeeper.CallContractWithInput(
+		deps.Ctx, deps.Sender.EthAddr, &contract, true, input,
+	)
+	s.Require().NoError(err)
+
+	s.Run("Populate dirty journal entries. Remove with Commit", func() {
+		stateDB := evmObj.StateDB.(*statedb.StateDB)
+		s.Equal(0, stateDB.DirtiesCount())
+
+		randomAcc := evmtest.NewEthPrivAcc().EthAddr
+		balDelta := evm.NativeToWei(big.NewInt(4))
+		// 2 dirties from [createObjectChange, balanceChange]
+		stateDB.AddBalance(randomAcc, balDelta)
+		// 1 dirties from [balanceChange]
+		stateDB.AddBalance(randomAcc, balDelta)
+		// 1 dirties from [balanceChange]
+		stateDB.SubBalance(randomAcc, balDelta)
+		if stateDB.DirtiesCount() != 4 {
+			debugDirtiesCountMismatch(stateDB, s.T())
+			s.FailNow("expected 4 dirty journal changes")
+		}
+
+		err = stateDB.Commit() // Dirties should be gone
+		s.NoError(err)
+		if stateDB.DirtiesCount() != 0 {
+			debugDirtiesCountMismatch(stateDB, s.T())
+			s.FailNow("expected 0 dirty journal changes")
+		}
+	})
+
+	s.Run("Emulate a contract that calls another contract", func() {
+		randomAcc := evmtest.NewEthPrivAcc().EthAddr
+		to, amount := randomAcc, big.NewInt(69_000)
+		input, err := embeds.SmartContract_ERC20Minter.ABI.Pack("transfer", to, amount)
+		s.Require().NoError(err)
+
+		leftoverGas := serverconfig.DefaultEthCallGasLimit
+		_, _, err = evmObj.Call(
+			vm.AccountRef(deps.Sender.EthAddr),
+			contract,
+			input,
+			leftoverGas,
+			big.NewInt(0),
+		)
+		s.Require().NoError(err)
+		stateDB := evmObj.StateDB.(*statedb.StateDB)
+		if stateDB.DirtiesCount() != 2 {
+			debugDirtiesCountMismatch(stateDB, s.T())
+			s.FailNow("expected 2 dirty journal changes")
+		}
+
+		// The contract calling itself is invalid in this context.
+		// Note the comment in vm.Contract:
+		//
+		// type Contract struct {
+		// 	// CallerAddress is the result of the caller which initialized this
+		// 	// contract. However when the "call method" is delegated this value
+		// 	// needs to be initialized to that of the caller's caller.
+		// 	CallerAddress common.Address
+		// 	// ...
+		// 	}
+		// 	//
+		_, _, err = evmObj.Call(
+			vm.AccountRef(contract),
+			contract,
+			input,
+			leftoverGas,
+			big.NewInt(0),
+		)
+		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)
+		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")
+		}
+	})
+}
+
+func debugDirtiesCountMismatch(db *statedb.StateDB, t *testing.T) string {
+	lines := []string{}
+	dirties := db.Dirties()
+	stateObjects := db.StateObjects()
+	for addr, dirtyCountForAddr := range dirties {
+		lines = append(lines, fmt.Sprintf("Dirty addr: %s, dirtyCountForAddr=%d", addr, dirtyCountForAddr))
+
+		// Inspect the actual state object
+		maybeObj := stateObjects[addr]
+		if maybeObj == nil {
+			lines = append(lines, "  no state object found!")
+			continue
+		}
+		obj := *maybeObj
+
+		lines = append(lines, fmt.Sprintf("  balance: %s", obj.Balance()))
+		lines = append(lines, fmt.Sprintf("  suicided: %v", obj.Suicided))
+		lines = append(lines, fmt.Sprintf("  dirtyCode: %v", obj.DirtyCode))
+
+		// Print storage state
+		lines = append(lines, fmt.Sprintf("  len(obj.DirtyStorage) entries: %d", len(obj.DirtyStorage)))
+		for k, v := range obj.DirtyStorage {
+			lines = append(lines, fmt.Sprintf("    key: %s, value: %s", k.Hex(), v.Hex()))
+			origVal := obj.OriginStorage[k]
+			lines = append(lines, fmt.Sprintf("    origin value: %s", origVal.Hex()))
+		}
+	}
+
+	t.Log("debugDirtiesCountMismatch:\n", strings.Join(lines, "\n"))
+	return ""
+}
diff --git a/x/evm/statedb/state_object.go b/x/evm/statedb/state_object.go
index bebbf7b40..e371beae0 100644
--- a/x/evm/statedb/state_object.go
+++ b/x/evm/statedb/state_object.go
@@ -115,14 +115,14 @@ type stateObject struct {
 	code    []byte
 
 	// state storage
-	originStorage Storage
-	dirtyStorage  Storage
+	OriginStorage Storage
+	DirtyStorage  Storage
 
 	address common.Address
 
 	// flags
-	dirtyCode bool
-	suicided  bool
+	DirtyCode bool
+	Suicided  bool
 }
 
 // newObject creates a state object.
@@ -138,8 +138,8 @@ func newObject(db *StateDB, address common.Address, account Account) *stateObjec
 		address: address,
 		// Reflect the micronibi (unibi) balance in wei
 		account:       account.ToWei(),
-		originStorage: make(Storage),
-		dirtyStorage:  make(Storage),
+		OriginStorage: make(Storage),
+		DirtyStorage:  make(Storage),
 	}
 }
 
@@ -170,9 +170,9 @@ func (s *stateObject) SubBalance(amount *big.Int) {
 
 // SetBalance update account balance.
 func (s *stateObject) SetBalance(amount *big.Int) {
-	s.db.journal.append(balanceChange{
+	s.db.Journal.append(balanceChange{
 		account: &s.address,
-		prev:    new(big.Int).Set(s.account.BalanceWei),
+		prevWei: new(big.Int).Set(s.account.BalanceWei),
 	})
 	s.setBalance(amount)
 }
@@ -212,7 +212,7 @@ func (s *stateObject) CodeSize() int {
 // SetCode set contract code to account
 func (s *stateObject) SetCode(codeHash common.Hash, code []byte) {
 	prevcode := s.Code()
-	s.db.journal.append(codeChange{
+	s.db.Journal.append(codeChange{
 		account:  &s.address,
 		prevhash: s.CodeHash(),
 		prevcode: prevcode,
@@ -223,12 +223,12 @@ func (s *stateObject) SetCode(codeHash common.Hash, code []byte) {
 func (s *stateObject) setCode(codeHash common.Hash, code []byte) {
 	s.code = code
 	s.account.CodeHash = codeHash[:]
-	s.dirtyCode = true
+	s.DirtyCode = true
 }
 
 // SetNonce set nonce to account
 func (s *stateObject) SetNonce(nonce uint64) {
-	s.db.journal.append(nonceChange{
+	s.db.Journal.append(nonceChange{
 		account: &s.address,
 		prev:    s.account.Nonce,
 	})
@@ -256,18 +256,18 @@ func (s *stateObject) Nonce() uint64 {
 
 // GetCommittedState query the committed state
 func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
-	if value, cached := s.originStorage[key]; cached {
+	if value, cached := s.OriginStorage[key]; cached {
 		return value
 	}
 	// If no live objects are available, load it from keeper
 	value := s.db.keeper.GetState(s.db.ctx, s.Address(), key)
-	s.originStorage[key] = value
+	s.OriginStorage[key] = value
 	return value
 }
 
 // GetState query the current state (including dirty state)
 func (s *stateObject) GetState(key common.Hash) common.Hash {
-	if value, dirty := s.dirtyStorage[key]; dirty {
+	if value, dirty := s.DirtyStorage[key]; dirty {
 		return value
 	}
 	return s.GetCommittedState(key)
@@ -281,7 +281,7 @@ func (s *stateObject) SetState(key common.Hash, value common.Hash) {
 		return
 	}
 	// New value is different, update and journal the change
-	s.db.journal.append(storageChange{
+	s.db.Journal.append(storageChange{
 		account:  &s.address,
 		key:      key,
 		prevalue: prev,
@@ -290,5 +290,5 @@ func (s *stateObject) SetState(key common.Hash, value common.Hash) {
 }
 
 func (s *stateObject) setState(key, value common.Hash) {
-	s.dirtyStorage[key] = value
+	s.DirtyStorage[key] = value
 }
diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go
index 27b81a3ce..223e92edb 100644
--- a/x/evm/statedb/statedb.go
+++ b/x/evm/statedb/statedb.go
@@ -30,11 +30,12 @@ var _ vm.StateDB = &StateDB{}
 // * Accounts
 type StateDB struct {
 	keeper Keeper
-	ctx    sdk.Context
+	// ctx is the persistent context used for official `StateDB.Commit` calls.
+	ctx sdk.Context
 
 	// Journal of state modifications. This is the backbone of
 	// Snapshot and RevertToSnapshot.
-	journal        *journal
+	Journal        *journal
 	validRevisions []revision
 	nextRevisionID int
 
@@ -58,7 +59,7 @@ func New(ctx sdk.Context, keeper Keeper, txConfig TxConfig) *StateDB {
 		keeper:       keeper,
 		ctx:          ctx,
 		stateObjects: make(map[common.Address]*stateObject),
-		journal:      newJournal(),
+		Journal:      newJournal(),
 		accessList:   newAccessList(),
 
 		txConfig: txConfig,
@@ -77,7 +78,7 @@ func (s *StateDB) GetContext() sdk.Context {
 
 // AddLog adds a log, called by evm.
 func (s *StateDB) AddLog(log *gethcore.Log) {
-	s.journal.append(addLogChange{})
+	s.Journal.append(addLogChange{})
 
 	log.TxHash = s.txConfig.TxHash
 	log.BlockHash = s.txConfig.BlockHash
@@ -93,14 +94,14 @@ func (s *StateDB) Logs() []*gethcore.Log {
 
 // AddRefund adds gas to the refund counter
 func (s *StateDB) AddRefund(gas uint64) {
-	s.journal.append(refundChange{prev: s.refund})
+	s.Journal.append(refundChange{prev: s.refund})
 	s.refund += gas
 }
 
 // SubRefund removes gas from the refund counter.
 // This method will panic if the refund counter goes below zero
 func (s *StateDB) SubRefund(gas uint64) {
-	s.journal.append(refundChange{prev: s.refund})
+	s.Journal.append(refundChange{prev: s.refund})
 	if gas > s.refund {
 		panic(fmt.Sprintf("Refund counter below zero (gas: %d > refund: %d)", gas, s.refund))
 	}
@@ -193,7 +194,7 @@ func (s *StateDB) GetRefund() uint64 {
 func (s *StateDB) HasSuicided(addr common.Address) bool {
 	stateObject := s.getStateObject(addr)
 	if stateObject != nil {
-		return stateObject.suicided
+		return stateObject.Suicided
 	}
 	return false
 }
@@ -239,9 +240,9 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject)
 
 	newobj = newObject(s, addr, Account{})
 	if prev == nil {
-		s.journal.append(createObjectChange{account: &addr})
+		s.Journal.append(createObjectChange{account: &addr})
 	} else {
-		s.journal.append(resetObjectChange{prev: prev})
+		s.Journal.append(resetObjectChange{prev: prev})
 	}
 	s.setStateObject(newobj)
 	if prev != nil {
@@ -274,7 +275,7 @@ func (s *StateDB) ForEachStorage(addr common.Address, cb func(key, value common.
 		return nil
 	}
 	s.keeper.ForEachStorage(s.ctx, addr, func(key, value common.Hash) bool {
-		if value, dirty := so.dirtyStorage[key]; dirty {
+		if value, dirty := so.DirtyStorage[key]; dirty {
 			return cb(key, value)
 		}
 		if len(value) > 0 {
@@ -294,18 +295,25 @@ func (s *StateDB) setStateObject(object *stateObject) {
  */
 
 // AddBalance adds amount to the account associated with addr.
-func (s *StateDB) AddBalance(addr common.Address, amount *big.Int) {
+func (s *StateDB) AddBalance(addr common.Address, wei *big.Int) {
 	stateObject := s.getOrNewStateObject(addr)
 	if stateObject != nil {
-		stateObject.AddBalance(amount)
+		stateObject.AddBalance(wei)
 	}
 }
 
 // SubBalance subtracts amount from the account associated with addr.
-func (s *StateDB) SubBalance(addr common.Address, amount *big.Int) {
+func (s *StateDB) SubBalance(addr common.Address, wei *big.Int) {
 	stateObject := s.getOrNewStateObject(addr)
 	if stateObject != nil {
-		stateObject.SubBalance(amount)
+		stateObject.SubBalance(wei)
+	}
+}
+
+func (s *StateDB) SetBalanceWei(addr common.Address, wei *big.Int) {
+	stateObject := s.getOrNewStateObject(addr)
+	if stateObject != nil {
+		stateObject.SetBalance(wei)
 	}
 }
 
@@ -343,12 +351,12 @@ func (s *StateDB) Suicide(addr common.Address) bool {
 	if stateObject == nil {
 		return false
 	}
-	s.journal.append(suicideChange{
+	s.Journal.append(suicideChange{
 		account:     &addr,
-		prev:        stateObject.suicided,
+		prev:        stateObject.Suicided,
 		prevbalance: new(big.Int).Set(stateObject.Balance()),
 	})
-	stateObject.suicided = true
+	stateObject.Suicided = true
 	stateObject.account.BalanceWei = new(big.Int)
 
 	return true
@@ -388,7 +396,7 @@ func (s *StateDB) PrepareAccessList(
 // AddAddressToAccessList adds the given address to the access list
 func (s *StateDB) AddAddressToAccessList(addr common.Address) {
 	if s.accessList.AddAddress(addr) {
-		s.journal.append(accessListAddAccountChange{&addr})
+		s.Journal.append(accessListAddAccountChange{&addr})
 	}
 }
 
@@ -400,10 +408,10 @@ func (s *StateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) {
 		// scope of 'address' without having the 'address' become already added
 		// to the access list (via call-variant, create, etc).
 		// Better safe than sorry, though
-		s.journal.append(accessListAddAccountChange{&addr})
+		s.Journal.append(accessListAddAccountChange{&addr})
 	}
 	if slotMod {
-		s.journal.append(accessListAddSlotChange{
+		s.Journal.append(accessListAddSlotChange{
 			address: &addr,
 			slot:    &slot,
 		})
@@ -424,7 +432,7 @@ func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addre
 func (s *StateDB) Snapshot() int {
 	id := s.nextRevisionID
 	s.nextRevisionID++
-	s.validRevisions = append(s.validRevisions, revision{id, s.journal.length()})
+	s.validRevisions = append(s.validRevisions, revision{id, s.Journal.Length()})
 	return id
 }
 
@@ -440,7 +448,7 @@ func (s *StateDB) RevertToSnapshot(revid int) {
 	snapshot := s.validRevisions[idx].journalIndex
 
 	// Replay the journal to undo changes and remove invalidated snapshots
-	s.journal.Revert(s, snapshot)
+	s.Journal.Revert(s, snapshot)
 	s.validRevisions = s.validRevisions[:idx]
 }
 
@@ -449,31 +457,45 @@ func errorf(format string, args ...any) error {
 	return fmt.Errorf("StateDB error: "+format, args...)
 }
 
-// Commit writes the dirty states to keeper
-// the StateDB object should be discarded after committed.
+// 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.
 func (s *StateDB) Commit() error {
-	for _, addr := range s.journal.sortedDirties() {
-		obj := s.stateObjects[addr]
-		if obj.suicided {
-			if err := s.keeper.DeleteAccount(s.ctx, obj.Address()); err != nil {
-				return errorf("failed to delete account: %w")
+	ctx := s.GetContext()
+	for _, addr := range s.Journal.sortedDirties() {
+		obj := s.getStateObject(addr)
+		if obj == nil {
+			continue
+		}
+		if obj.Suicided {
+			// Invariant: After [StateDB.Suicide] for some address, the
+			// corresponding account's state object is only available until the
+			// state is committed.
+			if err := s.keeper.DeleteAccount(ctx, obj.Address()); err != nil {
+				return errorf("failed to delete account: %w", err)
 			}
+			delete(s.stateObjects, addr)
 		} else {
-			if obj.code != nil && obj.dirtyCode {
-				s.keeper.SetCode(s.ctx, obj.CodeHash(), obj.code)
+			if obj.code != nil && obj.DirtyCode {
+				s.keeper.SetCode(ctx, obj.CodeHash(), obj.code)
 			}
-			if err := s.keeper.SetAccount(s.ctx, obj.Address(), obj.account.ToNative()); err != nil {
-				return errorf("failed to set account: %w")
+			if err := s.keeper.SetAccount(ctx, obj.Address(), obj.account.ToNative()); err != nil {
+				return errorf("failed to set account: %w", err)
 			}
-			for _, key := range obj.dirtyStorage.SortedKeys() {
-				value := obj.dirtyStorage[key]
-				// Skip noop changes, persist actual changes
-				if value == obj.originStorage[key] {
+			for _, key := range obj.DirtyStorage.SortedKeys() {
+				dirtyVal := obj.DirtyStorage[key]
+				// Values that match origin storage are not dirty.
+				if dirtyVal == obj.OriginStorage[key] {
 					continue
 				}
-				s.keeper.SetState(s.ctx, obj.Address(), key, value.Bytes())
+				// Persist committed changes
+				s.keeper.SetState(ctx, obj.Address(), key, dirtyVal.Bytes())
+				obj.OriginStorage[key] = dirtyVal
 			}
 		}
+		// Clear the dirty counts because all state changes have been
+		// committed.
+		s.Journal.dirties[addr] = 0
 	}
 	return nil
 }
diff --git a/x/evm/statedb/statedb_test.go b/x/evm/statedb/statedb_test.go
index b8b4d1741..7919d3da0 100644
--- a/x/evm/statedb/statedb_test.go
+++ b/x/evm/statedb/statedb_test.go
@@ -513,11 +513,12 @@ func (s *Suite) TestLog() {
 		txIdx       = uint(1)
 		logIdx      = uint(1)
 	)
-	txConfig := statedb.NewTxConfig(
-		blockHash,
-		txHash,
-		txIdx, logIdx,
-	)
+	txConfig := statedb.TxConfig{
+		BlockHash: blockHash,
+		TxHash:    txHash,
+		TxIndex:   txIdx,
+		LogIndex:  logIdx,
+	}
 
 	deps := evmtest.NewTestDeps()
 	db := statedb.New(deps.Ctx, &deps.App.EvmKeeper, txConfig)
diff --git a/x/evm/tx.pb.go b/x/evm/tx.pb.go
index f2082703b..f1ab92dae 100644
--- a/x/evm/tx.pb.go
+++ b/x/evm/tx.pb.go
@@ -475,8 +475,8 @@ func (m *MsgUpdateParamsResponse) XXX_DiscardUnknown() {
 var xxx_messageInfo_MsgUpdateParamsResponse proto.InternalMessageInfo
 
 // MsgCreateFunToken: Arguments to create a "FunToken" mapping. Either the ERC20
-// contract address can be given to create the mapping to a bank coin, or the
-// denomination for a bank coin can be given to create the mapping to an ERC20.
+// contract address can be given to create the mapping to a Bank Coin, or the
+// denomination for a Bank Coin can be given to create the mapping to an ERC20.
 type MsgCreateFunToken struct {
 	// Hexadecimal address of the ERC20 token to which the `FunToken` maps
 	FromErc20 *github_com_NibiruChain_nibiru_v2_eth.EIP55Addr `protobuf:"bytes,1,opt,name=from_erc20,json=fromErc20,proto3,customtype=github.com/NibiruChain/nibiru/v2/eth.EIP55Addr" json:"from_erc20,omitempty"`
@@ -578,13 +578,13 @@ func (m *MsgCreateFunTokenResponse) GetFuntokenMapping() FunToken {
 	return FunToken{}
 }
 
-// MsgConvertCoinToEvm: Arguments to send a bank coin to ERC-20 representation
+// MsgConvertCoinToEvm: Arguments to send a Bank Coin to ERC-20 representation
 type MsgConvertCoinToEvm struct {
 	// Hexadecimal address of the ERC20 token to which the `FunToken` maps
 	ToEthAddr github_com_NibiruChain_nibiru_v2_eth.EIP55Addr `protobuf:"bytes,1,opt,name=to_eth_addr,json=toEthAddr,proto3,customtype=github.com/NibiruChain/nibiru/v2/eth.EIP55Addr" json:"to_eth_addr"`
 	// Sender: Address for the signer of the transaction.
 	Sender string `protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty"`
-	// Bank coin to get converted to ERC20
+	// Bank Coin to get converted to ERC20
 	BankCoin types1.Coin `protobuf:"bytes,3,opt,name=bank_coin,json=bankCoin,proto3" json:"bank_coin" yaml:"bank_coin"`
 }
 
@@ -784,12 +784,12 @@ const _ = grpc.SupportPackageIsVersion4
 type MsgClient interface {
 	// EthereumTx defines a method submitting Ethereum transactions.
 	EthereumTx(ctx context.Context, in *MsgEthereumTx, opts ...grpc.CallOption) (*MsgEthereumTxResponse, error)
-	// UpdateParams defined a governance operation for updating the x/evm module parameters.
-	// The authority is hard-coded to the Cosmos SDK x/gov module account
+	// UpdateParams defined a governance operation for updating the x/evm module
+	// parameters. The authority is hard-coded to the x/gov module account
 	UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error)
 	// CreateFunToken: Create a "FunToken" mapping. Either the ERC20 contract
-	// address can be given to create the mapping to a bank coin, or the
-	// denomination for a bank coin can be given to create the mapping to an ERC20.
+	// address can be given to create the mapping to a Bank Coin, or the
+	// denomination for a Bank Coin can be given to create the mapping to an ERC20.
 	CreateFunToken(ctx context.Context, in *MsgCreateFunToken, opts ...grpc.CallOption) (*MsgCreateFunTokenResponse, error)
 	// ConvertCoinToEvm: Sends a coin with a valid "FunToken" mapping to the
 	// given recipient address ("to_eth_addr") in the corresponding ERC20
@@ -845,12 +845,12 @@ func (c *msgClient) ConvertCoinToEvm(ctx context.Context, in *MsgConvertCoinToEv
 type MsgServer interface {
 	// EthereumTx defines a method submitting Ethereum transactions.
 	EthereumTx(context.Context, *MsgEthereumTx) (*MsgEthereumTxResponse, error)
-	// UpdateParams defined a governance operation for updating the x/evm module parameters.
-	// The authority is hard-coded to the Cosmos SDK x/gov module account
+	// UpdateParams defined a governance operation for updating the x/evm module
+	// parameters. The authority is hard-coded to the x/gov module account
 	UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error)
 	// CreateFunToken: Create a "FunToken" mapping. Either the ERC20 contract
-	// address can be given to create the mapping to a bank coin, or the
-	// denomination for a bank coin can be given to create the mapping to an ERC20.
+	// address can be given to create the mapping to a Bank Coin, or the
+	// denomination for a Bank Coin can be given to create the mapping to an ERC20.
 	CreateFunToken(context.Context, *MsgCreateFunToken) (*MsgCreateFunTokenResponse, error)
 	// ConvertCoinToEvm: Sends a coin with a valid "FunToken" mapping to the
 	// given recipient address ("to_eth_addr") in the corresponding ERC20