Skip to content
This repository has been archived by the owner on Aug 2, 2021. It is now read-only.

swap: refactor cashout logic to cashout processor #2066

Merged
merged 7 commits into from
Jan 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 36 additions & 10 deletions contracts/swap/swap.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ var (
type Backend interface {
bind.ContractBackend
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error)
}

// Contract interface defines the methods exported from the underlying go-bindings for the smart contract
Expand All @@ -48,8 +49,10 @@ type Contract interface {
Withdraw(auth *bind.TransactOpts, amount *big.Int) (*types.Receipt, error)
// Deposit sends a raw transaction to the chequebook, triggering the fallback—depositing amount
Deposit(auth *bind.TransactOpts, amout *big.Int) (*types.Receipt, error)
// CashChequeBeneficiary cashes the cheque by the beneficiary
CashChequeBeneficiary(auth *bind.TransactOpts, beneficiary common.Address, cumulativePayout *big.Int, ownerSig []byte) (*CashChequeResult, *types.Receipt, error)
// CashChequeBeneficiaryStart sends the transaction to cash a cheque as the beneficiary
CashChequeBeneficiaryStart(opts *bind.TransactOpts, beneficiary common.Address, cumulativePayout *big.Int, ownerSig []byte) (*types.Transaction, error)
// CashChequeBeneficiaryResult processes the receipt from a CashChequeBeneficiary transaction
CashChequeBeneficiaryResult(receipt *types.Receipt) *CashChequeResult
// LiquidBalance returns the LiquidBalance (total balance in ERC20-token - total hard deposits in ERC20-token) of the chequebook
LiquidBalance(auth *bind.CallOpts) (*big.Int, error)
//Token returns the address of the ERC20 contract, used by the chequebook
Expand Down Expand Up @@ -139,17 +142,17 @@ func (s simpleContract) Deposit(auth *bind.TransactOpts, amount *big.Int) (*type
return WaitFunc(auth.Context, s.backend, tx)
}

// CashChequeBeneficiary cashes the cheque on the blockchain and blocks until the transaction is mined.
func (s simpleContract) CashChequeBeneficiary(opts *bind.TransactOpts, beneficiary common.Address, cumulativePayout *big.Int, ownerSig []byte) (*CashChequeResult, *types.Receipt, error) {
// CashChequeBeneficiaryStart sends the transaction to cash a cheque as the beneficiary
func (s simpleContract) CashChequeBeneficiaryStart(opts *bind.TransactOpts, beneficiary common.Address, cumulativePayout *big.Int, ownerSig []byte) (*types.Transaction, error) {
tx, err := s.instance.CashChequeBeneficiary(opts, beneficiary, cumulativePayout, ownerSig)
if err != nil {
return nil, nil, err
}
receipt, err := WaitFunc(opts.Context, s.backend, tx)
if err != nil {
return nil, nil, err
return nil, err
}
return tx, nil
}

// CashChequeBeneficiaryResult processes the receipt from a CashChequeBeneficiary transaction
func (s simpleContract) CashChequeBeneficiaryResult(receipt *types.Receipt) *CashChequeResult {
result := &CashChequeResult{
Bounced: false,
}
Expand All @@ -170,7 +173,7 @@ func (s simpleContract) CashChequeBeneficiary(opts *bind.TransactOpts, beneficia
}
}

return result, receipt, nil
return result
}

// LiquidBalance returns the LiquidBalance (total balance in ERC20-token - total hard deposits in ERC20-token) of the chequebook
Expand Down Expand Up @@ -237,3 +240,26 @@ func waitForTx(ctx context.Context, backend Backend, tx *types.Transaction) (*ty
}
return receipt, nil
}

// WaitForTransactionByHash waits for a transaction to by mined by hash
func WaitForTransactionByHash(ctx context.Context, backend Backend, txHash common.Hash) (*types.Receipt, error) {
tx, pending, err := backend.TransactionByHash(ctx, txHash)
if err != nil {
return nil, err
}

var receipt *types.Receipt
if pending {
receipt, err = WaitFunc(ctx, backend, tx)
if err != nil {
return nil, err
}
} else {
receipt, err = backend.TransactionReceipt(ctx, txHash)
if err != nil {
return nil, err
}
}

return receipt, nil
}
136 changes: 136 additions & 0 deletions swap/cashout.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright 2020 The Swarm Authors
// This file is part of the Swarm library.
//
// The Swarm library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The Swarm library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the Swarm library. If not, see <http://www.gnu.org/licenses/>.
package swap

import (
"context"
"crypto/ecdsa"
"math/big"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/metrics"
contract "github.com/ethersphere/swarm/contracts/swap"
)

// CashChequeBeneficiaryTransactionCost is the expected gas cost of a CashChequeBeneficiary transaction
const CashChequeBeneficiaryTransactionCost = 50000

// CashoutProcessor holds all relevant fields needed for processing cashouts
type CashoutProcessor struct {
backend contract.Backend // ethereum backend to use
privateKey *ecdsa.PrivateKey // private key to use
}

// CashoutRequest represents a request for a cashout operation
type CashoutRequest struct {
Cheque Cheque // cheque to be cashed
Destination common.Address // destination for the payout
}

// ActiveCashout stores the necessary information for a cashout in progess
type ActiveCashout struct {
Request CashoutRequest // the request that caused this cashout
TransactionHash common.Hash // the hash of the current transaction for this request
}

// newCashoutProcessor creates a new instance of CashoutProcessor
func newCashoutProcessor(backend contract.Backend, privateKey *ecdsa.PrivateKey) *CashoutProcessor {
return &CashoutProcessor{
backend: backend,
privateKey: privateKey,
}
}

// cashCheque tries to cash the cheque specified in the request
// after the transaction is sent it waits on its success
func (c *CashoutProcessor) cashCheque(ctx context.Context, request *CashoutRequest) error {
cheque := request.Cheque
opts := bind.NewKeyedTransactor(c.privateKey)
opts.Context = ctx

otherSwap, err := contract.InstanceAt(cheque.Contract, c.backend)
if err != nil {
return err
}

tx, err := otherSwap.CashChequeBeneficiaryStart(opts, request.Destination, big.NewInt(int64(cheque.CumulativePayout)), cheque.Signature)
if err != nil {
return err
}

// this blocks until the cashout has been successfully processed
return c.waitForAndProcessActiveCashout(&ActiveCashout{
Request: *request,
TransactionHash: tx.Hash(),
})
}

// estimatePayout estimates the payout for a given cheque as well as the transaction cost
func (c *CashoutProcessor) estimatePayout(ctx context.Context, cheque *Cheque) (expectedPayout uint64, transactionCosts uint64, err error) {
otherSwap, err := contract.InstanceAt(cheque.Contract, c.backend)
if err != nil {
return 0, 0, err
}

paidOut, err := otherSwap.PaidOut(&bind.CallOpts{Context: ctx}, cheque.Beneficiary)
if err != nil {
return 0, 0, err
}

gasPrice, err := c.backend.SuggestGasPrice(ctx)
if err != nil {
return 0, 0, err
}

transactionCosts = gasPrice.Uint64() * CashChequeBeneficiaryTransactionCost

if paidOut.Cmp(big.NewInt(int64(cheque.CumulativePayout))) > 0 {
return 0, transactionCosts, nil
}

expectedPayout = cheque.CumulativePayout - paidOut.Uint64()

return expectedPayout, transactionCosts, nil
}

// waitForAndProcessActiveCashout waits for activeCashout to complete
func (c *CashoutProcessor) waitForAndProcessActiveCashout(activeCashout *ActiveCashout) error {
ctx, cancel := context.WithTimeout(context.Background(), DefaultTransactionTimeout)
defer cancel()

receipt, err := contract.WaitForTransactionByHash(ctx, c.backend, activeCashout.TransactionHash)
Eknir marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}

otherSwap, err := contract.InstanceAt(activeCashout.Request.Cheque.Contract, c.backend)
if err != nil {
return err
}

result := otherSwap.CashChequeBeneficiaryResult(receipt)

metrics.GetOrRegisterCounter("swap.cheques.cashed.honey", nil).Inc(result.TotalPayout.Int64())

if result.Bounced {
metrics.GetOrRegisterCounter("swap.cheques.cashed.bounced", nil).Inc(1)
swapLog.Warn("cheque bounced", "tx", receipt.TxHash)
}

swapLog.Info("cheque cashed", "honey", activeCashout.Request.Cheque.Honey)
return nil
}
Loading