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 }