Commit 364c64d6 authored by Ralph Pichler's avatar Ralph Pichler Committed by GitHub

cashout cheques (#740)

parent ec1da284
......@@ -89,6 +89,34 @@ components:
connectedPeers:
type: object
Cheque:
type: object
properties:
beneficiary:
$ref: '#/components/schemas/EthereumAddress'
chequebook:
$ref: '#/components/schemas/EthereumAddress'
payout:
type: integer
ChequeAllPeersResponse:
type: object
properties:
lastcheques:
type: array
items:
$ref: '#/components/schemas/ChequePeerResponse'
ChequePeerResponse:
type: object
properties:
peer:
$ref: '#/components/schemas/SwarmAddress'
lastreceived:
$ref: '#/components/schemas/Cheque'
lastsent:
$ref: '#/components/schemas/Cheque'
DateTime:
type: string
format: date-time
......@@ -100,6 +128,11 @@ components:
type: string
example: "5.0018ms"
EthereumAddress:
type: string
pattern: '^[A-Fa-f0-9]{40}$'
example: "36b7efd913ca4cf880b8eeac5093fa27b0825906"
FileName:
type: string
......@@ -213,9 +246,46 @@ components:
- $ref: '#/components/schemas/SwarmEncryptedReference'
- $ref: '#/components/schemas/DomainName'
SwapCashoutResult:
type: object
properties:
recipient:
$ref: '#/components/schemas/EthereumAddress'
lastPayout:
type: integer
bounced:
type: boolean
SwapCashoutStatus:
type: object
properties:
peer:
$ref: '#/components/schemas/SwarmAddress'
chequebook:
$ref: '#/components/schemas/EthereumAddress'
cumulativePayout:
type: integer
beneficiary:
$ref: '#/components/schemas/EthereumAddress'
transactionHash:
$ref: '#/components/schemas/TransactionHash'
result:
$ref: '#/components/schemas/SwapCashoutResult'
TagName:
type: string
TransactionHash:
type: string
pattern: '^[A-Fa-f0-9]{64}$'
example: "e28a34ffe7b1710c1baf97ca6d71d81b7f159a9920910876856c8d94dd7be4ae"
TransactionResponse:
type: object
properties:
transactionHash:
$ref: '#/components/schemas/TransactionHash'
Uid:
type: integer
......
......@@ -311,3 +311,98 @@ paths:
$ref: 'SwarmCommon.yaml#/components/responses/500'
default:
description: Default response
'/chequebook/cashout/{peer-id}':
get:
summary: Get last cashout action for the peer
parameters:
- in: path
name: peer-id
schema:
$ref: 'SwarmCommon.yaml#/components/schemas/SwarmAddress'
required: true
description: Swarm address of peer
tags:
- Chequebook
responses:
'200':
description: Cashout status
content:
application/json:
schema:
$ref: 'SwarmCommon.yaml#/components/schemas/SwapCashoutStatus'
'404':
$ref: 'SwarmCommon.yaml#/components/responses/404'
'500':
$ref: 'SwarmCommon.yaml#/components/responses/500'
default:
description: Default response
post:
summary: Cashout the last cheque for the peer
parameters:
- in: path
name: peer-id
schema:
$ref: 'SwarmCommon.yaml#/components/schemas/SwarmAddress'
required: true
description: Swarm address of peer
tags:
- Chequebook
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: 'SwarmCommon.yaml#/components/schemas/TransactionResponse'
'404':
$ref: 'SwarmCommon.yaml#/components/responses/404'
'500':
$ref: 'SwarmCommon.yaml#/components/responses/500'
default:
description: Default response
'/chequebook/cheque/{peer-id}':
get:
summary: Get last cheques for the peer
parameters:
- in: path
name: peer-id
schema:
$ref: 'SwarmCommon.yaml#/components/schemas/SwarmAddress'
required: true
description: Swarm address of peer
tags:
- Chequebook
responses:
'200':
description: Last cheques
content:
application/json:
schema:
$ref: 'SwarmCommon.yaml#/components/schemas/ChequePeerResponse'
'404':
$ref: 'SwarmCommon.yaml#/components/responses/404'
'500':
$ref: 'SwarmCommon.yaml#/components/responses/500'
default:
description: Default response
'/chequebook/cheque':
get:
summary: Get last cheques for all peers
tags:
- Chequebook
responses:
'200':
description: Last cheques
content:
application/json:
schema:
$ref: 'SwarmCommon.yaml#/components/schemas/ChequeAllPeersResponse'
'404':
$ref: 'SwarmCommon.yaml#/components/responses/404'
'500':
$ref: 'SwarmCommon.yaml#/components/responses/500'
default:
description: Default response
\ No newline at end of file
......@@ -5,9 +5,11 @@
package debugapi
import (
"errors"
"math/big"
"net/http"
"github.com/ethereum/go-ethereum/common"
"github.com/ethersphere/bee/pkg/jsonhttp"
"github.com/ethersphere/bee/pkg/settlement/swap/chequebook"
......@@ -19,6 +21,10 @@ var (
errChequebookBalance = "cannot get chequebook balance"
errCantLastChequePeer = "cannot get last cheque for peer"
errCantLastCheque = "cannot get last cheque for all peers"
errCannotCash = "cannot cash cheque"
errCannotCashStatus = "cannot get cashout status"
errNoCashout = "no prior cashout"
errNoCheque = "no prior cheque"
)
type chequebookBalanceResponse struct {
......@@ -179,3 +185,92 @@ func (s *server) chequebookAllLastHandler(w http.ResponseWriter, r *http.Request
jsonhttp.OK(w, chequebookLastChequesResponse{LastCheques: lcresponses})
}
type swapCashoutResponse struct {
TransactionHash string `json:"transactionHash"`
}
func (s *server) swapCashoutHandler(w http.ResponseWriter, r *http.Request) {
addr := mux.Vars(r)["peer"]
peer, err := swarm.ParseHexAddress(addr)
if err != nil {
s.Logger.Debugf("debug api: cashout peer: invalid peer address %s: %v", addr, err)
s.Logger.Error("debug api: cashout peer: invalid peer address %s", addr)
jsonhttp.NotFound(w, errInvaliAddress)
return
}
txHash, err := s.Swap.CashCheque(r.Context(), peer)
if err != nil {
s.Logger.Debugf("debug api: cashout peer: cannot cash %s: %v", addr, err)
s.Logger.Error("debug api: cashout peer: cannot cash %s", addr)
jsonhttp.InternalServerError(w, errCannotCash)
return
}
jsonhttp.OK(w, swapCashoutResponse{TransactionHash: txHash.String()})
}
type swapCashoutStatusResult struct {
Recipient common.Address `json:"recipient"`
LastPayout *big.Int `json:"lastPayout"`
Bounced bool `json:"bounced"`
}
type swapCashoutStatusResponse struct {
Peer swarm.Address `json:"peer"`
Chequebook common.Address `json:"chequebook"`
CumulativePayout *big.Int `json:"cumulativePayout"`
Beneficiary common.Address `json:"beneficiary"`
TransactionHash common.Hash `json:"transactionHash"`
Result *swapCashoutStatusResult `json:"result"`
}
func (s *server) swapCashoutStatusHandler(w http.ResponseWriter, r *http.Request) {
addr := mux.Vars(r)["peer"]
peer, err := swarm.ParseHexAddress(addr)
if err != nil {
s.Logger.Debugf("debug api: cashout status peer: invalid peer address %s: %v", addr, err)
s.Logger.Error("debug api: cashout status peer: invalid peer address %s", addr)
jsonhttp.NotFound(w, errInvaliAddress)
return
}
status, err := s.Swap.CashoutStatus(r.Context(), peer)
if err != nil {
if errors.Is(err, chequebook.ErrNoCheque) {
s.Logger.Debugf("debug api: cashout status peer: %v", addr, err)
s.Logger.Error("debug api: cashout status peer: %s", addr)
jsonhttp.NotFound(w, errNoCheque)
return
}
if errors.Is(err, chequebook.ErrNoCashout) {
s.Logger.Debugf("debug api: cashout status peer: %v", addr, err)
s.Logger.Error("debug api: cashout status peer: %s", addr)
jsonhttp.NotFound(w, errNoCashout)
return
}
s.Logger.Debugf("debug api: cashout status peer: cannot get status %s: %v", addr, err)
s.Logger.Error("debug api: cashout status peer: cannot get status %s", addr)
jsonhttp.InternalServerError(w, errCannotCashStatus)
return
}
var result *swapCashoutStatusResult
if status.Result != nil {
result = &swapCashoutStatusResult{
Recipient: status.Result.Recipient,
LastPayout: status.Result.TotalPayout,
Bounced: status.Result.Bounced,
}
}
jsonhttp.OK(w, swapCashoutStatusResponse{
Peer: peer,
TransactionHash: status.TxHash,
Chequebook: status.Cheque.Chequebook,
CumulativePayout: status.Cheque.CumulativePayout,
Beneficiary: status.Cheque.Beneficiary,
Result: result,
})
}
......@@ -355,6 +355,102 @@ func TestChequebookLastChequesPeer(t *testing.T) {
}
func TestChequebookCashout(t *testing.T) {
addr := swarm.MustParseHexAddress("1000000000000000000000000000000000000000000000000000000000000000")
deployCashingHash := common.HexToHash("0xffff")
cashChequeFunc := func(ctx context.Context, peer swarm.Address) (common.Hash, error) {
return deployCashingHash, nil
}
testServer := newTestServer(t, testServerOptions{
SwapOpts: []swapmock.Option{swapmock.WithCashChequeFunc(cashChequeFunc)},
})
expected := &debugapi.SwapCashoutResponse{TransactionHash: deployCashingHash.String()}
var got *debugapi.SwapCashoutResponse
jsonhttptest.Request(t, testServer.Client, http.MethodPost, "/chequebook/cashout/"+addr.String(), http.StatusOK,
jsonhttptest.WithUnmarshalJSONResponse(&got),
)
if !reflect.DeepEqual(got, expected) {
t.Fatalf("Got: \n %+v \n\n Expected: \n %+v \n\n", got, expected)
}
}
func TestChequebookCashoutStatus(t *testing.T) {
actionTxHash := common.HexToHash("0xacfe")
addr := swarm.MustParseHexAddress("1000000000000000000000000000000000000000000000000000000000000000")
beneficiary := common.HexToAddress("0xfff0")
recipientAddress := common.HexToAddress("efff")
totalPayout := big.NewInt(100)
cumulativePayout := big.NewInt(700)
chequebookAddress := common.HexToAddress("0xcfec")
peer := swarm.MustParseHexAddress("1000000000000000000000000000000000000000000000000000000000000000")
sig := make([]byte, 65)
cheque := &chequebook.SignedCheque{
Cheque: chequebook.Cheque{
Beneficiary: beneficiary,
CumulativePayout: cumulativePayout,
Chequebook: chequebookAddress,
},
Signature: sig,
}
result := &chequebook.CashChequeResult{
Beneficiary: cheque.Beneficiary,
Recipient: recipientAddress,
Caller: cheque.Beneficiary,
TotalPayout: totalPayout,
CumulativePayout: cumulativePayout,
CallerPayout: big.NewInt(0),
Bounced: false,
}
cashoutStatusFunc := func(ctx context.Context, peer swarm.Address) (*chequebook.CashoutStatus, error) {
status := &chequebook.CashoutStatus{
TxHash: actionTxHash,
Cheque: *cheque,
Result: result,
Reverted: false,
}
return status, nil
}
testServer := newTestServer(t, testServerOptions{
SwapOpts: []swapmock.Option{swapmock.WithCashoutStatusFunc(cashoutStatusFunc)},
})
statusResult := &debugapi.SwapCashoutStatusResult{
Recipient: recipientAddress,
LastPayout: totalPayout,
Bounced: false,
}
expected := &debugapi.SwapCashoutStatusResponse{
Peer: peer,
TransactionHash: actionTxHash,
Chequebook: chequebookAddress,
CumulativePayout: cumulativePayout,
Beneficiary: cheque.Beneficiary,
Result: statusResult,
}
var got *debugapi.SwapCashoutStatusResponse
jsonhttptest.Request(t, testServer.Client, http.MethodGet, "/chequebook/cashout/"+addr.String(), http.StatusOK,
jsonhttptest.WithUnmarshalJSONResponse(&got),
)
if !reflect.DeepEqual(got, expected) {
t.Fatalf("Got: \n %+v \n\n Expected: \n %+v \n\n", got, expected)
}
}
func LastChequesEqual(a, b *debugapi.ChequebookLastChequesResponse) bool {
var state bool
......
......@@ -21,6 +21,9 @@ type (
ChequebookLastChequePeerResponse = chequebookLastChequePeerResponse
ChequebookLastChequesResponse = chequebookLastChequesResponse
ChequebookLastChequesPeerResponse = chequebookLastChequesPeerResponse
SwapCashoutResponse = swapCashoutResponse
SwapCashoutStatusResponse = swapCashoutStatusResponse
SwapCashoutStatusResult = swapCashoutStatusResult
)
var (
......
......@@ -110,6 +110,11 @@ func (s *server) setupRouting() {
router.Handle("/chequebook/cheque", jsonhttp.MethodHandler{
"GET": http.HandlerFunc(s.chequebookAllLastHandler),
})
router.Handle("/chequebook/cashout/{peer}", jsonhttp.MethodHandler{
"GET": http.HandlerFunc(s.swapCashoutStatusHandler),
"POST": http.HandlerFunc(s.swapCashoutHandler),
})
}
baseRouter.Handle("/", web.ChainHandlers(
logging.NewHTTPAccessLogHandler(s.Logger, logrus.InfoLevel, "debug api access"),
......
......@@ -154,6 +154,7 @@ func NewBee(addr string, swarmAddress swarm.Address, publicKey ecdsa.PublicKey,
var chequebookService chequebook.Service
var chequeStore chequebook.ChequeStore
var cashoutService chequebook.CashoutService
var overlayEthAddress common.Address
if o.SwapEnable {
swapBackend, err := ethclient.Dial(o.SwapEndpoint)
......@@ -216,6 +217,11 @@ func NewBee(addr string, swarmAddress swarm.Address, publicKey ecdsa.PublicKey,
}
chequeStore = chequebook.NewChequeStore(stateStore, swapBackend, chequebookFactory, chainID.Int64(), overlayEthAddress, chequebook.NewSimpleSwapBindings, chequebook.RecoverCheque)
cashoutService, err = chequebook.NewCashoutService(stateStore, chequebook.NewSimpleSwapBindings, swapBackend, transactionService, chequeStore)
if err != nil {
return nil, err
}
}
p2ps, err := libp2p.New(p2pCtx, signer, networkID, swarmAddress, addr, addressbook, stateStore, logger, tracer, libp2p.Options{
......@@ -279,7 +285,7 @@ func NewBee(addr string, swarmAddress swarm.Address, publicKey ecdsa.PublicKey,
if o.SwapEnable {
swapProtocol := swapprotocol.New(p2ps, logger, overlayEthAddress)
swapAddressBook := swap.NewAddressbook(stateStore)
swapService = swap.New(swapProtocol, logger, stateStore, chequebookService, chequeStore, swapAddressBook, networkID)
swapService = swap.New(swapProtocol, logger, stateStore, chequebookService, chequeStore, swapAddressBook, networkID, cashoutService)
swapProtocol.SetSwap(swapService)
if err = p2ps.AddProtocol(swapProtocol.Protocol()); err != nil {
return nil, fmt.Errorf("swap protocol: %w", err)
......
......@@ -19,6 +19,8 @@ type SimpleSwapBinding interface {
Issuer(*bind.CallOpts) (common.Address, error)
TotalPaidOut(*bind.CallOpts) (*big.Int, error)
PaidOut(*bind.CallOpts, common.Address) (*big.Int, error)
ParseChequeCashed(types.Log) (*simpleswapfactory.ERC20SimpleSwapChequeCashed, error)
ParseChequeBounced(types.Log) (*simpleswapfactory.ERC20SimpleSwapChequeBounced, error)
}
type SimpleSwapBindingFunc = func(common.Address, bind.ContractBackend) (SimpleSwapBinding, error)
......
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package chequebook
import (
"context"
"errors"
"fmt"
"math/big"
"strings"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethersphere/bee/pkg/storage"
"github.com/ethersphere/sw3-bindings/v2/simpleswapfactory"
)
var (
// ErrNoCashout is the error if there has not been any cashout action for the chequebook
ErrNoCashout = errors.New("no prior cashout")
)
// CashoutService is the service responsible for managing cashout actions
type CashoutService interface {
// CashCheque sends a cashing transaction for the last cheque of the chequebook
CashCheque(ctx context.Context, chequebook common.Address, recipient common.Address) (common.Hash, error)
// CashoutStatus gets the status of the latest cashout transaction for the chequebook
CashoutStatus(ctx context.Context, chequebookAddress common.Address) (*CashoutStatus, error)
}
type cashoutService struct {
store storage.StateStorer
simpleSwapBindingFunc SimpleSwapBindingFunc
backend Backend
transactionService TransactionService
chequebookABI abi.ABI
chequeStore ChequeStore
}
// CashoutStatus is the action plus its result
type CashoutStatus struct {
TxHash common.Hash
Cheque SignedCheque // the cheque that was used to cashout which may be different from the latest cheque
Result *CashChequeResult
Reverted bool
}
// CashChequeResult summarizes the result of a CashCheque or CashChequeBeneficiary call
type CashChequeResult struct {
Beneficiary common.Address // beneficiary of the cheque
Recipient common.Address // address which received the funds
Caller common.Address // caller of cashCheque
TotalPayout *big.Int // total amount that was paid out in this call
CumulativePayout *big.Int // cumulative payout of the cheque that was cashed
CallerPayout *big.Int // payout for the caller of cashCheque
Bounced bool // indicates wether parts of the cheque bounced
}
// cashoutAction is the data we store for a cashout
type cashoutAction struct {
TxHash common.Hash
Cheque SignedCheque // the cheque that was used to cashout which may be different from the latest cheque
}
// NewCashoutService creates a new CashoutService
func NewCashoutService(
store storage.StateStorer,
simpleSwapBindingFunc SimpleSwapBindingFunc,
backend Backend,
transactionService TransactionService,
chequeStore ChequeStore,
) (CashoutService, error) {
chequebookABI, err := abi.JSON(strings.NewReader(simpleswapfactory.ERC20SimpleSwapABI))
if err != nil {
return nil, err
}
return &cashoutService{
store: store,
simpleSwapBindingFunc: simpleSwapBindingFunc,
backend: backend,
transactionService: transactionService,
chequebookABI: chequebookABI,
chequeStore: chequeStore,
}, nil
}
// cashoutActionKey computes the store key for the last cashout action for the chequebook
func cashoutActionKey(chequebook common.Address) string {
return fmt.Sprintf("cashout_%x", chequebook)
}
// CashCheque sends a cashout transaction for the last cheque of the chequebook
func (s *cashoutService) CashCheque(ctx context.Context, chequebook common.Address, recipient common.Address) (common.Hash, error) {
cheque, err := s.chequeStore.LastCheque(chequebook)
if err != nil {
return common.Hash{}, err
}
callData, err := s.chequebookABI.Pack("cashChequeBeneficiary", recipient, cheque.CumulativePayout, cheque.Signature)
if err != nil {
return common.Hash{}, err
}
request := &TxRequest{
To: chequebook,
Data: callData,
GasPrice: nil,
GasLimit: 0,
Value: big.NewInt(0),
}
txHash, err := s.transactionService.Send(ctx, request)
if err != nil {
return common.Hash{}, err
}
err = s.store.Put(cashoutActionKey(chequebook), &cashoutAction{
TxHash: txHash,
Cheque: *cheque,
})
if err != nil {
return common.Hash{}, err
}
return txHash, nil
}
// CashoutStatus gets the status of the latest cashout transaction for the chequebook
func (s *cashoutService) CashoutStatus(ctx context.Context, chequebookAddress common.Address) (*CashoutStatus, error) {
var action *cashoutAction
err := s.store.Get(cashoutActionKey(chequebookAddress), &action)
if err != nil {
if errors.Is(err, storage.ErrNotFound) {
return nil, ErrNoCashout
}
return nil, err
}
_, pending, err := s.backend.TransactionByHash(ctx, action.TxHash)
if err != nil {
return nil, err
}
if pending {
return &CashoutStatus{
TxHash: action.TxHash,
Cheque: action.Cheque,
Result: nil,
Reverted: false,
}, nil
}
receipt, err := s.backend.TransactionReceipt(ctx, action.TxHash)
if err != nil {
return nil, err
}
if receipt.Status == types.ReceiptStatusFailed {
return &CashoutStatus{
TxHash: action.TxHash,
Cheque: action.Cheque,
Result: nil,
Reverted: true,
}, nil
}
result, err := s.parseCashChequeBeneficiaryReceipt(chequebookAddress, receipt)
if err != nil {
return nil, err
}
return &CashoutStatus{
TxHash: action.TxHash,
Cheque: action.Cheque,
Result: result,
Reverted: false,
}, nil
}
// parseCashChequeBeneficiaryReceipt processes the receipt from a CashChequeBeneficiary transaction
func (s *cashoutService) parseCashChequeBeneficiaryReceipt(chequebookAddress common.Address, receipt *types.Receipt) (*CashChequeResult, error) {
result := &CashChequeResult{
Bounced: false,
}
binding, err := s.simpleSwapBindingFunc(chequebookAddress, s.backend)
if err != nil {
return nil, err
}
for _, log := range receipt.Logs {
if log.Address != chequebookAddress {
continue
}
if event, err := binding.ParseChequeCashed(*log); err == nil {
result.Beneficiary = event.Beneficiary
result.Caller = event.Caller
result.CallerPayout = event.CallerPayout
result.TotalPayout = event.TotalPayout
result.CumulativePayout = event.CumulativePayout
result.Recipient = event.Recipient
} else if _, err := binding.ParseChequeBounced(*log); err == nil {
result.Bounced = true
}
}
return result, nil
}
// Equal compares to CashChequeResults
func (r *CashChequeResult) Equal(o *CashChequeResult) bool {
if r.Beneficiary != o.Beneficiary {
return false
}
if r.Bounced != o.Bounced {
return false
}
if r.Caller != o.Caller {
return false
}
if r.CallerPayout.Cmp(o.CallerPayout) != 0 {
return false
}
if r.CumulativePayout.Cmp(o.CumulativePayout) != 0 {
return false
}
if r.Recipient != o.Recipient {
return false
}
if r.TotalPayout.Cmp(o.TotalPayout) != 0 {
return false
}
return true
}
This diff is collapsed.
......@@ -25,6 +25,7 @@ type backendMock struct {
estimateGas func(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error)
transactionReceipt func(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
pendingNonceAt func(ctx context.Context, account common.Address) (uint64, error)
transactionByHash func(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error)
}
func (m *backendMock) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
......@@ -67,6 +68,10 @@ func (m *backendMock) TransactionReceipt(ctx context.Context, txHash common.Hash
return m.transactionReceipt(ctx, txHash)
}
func (m *backendMock) TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) {
return m.transactionByHash(ctx, hash)
}
type transactionServiceMock struct {
send func(ctx context.Context, request *chequebook.TxRequest) (txHash common.Hash, err error)
waitForReceipt func(ctx context.Context, txHash common.Hash) (receipt *types.Receipt, err error)
......@@ -98,10 +103,12 @@ func (m *simpleSwapFactoryBindingMock) ERC20Address(o *bind.CallOpts) (common.Ad
}
type simpleSwapBindingMock struct {
balance func(*bind.CallOpts) (*big.Int, error)
issuer func(*bind.CallOpts) (common.Address, error)
totalPaidOut func(o *bind.CallOpts) (*big.Int, error)
paidOut func(*bind.CallOpts, common.Address) (*big.Int, error)
balance func(*bind.CallOpts) (*big.Int, error)
issuer func(*bind.CallOpts) (common.Address, error)
totalPaidOut func(o *bind.CallOpts) (*big.Int, error)
paidOut func(*bind.CallOpts, common.Address) (*big.Int, error)
parseChequeCashed func(types.Log) (*simpleswapfactory.ERC20SimpleSwapChequeCashed, error)
parseChequeBounced func(types.Log) (*simpleswapfactory.ERC20SimpleSwapChequeBounced, error)
}
func (m *simpleSwapBindingMock) Balance(o *bind.CallOpts) (*big.Int, error) {
......@@ -116,6 +123,14 @@ func (m *simpleSwapBindingMock) TotalPaidOut(o *bind.CallOpts) (*big.Int, error)
return m.totalPaidOut(o)
}
func (m *simpleSwapBindingMock) ParseChequeCashed(l types.Log) (*simpleswapfactory.ERC20SimpleSwapChequeCashed, error) {
return m.parseChequeCashed(l)
}
func (m *simpleSwapBindingMock) ParseChequeBounced(l types.Log) (*simpleswapfactory.ERC20SimpleSwapChequeBounced, error) {
return m.parseChequeBounced(l)
}
func (m *simpleSwapBindingMock) PaidOut(o *bind.CallOpts, c common.Address) (*big.Int, error) {
return m.paidOut(o, c)
}
......
......@@ -26,6 +26,7 @@ var (
type Backend interface {
bind.ContractBackend
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error)
}
// TxRequest describes a request for a transaction that can be executed.
......
......@@ -31,6 +31,9 @@ type Service struct {
lastReceivedChequeFunc func(swarm.Address) (*chequebook.SignedCheque, error)
lastReceivedChequesFunc func() (map[string]*chequebook.SignedCheque, error)
cashChequeFunc func(ctx context.Context, peer swarm.Address) (common.Hash, error)
cashoutStatusFunc func(ctx context.Context, peer swarm.Address) (*chequebook.CashoutStatus, error)
}
// WithsettlementFunc sets the mock settlement function
......@@ -108,6 +111,18 @@ func WithLastReceivedChequesFunc(f func() (map[string]*chequebook.SignedCheque,
})
}
func WithCashChequeFunc(f func(ctx context.Context, peer swarm.Address) (common.Hash, error)) Option {
return optionFunc(func(s *Service) {
s.cashChequeFunc = f
})
}
func WithCashoutStatusFunc(f func(ctx context.Context, peer swarm.Address) (*chequebook.CashoutStatus, error)) Option {
return optionFunc(func(s *Service) {
s.cashoutStatusFunc = f
})
}
// New creates the mock swap implementation
func New(opts ...Option) settlement.Interface {
mock := new(Service)
......@@ -218,6 +233,20 @@ func (s *Service) LastReceivedCheques() (map[string]*chequebook.SignedCheque, er
return nil, nil
}
func (s *Service) CashCheque(ctx context.Context, peer swarm.Address) (common.Hash, error) {
if s.cashChequeFunc != nil {
return s.cashChequeFunc(ctx, peer)
}
return common.Hash{}, nil
}
func (s *Service) CashoutStatus(ctx context.Context, peer swarm.Address) (*chequebook.CashoutStatus, error) {
if s.cashoutStatusFunc != nil {
return s.cashoutStatusFunc(ctx, peer)
}
return nil, nil
}
// Option is the option passed to the mock settlement service
type Option interface {
apply(*Service)
......
......@@ -37,6 +37,10 @@ type ApiInterface interface {
LastReceivedCheque(peer swarm.Address) (*chequebook.SignedCheque, error)
// LastReceivedCheques returns the list of last received cheques for all peers
LastReceivedCheques() (map[string]*chequebook.SignedCheque, error)
// CashCheque sends a cashing transaction for the last cheque of the peer
CashCheque(ctx context.Context, peer swarm.Address) (common.Hash, error)
// CashoutStatus gets the status of the latest cashout transaction for the peers chequebook
CashoutStatus(ctx context.Context, peer swarm.Address) (*chequebook.CashoutStatus, error)
}
// Service is the implementation of the swap settlement layer.
......@@ -48,12 +52,13 @@ type Service struct {
metrics metrics
chequebook chequebook.Service
chequeStore chequebook.ChequeStore
cashout chequebook.CashoutService
addressbook Addressbook
networkID uint64
}
// New creates a new swap Service.
func New(proto swapprotocol.Interface, logger logging.Logger, store storage.StateStorer, chequebook chequebook.Service, chequeStore chequebook.ChequeStore, addressbook Addressbook, networkID uint64) *Service {
func New(proto swapprotocol.Interface, logger logging.Logger, store storage.StateStorer, chequebook chequebook.Service, chequeStore chequebook.ChequeStore, addressbook Addressbook, networkID uint64, cashout chequebook.CashoutService) *Service {
return &Service{
proto: proto,
logger: logger,
......@@ -63,6 +68,7 @@ func New(proto swapprotocol.Interface, logger logging.Logger, store storage.Stat
chequeStore: chequeStore,
addressbook: addressbook,
networkID: networkID,
cashout: cashout,
}
}
......@@ -293,3 +299,27 @@ func (s *Service) LastReceivedCheques() (map[string]*chequebook.SignedCheque, er
return resultmap, nil
}
// CashCheque sends a cashing transaction for the last cheque of the peer
func (s *Service) CashCheque(ctx context.Context, peer swarm.Address) (common.Hash, error) {
chequebookAddress, known, err := s.addressbook.Chequebook(peer)
if err != nil {
return common.Hash{}, err
}
if !known {
return common.Hash{}, chequebook.ErrNoCheque
}
return s.cashout.CashCheque(ctx, chequebookAddress, s.chequebook.Address())
}
// CashoutStatus gets the status of the latest cashout transaction for the peers chequebook
func (s *Service) CashoutStatus(ctx context.Context, peer swarm.Address) (*chequebook.CashoutStatus, error) {
chequebookAddress, known, err := s.addressbook.Chequebook(peer)
if err != nil {
return nil, err
}
if !known {
return nil, chequebook.ErrNoCheque
}
return s.cashout.CashoutStatus(ctx, chequebookAddress)
}
......@@ -74,6 +74,18 @@ func (m *addressbookMock) PutChequebook(peer swarm.Address, chequebook common.Ad
return m.putChequebook(peer, chequebook)
}
type cashoutMock struct {
cashCheque func(ctx context.Context, chequebook common.Address, recipient common.Address) (common.Hash, error)
cashoutStatus func(ctx context.Context, chequebookAddress common.Address) (*chequebook.CashoutStatus, error)
}
func (m *cashoutMock) CashCheque(ctx context.Context, chequebook common.Address, recipient common.Address) (common.Hash, error) {
return m.cashCheque(ctx, chequebook, recipient)
}
func (m *cashoutMock) CashoutStatus(ctx context.Context, chequebookAddress common.Address) (*chequebook.CashoutStatus, error) {
return m.cashoutStatus(ctx, chequebookAddress)
}
func TestReceiveCheque(t *testing.T) {
logger := logging.New(ioutil.Discard, 0)
store := mockstore.NewStateStore()
......@@ -126,6 +138,7 @@ func TestReceiveCheque(t *testing.T) {
chequeStore,
addressbook,
networkID,
&cashoutMock{},
)
observer := &testObserver{}
......@@ -187,6 +200,7 @@ func TestReceiveChequeReject(t *testing.T) {
chequeStore,
addressbook,
networkID,
&cashoutMock{},
)
observer := &testObserver{}
......@@ -237,6 +251,7 @@ func TestReceiveChequeWrongChequebook(t *testing.T) {
chequeStore,
addressbook,
networkID,
&cashoutMock{},
)
observer := &testObserver{}
......@@ -308,6 +323,7 @@ func TestPay(t *testing.T) {
mockchequestore.NewChequeStore(),
addressbook,
networkID,
&cashoutMock{},
)
err := swap.Pay(context.Background(), peer, amount)
......@@ -357,6 +373,7 @@ func TestPayIssueError(t *testing.T) {
mockchequestore.NewChequeStore(),
addressbook,
networkID,
&cashoutMock{},
)
err := swap.Pay(context.Background(), peer, amount)
......@@ -389,6 +406,7 @@ func TestPayUnknownBeneficiary(t *testing.T) {
mockchequestore.NewChequeStore(),
addressbook,
networkID,
&cashoutMock{},
)
err := swapService.Pay(context.Background(), peer, amount)
......@@ -422,6 +440,7 @@ func TestHandshake(t *testing.T) {
},
},
networkID,
&cashoutMock{},
)
err := swapService.Handshake(peer, beneficiary)
......@@ -459,6 +478,7 @@ func TestHandshakeNewPeer(t *testing.T) {
},
},
networkID,
&cashoutMock{},
)
err := swapService.Handshake(peer, beneficiary)
......@@ -487,6 +507,7 @@ func TestHandshakeWrongBeneficiary(t *testing.T) {
mockchequestore.NewChequeStore(),
&addressbookMock{},
networkID,
&cashoutMock{},
)
err := swapService.Handshake(peer, beneficiary)
......@@ -494,3 +515,100 @@ func TestHandshakeWrongBeneficiary(t *testing.T) {
t.Fatalf("wrong error. wanted %v, got %v", swap.ErrWrongBeneficiary, err)
}
}
func TestCashout(t *testing.T) {
logger := logging.New(ioutil.Discard, 0)
store := mockstore.NewStateStore()
theirChequebookAddress := common.HexToAddress("ffff")
ourChequebookAddress := common.HexToAddress("fffa")
peer := swarm.MustParseHexAddress("abcd")
txHash := common.HexToHash("eeee")
addressbook := &addressbookMock{
chequebook: func(p swarm.Address) (common.Address, bool, error) {
if !peer.Equal(p) {
t.Fatal("querying chequebook for wrong peer")
}
return theirChequebookAddress, true, nil
},
}
swapService := swap.New(
&swapProtocolMock{},
logger,
store,
mockchequebook.NewChequebook(
mockchequebook.WithChequebookAddressFunc(func() common.Address {
return ourChequebookAddress
}),
),
mockchequestore.NewChequeStore(),
addressbook,
uint64(1),
&cashoutMock{
cashCheque: func(ctx context.Context, c common.Address, r common.Address) (common.Hash, error) {
if c != theirChequebookAddress {
t.Fatalf("not cashing with the right chequebook. wanted %v, got %v", theirChequebookAddress, c)
}
if r != ourChequebookAddress {
t.Fatalf("not cashing with the right recipient. wanted %v, got %v", ourChequebookAddress, r)
}
return txHash, nil
},
},
)
returnedHash, err := swapService.CashCheque(context.Background(), peer)
if err != nil {
t.Fatal(err)
}
if returnedHash != txHash {
t.Fatalf("go wrong tx hash. wanted %v, got %v", txHash, returnedHash)
}
}
func TestCashoutStatus(t *testing.T) {
logger := logging.New(ioutil.Discard, 0)
store := mockstore.NewStateStore()
theirChequebookAddress := common.HexToAddress("ffff")
peer := swarm.MustParseHexAddress("abcd")
addressbook := &addressbookMock{
chequebook: func(p swarm.Address) (common.Address, bool, error) {
if !peer.Equal(p) {
t.Fatal("querying chequebook for wrong peer")
}
return theirChequebookAddress, true, nil
},
}
expectedStatus := &chequebook.CashoutStatus{}
swapService := swap.New(
&swapProtocolMock{},
logger,
store,
mockchequebook.NewChequebook(),
mockchequestore.NewChequeStore(),
addressbook,
uint64(1),
&cashoutMock{
cashoutStatus: func(ctx context.Context, c common.Address) (*chequebook.CashoutStatus, error) {
if c != theirChequebookAddress {
t.Fatalf("getting status for wrong chequebook. wanted %v, got %v", theirChequebookAddress, c)
}
return expectedStatus, nil
},
},
)
returnedStatus, err := swapService.CashoutStatus(context.Background(), peer)
if err != nil {
t.Fatal(err)
}
if expectedStatus != returnedStatus {
t.Fatalf("go wrong status. wanted %v, got %v", expectedStatus, returnedStatus)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment