github.com/ethersphere/bee/v2@v2.2.0/pkg/settlement/swap/chequebook/cashout_test.go (about)

     1  // Copyright 2020 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package chequebook_test
     6  
     7  import (
     8  	"context"
     9  	"math/big"
    10  	"testing"
    11  
    12  	"github.com/ethereum/go-ethereum/common"
    13  	"github.com/ethereum/go-ethereum/core/types"
    14  	"github.com/ethersphere/bee/v2/pkg/settlement/swap/chequebook"
    15  	chequestoremock "github.com/ethersphere/bee/v2/pkg/settlement/swap/chequestore/mock"
    16  	storemock "github.com/ethersphere/bee/v2/pkg/statestore/mock"
    17  	"github.com/ethersphere/bee/v2/pkg/transaction/backendmock"
    18  	transactionmock "github.com/ethersphere/bee/v2/pkg/transaction/mock"
    19  	"github.com/ethersphere/bee/v2/pkg/util/abiutil"
    20  	"github.com/ethersphere/go-sw3-abi/sw3abi"
    21  )
    22  
    23  var (
    24  	chequebookABI          = abiutil.MustParseABI(sw3abi.ERC20SimpleSwapABIv0_6_5)
    25  	chequeCashedEventType  = chequebookABI.Events["ChequeCashed"]
    26  	chequeBouncedEventType = chequebookABI.Events["ChequeBounced"]
    27  )
    28  
    29  func TestCashout(t *testing.T) {
    30  	t.Parallel()
    31  
    32  	chequebookAddress := common.HexToAddress("abcd")
    33  	recipientAddress := common.HexToAddress("efff")
    34  	txHash := common.HexToHash("dddd")
    35  	totalPayout := big.NewInt(100)
    36  	cumulativePayout := big.NewInt(500)
    37  
    38  	cheque := &chequebook.SignedCheque{
    39  		Cheque: chequebook.Cheque{
    40  			Beneficiary:      common.HexToAddress("aaaa"),
    41  			CumulativePayout: cumulativePayout,
    42  			Chequebook:       chequebookAddress,
    43  		},
    44  		Signature: []byte{},
    45  	}
    46  
    47  	store := storemock.NewStateStore()
    48  	cashoutService := chequebook.NewCashoutService(
    49  		store,
    50  		backendmock.New(
    51  			backendmock.WithTransactionByHashFunc(func(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) {
    52  				if hash != txHash {
    53  					t.Fatalf("fetching wrong transaction. wanted %v, got %v", txHash, hash)
    54  				}
    55  				return nil, false, nil
    56  			}),
    57  			backendmock.WithTransactionReceiptFunc(func(ctx context.Context, hash common.Hash) (*types.Receipt, error) {
    58  				if hash != txHash {
    59  					t.Fatalf("fetching receipt for transaction. wanted %v, got %v", txHash, hash)
    60  				}
    61  
    62  				logData, err := chequeCashedEventType.Inputs.NonIndexed().Pack(totalPayout, cumulativePayout, big.NewInt(0))
    63  				if err != nil {
    64  					t.Fatal(err)
    65  				}
    66  
    67  				return &types.Receipt{
    68  					Status: types.ReceiptStatusSuccessful,
    69  					Logs: []*types.Log{
    70  						{
    71  							Address: chequebookAddress,
    72  							Topics: []common.Hash{
    73  								chequeCashedEventType.ID,
    74  								common.BytesToHash(cheque.Beneficiary.Bytes()),
    75  								common.BytesToHash(recipientAddress.Bytes()),
    76  								common.BytesToHash(cheque.Beneficiary.Bytes()),
    77  							},
    78  							Data: logData,
    79  						},
    80  					},
    81  				}, nil
    82  			}),
    83  		),
    84  		transactionmock.New(
    85  			transactionmock.WithABISend(&chequebookABI, txHash, chequebookAddress, big.NewInt(0), "cashChequeBeneficiary", recipientAddress, cheque.CumulativePayout, cheque.Signature),
    86  		),
    87  		chequestoremock.NewChequeStore(
    88  			chequestoremock.WithLastChequeFunc(func(c common.Address) (*chequebook.SignedCheque, error) {
    89  				if c != chequebookAddress {
    90  					t.Fatalf("using wrong chequebook. wanted %v, got %v", chequebookAddress, c)
    91  				}
    92  				return cheque, nil
    93  			}),
    94  		),
    95  	)
    96  
    97  	returnedTxHash, err := cashoutService.CashCheque(context.Background(), chequebookAddress, recipientAddress)
    98  	if err != nil {
    99  		t.Fatal(err)
   100  	}
   101  
   102  	if returnedTxHash != txHash {
   103  		t.Fatalf("returned wrong transaction hash. wanted %v, got %v", txHash, returnedTxHash)
   104  	}
   105  
   106  	status, err := cashoutService.CashoutStatus(context.Background(), chequebookAddress)
   107  	if err != nil {
   108  		t.Fatal(err)
   109  	}
   110  
   111  	verifyStatus(t, status, chequebook.CashoutStatus{
   112  		Last: &chequebook.LastCashout{
   113  			TxHash: txHash,
   114  			Cheque: *cheque,
   115  			Result: &chequebook.CashChequeResult{
   116  				Beneficiary:      cheque.Beneficiary,
   117  				Recipient:        recipientAddress,
   118  				Caller:           cheque.Beneficiary,
   119  				TotalPayout:      totalPayout,
   120  				CumulativePayout: cumulativePayout,
   121  				CallerPayout:     big.NewInt(0),
   122  				Bounced:          false,
   123  			},
   124  			Reverted: false,
   125  		},
   126  		UncashedAmount: big.NewInt(0),
   127  	})
   128  }
   129  
   130  func TestCashoutBounced(t *testing.T) {
   131  	t.Parallel()
   132  
   133  	chequebookAddress := common.HexToAddress("abcd")
   134  	recipientAddress := common.HexToAddress("efff")
   135  	txHash := common.HexToHash("dddd")
   136  	totalPayout := big.NewInt(100)
   137  	cumulativePayout := big.NewInt(500)
   138  
   139  	cheque := &chequebook.SignedCheque{
   140  		Cheque: chequebook.Cheque{
   141  			Beneficiary:      common.HexToAddress("aaaa"),
   142  			CumulativePayout: cumulativePayout,
   143  			Chequebook:       chequebookAddress,
   144  		},
   145  		Signature: []byte{},
   146  	}
   147  
   148  	store := storemock.NewStateStore()
   149  	cashoutService := chequebook.NewCashoutService(
   150  		store,
   151  		backendmock.New(
   152  			backendmock.WithTransactionByHashFunc(func(ctx context.Context, hash common.Hash) (*types.Transaction, bool, error) {
   153  				if hash != txHash {
   154  					t.Fatalf("fetching wrong transaction. wanted %v, got %v", txHash, hash)
   155  				}
   156  				return nil, false, nil
   157  			}),
   158  			backendmock.WithTransactionReceiptFunc(func(ctx context.Context, hash common.Hash) (*types.Receipt, error) {
   159  				if hash != txHash {
   160  					t.Fatalf("fetching receipt for transaction. wanted %v, got %v", txHash, hash)
   161  				}
   162  
   163  				chequeCashedLogData, err := chequeCashedEventType.Inputs.NonIndexed().Pack(totalPayout, cumulativePayout, big.NewInt(0))
   164  				if err != nil {
   165  					t.Fatal(err)
   166  				}
   167  
   168  				return &types.Receipt{
   169  					Status: types.ReceiptStatusSuccessful,
   170  					Logs: []*types.Log{
   171  						{
   172  							Address: chequebookAddress,
   173  							Topics: []common.Hash{
   174  								chequeCashedEventType.ID,
   175  								common.BytesToHash(cheque.Beneficiary.Bytes()),
   176  								common.BytesToHash(recipientAddress.Bytes()),
   177  								common.BytesToHash(cheque.Beneficiary.Bytes()),
   178  							},
   179  							Data: chequeCashedLogData,
   180  						},
   181  						{
   182  							Address: chequebookAddress,
   183  							Topics:  []common.Hash{chequeBouncedEventType.ID},
   184  						},
   185  					},
   186  				}, nil
   187  			}),
   188  		),
   189  		transactionmock.New(
   190  			transactionmock.WithABISend(&chequebookABI, txHash, chequebookAddress, big.NewInt(0), "cashChequeBeneficiary", recipientAddress, cheque.CumulativePayout, cheque.Signature),
   191  		),
   192  		chequestoremock.NewChequeStore(
   193  			chequestoremock.WithLastChequeFunc(func(c common.Address) (*chequebook.SignedCheque, error) {
   194  				if c != chequebookAddress {
   195  					t.Fatalf("using wrong chequebook. wanted %v, got %v", chequebookAddress, c)
   196  				}
   197  				return cheque, nil
   198  			}),
   199  		),
   200  	)
   201  
   202  	returnedTxHash, err := cashoutService.CashCheque(context.Background(), chequebookAddress, recipientAddress)
   203  	if err != nil {
   204  		t.Fatal(err)
   205  	}
   206  
   207  	if returnedTxHash != txHash {
   208  		t.Fatalf("returned wrong transaction hash. wanted %v, got %v", txHash, returnedTxHash)
   209  	}
   210  
   211  	status, err := cashoutService.CashoutStatus(context.Background(), chequebookAddress)
   212  	if err != nil {
   213  		t.Fatal(err)
   214  	}
   215  
   216  	verifyStatus(t, status, chequebook.CashoutStatus{
   217  		Last: &chequebook.LastCashout{
   218  			TxHash: txHash,
   219  			Cheque: *cheque,
   220  			Result: &chequebook.CashChequeResult{
   221  				Beneficiary:      cheque.Beneficiary,
   222  				Recipient:        recipientAddress,
   223  				Caller:           cheque.Beneficiary,
   224  				TotalPayout:      totalPayout,
   225  				CumulativePayout: cumulativePayout,
   226  				CallerPayout:     big.NewInt(0),
   227  				Bounced:          true,
   228  			},
   229  			Reverted: false,
   230  		},
   231  		UncashedAmount: big.NewInt(0),
   232  	})
   233  }
   234  
   235  func TestCashoutStatusReverted(t *testing.T) {
   236  	t.Parallel()
   237  
   238  	chequebookAddress := common.HexToAddress("abcd")
   239  	recipientAddress := common.HexToAddress("efff")
   240  	txHash := common.HexToHash("dddd")
   241  	cumulativePayout := big.NewInt(500)
   242  	onChainPaidOut := big.NewInt(100)
   243  	beneficiary := common.HexToAddress("aaaa")
   244  
   245  	cheque := &chequebook.SignedCheque{
   246  		Cheque: chequebook.Cheque{
   247  			Beneficiary:      beneficiary,
   248  			CumulativePayout: cumulativePayout,
   249  			Chequebook:       chequebookAddress,
   250  		},
   251  		Signature: []byte{},
   252  	}
   253  
   254  	store := storemock.NewStateStore()
   255  	cashoutService := chequebook.NewCashoutService(
   256  		store,
   257  		backendmock.New(
   258  			backendmock.WithTransactionByHashFunc(func(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) {
   259  				if hash != txHash {
   260  					t.Fatalf("fetching wrong transaction. wanted %v, got %v", txHash, hash)
   261  				}
   262  				return nil, false, nil
   263  			}),
   264  			backendmock.WithTransactionReceiptFunc(func(ctx context.Context, hash common.Hash) (*types.Receipt, error) {
   265  				if hash != txHash {
   266  					t.Fatalf("fetching receipt for transaction. wanted %v, got %v", txHash, hash)
   267  				}
   268  				return &types.Receipt{
   269  					Status: types.ReceiptStatusFailed,
   270  				}, nil
   271  			}),
   272  		),
   273  		transactionmock.New(
   274  			transactionmock.WithABISend(&chequebookABI, txHash, chequebookAddress, big.NewInt(0), "cashChequeBeneficiary", recipientAddress, cheque.CumulativePayout, cheque.Signature),
   275  			transactionmock.WithABICall(&chequebookABI, chequebookAddress, onChainPaidOut.FillBytes(make([]byte, 32)), "paidOut", beneficiary),
   276  		),
   277  		chequestoremock.NewChequeStore(
   278  			chequestoremock.WithLastChequeFunc(func(c common.Address) (*chequebook.SignedCheque, error) {
   279  				if c != chequebookAddress {
   280  					t.Fatalf("using wrong chequebook. wanted %v, got %v", chequebookAddress, c)
   281  				}
   282  				return cheque, nil
   283  			}),
   284  		),
   285  	)
   286  
   287  	returnedTxHash, err := cashoutService.CashCheque(context.Background(), chequebookAddress, recipientAddress)
   288  	if err != nil {
   289  		t.Fatal(err)
   290  	}
   291  
   292  	if returnedTxHash != txHash {
   293  		t.Fatalf("returned wrong transaction hash. wanted %v, got %v", txHash, returnedTxHash)
   294  	}
   295  
   296  	status, err := cashoutService.CashoutStatus(context.Background(), chequebookAddress)
   297  	if err != nil {
   298  		t.Fatal(err)
   299  	}
   300  
   301  	verifyStatus(t, status, chequebook.CashoutStatus{
   302  		Last: &chequebook.LastCashout{
   303  			Reverted: true,
   304  			TxHash:   txHash,
   305  			Cheque:   *cheque,
   306  		},
   307  		UncashedAmount: new(big.Int).Sub(cheque.CumulativePayout, onChainPaidOut),
   308  	})
   309  }
   310  
   311  func TestCashoutStatusPending(t *testing.T) {
   312  	t.Parallel()
   313  
   314  	chequebookAddress := common.HexToAddress("abcd")
   315  	recipientAddress := common.HexToAddress("efff")
   316  	txHash := common.HexToHash("dddd")
   317  	cumulativePayout := big.NewInt(500)
   318  
   319  	cheque := &chequebook.SignedCheque{
   320  		Cheque: chequebook.Cheque{
   321  			Beneficiary:      common.HexToAddress("aaaa"),
   322  			CumulativePayout: cumulativePayout,
   323  			Chequebook:       chequebookAddress,
   324  		},
   325  		Signature: []byte{},
   326  	}
   327  
   328  	store := storemock.NewStateStore()
   329  	cashoutService := chequebook.NewCashoutService(
   330  		store,
   331  		backendmock.New(
   332  			backendmock.WithTransactionByHashFunc(func(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) {
   333  				if hash != txHash {
   334  					t.Fatalf("fetching wrong transaction. wanted %v, got %v", txHash, hash)
   335  				}
   336  				return nil, true, nil
   337  			}),
   338  		),
   339  		transactionmock.New(
   340  			transactionmock.WithABISend(&chequebookABI, txHash, chequebookAddress, big.NewInt(0), "cashChequeBeneficiary", recipientAddress, cheque.CumulativePayout, cheque.Signature),
   341  		),
   342  		chequestoremock.NewChequeStore(
   343  			chequestoremock.WithLastChequeFunc(func(c common.Address) (*chequebook.SignedCheque, error) {
   344  				if c != chequebookAddress {
   345  					t.Fatalf("using wrong chequebook. wanted %v, got %v", chequebookAddress, c)
   346  				}
   347  				return cheque, nil
   348  			}),
   349  		),
   350  	)
   351  
   352  	returnedTxHash, err := cashoutService.CashCheque(context.Background(), chequebookAddress, recipientAddress)
   353  	if err != nil {
   354  		t.Fatal(err)
   355  	}
   356  
   357  	if returnedTxHash != txHash {
   358  		t.Fatalf("returned wrong transaction hash. wanted %v, got %v", txHash, returnedTxHash)
   359  	}
   360  
   361  	status, err := cashoutService.CashoutStatus(context.Background(), chequebookAddress)
   362  	if err != nil {
   363  		t.Fatal(err)
   364  	}
   365  
   366  	verifyStatus(t, status, chequebook.CashoutStatus{
   367  		Last: &chequebook.LastCashout{
   368  			Reverted: false,
   369  			TxHash:   txHash,
   370  			Cheque:   *cheque,
   371  			Result:   nil,
   372  		},
   373  		UncashedAmount: big.NewInt(0),
   374  	})
   375  
   376  }
   377  
   378  func verifyStatus(t *testing.T, status *chequebook.CashoutStatus, expected chequebook.CashoutStatus) {
   379  	t.Helper()
   380  
   381  	if expected.Last == nil {
   382  		if status.Last != nil {
   383  			t.Fatal("unexpected last cashout")
   384  		}
   385  	} else {
   386  		if status.Last == nil {
   387  			t.Fatal("no last cashout")
   388  		}
   389  		if status.Last.Reverted != expected.Last.Reverted {
   390  			t.Fatalf("wrong reverted value. wanted %v, got %v", expected.Last.Reverted, status.Last.Reverted)
   391  		}
   392  		if status.Last.TxHash != expected.Last.TxHash {
   393  			t.Fatalf("wrong transaction hash. wanted %v, got %v", expected.Last.TxHash, status.Last.TxHash)
   394  		}
   395  		if !status.Last.Cheque.Equal(&expected.Last.Cheque) {
   396  			t.Fatalf("wrong cheque in status. wanted %v, got %v", expected.Last.Cheque, status.Last.Cheque)
   397  		}
   398  
   399  		if expected.Last.Result != nil {
   400  			if !expected.Last.Result.Equal(status.Last.Result) {
   401  				t.Fatalf("wrong result. wanted %v, got %v", expected.Last.Result, status.Last.Result)
   402  			}
   403  		}
   404  	}
   405  
   406  	if status.UncashedAmount.Cmp(expected.UncashedAmount) != 0 {
   407  		t.Fatalf("wrong uncashed amount. wanted %d, got %d", expected.UncashedAmount, status.UncashedAmount)
   408  	}
   409  }