diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c017d74e..ca1037d84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -98,6 +98,7 @@ depend on x/bank, such as the EVM and Wasm modules. tests for race conditions within funtoken precompile - [#2100](https://github.com/NibiruChain/nibiru/pull/2100) - refactor: cleanup statedb and precompile sections - [#2101](https://github.com/NibiruChain/nibiru/pull/2101) - fix(evm): tx receipt proper marshalling +- [#2105](https://github.com/NibiruChain/nibiru/pull/2105) - test(evm): precompile call with revert #### Nibiru EVM | Before Audit 1 - 2024-10-18 diff --git a/x/evm/embeds/artifacts/contracts/TestPrecompileSelfCallRevert.sol/TestPrecompileSelfCallRevert.json b/x/evm/embeds/artifacts/contracts/TestPrecompileSelfCallRevert.sol/TestPrecompileSelfCallRevert.json new file mode 100644 index 000000000..c56dfefac --- /dev/null +++ b/x/evm/embeds/artifacts/contracts/TestPrecompileSelfCallRevert.sol/TestPrecompileSelfCallRevert.json @@ -0,0 +1,78 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "TestPrecompileSelfCallRevert", + "sourceName": "contracts/TestPrecompileSelfCallRevert.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "erc20_", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address payable", + "name": "nativeRecipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "nativeAmount", + "type": "uint256" + }, + { + "internalType": "string", + "name": "precompileRecipient", + "type": "string" + }, + { + "internalType": "uint256", + "name": "precompileAmount", + "type": "uint256" + } + ], + "name": "selfCallTransferFunds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address payable", + "name": "nativeRecipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "nativeAmount", + "type": "uint256" + }, + { + "internalType": "string", + "name": "precompileRecipient", + "type": "string" + }, + { + "internalType": "uint256", + "name": "precompileAmount", + "type": "uint256" + } + ], + "name": "transferFunds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x60806040526000600155604051610c8e380380610c8e833981810160405281019061002a91906100d3565b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610100565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100a082610075565b9050919050565b6100b081610095565b81146100bb57600080fd5b50565b6000815190506100cd816100a7565b92915050565b6000602082840312156100e9576100e8610070565b5b60006100f7848285016100be565b91505092915050565b610b7f8061010f6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80636ad4fe961461003b578063705964db14610057575b600080fd5b610055600480360381019061005091906106c1565b610073565b005b610071600480360381019061006c91906106c1565b610208565b005b8373ffffffffffffffffffffffffffffffffffffffff166108fc849081150290604051600060405180830381858888f193505050506100e7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100de906107a1565b60405180910390fd5b600061080073ffffffffffffffffffffffffffffffffffffffff166303003bc560008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1684866040518463ffffffff1660e01b81526004016101489392919061085f565b6020604051808303816000875af1158015610167573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061018b91906108b2565b9050818114610199856102b2565b6101a2846102b2565b6040516020016101b39291906109d9565b60405160208183030381529060405290610203576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101fa9190610a26565b60405180910390fd5b600080fd5b6001600081548092919061021b90610a77565b91905055503073ffffffffffffffffffffffffffffffffffffffff16636ad4fe96858585856040518563ffffffff1660e01b815260040161025f9493929190610ace565b600060405180830381600087803b15801561027957600080fd5b505af192505050801561028a575060015b6102ab57600160008154809291906102a190610a77565b91905055506102ac565b5b50505050565b6060600060016102c184610380565b01905060008167ffffffffffffffff8111156102e0576102df610596565b5b6040519080825280601f01601f1916602001820160405280156103125781602001600182028036833780820191505090505b509050600082602001820190505b600115610375578080600190039150507f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a858161036957610368610b1a565b5b04945060008503610320575b819350505050919050565b600080600090507a184f03e93ff9f4daa797ed6e38ed64bf6a1f01000000000000000083106103de577a184f03e93ff9f4daa797ed6e38ed64bf6a1f01000000000000000083816103d4576103d3610b1a565b5b0492506040810190505b6d04ee2d6d415b85acef8100000000831061041b576d04ee2d6d415b85acef8100000000838161041157610410610b1a565b5b0492506020810190505b662386f26fc10000831061044a57662386f26fc1000083816104405761043f610b1a565b5b0492506010810190505b6305f5e1008310610473576305f5e100838161046957610468610b1a565b5b0492506008810190505b612710831061049857612710838161048e5761048d610b1a565b5b0492506004810190505b606483106104bb57606483816104b1576104b0610b1a565b5b0492506002810190505b600a83106104ca576001810190505b80915050919050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610512826104e7565b9050919050565b61052281610507565b811461052d57600080fd5b50565b60008135905061053f81610519565b92915050565b6000819050919050565b61055881610545565b811461056357600080fd5b50565b6000813590506105758161054f565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6105ce82610585565b810181811067ffffffffffffffff821117156105ed576105ec610596565b5b80604052505050565b60006106006104d3565b905061060c82826105c5565b919050565b600067ffffffffffffffff82111561062c5761062b610596565b5b61063582610585565b9050602081019050919050565b82818337600083830152505050565b600061066461065f84610611565b6105f6565b9050828152602081018484840111156106805761067f610580565b5b61068b848285610642565b509392505050565b600082601f8301126106a8576106a761057b565b5b81356106b8848260208601610651565b91505092915050565b600080600080608085870312156106db576106da6104dd565b5b60006106e987828801610530565b94505060206106fa87828801610566565b935050604085013567ffffffffffffffff81111561071b5761071a6104e2565b5b61072787828801610693565b925050606061073887828801610566565b91505092959194509250565b600082825260208201905092915050565b7f455448207472616e73666572206661696c656400000000000000000000000000600082015250565b600061078b601383610744565b915061079682610755565b602082019050919050565b600060208201905081810360008301526107ba8161077e565b9050919050565b60006107cc826104e7565b9050919050565b6107dc816107c1565b82525050565b6107eb81610545565b82525050565b600081519050919050565b60005b8381101561081a5780820151818401526020810190506107ff565b60008484015250505050565b6000610831826107f1565b61083b8185610744565b935061084b8185602086016107fc565b61085481610585565b840191505092915050565b600060608201905061087460008301866107d3565b61088160208301856107e2565b81810360408301526108938184610826565b9050949350505050565b6000815190506108ac8161054f565b92915050565b6000602082840312156108c8576108c76104dd565b5b60006108d68482850161089d565b91505092915050565b600081905092915050565b7f4946756e546f6b656e2e62616e6b53656e64207375636365656465642062757460008201527f207472616e73666572726564207468652077726f6e6720616d6f756e74000000602082015250565b6000610946603d836108df565b9150610951826108ea565b603d82019050919050565b7f73656e74416d6f756e7420000000000000000000000000000000000000000000815250565b600061098d826107f1565b61099781856108df565b93506109a78185602086016107fc565b80840191505092915050565b7f6578706563746564200000000000000000000000000000000000000000000000815250565b60006109e482610939565b91506109ef8261095c565b600b820191506109ff8285610982565b9150610a0a826109b3565b600982019150610a1a8284610982565b91508190509392505050565b60006020820190508181036000830152610a408184610826565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610a8282610545565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610ab457610ab3610a48565b5b600182019050919050565b610ac881610507565b82525050565b6000608082019050610ae36000830187610abf565b610af060208301866107e2565b8181036040830152610b028185610826565b9050610b1160608301846107e2565b95945050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fdfea26469706673582212209f94c0998ce7cd55cbc2c6e9d67d15c37043043fd2ad0f792c2c2c8b09b8284764736f6c63430008180033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c80636ad4fe961461003b578063705964db14610057575b600080fd5b610055600480360381019061005091906106c1565b610073565b005b610071600480360381019061006c91906106c1565b610208565b005b8373ffffffffffffffffffffffffffffffffffffffff166108fc849081150290604051600060405180830381858888f193505050506100e7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100de906107a1565b60405180910390fd5b600061080073ffffffffffffffffffffffffffffffffffffffff166303003bc560008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1684866040518463ffffffff1660e01b81526004016101489392919061085f565b6020604051808303816000875af1158015610167573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061018b91906108b2565b9050818114610199856102b2565b6101a2846102b2565b6040516020016101b39291906109d9565b60405160208183030381529060405290610203576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101fa9190610a26565b60405180910390fd5b600080fd5b6001600081548092919061021b90610a77565b91905055503073ffffffffffffffffffffffffffffffffffffffff16636ad4fe96858585856040518563ffffffff1660e01b815260040161025f9493929190610ace565b600060405180830381600087803b15801561027957600080fd5b505af192505050801561028a575060015b6102ab57600160008154809291906102a190610a77565b91905055506102ac565b5b50505050565b6060600060016102c184610380565b01905060008167ffffffffffffffff8111156102e0576102df610596565b5b6040519080825280601f01601f1916602001820160405280156103125781602001600182028036833780820191505090505b509050600082602001820190505b600115610375578080600190039150507f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a858161036957610368610b1a565b5b04945060008503610320575b819350505050919050565b600080600090507a184f03e93ff9f4daa797ed6e38ed64bf6a1f01000000000000000083106103de577a184f03e93ff9f4daa797ed6e38ed64bf6a1f01000000000000000083816103d4576103d3610b1a565b5b0492506040810190505b6d04ee2d6d415b85acef8100000000831061041b576d04ee2d6d415b85acef8100000000838161041157610410610b1a565b5b0492506020810190505b662386f26fc10000831061044a57662386f26fc1000083816104405761043f610b1a565b5b0492506010810190505b6305f5e1008310610473576305f5e100838161046957610468610b1a565b5b0492506008810190505b612710831061049857612710838161048e5761048d610b1a565b5b0492506004810190505b606483106104bb57606483816104b1576104b0610b1a565b5b0492506002810190505b600a83106104ca576001810190505b80915050919050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610512826104e7565b9050919050565b61052281610507565b811461052d57600080fd5b50565b60008135905061053f81610519565b92915050565b6000819050919050565b61055881610545565b811461056357600080fd5b50565b6000813590506105758161054f565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6105ce82610585565b810181811067ffffffffffffffff821117156105ed576105ec610596565b5b80604052505050565b60006106006104d3565b905061060c82826105c5565b919050565b600067ffffffffffffffff82111561062c5761062b610596565b5b61063582610585565b9050602081019050919050565b82818337600083830152505050565b600061066461065f84610611565b6105f6565b9050828152602081018484840111156106805761067f610580565b5b61068b848285610642565b509392505050565b600082601f8301126106a8576106a761057b565b5b81356106b8848260208601610651565b91505092915050565b600080600080608085870312156106db576106da6104dd565b5b60006106e987828801610530565b94505060206106fa87828801610566565b935050604085013567ffffffffffffffff81111561071b5761071a6104e2565b5b61072787828801610693565b925050606061073887828801610566565b91505092959194509250565b600082825260208201905092915050565b7f455448207472616e73666572206661696c656400000000000000000000000000600082015250565b600061078b601383610744565b915061079682610755565b602082019050919050565b600060208201905081810360008301526107ba8161077e565b9050919050565b60006107cc826104e7565b9050919050565b6107dc816107c1565b82525050565b6107eb81610545565b82525050565b600081519050919050565b60005b8381101561081a5780820151818401526020810190506107ff565b60008484015250505050565b6000610831826107f1565b61083b8185610744565b935061084b8185602086016107fc565b61085481610585565b840191505092915050565b600060608201905061087460008301866107d3565b61088160208301856107e2565b81810360408301526108938184610826565b9050949350505050565b6000815190506108ac8161054f565b92915050565b6000602082840312156108c8576108c76104dd565b5b60006108d68482850161089d565b91505092915050565b600081905092915050565b7f4946756e546f6b656e2e62616e6b53656e64207375636365656465642062757460008201527f207472616e73666572726564207468652077726f6e6720616d6f756e74000000602082015250565b6000610946603d836108df565b9150610951826108ea565b603d82019050919050565b7f73656e74416d6f756e7420000000000000000000000000000000000000000000815250565b600061098d826107f1565b61099781856108df565b93506109a78185602086016107fc565b80840191505092915050565b7f6578706563746564200000000000000000000000000000000000000000000000815250565b60006109e482610939565b91506109ef8261095c565b600b820191506109ff8285610982565b9150610a0a826109b3565b600982019150610a1a8284610982565b91508190509392505050565b60006020820190508181036000830152610a408184610826565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610a8282610545565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610ab457610ab3610a48565b5b600182019050919050565b610ac881610507565b82525050565b6000608082019050610ae36000830187610abf565b610af060208301866107e2565b8181036040830152610b028185610826565b9050610b1160608301846107e2565b95945050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fdfea26469706673582212209f94c0998ce7cd55cbc2c6e9d67d15c37043043fd2ad0f792c2c2c8b09b8284764736f6c63430008180033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/x/evm/embeds/contracts/TestPrecompileSelfCallRevert.sol b/x/evm/embeds/contracts/TestPrecompileSelfCallRevert.sol new file mode 100644 index 000000000..972691a71 --- /dev/null +++ b/x/evm/embeds/contracts/TestPrecompileSelfCallRevert.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import "./IFunToken.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; + +contract TestPrecompileSelfCallRevert { + address erc20; + uint counter = 0; + + constructor(address erc20_) payable { + erc20 = erc20_; + } + + function selfCallTransferFunds( + address payable nativeRecipient, + uint256 nativeAmount, + string memory precompileRecipient, + uint256 precompileAmount + ) external { + counter++; + try + TestPrecompileSelfCallRevert(payable(address(this))).transferFunds( + nativeRecipient, + nativeAmount, + precompileRecipient, + precompileAmount + ) + {} catch // [1] + { + counter++; + } + } + + function transferFunds( + address payable nativeRecipient, + uint256 nativeAmount, + string memory precompileRecipient, + uint256 precompileAmount + ) external { + require(nativeRecipient.send(nativeAmount), "ETH transfer failed"); // wei + + uint256 sentAmount = FUNTOKEN_PRECOMPILE.bankSend( + erc20, + precompileAmount, // micro-WNIBI + precompileRecipient + ); + + require( + sentAmount == precompileAmount, + string.concat( + "IFunToken.bankSend succeeded but transferred the wrong amount", + "sentAmount ", + Strings.toString(nativeAmount), + "expected ", + Strings.toString(precompileAmount) + ) + ); + + revert(); // [4] + } +} diff --git a/x/evm/embeds/embeds.go b/x/evm/embeds/embeds.go index 12b0d981c..202b26525 100644 --- a/x/evm/embeds/embeds.go +++ b/x/evm/embeds/embeds.go @@ -33,6 +33,8 @@ var ( testERC20TransferThenPrecompileSendJson []byte //go:embed artifacts/contracts/TestNativeSendThenPrecompileSend.sol/TestNativeSendThenPrecompileSend.json testNativeSendThenPrecompileSendJson []byte + //go:embed artifacts/contracts/TestPrecompileSelfCallRevert.sol/TestPrecompileSelfCallRevert.json + testPrecompileSelfCallRevertJson []byte ) var ( @@ -96,6 +98,16 @@ var ( Name: "TestERC20TransferThenPrecompileSend.sol", EmbedJSON: testERC20TransferThenPrecompileSendJson, } + + // SmartContract_TestPrecompileSelfCallRevert is a test contract + // that creates another instance of itself, calls the precompile method and then force reverts. + // It tests a race condition where the state DB commit + // may save the wrong state before the precompile execution, not revert it entirely, + // potentially causing an infinite mint of funds. + SmartContract_TestPrecompileSelfCallRevert = CompiledEvmContract{ + Name: "TestPrecompileSelfCallRevert.sol", + EmbedJSON: testPrecompileSelfCallRevertJson, + } ) func init() { @@ -108,6 +120,7 @@ func init() { SmartContract_TestERC20MaliciousTransfer.MustLoad() SmartContract_TestNativeSendThenPrecompileSendJson.MustLoad() SmartContract_TestERC20TransferThenPrecompileSend.MustLoad() + SmartContract_TestPrecompileSelfCallRevert.MustLoad() } type CompiledEvmContract struct { diff --git a/x/evm/keeper/funtoken_from_coin_test.go b/x/evm/keeper/funtoken_from_coin_test.go index f69143772..646391eea 100644 --- a/x/evm/keeper/funtoken_from_coin_test.go +++ b/x/evm/keeper/funtoken_from_coin_test.go @@ -447,23 +447,142 @@ func (s *FunTokenFromCoinSuite) TestERC20TransferThenPrecompileSend() { ) s.Require().NoError(err) - // Check 1: Alice has 9 NIBI in bank - aliceBankBalance := deps.App.BankKeeper.GetBalance(deps.Ctx, alice.NibiruAddr, bankDenom) - s.Require().Equal(sdk.NewInt(9e6), aliceBankBalance.Amount) + evmtest.FunTokenBalanceAssert{ + FunToken: funToken, + Account: alice.EthAddr, + BalanceBank: big.NewInt(9e6), + BalanceERC20: big.NewInt(1e6), + Description: "Alice has 9 NIBI / 1 WNIBI", + }.Assert(s.T(), deps) + + evmtest.FunTokenBalanceAssert{ + FunToken: funToken, + Account: testContractAddr, + BalanceBank: big.NewInt(0), + BalanceERC20: big.NewInt(0), + Description: "Test contract 0 NIBI / 0 WNIBI", + }.Assert(s.T(), deps) + + evmtest.FunTokenBalanceAssert{ + FunToken: funToken, + Account: evm.EVM_MODULE_ADDRESS, + BalanceBank: big.NewInt(1e6), + BalanceERC20: big.NewInt(0e6), + Description: "Module account has 1 NIBI escrowed", + }.Assert(s.T(), deps) +} - // Check 2: Alice has 1 WNIBI on ERC20 - aliceERC20Balance, err := deps.EvmKeeper.ERC20().BalanceOf(funToken.Erc20Addr.Address, alice.EthAddr, deps.Ctx) +// TestPrecompileSelfCallRevert +// 1. Creates a funtoken from coin. +// 2. Using the test contract, creates another instance of itself, calls the precompile method and then force reverts. +// It tests a race condition where the state DB commit +// may save the wrong state before the precompile execution, not revert it entirely, +// potentially causing an infinite mint of funds. +// +// INITIAL STATE: +// - Test contract funds: 10 NIBI, 10 WNIBI +// CONTRACT CALL: +// - Sends 1 NIBI to Alice using native send and 1 WNIBI -> NIBI to Charles using precompile +// EXPECTED: +// - all changes reverted +// - Test contract funds: 10 NIBI, 10 WNIBI +// - Alice: 0 NIBI +// - Charles: 0 NIBI +// - Module account: 10 NIBI escrowed (which Test contract holds as 10 WNIBI) +func (s *FunTokenFromCoinSuite) TestPrecompileSelfCallRevert() { + deps := evmtest.NewTestDeps() + bankDenom := evm.EVMBankDenom + + // Initial setup + funToken := s.fundAndCreateFunToken(deps, 10e6) + + s.T().Log("Deploy Test Contract") + deployResp, err := evmtest.DeployContract( + &deps, + embeds.SmartContract_TestPrecompileSelfCallRevert, + funToken.Erc20Addr.Address, + ) s.Require().NoError(err) - s.Require().Zero(big.NewInt(1e6).Cmp(aliceERC20Balance)) - // Check 3: test contract has 0 WNIBI on ERC20 - testContractERC20Balance, err := deps.EvmKeeper.ERC20().BalanceOf(funToken.Erc20Addr.Address, testContractAddr, deps.Ctx) + testContractAddr := deployResp.ContractAddr + + s.T().Log("Convert bank coin to erc-20: give test contract 10 WNIBI (erc20)") + _, err = deps.EvmKeeper.ConvertCoinToEvm( + sdk.WrapSDKContext(deps.Ctx), + &evm.MsgConvertCoinToEvm{ + Sender: deps.Sender.NibiruAddr.String(), + BankCoin: sdk.NewCoin(bankDenom, sdk.NewInt(10e6)), + ToEthAddr: eth.EIP55Addr{Address: testContractAddr}, + }, + ) s.Require().NoError(err) - s.Require().Zero(big.NewInt(0).Cmp(testContractERC20Balance)) - // Check 4: module balance has 1 NIBI escrowed (which Alice holds as 1 WNIBI) - moduleBalance := deps.App.BankKeeper.GetBalance(deps.Ctx, authtypes.NewModuleAddress(evm.ModuleName), bankDenom) - s.Require().Equal(sdk.NewInt(1e6), moduleBalance.Amount) + s.T().Log("Give the test contract 10 NIBI (native)") + s.Require().NoError(testapp.FundAccount( + deps.App.BankKeeper, + deps.Ctx, + eth.EthAddrToNibiruAddr(testContractAddr), + sdk.NewCoins(sdk.NewCoin(bankDenom, sdk.NewInt(10e6))), + )) + + evmtest.FunTokenBalanceAssert{ + FunToken: funToken, + Account: testContractAddr, + BalanceBank: big.NewInt(10e6), + BalanceERC20: big.NewInt(10e6), + Description: "Initial contract state sanity check: 10 NIBI / 10 WNIBI", + }.Assert(s.T(), deps) + + // Create Alice and Charles. Contract will try to send Alice native coins and send Charles tokens via bankSend + alice := evmtest.NewEthPrivAcc() + charles := evmtest.NewEthPrivAcc() + + s.T().Log("call test contract") + _, err = deps.EvmKeeper.CallContract( + deps.Ctx, + embeds.SmartContract_TestPrecompileSelfCallRevert.ABI, + deps.Sender.EthAddr, + &testContractAddr, + true, + "selfCallTransferFunds", + alice.EthAddr, + evm.NativeToWei(big.NewInt(1e6)), // native send uses wei units, + charles.NibiruAddr.String(), + big.NewInt(9e6), // for precompile bankSend: 6 decimals + ) + s.Require().NoError(err) + + evmtest.FunTokenBalanceAssert{ + FunToken: funToken, + Account: alice.EthAddr, + BalanceBank: big.NewInt(0), + BalanceERC20: big.NewInt(0), + Description: "Alice has 0 NIBI / 0 WNIBI", + }.Assert(s.T(), deps) + + evmtest.FunTokenBalanceAssert{ + FunToken: funToken, + Account: charles.EthAddr, + BalanceBank: big.NewInt(0), + BalanceERC20: big.NewInt(0), + Description: "Charles has 0 NIBI / 0 WNIBI", + }.Assert(s.T(), deps) + + evmtest.FunTokenBalanceAssert{ + FunToken: funToken, + Account: testContractAddr, + BalanceBank: big.NewInt(10e6), + BalanceERC20: big.NewInt(10e6), + Description: "Test contract has 10 NIBI / 10 WNIBI", + }.Assert(s.T(), deps) + + evmtest.FunTokenBalanceAssert{ + FunToken: funToken, + Account: evm.EVM_MODULE_ADDRESS, + BalanceBank: big.NewInt(10e6), + BalanceERC20: big.NewInt(0), + Description: "Module account has 10 NIBI escrowed", + }.Assert(s.T(), deps) } // fundAndCreateFunToken creates initial setup for tests