github.com/filecoin-project/specs-actors/v4@v4.0.2/actors/util/adt/balancetable_test.go (about)

     1  package adt_test
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/filecoin-project/go-address"
     7  	"github.com/filecoin-project/go-state-types/abi"
     8  	"github.com/filecoin-project/go-state-types/big"
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/filecoin-project/specs-actors/v4/actors/builtin"
    13  	"github.com/filecoin-project/specs-actors/v4/actors/util/adt"
    14  	"github.com/filecoin-project/specs-actors/v4/support/mock"
    15  	tutil "github.com/filecoin-project/specs-actors/v4/support/testing"
    16  )
    17  
    18  func TestBalanceTable(t *testing.T) {
    19  	buildBalanceTable := func() *adt.BalanceTable {
    20  		rt := mock.NewBuilder(address.Undef).Build(t)
    21  		store := adt.AsStore(rt)
    22  		emptyMap, err := adt.MakeEmptyMap(store, builtin.DefaultHamtBitwidth)
    23  		require.NoError(t, err)
    24  
    25  		bt, err := adt.AsBalanceTable(store, tutil.MustRoot(t, emptyMap))
    26  		require.NoError(t, err)
    27  		return bt
    28  	}
    29  
    30  	t.Run("Add adds or creates", func(t *testing.T) {
    31  		addr := tutil.NewIDAddr(t, 100)
    32  		bt := buildBalanceTable()
    33  
    34  		prev, err := bt.Get(addr)
    35  		assert.NoError(t, err)
    36  		assert.Equal(t, big.Zero(), prev)
    37  
    38  		err = bt.Add(addr, abi.NewTokenAmount(10))
    39  		assert.NoError(t, err)
    40  		amount, err := bt.Get(addr)
    41  		assert.NoError(t, err)
    42  		assert.Equal(t, abi.NewTokenAmount(10), amount)
    43  
    44  		err = bt.Add(addr, abi.NewTokenAmount(20))
    45  		assert.NoError(t, err)
    46  		amount, err = bt.Get(addr)
    47  		assert.NoError(t, err)
    48  		assert.Equal(t, abi.NewTokenAmount(30), amount)
    49  
    50  		// Add negative to subtract.
    51  		err = bt.Add(addr, abi.NewTokenAmount(-30))
    52  		assert.NoError(t, err)
    53  		amount, err = bt.Get(addr)
    54  		assert.NoError(t, err)
    55  		assert.Equal(t, abi.NewTokenAmount(0), amount)
    56  		// The zero entry is not stored.
    57  		found, err := ((*adt.Map)(bt)).Get(abi.AddrKey(addr), nil)
    58  		require.NoError(t, err)
    59  		require.False(t, found)
    60  	})
    61  
    62  	t.Run("Must subtract fails if account balance is insufficient", func(t *testing.T) {
    63  		addr := tutil.NewIDAddr(t, 100)
    64  		bt := buildBalanceTable()
    65  
    66  		// Ok to subtract zero from nothing
    67  		require.NoError(t, bt.MustSubtract(addr, abi.NewTokenAmount(0)))
    68  
    69  		// Fail to subtract something from nothing
    70  		require.Error(t, bt.MustSubtract(addr, abi.NewTokenAmount(1)))
    71  
    72  		require.NoError(t, bt.Add(addr, abi.NewTokenAmount(5)))
    73  
    74  		// Fail to subtract more than available
    75  		require.Error(t, bt.MustSubtract(addr, abi.NewTokenAmount(6)))
    76  		bal, err := bt.Get(addr)
    77  		require.NoError(t, err)
    78  		require.Equal(t, abi.NewTokenAmount(5), bal)
    79  
    80  		// Ok to subtract less than available
    81  		require.NoError(t, bt.MustSubtract(addr, abi.NewTokenAmount(4)))
    82  		bal, err = bt.Get(addr)
    83  		require.NoError(t, err)
    84  		require.Equal(t, abi.NewTokenAmount(1), bal)
    85  		// ...and the rest
    86  		require.NoError(t, bt.MustSubtract(addr, abi.NewTokenAmount(1)))
    87  		bal, err = bt.Get(addr)
    88  		require.NoError(t, err)
    89  		require.Equal(t, abi.NewTokenAmount(0), bal)
    90  
    91  		// The zero entry is not stored.
    92  		found, err := ((*adt.Map)(bt)).Get(abi.AddrKey(addr), nil)
    93  		require.NoError(t, err)
    94  		require.False(t, found)
    95  	})
    96  
    97  	t.Run("Total returns total amount tracked", func(t *testing.T) {
    98  		addr1 := tutil.NewIDAddr(t, 100)
    99  		addr2 := tutil.NewIDAddr(t, 101)
   100  
   101  		bt := buildBalanceTable()
   102  		total, err := bt.Total()
   103  		assert.NoError(t, err)
   104  		assert.Equal(t, big.Zero(), total)
   105  
   106  		testCases := []struct {
   107  			amount int64
   108  			addr   address.Address
   109  			total  int64
   110  		}{
   111  			{10, addr1, 10},
   112  			{20, addr1, 30},
   113  			{40, addr2, 70},
   114  			{50, addr2, 120},
   115  		}
   116  
   117  		for _, tc := range testCases {
   118  			err = bt.Add(tc.addr, abi.NewTokenAmount(tc.amount))
   119  			assert.NoError(t, err)
   120  
   121  			total, err = bt.Total()
   122  			assert.NoError(t, err)
   123  			assert.Equal(t, abi.NewTokenAmount(tc.total), total)
   124  		}
   125  	})
   126  }
   127  
   128  func TestSubtractWithMinimum(t *testing.T) {
   129  	buildBalanceTable := func() *adt.BalanceTable {
   130  		rt := mock.NewBuilder(address.Undef).Build(t)
   131  		store := adt.AsStore(rt)
   132  		emptyMap, err := adt.MakeEmptyMap(store, builtin.DefaultHamtBitwidth)
   133  		require.NoError(t, err)
   134  
   135  		bt, err := adt.AsBalanceTable(store, tutil.MustRoot(t, emptyMap))
   136  		require.NoError(t, err)
   137  		return bt
   138  	}
   139  	addr := tutil.NewIDAddr(t, 100)
   140  	zeroAmt := abi.NewTokenAmount(0)
   141  
   142  	t.Run("ok with zero balance", func(t *testing.T) {
   143  		bt := buildBalanceTable()
   144  		s, err := bt.SubtractWithMinimum(addr, zeroAmt, zeroAmt)
   145  		require.NoError(t, err)
   146  		require.EqualValues(t, zeroAmt, s)
   147  	})
   148  
   149  	t.Run("withdraw available when account does not have sufficient balance", func(t *testing.T) {
   150  		bt := buildBalanceTable()
   151  		require.NoError(t, bt.Add(addr, abi.NewTokenAmount(5)))
   152  
   153  		s, err := bt.SubtractWithMinimum(addr, abi.NewTokenAmount(2), abi.NewTokenAmount(4))
   154  		require.NoError(t, err)
   155  		require.EqualValues(t, abi.NewTokenAmount(1), s)
   156  
   157  		remaining, err := bt.Get(addr)
   158  		require.NoError(t, err)
   159  		require.EqualValues(t, abi.NewTokenAmount(4), remaining)
   160  	})
   161  
   162  	t.Run("account has sufficient balance", func(t *testing.T) {
   163  		bt := buildBalanceTable()
   164  		require.NoError(t, bt.Add(addr, abi.NewTokenAmount(5)))
   165  
   166  		s, err := bt.SubtractWithMinimum(addr, abi.NewTokenAmount(3), abi.NewTokenAmount(2))
   167  		require.NoError(t, err)
   168  		require.EqualValues(t, abi.NewTokenAmount(3), s)
   169  
   170  		remaining, err := bt.Get(addr)
   171  		require.NoError(t, err)
   172  		require.EqualValues(t, abi.NewTokenAmount(2), remaining)
   173  	})
   174  }