diff --git a/client/asset/eth/eth.go b/client/asset/eth/eth.go index f14abf8ee5..166b334f57 100644 --- a/client/asset/eth/eth.go +++ b/client/asset/eth/eth.go @@ -34,6 +34,9 @@ const ( BipID = 60 defaultGasFee = 8.2e10 defaultGasFeeLimit = 2e11 + + // Eth balances are floored as gwei, or 1e9 wei. + gweiFactor = 1e9 ) var ( @@ -67,12 +70,13 @@ var ( // WalletInfo defines some general information about a Ethereum wallet. WalletInfo = &asset.WalletInfo{ Name: "Ethereum", - Units: "wei", + Units: "gwei", DefaultConfigPath: defaultAppDir, // Incorrect if changed by user? ConfigOpts: configOpts, } notImplementedErr = errors.New("not implemented") mainnetContractAddr = common.HexToAddress("") + gweiFactorBig = big.NewInt(gweiFactor) ) // Check that Driver implements asset.Driver. @@ -224,19 +228,23 @@ func (eth *ExchangeWallet) OwnsAddress(address string) (bool, error) { // Balance returns the total available funds in the account. // -// NOTE: Ethereum balance does not return Immature or Locked values. +// NOTE: Ethereum Balance does not return Immature or Locked values. // -// TODO: Ethereum balances can easily go over the max value of a uint64. -// asset.Balance must be changed in a way to accomodate this. +// NOTE: The eth node returns balances in wei. Those are flored and stored as +// gwei, or 1e9 wei. func (eth *ExchangeWallet) Balance() (*asset.Balance, error) { eth.acctMtx.RLock() defer eth.acctMtx.RUnlock() - bigbal, err := eth.node.balance(eth.ctx, eth.acct) + bigBal, err := eth.node.balance(eth.ctx, eth.acct) if err != nil { return nil, err } + bigBal.Div(bigBal, gweiFactorBig) + if !bigBal.IsUint64() { + return nil, fmt.Errorf("balance %v gwei is too big for a uint64", bigBal) + } bal := &asset.Balance{ - Available: bigbal.Uint64(), + Available: bigBal.Uint64(), // Immature: , How to know? // Locked: , Not lockable? } diff --git a/client/asset/eth/eth_test.go b/client/asset/eth/eth_test.go index 6aa12d3c56..3ce219e825 100644 --- a/client/asset/eth/eth_test.go +++ b/client/asset/eth/eth_test.go @@ -29,6 +29,8 @@ type testNode struct { bestBlkHashErr error blk *types.Block blkErr error + bal *big.Int + balErr error } func (n *testNode) connect(ctx context.Context, node *node.Node, addr common.Address) error { @@ -45,7 +47,7 @@ func (n *testNode) accounts() []*accounts.Account { return nil } func (n *testNode) balance(ctx context.Context, acct *accounts.Account) (*big.Int, error) { - return nil, nil + return n.bal, n.balErr } func (n *testNode) sendToAddr(ctx context.Context, acct *accounts.Account, addr common.Address, amt, gasFee *big.Int) (common.Hash, error) { return common.Hash{}, nil @@ -177,3 +179,69 @@ func TestCheckForNewBlocks(t *testing.T) { } } + +func TestBalance(t *testing.T) { + maxInt := ^uint64(0) + maxWei := new(big.Int).SetUint64(maxInt) + maxWei.Mul(maxWei, gweiFactorBig) + overMaxWei := new(big.Int).Set(maxWei) + overMaxWei.Add(overMaxWei, gweiFactorBig) + tests := []struct { + name string + bal *big.Int + balErr error + wantBal uint64 + wantErr bool + }{{ + name: "ok zero", + bal: big.NewInt(0), + wantBal: 0, + }, { + name: "ok rounded down", + bal: big.NewInt(gweiFactor - 1), + wantBal: 0, + }, { + name: "ok one", + bal: big.NewInt(gweiFactor), + wantBal: 1, + }, { + name: "ok max int", + bal: maxWei, + wantBal: maxInt, + }, { + name: "over max int", + bal: overMaxWei, + wantErr: true, + }, { + name: "node balance error", + bal: big.NewInt(0), + balErr: errors.New(""), + wantErr: true, + }} + + for _, test := range tests { + ctx, cancel := context.WithCancel(context.Background()) + node := &testNode{} + node.bal = test.bal + node.balErr = test.balErr + eth := &ExchangeWallet{ + node: node, + ctx: ctx, + log: tLogger, + } + bal, err := eth.Balance() + cancel() + if test.wantErr { + if err == nil { + t.Fatalf("expected error for test %q", test.name) + } + continue + } + if err != nil { + t.Fatalf("unexpected error for test %q: %v", test.name, err) + } + if bal.Available != test.wantBal { + t.Fatalf("want available balance %v got %v for test %q", test.wantBal, bal.Available, test.name) + } + } +}