diff --git a/paychmgr/paychvoucherfunds_test.go b/paychmgr/paychvoucherfunds_test.go new file mode 100644 index 000000000..735ea54ac --- /dev/null +++ b/paychmgr/paychvoucherfunds_test.go @@ -0,0 +1,107 @@ +package paychmgr + +import ( + "context" + "testing" + + "github.com/filecoin-project/lotus/chain/types" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/specs-actors/actors/builtin" + "github.com/filecoin-project/specs-actors/actors/builtin/account" + paych "github.com/filecoin-project/specs-actors/actors/builtin/paych" + tutils "github.com/filecoin-project/specs-actors/support/testing" + "github.com/ipfs/go-cid" + ds "github.com/ipfs/go-datastore" + ds_sync "github.com/ipfs/go-datastore/sync" + "github.com/stretchr/testify/require" +) + +// TestPaychAddVoucherAfterAddFunds tests adding a voucher to a channel with +// insufficient funds, then adding funds to the channel, then adding the +// voucher again +func TestPaychAddVoucherAfterAddFunds(t *testing.T) { + ctx := context.Background() + store := NewStore(ds_sync.MutexWrap(ds.NewMapDatastore())) + + fromKeyPrivate, fromKeyPublic := testGenerateKeyPair(t) + ch := tutils.NewIDAddr(t, 100) + from := tutils.NewSECP256K1Addr(t, string(fromKeyPublic)) + to := tutils.NewSECP256K1Addr(t, "secpTo") + fromAcct := tutils.NewActorAddr(t, "fromAct") + toAcct := tutils.NewActorAddr(t, "toAct") + + mock := newMockManagerAPI() + defer mock.close() + + // Add the from signing key to the wallet + mock.setAccountState(fromAcct, account.State{Address: from}) + mock.setAccountState(toAcct, account.State{Address: to}) + mock.addSigningKey(fromKeyPrivate) + + mgr, err := newManager(store, mock) + require.NoError(t, err) + + // Send create message for a channel with value 10 + createAmt := big.NewInt(10) + _, createMsgCid, err := mgr.GetPaych(ctx, from, to, createAmt) + require.NoError(t, err) + + // Send create channel response + response := testChannelResponse(t, ch) + mock.receiveMsgResponse(createMsgCid, response) + + // Create an actor in state for the channel with the initial channel balance + act := &types.Actor{ + Code: builtin.AccountActorCodeID, + Head: cid.Cid{}, + Nonce: 0, + Balance: createAmt, + } + mock.setPaychState(ch, act, paych.State{ + From: fromAcct, + To: toAcct, + SettlingAt: abi.ChainEpoch(0), + MinSettleHeight: abi.ChainEpoch(0), + }) + + // Wait for create response to be processed by manager + _, err = mgr.GetPaychWaitReady(ctx, createMsgCid) + require.NoError(t, err) + + // Create a voucher with a value equal to the channel balance + voucher := paych.SignedVoucher{Amount: createAmt, Lane: 1} + res, err := mgr.CreateVoucher(ctx, ch, voucher) + require.NoError(t, err) + require.NotNil(t, res.Voucher) + + // Create a voucher in a different lane with an amount that exceeds the + // channel balance + excessAmt := types.NewInt(5) + voucher = paych.SignedVoucher{Amount: excessAmt, Lane: 2} + res, err = mgr.CreateVoucher(ctx, ch, voucher) + require.NoError(t, err) + require.Nil(t, res.Voucher) + require.Equal(t, res.Shortfall, excessAmt) + + // Add funds so as to cover the voucher shortfall + _, addFundsMsgCid, err := mgr.GetPaych(ctx, from, to, excessAmt) + require.NoError(t, err) + + // Trigger add funds confirmation + mock.receiveMsgResponse(addFundsMsgCid, types.MessageReceipt{ExitCode: 0}) + + // Update actor test case balance to reflect added funds + act.Balance = types.BigAdd(createAmt, excessAmt) + + // Wait for add funds confirmation to be processed by manager + _, err = mgr.GetPaychWaitReady(ctx, addFundsMsgCid) + require.NoError(t, err) + + // Adding same voucher that previously exceeded channel balance + // should succeed now that the channel balance has been increased + res, err = mgr.CreateVoucher(ctx, ch, voucher) + require.NoError(t, err) + require.NotNil(t, res.Voucher) +}