Commit 906c0bda authored by Ralph Pichler's avatar Ralph Pichler Committed by GitHub

chequebook: remove use of bindings for factory (#1361)

parent 9cf23652
......@@ -95,17 +95,11 @@ func InitChequebookFactory(
logger.Infof("using custom factory address: %x", addr)
}
chequebookFactory, err := chequebook.NewFactory(
return chequebook.NewFactory(
backend,
transactionService,
addr,
chequebook.NewSimpleSwapFactoryBindingFunc,
)
if err != nil {
return nil, fmt.Errorf("new factory: %w", err)
}
return chequebookFactory, nil
), nil
}
// InitChequebookService will initialize the chequebook service with the given
......
......@@ -38,16 +38,3 @@ type ERC20BindingFunc = func(common.Address, bind.ContractBackend) (ERC20Binding
func NewERC20Bindings(address common.Address, backend bind.ContractBackend) (ERC20Binding, error) {
return simpleswapfactory.NewERC20(address, backend)
}
// SimpleSwapFactoryBinding is the interface for the generated go bindings for SimpleSwapFactory
type SimpleSwapFactoryBinding interface {
DeployedContracts(*bind.CallOpts, common.Address) (bool, error)
ERC20Address(*bind.CallOpts) (common.Address, error)
}
type SimpleSwapFactoryBindingFunc = func(common.Address, bind.ContractBackend) (SimpleSwapFactoryBinding, error)
// NewSimpleSwapFactoryBindingFunc generates the default go bindings
func NewSimpleSwapFactoryBindingFunc(address common.Address, backend bind.ContractBackend) (SimpleSwapFactoryBinding, error) {
return simpleswapfactory.NewSimpleSwapFactory(address, backend)
}
......@@ -13,18 +13,6 @@ import (
"github.com/ethersphere/bee/pkg/settlement/swap/chequebook"
)
type simpleSwapFactoryBindingMock struct {
erc20Address func(*bind.CallOpts) (common.Address, error)
deployedContracts func(*bind.CallOpts, common.Address) (bool, error)
}
func (m *simpleSwapFactoryBindingMock) DeployedContracts(o *bind.CallOpts, a common.Address) (bool, error) {
return m.deployedContracts(o, a)
}
func (m *simpleSwapFactoryBindingMock) ERC20Address(o *bind.CallOpts) (common.Address, error) {
return m.erc20Address(o)
}
type simpleSwapBindingMock struct {
balance func(*bind.CallOpts) (*big.Int, error)
issuer func(*bind.CallOpts) (common.Address, error)
......
......@@ -10,7 +10,7 @@ import (
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethersphere/bee/pkg/settlement/swap/transaction"
"github.com/ethersphere/sw3-bindings/v3/simpleswapfactory"
......@@ -20,6 +20,7 @@ import (
var (
ErrInvalidFactory = errors.New("not a valid factory contract")
ErrNotDeployedByFactory = errors.New("chequebook not deployed by factory")
errDecodeABI = errors.New("could not decode abi data")
factoryABI = transaction.ParseABIUnchecked(simpleswapfactory.SimpleSwapFactoryABI)
simpleSwapDeployedEventType = factoryABI.Events["SimpleSwapDeployed"]
......@@ -43,8 +44,6 @@ type factory struct {
backend transaction.Backend
transactionService transaction.Service
address common.Address
instance SimpleSwapFactoryBinding
}
type simpleSwapDeployedEvent struct {
......@@ -52,18 +51,12 @@ type simpleSwapDeployedEvent struct {
}
// 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) {
instance, err := simpleSwapFactoryBindingFunc(address, backend)
if err != nil {
return nil, err
}
func NewFactory(backend transaction.Backend, transactionService transaction.Service, address common.Address) Factory {
return &factory{
backend: backend,
transactionService: transactionService,
address: address,
instance: instance,
}, nil
}
}
// Deploy deploys a new chequebook and returns once the transaction has been submitted.
......@@ -121,13 +114,33 @@ func (c *factory) VerifyBytecode(ctx context.Context) (err error) {
// VerifyChequebook checks that the supplied chequebook has been deployed by this factory.
func (c *factory) VerifyChequebook(ctx context.Context, chequebook common.Address) error {
deployed, err := c.instance.DeployedContracts(&bind.CallOpts{
Context: ctx,
}, chequebook)
callData, err := factoryABI.Pack("deployedContracts", chequebook)
if err != nil {
return err
}
if !deployed {
output, err := c.transactionService.Call(ctx, &transaction.TxRequest{
To: &c.address,
Data: callData,
})
if err != nil {
return err
}
results, err := factoryABI.Unpack("deployedContracts", output)
if err != nil {
return err
}
if len(results) != 1 {
return errDecodeABI
}
deployed, ok := abi.ConvertType(results[0], new(bool)).(*bool)
if !ok || deployed == nil {
return errDecodeABI
}
if !*deployed {
return ErrNotDeployedByFactory
}
return nil
......@@ -135,13 +148,33 @@ func (c *factory) VerifyChequebook(ctx context.Context, chequebook common.Addres
// ERC20Address returns the token for which this factory deploys chequebooks.
func (c *factory) ERC20Address(ctx context.Context) (common.Address, error) {
erc20Address, err := c.instance.ERC20Address(&bind.CallOpts{
Context: ctx,
callData, err := factoryABI.Pack("ERC20Address")
if err != nil {
return common.Address{}, err
}
output, err := c.transactionService.Call(ctx, &transaction.TxRequest{
To: &c.address,
Data: callData,
})
if err != nil {
return common.Address{}, err
}
return erc20Address, nil
results, err := factoryABI.Unpack("ERC20Address", output)
if err != nil {
return common.Address{}, err
}
if len(results) != 1 {
return common.Address{}, errDecodeABI
}
erc20Address, ok := abi.ConvertType(results[0], new(common.Address)).(*common.Address)
if !ok || erc20Address == nil {
return common.Address{}, errDecodeABI
}
return *erc20Address, nil
}
// DiscoverFactoryAddress returns the canonical factory for this chainID
......
......@@ -10,7 +10,6 @@ import (
"math/big"
"testing"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethersphere/bee/pkg/settlement/swap/chequebook"
......@@ -25,35 +24,20 @@ var (
simpleSwapDeployedEvent = factoryABI.Events["SimpleSwapDeployed"]
)
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,
func(addr common.Address, b bind.ContractBackend) (chequebook.SimpleSwapFactoryBinding, error) {
if addr != factoryAddress {
t.Fatalf("initialised binding with wrong address. wanted %x, got %x", factoryAddress, addr)
}
if b != backend {
t.Fatal("initialised binding with wrong backend")
}
return simpleSwapFactoryBinding, nil
})
}
func TestFactoryERC20Address(t *testing.T) {
factoryAddress := common.HexToAddress("0xabcd")
erc20Address := common.HexToAddress("0xeffff")
factory, err := newTestFactory(
t,
factoryAddress,
factory := chequebook.NewFactory(
backendmock.New(),
transactionmock.New(),
&simpleSwapFactoryBindingMock{
erc20Address: func(*bind.CallOpts) (common.Address, error) {
return erc20Address, nil
},
})
if err != nil {
t.Fatal(err)
}
transactionmock.New(
transactionmock.WithABICall(
&factoryABI,
erc20Address.Hash().Bytes(),
"ERC20Address",
),
),
factoryAddress,
)
addr, err := factory.ERC20Address(context.Background())
if err != nil {
......@@ -67,9 +51,7 @@ func TestFactoryERC20Address(t *testing.T) {
func TestFactoryVerifySelf(t *testing.T) {
factoryAddress := common.HexToAddress("0xabcd")
factory, err := newTestFactory(
t,
factoryAddress,
factory := chequebook.NewFactory(
backendmock.New(
backendmock.WithCodeAtFunc(func(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
if contract != factoryAddress {
......@@ -82,12 +64,10 @@ func TestFactoryVerifySelf(t *testing.T) {
}),
),
transactionmock.New(),
&simpleSwapFactoryBindingMock{})
if err != nil {
t.Fatal(err)
}
factoryAddress,
)
err = factory.VerifyBytecode(context.Background())
err := factory.VerifyBytecode(context.Background())
if err != nil {
t.Fatal(err)
}
......@@ -95,9 +75,7 @@ func TestFactoryVerifySelf(t *testing.T) {
func TestFactoryVerifySelfInvalidCode(t *testing.T) {
factoryAddress := common.HexToAddress("0xabcd")
factory, err := newTestFactory(
t,
factoryAddress,
factory := chequebook.NewFactory(
backendmock.New(
backendmock.WithCodeAtFunc(func(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
if contract != factoryAddress {
......@@ -110,12 +88,10 @@ func TestFactoryVerifySelfInvalidCode(t *testing.T) {
}),
),
transactionmock.New(),
&simpleSwapFactoryBindingMock{})
if err != nil {
t.Fatal(err)
}
factoryAddress,
)
err = factory.VerifyBytecode(context.Background())
err := factory.VerifyBytecode(context.Background())
if err == nil {
t.Fatal("verified invalid factory")
}
......@@ -127,24 +103,19 @@ func TestFactoryVerifySelfInvalidCode(t *testing.T) {
func TestFactoryVerifyChequebook(t *testing.T) {
factoryAddress := common.HexToAddress("0xabcd")
chequebookAddress := common.HexToAddress("0xefff")
factory, err := newTestFactory(
t,
factoryAddress,
factory := chequebook.NewFactory(
backendmock.New(),
transactionmock.New(),
&simpleSwapFactoryBindingMock{
deployedContracts: func(o *bind.CallOpts, address common.Address) (bool, error) {
if address != chequebookAddress {
t.Fatalf("checked for wrong contract. wanted %v, got %v", chequebookAddress, address)
}
return true, nil
},
})
if err != nil {
t.Fatal(err)
}
err = factory.VerifyChequebook(context.Background(), chequebookAddress)
transactionmock.New(
transactionmock.WithABICall(
&factoryABI,
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"),
"deployedContracts",
chequebookAddress,
),
),
factoryAddress,
)
err := factory.VerifyChequebook(context.Background(), chequebookAddress)
if err != nil {
t.Fatal(err)
}
......@@ -153,24 +124,20 @@ func TestFactoryVerifyChequebook(t *testing.T) {
func TestFactoryVerifyChequebookInvalid(t *testing.T) {
factoryAddress := common.HexToAddress("0xabcd")
chequebookAddress := common.HexToAddress("0xefff")
factory, err := newTestFactory(
t,
factoryAddress,
factory := chequebook.NewFactory(
backendmock.New(),
transactionmock.New(),
&simpleSwapFactoryBindingMock{
deployedContracts: func(o *bind.CallOpts, address common.Address) (bool, error) {
if address != chequebookAddress {
t.Fatalf("checked for wrong contract. wanted %v, got %v", chequebookAddress, address)
}
return false, nil
},
})
if err != nil {
t.Fatal(err)
}
transactionmock.New(
transactionmock.WithABICall(
&factoryABI,
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000"),
"deployedContracts",
chequebookAddress,
),
),
factoryAddress,
)
err = factory.VerifyChequebook(context.Background(), chequebookAddress)
err := factory.VerifyChequebook(context.Background(), chequebookAddress)
if err == nil {
t.Fatal("verified invalid chequebook")
}
......@@ -185,20 +152,10 @@ func TestFactoryDeploy(t *testing.T) {
defaultTimeout := big.NewInt(1)
deployTransactionHash := common.HexToHash("0xffff")
deployAddress := common.HexToAddress("0xdddd")
factory, err := newTestFactory(
t,
factoryAddress,
factory := chequebook.NewFactory(
backendmock.New(),
transactionmock.New(
transactionmock.WithSendFunc(func(ctx context.Context, request *transaction.TxRequest) (txHash common.Hash, err error) {
if request.To != nil && *request.To != factoryAddress {
t.Fatalf("sending to wrong address. wanted %x, got %x", factoryAddress, request.To)
}
if request.Value.Cmp(big.NewInt(0)) != 0 {
t.Fatal("trying to send ether")
}
return deployTransactionHash, nil
}),
transactionmock.WithABISend(&factoryABI, deployTransactionHash, factoryAddress, big.NewInt(0), "deploySimpleSwap", issuerAddress, defaultTimeout),
transactionmock.WithWaitForReceiptFunc(func(ctx context.Context, txHash common.Hash) (receipt *types.Receipt, err error) {
if txHash != deployTransactionHash {
t.Fatalf("waiting for wrong transaction. wanted %x, got %x", deployTransactionHash, txHash)
......@@ -220,13 +177,10 @@ func TestFactoryDeploy(t *testing.T) {
},
},
}, nil
}),
),
nil,
},
)),
factoryAddress,
)
if err != nil {
t.Fatal(err)
}
txHash, err := factory.Deploy(context.Background(), issuerAddress, defaultTimeout)
if err != nil {
......@@ -250,9 +204,7 @@ func TestFactoryDeploy(t *testing.T) {
func TestFactoryDeployReverted(t *testing.T) {
factoryAddress := common.HexToAddress("0xabcd")
deployTransactionHash := common.HexToHash("0xffff")
factory, err := newTestFactory(
t,
factoryAddress,
factory := chequebook.NewFactory(
backendmock.New(),
transactionmock.New(
transactionmock.WithWaitForReceiptFunc(func(ctx context.Context, txHash common.Hash) (receipt *types.Receipt, err error) {
......@@ -264,13 +216,10 @@ func TestFactoryDeployReverted(t *testing.T) {
}, nil
}),
),
&simpleSwapFactoryBindingMock{},
factoryAddress,
)
if err != nil {
t.Fatal(err)
}
_, err = factory.WaitDeployed(context.Background(), deployTransactionHash)
_, err := factory.WaitDeployed(context.Background(), deployTransactionHash)
if err == nil {
t.Fatal("returned failed chequebook deployment")
}
......
......@@ -5,9 +5,13 @@
package mock
import (
"bytes"
"context"
"errors"
"fmt"
"math/big"
"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/settlement/swap/transaction"
......@@ -16,6 +20,7 @@ import (
type transactionServiceMock struct {
send func(ctx context.Context, request *transaction.TxRequest) (txHash common.Hash, err error)
waitForReceipt func(ctx context.Context, txHash common.Hash) (receipt *types.Receipt, err error)
call func(ctx context.Context, request *transaction.TxRequest) (result []byte, err error)
}
func (m *transactionServiceMock) Send(ctx context.Context, request *transaction.TxRequest) (txHash common.Hash, err error) {
......@@ -32,6 +37,13 @@ func (m *transactionServiceMock) WaitForReceipt(ctx context.Context, txHash comm
return nil, errors.New("not implemented")
}
func (m *transactionServiceMock) Call(ctx context.Context, request *transaction.TxRequest) (result []byte, err error) {
if m.call != nil {
return m.call(ctx, request)
}
return nil, errors.New("not implemented")
}
// Option is the option passed to the mock Chequebook service
type Option interface {
apply(*transactionServiceMock)
......@@ -53,6 +65,12 @@ func WithWaitForReceiptFunc(f func(ctx context.Context, txHash common.Hash) (rec
})
}
func WithCallFunc(f func(ctx context.Context, request *transaction.TxRequest) (result []byte, err error)) Option {
return optionFunc(func(s *transactionServiceMock) {
s.call = f
})
}
func New(opts ...Option) transaction.Service {
mock := new(transactionServiceMock)
for _, o := range opts {
......@@ -60,3 +78,72 @@ func New(opts ...Option) transaction.Service {
}
return mock
}
type Call struct {
abi *abi.ABI
result []byte
method string
params []interface{}
}
func ABICall(abi *abi.ABI, result []byte, method string, params ...interface{}) Call {
return Call{
abi: abi,
result: result,
method: method,
params: params,
}
}
func WithABICallSequence(calls ...Call) Option {
return optionFunc(func(s *transactionServiceMock) {
s.call = func(ctx context.Context, request *transaction.TxRequest) ([]byte, error) {
if len(calls) == 0 {
return nil, errors.New("unexpected call")
}
call := calls[0]
data, err := call.abi.Pack(call.method, call.params...)
if err != nil {
return nil, err
}
if !bytes.Equal(data, request.Data) {
return nil, fmt.Errorf("wrong data. wanted %x, got %x", data, request.Data)
}
calls = calls[1:]
return call.result, nil
}
})
}
func WithABICall(abi *abi.ABI, result []byte, method string, params ...interface{}) Option {
return WithABICallSequence(ABICall(abi, result, method, params...))
}
func WithABISend(abi *abi.ABI, txHash common.Hash, expectedAddress common.Address, expectedValue *big.Int, method string, params ...interface{}) Option {
return optionFunc(func(s *transactionServiceMock) {
s.send = func(ctx context.Context, request *transaction.TxRequest) (common.Hash, error) {
data, err := abi.Pack(method, params...)
if err != nil {
return common.Hash{}, err
}
if !bytes.Equal(data, request.Data) {
return common.Hash{}, fmt.Errorf("wrong data. wanted %x, got %x", data, request.Data)
}
if request.To != nil && *request.To != expectedAddress {
return common.Hash{}, fmt.Errorf("sending to wrong contract. wanted %x, got %x", expectedAddress, request.To)
}
if request.Value.Cmp(expectedValue) != 0 {
return common.Hash{}, fmt.Errorf("sending with wrong value. wanted %d, got %d", expectedValue, request.Value)
}
return txHash, nil
}
})
}
......@@ -44,6 +44,8 @@ type TxRequest struct {
type Service interface {
// Send creates a transaction based on the request and sends it.
Send(ctx context.Context, request *TxRequest) (txHash common.Hash, err error)
// Call simulate a transaction based on the request.
Call(ctx context.Context, request *TxRequest) (result []byte, err error)
// WaitForReceipt waits until either the transaction with the given hash has been mined or the context is cancelled.
WaitForReceipt(ctx context.Context, txHash common.Hash) (receipt *types.Receipt, err error)
}
......@@ -109,6 +111,24 @@ func (t *transactionService) Send(ctx context.Context, request *TxRequest) (txHa
return signedTx.Hash(), nil
}
func (t *transactionService) Call(ctx context.Context, request *TxRequest) ([]byte, error) {
msg := ethereum.CallMsg{
From: t.sender,
To: request.To,
Data: request.Data,
GasPrice: request.GasPrice,
Gas: request.GasLimit,
Value: request.Value,
}
data, err := t.backend.CallContract(ctx, msg, nil)
if err != nil {
return nil, err
}
return data, nil
}
// WaitForReceipt waits until either the transaction with the given hash has
// been mined or the context is cancelled.
func (t *transactionService) WaitForReceipt(ctx context.Context, txHash common.Hash) (receipt *types.Receipt, err error) {
......
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