Skip to content

Commit

Permalink
fix(devnet-sdk): ensure balances are comparable
Browse files Browse the repository at this point in the history
In error cases, make sure we can still compare resulting balances
without crashing.

In particular, this makes interop_smoke_test skip properly if it can't
find a funded wallet to use.
  • Loading branch information
sigma committed Feb 10, 2025
1 parent 6b849f6 commit 48ac483
Show file tree
Hide file tree
Showing 4 changed files with 286 additions and 43 deletions.
5 changes: 3 additions & 2 deletions devnet-sdk/system/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package system
import (
"context"
"fmt"
"math/big"
"strings"

"github.com/ethereum-optimism/optimism/devnet-sdk/types"
Expand Down Expand Up @@ -52,12 +53,12 @@ func (w *wallet) SendETH(to types.Address, amount types.Balance) types.WriteInvo
func (w *wallet) Balance() types.Balance {
client, err := w.chain.getClient()
if err != nil {
return types.Balance{}
return types.NewBalance(new(big.Int))
}

balance, err := client.BalanceAt(context.Background(), w.address, nil)
if err != nil {
return types.Balance{}
return types.NewBalance(new(big.Int))
}

return types.NewBalance(balance)
Expand Down
103 changes: 103 additions & 0 deletions devnet-sdk/system/wallet_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package system

import (
"context"
"math/big"
"testing"

"github.com/ethereum-optimism/optimism/devnet-sdk/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

// balanceTestChain is a minimal interface for testing balance functionality
type balanceTestChain interface {
getClient() (*ethclient.Client, error)
}

// testWallet is a minimal wallet implementation for testing balance functionality
type testWallet struct {
privateKey types.Key
address types.Address
chain *mockChainForBalance // Use concrete type to access mock client directly
}

func (w *testWallet) Balance() types.Balance {
// Use the mock client directly instead of going through getClient()
balance, err := w.chain.client.BalanceAt(context.Background(), w.address, nil)
if err != nil {
return types.NewBalance(new(big.Int))
}

return types.NewBalance(balance)
}

// mockEthClient implements a mock ethereum client for testing
type mockEthClient struct {
mock.Mock
}

func (m *mockEthClient) BalanceAt(ctx context.Context, account types.Address, blockNumber *big.Int) (*big.Int, error) {
args := m.Called(ctx, account, blockNumber)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*big.Int), args.Error(1)
}

// mockChainForBalance implements just enough of the chain interface for balance testing
type mockChainForBalance struct {
mock.Mock
client *mockEthClient
}

func (m *mockChainForBalance) getClient() (*ethclient.Client, error) {
args := m.Called()
return nil, args.Error(1)
}

func TestWalletBalance(t *testing.T) {
tests := []struct {
name string
setupMock func(*mockChainForBalance)
expectedValue *big.Int
}{
{
name: "successful balance fetch",
setupMock: func(m *mockChainForBalance) {
balance := big.NewInt(1000000000000000000) // 1 ETH
m.client.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Return(balance, nil)
},
expectedValue: big.NewInt(1000000000000000000),
},
{
name: "balance fetch error returns zero",
setupMock: func(m *mockChainForBalance) {
m.client.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Return(nil, assert.AnError)
},
expectedValue: new(big.Int),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockChain := &mockChainForBalance{
client: new(mockEthClient),
}
tt.setupMock(mockChain)

w := &testWallet{
privateKey: "test-key",
address: types.Address{},
chain: mockChain,
}

balance := w.Balance()
assert.Equal(t, 0, balance.Int.Cmp(tt.expectedValue))

mockChain.AssertExpectations(t)
mockChain.client.AssertExpectations(t)
})
}
}
20 changes: 19 additions & 1 deletion devnet-sdk/types/balance.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,34 @@ func (b Balance) Mul(f float64) Balance {

// GreaterThan returns true if this balance is greater than other
func (b Balance) GreaterThan(other Balance) bool {
if b.Int == nil {
return false
}
if other.Int == nil {
return true
}
return b.Int.Cmp(other.Int) > 0
}

// LessThan returns true if this balance is less than other
func (b Balance) LessThan(other Balance) bool {
if b.Int == nil {
return other.Int != nil
}
if other.Int == nil {
return false
}
return b.Int.Cmp(other.Int) < 0
}

// Equal returns true if this balance equals other
func (b Balance) Equal(other Balance) bool {
if b.Int == nil {
return other.Int == nil
}
if other.Int == nil {
return false
}
return b.Int.Cmp(other.Int) == 0
}

Expand All @@ -59,7 +77,7 @@ func (b Balance) LogValue() slog.Value {

// 1 ETH = 1e18 Wei
if eth.Cmp(new(big.Float).SetFloat64(0.001)) >= 0 {
str := eth.Text('g', 3)
str := eth.Text('f', 0)
return slog.StringValue(fmt.Sprintf("%s ETH", str))
}

Expand Down
Loading

0 comments on commit 48ac483

Please sign in to comment.