Skip to content

Commit

Permalink
feat(evm): use atto denomination for the wei units in the EVM so that…
Browse files Browse the repository at this point in the history
… 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
  • Loading branch information
Unique-Divine authored Aug 6, 2024
1 parent d2601f1 commit cbb14ba
Show file tree
Hide file tree
Showing 38 changed files with 1,130 additions and 652 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 3 additions & 1 deletion app/evmante/evmante_can_transfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
)
}
Expand Down
12 changes: 9 additions & 3 deletions app/evmante/evmante_can_transfer_test.go
Original file line number Diff line number Diff line change
@@ -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"
)
Expand All @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions app/evmante/evmante_gas_consume_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
7 changes: 5 additions & 2 deletions app/evmante/evmante_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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.
Expand Down
5 changes: 3 additions & 2 deletions app/evmante/evmante_verify_eth_acc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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")
}
}
Expand Down
2 changes: 1 addition & 1 deletion app/evmante/evmante_verify_eth_acc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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: "",
Expand Down
25 changes: 18 additions & 7 deletions e2e/evm/test/basic_queries.test.ts
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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)
})
67 changes: 48 additions & 19 deletions e2e/evm/test/contract_send_nibi.test.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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)

})
31 changes: 20 additions & 11 deletions eth/eth_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}
Expand All @@ -46,23 +55,23 @@ 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
}

// 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
}
Expand All @@ -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(),
}
}
Loading

0 comments on commit cbb14ba

Please sign in to comment.