Commit bacba66c authored by Ralph Pichler's avatar Ralph Pichler Committed by GitHub

chequebook: better event parsing (#1360)

parent b2145c85
...@@ -157,7 +157,7 @@ func initChequeStoreCashout( ...@@ -157,7 +157,7 @@ func initChequeStoreCashout(
chainID int64, chainID int64,
overlayEthAddress common.Address, overlayEthAddress common.Address,
transactionService transaction.Service, transactionService transaction.Service,
) (chequebook.ChequeStore, chequebook.CashoutService, error) { ) (chequebook.ChequeStore, chequebook.CashoutService) {
chequeStore := chequebook.NewChequeStore( chequeStore := chequebook.NewChequeStore(
stateStore, stateStore,
swapBackend, swapBackend,
...@@ -168,18 +168,14 @@ func initChequeStoreCashout( ...@@ -168,18 +168,14 @@ func initChequeStoreCashout(
chequebook.RecoverCheque, chequebook.RecoverCheque,
) )
cashout, err := chequebook.NewCashoutService( cashout := chequebook.NewCashoutService(
stateStore, stateStore,
chequebook.NewSimpleSwapBindings,
swapBackend, swapBackend,
transactionService, transactionService,
chequeStore, chequeStore,
) )
if err != nil {
return nil, nil, err
}
return chequeStore, cashout, nil return chequeStore, cashout
} }
// InitSwap will initialize and register the swap service. // InitSwap will initialize and register the swap service.
......
...@@ -222,7 +222,7 @@ func NewBee(addr string, swarmAddress swarm.Address, publicKey ecdsa.PublicKey, ...@@ -222,7 +222,7 @@ func NewBee(addr string, swarmAddress swarm.Address, publicKey ecdsa.PublicKey,
return nil, err return nil, err
} }
chequeStore, cashoutService, err = initChequeStoreCashout( chequeStore, cashoutService = initChequeStoreCashout(
stateStore, stateStore,
swapBackend, swapBackend,
chequebookFactory, chequebookFactory,
...@@ -230,9 +230,6 @@ func NewBee(addr string, swarmAddress swarm.Address, publicKey ecdsa.PublicKey, ...@@ -230,9 +230,6 @@ func NewBee(addr string, swarmAddress swarm.Address, publicKey ecdsa.PublicKey,
overlayEthAddress, overlayEthAddress,
transactionService, transactionService,
) )
if err != nil {
return nil, err
}
} }
p2ps, err := libp2p.New(p2pCtx, signer, networkID, swarmAddress, addr, addressbook, stateStore, logger, tracer, libp2p.Options{ p2ps, err := libp2p.New(p2pCtx, signer, networkID, swarmAddress, addr, addressbook, stateStore, logger, tracer, libp2p.Options{
......
...@@ -9,7 +9,6 @@ import ( ...@@ -9,7 +9,6 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethersphere/sw3-bindings/v3/simpleswapfactory" "github.com/ethersphere/sw3-bindings/v3/simpleswapfactory"
) )
...@@ -19,8 +18,6 @@ type SimpleSwapBinding interface { ...@@ -19,8 +18,6 @@ type SimpleSwapBinding interface {
Issuer(*bind.CallOpts) (common.Address, error) Issuer(*bind.CallOpts) (common.Address, error)
TotalPaidOut(*bind.CallOpts) (*big.Int, error) TotalPaidOut(*bind.CallOpts) (*big.Int, error)
PaidOut(*bind.CallOpts, common.Address) (*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) type SimpleSwapBindingFunc = func(common.Address, bind.ContractBackend) (SimpleSwapBinding, error)
...@@ -44,7 +41,6 @@ func NewERC20Bindings(address common.Address, backend bind.ContractBackend) (ERC ...@@ -44,7 +41,6 @@ func NewERC20Bindings(address common.Address, backend bind.ContractBackend) (ERC
// SimpleSwapFactoryBinding is the interface for the generated go bindings for SimpleSwapFactory // SimpleSwapFactoryBinding is the interface for the generated go bindings for SimpleSwapFactory
type SimpleSwapFactoryBinding interface { type SimpleSwapFactoryBinding interface {
ParseSimpleSwapDeployed(types.Log) (*simpleswapfactory.SimpleSwapFactorySimpleSwapDeployed, error)
DeployedContracts(*bind.CallOpts, common.Address) (bool, error) DeployedContracts(*bind.CallOpts, common.Address) (bool, error)
ERC20Address(*bind.CallOpts) (common.Address, error) ERC20Address(*bind.CallOpts) (common.Address, error)
} }
......
...@@ -9,14 +9,11 @@ import ( ...@@ -9,14 +9,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
"strings"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethersphere/bee/pkg/settlement/swap/transaction" "github.com/ethersphere/bee/pkg/settlement/swap/transaction"
"github.com/ethersphere/bee/pkg/storage" "github.com/ethersphere/bee/pkg/storage"
"github.com/ethersphere/sw3-bindings/v3/simpleswapfactory"
) )
var ( var (
...@@ -33,12 +30,10 @@ type CashoutService interface { ...@@ -33,12 +30,10 @@ type CashoutService interface {
} }
type cashoutService struct { type cashoutService struct {
store storage.StateStorer store storage.StateStorer
simpleSwapBindingFunc SimpleSwapBindingFunc backend transaction.Backend
backend transaction.Backend transactionService transaction.Service
transactionService transaction.Service chequeStore ChequeStore
chequebookABI abi.ABI
chequeStore ChequeStore
} }
// CashoutStatus is the action plus its result // CashoutStatus is the action plus its result
...@@ -65,28 +60,28 @@ type cashoutAction struct { ...@@ -65,28 +60,28 @@ type cashoutAction struct {
TxHash common.Hash TxHash common.Hash
Cheque SignedCheque // the cheque that was used to cashout which may be different from the latest cheque Cheque SignedCheque // the cheque that was used to cashout which may be different from the latest cheque
} }
type chequeCashedEvent struct {
Beneficiary common.Address
Recipient common.Address
Caller common.Address
TotalPayout *big.Int
CumulativePayout *big.Int
CallerPayout *big.Int
}
// NewCashoutService creates a new CashoutService // NewCashoutService creates a new CashoutService
func NewCashoutService( func NewCashoutService(
store storage.StateStorer, store storage.StateStorer,
simpleSwapBindingFunc SimpleSwapBindingFunc,
backend transaction.Backend, backend transaction.Backend,
transactionService transaction.Service, transactionService transaction.Service,
chequeStore ChequeStore, chequeStore ChequeStore,
) (CashoutService, error) { ) CashoutService {
chequebookABI, err := abi.JSON(strings.NewReader(simpleswapfactory.ERC20SimpleSwapABI))
if err != nil {
return nil, err
}
return &cashoutService{ return &cashoutService{
store: store, store: store,
simpleSwapBindingFunc: simpleSwapBindingFunc, backend: backend,
backend: backend, transactionService: transactionService,
transactionService: transactionService, chequeStore: chequeStore,
chequebookABI: chequebookABI, }
chequeStore: chequeStore,
}, nil
} }
// cashoutActionKey computes the store key for the last cashout action for the chequebook // cashoutActionKey computes the store key for the last cashout action for the chequebook
...@@ -101,7 +96,7 @@ func (s *cashoutService) CashCheque(ctx context.Context, chequebook, recipient c ...@@ -101,7 +96,7 @@ func (s *cashoutService) CashCheque(ctx context.Context, chequebook, recipient c
return common.Hash{}, err return common.Hash{}, err
} }
callData, err := s.chequebookABI.Pack("cashChequeBeneficiary", recipient, cheque.CumulativePayout, cheque.Signature) callData, err := chequebookABI.Pack("cashChequeBeneficiary", recipient, cheque.CumulativePayout, cheque.Signature)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
...@@ -188,25 +183,24 @@ func (s *cashoutService) parseCashChequeBeneficiaryReceipt(chequebookAddress com ...@@ -188,25 +183,24 @@ func (s *cashoutService) parseCashChequeBeneficiaryReceipt(chequebookAddress com
Bounced: false, Bounced: false,
} }
binding, err := s.simpleSwapBindingFunc(chequebookAddress, s.backend) var cashedEvent chequeCashedEvent
err := transaction.FindSingleEvent(&chequebookABI, receipt, chequebookAddress, chequeCashedEventType, &cashedEvent)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, log := range receipt.Logs { result.Beneficiary = cashedEvent.Beneficiary
if log.Address != chequebookAddress { result.Caller = cashedEvent.Caller
continue result.CallerPayout = cashedEvent.CallerPayout
} result.TotalPayout = cashedEvent.TotalPayout
if event, err := binding.ParseChequeCashed(*log); err == nil { result.CumulativePayout = cashedEvent.CumulativePayout
result.Beneficiary = event.Beneficiary result.Recipient = cashedEvent.Recipient
result.Caller = event.Caller
result.CallerPayout = event.CallerPayout err = transaction.FindSingleEvent(&chequebookABI, receipt, chequebookAddress, chequeBouncedEventType, nil)
result.TotalPayout = event.TotalPayout if err == nil {
result.CumulativePayout = event.CumulativePayout result.Bounced = true
result.Recipient = event.Recipient } else if !errors.Is(err, transaction.ErrEventNotFound) {
} else if _, err := binding.ParseChequeBounced(*log); err == nil { return nil, err
result.Bounced = true
}
} }
return result, nil return result, nil
......
...@@ -6,11 +6,9 @@ package chequebook_test ...@@ -6,11 +6,9 @@ package chequebook_test
import ( import (
"context" "context"
"errors"
"math/big" "math/big"
"testing" "testing"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethersphere/bee/pkg/settlement/swap/chequebook" "github.com/ethersphere/bee/pkg/settlement/swap/chequebook"
...@@ -22,11 +20,16 @@ import ( ...@@ -22,11 +20,16 @@ import (
"github.com/ethersphere/sw3-bindings/v3/simpleswapfactory" "github.com/ethersphere/sw3-bindings/v3/simpleswapfactory"
) )
var (
chequebookABI = transaction.ParseABIUnchecked(simpleswapfactory.ERC20SimpleSwapABI)
chequeCashedEventType = chequebookABI.Events["ChequeCashed"]
chequeBouncedEventType = chequebookABI.Events["ChequeBounced"]
)
func TestCashout(t *testing.T) { func TestCashout(t *testing.T) {
chequebookAddress := common.HexToAddress("abcd") chequebookAddress := common.HexToAddress("abcd")
recipientAddress := common.HexToAddress("efff") recipientAddress := common.HexToAddress("efff")
txHash := common.HexToHash("dddd") txHash := common.HexToHash("dddd")
log1Topic := common.HexToHash("eeee")
totalPayout := big.NewInt(100) totalPayout := big.NewInt(100)
cumulativePayout := big.NewInt(500) cumulativePayout := big.NewInt(500)
...@@ -40,25 +43,8 @@ func TestCashout(t *testing.T) { ...@@ -40,25 +43,8 @@ func TestCashout(t *testing.T) {
} }
store := storemock.NewStateStore() store := storemock.NewStateStore()
cashoutService, err := chequebook.NewCashoutService( cashoutService := chequebook.NewCashoutService(
store, store,
func(common.Address, bind.ContractBackend) (chequebook.SimpleSwapBinding, error) {
return &simpleSwapBindingMock{
parseChequeCashed: func(l types.Log) (*simpleswapfactory.ERC20SimpleSwapChequeCashed, error) {
if l.Topics[0] != log1Topic {
t.Fatalf("parsing wrong log. wanted %v, got %v", log1Topic, l.Topics[0])
}
return &simpleswapfactory.ERC20SimpleSwapChequeCashed{
Beneficiary: cheque.Beneficiary,
Recipient: recipientAddress,
Caller: cheque.Beneficiary,
TotalPayout: totalPayout,
CumulativePayout: cumulativePayout,
CallerPayout: big.NewInt(0),
}, nil
},
}, nil
},
backendmock.New( backendmock.New(
backendmock.WithTransactionByHashFunc(func(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) { backendmock.WithTransactionByHashFunc(func(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) {
if hash != txHash { if hash != txHash {
...@@ -70,12 +56,19 @@ func TestCashout(t *testing.T) { ...@@ -70,12 +56,19 @@ func TestCashout(t *testing.T) {
if hash != txHash { if hash != txHash {
t.Fatalf("fetching receipt for transaction. wanted %v, got %v", txHash, hash) t.Fatalf("fetching receipt for transaction. wanted %v, got %v", txHash, hash)
} }
logData, err := chequeCashedEventType.Inputs.NonIndexed().Pack(totalPayout, cumulativePayout, big.NewInt(0))
if err != nil {
t.Fatal(err)
}
return &types.Receipt{ return &types.Receipt{
Status: types.ReceiptStatusSuccessful, Status: types.ReceiptStatusSuccessful,
Logs: []*types.Log{ Logs: []*types.Log{
{ {
Address: chequebookAddress, Address: chequebookAddress,
Topics: []common.Hash{log1Topic}, Topics: []common.Hash{chequeCashedEventType.ID, cheque.Beneficiary.Hash(), recipientAddress.Hash(), cheque.Beneficiary.Hash()},
Data: logData,
}, },
}, },
}, nil }, nil
...@@ -101,9 +94,6 @@ func TestCashout(t *testing.T) { ...@@ -101,9 +94,6 @@ func TestCashout(t *testing.T) {
}), }),
), ),
) )
if err != nil {
t.Fatal(err)
}
returnedTxHash, err := cashoutService.CashCheque(context.Background(), chequebookAddress, recipientAddress) returnedTxHash, err := cashoutService.CashCheque(context.Background(), chequebookAddress, recipientAddress)
if err != nil { if err != nil {
...@@ -154,8 +144,6 @@ func TestCashoutBounced(t *testing.T) { ...@@ -154,8 +144,6 @@ func TestCashoutBounced(t *testing.T) {
chequebookAddress := common.HexToAddress("abcd") chequebookAddress := common.HexToAddress("abcd")
recipientAddress := common.HexToAddress("efff") recipientAddress := common.HexToAddress("efff")
txHash := common.HexToHash("dddd") txHash := common.HexToHash("dddd")
log1Topic := common.HexToHash("eeee")
logBouncedTopic := common.HexToHash("bbbb")
totalPayout := big.NewInt(100) totalPayout := big.NewInt(100)
cumulativePayout := big.NewInt(500) cumulativePayout := big.NewInt(500)
...@@ -169,31 +157,8 @@ func TestCashoutBounced(t *testing.T) { ...@@ -169,31 +157,8 @@ func TestCashoutBounced(t *testing.T) {
} }
store := storemock.NewStateStore() store := storemock.NewStateStore()
cashoutService, err := chequebook.NewCashoutService( cashoutService := chequebook.NewCashoutService(
store, store,
func(common.Address, bind.ContractBackend) (chequebook.SimpleSwapBinding, error) {
return &simpleSwapBindingMock{
parseChequeCashed: func(l types.Log) (*simpleswapfactory.ERC20SimpleSwapChequeCashed, error) {
if l.Topics[0] != log1Topic {
return nil, errors.New("")
}
return &simpleswapfactory.ERC20SimpleSwapChequeCashed{
Beneficiary: cheque.Beneficiary,
Recipient: recipientAddress,
Caller: cheque.Beneficiary,
TotalPayout: totalPayout,
CumulativePayout: cumulativePayout,
CallerPayout: big.NewInt(0),
}, nil
},
parseChequeBounced: func(l types.Log) (*simpleswapfactory.ERC20SimpleSwapChequeBounced, error) {
if l.Topics[0] != logBouncedTopic {
t.Fatalf("parsing wrong bounced log. wanted %v, got %v", logBouncedTopic, l.Topics[0])
}
return &simpleswapfactory.ERC20SimpleSwapChequeBounced{}, nil
},
}, nil
},
backendmock.New( backendmock.New(
backendmock.WithTransactionByHashFunc(func(ctx context.Context, hash common.Hash) (*types.Transaction, bool, error) { backendmock.WithTransactionByHashFunc(func(ctx context.Context, hash common.Hash) (*types.Transaction, bool, error) {
if hash != txHash { if hash != txHash {
...@@ -205,16 +170,23 @@ func TestCashoutBounced(t *testing.T) { ...@@ -205,16 +170,23 @@ func TestCashoutBounced(t *testing.T) {
if hash != txHash { if hash != txHash {
t.Fatalf("fetching receipt for transaction. wanted %v, got %v", txHash, hash) t.Fatalf("fetching receipt for transaction. wanted %v, got %v", txHash, hash)
} }
chequeCashedLogData, err := chequeCashedEventType.Inputs.NonIndexed().Pack(totalPayout, cumulativePayout, big.NewInt(0))
if err != nil {
t.Fatal(err)
}
return &types.Receipt{ return &types.Receipt{
Status: types.ReceiptStatusSuccessful, Status: types.ReceiptStatusSuccessful,
Logs: []*types.Log{ Logs: []*types.Log{
{ {
Address: chequebookAddress, Address: chequebookAddress,
Topics: []common.Hash{log1Topic}, Topics: []common.Hash{chequeCashedEventType.ID, cheque.Beneficiary.Hash(), recipientAddress.Hash(), cheque.Beneficiary.Hash()},
Data: chequeCashedLogData,
}, },
{ {
Address: chequebookAddress, Address: chequebookAddress,
Topics: []common.Hash{logBouncedTopic}, Topics: []common.Hash{chequeBouncedEventType.ID},
}, },
}, },
}, nil }, nil
...@@ -240,9 +212,6 @@ func TestCashoutBounced(t *testing.T) { ...@@ -240,9 +212,6 @@ func TestCashoutBounced(t *testing.T) {
}), }),
), ),
) )
if err != nil {
t.Fatal(err)
}
returnedTxHash, err := cashoutService.CashCheque(context.Background(), chequebookAddress, recipientAddress) returnedTxHash, err := cashoutService.CashCheque(context.Background(), chequebookAddress, recipientAddress)
if err != nil { if err != nil {
...@@ -305,11 +274,8 @@ func TestCashoutStatusReverted(t *testing.T) { ...@@ -305,11 +274,8 @@ func TestCashoutStatusReverted(t *testing.T) {
} }
store := storemock.NewStateStore() store := storemock.NewStateStore()
cashoutService, err := chequebook.NewCashoutService( cashoutService := chequebook.NewCashoutService(
store, store,
func(common.Address, bind.ContractBackend) (chequebook.SimpleSwapBinding, error) {
return &simpleSwapBindingMock{}, nil
},
backendmock.New( backendmock.New(
backendmock.WithTransactionByHashFunc(func(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) { backendmock.WithTransactionByHashFunc(func(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) {
if hash != txHash { if hash != txHash {
...@@ -340,9 +306,6 @@ func TestCashoutStatusReverted(t *testing.T) { ...@@ -340,9 +306,6 @@ func TestCashoutStatusReverted(t *testing.T) {
}), }),
), ),
) )
if err != nil {
t.Fatal(err)
}
returnedTxHash, err := cashoutService.CashCheque(context.Background(), chequebookAddress, recipientAddress) returnedTxHash, err := cashoutService.CashCheque(context.Background(), chequebookAddress, recipientAddress)
if err != nil { if err != nil {
...@@ -387,11 +350,8 @@ func TestCashoutStatusPending(t *testing.T) { ...@@ -387,11 +350,8 @@ func TestCashoutStatusPending(t *testing.T) {
} }
store := storemock.NewStateStore() store := storemock.NewStateStore()
cashoutService, err := chequebook.NewCashoutService( cashoutService := chequebook.NewCashoutService(
store, store,
func(common.Address, bind.ContractBackend) (chequebook.SimpleSwapBinding, error) {
return &simpleSwapBindingMock{}, nil
},
backendmock.New( backendmock.New(
backendmock.WithTransactionByHashFunc(func(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) { backendmock.WithTransactionByHashFunc(func(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) {
if hash != txHash { if hash != txHash {
...@@ -414,9 +374,6 @@ func TestCashoutStatusPending(t *testing.T) { ...@@ -414,9 +374,6 @@ func TestCashoutStatusPending(t *testing.T) {
}), }),
), ),
) )
if err != nil {
t.Fatal(err)
}
returnedTxHash, err := cashoutService.CashCheque(context.Background(), chequebookAddress, recipientAddress) returnedTxHash, err := cashoutService.CashCheque(context.Background(), chequebookAddress, recipientAddress)
if err != nil { if err != nil {
......
...@@ -12,7 +12,6 @@ import ( ...@@ -12,7 +12,6 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethersphere/bee/pkg/settlement/swap/transaction" "github.com/ethersphere/bee/pkg/settlement/swap/transaction"
...@@ -33,6 +32,11 @@ var ( ...@@ -33,6 +32,11 @@ var (
ErrOutOfFunds = errors.New("chequebook out of funds") ErrOutOfFunds = errors.New("chequebook out of funds")
// ErrInsufficientFunds is the error when the chequebook has not enough free funds for a user action // ErrInsufficientFunds is the error when the chequebook has not enough free funds for a user action
ErrInsufficientFunds = errors.New("insufficient token balance") ErrInsufficientFunds = errors.New("insufficient token balance")
chequebookABI = transaction.ParseABIUnchecked(simpleswapfactory.ERC20SimpleSwapABI)
chequeCashedEventType = chequebookABI.Events["ChequeCashed"]
chequeBouncedEventType = chequebookABI.Events["ChequeBounced"]
erc20ABI = transaction.ParseABIUnchecked(simpleswapfactory.ERC20ABI)
) )
// Service is the main interface for interacting with the nodes chequebook. // Service is the main interface for interacting with the nodes chequebook.
...@@ -63,12 +67,10 @@ type service struct { ...@@ -63,12 +67,10 @@ type service struct {
transactionService transaction.Service transactionService transaction.Service
address common.Address address common.Address
chequebookABI abi.ABI
chequebookInstance SimpleSwapBinding chequebookInstance SimpleSwapBinding
ownerAddress common.Address ownerAddress common.Address
erc20Address common.Address erc20Address common.Address
erc20ABI abi.ABI
erc20Instance ERC20Binding erc20Instance ERC20Binding
store storage.StateStorer store storage.StateStorer
...@@ -78,16 +80,6 @@ type service struct { ...@@ -78,16 +80,6 @@ type service struct {
// New creates a new chequebook service for the provided chequebook contract. // New creates a new chequebook service for the provided chequebook contract.
func New(backend transaction.Backend, transactionService transaction.Service, address, erc20Address, ownerAddress common.Address, store storage.StateStorer, chequeSigner ChequeSigner, simpleSwapBindingFunc SimpleSwapBindingFunc, erc20BindingFunc ERC20BindingFunc) (Service, error) { func New(backend transaction.Backend, transactionService transaction.Service, address, erc20Address, ownerAddress common.Address, store storage.StateStorer, chequeSigner ChequeSigner, simpleSwapBindingFunc SimpleSwapBindingFunc, erc20BindingFunc ERC20BindingFunc) (Service, error) {
chequebookABI, err := abi.JSON(strings.NewReader(simpleswapfactory.ERC20SimpleSwapABI))
if err != nil {
return nil, err
}
erc20ABI, err := abi.JSON(strings.NewReader(simpleswapfactory.ERC20ABI))
if err != nil {
return nil, err
}
chequebookInstance, err := simpleSwapBindingFunc(address, backend) chequebookInstance, err := simpleSwapBindingFunc(address, backend)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -102,11 +94,9 @@ func New(backend transaction.Backend, transactionService transaction.Service, ad ...@@ -102,11 +94,9 @@ func New(backend transaction.Backend, transactionService transaction.Service, ad
backend: backend, backend: backend,
transactionService: transactionService, transactionService: transactionService,
address: address, address: address,
chequebookABI: chequebookABI,
chequebookInstance: chequebookInstance, chequebookInstance: chequebookInstance,
ownerAddress: ownerAddress, ownerAddress: ownerAddress,
erc20Address: erc20Address, erc20Address: erc20Address,
erc20ABI: erc20ABI,
erc20Instance: erc20Instance, erc20Instance: erc20Instance,
store: store, store: store,
chequeSigner: chequeSigner, chequeSigner: chequeSigner,
...@@ -133,7 +123,7 @@ func (s *service) Deposit(ctx context.Context, amount *big.Int) (hash common.Has ...@@ -133,7 +123,7 @@ func (s *service) Deposit(ctx context.Context, amount *big.Int) (hash common.Has
return common.Hash{}, ErrInsufficientFunds return common.Hash{}, ErrInsufficientFunds
} }
callData, err := s.erc20ABI.Pack("transfer", s.address, amount) callData, err := erc20ABI.Pack("transfer", s.address, amount)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
...@@ -365,7 +355,7 @@ func (s *service) Withdraw(ctx context.Context, amount *big.Int) (hash common.Ha ...@@ -365,7 +355,7 @@ func (s *service) Withdraw(ctx context.Context, amount *big.Int) (hash common.Ha
return common.Hash{}, ErrInsufficientFunds return common.Hash{}, ErrInsufficientFunds
} }
callData, err := s.chequebookABI.Pack("withdraw", amount) callData, err := chequebookABI.Pack("withdraw", amount)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
......
...@@ -10,19 +10,12 @@ import ( ...@@ -10,19 +10,12 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethersphere/bee/pkg/settlement/swap/chequebook" "github.com/ethersphere/bee/pkg/settlement/swap/chequebook"
"github.com/ethersphere/sw3-bindings/v3/simpleswapfactory"
) )
type simpleSwapFactoryBindingMock struct { type simpleSwapFactoryBindingMock struct {
erc20Address func(*bind.CallOpts) (common.Address, error) erc20Address func(*bind.CallOpts) (common.Address, error)
deployedContracts func(*bind.CallOpts, common.Address) (bool, error) deployedContracts func(*bind.CallOpts, common.Address) (bool, error)
parseSimpleSwapDeployed func(types.Log) (*simpleswapfactory.SimpleSwapFactorySimpleSwapDeployed, error)
}
func (m *simpleSwapFactoryBindingMock) ParseSimpleSwapDeployed(l types.Log) (*simpleswapfactory.SimpleSwapFactorySimpleSwapDeployed, error) {
return m.parseSimpleSwapDeployed(l)
} }
func (m *simpleSwapFactoryBindingMock) DeployedContracts(o *bind.CallOpts, a common.Address) (bool, error) { func (m *simpleSwapFactoryBindingMock) DeployedContracts(o *bind.CallOpts, a common.Address) (bool, error) {
...@@ -33,12 +26,10 @@ func (m *simpleSwapFactoryBindingMock) ERC20Address(o *bind.CallOpts) (common.Ad ...@@ -33,12 +26,10 @@ func (m *simpleSwapFactoryBindingMock) ERC20Address(o *bind.CallOpts) (common.Ad
} }
type simpleSwapBindingMock struct { type simpleSwapBindingMock struct {
balance func(*bind.CallOpts) (*big.Int, error) balance func(*bind.CallOpts) (*big.Int, error)
issuer func(*bind.CallOpts) (common.Address, error) issuer func(*bind.CallOpts) (common.Address, error)
totalPaidOut func(o *bind.CallOpts) (*big.Int, error) totalPaidOut func(o *bind.CallOpts) (*big.Int, error)
paidOut func(*bind.CallOpts, common.Address) (*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) { func (m *simpleSwapBindingMock) Balance(o *bind.CallOpts) (*big.Int, error) {
...@@ -53,14 +44,6 @@ func (m *simpleSwapBindingMock) TotalPaidOut(o *bind.CallOpts) (*big.Int, error) ...@@ -53,14 +44,6 @@ func (m *simpleSwapBindingMock) TotalPaidOut(o *bind.CallOpts) (*big.Int, error)
return m.totalPaidOut(o) 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) { func (m *simpleSwapBindingMock) PaidOut(o *bind.CallOpts, c common.Address) (*big.Int, error) {
return m.paidOut(o, c) return m.paidOut(o, c)
} }
......
...@@ -7,13 +7,11 @@ package chequebook ...@@ -7,13 +7,11 @@ package chequebook
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt"
"math/big" "math/big"
"strings"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethersphere/bee/pkg/settlement/swap/transaction" "github.com/ethersphere/bee/pkg/settlement/swap/transaction"
"github.com/ethersphere/sw3-bindings/v3/simpleswapfactory" "github.com/ethersphere/sw3-bindings/v3/simpleswapfactory"
"golang.org/x/net/context" "golang.org/x/net/context"
...@@ -22,6 +20,9 @@ import ( ...@@ -22,6 +20,9 @@ import (
var ( var (
ErrInvalidFactory = errors.New("not a valid factory contract") ErrInvalidFactory = errors.New("not a valid factory contract")
ErrNotDeployedByFactory = errors.New("chequebook not deployed by factory") ErrNotDeployedByFactory = errors.New("chequebook not deployed by factory")
factoryABI = transaction.ParseABIUnchecked(simpleswapfactory.SimpleSwapFactoryABI)
simpleSwapDeployedEventType = factoryABI.Events["SimpleSwapDeployed"]
) )
// Factory is the main interface for interacting with the chequebook factory. // Factory is the main interface for interacting with the chequebook factory.
...@@ -43,17 +44,15 @@ type factory struct { ...@@ -43,17 +44,15 @@ type factory struct {
transactionService transaction.Service transactionService transaction.Service
address common.Address address common.Address
ABI abi.ABI
instance SimpleSwapFactoryBinding instance SimpleSwapFactoryBinding
} }
type simpleSwapDeployedEvent struct {
ContractAddress common.Address
}
// NewFactory creates a new factory service for the provided factory contract. // NewFactory creates a new factory service for the provided factory contract.
func NewFactory(backend transaction.Backend, transactionService transaction.Service, address common.Address, simpleSwapFactoryBindingFunc SimpleSwapFactoryBindingFunc) (Factory, error) { func NewFactory(backend transaction.Backend, transactionService transaction.Service, address common.Address, simpleSwapFactoryBindingFunc SimpleSwapFactoryBindingFunc) (Factory, error) {
ABI, err := abi.JSON(strings.NewReader(simpleswapfactory.SimpleSwapFactoryABI))
if err != nil {
return nil, err
}
instance, err := simpleSwapFactoryBindingFunc(address, backend) instance, err := simpleSwapFactoryBindingFunc(address, backend)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -63,14 +62,13 @@ func NewFactory(backend transaction.Backend, transactionService transaction.Serv ...@@ -63,14 +62,13 @@ func NewFactory(backend transaction.Backend, transactionService transaction.Serv
backend: backend, backend: backend,
transactionService: transactionService, transactionService: transactionService,
address: address, address: address,
ABI: ABI,
instance: instance, instance: instance,
}, nil }, nil
} }
// Deploy deploys a new chequebook and returns once the transaction has been submitted. // Deploy deploys a new chequebook and returns once the transaction has been submitted.
func (c *factory) Deploy(ctx context.Context, issuer common.Address, defaultHardDepositTimeoutDuration *big.Int) (common.Hash, error) { func (c *factory) Deploy(ctx context.Context, issuer common.Address, defaultHardDepositTimeoutDuration *big.Int) (common.Hash, error) {
callData, err := c.ABI.Pack("deploySimpleSwap", issuer, big.NewInt(0).Set(defaultHardDepositTimeoutDuration)) callData, err := factoryABI.Pack("deploySimpleSwap", issuer, big.NewInt(0).Set(defaultHardDepositTimeoutDuration))
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
...@@ -98,32 +96,13 @@ func (c *factory) WaitDeployed(ctx context.Context, txHash common.Hash) (common. ...@@ -98,32 +96,13 @@ func (c *factory) WaitDeployed(ctx context.Context, txHash common.Hash) (common.
return common.Address{}, err return common.Address{}, err
} }
chequebookAddress, err := c.parseDeployReceipt(receipt) var event simpleSwapDeployedEvent
err = transaction.FindSingleEvent(&factoryABI, receipt, c.address, simpleSwapDeployedEventType, &event)
if err != nil { if err != nil {
return common.Address{}, err return common.Address{}, fmt.Errorf("contract deployment failed: %w", err)
} }
return chequebookAddress, nil return event.ContractAddress, nil
}
// parseDeployReceipt parses the address of the deployed chequebook from the receipt.
func (c *factory) parseDeployReceipt(receipt *types.Receipt) (address common.Address, err error) {
if receipt.Status != 1 {
return common.Address{}, transaction.ErrTransactionReverted
}
for _, log := range receipt.Logs {
if log.Address != c.address {
continue
}
if event, err := c.instance.ParseSimpleSwapDeployed(*log); err == nil {
address = event.ContractAddress
break
}
}
if (address == common.Address{}) {
return common.Address{}, errors.New("contract deployment failed")
}
return address, nil
} }
// VerifyBytecode checks that the factory is valid. // VerifyBytecode checks that the factory is valid.
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
package chequebook_test package chequebook_test
import ( import (
"bytes"
"context" "context"
"errors" "errors"
"math/big" "math/big"
...@@ -21,6 +20,11 @@ import ( ...@@ -21,6 +20,11 @@ import (
"github.com/ethersphere/sw3-bindings/v3/simpleswapfactory" "github.com/ethersphere/sw3-bindings/v3/simpleswapfactory"
) )
var (
factoryABI = transaction.ParseABIUnchecked(simpleswapfactory.SimpleSwapFactoryABI)
simpleSwapDeployedEvent = factoryABI.Events["SimpleSwapDeployed"]
)
func newTestFactory(t *testing.T, factoryAddress common.Address, backend transaction.Backend, transactionService transaction.Service, simpleSwapFactoryBinding chequebook.SimpleSwapFactoryBinding) (chequebook.Factory, error) { func newTestFactory(t *testing.T, factoryAddress common.Address, backend transaction.Backend, transactionService transaction.Service, simpleSwapFactoryBinding chequebook.SimpleSwapFactoryBinding) (chequebook.Factory, error) {
return chequebook.NewFactory(backend, transactionService, factoryAddress, return chequebook.NewFactory(backend, transactionService, factoryAddress,
func(addr common.Address, b bind.ContractBackend) (chequebook.SimpleSwapFactoryBinding, error) { func(addr common.Address, b bind.ContractBackend) (chequebook.SimpleSwapFactoryBinding, error) {
...@@ -181,7 +185,6 @@ func TestFactoryDeploy(t *testing.T) { ...@@ -181,7 +185,6 @@ func TestFactoryDeploy(t *testing.T) {
defaultTimeout := big.NewInt(1) defaultTimeout := big.NewInt(1)
deployTransactionHash := common.HexToHash("0xffff") deployTransactionHash := common.HexToHash("0xffff")
deployAddress := common.HexToAddress("0xdddd") deployAddress := common.HexToAddress("0xdddd")
logData := common.Hex2Bytes("0xcccc")
factory, err := newTestFactory( factory, err := newTestFactory(
t, t,
factoryAddress, factoryAddress,
...@@ -200,6 +203,10 @@ func TestFactoryDeploy(t *testing.T) { ...@@ -200,6 +203,10 @@ func TestFactoryDeploy(t *testing.T) {
if txHash != deployTransactionHash { if txHash != deployTransactionHash {
t.Fatalf("waiting for wrong transaction. wanted %x, got %x", deployTransactionHash, txHash) t.Fatalf("waiting for wrong transaction. wanted %x, got %x", deployTransactionHash, txHash)
} }
logData, err := simpleSwapDeployedEvent.Inputs.NonIndexed().Pack(deployAddress)
if err != nil {
t.Fatal(err)
}
return &types.Receipt{ return &types.Receipt{
Status: 1, Status: 1,
Logs: []*types.Log{ Logs: []*types.Log{
...@@ -208,22 +215,15 @@ func TestFactoryDeploy(t *testing.T) { ...@@ -208,22 +215,15 @@ func TestFactoryDeploy(t *testing.T) {
}, },
{ {
Address: factoryAddress, Address: factoryAddress,
Topics: []common.Hash{simpleSwapDeployedEvent.ID},
Data: logData, Data: logData,
}, },
}, },
}, nil }, nil
}), }),
), ),
&simpleSwapFactoryBindingMock{ nil,
parseSimpleSwapDeployed: func(log types.Log) (*simpleswapfactory.SimpleSwapFactorySimpleSwapDeployed, error) { )
if !bytes.Equal(log.Data, logData) {
t.Fatal("trying to parse wrong log")
}
return &simpleswapfactory.SimpleSwapFactorySimpleSwapDeployed{
ContractAddress: deployAddress,
}, nil
},
})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
......
...@@ -6,9 +6,12 @@ package transaction ...@@ -6,9 +6,12 @@ package transaction
import ( import (
"context" "context"
"fmt"
"math/big" "math/big"
"strings"
"time" "time"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
...@@ -65,3 +68,12 @@ func WaitSynced(ctx context.Context, backend Backend, maxDelay time.Duration) er ...@@ -65,3 +68,12 @@ func WaitSynced(ctx context.Context, backend Backend, maxDelay time.Duration) er
} }
} }
} }
// ParseABIUnchecked will parse a valid json abi. Only use this with string constants known to be correct.
func ParseABIUnchecked(json string) abi.ABI {
cabi, err := abi.JSON(strings.NewReader(json))
if err != nil {
panic(fmt.Sprintf("error creating ABI for contract: %v", err))
}
return cabi
}
// Copyright 2021 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 transaction
import (
"errors"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
var (
ErrEventNotFound = errors.New("event not found")
ErrNoTopic = errors.New("no topic")
)
// ParseEvent will parse the specified abi event from the given log
func ParseEvent(a *abi.ABI, eventName string, c interface{}, e types.Log) error {
if len(e.Topics) == 0 {
return ErrNoTopic
}
if len(e.Data) > 0 {
if err := a.UnpackIntoInterface(c, eventName, e.Data); err != nil {
return err
}
}
var indexed abi.Arguments
for _, arg := range a.Events[eventName].Inputs {
if arg.Indexed {
indexed = append(indexed, arg)
}
}
return abi.ParseTopics(c, indexed, e.Topics[1:])
}
// FindSingleEvent will find the first event of the given kind.
func FindSingleEvent(abi *abi.ABI, receipt *types.Receipt, contractAddress common.Address, event abi.Event, out interface{}) error {
if receipt.Status != 1 {
return ErrTransactionReverted
}
for _, log := range receipt.Logs {
if log.Address != contractAddress {
continue
}
if len(log.Topics) == 0 {
continue
}
if log.Topics[0] != event.ID {
continue
}
return ParseEvent(abi, event.Name, out, *log)
}
return ErrEventNotFound
}
// Copyright 2021 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 transaction_test
import (
"errors"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethersphere/bee/pkg/settlement/swap/transaction"
"github.com/ethersphere/sw3-bindings/v3/simpleswapfactory"
)
var (
erc20ABI = transaction.ParseABIUnchecked(simpleswapfactory.ERC20ABI)
)
type transferEvent struct {
From common.Address
To common.Address
Value *big.Int
}
func newTransferLog(address common.Address, from common.Address, to common.Address, value *big.Int) *types.Log {
return &types.Log{
Topics: []common.Hash{
erc20ABI.Events["Transfer"].ID,
from.Hash(),
to.Hash(),
},
Data: value.FillBytes(make([]byte, 32)),
Address: address,
}
}
func TestParseEvent(t *testing.T) {
from := common.HexToAddress("00")
to := common.HexToAddress("01")
value := big.NewInt(0)
t.Run("ok", func(t *testing.T) {
var event transferEvent
err := transaction.ParseEvent(&erc20ABI, "Transfer", &event, *newTransferLog(common.Address{}, from, to, value))
if err != nil {
t.Fatal(err)
}
if event.From != from {
t.Fatalf("parsed wrong from. wanted %x, got %x", from, event.From)
}
if event.To != to {
t.Fatalf("parsed wrong to. wanted %x, got %x", to, event.To)
}
if value.Cmp(event.Value) != 0 {
t.Fatalf("parsed wrong value. wanted %d, got %d", value, event.Value)
}
})
t.Run("no topic", func(t *testing.T) {
var event transferEvent
err := transaction.ParseEvent(&erc20ABI, "Transfer", &event, types.Log{
Topics: []common.Hash{},
})
if !errors.Is(err, transaction.ErrNoTopic) {
t.Fatalf("expected error %v, got %v", transaction.ErrNoTopic, err)
}
})
}
func TestFindSingleEvent(t *testing.T) {
contractAddress := common.HexToAddress("abcd")
from := common.HexToAddress("00")
to := common.HexToAddress("01")
value := big.NewInt(0)
t.Run("ok", func(t *testing.T) {
var event transferEvent
err := transaction.FindSingleEvent(
&erc20ABI,
&types.Receipt{
Logs: []*types.Log{
newTransferLog(from, to, from, value), // event from different contract
{Topics: []common.Hash{{}}, Address: contractAddress}, // different event from same contract
newTransferLog(contractAddress, from, to, value),
},
Status: 1,
},
contractAddress,
erc20ABI.Events["Transfer"],
&event,
)
if err != nil {
t.Fatal(err)
}
if event.From != from {
t.Fatalf("parsed wrong from. wanted %x, got %x", from, event.From)
}
if event.To != to {
t.Fatalf("parsed wrong to. wanted %x, got %x", to, event.To)
}
if value.Cmp(event.Value) != 0 {
t.Fatalf("parsed wrong value. wanted %d, got %d", value, event.Value)
}
})
t.Run("not found", func(t *testing.T) {
var event transferEvent
err := transaction.FindSingleEvent(
&erc20ABI,
&types.Receipt{
Logs: []*types.Log{
newTransferLog(from, to, from, value), // event from different contract
{Topics: []common.Hash{{}}, Address: contractAddress}, // different event from same contract
},
Status: 1,
},
contractAddress,
erc20ABI.Events["Transfer"],
&event,
)
if !errors.Is(err, transaction.ErrEventNotFound) {
t.Fatalf("wanted error %v, got %v", transaction.ErrEventNotFound, err)
}
})
t.Run("Reverted", func(t *testing.T) {
var event transferEvent
err := transaction.FindSingleEvent(
&erc20ABI,
&types.Receipt{Status: 0},
contractAddress,
erc20ABI.Events["Transfer"],
&event,
)
if !errors.Is(err, transaction.ErrTransactionReverted) {
t.Fatalf("wanted error %v, got %v", transaction.ErrTransactionReverted, err)
}
})
}
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