Skip to content

Commit

Permalink
feat(evm): Combine both account queries into "/eth.evm.v1.Query/EthAc…
Browse files Browse the repository at this point in the history
…count", accepting both nibi-prefixed Bech32 addresses and Ethereum-type hexadecimal addresses as input. (#1986)

* 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

* Squashed commit of the following: merge ud/attonibi

commit e171a5e
Merge: 4419067 3c73f03
Author: Unique-Divine <[email protected]>
Date:   Mon Aug 5 06:01:44 2024 -0500

    Merge branch 'main' into ud/attonibi

commit 4419067
Author: Unique-Divine <[email protected]>
Date:   Mon Aug 5 03:04:46 2024 +0200

    test(statedb_test.go): more thorough test cases

commit 18995b6
Author: Unique-Divine <[email protected]>
Date:   Mon Aug 5 01:25:14 2024 +0200

    test(statedb): complete the wei-based account migration. Remove all mocks

commit 9cd1edf
Author: Unique-Divine <[email protected]>
Date:   Sun Aug 4 04:32:31 2024 +0200

    chore: wei unit migration

commit cd9642b
Author: Unique-Divine <[email protected]>
Date:   Sun Aug 4 04:27:33 2024 +0200

    math functions for unibi and wei

commit a1c5782
Author: Unique-Divine <[email protected]>
Date:   Sun Aug 4 04:26:58 2024 +0200

    refactor(statedb): separate Account and AccountWei to have state objects manipulate in wei units

commit e106002
Author: Unique-Divine <[email protected]>
Date:   Tue Jul 30 02:25:34 2024 +0100

    changelog

commit 3335463
Author: Unique-Divine <[email protected]>
Date:   Tue Jul 30 02:23:33 2024 +0100

    refactor: use pebbledb as the test db

commit 1f0d8d0
Author: Unique-Divine <[email protected]>
Date:   Tue Jul 30 01:51:38 2024 +0100

    refactor: remove unused vars. improve error clarity for testnetwork/New

* address conversion fns and tests

* feat(evm): Combine both account queries into "/eth.evm.v1.Query/EthAccount", accepting both nibi-prefixed Bech32 addresses and Ethereum-type hexadecimal addresses as input.

* fix(evmante): check balance in wei, deduct unibi gas

* fix(e2e): JavaScript BigInt is a joke

* 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 cbb14ba commit 7dd9dd4
Show file tree
Hide file tree
Showing 13 changed files with 343 additions and 837 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [#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.
=======
- [#1986](https://github.com/NibiruChain/nibiru/pull/1986) - feat(evm): Combine both account queries into "/eth.evm.v1.Query/EthAccount", accepting both nibi-prefixed Bech32 addresses and Ethereum-type hexadecimal addresses as input.

#### Dapp modules: perp, spot, oracle, etc

Expand Down
30 changes: 15 additions & 15 deletions e2e/evm/test/contract_send_nibi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,88 +11,88 @@ describe("Send NIBI via smart contract", async () => {

it("should send via transfer method", async () => {
const recipient = Wallet.createRandom()
const transferValue = toBigInt(5e12) * toBigInt(1e6) // 5 micro NIBI
const weiToSend = toBigInt(5e12) * toBigInt(1e6) // 5 micro NIBI

const ownerBalanceBefore = await provider.getBalance(account) // NIBI
const recipientBalanceBefore = await provider.getBalance(recipient) // NIBI
expect(recipientBalanceBefore).toEqual(BigInt(0))

const tx = await contract.sendViaTransfer(recipient, {
value: transferValue,
value: weiToSend,
})
const receipt = await tx.wait(1, 5e3)

// Assert balances with logging
const tenPow12 = toBigInt(1e12)
const txCostMicronibi = transferValue / tenPow12 + receipt.gasUsed
const txCostMicronibi = weiToSend / tenPow12 + receipt.gasUsed
const txCostWei = txCostMicronibi * tenPow12
const expectedOwnerWei = ownerBalanceBefore - txCostWei
console.debug("DEBUG should send via transfer method %o:", {
ownerBalanceBefore,
transferValue,
weiToSend,
gasUsed: receipt.gasUsed,
gasPrice: `${receipt.gasPrice.toString()} micronibi`,
expectedOwnerWei,
})
expect(provider.getBalance(account)).resolves.toBe(expectedOwnerWei)
expect(provider.getBalance(recipient)).resolves.toBe(transferValue)
expect(provider.getBalance(recipient)).resolves.toBe(weiToSend)
}, 20e3)

it("should send via send method", async () => {
const recipient = Wallet.createRandom()
const transferValue = toBigInt(100e12) * toBigInt(1e6) // 100 NIBi
const weiToSend = toBigInt(100e12) * toBigInt(1e6) // 100 NIBi

const ownerBalanceBefore = await provider.getBalance(account) // NIBI
const recipientBalanceBefore = await provider.getBalance(recipient) // NIBI
expect(recipientBalanceBefore).toEqual(BigInt(0))

const tx = await contract.sendViaSend(recipient, {
value: transferValue,
value: weiToSend,
})
const receipt = await tx.wait(1, 5e3)

// Assert balances with logging
const tenPow12 = toBigInt(1e12)
const txCostMicronibi = transferValue / tenPow12 + receipt.gasUsed
const txCostMicronibi = weiToSend / tenPow12 + receipt.gasUsed
const txCostWei = txCostMicronibi * tenPow12
const expectedOwnerWei = ownerBalanceBefore - txCostWei
console.debug("DEBUG send via send method %o:", {
ownerBalanceBefore,
transferValue,
weiToSend,
gasUsed: receipt.gasUsed,
gasPrice: `${receipt.gasPrice.toString()} micronibi`,
expectedOwnerWei,
})
expect(provider.getBalance(account)).resolves.toBe(expectedOwnerWei)
expect(provider.getBalance(recipient)).resolves.toBe(transferValue)
expect(provider.getBalance(recipient)).resolves.toBe(weiToSend)
}, 20e3)

it("should send via transfer method", async () => {
const recipient = Wallet.createRandom()
const transferValue = toBigInt(100e12) * toBigInt(1e6) // 100 NIBI
const weiToSend = toBigInt(100e12) * toBigInt(1e6) // 100 NIBI

const ownerBalanceBefore = await provider.getBalance(account) // NIBI
const recipientBalanceBefore = await provider.getBalance(recipient) // NIBI
expect(recipientBalanceBefore).toEqual(BigInt(0))

const tx = await contract.sendViaCall(recipient, {
value: transferValue,
value: weiToSend,
})
const receipt = await tx.wait(1, 5e3)

// Assert balances with logging
const tenPow12 = toBigInt(1e12)
const txCostMicronibi = transferValue / tenPow12 + receipt.gasUsed
const txCostMicronibi = weiToSend / tenPow12 + receipt.gasUsed
const txCostWei = txCostMicronibi * tenPow12
const expectedOwnerWei = ownerBalanceBefore - txCostWei
console.debug("DEBUG should send via transfer method %o:", {
ownerBalanceBefore,
transferValue,
weiToSend,
gasUsed: receipt.gasUsed,
gasPrice: `${receipt.gasPrice.toString()} micronibi`,
expectedOwnerWei,
})
expect(provider.getBalance(account)).resolves.toBe(expectedOwnerWei)
expect(provider.getBalance(recipient)).resolves.toBe(transferValue)
expect(provider.getBalance(recipient)).resolves.toBe(weiToSend)
}, 20e3)
})
32 changes: 0 additions & 32 deletions eth/rpc/backend/mocks/evm_query_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 17 additions & 32 deletions proto/eth/evm/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,12 @@ option go_package = "github.com/NibiruChain/nibiru/x/evm";

// Query defines the gRPC querier service.
service Query {
// EthAccount queries an Ethereum account.
// EthAccount queries a Nibiru account using its EVM address or Bech32 Nibiru
// address.
rpc EthAccount(QueryEthAccountRequest) returns (QueryEthAccountResponse) {
option (google.api.http).get = "/nibiru/evm/v1/eth_account/{address}";
}

// NibiruAccount queries the Bech32 Nibiru address corresponding to Nibiru EVM account.
rpc NibiruAccount(QueryNibiruAccountRequest) returns (QueryNibiruAccountResponse) {
option (google.api.http).get = "/nibiru/evm/v1/nibiru_account/{address}";
}

// ValidatorAccount queries an Ethereum account's from a validator consensus
// Address.
rpc ValidatorAccount(QueryValidatorAccountRequest) returns (QueryValidatorAccountResponse) {
Expand Down Expand Up @@ -86,39 +82,28 @@ message QueryEthAccountRequest {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

// address is the ethereum hex address to query the account for.
// address is the Ethereum hex address or nibi Bech32 address to query the account for.
string address = 1;
}

// QueryEthAccountResponse is the response type for the Query/Account RPC method.
// QueryEthAccountResponse is the response type for the Query/EthAccount RPC method.
message QueryEthAccountResponse {
// balance is the balance of unibi (micronibi).
string balance = 1;
// balance_wei is the balance of wei (attoether, where NIBI is ether).
string balance_wei = 1;
string balance_wei = 2;
// code_hash is the hex-formatted code bytes from the EOA.
string code_hash = 2;
string code_hash = 3;
// nonce is the account's sequence number.
uint64 nonce = 3;
}
uint64 nonce = 4;

// QueryNibiruAccountRequest is the request type for the Query/NibiruAccount RPC
// method.
message QueryNibiruAccountRequest {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;
// eth_address: The hexadecimal-encoded string representing the 20 byte address
// of a Nibiru EVM account.
string eth_address = 5;

// address is the ethereum hex address to query the account for.
string address = 1;
}

// QueryNibiruAccountResponse is the response type for the Query/NibiruAccount
// RPC method.
message QueryNibiruAccountResponse {
// Nibiru bech32 account "address"
string address = 1;
// sequence is the account's sequence number.
uint64 sequence = 2;
// account_number is the account number
uint64 account_number = 3;
// bech32_address is the nibi-prefixed address of the account that can receive
// bank transfers ("cosmos.bank.v1beta1.MsgSend").
string bech32_address = 6;
}

// QueryValidatorAccountRequest is the request type for the
Expand Down Expand Up @@ -248,10 +233,10 @@ message QueryTraceTxRequest {
reserved 2;
reserved "tx_index";
// trace_config holds extra parameters to trace functions.
TraceConfig trace_config = 3;
eth.evm.v1.TraceConfig trace_config = 3;
// predecessors is an array of transactions included in the same block
// need to be replayed first to get correct context for tracing.
repeated MsgEthereumTx predecessors = 4;
repeated eth.evm.v1.MsgEthereumTx predecessors = 4;
// block_number of requested transaction
int64 block_number = 5;
// block_hash of requested transaction
Expand Down
11 changes: 3 additions & 8 deletions x/evm/evm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,17 +129,12 @@ func (s *TestSuite) TestModuleAddressEVM() {
// EVM module should have mint perms
deps := evmtest.NewTestDeps()
{
_, err := deps.EvmKeeper.EthAccount(deps.GoCtx(), &evm.QueryEthAccountRequest{
resp, err := deps.EvmKeeper.EthAccount(deps.GoCtx(), &evm.QueryEthAccountRequest{
Address: evmModuleAddr.Hex(),
})
s.NoError(err)
}
{
resp, err := deps.EvmKeeper.NibiruAccount(deps.GoCtx(), &evm.QueryNibiruAccountRequest{
Address: evmModuleAddr.Hex(),
})
s.NoError(err)
s.Equal(nibiAddr.String(), resp.Address)
s.Equal(nibiAddr.String(), resp.Bech32Address)
s.Equal(evmModuleAddr.String(), resp.EthAddress)
}
}

Expand Down
4 changes: 0 additions & 4 deletions x/evm/evmtest/eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,6 @@ func NewEthAccInfo() EthPrivKeyAcc {
}
}

func EthAddrToNibiruAddr(ethAddr gethcommon.Address) sdk.AccAddress {
return ethAddr.Bytes()
}

type EthPrivKeyAcc struct {
EthAddr gethcommon.Address
NibiruAddr sdk.AccAddress
Expand Down
2 changes: 1 addition & 1 deletion x/evm/keeper/gas_fees.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func CheckSenderBalance(
if balanceWei.Cmp(big.NewInt(0)) < 0 || balanceWei.Cmp(cost) < 0 {
return errors.Wrapf(
errortypes.ErrInsufficientFunds,
"sender balance < tx cost (%s < %s)", balanceWei, txData.Cost(),
"sender balance < tx cost (%s < %s)", balanceWei, cost,
)
}
return nil
Expand Down
68 changes: 23 additions & 45 deletions x/evm/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,65 +37,43 @@ import (
var _ evm.QueryServer = &Keeper{}

// EthAccount: Implements the gRPC query for "/eth.evm.v1.Query/EthAccount".
// EthAccount retrieves the account details for a given Ethereum hex address.
// EthAccount retrieves the account and balance details for an account with the
// given address.
//
// Parameters:
// - goCtx: The context.Context object representing the request context.
// - req: Request containing the Ethereum hexadecimal address.
//
// Returns:
// - A pointer to the QueryEthAccountResponse object containing the account details.
// - An error if the account retrieval process encounters any issues.
// - req: Request containing the address in either Ethereum hexadecimal or
// Bech32 format.
func (k Keeper) EthAccount(
goCtx context.Context, req *evm.QueryEthAccountRequest,
) (*evm.QueryEthAccountResponse, error) {
if err := req.Validate(); err != nil {
isBech32, err := req.Validate()
if err != nil {
return nil, err
}

addr := gethcommon.HexToAddress(req.Address)
ctx := sdk.UnwrapSDKContext(goCtx)
acct := k.GetAccountOrEmpty(ctx, addr)

return &evm.QueryEthAccountResponse{
BalanceWei: evm.NativeToWei(acct.BalanceNative).String(),
CodeHash: gethcommon.BytesToHash(acct.CodeHash).Hex(),
Nonce: acct.Nonce,
}, nil
}
var addrEth gethcommon.Address
var addrBech32 sdk.AccAddress

// NibiruAccount: Implements the gRPC query for "/eth.evm.v1.Query/NibiruAccount".
// NibiruAccount retrieves the Cosmos account details for a given Ethereum address.
//
// Parameters:
// - goCtx: The context.Context object representing the request context.
// - req: The QueryNibiruAccountRequest object containing the Ethereum address.
//
// Returns:
// - A pointer to the QueryNibiruAccountResponse object containing the Cosmos account details.
// - An error if the account retrieval process encounters any issues.
func (k Keeper) NibiruAccount(
goCtx context.Context, req *evm.QueryNibiruAccountRequest,
) (resp *evm.QueryNibiruAccountResponse, err error) {
if err := req.Validate(); err != nil {
return resp, err
if isBech32 {
addrBech32 = sdk.MustAccAddressFromBech32(req.Address)
addrEth = eth.NibiruAddrToEthAddr(addrBech32)
} else {
addrEth = gethcommon.HexToAddress(req.Address)
addrBech32 = eth.EthAddrToNibiruAddr(addrEth)
}

ctx := sdk.UnwrapSDKContext(goCtx)
ethAddr := gethcommon.HexToAddress(req.Address)
nibiruAddr := sdk.AccAddress(ethAddr.Bytes())
acct := k.GetAccountOrEmpty(ctx, addrEth)

accountOrNil := k.accountKeeper.GetAccount(ctx, nibiruAddr)
resp = &evm.QueryNibiruAccountResponse{
Address: nibiruAddr.String(),
}

if accountOrNil != nil {
resp.Sequence = accountOrNil.GetSequence()
resp.AccountNumber = accountOrNil.GetAccountNumber()
}

return resp, nil
return &evm.QueryEthAccountResponse{
Balance: acct.BalanceNative.String(),
BalanceWei: evm.NativeToWei(acct.BalanceNative).String(),
CodeHash: gethcommon.BytesToHash(acct.CodeHash).Hex(),
Nonce: acct.Nonce,
EthAddress: addrEth.Hex(),
Bech32Address: addrBech32.String(),
}, nil
}

// ValidatorAccount: Implements the gRPC query for
Expand Down
Loading

0 comments on commit 7dd9dd4

Please sign in to comment.