From cbb14bab5a934474ead221889cc2ebdeb01fbca8 Mon Sep 17 00:00:00 2001 From: Unique Divine <51418232+Unique-Divine@users.noreply.github.com> Date: Tue, 6 Aug 2024 16:15:06 +0200 Subject: [PATCH] feat(evm): use atto denomination for the wei units in the EVM so that NIBI is "ether" to clients (#1985) * refactor: remove unused vars. improve error clarity for testnetwork/New * refactor: use pebbledb as the test db * changelog * refactor(statedb): separate Account and AccountWei to have state objects manipulate in wei units * math functions for unibi and wei * chore: wei unit migration * test(statedb): complete the wei-based account migration. Remove all mocks * test(statedb_test.go): more thorough test cases * fix(e2e): avoid BigInt overflow with 10^18 values * pull /eth from ud/account-query * fix(evmante): CheckSenderBalance needs to use wei * revert: add back NibiruAccount query to mock client * fix(e2e-evm): add logging and fix tests * chore: resolve last few merge conflicts * refactor: include variable name change suggestion for BalanceNative * refactor: include variable name change suggestion for BalanceNative --- CHANGELOG.md | 4 +- app/evmante/evmante_can_transfer.go | 4 +- app/evmante/evmante_can_transfer_test.go | 12 +- app/evmante/evmante_gas_consume_test.go | 4 +- app/evmante/evmante_handler_test.go | 7 +- app/evmante/evmante_verify_eth_acc.go | 5 +- app/evmante/evmante_verify_eth_acc_test.go | 2 +- e2e/evm/test/basic_queries.test.ts | 25 +- e2e/evm/test/contract_send_nibi.test.ts | 67 ++- eth/eth_account.go | 31 +- eth/eth_account_test.go | 40 ++ eth/rpc/backend/account_info.go | 11 +- eth/rpc/backend/evm_query_client_test.go | 12 +- eth/rpc/backend/mocks/evm_query_client.go | 50 +-- eth/rpc/rpcapi/eth_api_test.go | 86 +++- eth/safe_math_test.go | 12 +- eth/state_encoder_test.go | 9 - proto/eth/evm/v1/query.proto | 8 +- x/evm/const.go | 63 +++ x/evm/evm_test.go | 65 +++ x/evm/evmtest/eth.go | 2 +- x/evm/evmtest/test_deps.go | 2 +- x/evm/evmtest/tx.go | 29 ++ x/evm/keeper/gas_fees.go | 6 +- x/evm/keeper/grpc_query.go | 21 +- x/evm/keeper/grpc_query_test.go | 54 ++- x/evm/keeper/keeper.go | 7 +- x/evm/keeper/keeper_test.go | 4 +- x/evm/keeper/msg_ethereum_tx_test.go | 8 +- x/evm/keeper/msg_server.go | 42 +- x/evm/keeper/statedb.go | 16 +- x/evm/keeper/statedb_test.go | 93 ++++ x/evm/query.pb.go | 274 +++++++----- x/evm/statedb/mock_test.go | 115 ----- x/evm/statedb/state_object.go | 104 +++-- x/evm/statedb/statedb.go | 21 +- x/evm/statedb/statedb_test.go | 466 +++++++++++---------- x/evm/tx_data.go | 1 + 38 files changed, 1130 insertions(+), 652 deletions(-) create mode 100644 eth/eth_account_test.go create mode 100644 x/evm/keeper/statedb_test.go delete mode 100644 x/evm/statedb/mock_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cf6f89bc..171c77ec6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -93,10 +93,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#1973](https://github.com/NibiruChain/nibiru/pull/1973) - chore(appconst): Add chain IDs ending in "3" to the "knownEthChainIDMap". This makes it possible to use devnet 3 and testnet 3. - [#1976](https://github.com/NibiruChain/nibiru/pull/1976) - refactor(evm): unique chain ids for all networks - [#1977](https://github.com/NibiruChain/nibiru/pull/1977) - fix(localnet): rolled back change of evm validator address with cosmos derivation path +- [#1979](https://github.com/NibiruChain/nibiru/pull/1979) -refactor(db): use pebbledb as the default db in integration tests - [#1981](https://github.com/NibiruChain/nibiru/pull/1981) - fix(evm): remove isCheckTx() short circuit on `AnteDecVerifyEthAcc` -- [#1979](https://github.com/NibiruChain/nibiru/pull/1979) - refactor(db): use pebbledb as the default db in integration tests - [#1982](https://github.com/NibiruChain/nibiru/pull/1982) - feat(evm): add GlobalMinGasPrices - [#1983](https://github.com/NibiruChain/nibiru/pull/1983) - chore(evm): remove ExtensionOptionsWeb3Tx and ExtensionOptionDynamicFeeTx +- [#1985](https://github.com/NibiruChain/nibiru/pull/1985) - feat(evm)!: Use atto denomination for the wei units in the EVM so that NIBI is "ether" to clients. Only micronibi (unibi) amounts can be transferred. All clients follow the constraint equation, 1 ether == 1 NIBI == 10^6 unibi == 10^18 wei. +======= #### Dapp modules: perp, spot, oracle, etc diff --git a/app/evmante/evmante_can_transfer.go b/app/evmante/evmante_can_transfer.go index 2826aa742..62196c794 100644 --- a/app/evmante/evmante_can_transfer.go +++ b/app/evmante/evmante_can_transfer.go @@ -87,10 +87,12 @@ func (ctd CanTransferDecorator) AnteHandle( // NOTE: here the gas consumed is from the context with the infinite gas meter if coreMsg.Value().Sign() > 0 && !evmInstance.Context.CanTransfer(stateDB, coreMsg.From(), coreMsg.Value()) { + balanceWei := stateDB.GetBalance(coreMsg.From()) return ctx, errors.Wrapf( errortypes.ErrInsufficientFunds, - "failed to transfer %s from address %s using the EVM block context transfer function", + "failed to transfer %s wei (balance=%s) from address %s using the EVM block context transfer function", coreMsg.Value(), + balanceWei, coreMsg.From(), ) } diff --git a/app/evmante/evmante_can_transfer_test.go b/app/evmante/evmante_can_transfer_test.go index 9345b144a..31cfa3443 100644 --- a/app/evmante/evmante_can_transfer_test.go +++ b/app/evmante/evmante_can_transfer_test.go @@ -1,12 +1,11 @@ package evmante_test import ( - "math/big" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/NibiruChain/nibiru/app/evmante" "github.com/NibiruChain/nibiru/eth" + "github.com/NibiruChain/nibiru/x/common/testutil/testapp" "github.com/NibiruChain/nibiru/x/evm/evmtest" "github.com/NibiruChain/nibiru/x/evm/statedb" ) @@ -22,7 +21,14 @@ func (s *TestSuite) TestCanTransferDecorator() { { name: "happy: signed tx, sufficient funds", beforeTxSetup: func(deps *evmtest.TestDeps, sdb *statedb.StateDB) { - sdb.AddBalance(deps.Sender.EthAddr, big.NewInt(100)) + s.NoError( + testapp.FundAccount( + deps.Chain.BankKeeper, + deps.Ctx, + deps.Sender.NibiruAddr, + sdk.NewCoins(sdk.NewInt64Coin(eth.EthBaseDenom, 100)), + ), + ) }, txSetup: func(deps *evmtest.TestDeps) sdk.FeeTx { txMsg := evmtest.HappyTransferTx(deps, 0) diff --git a/app/evmante/evmante_gas_consume_test.go b/app/evmante/evmante_gas_consume_test.go index a9b88e252..04f85c5e8 100644 --- a/app/evmante/evmante_gas_consume_test.go +++ b/app/evmante/evmante_gas_consume_test.go @@ -25,7 +25,7 @@ func (s *TestSuite) TestAnteDecEthGasConsume() { name: "happy: sender with funds", beforeTxSetup: func(deps *evmtest.TestDeps, sdb *statedb.StateDB) { gasLimit := happyGasLimit() - balance := new(big.Int).Add(gasLimit, big.NewInt(100)) + balance := evm.NativeToWei(new(big.Int).Add(gasLimit, big.NewInt(100))) sdb.AddBalance(deps.Sender.EthAddr, balance) }, txSetup: evmtest.HappyCreateContractTx, @@ -46,7 +46,7 @@ func (s *TestSuite) TestAnteDecEthGasConsume() { name: "sad: out of gas", beforeTxSetup: func(deps *evmtest.TestDeps, sdb *statedb.StateDB) { gasLimit := happyGasLimit() - balance := new(big.Int).Add(gasLimit, big.NewInt(100)) + balance := evm.NativeToWei(new(big.Int).Add(gasLimit, big.NewInt(100))) sdb.AddBalance(deps.Sender.EthAddr, balance) }, txSetup: evmtest.HappyCreateContractTx, diff --git a/app/evmante/evmante_handler_test.go b/app/evmante/evmante_handler_test.go index 8d63573ac..c1fa5c8d0 100644 --- a/app/evmante/evmante_handler_test.go +++ b/app/evmante/evmante_handler_test.go @@ -11,6 +11,7 @@ import ( "github.com/NibiruChain/nibiru/app/ante" "github.com/NibiruChain/nibiru/app/evmante" "github.com/NibiruChain/nibiru/eth" + "github.com/NibiruChain/nibiru/x/evm" "github.com/NibiruChain/nibiru/x/evm/evmtest" "github.com/NibiruChain/nibiru/x/evm/statedb" ) @@ -26,16 +27,18 @@ func (s *TestSuite) TestAnteHandlerEVM() { { name: "happy: signed tx, sufficient funds", beforeTxSetup: func(deps *evmtest.TestDeps, sdb *statedb.StateDB) { + balanceMicronibi := new(big.Int).Add(evmtest.GasLimitCreateContract(), big.NewInt(100)) sdb.AddBalance( deps.Sender.EthAddr, - new(big.Int).Add(evmtest.GasLimitCreateContract(), big.NewInt(100)), + evm.NativeToWei(balanceMicronibi), ) }, ctxSetup: func(deps *evmtest.TestDeps) { gasPrice := sdk.NewInt64Coin("unibi", 1) + maxGasMicronibi := new(big.Int).Add(evmtest.GasLimitCreateContract(), big.NewInt(100)) cp := &tmproto.ConsensusParams{ Block: &tmproto.BlockParams{ - MaxGas: new(big.Int).Add(evmtest.GasLimitCreateContract(), big.NewInt(100)).Int64(), + MaxGas: evm.NativeToWei(maxGasMicronibi).Int64(), }, } deps.Ctx = deps.Ctx. diff --git a/app/evmante/evmante_verify_eth_acc.go b/app/evmante/evmante_verify_eth_acc.go index 051f889b3..80f591926 100644 --- a/app/evmante/evmante_verify_eth_acc.go +++ b/app/evmante/evmante_verify_eth_acc.go @@ -3,7 +3,6 @@ package evmante import ( "cosmossdk.io/errors" - sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" errortypes "github.com/cosmos/cosmos-sdk/types/errors" gethcommon "github.com/ethereum/go-ethereum/common" @@ -69,7 +68,9 @@ func (anteDec AnteDecVerifyEthAcc) AnteHandle( "the sender is not EOA: address %s, codeHash <%s>", fromAddr, acct.CodeHash) } - if err := keeper.CheckSenderBalance(sdkmath.NewIntFromBigInt(acct.Balance), txData); err != nil { + if err := keeper.CheckSenderBalance( + evm.NativeToWei(acct.BalanceNative), txData, + ); err != nil { return ctx, errors.Wrap(err, "failed to check sender balance") } } diff --git a/app/evmante/evmante_verify_eth_acc_test.go b/app/evmante/evmante_verify_eth_acc_test.go index 06ba88604..f0fc05c08 100644 --- a/app/evmante/evmante_verify_eth_acc_test.go +++ b/app/evmante/evmante_verify_eth_acc_test.go @@ -21,7 +21,7 @@ func (s *TestSuite) TestAnteDecoratorVerifyEthAcc_CheckTx() { { name: "happy: sender with funds", beforeTxSetup: func(deps *evmtest.TestDeps, sdb *statedb.StateDB) { - sdb.AddBalance(deps.Sender.EthAddr, happyGasLimit()) + sdb.AddBalance(deps.Sender.EthAddr, evm.NativeToWei(happyGasLimit())) }, txSetup: evmtest.HappyCreateContractTx, wantErr: "", diff --git a/e2e/evm/test/basic_queries.test.ts b/e2e/evm/test/basic_queries.test.ts index 37643cce4..3ed1e2a8b 100644 --- a/e2e/evm/test/basic_queries.test.ts +++ b/e2e/evm/test/basic_queries.test.ts @@ -1,11 +1,11 @@ -import { describe, expect, it } from "bun:test"; // eslint-disable-line import/no-unresolved -import { toBigInt, Wallet } from "ethers"; -import { account, provider } from "./setup"; +import { describe, expect, it } from "bun:test" // eslint-disable-line import/no-unresolved +import { toBigInt, Wallet } from "ethers" +import { account, provider } from "./setup" describe("Basic Queries", () => { it("Simple transfer, balance check", async () => { const alice = Wallet.createRandom() - const amountToSend = toBigInt(1e3) // unibi + const amountToSend = toBigInt(5e12) * toBigInt(1e6) // unibi const senderBalanceBefore = await provider.getBalance(account) const recipientBalanceBefore = await provider.getBalance(alice) @@ -19,14 +19,25 @@ describe("Basic Queries", () => { value: amountToSend, } const txResponse = await account.sendTransaction(transaction) - await txResponse.wait() + await txResponse.wait(1, 10e3) expect(txResponse).toHaveProperty("blockHash") const senderBalanceAfter = await provider.getBalance(account) const recipientBalanceAfter = await provider.getBalance(alice) - const expectedSenderBalance = senderBalanceBefore - amountToSend - 50000n // 50k gas for the transaction - expect(senderBalanceAfter).toEqual(expectedSenderBalance) + // Assert balances with logging + const tenPow12 = toBigInt(1e12) + const gasUsed = 50000n // 50k gas for the transaction + const txCostMicronibi = amountToSend / tenPow12 + gasUsed + const txCostWei = txCostMicronibi * tenPow12 + const expectedSenderWei = senderBalanceBefore - txCostWei + console.debug("DEBUG should send via transfer method %o:", { + senderBalanceBefore, + amountToSend, + expectedSenderWei, + senderBalanceAfter, + }) + expect(senderBalanceAfter).toEqual(expectedSenderWei) expect(recipientBalanceAfter).toEqual(amountToSend) }, 20e3) }) diff --git a/e2e/evm/test/contract_send_nibi.test.ts b/e2e/evm/test/contract_send_nibi.test.ts index 2390ab442..08c5e6422 100644 --- a/e2e/evm/test/contract_send_nibi.test.ts +++ b/e2e/evm/test/contract_send_nibi.test.ts @@ -1,17 +1,17 @@ -import { describe, expect, it } from "bun:test"; // eslint-disable-line import/no-unresolved -import { toBigInt, Wallet } from "ethers"; -import { SendNibiCompiled__factory } from "../types/ethers-contracts"; -import { account, provider } from "./setup"; +import { describe, expect, it } from "bun:test" // eslint-disable-line import/no-unresolved +import { toBigInt, Wallet } from "ethers" +import { SendNibiCompiled__factory } from "../types/ethers-contracts" +import { account, provider } from "./setup" describe("Send NIBI via smart contract", async () => { - const factory = new SendNibiCompiled__factory(account); - const contract = await factory.deploy(); + const factory = new SendNibiCompiled__factory(account) + const contract = await factory.deploy() await contract.waitForDeployment() expect(contract.getAddress()).resolves.toBeDefined() it("should send via transfer method", async () => { const recipient = Wallet.createRandom() - const transferValue = toBigInt(100e6) // NIBI + const transferValue = toBigInt(5e12) * toBigInt(1e6) // 5 micro NIBI const ownerBalanceBefore = await provider.getBalance(account) // NIBI const recipientBalanceBefore = await provider.getBalance(recipient) // NIBI @@ -22,15 +22,25 @@ describe("Send NIBI via smart contract", async () => { }) const receipt = await tx.wait(1, 5e3) - expect(provider.getBalance(account)).resolves.toBe( - ownerBalanceBefore - transferValue - receipt.gasUsed, - ) + // Assert balances with logging + const tenPow12 = toBigInt(1e12) + const txCostMicronibi = transferValue / tenPow12 + receipt.gasUsed + const txCostWei = txCostMicronibi * tenPow12 + const expectedOwnerWei = ownerBalanceBefore - txCostWei + console.debug("DEBUG should send via transfer method %o:", { + ownerBalanceBefore, + transferValue, + gasUsed: receipt.gasUsed, + gasPrice: `${receipt.gasPrice.toString()} micronibi`, + expectedOwnerWei, + }) + expect(provider.getBalance(account)).resolves.toBe(expectedOwnerWei) expect(provider.getBalance(recipient)).resolves.toBe(transferValue) }, 20e3) it("should send via send method", async () => { const recipient = Wallet.createRandom() - const transferValue = toBigInt(100e6) // NIBI + const transferValue = toBigInt(100e12) * toBigInt(1e6) // 100 NIBi const ownerBalanceBefore = await provider.getBalance(account) // NIBI const recipientBalanceBefore = await provider.getBalance(recipient) // NIBI @@ -41,15 +51,25 @@ describe("Send NIBI via smart contract", async () => { }) const receipt = await tx.wait(1, 5e3) - expect(provider.getBalance(account)).resolves.toBe( - ownerBalanceBefore - transferValue - receipt.gasUsed, - ) + // Assert balances with logging + const tenPow12 = toBigInt(1e12) + const txCostMicronibi = transferValue / tenPow12 + receipt.gasUsed + const txCostWei = txCostMicronibi * tenPow12 + const expectedOwnerWei = ownerBalanceBefore - txCostWei + console.debug("DEBUG send via send method %o:", { + ownerBalanceBefore, + transferValue, + gasUsed: receipt.gasUsed, + gasPrice: `${receipt.gasPrice.toString()} micronibi`, + expectedOwnerWei, + }) + expect(provider.getBalance(account)).resolves.toBe(expectedOwnerWei) expect(provider.getBalance(recipient)).resolves.toBe(transferValue) }, 20e3) it("should send via transfer method", async () => { const recipient = Wallet.createRandom() - const transferValue = toBigInt(100e6) // NIBI + const transferValue = toBigInt(100e12) * toBigInt(1e6) // 100 NIBI const ownerBalanceBefore = await provider.getBalance(account) // NIBI const recipientBalanceBefore = await provider.getBalance(recipient) // NIBI @@ -60,10 +80,19 @@ describe("Send NIBI via smart contract", async () => { }) const receipt = await tx.wait(1, 5e3) - expect(provider.getBalance(account)).resolves.toBe( - ownerBalanceBefore - transferValue - receipt.gasUsed, - ) + // Assert balances with logging + const tenPow12 = toBigInt(1e12) + const txCostMicronibi = transferValue / tenPow12 + receipt.gasUsed + const txCostWei = txCostMicronibi * tenPow12 + const expectedOwnerWei = ownerBalanceBefore - txCostWei + console.debug("DEBUG should send via transfer method %o:", { + ownerBalanceBefore, + transferValue, + gasUsed: receipt.gasUsed, + gasPrice: `${receipt.gasPrice.toString()} micronibi`, + expectedOwnerWei, + }) + expect(provider.getBalance(account)).resolves.toBe(expectedOwnerWei) expect(provider.getBalance(recipient)).resolves.toBe(transferValue) }, 20e3) - }) diff --git a/eth/eth_account.go b/eth/eth_account.go index 781f09f06..85a857b5a 100644 --- a/eth/eth_account.go +++ b/eth/eth_account.go @@ -7,10 +7,19 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/ethereum/go-ethereum/common" + sdk "github.com/cosmos/cosmos-sdk/types" + gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) +func EthAddrToNibiruAddr(ethAddr gethcommon.Address) sdk.AccAddress { + return ethAddr.Bytes() +} + +func NibiruAddrToEthAddr(nibiruAddr sdk.AccAddress) gethcommon.Address { + return gethcommon.BytesToAddress(nibiruAddr.Bytes()) +} + var ( _ authtypes.AccountI = (*EthAccount)(nil) _ EthAccountI = (*EthAccount)(nil) @@ -32,11 +41,11 @@ const ( type EthAccountI interface { //revive:disable-line:exported authtypes.AccountI // EthAddress returns the ethereum Address representation of the AccAddress - EthAddress() common.Address + EthAddress() gethcommon.Address // CodeHash is the keccak256 hash of the contract code (if any) - GetCodeHash() common.Hash + GetCodeHash() gethcommon.Hash // SetCodeHash sets the code hash to the account fields - SetCodeHash(code common.Hash) error + SetCodeHash(code gethcommon.Hash) error // Type returns the type of Ethereum Account (EOA or Contract) Type() EthAccType } @@ -46,15 +55,15 @@ func (acc EthAccount) GetBaseAccount() *authtypes.BaseAccount { } // EthAddress returns the account address ethereum format. -func (acc EthAccount) EthAddress() common.Address { - return common.BytesToAddress(acc.GetAddress().Bytes()) +func (acc EthAccount) EthAddress() gethcommon.Address { + return gethcommon.BytesToAddress(acc.GetAddress().Bytes()) } -func (acc EthAccount) GetCodeHash() common.Hash { - return common.HexToHash(acc.CodeHash) +func (acc EthAccount) GetCodeHash() gethcommon.Hash { + return gethcommon.HexToHash(acc.CodeHash) } -func (acc *EthAccount) SetCodeHash(codeHash common.Hash) error { +func (acc *EthAccount) SetCodeHash(codeHash gethcommon.Hash) error { acc.CodeHash = codeHash.Hex() return nil } @@ -62,7 +71,7 @@ func (acc *EthAccount) SetCodeHash(codeHash common.Hash) error { // Type returns the type of Ethereum Account (EOA or Contract) func (acc EthAccount) Type() EthAccType { if bytes.Equal( - emptyCodeHash, common.HexToHash(acc.CodeHash).Bytes(), + emptyCodeHash, gethcommon.HexToHash(acc.CodeHash).Bytes(), ) { return EthAccType_EOA } @@ -79,6 +88,6 @@ var emptyCodeHash = crypto.Keccak256(nil) func ProtoBaseAccount() authtypes.AccountI { return &EthAccount{ BaseAccount: &authtypes.BaseAccount{}, - CodeHash: common.BytesToHash(emptyCodeHash).String(), + CodeHash: gethcommon.BytesToHash(emptyCodeHash).String(), } } diff --git a/eth/eth_account_test.go b/eth/eth_account_test.go new file mode 100644 index 000000000..3777f086d --- /dev/null +++ b/eth/eth_account_test.go @@ -0,0 +1,40 @@ +package eth_test + +import ( + "github.com/NibiruChain/nibiru/eth" + "github.com/NibiruChain/nibiru/x/evm/evmtest" +) + +func (s *Suite) TestEthAddrToNibiruAddr() { + accInfo := evmtest.NewEthAccInfo() + s.Equal( + accInfo.EthAddr, + eth.NibiruAddrToEthAddr(accInfo.NibiruAddr), + ) + s.Equal( + accInfo.NibiruAddr, + eth.EthAddrToNibiruAddr(accInfo.EthAddr), + ) + + s.T().Log("unit operation - hex -> nibi -> hex") + { + addr := evmtest.NewEthAccInfo().NibiruAddr + s.Equal( + addr, + eth.EthAddrToNibiruAddr( + eth.NibiruAddrToEthAddr(addr), + ), + ) + } + + s.T().Log("unit operation - nibi -> hex -> nibi") + { + addr := evmtest.NewEthAccInfo().EthAddr + s.Equal( + addr, + eth.NibiruAddrToEthAddr( + eth.EthAddrToNibiruAddr(addr), + ), + ) + } +} diff --git a/eth/rpc/backend/account_info.go b/eth/rpc/backend/account_info.go index a3340983d..f27e3c70d 100644 --- a/eth/rpc/backend/account_info.go +++ b/eth/rpc/backend/account_info.go @@ -111,7 +111,7 @@ func (b *Backend) GetProof( return nil, err } - balance, ok := sdkmath.NewIntFromString(res.Balance) + balance, ok := sdkmath.NewIntFromString(res.BalanceWei) if !ok { return nil, errors.New("invalid balance") } @@ -152,7 +152,10 @@ func (b *Backend) GetStorageAt(address common.Address, key string, blockNrOrHash } // GetBalance returns the provided account's balance up to the provided block number. -func (b *Backend) GetBalance(address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Big, error) { +func (b *Backend) GetBalance( + address common.Address, + blockNrOrHash rpc.BlockNumberOrHash, +) (*hexutil.Big, error) { blockNum, err := b.BlockNumberFromTendermint(blockNrOrHash) if err != nil { return nil, err @@ -172,9 +175,9 @@ func (b *Backend) GetBalance(address common.Address, blockNrOrHash rpc.BlockNumb return nil, err } - val, ok := sdkmath.NewIntFromString(res.Balance) + val, ok := sdkmath.NewIntFromString(res.BalanceWei) if !ok { - return nil, errors.New("invalid balance") + return nil, fmt.Errorf("invalid balance: %s", res.BalanceWei) } // balance can only be negative in case of pruned node diff --git a/eth/rpc/backend/evm_query_client_test.go b/eth/rpc/backend/evm_query_client_test.go index 974694dc9..3a5481a9e 100644 --- a/eth/rpc/backend/evm_query_client_test.go +++ b/eth/rpc/backend/evm_query_client_test.go @@ -289,9 +289,9 @@ func RegisterAccount( ) { queryClient.On("EthAccount", rpc.NewContextWithHeight(height), &evm.QueryEthAccountRequest{Address: addr.String()}). Return(&evm.QueryEthAccountResponse{ - Balance: "0", - CodeHash: "", - Nonce: 0, + BalanceWei: "0", + CodeHash: "", + Nonce: 0, }, nil, ) @@ -302,21 +302,21 @@ func RegisterBalance( queryClient *mocks.EVMQueryClient, addr common.Address, height int64, ) { queryClient.On("Balance", rpc.NewContextWithHeight(height), &evm.QueryBalanceRequest{Address: addr.String()}). - Return(&evm.QueryBalanceResponse{Balance: "1"}, nil) + Return(&evm.QueryBalanceResponse{BalanceWei: "1"}, nil) } func RegisterBalanceInvalid( queryClient *mocks.EVMQueryClient, addr common.Address, height int64, ) { queryClient.On("Balance", rpc.NewContextWithHeight(height), &evm.QueryBalanceRequest{Address: addr.String()}). - Return(&evm.QueryBalanceResponse{Balance: "invalid"}, nil) + Return(&evm.QueryBalanceResponse{BalanceWei: "invalid"}, nil) } func RegisterBalanceNegative( queryClient *mocks.EVMQueryClient, addr common.Address, height int64, ) { queryClient.On("Balance", rpc.NewContextWithHeight(height), &evm.QueryBalanceRequest{Address: addr.String()}). - Return(&evm.QueryBalanceResponse{Balance: "-1"}, nil) + Return(&evm.QueryBalanceResponse{BalanceWei: "-1"}, nil) } func RegisterBalanceError( diff --git a/eth/rpc/backend/mocks/evm_query_client.go b/eth/rpc/backend/mocks/evm_query_client.go index 252ee8b0f..1f90610a4 100644 --- a/eth/rpc/backend/mocks/evm_query_client.go +++ b/eth/rpc/backend/mocks/evm_query_client.go @@ -47,8 +47,10 @@ func (_m *EVMQueryClient) EthAccount(ctx context.Context, in *evm.QueryEthAccoun return r0, r1 } -// Balance provides a mock function with given fields: ctx, in, opts -func (_m *EVMQueryClient) Balance(ctx context.Context, in *evm.QueryBalanceRequest, opts ...grpc.CallOption) (*evm.QueryBalanceResponse, error) { +// NibiruAccount provides a mock function with given fields: ctx, in, opts +func (_m *EVMQueryClient) NibiruAccount( + ctx context.Context, in *evm.QueryNibiruAccountRequest, opts ...grpc.CallOption, +) (*evm.QueryNibiruAccountResponse, error) { _va := make([]interface{}, len(opts)) for _i := range opts { _va[_i] = opts[_i] @@ -58,17 +60,17 @@ func (_m *EVMQueryClient) Balance(ctx context.Context, in *evm.QueryBalanceReque _ca = append(_ca, _va...) ret := _m.Called(_ca...) - var r0 *evm.QueryBalanceResponse - if rf, ok := ret.Get(0).(func(context.Context, *evm.QueryBalanceRequest, ...grpc.CallOption) *evm.QueryBalanceResponse); ok { + var r0 *evm.QueryNibiruAccountResponse + if rf, ok := ret.Get(0).(func(context.Context, *evm.QueryNibiruAccountRequest, ...grpc.CallOption) *evm.QueryNibiruAccountResponse); ok { r0 = rf(ctx, in, opts...) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*evm.QueryBalanceResponse) + r0 = ret.Get(0).(*evm.QueryNibiruAccountResponse) } } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, *evm.QueryBalanceRequest, ...grpc.CallOption) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *evm.QueryNibiruAccountRequest, ...grpc.CallOption) error); ok { r1 = rf(ctx, in, opts...) } else { r1 = ret.Error(1) @@ -77,8 +79,8 @@ func (_m *EVMQueryClient) Balance(ctx context.Context, in *evm.QueryBalanceReque return r0, r1 } -// BaseFee provides a mock function with given fields: ctx, in, opts -func (_m *EVMQueryClient) BaseFee(ctx context.Context, in *evm.QueryBaseFeeRequest, opts ...grpc.CallOption) (*evm.QueryBaseFeeResponse, error) { +// Balance provides a mock function with given fields: ctx, in, opts +func (_m *EVMQueryClient) Balance(ctx context.Context, in *evm.QueryBalanceRequest, opts ...grpc.CallOption) (*evm.QueryBalanceResponse, error) { _va := make([]interface{}, len(opts)) for _i := range opts { _va[_i] = opts[_i] @@ -88,17 +90,17 @@ func (_m *EVMQueryClient) BaseFee(ctx context.Context, in *evm.QueryBaseFeeReque _ca = append(_ca, _va...) ret := _m.Called(_ca...) - var r0 *evm.QueryBaseFeeResponse - if rf, ok := ret.Get(0).(func(context.Context, *evm.QueryBaseFeeRequest, ...grpc.CallOption) *evm.QueryBaseFeeResponse); ok { + var r0 *evm.QueryBalanceResponse + if rf, ok := ret.Get(0).(func(context.Context, *evm.QueryBalanceRequest, ...grpc.CallOption) *evm.QueryBalanceResponse); ok { r0 = rf(ctx, in, opts...) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*evm.QueryBaseFeeResponse) + r0 = ret.Get(0).(*evm.QueryBalanceResponse) } } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, *evm.QueryBaseFeeRequest, ...grpc.CallOption) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *evm.QueryBalanceRequest, ...grpc.CallOption) error); ok { r1 = rf(ctx, in, opts...) } else { r1 = ret.Error(1) @@ -107,8 +109,8 @@ func (_m *EVMQueryClient) BaseFee(ctx context.Context, in *evm.QueryBaseFeeReque return r0, r1 } -// Code provides a mock function with given fields: ctx, in, opts -func (_m *EVMQueryClient) Code(ctx context.Context, in *evm.QueryCodeRequest, opts ...grpc.CallOption) (*evm.QueryCodeResponse, error) { +// BaseFee provides a mock function with given fields: ctx, in, opts +func (_m *EVMQueryClient) BaseFee(ctx context.Context, in *evm.QueryBaseFeeRequest, opts ...grpc.CallOption) (*evm.QueryBaseFeeResponse, error) { _va := make([]interface{}, len(opts)) for _i := range opts { _va[_i] = opts[_i] @@ -118,17 +120,17 @@ func (_m *EVMQueryClient) Code(ctx context.Context, in *evm.QueryCodeRequest, op _ca = append(_ca, _va...) ret := _m.Called(_ca...) - var r0 *evm.QueryCodeResponse - if rf, ok := ret.Get(0).(func(context.Context, *evm.QueryCodeRequest, ...grpc.CallOption) *evm.QueryCodeResponse); ok { + var r0 *evm.QueryBaseFeeResponse + if rf, ok := ret.Get(0).(func(context.Context, *evm.QueryBaseFeeRequest, ...grpc.CallOption) *evm.QueryBaseFeeResponse); ok { r0 = rf(ctx, in, opts...) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*evm.QueryCodeResponse) + r0 = ret.Get(0).(*evm.QueryBaseFeeResponse) } } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, *evm.QueryCodeRequest, ...grpc.CallOption) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *evm.QueryBaseFeeRequest, ...grpc.CallOption) error); ok { r1 = rf(ctx, in, opts...) } else { r1 = ret.Error(1) @@ -137,8 +139,8 @@ func (_m *EVMQueryClient) Code(ctx context.Context, in *evm.QueryCodeRequest, op return r0, r1 } -// NibiruAccount provides a mock function with given fields: ctx, in, opts -func (_m *EVMQueryClient) NibiruAccount(ctx context.Context, in *evm.QueryNibiruAccountRequest, opts ...grpc.CallOption) (*evm.QueryNibiruAccountResponse, error) { +// Code provides a mock function with given fields: ctx, in, opts +func (_m *EVMQueryClient) Code(ctx context.Context, in *evm.QueryCodeRequest, opts ...grpc.CallOption) (*evm.QueryCodeResponse, error) { _va := make([]interface{}, len(opts)) for _i := range opts { _va[_i] = opts[_i] @@ -148,17 +150,17 @@ func (_m *EVMQueryClient) NibiruAccount(ctx context.Context, in *evm.QueryNibiru _ca = append(_ca, _va...) ret := _m.Called(_ca...) - var r0 *evm.QueryNibiruAccountResponse - if rf, ok := ret.Get(0).(func(context.Context, *evm.QueryNibiruAccountRequest, ...grpc.CallOption) *evm.QueryNibiruAccountResponse); ok { + var r0 *evm.QueryCodeResponse + if rf, ok := ret.Get(0).(func(context.Context, *evm.QueryCodeRequest, ...grpc.CallOption) *evm.QueryCodeResponse); ok { r0 = rf(ctx, in, opts...) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*evm.QueryNibiruAccountResponse) + r0 = ret.Get(0).(*evm.QueryCodeResponse) } } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, *evm.QueryNibiruAccountRequest, ...grpc.CallOption) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *evm.QueryCodeRequest, ...grpc.CallOption) error); ok { r1 = rf(ctx, in, opts...) } else { r1 = ret.Error(1) diff --git a/eth/rpc/rpcapi/eth_api_test.go b/eth/rpc/rpcapi/eth_api_test.go index 89398a373..7e4170216 100644 --- a/eth/rpc/rpcapi/eth_api_test.go +++ b/eth/rpc/rpcapi/eth_api_test.go @@ -3,10 +3,13 @@ package rpcapi_test import ( "context" "crypto/ecdsa" + "fmt" "math/big" + "strings" "testing" sdk "github.com/cosmos/cosmos-sdk/types" + bank "github.com/cosmos/cosmos-sdk/x/bank/types" geth "github.com/ethereum/go-ethereum" gethcommon "github.com/ethereum/go-ethereum/common" gethcore "github.com/ethereum/go-ethereum/core/types" @@ -15,11 +18,12 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/NibiruChain/nibiru/app/appconst" + "github.com/NibiruChain/nibiru/eth" + "github.com/NibiruChain/nibiru/gosdk" nibirucommon "github.com/NibiruChain/nibiru/x/common" - "github.com/NibiruChain/nibiru/x/common/denoms" + "github.com/NibiruChain/nibiru/x/evm" "github.com/NibiruChain/nibiru/x/evm/embeds" - "github.com/NibiruChain/nibiru/x/evm/evmtest" "github.com/stretchr/testify/suite" @@ -30,7 +34,10 @@ import ( "github.com/NibiruChain/nibiru/x/common/testutil/testnetwork" ) -var _ suite.TearDownAllSuite = (*TestSuite)(nil) +var ( + _ suite.TearDownAllSuite = (*TestSuite)(nil) + _ suite.SetupAllSuite = (*TestSuite)(nil) +) type TestSuite struct { suite.Suite @@ -50,7 +57,8 @@ func TestSuite_RunAll(t *testing.T) { suite.Run(t, new(TestSuite)) } -// SetupSuite initialize network +// SetupSuite runs before every test in the suite. Implements the +// "suite.SetupAllSuite" interface. func (s *TestSuite) SetupSuite() { testutil.BeforeIntegrationSuite(s.T()) testapp.EnsureNibiruPrefix() @@ -70,12 +78,12 @@ func (s *TestSuite) SetupSuite() { testAccPrivateKey, _ := crypto.GenerateKey() s.fundedAccPrivateKey = testAccPrivateKey s.fundedAccEthAddr = crypto.PubkeyToAddress(testAccPrivateKey.PublicKey) - s.fundedAccNibiAddr = evmtest.EthAddrToNibiruAddr(s.fundedAccEthAddr) + s.fundedAccNibiAddr = eth.EthAddrToNibiruAddr(s.fundedAccEthAddr) val := s.network.Validators[0] - funds := sdk.NewCoins(sdk.NewInt64Coin(denoms.NIBI, 100_000_000)) // 10 NIBI - s.NoError(testnetwork.FillWalletFromValidator(s.fundedAccNibiAddr, funds, val, denoms.NIBI)) + funds := sdk.NewCoins(sdk.NewInt64Coin(eth.EthBaseDenom, 100_000_000)) // 10 NIBI + s.NoError(testnetwork.FillWalletFromValidator(s.fundedAccNibiAddr, funds, val, eth.EthBaseDenom)) s.NoError(s.network.WaitForNextBlock()) } @@ -172,11 +180,20 @@ func (s *TestSuite) Test_EstimateGas() { From: s.fundedAccEthAddr, To: &testAccEthAddr, Gas: gasLimit, - Value: big.NewInt(1), + Value: evm.NativeToWei(big.NewInt(1)), } gasEstimated, err := s.ethClient.EstimateGas(context.Background(), msg) s.NoError(err) - s.Equal(gasEstimated, gasLimit) + s.Equal(fmt.Sprintf("%d", gasLimit), fmt.Sprintf("%d", gasEstimated)) + + for _, msgValue := range []*big.Int{ + big.NewInt(1), + new(big.Int).Sub(evm.NativeToWei(big.NewInt(1)), big.NewInt(1)), // 10^12 - 1 + } { + msg.Value = msgValue + _, err = s.ethClient.EstimateGas(context.Background(), msg) + s.ErrorContains(err, "StateDB: wei amount is too small") + } } // Test_SuggestGasPrice EVM method: eth_gasPrice @@ -193,44 +210,67 @@ func (s *TestSuite) Test_SimpleTransferTransaction() { nonce, err := s.ethClient.PendingNonceAt(context.Background(), s.fundedAccEthAddr) s.NoError(err) - senderBalanceBefore, err := s.ethClient.BalanceAt( + recipientAddr := gethcommon.BytesToAddress(testnetwork.NewAccount(s.network, "recipient")) + recipientBalanceBefore, err := s.ethClient.BalanceAt(context.Background(), recipientAddr, nil) + s.Require().NoError(err) + s.Equal(int64(0), recipientBalanceBefore.Int64()) + + s.T().Log("make sure the sender has enough funds") + weiToSend := evm.NativeToWei(big.NewInt(1)) // 1 unibi + funds := sdk.NewCoins(sdk.NewInt64Coin(eth.EthBaseDenom, 5_000_000)) // 5 * 10^6 unibi + s.Require().NoError(testnetwork.FillWalletFromValidator( + s.fundedAccNibiAddr, funds, s.network.Validators[0], eth.EthBaseDenom), + ) + s.NoError(s.network.WaitForNextBlock()) + + senderBalanceBeforeWei, err := s.ethClient.BalanceAt( context.Background(), s.fundedAccEthAddr, nil, ) s.NoError(err) - recipientAddr := gethcommon.BytesToAddress(testnetwork.NewAccount(s.network, "recipient")) - recipientBalanceBefore, err := s.ethClient.BalanceAt(context.Background(), recipientAddr, nil) + + grpcUrl := s.network.Validators[0].AppConfig.GRPC.Address + grpcConn, err := gosdk.GetGRPCConnection(grpcUrl, true, 5) s.NoError(err) - s.Equal(int64(0), recipientBalanceBefore.Int64()) - amountToSend := big.NewInt(1000) + querier := bank.NewQueryClient(grpcConn) + resp, err := querier.Balance(context.Background(), &bank.QueryBalanceRequest{ + Address: s.fundedAccNibiAddr.String(), + Denom: eth.EthBaseDenom, + }) + s.Require().NoError(err) + s.Equal("105"+strings.Repeat("0", 6), resp.Balance.Amount.String()) + s.T().Logf("Sending %d wei to %s", weiToSend, recipientAddr.Hex()) signer := gethcore.LatestSignerForChainID(chainID) + gasPrice := big.NewInt(1) tx, err := gethcore.SignNewTx( s.fundedAccPrivateKey, signer, &gethcore.LegacyTx{ Nonce: nonce, To: &recipientAddr, - Value: amountToSend, + Value: weiToSend, Gas: params.TxGas, - GasPrice: big.NewInt(1), + GasPrice: gasPrice, }) s.NoError(err) err = s.ethClient.SendTransaction(context.Background(), tx) - s.NoError(err) + s.Require().NoError(err) s.NoError(s.network.WaitForNextBlock()) - senderAmountAfter, err := s.ethClient.BalanceAt(context.Background(), s.fundedAccEthAddr, nil) + senderAmountAfterWei, err := s.ethClient.BalanceAt(context.Background(), s.fundedAccEthAddr, nil) s.NoError(err) - expectedSenderBalance := senderBalanceBefore.Sub(senderBalanceBefore, amountToSend) - expectedSenderBalance = expectedSenderBalance.Sub(senderBalanceBefore, big.NewInt(int64(params.TxGas))) - - s.Equal(expectedSenderBalance.Int64(), senderAmountAfter.Int64()) + costOfTx := new(big.Int).Add( + weiToSend, + new(big.Int).Mul(evm.NativeToWei(new(big.Int).SetUint64(params.TxGas)), gasPrice), + ) + wantSenderBalWei := new(big.Int).Sub(senderBalanceBeforeWei, costOfTx) + s.Equal(wantSenderBalWei.String(), senderAmountAfterWei.String(), "surpising sender balance") recipientBalanceAfter, err := s.ethClient.BalanceAt(context.Background(), recipientAddr, nil) s.NoError(err) - s.Equal(amountToSend.Int64(), recipientBalanceAfter.Int64()) + s.Equal(weiToSend.String(), recipientBalanceAfter.String()) } // Test_SmartContract includes contract deployment, query, execution diff --git a/eth/safe_math_test.go b/eth/safe_math_test.go index a956251cf..655622ded 100644 --- a/eth/safe_math_test.go +++ b/eth/safe_math_test.go @@ -13,15 +13,15 @@ import ( const maxInt64 = 9223372036854775807 -type SuiteSafeMath struct { +type Suite struct { suite.Suite } -func TestTypesSuite(t *testing.T) { - suite.Run(t, new(SuiteSafeMath)) +func TestSuite_RunAll(t *testing.T) { + suite.Run(t, new(Suite)) } -func (s *SuiteSafeMath) TestSafeNewIntFromBigInt() { +func (s *Suite) TestSafeNewIntFromBigInt() { tests := []struct { name string input *big.Int @@ -57,7 +57,7 @@ func (s *SuiteSafeMath) TestSafeNewIntFromBigInt() { } } -func (s *SuiteSafeMath) TestIsValidInt256() { +func (s *Suite) TestIsValidInt256() { tests := []struct { name string input *big.Int @@ -88,7 +88,7 @@ func (s *SuiteSafeMath) TestIsValidInt256() { } } -func (s *SuiteSafeMath) TestSafeInt64() { +func (s *Suite) TestSafeInt64() { tests := []struct { name string input uint64 diff --git a/eth/state_encoder_test.go b/eth/state_encoder_test.go index 03eff2ac6..be1bf36e4 100644 --- a/eth/state_encoder_test.go +++ b/eth/state_encoder_test.go @@ -6,7 +6,6 @@ import ( "github.com/NibiruChain/collections" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" "github.com/NibiruChain/nibiru/eth" ) @@ -34,14 +33,6 @@ func assertBijectiveValue[T any](t *testing.T, encoder collections.ValueEncoder[ require.NotEmpty(t, encoder.Name()) } -type Suite struct { - suite.Suite -} - -func TestSuite_RunAll(t *testing.T) { - suite.Run(t, new(Suite)) -} - func (s *Suite) TestEncoderBytes() { testCases := []struct { name string diff --git a/proto/eth/evm/v1/query.proto b/proto/eth/evm/v1/query.proto index e1e7d0b4b..15cfbe125 100644 --- a/proto/eth/evm/v1/query.proto +++ b/proto/eth/evm/v1/query.proto @@ -92,8 +92,8 @@ message QueryEthAccountRequest { // QueryEthAccountResponse is the response type for the Query/Account RPC method. message QueryEthAccountResponse { - // balance is the balance of the EVM denomination. - string balance = 1; + // balance_wei is the balance of wei (attoether, where NIBI is ether). + string balance_wei = 1; // code_hash is the hex-formatted code bytes from the EOA. string code_hash = 2; // nonce is the account's sequence number. @@ -153,8 +153,10 @@ message QueryBalanceRequest { // QueryBalanceResponse is the response type for the Query/Balance RPC method. message QueryBalanceResponse { - // balance is the balance of the EVM denomination. + // balance is the balance of the EVM denomination string balance = 1; + // balance is the balance of the EVM denomination in units of wei. + string balance_wei = 2; } // QueryStorageRequest is the request type for the Query/Storage RPC method. diff --git a/x/evm/const.go b/x/evm/const.go index 360da5b49..1e41138d1 100644 --- a/x/evm/const.go +++ b/x/evm/const.go @@ -2,6 +2,9 @@ package evm import ( + "fmt" + "math/big" + "github.com/NibiruChain/collections" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" gethcommon "github.com/ethereum/go-ethereum/common" @@ -87,3 +90,63 @@ var ( zeroAddr gethcommon.Address evmModuleAddr gethcommon.Address ) + +// NativeToWei converts a "unibi" amount to "wei" units for the EVM. +// +// Micronibi, labeled "unibi", is the base denomination for NIBI. For NIBI to be +// considered "ether" by Ethereum clients, we need to follow the constraint +// equation: 1 NIBI = 10^18 wei. +// +// Since 1 NIBI = 10^6 micronibi = 10^6 unibi, the following is true: +// 10^0 unibi == 10^12 wei +func NativeToWei(evmDenomAmount *big.Int) (weiAmount *big.Int) { + pow10 := new(big.Int).Exp(big.NewInt(10), big.NewInt(12), nil) + return new(big.Int).Mul(evmDenomAmount, pow10) +} + +// WeiToNative converts a "wei" amount to "unibi" units. +// +// Micronibi, labeled "unibi", is the base denomination for NIBI. For NIBI to be +// considered "ether" by Ethereum clients, we need to follow the constraint +// equation: 1 NIBI = 10^18 wei. +// +// Since 1 NIBI = 10^6 micronibi = 10^6 unibi, the following is true: +// 10^0 unibi == 10^12 wei +func WeiToNative(weiAmount *big.Int) (evmDenomAmount *big.Int) { + pow10 := new(big.Int).Exp(big.NewInt(10), big.NewInt(12), nil) + return new(big.Int).Quo(weiAmount, pow10) +} + +// ParseWeiAsMultipleOfMicronibi truncates the given wei amount to the highest +// multiple of 1 micronibi (10^12 wei). It returns the truncated value and an +// error if the input value is too small. +// +// Args: +// - weiInt (*big.Int): The amount of wei to be parsed. +// +// Returns: +// - newWeiInt (*big.Int): The truncated amount of wei, which is a multiple of 1 micronibi. +// - err (error): An error indicating if the input value is within the range +// (1, 10^12) inclusive. +// +// Example: +// +// Input number: 123456789012345678901234567890 +// Parsed number: 123456789012 * 10^12 +func ParseWeiAsMultipleOfMicronibi(weiInt *big.Int) (newWeiInt *big.Int, err error) { + // if "weiValue" is nil, 0, or negative, early return + if weiInt == nil || !(weiInt.Cmp(big.NewInt(0)) > 0) { + return weiInt, nil + } + + // err if weiInt is too small + tenPow12 := new(big.Int).Exp(big.NewInt(10), big.NewInt(12), nil) + if weiInt.Cmp(tenPow12) < 0 { + return weiInt, fmt.Errorf( + "wei amount is too small (%s), cannot transfer less than 1 micronibi. 10^18 wei == 1 NIBI == 10^6 micronibi", weiInt) + } + + // truncate to highest micronibi amount + newWeiInt = NativeToWei(WeiToNative(weiInt)) + return newWeiInt, nil +} diff --git a/x/evm/evm_test.go b/x/evm/evm_test.go index d73be4962..0332df5a3 100644 --- a/x/evm/evm_test.go +++ b/x/evm/evm_test.go @@ -2,7 +2,9 @@ package evm_test import ( + "math/big" "strconv" + "strings" "testing" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -140,3 +142,66 @@ func (s *TestSuite) TestModuleAddressEVM() { s.Equal(nibiAddr.String(), resp.Address) } } + +func (s *TestSuite) TestWeiConversion() { + { + unibiAmt := big.NewInt(420) + s.Equal( + unibiAmt, + evm.WeiToNative(evm.NativeToWei(unibiAmt)), + "native -> wei -> native should be an identity operation", + ) + + weiAmt := evm.NativeToWei(unibiAmt) + want := "420" + strings.Repeat("0", 12) + s.Equal(weiAmt.String(), want) + } + + tenPow12 := new(big.Int).Exp(big.NewInt(10), big.NewInt(12), nil) + for _, tc := range []struct { + weiAmtIn string + want *big.Int + wantError string + }{ + { + // Input number: 123456789012345678901234567890 + // Parsed number: 123456789012345678 * 10^12 + weiAmtIn: "123456789012345678901234567890", + want: evm.NativeToWei(big.NewInt(123456789012345678)), + wantError: "", + }, + { + weiAmtIn: "123456789012345678901234567890", + want: evm.NativeToWei(big.NewInt(123456789012345678)), + wantError: "", + }, + { + weiAmtIn: "0", + want: big.NewInt(0), + wantError: "", + }, + { + weiAmtIn: "1", + wantError: "cannot transfer less than 1 micronibi.", + }, + { + weiAmtIn: new(big.Int).Sub( + tenPow12, big.NewInt(1), + ).String(), + wantError: "cannot transfer less than 1 micronibi.", + }, + { + weiAmtIn: "500", + wantError: "cannot transfer less than 1 micronibi.", + }, + } { + weiAmtIn, _ := new(big.Int).SetString(tc.weiAmtIn, 10) + got, err := evm.ParseWeiAsMultipleOfMicronibi(weiAmtIn) + if tc.wantError != "" { + s.Require().ErrorContains(err, tc.wantError) + return + } + s.NoError(err) + s.Require().Equal(tc.want.String(), got.String()) + } +} diff --git a/x/evm/evmtest/eth.go b/x/evm/evmtest/eth.go index e9f9717c5..6a069b429 100644 --- a/x/evm/evmtest/eth.go +++ b/x/evm/evmtest/eth.go @@ -32,7 +32,7 @@ func NewEthAccInfo() EthPrivKeyAcc { ethAddr := crypto.PubkeyToAddress(privKeyE.PublicKey) return EthPrivKeyAcc{ EthAddr: ethAddr, - NibiruAddr: EthAddrToNibiruAddr(ethAddr), + NibiruAddr: eth.EthAddrToNibiruAddr(ethAddr), PrivKey: privkey, PrivKeyE: privKeyE, KeyringSigner: NewSigner(privkey), diff --git a/x/evm/evmtest/test_deps.go b/x/evm/evmtest/test_deps.go index adf82fd0c..1ad239c73 100644 --- a/x/evm/evmtest/test_deps.go +++ b/x/evm/evmtest/test_deps.go @@ -49,7 +49,7 @@ func NewTestDeps() TestDeps { } } -func (deps *TestDeps) StateDB() *statedb.StateDB { +func (deps TestDeps) StateDB() *statedb.StateDB { return statedb.New(deps.Ctx, &deps.Chain.EvmKeeper, statedb.NewEmptyTxConfig( gethcommon.BytesToHash(deps.Ctx.HeaderHash().Bytes()), diff --git a/x/evm/evmtest/tx.go b/x/evm/evmtest/tx.go index 5d09c4d5e..0256d17b3 100644 --- a/x/evm/evmtest/tx.go +++ b/x/evm/evmtest/tx.go @@ -241,3 +241,32 @@ func GenerateAndSignEthTxMsg( keyringSigner := deps.Sender.KeyringSigner return txMsg, txMsg.Sign(gethSigner, keyringSigner) } + +func TransferWei( + deps *TestDeps, + to gethcommon.Address, + amountWei *big.Int, +) error { + ethAcc := deps.Sender + var innerTxData []byte = nil + var accessList gethcore.AccessList = nil + ethTxMsg, err := NewEthTxMsgFromTxData( + deps, + gethcore.LegacyTxType, + innerTxData, + deps.StateDB().GetNonce(ethAcc.EthAddr), + &to, + amountWei, + gethparams.TxGas, + accessList, + ) + if err != nil { + return fmt.Errorf("error while transferring wei: %w", err) + } + + _, err = deps.Chain.EvmKeeper.EthereumTx(deps.GoCtx(), ethTxMsg) + if err != nil { + return fmt.Errorf("error while transferring wei: %w", err) + } + return err +} diff --git a/x/evm/keeper/gas_fees.go b/x/evm/keeper/gas_fees.go index 5ab0ade35..81dc94f8d 100644 --- a/x/evm/keeper/gas_fees.go +++ b/x/evm/keeper/gas_fees.go @@ -82,7 +82,7 @@ func GasToRefund(availableRefund, gasConsumed, refundQuotient uint64) uint64 { // CheckSenderBalance validates that the tx cost value is positive and that the // sender has enough funds to pay for the fees and value of the transaction. func CheckSenderBalance( - balance sdkmath.Int, + balanceWei *big.Int, txData evm.TxData, ) error { cost := txData.Cost() @@ -94,10 +94,10 @@ func CheckSenderBalance( ) } - if balance.IsNegative() || balance.BigInt().Cmp(cost) < 0 { + if balanceWei.Cmp(big.NewInt(0)) < 0 || balanceWei.Cmp(cost) < 0 { return errors.Wrapf( errortypes.ErrInsufficientFunds, - "sender balance < tx cost (%s < %s)", balance, txData.Cost(), + "sender balance < tx cost (%s < %s)", balanceWei, txData.Cost(), ) } return nil diff --git a/x/evm/keeper/grpc_query.go b/x/evm/keeper/grpc_query.go index e4dc7e78b..cd6127e12 100644 --- a/x/evm/keeper/grpc_query.go +++ b/x/evm/keeper/grpc_query.go @@ -58,9 +58,9 @@ func (k Keeper) EthAccount( acct := k.GetAccountOrEmpty(ctx, addr) return &evm.QueryEthAccountResponse{ - Balance: acct.Balance.String(), - CodeHash: gethcommon.BytesToHash(acct.CodeHash).Hex(), - Nonce: acct.Nonce, + BalanceWei: evm.NativeToWei(acct.BalanceNative).String(), + CodeHash: gethcommon.BytesToHash(acct.CodeHash).Hex(), + Nonce: acct.Nonce, }, nil } @@ -139,8 +139,8 @@ func (k Keeper) ValidatorAccount( } // Balance: Implements the gRPC query for "/eth.evm.v1.Query/Balance". -// Balance retrieves the balance of an Ethereum address in "Ether", which -// actually refers to NIBI tokens on Nibiru EVM. +// Balance retrieves the balance of an Ethereum address in "wei", the smallest +// unit of "Ether". Ether refers to NIBI tokens on Nibiru EVM. // // Parameters: // - goCtx: The context.Context object representing the request context. @@ -156,7 +156,8 @@ func (k Keeper) Balance(goCtx context.Context, req *evm.QueryBalanceRequest) (*e ctx := sdk.UnwrapSDKContext(goCtx) balanceInt := k.GetEvmGasBalance(ctx, gethcommon.HexToAddress(req.Address)) return &evm.QueryBalanceResponse{ - Balance: balanceInt.String(), + Balance: balanceInt.String(), + BalanceWei: evm.NativeToWei(balanceInt).String(), }, nil } @@ -444,7 +445,7 @@ func (k Keeper) EstimateGasForEvmCallType( if errors.Is(err, core.ErrIntrinsicGas) { return true, nil, nil // Special case, raise gas limit } - return true, nil, err // Bail out + return true, nil, fmt.Errorf("error applying EVM message to StateDB: %w", err) // Bail out } return len(rsp.VmError) > 0, rsp, nil } @@ -459,15 +460,15 @@ func (k Keeper) EstimateGasForEvmCallType( if hi == gasCap { failed, result, err := executable(hi) if err != nil { - return nil, err + return nil, fmt.Errorf("eth call exec error: %w", err) } if failed { if result != nil && result.VmError != vm.ErrOutOfGas.Error() { if result.VmError == vm.ErrExecutionReverted.Error() { - return nil, evm.NewExecErrorWithReason(result.Ret) + return nil, fmt.Errorf("VMError: %w", evm.NewExecErrorWithReason(result.Ret)) } - return nil, errors.New(result.VmError) + return nil, fmt.Errorf("VMError: %s", result.VmError) } // Otherwise, the specified gas cap is too low return nil, fmt.Errorf("gas required exceeds allowance (%d)", gasCap) diff --git a/x/evm/keeper/grpc_query_test.go b/x/evm/keeper/grpc_query_test.go index 1758eefc1..c2ad4bb62 100644 --- a/x/evm/keeper/grpc_query_test.go +++ b/x/evm/keeper/grpc_query_test.go @@ -29,7 +29,8 @@ type TestCase[In, Out any] struct { req In, wantResp Out, ) - wantErr string + onTestEnd func(deps *evmtest.TestDeps) + wantErr string } func InvalidEthAddr() string { return "0x0000" } @@ -149,9 +150,9 @@ func (s *Suite) TestQueryEthAccount() { Address: InvalidEthAddr(), } wantResp = &evm.QueryEthAccountResponse{ - Balance: "0", - CodeHash: gethcommon.BytesToHash(evm.EmptyCodeHash).Hex(), - Nonce: 0, + BalanceWei: "0", + CodeHash: gethcommon.BytesToHash(evm.EmptyCodeHash).Hex(), + Nonce: 0, } return req, wantResp }, @@ -176,9 +177,9 @@ func (s *Suite) TestQueryEthAccount() { Address: deps.Sender.EthAddr.Hex(), } wantResp = &evm.QueryEthAccountResponse{ - Balance: "420", - CodeHash: gethcommon.BytesToHash(evm.EmptyCodeHash).Hex(), - Nonce: 0, + BalanceWei: "420" + strings.Repeat("0", 12), + CodeHash: gethcommon.BytesToHash(evm.EmptyCodeHash).Hex(), + Nonce: 0, } return req, wantResp }, @@ -543,7 +544,7 @@ func (s *Suite) TestQueryBalance() { Address: InvalidEthAddr(), } wantResp = &evm.QueryBalanceResponse{ - Balance: "0", + BalanceWei: "0", } return req, wantResp }, @@ -556,7 +557,8 @@ func (s *Suite) TestQueryBalance() { Address: evmtest.NewEthAccInfo().EthAddr.String(), } wantResp = &evm.QueryBalanceResponse{ - Balance: "0", + Balance: "0", + BalanceWei: "0", } return req, wantResp }, @@ -581,7 +583,8 @@ func (s *Suite) TestQueryBalance() { Address: deps.Sender.EthAddr.Hex(), } wantResp = &evm.QueryBalanceResponse{ - Balance: "420", + Balance: "420", + BalanceWei: "420" + strings.Repeat("0", 12), } return req, wantResp }, @@ -681,7 +684,8 @@ func (s *Suite) TestEstimateGasForEvmCallType() { }, { name: "happy: estimate gas for transfer", - setup: func(deps *evmtest.TestDeps) { + scenario: func(deps *evmtest.TestDeps) (req In, wantResp Out) { + // fund the account chain := deps.Chain ethAddr := deps.Sender.EthAddr coins := sdk.Coins{sdk.NewInt64Coin(evm.DefaultEVMDenom, 1000)} @@ -690,10 +694,17 @@ func (s *Suite) TestEstimateGasForEvmCallType() { err = chain.BankKeeper.SendCoinsFromModuleToAccount( deps.Ctx, evm.ModuleName, ethAddr.Bytes(), coins) s.Require().NoError(err) - }, - scenario: func(deps *evmtest.TestDeps) (req In, wantResp Out) { + + // assert balance of 1000 * 10^12 wei + resp, _ := deps.Chain.EvmKeeper.Balance(deps.GoCtx(), &evm.QueryBalanceRequest{ + Address: deps.Sender.EthAddr.Hex(), + }) + s.Equal("1000", resp.Balance) + s.Require().Equal("1000"+strings.Repeat("0", 12), resp.BalanceWei) + + // Send Eth call to transfer from the account - 5 * 10^12 wei recipient := evmtest.NewEthAccInfo().EthAddr - amountToSend := hexutil.Big(*big.NewInt(10)) + amountToSend := hexutil.Big(*evm.NativeToWei(big.NewInt(5))) gasLimitArg := hexutil.Uint64(100000) jsonTxArgs, err := json.Marshal(&evm.JsonTxArgs{ @@ -713,12 +724,14 @@ func (s *Suite) TestEstimateGasForEvmCallType() { return req, wantResp }, wantErr: "", + onTestEnd: func(deps *evmtest.TestDeps) { + }, }, { name: "sad: insufficient balance for transfer", scenario: func(deps *evmtest.TestDeps) (req In, wantResp Out) { recipient := evmtest.NewEthAccInfo().EthAddr - amountToSend := hexutil.Big(*big.NewInt(10)) + amountToSend := hexutil.Big(*evm.NativeToWei(big.NewInt(10))) jsonTxArgs, err := json.Marshal(&evm.JsonTxArgs{ From: &deps.Sender.EthAddr, @@ -752,6 +765,10 @@ func (s *Suite) TestEstimateGasForEvmCallType() { } s.Assert().NoError(err) s.EqualValues(wantResp, gotResp) + + if tc.onTestEnd != nil { + tc.onTestEnd(&deps) + } }) } } @@ -781,9 +798,8 @@ func (s *Suite) TestTraceTx() { wantErr: "", }, { - "happy: trace erc-20 transfer tx", - nil, - func(deps *evmtest.TestDeps) (req In, wantResp Out) { + name: "happy: trace erc-20 transfer tx", + scenario: func(deps *evmtest.TestDeps) (req In, wantResp Out) { txMsg, predecessors := evmtest.DeployAndExecuteERC20Transfer(deps, s.T()) req = &evm.QueryTraceTxRequest{ @@ -793,7 +809,7 @@ func (s *Suite) TestTraceTx() { wantResp = TraceERC20Transfer() return req, wantResp }, - "", + wantErr: "", }, } diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index eaedb8ec0..debdddfb5 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -82,8 +82,8 @@ func NewKeeper( // GetEvmGasBalance: Implements `evm.EVMKeeper` from // "github.com/NibiruChain/nibiru/app/ante/evm": Load account's balance of gas -// tokens for EVM execution -func (k *Keeper) GetEvmGasBalance(ctx sdk.Context, addr gethcommon.Address) *big.Int { +// tokens for EVM execution in EVM denom units. +func (k *Keeper) GetEvmGasBalance(ctx sdk.Context, addr gethcommon.Address) (balance *big.Int) { nibiruAddr := sdk.AccAddress(addr.Bytes()) evmParams := k.GetParams(ctx) evmDenom := evmParams.GetEvmDenom() @@ -91,8 +91,7 @@ func (k *Keeper) GetEvmGasBalance(ctx sdk.Context, addr gethcommon.Address) *big if evmDenom == "" { return big.NewInt(-1) } - coin := k.bankKeeper.GetBalance(ctx, nibiruAddr, evmDenom) - return coin.Amount.BigInt() + return k.bankKeeper.GetBalance(ctx, nibiruAddr, evmDenom).Amount.BigInt() } func (k Keeper) EthChainID(ctx sdk.Context) *big.Int { diff --git a/x/evm/keeper/keeper_test.go b/x/evm/keeper/keeper_test.go index 1c81893b3..0ddc2de45 100644 --- a/x/evm/keeper/keeper_test.go +++ b/x/evm/keeper/keeper_test.go @@ -10,8 +10,8 @@ type Suite struct { suite.Suite } -// TestKeeperSuite: Runs all the tests in the suite. -func TestKeeperSuite(t *testing.T) { +// TestSuite: Runs all the tests in the suite. +func TestSuite(t *testing.T) { s := new(Suite) suite.Run(t, s) } diff --git a/x/evm/keeper/msg_ethereum_tx_test.go b/x/evm/keeper/msg_ethereum_tx_test.go index 389002f6f..2397b08be 100644 --- a/x/evm/keeper/msg_ethereum_tx_test.go +++ b/x/evm/keeper/msg_ethereum_tx_test.go @@ -188,12 +188,12 @@ func (s *Suite) TestMsgEthereumTx_SimpleTransfer() { deps := evmtest.NewTestDeps() ethAcc := deps.Sender - amount := int64(123) + fundedAmount := evm.NativeToWei(big.NewInt(123)).Int64() err := testapp.FundAccount( deps.Chain.BankKeeper, deps.Ctx, deps.Sender.NibiruAddr, - sdk.NewCoins(sdk.NewInt64Coin("unibi", amount)), + sdk.NewCoins(sdk.NewInt64Coin("unibi", fundedAmount)), ) s.Require().NoError(err) @@ -208,7 +208,7 @@ func (s *Suite) TestMsgEthereumTx_SimpleTransfer() { innerTxData, deps.StateDB().GetNonce(ethAcc.EthAddr), &to, - big.NewInt(amount), + big.NewInt(fundedAmount), gethparams.TxGas, accessList, ) @@ -229,7 +229,7 @@ func (s *Suite) TestMsgEthereumTx_SimpleTransfer() { &evm.EventTransfer{ Sender: ethAcc.EthAddr.String(), Recipient: to.String(), - Amount: strconv.FormatInt(amount, 10), + Amount: strconv.FormatInt(fundedAmount, 10), }, ) } diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go index 24f5dbe2b..5e4efe9dd 100644 --- a/x/evm/keeper/msg_server.go +++ b/x/evm/keeper/msg_server.go @@ -417,19 +417,31 @@ func (k *Keeper) ApplyEvmMsg(ctx sdk.Context, } leftoverGas -= intrinsicGas - // access list preparation is moved from ante handler to here, because it's needed when `ApplyMessage` is called - // under contexts where ante handlers are not run, for example `eth_call` and `eth_estimateGas`. - stateDB.PrepareAccessList(msg.From(), msg.To(), evmObj.ActivePrecompiles(params.Rules{}), msg.AccessList()) + // access list preparation is moved from ante handler to here, because it's + // needed when `ApplyMessage` is called under contexts where ante handlers + // are not run, for example `eth_call` and `eth_estimateGas`. + stateDB.PrepareAccessList( + msg.From(), + msg.To(), + evmObj.ActivePrecompiles(params.Rules{}), + msg.AccessList(), + ) + + msgWei, err := ParseWeiAsMultipleOfMicronibi(msg.Value()) + if err != nil { + return nil, err + // return nil, fmt.Errorf("cannot use \"value\" in wei that can't be converted to unibi. %s is not divisible by 10^12", msg.Value()) + } if contractCreation { // take over the nonce management from evm: // - reset sender's nonce to msg.Nonce() before calling evm. // - increase sender's nonce by one no matter the result. stateDB.SetNonce(sender.Address(), msg.Nonce()) - ret, _, leftoverGas, vmErr = evmObj.Create(sender, msg.Data(), leftoverGas, msg.Value()) + ret, _, leftoverGas, vmErr = evmObj.Create(sender, msg.Data(), leftoverGas, msgWei) stateDB.SetNonce(sender.Address(), msg.Nonce()+1) } else { - ret, leftoverGas, vmErr = evmObj.Call(sender, *msg.To(), msg.Data(), leftoverGas, msg.Value()) + ret, leftoverGas, vmErr = evmObj.Call(sender, *msg.To(), msg.Data(), leftoverGas, msgWei) } // After EIP-3529: refunds are capped to gasUsed / 5 @@ -456,7 +468,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, errors.Wrap(err, "failed to commit stateDB") + return nil, fmt.Errorf("failed to commit stateDB: %w", err) } } @@ -485,6 +497,24 @@ func (k *Keeper) ApplyEvmMsg(ctx sdk.Context, }, nil } +func ParseWeiAsMultipleOfMicronibi(weiInt *big.Int) (newWeiInt *big.Int, err error) { + // if "weiValue" is nil, 0, or negative, early return + if weiInt == nil || !(weiInt.Cmp(big.NewInt(0)) > 0) { + return weiInt, nil + } + + // err if weiInt is too small + tenPow12 := new(big.Int).Exp(big.NewInt(10), big.NewInt(12), nil) + if weiInt.Cmp(tenPow12) < 0 { + return weiInt, fmt.Errorf( + "wei amount is too small (%s), cannot transfer less than 1 micronibi. 10^18 wei == 1 NIBI == 10^6 micronibi", weiInt) + } + + // truncate to highest micronibi amount + newWeiInt = evm.NativeToWei(evm.WeiToNative(weiInt)) + return newWeiInt, nil +} + // CreateFunToken is a gRPC transaction message for creating fungible token // ("FunToken") a mapping between a bank coin and ERC20 token. // diff --git a/x/evm/keeper/statedb.go b/x/evm/keeper/statedb.go index 3ce4ba434..4f718be5f 100644 --- a/x/evm/keeper/statedb.go +++ b/x/evm/keeper/statedb.go @@ -30,7 +30,7 @@ func (k *Keeper) GetAccount(ctx sdk.Context, addr gethcommon.Address) *statedb.A return nil } - acct.Balance = k.GetEvmGasBalance(ctx, addr) + acct.BalanceNative = k.GetEvmGasBalance(ctx, addr) return acct } @@ -67,13 +67,13 @@ func (k *Keeper) ForEachStorage( // SetAccBalance update account's balance, compare with current balance first, then decide to mint or burn. func (k *Keeper) SetAccBalance( - ctx sdk.Context, addr gethcommon.Address, amount *big.Int, + ctx sdk.Context, addr gethcommon.Address, amountEvmDenom *big.Int, ) error { nativeAddr := sdk.AccAddress(addr.Bytes()) params := k.GetParams(ctx) - coin := k.bankKeeper.GetBalance(ctx, nativeAddr, params.EvmDenom) - balance := coin.Amount.BigInt() - delta := new(big.Int).Sub(amount, balance) + balance := k.bankKeeper.GetBalance(ctx, nativeAddr, params.EvmDenom).Amount.BigInt() + delta := new(big.Int).Sub(amountEvmDenom, balance) + switch delta.Sign() { case 1: // mint @@ -126,7 +126,7 @@ func (k *Keeper) SetAccount( k.accountKeeper.SetAccount(ctx, acct) - if err := k.SetAccBalance(ctx, addr, account.Balance); err != nil { + if err := k.SetAccBalance(ctx, addr, account.BalanceNative); err != nil { return err } @@ -213,7 +213,7 @@ func (k *Keeper) GetAccountOrEmpty( // empty account return statedb.Account{ - Balance: new(big.Int), - CodeHash: evm.EmptyCodeHash, + BalanceNative: new(big.Int), + CodeHash: evm.EmptyCodeHash, } } diff --git a/x/evm/keeper/statedb_test.go b/x/evm/keeper/statedb_test.go new file mode 100644 index 000000000..903dee7d1 --- /dev/null +++ b/x/evm/keeper/statedb_test.go @@ -0,0 +1,93 @@ +// Copyright (c) 2023-2024 Nibi, Inc. +package keeper_test + +import ( + "math/big" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/NibiruChain/nibiru/eth" + "github.com/NibiruChain/nibiru/x/common/testutil/testapp" + "github.com/NibiruChain/nibiru/x/evm" + "github.com/NibiruChain/nibiru/x/evm/evmtest" + + gethcommon "github.com/ethereum/go-ethereum/common" +) + +// TestStateDBBalance tests the behavior of the StateDB with regards to account +// balances, ensuring correct conversion between native tokens (unibi) and EVM +// tokens (wei), as well as proper balance updates during transfers. +func (s *Suite) TestStateDBBalance() { + deps := evmtest.NewTestDeps() + { + db := deps.StateDB() + s.Equal("0", db.GetBalance(deps.Sender.EthAddr).String()) + + s.T().Log("fund account in unibi. See expected wei amount.") + err := testapp.FundAccount( + deps.Chain.BankKeeper, + deps.Ctx, + deps.Sender.NibiruAddr, + sdk.NewCoins(sdk.NewInt64Coin(evm.DefaultEVMDenom, 42)), + ) + s.NoError(err) + s.Equal( + "42"+strings.Repeat("0", 12), + db.GetBalance(deps.Sender.EthAddr).String(), + ) + s.Equal( + "42", + deps.Chain.BankKeeper.GetBalance(deps.Ctx, deps.Sender.NibiruAddr, evm.DefaultEVMDenom).Amount.String(), + ) + } + + s.T().Log("Send via EVM transfer. See expected wei amounts.") + to := gethcommon.HexToAddress("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") + { + err := evmtest.TransferWei(&deps, to, evm.NativeToWei(big.NewInt(12))) + s.Require().NoError(err) + db := deps.StateDB() + s.Equal( + "30"+strings.Repeat("0", 12), + db.GetBalance(deps.Sender.EthAddr).String(), + ) + s.Equal( + "12"+strings.Repeat("0", 12), + db.GetBalance(to).String(), + ) + + s.T().Log("Send via EVM transfer with too little wei. Should error") + err = evmtest.TransferWei(&deps, to, big.NewInt(12)) + s.Require().ErrorContains(err, "wei amount is too small") + } + + s.T().Log("Send via bank transfer from account to account. See expected wei amounts.") + { + deps := evmtest.NewTestDeps() + toNibiAddr := eth.EthAddrToNibiruAddr(to) + err := testapp.FundAccount( + deps.Chain.BankKeeper, + deps.Ctx, + deps.Sender.NibiruAddr, + sdk.NewCoins(sdk.NewInt64Coin(evm.DefaultEVMDenom, 8)), + ) + s.NoError(err) + err = deps.Chain.BankKeeper.SendCoins( + deps.Ctx, deps.Sender.NibiruAddr, + toNibiAddr, + sdk.NewCoins(sdk.NewInt64Coin(evm.DefaultEVMDenom, 3)), + ) + s.NoError(err) + + db := deps.StateDB() + s.Equal( + "3"+strings.Repeat("0", 12), + db.GetBalance(to).String(), + ) + s.Equal( + "5"+strings.Repeat("0", 12), + db.GetBalance(deps.Sender.EthAddr).String(), + ) + } +} diff --git a/x/evm/query.pb.go b/x/evm/query.pb.go index 3463410a1..609bc55fe 100644 --- a/x/evm/query.pb.go +++ b/x/evm/query.pb.go @@ -77,8 +77,8 @@ var xxx_messageInfo_QueryEthAccountRequest proto.InternalMessageInfo // QueryEthAccountResponse is the response type for the Query/Account RPC method. type QueryEthAccountResponse struct { - // balance is the balance of the EVM denomination. - Balance string `protobuf:"bytes,1,opt,name=balance,proto3" json:"balance,omitempty"` + // balance_wei is the balance of wei (attoether, where NIBI is ether). + BalanceWei string `protobuf:"bytes,1,opt,name=balance_wei,json=balanceWei,proto3" json:"balance_wei,omitempty"` // code_hash is the hex-formatted code bytes from the EOA. CodeHash string `protobuf:"bytes,2,opt,name=code_hash,json=codeHash,proto3" json:"code_hash,omitempty"` // nonce is the account's sequence number. @@ -118,9 +118,9 @@ func (m *QueryEthAccountResponse) XXX_DiscardUnknown() { var xxx_messageInfo_QueryEthAccountResponse proto.InternalMessageInfo -func (m *QueryEthAccountResponse) GetBalance() string { +func (m *QueryEthAccountResponse) GetBalanceWei() string { if m != nil { - return m.Balance + return m.BalanceWei } return "" } @@ -390,8 +390,10 @@ var xxx_messageInfo_QueryBalanceRequest proto.InternalMessageInfo // QueryBalanceResponse is the response type for the Query/Balance RPC method. type QueryBalanceResponse struct { - // balance is the balance of the EVM denomination. + // balance is the balance of the EVM denomination Balance string `protobuf:"bytes,1,opt,name=balance,proto3" json:"balance,omitempty"` + // balance is the balance of the EVM denomination in units of wei. + BalanceWei string `protobuf:"bytes,2,opt,name=balance_wei,json=balanceWei,proto3" json:"balance_wei,omitempty"` } func (m *QueryBalanceResponse) Reset() { *m = QueryBalanceResponse{} } @@ -434,6 +436,13 @@ func (m *QueryBalanceResponse) GetBalance() string { return "" } +func (m *QueryBalanceResponse) GetBalanceWei() string { + if m != nil { + return m.BalanceWei + } + return "" +} + // QueryStorageRequest is the request type for the Query/Storage RPC method. type QueryStorageRequest struct { // address is the ethereum hex address to query the storage state for. @@ -1411,104 +1420,106 @@ func init() { func init() { proto.RegisterFile("eth/evm/v1/query.proto", fileDescriptor_ffa36cdc5add14ed) } var fileDescriptor_ffa36cdc5add14ed = []byte{ - // 1550 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x56, 0x4f, 0x6f, 0x1b, 0x45, - 0x14, 0xcf, 0xc6, 0x4e, 0xec, 0x3c, 0xa7, 0x4d, 0x98, 0xa6, 0x4d, 0xb2, 0x4d, 0xec, 0x64, 0x43, - 0x93, 0xb4, 0xb4, 0xbb, 0x4d, 0x40, 0x20, 0x2a, 0x2a, 0xd4, 0x44, 0x69, 0x28, 0xfd, 0xa3, 0x62, - 0x22, 0x0e, 0x20, 0x64, 0x8d, 0xed, 0xc9, 0x7a, 0x15, 0x7b, 0xc7, 0xdd, 0x19, 0x07, 0x87, 0x92, - 0x0b, 0xbd, 0x20, 0x21, 0x44, 0x25, 0xbe, 0x40, 0x4f, 0x7c, 0x05, 0xbe, 0x42, 0x8f, 0x95, 0x10, - 0x12, 0xe2, 0x50, 0x50, 0xcb, 0x81, 0x33, 0x47, 0x4e, 0x68, 0x66, 0x67, 0xbc, 0x6b, 0x7b, 0x9d, - 0x50, 0xfe, 0xdc, 0x38, 0xed, 0xce, 0xcc, 0x7b, 0xef, 0xf7, 0x9b, 0x99, 0x37, 0xef, 0xfd, 0xe0, - 0x0c, 0xe1, 0x35, 0x87, 0xec, 0x37, 0x9c, 0xfd, 0x35, 0xe7, 0x5e, 0x8b, 0x04, 0x07, 0x76, 0x33, - 0xa0, 0x9c, 0x22, 0x20, 0xbc, 0x66, 0x93, 0xfd, 0x86, 0xbd, 0xbf, 0x66, 0x5e, 0xa8, 0x50, 0xd6, - 0xa0, 0xcc, 0x29, 0x63, 0x46, 0x42, 0x23, 0x67, 0x7f, 0xad, 0x4c, 0x38, 0x5e, 0x73, 0x9a, 0xd8, - 0xf5, 0x7c, 0xcc, 0x3d, 0xea, 0x87, 0x7e, 0xe6, 0x54, 0x2c, 0x9e, 0x70, 0x0f, 0x67, 0x4f, 0xc5, - 0x66, 0x79, 0x5b, 0x9b, 0xba, 0xd4, 0xa5, 0xf2, 0xd7, 0x11, 0x7f, 0x6a, 0x76, 0xce, 0xa5, 0xd4, - 0xad, 0x13, 0x07, 0x37, 0x3d, 0x07, 0xfb, 0x3e, 0xe5, 0x32, 0x3a, 0x53, 0xab, 0x05, 0xb5, 0x2a, - 0x47, 0xe5, 0xd6, 0xae, 0xc3, 0xbd, 0x06, 0x61, 0x1c, 0x37, 0x9a, 0xa1, 0x81, 0xf5, 0x16, 0x9c, - 0x79, 0x4f, 0x30, 0xdc, 0xe2, 0xb5, 0x6b, 0x95, 0x0a, 0x6d, 0xf9, 0xbc, 0x48, 0xee, 0xb5, 0x08, - 0xe3, 0x68, 0x06, 0x32, 0xb8, 0x5a, 0x0d, 0x08, 0x63, 0x33, 0xc6, 0x82, 0xb1, 0x3a, 0x56, 0xd4, - 0xc3, 0x2b, 0xd9, 0x2f, 0x1e, 0x15, 0x86, 0x7e, 0x7b, 0x54, 0x18, 0xb2, 0x76, 0x61, 0xba, 0xcf, - 0x9b, 0x35, 0xa9, 0xcf, 0x88, 0x70, 0x2f, 0xe3, 0x3a, 0xf6, 0x2b, 0x44, 0xbb, 0xab, 0x21, 0x3a, - 0x0b, 0x63, 0x15, 0x5a, 0x25, 0xa5, 0x1a, 0x66, 0xb5, 0x99, 0x61, 0xb9, 0x96, 0x15, 0x13, 0xef, - 0x60, 0x56, 0x43, 0x53, 0x30, 0xe2, 0x53, 0xe1, 0x94, 0x5a, 0x30, 0x56, 0xd3, 0xc5, 0x70, 0x60, - 0xbd, 0x0d, 0xb3, 0x12, 0xe7, 0x8e, 0x57, 0xf6, 0x82, 0xd6, 0xdf, 0x20, 0x7a, 0x00, 0x66, 0x52, - 0x80, 0x88, 0x6b, 0x72, 0x04, 0x64, 0x42, 0x96, 0x09, 0x18, 0xc1, 0x68, 0x58, 0x32, 0xea, 0x8c, - 0xd1, 0x39, 0x38, 0x89, 0xc3, 0x40, 0x25, 0xbf, 0xd5, 0x28, 0x93, 0x40, 0x71, 0x3e, 0xa1, 0x66, - 0xef, 0xc8, 0x49, 0xeb, 0x26, 0xcc, 0x49, 0xe8, 0x0f, 0x70, 0xdd, 0xab, 0x62, 0x4e, 0x83, 0x1e, - 0xfa, 0x8b, 0x30, 0x5e, 0xa1, 0x3e, 0x2b, 0x75, 0x33, 0xc8, 0x89, 0xb9, 0x6b, 0x7d, 0xfb, 0xf8, - 0xd2, 0x80, 0xf9, 0x01, 0xd1, 0xd4, 0x5e, 0x56, 0x60, 0x42, 0xb3, 0xea, 0x8e, 0xa8, 0xc9, 0x5e, - 0xfb, 0xf7, 0xb6, 0xf6, 0x26, 0x9c, 0x92, 0x64, 0x36, 0xc2, 0x9b, 0x7d, 0x91, 0x0b, 0xb9, 0x0c, - 0x53, 0xdd, 0xae, 0xc7, 0xa5, 0x8d, 0x75, 0x53, 0x81, 0xbd, 0xcf, 0x69, 0x80, 0xdd, 0xe3, 0xc1, - 0xd0, 0x24, 0xa4, 0xf6, 0xc8, 0x81, 0xca, 0x30, 0xf1, 0x1b, 0x83, 0xbf, 0xa8, 0xe0, 0x3b, 0xc1, - 0x14, 0xfc, 0x14, 0x8c, 0xec, 0xe3, 0x7a, 0x4b, 0x83, 0x87, 0x03, 0xeb, 0x75, 0x98, 0x94, 0xd6, - 0x9b, 0xb4, 0xfa, 0x42, 0x9b, 0x5c, 0x81, 0x97, 0x62, 0x7e, 0x0a, 0x02, 0x41, 0x5a, 0x64, 0xbb, - 0xf4, 0x1a, 0x2f, 0xca, 0x7f, 0xeb, 0x53, 0x40, 0xd2, 0x70, 0xa7, 0x7d, 0x8b, 0xba, 0x4c, 0x43, - 0x20, 0x48, 0xcb, 0x37, 0x12, 0xc6, 0x97, 0xff, 0xe8, 0x3a, 0x40, 0x54, 0x43, 0xe4, 0xde, 0x72, - 0xeb, 0xcb, 0x76, 0x58, 0x70, 0x6c, 0x51, 0x70, 0xec, 0xb0, 0x2a, 0xa9, 0x82, 0x63, 0xdf, 0x8d, - 0x8e, 0xaa, 0x18, 0xf3, 0x8c, 0x91, 0x7c, 0x60, 0xa8, 0x83, 0xd5, 0xe0, 0x8a, 0xe7, 0x12, 0xa4, - 0xeb, 0xd4, 0x15, 0xbb, 0x4b, 0xad, 0xe6, 0xd6, 0x27, 0xec, 0xa8, 0xc0, 0xd9, 0xb7, 0xa8, 0x5b, - 0x94, 0x8b, 0x68, 0x3b, 0x81, 0xce, 0xca, 0xb1, 0x74, 0x42, 0x84, 0x38, 0x1f, 0x6b, 0x4a, 0x9d, - 0xc0, 0x5d, 0x1c, 0xe0, 0x86, 0x3e, 0x01, 0x6b, 0x5b, 0x51, 0xd3, 0xb3, 0x8a, 0xda, 0x65, 0x18, - 0x6d, 0xca, 0x19, 0x79, 0x34, 0xb9, 0x75, 0x14, 0x27, 0x17, 0xda, 0x6e, 0xa4, 0x1f, 0x3f, 0x2d, - 0x0c, 0x15, 0x95, 0x9d, 0xf5, 0x9d, 0x01, 0x27, 0xb7, 0x78, 0x6d, 0x13, 0xd7, 0xeb, 0xb1, 0xd3, - 0xc5, 0x81, 0xcb, 0xf4, 0x3d, 0x88, 0x7f, 0x34, 0x0d, 0x19, 0x17, 0xb3, 0x52, 0x05, 0x37, 0xd5, - 0x93, 0x18, 0x75, 0x31, 0xdb, 0xc4, 0x4d, 0xf4, 0x31, 0x4c, 0x36, 0x03, 0xda, 0xa4, 0x8c, 0x04, - 0x9d, 0x67, 0x25, 0x9e, 0xc4, 0xf8, 0xc6, 0xfa, 0x1f, 0x4f, 0x0b, 0xb6, 0xeb, 0xf1, 0x5a, 0xab, - 0x6c, 0x57, 0x68, 0xc3, 0x51, 0xb5, 0x3f, 0xfc, 0x5c, 0x62, 0xd5, 0x3d, 0x87, 0x1f, 0x34, 0x09, - 0xb3, 0x37, 0xa3, 0xf7, 0x5c, 0x9c, 0xd0, 0xb1, 0xf4, 0x5b, 0x9c, 0x85, 0x6c, 0xa5, 0x86, 0x3d, - 0xbf, 0xe4, 0x55, 0x67, 0xd2, 0x0b, 0xc6, 0x6a, 0xaa, 0x98, 0x91, 0xe3, 0x1b, 0x55, 0x6b, 0x05, - 0x4e, 0x6d, 0x31, 0xee, 0x35, 0x30, 0x27, 0xdb, 0x38, 0x3a, 0x82, 0x49, 0x48, 0xb9, 0x38, 0x24, - 0x9f, 0x2e, 0x8a, 0x5f, 0xeb, 0xf7, 0x94, 0xbe, 0xc7, 0x00, 0x57, 0xc8, 0x4e, 0x5b, 0xef, 0xf3, - 0x15, 0x48, 0x35, 0x98, 0xab, 0x4e, 0x6a, 0x36, 0x7e, 0x52, 0xb7, 0x99, 0xbb, 0xc5, 0x6b, 0x24, - 0x20, 0xad, 0xc6, 0x4e, 0xbb, 0x28, 0xac, 0xd0, 0x15, 0x18, 0xe7, 0xc2, 0xbd, 0x54, 0xa1, 0xfe, - 0xae, 0xe7, 0xca, 0x3d, 0xe6, 0xd6, 0xa7, 0xe3, 0x5e, 0x32, 0xfc, 0xa6, 0x5c, 0x2e, 0xe6, 0x78, - 0x34, 0x40, 0x57, 0x61, 0xbc, 0x19, 0x90, 0x2a, 0xa9, 0x10, 0xc6, 0x68, 0xc0, 0x66, 0xd2, 0x32, - 0x71, 0x8e, 0x40, 0xec, 0x32, 0x17, 0x75, 0xb0, 0x5c, 0xa7, 0x95, 0x3d, 0x5d, 0x71, 0x46, 0xe4, - 0x39, 0xe4, 0xe4, 0x5c, 0x58, 0x6f, 0xd0, 0x3c, 0x40, 0x68, 0x22, 0x9f, 0xc5, 0xa8, 0x7c, 0x16, - 0x63, 0x72, 0x46, 0xf6, 0x8e, 0x4d, 0xbd, 0x2c, 0x9a, 0xdc, 0x4c, 0x46, 0x52, 0x37, 0xed, 0xb0, - 0x03, 0xda, 0xba, 0x03, 0xda, 0x3b, 0xba, 0x03, 0x6e, 0x64, 0x45, 0x8a, 0x3c, 0xfc, 0xb9, 0x60, - 0xa8, 0x20, 0x62, 0x25, 0xf1, 0xa6, 0xb3, 0xff, 0xcd, 0x4d, 0x8f, 0x75, 0xdd, 0x34, 0xb2, 0xe0, - 0x44, 0x48, 0xbf, 0x81, 0xdb, 0x25, 0x71, 0xb9, 0x10, 0x3b, 0x81, 0xdb, 0xb8, 0xbd, 0x8d, 0xd9, - 0xbb, 0xe9, 0xec, 0xf0, 0x64, 0xaa, 0x98, 0xe5, 0xed, 0x92, 0xe7, 0x57, 0x49, 0xdb, 0xba, 0xa0, - 0xea, 0x58, 0xe7, 0xce, 0xa3, 0x22, 0x53, 0xc5, 0x1c, 0xeb, 0xe4, 0x16, 0xff, 0xd6, 0xb7, 0x29, - 0xd5, 0xeb, 0xa5, 0xf1, 0x86, 0x88, 0x1a, 0xcb, 0x11, 0xde, 0xd6, 0x4f, 0xfd, 0xa8, 0x1c, 0xe1, - 0x6d, 0xf6, 0x8f, 0x72, 0xe4, 0xff, 0x4b, 0x3e, 0xfe, 0x92, 0xad, 0x4b, 0x4a, 0x55, 0xc5, 0xef, - 0xe9, 0x88, 0x7b, 0x3d, 0xdd, 0xe9, 0xc2, 0x8c, 0x5c, 0x27, 0xba, 0xda, 0x5b, 0xb7, 0x3a, 0x1d, - 0x56, 0x4d, 0xab, 0x10, 0xaf, 0x41, 0x56, 0x14, 0xe6, 0xd2, 0x2e, 0x51, 0x5d, 0x6e, 0x63, 0xf6, - 0xa7, 0xa7, 0x85, 0xd3, 0xe1, 0x0e, 0x59, 0x75, 0xcf, 0xf6, 0xa8, 0xd3, 0xc0, 0xbc, 0x66, 0xdf, - 0xf0, 0xb9, 0xe8, 0xbe, 0xd2, 0xdb, 0xba, 0x0a, 0x67, 0x65, 0xb4, 0xeb, 0x2d, 0x7f, 0x87, 0xee, - 0x11, 0xff, 0x36, 0x6e, 0x36, 0x3d, 0xdf, 0xd5, 0x09, 0x34, 0x05, 0x23, 0x5c, 0x4c, 0xeb, 0xbe, - 0x29, 0x07, 0xb1, 0x26, 0xf3, 0x91, 0x12, 0x41, 0x7d, 0xee, 0x8a, 0xd4, 0x1a, 0x8c, 0xed, 0xb6, - 0xfc, 0x52, 0x14, 0x23, 0xb7, 0x3e, 0x15, 0x4f, 0x28, 0xed, 0x57, 0xcc, 0xee, 0xaa, 0xbf, 0x28, - 0xf8, 0xfa, 0x0f, 0xe3, 0x30, 0x22, 0xa3, 0xa3, 0x07, 0x06, 0x40, 0xa4, 0x45, 0x91, 0x15, 0x0f, - 0x91, 0x2c, 0x73, 0xcd, 0xa5, 0x23, 0x6d, 0x42, 0x7a, 0xd6, 0xc5, 0xcf, 0xbf, 0xff, 0xf5, 0x9b, - 0xe1, 0x65, 0xf4, 0xb2, 0xe3, 0x4b, 0x01, 0xd9, 0x51, 0xec, 0xbc, 0x56, 0x52, 0x92, 0xc8, 0xb9, - 0xaf, 0x12, 0xe9, 0x10, 0x7d, 0x6d, 0xc0, 0x89, 0x2e, 0xa1, 0x89, 0xce, 0xf5, 0x81, 0x24, 0x29, - 0x59, 0x73, 0xf9, 0x38, 0x33, 0x45, 0xc7, 0x91, 0x74, 0xce, 0xa3, 0x95, 0x1e, 0x3a, 0xe1, 0x28, - 0x81, 0xd1, 0x23, 0x03, 0x26, 0x7b, 0x15, 0x23, 0x5a, 0xed, 0x43, 0x1b, 0x20, 0x51, 0xcd, 0xf3, - 0x7f, 0xc1, 0x52, 0x51, 0x7b, 0x43, 0x52, 0x5b, 0x43, 0x4e, 0x0f, 0xb5, 0x7d, 0xed, 0x10, 0xb1, - 0x8b, 0xab, 0xde, 0x43, 0xf4, 0x09, 0x64, 0x94, 0x16, 0x44, 0x85, 0x3e, 0xb8, 0x6e, 0x81, 0x69, - 0x2e, 0x0c, 0x36, 0x50, 0x34, 0xce, 0x4b, 0x1a, 0x4b, 0x68, 0xb1, 0x87, 0x86, 0x12, 0x93, 0x2c, - 0x76, 0x36, 0x9f, 0x41, 0x46, 0xa9, 0xc0, 0x04, 0xe0, 0x6e, 0xb1, 0x99, 0x00, 0xdc, 0x23, 0x20, - 0x2d, 0x5b, 0x02, 0xaf, 0xa2, 0xe5, 0x1e, 0x60, 0x16, 0xda, 0x45, 0xb8, 0xce, 0xfd, 0x3d, 0x72, - 0x70, 0x88, 0xf6, 0x20, 0x2d, 0xd4, 0x21, 0x9a, 0xeb, 0x8b, 0x1c, 0x13, 0x9b, 0xe6, 0xfc, 0x80, - 0x55, 0x05, 0xba, 0x2c, 0x41, 0x17, 0x50, 0xbe, 0x07, 0x54, 0x68, 0xcb, 0xf8, 0x56, 0x6b, 0x30, - 0x1a, 0xaa, 0x23, 0x94, 0xef, 0x0b, 0xd8, 0x25, 0xbc, 0xcc, 0xc2, 0xc0, 0x75, 0x05, 0x39, 0x2f, - 0x21, 0xa7, 0xd1, 0xe9, 0x1e, 0xc8, 0x50, 0x6f, 0x21, 0x0f, 0x32, 0x4a, 0x6e, 0x21, 0x33, 0x1e, - 0xaa, 0x5b, 0x83, 0x99, 0x8b, 0x83, 0x5b, 0x8d, 0x06, 0x2a, 0x48, 0xa0, 0x59, 0x34, 0x9d, 0xf0, - 0xf4, 0x2a, 0x22, 0x3e, 0x85, 0x5c, 0x4c, 0x20, 0x1d, 0x09, 0xd7, 0xb5, 0xab, 0x04, 0x55, 0x65, - 0x2d, 0x49, 0xb0, 0x79, 0x74, 0xb6, 0x17, 0x4c, 0xd9, 0x8a, 0x8a, 0x8d, 0x1a, 0x90, 0x51, 0xed, - 0x36, 0x21, 0x61, 0xba, 0xc5, 0x57, 0x42, 0xc2, 0xf4, 0x74, 0xea, 0x81, 0xfb, 0x0b, 0x5b, 0x2c, - 0x6f, 0xa3, 0x03, 0x80, 0xa8, 0x11, 0x24, 0x94, 0xb4, 0xbe, 0x6e, 0x9e, 0x50, 0xd2, 0xfa, 0x3b, - 0x89, 0x65, 0x49, 0xdc, 0x39, 0x64, 0x26, 0xe2, 0xca, 0x76, 0x24, 0x76, 0xaa, 0xba, 0x47, 0xe2, - 0x9b, 0x8c, 0xb7, 0x9b, 0xc4, 0x37, 0xd9, 0xd5, 0x78, 0x06, 0xee, 0x54, 0x77, 0x23, 0xf4, 0x95, - 0x01, 0x13, 0x3d, 0x0d, 0x02, 0xad, 0xf4, 0x85, 0x4d, 0xee, 0x40, 0xe6, 0xea, 0xf1, 0x86, 0x8a, - 0xc7, 0x8a, 0xe4, 0xb1, 0x88, 0x0a, 0x3d, 0x3c, 0x76, 0x5b, 0xbe, 0xec, 0x3f, 0xce, 0x7d, 0xf9, - 0x39, 0xdc, 0xb8, 0xfa, 0xf8, 0x59, 0xde, 0x78, 0xf2, 0x2c, 0x6f, 0xfc, 0xf2, 0x2c, 0x6f, 0x3c, - 0x7c, 0x9e, 0x1f, 0x7a, 0xf2, 0x3c, 0x3f, 0xf4, 0xe3, 0xf3, 0xfc, 0xd0, 0x87, 0x4b, 0x31, 0x85, - 0x10, 0x96, 0xe8, 0x4d, 0xd1, 0xdf, 0x75, 0xc0, 0xb6, 0x08, 0x59, 0x1e, 0x95, 0x6a, 0xe4, 0xd5, - 0x3f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x68, 0x44, 0xe9, 0x8d, 0x33, 0x12, 0x00, 0x00, + // 1572 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x56, 0xcf, 0x6f, 0x1b, 0x45, + 0x1b, 0x8e, 0x63, 0x27, 0x76, 0x5e, 0xa7, 0x4d, 0xbe, 0x69, 0xda, 0x24, 0x6e, 0x62, 0x27, 0x9b, + 0xaf, 0x49, 0xda, 0xaf, 0xdd, 0xfd, 0x12, 0x10, 0x88, 0x8a, 0x0a, 0xd5, 0x51, 0x1a, 0x4a, 0x7f, + 0xa8, 0x35, 0x11, 0x48, 0x20, 0x64, 0x8d, 0xd7, 0x93, 0xf5, 0x2a, 0xde, 0x1d, 0x77, 0x67, 0x9c, + 0x3a, 0x94, 0x5c, 0xe8, 0x05, 0x09, 0x21, 0x2a, 0xf1, 0x0f, 0xf4, 0xc4, 0xbf, 0xc0, 0xbf, 0xd0, + 0x63, 0x25, 0x84, 0x84, 0x38, 0x14, 0xd4, 0x72, 0xe0, 0xcc, 0x91, 0x13, 0x9a, 0xd9, 0x99, 0x78, + 0xbd, 0x5e, 0x27, 0x94, 0x1f, 0x37, 0x4e, 0xbb, 0x33, 0xf3, 0xce, 0xfb, 0x3c, 0xf3, 0xeb, 0x7d, + 0x1e, 0x38, 0x43, 0x78, 0xc3, 0x22, 0x7b, 0x9e, 0xb5, 0xb7, 0x66, 0xdd, 0x6b, 0x93, 0x60, 0xdf, + 0x6c, 0x05, 0x94, 0x53, 0x04, 0x84, 0x37, 0x4c, 0xb2, 0xe7, 0x99, 0x7b, 0x6b, 0x85, 0x0b, 0x36, + 0x65, 0x1e, 0x65, 0x56, 0x0d, 0x33, 0x12, 0x06, 0x59, 0x7b, 0x6b, 0x35, 0xc2, 0xf1, 0x9a, 0xd5, + 0xc2, 0x8e, 0xeb, 0x63, 0xee, 0x52, 0x3f, 0x9c, 0x57, 0x98, 0x8a, 0xe4, 0x13, 0xd3, 0xc3, 0xde, + 0x53, 0x91, 0x5e, 0xde, 0xd1, 0xa1, 0x0e, 0x75, 0xa8, 0xfc, 0xb5, 0xc4, 0x9f, 0xea, 0x9d, 0x73, + 0x28, 0x75, 0x9a, 0xc4, 0xc2, 0x2d, 0xd7, 0xc2, 0xbe, 0x4f, 0xb9, 0xcc, 0xce, 0xd4, 0x68, 0x49, + 0x8d, 0xca, 0x56, 0xad, 0xbd, 0x63, 0x71, 0xd7, 0x23, 0x8c, 0x63, 0xaf, 0x15, 0x06, 0x18, 0x6f, + 0xc2, 0x99, 0xbb, 0x82, 0xe1, 0x26, 0x6f, 0x5c, 0xb5, 0x6d, 0xda, 0xf6, 0x79, 0x85, 0xdc, 0x6b, + 0x13, 0xc6, 0xd1, 0x0c, 0x64, 0x71, 0xbd, 0x1e, 0x10, 0xc6, 0x66, 0x52, 0x0b, 0xa9, 0xd5, 0xb1, + 0x8a, 0x6e, 0x5e, 0xce, 0x7d, 0xf6, 0xb8, 0x34, 0xf4, 0xcb, 0xe3, 0xd2, 0x90, 0xe1, 0xc1, 0x74, + 0xdf, 0x6c, 0xd6, 0xa2, 0x3e, 0x23, 0xa8, 0x04, 0xf9, 0x1a, 0x6e, 0x62, 0xdf, 0x26, 0xd5, 0xfb, + 0xc4, 0x55, 0x29, 0x40, 0x75, 0xbd, 0x4f, 0x5c, 0x74, 0x16, 0xc6, 0x6c, 0x5a, 0x27, 0xd5, 0x06, + 0x66, 0x8d, 0x99, 0x61, 0x39, 0x9c, 0x13, 0x1d, 0x6f, 0x63, 0xd6, 0x40, 0x53, 0x30, 0xe2, 0x53, + 0xdf, 0x26, 0x33, 0xe9, 0x85, 0xd4, 0x6a, 0xa6, 0x12, 0x36, 0x8c, 0xb7, 0x60, 0x56, 0xc2, 0xdd, + 0x76, 0x6b, 0x6e, 0xd0, 0xfe, 0x13, 0x7c, 0xf7, 0xa1, 0x90, 0x94, 0x40, 0x51, 0x1e, 0x98, 0x01, + 0x15, 0x20, 0xc7, 0x04, 0x8c, 0x60, 0x34, 0x2c, 0x19, 0x1d, 0xb6, 0xd1, 0x39, 0x38, 0x89, 0xc3, + 0x44, 0x55, 0xbf, 0xed, 0xd5, 0x48, 0xa0, 0x38, 0x9f, 0x50, 0xbd, 0xb7, 0x65, 0xa7, 0x71, 0x03, + 0xe6, 0x24, 0xf4, 0x7b, 0xb8, 0xe9, 0xd6, 0x31, 0xa7, 0x41, 0x8c, 0xfe, 0x22, 0x8c, 0xdb, 0xd4, + 0x67, 0xd5, 0x5e, 0x06, 0x79, 0xd1, 0x77, 0xb5, 0x6f, 0x1d, 0x9f, 0xa7, 0x60, 0x7e, 0x40, 0x36, + 0xb5, 0x96, 0x15, 0x98, 0xd0, 0xac, 0x7a, 0x33, 0x6a, 0xb2, 0x57, 0xff, 0xbe, 0xa5, 0xbd, 0x01, + 0xa7, 0x24, 0x99, 0x72, 0x78, 0xb8, 0x2f, 0x73, 0x20, 0x77, 0x61, 0xaa, 0x77, 0x6a, 0xf7, 0x28, + 0xd4, 0x55, 0xd1, 0x73, 0x55, 0x33, 0x7e, 0xaf, 0x86, 0xe3, 0xf7, 0xca, 0xb8, 0xa1, 0xd8, 0xbc, + 0xcb, 0x69, 0x80, 0x9d, 0xe3, 0xd9, 0xa0, 0x49, 0x48, 0xef, 0x92, 0x7d, 0x95, 0x49, 0xfc, 0x46, + 0xf8, 0x5d, 0x54, 0xfc, 0x0e, 0x93, 0x29, 0x7e, 0x53, 0x30, 0xb2, 0x87, 0x9b, 0x6d, 0xcd, 0x2e, + 0x6c, 0x18, 0xaf, 0xc1, 0xa4, 0x8c, 0xde, 0xa0, 0xf5, 0x97, 0xda, 0x85, 0x15, 0xf8, 0x4f, 0x64, + 0x9e, 0x82, 0x40, 0x90, 0x11, 0xcf, 0x41, 0xce, 0x1a, 0xaf, 0xc8, 0x7f, 0xe3, 0x63, 0x40, 0x32, + 0x70, 0xbb, 0x73, 0x93, 0x3a, 0x4c, 0x43, 0x20, 0xc8, 0xc8, 0x47, 0x14, 0xe6, 0x97, 0xff, 0xe8, + 0x1a, 0x40, 0xb7, 0xd6, 0xc8, 0xb5, 0xe5, 0xd7, 0x97, 0xcd, 0xb0, 0x30, 0x99, 0xa2, 0x30, 0x99, + 0x61, 0xf5, 0x52, 0x85, 0xc9, 0xbc, 0xd3, 0xdd, 0xaa, 0x4a, 0x64, 0x66, 0x84, 0xe4, 0xc3, 0x94, + 0xda, 0x58, 0x0d, 0xae, 0x78, 0x2e, 0x41, 0xa6, 0x49, 0x1d, 0xb1, 0xba, 0xf4, 0x6a, 0x7e, 0x7d, + 0xc2, 0xec, 0x16, 0x42, 0xf3, 0x26, 0x75, 0x2a, 0x72, 0x10, 0x6d, 0x25, 0xd0, 0x59, 0x39, 0x96, + 0x4e, 0x88, 0x10, 0xe5, 0x63, 0x4c, 0xa9, 0x1d, 0xb8, 0x83, 0x03, 0xec, 0xe9, 0x1d, 0x30, 0xb6, + 0x14, 0x35, 0xdd, 0xab, 0xa8, 0xfd, 0x1f, 0x46, 0x5b, 0xb2, 0x47, 0x6e, 0x4d, 0x7e, 0x1d, 0x45, + 0xc9, 0x85, 0xb1, 0xe5, 0xcc, 0x93, 0x67, 0xa5, 0xa1, 0x8a, 0x8a, 0x33, 0xbe, 0x49, 0xc1, 0xc9, + 0x4d, 0xde, 0xd8, 0xc0, 0xcd, 0x66, 0x64, 0x77, 0x71, 0xe0, 0x30, 0x7d, 0x0e, 0xe2, 0x1f, 0x4d, + 0x43, 0xd6, 0xc1, 0xac, 0x6a, 0xe3, 0x96, 0x7a, 0x33, 0xa3, 0x0e, 0x66, 0x1b, 0xb8, 0x85, 0x3e, + 0x82, 0xc9, 0x56, 0x40, 0x5b, 0x94, 0x91, 0xe0, 0xf0, 0xdd, 0x89, 0x37, 0x33, 0x5e, 0x5e, 0xff, + 0xed, 0x59, 0xc9, 0x74, 0x5c, 0xde, 0x68, 0xd7, 0x4c, 0x9b, 0x7a, 0x96, 0xd2, 0x88, 0xf0, 0x73, + 0x89, 0xd5, 0x77, 0x2d, 0xbe, 0xdf, 0x22, 0xcc, 0xdc, 0xe8, 0x3e, 0xf8, 0xca, 0x84, 0xce, 0xa5, + 0x1f, 0xeb, 0x2c, 0xe4, 0xec, 0x06, 0x76, 0xfd, 0xaa, 0x5b, 0x9f, 0xc9, 0x2c, 0xa4, 0x56, 0xd3, + 0x95, 0xac, 0x6c, 0x5f, 0xaf, 0x1b, 0x2b, 0x70, 0x6a, 0x93, 0x71, 0xd7, 0xc3, 0x9c, 0x6c, 0xe1, + 0xee, 0x16, 0x4c, 0x42, 0xda, 0xc1, 0x21, 0xf9, 0x4c, 0x45, 0xfc, 0x1a, 0xbf, 0xa6, 0xf5, 0x39, + 0x06, 0xd8, 0x26, 0xdb, 0x1d, 0xbd, 0xce, 0xff, 0x41, 0xda, 0x63, 0x8e, 0xda, 0xa9, 0xd9, 0xe8, + 0x4e, 0xdd, 0x62, 0xce, 0x26, 0x6f, 0x90, 0x80, 0xb4, 0xbd, 0xed, 0x4e, 0x45, 0x44, 0xa1, 0xcb, + 0x30, 0xce, 0xc5, 0xf4, 0xaa, 0x4d, 0xfd, 0x1d, 0xd7, 0x91, 0x6b, 0xcc, 0xaf, 0x4f, 0x47, 0x67, + 0xc9, 0xf4, 0x1b, 0x72, 0xb8, 0x92, 0xe7, 0xdd, 0x06, 0xba, 0x02, 0xe3, 0xad, 0x80, 0xd4, 0x89, + 0x4d, 0x18, 0xa3, 0x01, 0x9b, 0xc9, 0xc8, 0x8b, 0x73, 0x04, 0x62, 0x4f, 0xb8, 0x28, 0x94, 0xb5, + 0x26, 0xb5, 0x77, 0x75, 0x49, 0x1a, 0x91, 0xfb, 0x90, 0x97, 0x7d, 0x61, 0x41, 0x42, 0xf3, 0x00, + 0x61, 0x88, 0x7c, 0x16, 0xa3, 0xf2, 0x59, 0x8c, 0xc9, 0x1e, 0x29, 0x2e, 0x1b, 0x7a, 0x58, 0x88, + 0xe1, 0x4c, 0x56, 0x52, 0x2f, 0x98, 0xa1, 0x52, 0x9a, 0x5a, 0x29, 0xcd, 0x6d, 0xad, 0x94, 0xe5, + 0x9c, 0xb8, 0x22, 0x8f, 0x7e, 0x2c, 0xa5, 0x54, 0x12, 0x31, 0x92, 0x78, 0xd2, 0xb9, 0x7f, 0xe6, + 0xa4, 0xc7, 0x7a, 0x4e, 0x1a, 0x19, 0x70, 0x22, 0xa4, 0xef, 0xe1, 0x4e, 0x55, 0x1c, 0x2e, 0x44, + 0x76, 0xe0, 0x16, 0xee, 0x6c, 0x61, 0xf6, 0x4e, 0x26, 0x37, 0x3c, 0x99, 0xae, 0xe4, 0x78, 0xa7, + 0xea, 0xfa, 0x75, 0xd2, 0x31, 0x2e, 0xa8, 0x3a, 0x76, 0x78, 0xe6, 0xdd, 0x22, 0x53, 0xc7, 0x1c, + 0xeb, 0xcb, 0x2d, 0xfe, 0x8d, 0xaf, 0xd3, 0xca, 0x13, 0xc8, 0xe0, 0xb2, 0xc8, 0x1a, 0xb9, 0x23, + 0xbc, 0xa3, 0x9f, 0xfa, 0x51, 0x77, 0x84, 0x77, 0xd8, 0x5f, 0xba, 0x23, 0xff, 0x1e, 0xf2, 0xf1, + 0x87, 0x6c, 0x5c, 0x52, 0xee, 0x2b, 0x7a, 0x4e, 0x47, 0x9c, 0xeb, 0xe9, 0x43, 0x99, 0x66, 0xe4, + 0x1a, 0xd1, 0xd5, 0xde, 0xb8, 0x79, 0x28, 0xc1, 0xaa, 0x5b, 0xa5, 0x78, 0x15, 0x72, 0xa2, 0x30, + 0x57, 0x77, 0x88, 0x52, 0xb9, 0xf2, 0xec, 0x0f, 0xcf, 0x4a, 0xa7, 0xc3, 0x15, 0xb2, 0xfa, 0xae, + 0xe9, 0x52, 0xcb, 0xc3, 0xbc, 0x61, 0x5e, 0xf7, 0xb9, 0x90, 0x67, 0x39, 0xdb, 0xb8, 0x02, 0x67, + 0x65, 0xb6, 0x6b, 0x6d, 0x7f, 0x9b, 0xee, 0x12, 0xff, 0x16, 0x6e, 0xb5, 0x5c, 0xdf, 0xd1, 0x17, + 0x68, 0x0a, 0x46, 0xb8, 0xe8, 0xd6, 0xba, 0x29, 0x1b, 0x11, 0x91, 0xf9, 0x50, 0xb9, 0xa4, 0xbe, + 0xe9, 0x8a, 0xd4, 0x1a, 0x8c, 0xed, 0xb4, 0xfd, 0x6a, 0x37, 0x47, 0x7e, 0x7d, 0x2a, 0x7a, 0xa1, + 0xf4, 0xbc, 0x4a, 0x6e, 0x47, 0xfd, 0x75, 0x93, 0xaf, 0x7f, 0x37, 0x0e, 0x23, 0x32, 0x3b, 0x7a, + 0x98, 0x02, 0xe8, 0x7a, 0x56, 0x64, 0x44, 0x53, 0x24, 0xdb, 0xe1, 0xc2, 0xd2, 0x91, 0x31, 0x21, + 0x3d, 0xe3, 0xe2, 0xa7, 0xdf, 0xfe, 0xfc, 0xd5, 0xf0, 0x32, 0xfa, 0xaf, 0xe5, 0x4b, 0x87, 0x79, + 0xe8, 0xec, 0x79, 0xa3, 0xaa, 0x3c, 0x93, 0xf5, 0x40, 0x5d, 0xa4, 0x03, 0xf4, 0x65, 0x0a, 0x4e, + 0xf4, 0x38, 0x51, 0x74, 0xae, 0x0f, 0x24, 0xc9, 0xea, 0x16, 0x96, 0x8f, 0x0b, 0x53, 0x74, 0x2c, + 0x49, 0xe7, 0x3c, 0x5a, 0x89, 0xd1, 0x09, 0x5b, 0x09, 0x8c, 0x1e, 0xa7, 0x60, 0x32, 0x6e, 0x29, + 0xd1, 0x6a, 0x1f, 0xda, 0x00, 0x0f, 0x5b, 0x38, 0xff, 0x07, 0x22, 0x15, 0xb5, 0xd7, 0x25, 0xb5, + 0x35, 0x64, 0xc5, 0xa8, 0xed, 0xe9, 0x09, 0x5d, 0x76, 0x51, 0x5b, 0x7c, 0x80, 0xee, 0x43, 0xb6, + 0xac, 0xad, 0x60, 0x1f, 0x5c, 0xaf, 0x03, 0x2d, 0x2c, 0x0c, 0x0e, 0x50, 0x34, 0xce, 0x4b, 0x1a, + 0x4b, 0x68, 0x31, 0x46, 0x43, 0xf9, 0x49, 0x16, 0xd9, 0x9b, 0x4f, 0x20, 0xab, 0x5c, 0x60, 0x02, + 0x70, 0xaf, 0xd9, 0x4c, 0x00, 0x8e, 0x19, 0x48, 0xc3, 0x94, 0xc0, 0xab, 0x68, 0x39, 0x06, 0xcc, + 0xc2, 0xb8, 0x2e, 0xae, 0xf5, 0x60, 0x97, 0xec, 0x1f, 0xa0, 0x5d, 0xc8, 0x08, 0x77, 0x88, 0xe6, + 0xfa, 0x32, 0x47, 0xcc, 0x66, 0x61, 0x7e, 0xc0, 0xa8, 0x02, 0x5d, 0x96, 0xa0, 0x0b, 0xa8, 0x18, + 0x03, 0x15, 0xde, 0x32, 0xba, 0xd4, 0x06, 0x8c, 0x86, 0xee, 0x08, 0x15, 0xfb, 0x12, 0xf6, 0x18, + 0xaf, 0x42, 0x69, 0xe0, 0xb8, 0x82, 0x9c, 0x97, 0x90, 0xd3, 0xe8, 0x74, 0x0c, 0x32, 0xf4, 0x5b, + 0xc8, 0x85, 0xac, 0xb2, 0x5b, 0xa8, 0x10, 0x4d, 0xd5, 0xeb, 0xc1, 0x0a, 0x8b, 0x83, 0xa5, 0x46, + 0x03, 0x95, 0x24, 0xd0, 0x2c, 0x9a, 0x4e, 0x78, 0x7a, 0xb6, 0xc8, 0x4f, 0x21, 0x1f, 0x31, 0x48, + 0x47, 0xc2, 0xf5, 0xac, 0x2a, 0xc1, 0x55, 0x19, 0x4b, 0x12, 0x6c, 0x1e, 0x9d, 0x8d, 0x83, 0xa9, + 0x58, 0x51, 0xb1, 0x91, 0x07, 0x59, 0x25, 0xb7, 0x09, 0x17, 0xa6, 0xd7, 0x7c, 0x25, 0x5c, 0x98, + 0x98, 0x52, 0x0f, 0x5c, 0x5f, 0x28, 0xb1, 0xbc, 0x83, 0xf6, 0x01, 0xba, 0x42, 0x90, 0x50, 0xd2, + 0xfa, 0xd4, 0x3c, 0xa1, 0xa4, 0xf5, 0x2b, 0x89, 0x61, 0x48, 0xdc, 0x39, 0x54, 0x48, 0xc4, 0x95, + 0x72, 0x24, 0x56, 0xaa, 0xd4, 0x23, 0xf1, 0x4d, 0x46, 0xe5, 0x26, 0xf1, 0x4d, 0xf6, 0x08, 0xcf, + 0xc0, 0x95, 0x6a, 0x35, 0x42, 0x5f, 0xa4, 0x60, 0x22, 0x26, 0x10, 0x68, 0xa5, 0x2f, 0x6d, 0xb2, + 0x02, 0x15, 0x56, 0x8f, 0x0f, 0x54, 0x3c, 0x56, 0x24, 0x8f, 0x45, 0x54, 0x8a, 0xf1, 0xd8, 0x69, + 0xfb, 0x52, 0x7f, 0xac, 0x07, 0xf2, 0x73, 0x50, 0xbe, 0xf2, 0xe4, 0x79, 0x31, 0xf5, 0xf4, 0x79, + 0x31, 0xf5, 0xd3, 0xf3, 0x62, 0xea, 0xd1, 0x8b, 0xe2, 0xd0, 0xd3, 0x17, 0xc5, 0xa1, 0xef, 0x5f, + 0x14, 0x87, 0x3e, 0x58, 0x8a, 0x38, 0x84, 0xb0, 0x44, 0x6f, 0x08, 0x7d, 0xd7, 0x09, 0x3b, 0x22, + 0x65, 0x6d, 0x54, 0xba, 0x91, 0x57, 0x7e, 0x0f, 0x00, 0x00, 0xff, 0xff, 0xc4, 0xdd, 0x5e, 0x28, + 0x5b, 0x12, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -2115,10 +2126,10 @@ func (m *QueryEthAccountResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) i-- dAtA[i] = 0x12 } - if len(m.Balance) > 0 { - i -= len(m.Balance) - copy(dAtA[i:], m.Balance) - i = encodeVarintQuery(dAtA, i, uint64(len(m.Balance))) + if len(m.BalanceWei) > 0 { + i -= len(m.BalanceWei) + copy(dAtA[i:], m.BalanceWei) + i = encodeVarintQuery(dAtA, i, uint64(len(m.BalanceWei))) i-- dAtA[i] = 0xa } @@ -2315,6 +2326,13 @@ func (m *QueryBalanceResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.BalanceWei) > 0 { + i -= len(m.BalanceWei) + copy(dAtA[i:], m.BalanceWei) + i = encodeVarintQuery(dAtA, i, uint64(len(m.BalanceWei))) + i-- + dAtA[i] = 0x12 + } if len(m.Balance) > 0 { i -= len(m.Balance) copy(dAtA[i:], m.Balance) @@ -3071,7 +3089,7 @@ func (m *QueryEthAccountResponse) Size() (n int) { } var l int _ = l - l = len(m.Balance) + l = len(m.BalanceWei) if l > 0 { n += 1 + l + sovQuery(uint64(l)) } @@ -3172,6 +3190,10 @@ func (m *QueryBalanceResponse) Size() (n int) { if l > 0 { n += 1 + l + sovQuery(uint64(l)) } + l = len(m.BalanceWei) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } return n } @@ -3595,7 +3617,7 @@ func (m *QueryEthAccountResponse) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Balance", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field BalanceWei", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -3623,7 +3645,7 @@ func (m *QueryEthAccountResponse) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Balance = string(dAtA[iNdEx:postIndex]) + m.BalanceWei = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { @@ -4244,6 +4266,38 @@ func (m *QueryBalanceResponse) Unmarshal(dAtA []byte) error { } m.Balance = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BalanceWei", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BalanceWei = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipQuery(dAtA[iNdEx:]) diff --git a/x/evm/statedb/mock_test.go b/x/evm/statedb/mock_test.go deleted file mode 100644 index b4324abe9..000000000 --- a/x/evm/statedb/mock_test.go +++ /dev/null @@ -1,115 +0,0 @@ -package statedb_test - -import ( - "bytes" - "errors" - "math/big" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - - "github.com/NibiruChain/nibiru/x/evm/statedb" -) - -var ( - _ statedb.Keeper = &MockKeeper{} - errAddress common.Address = common.BigToAddress(big.NewInt(100)) - emptyCodeHash = crypto.Keccak256(nil) -) - -type MockAcount struct { - account statedb.Account - states statedb.Storage -} - -type MockKeeper struct { - accounts map[common.Address]MockAcount - codes map[common.Hash][]byte -} - -func NewMockKeeper() *MockKeeper { - return &MockKeeper{ - accounts: make(map[common.Address]MockAcount), - codes: make(map[common.Hash][]byte), - } -} - -func (k MockKeeper) GetAccount(_ sdk.Context, addr common.Address) *statedb.Account { - acct, ok := k.accounts[addr] - if !ok { - return nil - } - return &acct.account -} - -func (k MockKeeper) GetState(_ sdk.Context, addr common.Address, key common.Hash) common.Hash { - return k.accounts[addr].states[key] -} - -func (k MockKeeper) GetCode(_ sdk.Context, codeHash common.Hash) []byte { - return k.codes[codeHash] -} - -func (k MockKeeper) ForEachStorage(_ sdk.Context, addr common.Address, cb func(key, value common.Hash) bool) { - if acct, ok := k.accounts[addr]; ok { - for k, v := range acct.states { - if !cb(k, v) { - return - } - } - } -} - -func (k MockKeeper) SetAccount(_ sdk.Context, addr common.Address, account statedb.Account) error { - if addr == errAddress { - return errors.New("mock db error") - } - acct, exists := k.accounts[addr] - if exists { - // update - acct.account = account - k.accounts[addr] = acct - } else { - k.accounts[addr] = MockAcount{account: account, states: make(statedb.Storage)} - } - return nil -} - -func (k MockKeeper) SetState(_ sdk.Context, addr common.Address, key common.Hash, value []byte) { - if acct, ok := k.accounts[addr]; ok { - if len(value) == 0 { - delete(acct.states, key) - } else { - acct.states[key] = common.BytesToHash(value) - } - } -} - -func (k MockKeeper) SetCode(_ sdk.Context, codeHash []byte, code []byte) { - k.codes[common.BytesToHash(codeHash)] = code -} - -func (k MockKeeper) DeleteAccount(_ sdk.Context, addr common.Address) error { - if addr == errAddress { - return errors.New("mock db error") - } - old := k.accounts[addr] - delete(k.accounts, addr) - if !bytes.Equal(old.account.CodeHash, emptyCodeHash) { - delete(k.codes, common.BytesToHash(old.account.CodeHash)) - } - return nil -} - -func (k MockKeeper) Clone() *MockKeeper { - accounts := make(map[common.Address]MockAcount, len(k.accounts)) - for k, v := range k.accounts { - accounts[k] = v - } - codes := make(map[common.Hash][]byte, len(k.codes)) - for k, v := range k.codes { - codes[k] = v - } - return &MockKeeper{accounts, codes} -} diff --git a/x/evm/statedb/state_object.go b/x/evm/statedb/state_object.go index 39592af7c..e634a63b8 100644 --- a/x/evm/statedb/state_object.go +++ b/x/evm/statedb/state_object.go @@ -8,29 +8,73 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + + "github.com/NibiruChain/nibiru/x/evm" ) var emptyCodeHash = crypto.Keccak256(nil) -// Account is the Ethereum consensus representation of accounts. +// Account represents an Ethereum account as viewed by the Auth module state. The +// balance is stored in the smallest native unit (e.g., micronibi or unibi). // These objects are stored in the storage of auth module. type Account struct { - Nonce uint64 - Balance *big.Int + // BalanceNative is the micronibi (unibi) balance of the account, which is + // the official balance in the x/bank module state + BalanceNative *big.Int + // Nonce is the number of transactions sent from this account or, for contract accounts, the number of contract-creations made by this account + Nonce uint64 + // CodeHash is the hash of the contract code for this account, or nil if it's not a contract account + CodeHash []byte +} + +// AccountWei represents an Ethereum account as viewed by the EVM. This struct is +// derived from an `Account` but represents balances in wei, which is necessary +// for correct operation within the EVM. The EVM expects and operates on wei +// values, which are 10^12 times larger than the native unibi value due to the +// definition of NIBI as "ether". +type AccountWei struct { + BalanceWei *big.Int + // Nonce is the number of transactions sent from this account or, for contract accounts, the number of contract-creations made by this account + Nonce uint64 + // CodeHash is the hash of the contract code for this account, or nil if it's not a contract account CodeHash []byte } +// ToWei converts an Account (native representation) to AccountWei (EVM +// representation). This conversion is necessary when moving from the Cosmos SDK +// context to the EVM context. It multiplies the balance by 10^12 to convert from +// unibi to wei. +func (acc Account) ToWei() AccountWei { + return AccountWei{ + BalanceWei: evm.NativeToWei(acc.BalanceNative), + Nonce: acc.Nonce, + CodeHash: acc.CodeHash, + } +} + +// ToNative converts an AccountWei (EVM representation) back to an Account +// (native representation). This conversion is necessary when moving from the EVM +// context back to the Cosmos SDK context. It divides the balance by 10^12 to +// convert from wei to unibi. +func (acc AccountWei) ToNative() Account { + return Account{ + BalanceNative: evm.WeiToNative(acc.BalanceWei), + Nonce: acc.Nonce, + CodeHash: acc.CodeHash, + } +} + // NewEmptyAccount returns an empty account. func NewEmptyAccount() *Account { return &Account{ - Balance: new(big.Int), - CodeHash: emptyCodeHash, + BalanceNative: new(big.Int), + CodeHash: emptyCodeHash, } } // IsContract returns if the account contains contract code. -func (acct Account) IsContract() bool { - return !bytes.Equal(acct.CodeHash, emptyCodeHash) +func (acct *Account) IsContract() bool { + return (acct != nil) && !bytes.Equal(acct.CodeHash, emptyCodeHash) } // Storage represents in-memory cache/buffer of contract storage. @@ -48,11 +92,26 @@ func (s Storage) SortedKeys() []common.Hash { return keys } -// stateObject is the state of an acount +// stateObject represents the state of a Nibiru EVM account. +// It encapsulates both the account data (balance, nonce, code) and the contract +// storage state. stateObject serves as an in-memory cache and staging area for +// changes before they are committed to the underlying storage. +// +// Key features: +// 1. It uses AccountWei, which represents balances in wei for EVM compatibility. +// 2. It maintains both the original (committed) storage and dirty (uncommitted) storage. +// 3. It tracks whether the account has been marked for deletion (suicided). +// 4. It caches the contract code for efficient access. +// +// stateObjects are used to: +// - Efficiently manage and track changes to account state during EVM execution. +// - Provide a layer of abstraction between the EVM and the underlying storage. +// - Enable features like state reverting and snapshotting. +// - Optimize performance by minimizing direct access to the underlying storage. type stateObject struct { db *StateDB - account Account + account AccountWei code []byte // state storage @@ -68,28 +127,25 @@ type stateObject struct { // newObject creates a state object. func newObject(db *StateDB, address common.Address, account Account) *stateObject { - if account.Balance == nil { - account.Balance = new(big.Int) + if account.BalanceNative == nil { + account.BalanceNative = new(big.Int) } if account.CodeHash == nil { account.CodeHash = emptyCodeHash } return &stateObject{ - db: db, - address: address, - account: account, + db: db, + address: address, + // Reflect the micronibi (unibi) balance in wei + account: account.ToWei(), originStorage: make(Storage), dirtyStorage: make(Storage), } } -// empty returns whether the account is considered empty. -func (s *stateObject) empty() bool { - return s.account.Nonce == 0 && s.account.Balance.Sign() == 0 && bytes.Equal(s.account.CodeHash, emptyCodeHash) -} - -func (s *stateObject) markSuicided() { - s.suicided = true +// isEmpty returns whether the account is considered isEmpty. +func (s *stateObject) isEmpty() bool { + return s.account.Nonce == 0 && s.account.BalanceWei.Sign() == 0 && bytes.Equal(s.account.CodeHash, emptyCodeHash) } // AddBalance adds amount to s's balance. @@ -114,13 +170,13 @@ func (s *stateObject) SubBalance(amount *big.Int) { func (s *stateObject) SetBalance(amount *big.Int) { s.db.journal.append(balanceChange{ account: &s.address, - prev: new(big.Int).Set(s.account.Balance), + prev: new(big.Int).Set(s.account.BalanceWei), }) s.setBalance(amount) } func (s *stateObject) setBalance(amount *big.Int) { - s.account.Balance = amount + s.account.BalanceWei = amount } // @@ -188,7 +244,7 @@ func (s *stateObject) CodeHash() []byte { // Balance returns the balance of account func (s *stateObject) Balance() *big.Int { - return s.account.Balance + return s.account.BalanceWei } // Nonce returns the nonce of account diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go index d3a39790a..27b81a3ce 100644 --- a/x/evm/statedb/statedb.go +++ b/x/evm/statedb/statedb.go @@ -6,7 +6,6 @@ import ( "math/big" "sort" - errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" gethcore "github.com/ethereum/go-ethereum/core/types" @@ -118,7 +117,7 @@ func (s *StateDB) Exist(addr common.Address) bool { // or empty according to the EIP161 specification (balance = nonce = code = 0) func (s *StateDB) Empty(addr common.Address) bool { so := s.getStateObject(addr) - return so == nil || so.empty() + return so == nil || so.isEmpty() } // GetBalance retrieves the balance from the given address or 0 if object not found @@ -217,6 +216,7 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject { if account == nil { return nil } + // Insert into the live set obj := newObject(s, addr, *account) s.setStateObject(obj) @@ -263,7 +263,7 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) func (s *StateDB) CreateAccount(addr common.Address) { newObj, prev := s.createObject(addr) if prev != nil { - newObj.setBalance(prev.account.Balance) + newObj.setBalance(prev.account.BalanceWei) } } @@ -348,8 +348,8 @@ func (s *StateDB) Suicide(addr common.Address) bool { prev: stateObject.suicided, prevbalance: new(big.Int).Set(stateObject.Balance()), }) - stateObject.markSuicided() - stateObject.account.Balance = new(big.Int) + stateObject.suicided = true + stateObject.account.BalanceWei = new(big.Int) return true } @@ -444,6 +444,11 @@ func (s *StateDB) RevertToSnapshot(revid int) { s.validRevisions = s.validRevisions[:idx] } +// errorf: wrapper of "fmt.Errorf" specific to the current Go package. +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. func (s *StateDB) Commit() error { @@ -451,14 +456,14 @@ func (s *StateDB) Commit() error { obj := s.stateObjects[addr] if obj.suicided { if err := s.keeper.DeleteAccount(s.ctx, obj.Address()); err != nil { - return errorsmod.Wrap(err, "failed to delete account") + return errorf("failed to delete account: %w") } } else { if obj.code != nil && obj.dirtyCode { s.keeper.SetCode(s.ctx, obj.CodeHash(), obj.code) } - if err := s.keeper.SetAccount(s.ctx, obj.Address(), obj.account); err != nil { - return errorsmod.Wrap(err, "failed to set account") + if err := s.keeper.SetAccount(s.ctx, obj.Address(), obj.account.ToNative()); err != nil { + return errorf("failed to set account: %w") } for _, key := range obj.dirtyStorage.SortedKeys() { value := obj.dirtyStorage[key] diff --git a/x/evm/statedb/statedb_test.go b/x/evm/statedb/statedb_test.go index 21d4ac27e..56e3cddc4 100644 --- a/x/evm/statedb/statedb_test.go +++ b/x/evm/statedb/statedb_test.go @@ -4,67 +4,96 @@ import ( "math/big" "testing" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" gethcore "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" - "github.com/stretchr/testify/suite" + s "github.com/stretchr/testify/suite" + "github.com/NibiruChain/nibiru/x/common/set" + "github.com/NibiruChain/nibiru/x/evm/evmtest" "github.com/NibiruChain/nibiru/x/evm/statedb" ) +// emptyCodeHash: The hash for empty contract bytecode, or a blank byte +// array. This is the code hash for a non-existent or empty account. +var emptyCodeHash []byte = crypto.Keccak256(nil) + +// dummy variables for tests var ( - address common.Address = common.BigToAddress(big.NewInt(101)) - address2 common.Address = common.BigToAddress(big.NewInt(102)) - address3 common.Address = common.BigToAddress(big.NewInt(103)) - blockHash common.Hash = common.BigToHash(big.NewInt(9999)) - emptyTxConfig statedb.TxConfig = statedb.NewEmptyTxConfig(blockHash) + address common.Address = common.BigToAddress(big.NewInt(101)) + address2 common.Address = common.BigToAddress(big.NewInt(102)) + address3 common.Address = common.BigToAddress(big.NewInt(103)) + blockHash common.Hash = common.BigToHash(big.NewInt(9999)) + errAddress common.Address = common.BigToAddress(big.NewInt(100)) ) -type StateDBTestSuite struct { - suite.Suite +// TestSuite runs the entire test suite. +func TestSuite(t *testing.T) { + s.Run(t, new(Suite)) +} + +type Suite struct { + s.Suite +} + +// CollectContractStorage is a helper function that collects all storage key-value pairs +// for a given contract address using the ForEachStorage method of the StateDB. +// It returns a map of storage slots to their values. +func CollectContractStorage(db vm.StateDB) statedb.Storage { + storage := make(statedb.Storage) + err := db.ForEachStorage( + address, + func(k, v common.Hash) bool { + storage[k] = v + return true + }, + ) + if err != nil { + return nil + } + + return storage } -func (suite *StateDBTestSuite) TestAccount() { +func (s *Suite) TestAccount() { key1 := common.BigToHash(big.NewInt(1)) value1 := common.BigToHash(big.NewInt(2)) key2 := common.BigToHash(big.NewInt(3)) value2 := common.BigToHash(big.NewInt(4)) testCases := []struct { name string - malleate func(*statedb.StateDB) + malleate func(deps *evmtest.TestDeps, db *statedb.StateDB) }{ - {"non-exist account", func(db *statedb.StateDB) { - suite.Require().Equal(false, db.Exist(address)) - suite.Require().Equal(true, db.Empty(address)) - suite.Require().Equal(big.NewInt(0), db.GetBalance(address)) - suite.Require().Equal([]byte(nil), db.GetCode(address)) - suite.Require().Equal(common.Hash{}, db.GetCodeHash(address)) - suite.Require().Equal(uint64(0), db.GetNonce(address)) + {"non-exist account", func(deps *evmtest.TestDeps, db *statedb.StateDB) { + s.Require().Equal(false, db.Exist(address)) + s.Require().Equal(true, db.Empty(address)) + s.Require().Equal(big.NewInt(0), db.GetBalance(address)) + s.Require().Equal([]byte(nil), db.GetCode(address)) + s.Require().Equal(common.Hash{}, db.GetCodeHash(address)) + s.Require().Equal(uint64(0), db.GetNonce(address)) }}, - {"empty account", func(db *statedb.StateDB) { + {"empty account", func(deps *evmtest.TestDeps, db *statedb.StateDB) { db.CreateAccount(address) - suite.Require().NoError(db.Commit()) - - keeper := db.Keeper().(*MockKeeper) - acct := keeper.accounts[address] - suite.Require().Equal(statedb.NewEmptyAccount(), &acct.account) - suite.Require().Empty(acct.states) - suite.Require().False(acct.account.IsContract()) - - db = statedb.New(sdk.Context{}, keeper, emptyTxConfig) - suite.Require().Equal(true, db.Exist(address)) - suite.Require().Equal(true, db.Empty(address)) - suite.Require().Equal(big.NewInt(0), db.GetBalance(address)) - suite.Require().Equal([]byte(nil), db.GetCode(address)) - suite.Require().Equal(common.BytesToHash(emptyCodeHash), db.GetCodeHash(address)) - suite.Require().Equal(uint64(0), db.GetNonce(address)) + s.Require().NoError(db.Commit()) + + k := db.Keeper() + acct := k.GetAccount(deps.Ctx, address) + s.Require().EqualValues(statedb.NewEmptyAccount(), acct) + s.Require().Empty(CollectContractStorage(db)) + + db = deps.StateDB() + s.Require().Equal(true, db.Exist(address)) + s.Require().Equal(true, db.Empty(address)) + s.Require().Equal(big.NewInt(0), db.GetBalance(address)) + s.Require().Equal([]byte(nil), db.GetCode(address)) + s.Require().Equal(common.BytesToHash(emptyCodeHash), db.GetCodeHash(address)) + s.Require().Equal(uint64(0), db.GetNonce(address)) }}, - {"suicide", func(db *statedb.StateDB) { + {"suicide", func(deps *evmtest.TestDeps, db *statedb.StateDB) { // non-exist account. - suite.Require().False(db.Suicide(address)) - suite.Require().False(db.HasSuicided(address)) + s.Require().False(db.Suicide(address)) + s.Require().False(db.HasSuicided(address)) // create a contract account db.CreateAccount(address) @@ -72,45 +101,41 @@ func (suite *StateDBTestSuite) TestAccount() { db.AddBalance(address, big.NewInt(100)) db.SetState(address, key1, value1) db.SetState(address, key2, value2) - suite.Require().NoError(db.Commit()) + s.Require().NoError(db.Commit()) // suicide - db = statedb.New(sdk.Context{}, db.Keeper(), emptyTxConfig) - suite.Require().False(db.HasSuicided(address)) - suite.Require().True(db.Suicide(address)) + db = deps.StateDB() + s.Require().False(db.HasSuicided(address)) + s.Require().True(db.Suicide(address)) // check dirty state - suite.Require().True(db.HasSuicided(address)) + s.Require().True(db.HasSuicided(address)) // balance is cleared - suite.Require().Equal(big.NewInt(0), db.GetBalance(address)) + s.Require().Equal(big.NewInt(0), db.GetBalance(address)) // but code and state are still accessible in dirty state - suite.Require().Equal(value1, db.GetState(address, key1)) - suite.Require().Equal([]byte("hello world"), db.GetCode(address)) + s.Require().Equal(value1, db.GetState(address, key1)) + s.Require().Equal([]byte("hello world"), db.GetCode(address)) - suite.Require().NoError(db.Commit()) + s.Require().NoError(db.Commit()) // not accessible from StateDB anymore - db = statedb.New(sdk.Context{}, db.Keeper(), emptyTxConfig) - suite.Require().False(db.Exist(address)) - - // and cleared in keeper too - keeper := db.Keeper().(*MockKeeper) - suite.Require().Empty(keeper.accounts) - suite.Require().Empty(keeper.codes) + db = deps.StateDB() + s.Require().False(db.Exist(address)) + s.Require().Empty(CollectContractStorage(db)) }}, } for _, tc := range testCases { - suite.Run(tc.name, func() { - keeper := NewMockKeeper() - db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) - tc.malleate(db) + s.Run(tc.name, func() { + deps := evmtest.NewTestDeps() + db := deps.StateDB() + tc.malleate(&deps, db) }) } } -func (suite *StateDBTestSuite) TestAccountOverride() { - keeper := NewMockKeeper() - db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) +func (s *Suite) TestAccountOverride() { + deps := evmtest.NewTestDeps() + db := deps.StateDB() // test balance carry over when overwritten amount := big.NewInt(1) @@ -122,12 +147,12 @@ func (suite *StateDBTestSuite) TestAccountOverride() { db.CreateAccount(address) // check balance is not lost - suite.Require().Equal(amount, db.GetBalance(address)) + s.Require().Equal(amount, db.GetBalance(address)) // but nonce is reset - suite.Require().Equal(uint64(0), db.GetNonce(address)) + s.Require().Equal(uint64(0), db.GetNonce(address)) } -func (suite *StateDBTestSuite) TestDBError() { +func (s *Suite) TestDBError() { testCases := []struct { name string malleate func(vm.StateDB) @@ -137,17 +162,19 @@ func (suite *StateDBTestSuite) TestDBError() { }}, {"delete account", func(db vm.StateDB) { db.SetNonce(errAddress, 1) - suite.Require().True(db.Suicide(errAddress)) + s.Require().True(db.Suicide(errAddress)) + s.True(db.HasSuicided(errAddress)) }}, } for _, tc := range testCases { - db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) + deps := evmtest.NewTestDeps() + db := deps.StateDB() tc.malleate(db) - suite.Require().Error(db.Commit()) + s.Require().NoError(db.Commit()) } } -func (suite *StateDBTestSuite) TestBalance() { +func (s *Suite) TestBalance() { // NOTE: no need to test overflow/underflow, that is guaranteed by evm implementation. testCases := []struct { name string @@ -160,7 +187,7 @@ func (suite *StateDBTestSuite) TestBalance() { {"sub balance", func(db *statedb.StateDB) { db.AddBalance(address, big.NewInt(10)) // get dirty balance - suite.Require().Equal(big.NewInt(10), db.GetBalance(address)) + s.Require().Equal(big.NewInt(10), db.GetBalance(address)) db.SubBalance(address, big.NewInt(2)) }, big.NewInt(8)}, {"add zero balance", func(db *statedb.StateDB) { @@ -172,21 +199,22 @@ func (suite *StateDBTestSuite) TestBalance() { } for _, tc := range testCases { - suite.Run(tc.name, func() { - keeper := NewMockKeeper() - db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) + s.Run(tc.name, func() { + deps := evmtest.NewTestDeps() + db := deps.StateDB() tc.malleate(db) // check dirty state - suite.Require().Equal(tc.expBalance, db.GetBalance(address)) - suite.Require().NoError(db.Commit()) + s.Require().Equal(tc.expBalance, db.GetBalance(address)) + s.Require().NoError(db.Commit()) + // check committed balance too - suite.Require().Equal(tc.expBalance, keeper.accounts[address].account.Balance) + s.Require().Equal(tc.expBalance, db.GetBalance(address)) }) } } -func (suite *StateDBTestSuite) TestState() { +func (s *Suite) TestState() { key1 := common.BigToHash(big.NewInt(1)) value1 := common.BigToHash(big.NewInt(1)) testCases := []struct { @@ -205,47 +233,49 @@ func (suite *StateDBTestSuite) TestState() { }, statedb.Storage{}}, {"set state", func(db *statedb.StateDB) { // check empty initial state - suite.Require().Equal(common.Hash{}, db.GetState(address, key1)) - suite.Require().Equal(common.Hash{}, db.GetCommittedState(address, key1)) + s.Require().Equal(common.Hash{}, db.GetState(address, key1)) + s.Require().Equal(common.Hash{}, db.GetCommittedState(address, key1)) // set state db.SetState(address, key1, value1) // query dirty state - suite.Require().Equal(value1, db.GetState(address, key1)) + s.Require().Equal(value1, db.GetState(address, key1)) // check committed state is still not exist - suite.Require().Equal(common.Hash{}, db.GetCommittedState(address, key1)) + s.Require().Equal(common.Hash{}, db.GetCommittedState(address, key1)) // set same value again, should be noop db.SetState(address, key1, value1) - suite.Require().Equal(value1, db.GetState(address, key1)) + s.Require().Equal(value1, db.GetState(address, key1)) }, statedb.Storage{ key1: value1, }}, } for _, tc := range testCases { - suite.Run(tc.name, func() { - keeper := NewMockKeeper() - db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) + s.Run(tc.name, func() { + deps := evmtest.NewTestDeps() + db := deps.StateDB() tc.malleate(db) - suite.Require().NoError(db.Commit()) + s.Require().NoError(db.Commit()) // check committed states in keeper - suite.Require().Equal(tc.expStates, keeper.accounts[address].states) + for k, v := range tc.expStates { + s.Equal(v, db.GetState(address, k)) + } // check ForEachStorage - db = statedb.New(sdk.Context{}, keeper, emptyTxConfig) + db = deps.StateDB() collected := CollectContractStorage(db) if len(tc.expStates) > 0 { - suite.Require().Equal(tc.expStates, collected) + s.Require().Equal(tc.expStates, collected) } else { - suite.Require().Empty(collected) + s.Require().Empty(collected) } }) } } -func (suite *StateDBTestSuite) TestCode() { +func (s *Suite) TestCode() { code := []byte("hello world") codeHash := crypto.Keccak256Hash(code) @@ -265,28 +295,28 @@ func (suite *StateDBTestSuite) TestCode() { } for _, tc := range testCases { - suite.Run(tc.name, func() { - keeper := NewMockKeeper() - db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) + s.Run(tc.name, func() { + deps := evmtest.NewTestDeps() + db := deps.StateDB() tc.malleate(db) // check dirty state - suite.Require().Equal(tc.expCode, db.GetCode(address)) - suite.Require().Equal(len(tc.expCode), db.GetCodeSize(address)) - suite.Require().Equal(tc.expCodeHash, db.GetCodeHash(address)) + s.Require().Equal(tc.expCode, db.GetCode(address)) + s.Require().Equal(len(tc.expCode), db.GetCodeSize(address)) + s.Require().Equal(tc.expCodeHash, db.GetCodeHash(address)) - suite.Require().NoError(db.Commit()) + s.Require().NoError(db.Commit()) // check again - db = statedb.New(sdk.Context{}, keeper, emptyTxConfig) - suite.Require().Equal(tc.expCode, db.GetCode(address)) - suite.Require().Equal(len(tc.expCode), db.GetCodeSize(address)) - suite.Require().Equal(tc.expCodeHash, db.GetCodeHash(address)) + db = deps.StateDB() + s.Require().Equal(tc.expCode, db.GetCode(address)) + s.Require().Equal(len(tc.expCode), db.GetCodeSize(address)) + s.Require().Equal(tc.expCodeHash, db.GetCodeHash(address)) }) } } -func (suite *StateDBTestSuite) TestRevertSnapshot() { +func (s *Suite) TestRevertSnapshot() { v1 := common.BigToHash(big.NewInt(1)) v2 := common.BigToHash(big.NewInt(2)) v3 := common.BigToHash(big.NewInt(3)) @@ -313,7 +343,7 @@ func (suite *StateDBTestSuite) TestRevertSnapshot() { {"suicide", func(db vm.StateDB) { db.SetState(address, v1, v2) db.SetCode(address, []byte("hello world")) - suite.Require().True(db.Suicide(address)) + s.Require().True(db.Suicide(address)) }}, {"add log", func(db vm.StateDB) { db.AddLog(&gethcore.Log{ @@ -330,70 +360,78 @@ func (suite *StateDBTestSuite) TestRevertSnapshot() { }}, } for _, tc := range testCases { - suite.Run(tc.name, func() { - ctx := sdk.Context{} - keeper := NewMockKeeper() - - { - // do some arbitrary changes to the storage - db := statedb.New(ctx, keeper, emptyTxConfig) - db.SetNonce(address, 1) - db.AddBalance(address, big.NewInt(100)) - db.SetCode(address, []byte("hello world")) - db.SetState(address, v1, v2) - db.SetNonce(address2, 1) - suite.Require().NoError(db.Commit()) - } + s.Run(tc.name, func() { + deps := evmtest.NewTestDeps() + + // do some arbitrary changes to the storage + db := deps.StateDB() + db.SetNonce(address, 1) + db.AddBalance(address, big.NewInt(100)) + db.SetCode(address, []byte("hello world")) + db.SetState(address, v1, v2) + db.SetNonce(address2, 1) + s.Require().NoError(db.Commit()) - originalKeeper := keeper.Clone() + // Store original state values + originalNonce := db.GetNonce(address) + originalBalance := db.GetBalance(address) + originalCode := db.GetCode(address) + originalState := db.GetState(address, v1) + originalNonce2 := db.GetNonce(address2) // run test - db := statedb.New(ctx, keeper, emptyTxConfig) rev := db.Snapshot() tc.malleate(db) db.RevertToSnapshot(rev) // check empty states after revert - suite.Require().Zero(db.GetRefund()) - suite.Require().Empty(db.Logs()) + s.Require().Zero(db.GetRefund()) + s.Require().Empty(db.Logs()) - suite.Require().NoError(db.Commit()) + s.Require().NoError(db.Commit()) - // check keeper should stay the same - suite.Require().Equal(originalKeeper, keeper) + // Check again after commit to ensure persistence + s.Require().Equal(originalNonce, db.GetNonce(address)) + s.Require().Equal(originalBalance, db.GetBalance(address)) + s.Require().Equal(originalCode, db.GetCode(address)) + s.Require().Equal(originalState, db.GetState(address, v1)) + s.Require().Equal(originalNonce2, db.GetNonce(address2)) }) } } -func (suite *StateDBTestSuite) TestNestedSnapshot() { +func (s *Suite) TestNestedSnapshot() { key := common.BigToHash(big.NewInt(1)) value1 := common.BigToHash(big.NewInt(1)) value2 := common.BigToHash(big.NewInt(2)) - db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) + deps := evmtest.NewTestDeps() + db := deps.StateDB() rev1 := db.Snapshot() db.SetState(address, key, value1) rev2 := db.Snapshot() db.SetState(address, key, value2) - suite.Require().Equal(value2, db.GetState(address, key)) + s.Require().Equal(value2, db.GetState(address, key)) db.RevertToSnapshot(rev2) - suite.Require().Equal(value1, db.GetState(address, key)) + s.Require().Equal(value1, db.GetState(address, key)) db.RevertToSnapshot(rev1) - suite.Require().Equal(common.Hash{}, db.GetState(address, key)) + s.Require().Equal(common.Hash{}, db.GetState(address, key)) } -func (suite *StateDBTestSuite) TestInvalidSnapshotId() { - db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) - suite.Require().Panics(func() { +func (s *Suite) TestInvalidSnapshotId() { + deps := evmtest.NewTestDeps() + db := deps.StateDB() + + s.Require().Panics(func() { db.RevertToSnapshot(1) }) } -func (suite *StateDBTestSuite) TestAccessList() { +func (s *Suite) TestAccessList() { value1 := common.BigToHash(big.NewInt(1)) value2 := common.BigToHash(big.NewInt(2)) @@ -402,38 +440,38 @@ func (suite *StateDBTestSuite) TestAccessList() { malleate func(vm.StateDB) }{ {"add address", func(db vm.StateDB) { - suite.Require().False(db.AddressInAccessList(address)) + s.Require().False(db.AddressInAccessList(address)) db.AddAddressToAccessList(address) - suite.Require().True(db.AddressInAccessList(address)) + s.Require().True(db.AddressInAccessList(address)) addrPresent, slotPresent := db.SlotInAccessList(address, value1) - suite.Require().True(addrPresent) - suite.Require().False(slotPresent) + s.Require().True(addrPresent) + s.Require().False(slotPresent) // add again, should be no-op db.AddAddressToAccessList(address) - suite.Require().True(db.AddressInAccessList(address)) + s.Require().True(db.AddressInAccessList(address)) }}, {"add slot", func(db vm.StateDB) { addrPresent, slotPresent := db.SlotInAccessList(address, value1) - suite.Require().False(addrPresent) - suite.Require().False(slotPresent) + s.Require().False(addrPresent) + s.Require().False(slotPresent) db.AddSlotToAccessList(address, value1) addrPresent, slotPresent = db.SlotInAccessList(address, value1) - suite.Require().True(addrPresent) - suite.Require().True(slotPresent) + s.Require().True(addrPresent) + s.Require().True(slotPresent) // add another slot db.AddSlotToAccessList(address, value2) addrPresent, slotPresent = db.SlotInAccessList(address, value2) - suite.Require().True(addrPresent) - suite.Require().True(slotPresent) + s.Require().True(addrPresent) + s.Require().True(slotPresent) // add again, should be noop db.AddSlotToAccessList(address, value2) addrPresent, slotPresent = db.SlotInAccessList(address, value2) - suite.Require().True(addrPresent) - suite.Require().True(slotPresent) + s.Require().True(addrPresent) + s.Require().True(slotPresent) }}, {"prepare access list", func(db vm.StateDB) { al := gethcore.AccessList{{ @@ -444,68 +482,79 @@ func (suite *StateDBTestSuite) TestAccessList() { db.PrepareAccessList(address, &address2, vm.PrecompiledAddressesBerlin, al) // check sender and dst - suite.Require().True(db.AddressInAccessList(address)) - suite.Require().True(db.AddressInAccessList(address2)) + s.Require().True(db.AddressInAccessList(address)) + s.Require().True(db.AddressInAccessList(address2)) // check precompiles - suite.Require().True(db.AddressInAccessList(common.BytesToAddress([]byte{1}))) + s.Require().True(db.AddressInAccessList(common.BytesToAddress([]byte{1}))) // check AccessList - suite.Require().True(db.AddressInAccessList(address3)) + s.Require().True(db.AddressInAccessList(address3)) addrPresent, slotPresent := db.SlotInAccessList(address3, value1) - suite.Require().True(addrPresent) - suite.Require().True(slotPresent) + s.Require().True(addrPresent) + s.Require().True(slotPresent) addrPresent, slotPresent = db.SlotInAccessList(address3, value2) - suite.Require().True(addrPresent) - suite.Require().False(slotPresent) + s.Require().True(addrPresent) + s.Require().False(slotPresent) }}, } for _, tc := range testCases { - db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) + deps := evmtest.NewTestDeps() + db := deps.StateDB() tc.malleate(db) } } -func (suite *StateDBTestSuite) TestLog() { +func (s *Suite) TestLog() { txHash := common.BytesToHash([]byte("tx")) + // use a non-default tx config + const ( + blockNumber = uint64(1) + txIdx = uint(1) + logIdx = uint(1) + ) txConfig := statedb.NewTxConfig( blockHash, txHash, - 1, 1, + txIdx, logIdx, ) - db := statedb.New(sdk.Context{}, NewMockKeeper(), txConfig) - data := []byte("hello world") - db.AddLog(&gethcore.Log{ - Address: address, - Topics: []common.Hash{}, - Data: data, - BlockNumber: 1, - }) - suite.Require().Equal(1, len(db.Logs())) - expecedLog := &gethcore.Log{ - Address: address, - Topics: []common.Hash{}, - Data: data, - BlockNumber: 1, - BlockHash: blockHash, - TxHash: txHash, - TxIndex: 1, - Index: 1, - } - suite.Require().Equal(expecedLog, db.Logs()[0]) - db.AddLog(&gethcore.Log{ + deps := evmtest.NewTestDeps() + db := statedb.New(deps.Ctx, &deps.Chain.EvmKeeper, txConfig) + + logData := []byte("hello world") + log := &gethcore.Log{ Address: address, Topics: []common.Hash{}, - Data: data, - BlockNumber: 1, - }) - suite.Require().Equal(2, len(db.Logs())) - expecedLog.Index++ - suite.Require().Equal(expecedLog, db.Logs()[1]) + Data: logData, + BlockNumber: blockNumber, + } + db.AddLog(log) + s.Require().Equal(1, len(db.Logs())) + + wantLog := &gethcore.Log{ + Address: log.Address, + Topics: log.Topics, + Data: log.Data, + BlockNumber: log.BlockNumber, + + // New fields + BlockHash: blockHash, + TxHash: txHash, + TxIndex: txIdx, + Index: logIdx, + } + s.Require().Equal(wantLog, db.Logs()[0]) + + // Add a second log and assert values + db.AddLog(log) + wantLog.Index++ + s.Require().Equal(2, len(db.Logs())) + gotLog := db.Logs()[1] + s.Require().Equal(wantLog, gotLog) } -func (suite *StateDBTestSuite) TestRefund() { +func (s *Suite) TestRefund() { testCases := []struct { name string malleate func(vm.StateDB) @@ -525,37 +574,45 @@ func (suite *StateDBTestSuite) TestRefund() { }, 0, true}, } for _, tc := range testCases { - db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) + deps := evmtest.NewTestDeps() + db := deps.StateDB() if !tc.expPanic { tc.malleate(db) - suite.Require().Equal(tc.expRefund, db.GetRefund()) + s.Require().Equal(tc.expRefund, db.GetRefund()) } else { - suite.Require().Panics(func() { + s.Require().Panics(func() { tc.malleate(db) }) } } } -func (suite *StateDBTestSuite) TestIterateStorage() { +func (s *Suite) TestIterateStorage() { key1 := common.BigToHash(big.NewInt(1)) value1 := common.BigToHash(big.NewInt(2)) key2 := common.BigToHash(big.NewInt(3)) value2 := common.BigToHash(big.NewInt(4)) - keeper := NewMockKeeper() - db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) + deps := evmtest.NewTestDeps() + db := deps.StateDB() db.SetState(address, key1, value1) db.SetState(address, key2, value2) // ForEachStorage only iterate committed state - suite.Require().Empty(CollectContractStorage(db)) + s.Require().Empty(CollectContractStorage(db)) - suite.Require().NoError(db.Commit()) + s.Require().NoError(db.Commit()) storage := CollectContractStorage(db) - suite.Require().Equal(2, len(storage)) - suite.Require().Equal(keeper.accounts[address].states, storage) + s.Require().Equal(2, len(storage)) + + keySet := set.New[common.Hash](key1, key2) + valSet := set.New[common.Hash](value1, value2) + for _, stateKey := range storage.SortedKeys() { + stateValue := deps.EvmKeeper.GetState(deps.Ctx, address, stateKey) + s.True(keySet.Has(stateKey)) + s.True(valSet.Has(stateValue)) + } // break early iteration storage = make(statedb.Storage) @@ -564,23 +621,6 @@ func (suite *StateDBTestSuite) TestIterateStorage() { // return false to break early return false }) - suite.Require().NoError(err) - suite.Require().Equal(1, len(storage)) -} - -func CollectContractStorage(db vm.StateDB) statedb.Storage { - storage := make(statedb.Storage) - err := db.ForEachStorage(address, func(k, v common.Hash) bool { - storage[k] = v - return true - }) - if err != nil { - return nil - } - - return storage -} - -func TestStateDBTestSuite(t *testing.T) { - suite.Run(t, &StateDBTestSuite{}) + s.Require().NoError(err) + s.Require().Equal(1, len(storage)) } diff --git a/x/evm/tx_data.go b/x/evm/tx_data.go index 24dde1e19..375eaf63d 100644 --- a/x/evm/tx_data.go +++ b/x/evm/tx_data.go @@ -40,6 +40,7 @@ type TxData interface { // static fee Fee() *big.Int + // Cost is the gas cost of the transaction in wei Cost() *big.Int // effective gasPrice/fee/cost according to current base fee