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

swap: use the SimpleSwapFactory for deployment and verification #1803

Merged
merged 3 commits into from
Oct 23, 2019
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
1 change: 1 addition & 0 deletions api/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ type Config struct {
SwapInitialDeposit uint64 // initial deposit amount to the chequebook
SwapLogPath string // dir to swap related audit logs
Contract common.Address // address of the chequebook contract
SwapChequebookFactory common.Address // address of the chequebook factory contract
// end of Swap configs

*network.HiveParams
Expand Down
5 changes: 4 additions & 1 deletion cmd/swarm/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const (
SwarmEnvPort = "SWARM_PORT"
SwarmEnvNetworkID = "SWARM_NETWORK_ID"
SwarmEnvChequebookAddr = "SWARM_CHEQUEBOOK_ADDR"
SwarmEnvChequebookFactoryAddr = "SWARM_SWAP_CHEQUEBOOK_FACTORY_ADDR"
SwarmEnvInitialDeposit = "SWARM_INITIAL_DEPOSIT"
SwarmEnvSwapEnable = "SWARM_SWAP_ENABLE"
SwarmEnvSwapBackendURL = "SWARM_SWAP_BACKEND_URL"
Expand Down Expand Up @@ -183,7 +184,9 @@ func flagsOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Confi
if chbookaddr := ctx.GlobalString(SwarmSwapChequebookAddrFlag.Name); chbookaddr != "" {
currentConfig.Contract = common.HexToAddress(chbookaddr)
}

if chequebookFactoryAddress := ctx.GlobalString(SwarmSwapChequebookFactoryFlag.Name); chequebookFactoryAddress != "" {
currentConfig.SwapChequebookFactory = common.HexToAddress(chequebookFactoryAddress)
}
networkid := ctx.GlobalUint64(SwarmNetworkIdFlag.Name)
if networkid != 0 && networkid != network.DefaultNetworkID {
currentConfig.NetworkID = networkid
Expand Down
5 changes: 5 additions & 0 deletions cmd/swarm/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ var (
Usage: "SWAP chequebook contract address",
EnvVar: SwarmEnvChequebookAddr,
}
SwarmSwapChequebookFactoryFlag = cli.StringFlag{
Name: "swap-chequebook-factory",
Usage: "SWAP chequebook factory contract address",
EnvVar: SwarmEnvChequebookFactoryAddr,
}
SwarmSwapEnabledFlag = cli.BoolFlag{
Name: "swap",
Usage: "Swarm SWAP enabled (default false)",
Expand Down
1 change: 1 addition & 0 deletions cmd/swarm/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ func init() {
SwarmSwapPaymentThresholdFlag,
SwarmSwapLogPathFlag,
SwarmSwapChequebookAddrFlag,
SwarmSwapChequebookFactoryFlag,
SwarmSwapInitialDepositFlag,
// end of swap flags
SwarmSyncModeFlag,
Expand Down
125 changes: 125 additions & 0 deletions contracts/swap/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package swap
Eknir marked this conversation as resolved.
Show resolved Hide resolved

import (
"bytes"
"context"
"errors"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
chequebookFactory "github.com/ethersphere/go-sw3/contracts-v0-1-1/simpleswapfactory"
)

var (
// ErrNotDeployedByFactory is given when a contract was not deployed by the factory
ErrNotDeployedByFactory = errors.New("not deployed by factory")

// Deployments maps from network ids to deployed contract factories
Deployments = map[uint64]common.Address{
// Ropsten
3: common.HexToAddress("0x2e9C43E186eaF4fee10799d67e75f8CFc5BA3a0c"),
}
)

type simpleSwapFactory struct {
instance *chequebookFactory.SimpleSwapFactory
address common.Address
backend Backend
}

// SimpleSwapFactory interface defines the methods available for a factory contract for SimpleSwap
type SimpleSwapFactory interface {
// DeploySimpleSwap deploys a new SimpleSwap contract from the factory and returns the ready to use Contract abstraction
DeploySimpleSwap(auth *bind.TransactOpts, issuer common.Address, defaultHardDepositTimeoutDuration *big.Int) (Contract, error)
// VerifyContract verifies that the supplied address was deployed by this factory
VerifyContract(address common.Address) error
// VerifySelf verifies that this is a valid factory on the network
VerifySelf() error
}

// FactoryAt creates a SimpleSwapFactory instance for the given address and backend
func FactoryAt(address common.Address, backend Backend) (SimpleSwapFactory, error) {
Eknir marked this conversation as resolved.
Show resolved Hide resolved
simple, err := chequebookFactory.NewSimpleSwapFactory(address, backend)
if err != nil {
return nil, err
}
c := simpleSwapFactory{instance: simple, address: address, backend: backend}
return c, err
}

// FactoryAddressForNetwork gets the default factory address for a given network id
func FactoryAddressForNetwork(networkID uint64) (common.Address, error) {
address, ok := Deployments[networkID]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't know concurrency control here right, because this is only used at startup right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this only runs once during the constructor of Swap.

if !ok {
return common.Address{}, fmt.Errorf("no known factory contract for ethereum network %d", networkID)
}
return address, nil
}

// VerifySelf verifies that this is a valid factory on the network
func (sf simpleSwapFactory) VerifySelf() error {
code, err := sf.backend.CodeAt(context.Background(), sf.address, nil)
if err != nil {
return err
}

referenceCode := common.FromHex(chequebookFactory.SimpleSwapFactoryDeployedCode)
if !bytes.Equal(code, referenceCode) {
return errors.New("not a valid factory contract")
}

return nil
}

// DeploySimpleSwap deploys a new SimpleSwap contract from the factory and returns the ready to use Contract abstraction
func (sf simpleSwapFactory) DeploySimpleSwap(auth *bind.TransactOpts, issuer common.Address, defaultHardDepositTimeoutDuration *big.Int) (Contract, error) {
// for some reason the automatic gas estimation is too low
// this value was determind by deploying through truffle and rounding up to the next 100000
// the deployment cost should always be constant
auth.GasLimit = 1700000
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So how do you get to exactly this number and why is it a good value?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the real deployment cost (which is constant and can be determined by deploying through truffle) rounded up to the next 100000. I'll clarify this in a comment.

tx, err := sf.instance.DeploySimpleSwap(auth, issuer, defaultHardDepositTimeoutDuration)
if err != nil {
return nil, err
}

receipt, err := WaitFunc(auth, sf.backend, tx)
if err != nil {
return nil, err
}

// we iterate through the logs until we find the SimpleSwapDeployed event which contains the address of the new SimpleSwap contract
Copy link
Contributor

@holisticode holisticode Oct 9, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this ok to wait here until WaitFunc terminates? Couldn't that potentially be long? Wouldn't this block the entire boot process?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but that's how it was previously too (except there we were waiting for deployment of the chequebook directly).

mortelli marked this conversation as resolved.
Show resolved Hide resolved
address := common.Address{}
for _, log := range receipt.Logs {
if log.Address != sf.address {
continue
}
if event, err := sf.instance.ParseSimpleSwapDeployed(*log); err == nil {
address = event.ContractAddress
break
}
}
if (address == common.Address{}) {
return nil, errors.New("contract deployment failed")
}

simpleSwap, err := InstanceAt(address, sf.backend)
if err != nil {
return nil, err
}

return simpleSwap, nil
}

// VerifyContract verifies that the supplied address was deployed by this factory
func (sf simpleSwapFactory) VerifyContract(address common.Address) error {
isDeployed, err := sf.instance.DeployedContracts(&bind.CallOpts{}, address)
if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could just

if !isDeployed {
		return ErrNotDeployedByFactory
	}
	return err
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with this approach is that if err is not nil then isDeployed will also be false. So for actual errors it would also return ErrNotDeployedByFactory instead of err.

return err
}
if !isDeployed {
return ErrNotDeployedByFactory
}
return nil
}
34 changes: 9 additions & 25 deletions contracts/swap/swap.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
package swap

import (
"bytes"
"context"
"errors"
"math/big"
Expand All @@ -29,12 +28,10 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
contract "github.com/ethersphere/go-sw3/contracts-v0-1-0/simpleswap"
contract "github.com/ethersphere/go-sw3/contracts-v0-1-1/simpleswap"
)

var (
// ErrNotASwapContract is given when an address is verified not to have a SWAP contract based on its bytecode
ErrNotASwapContract = errors.New("not a swap contract")
// ErrTransactionReverted is given when the transaction that cashes a cheque is reverted
ErrTransactionReverted = errors.New("Transaction reverted")
)
Expand All @@ -48,7 +45,7 @@ type Backend interface {
// Contract interface defines the methods exported from the underlying go-bindings for the smart contract
type Contract interface {
// CashChequeBeneficiary cashes the cheque by the beneficiary
CashChequeBeneficiary(auth *bind.TransactOpts, backend Backend, beneficiary common.Address, cumulativePayout *big.Int, ownerSig []byte) (*CashChequeResult, *types.Receipt, error)
CashChequeBeneficiary(auth *bind.TransactOpts, beneficiary common.Address, cumulativePayout *big.Int, ownerSig []byte) (*CashChequeResult, *types.Receipt, error)
// ContractParams returns contract info (e.g. deployed address)
ContractParams() *Params
// Issuer returns the contract owner from the blockchain
Expand Down Expand Up @@ -78,12 +75,13 @@ type Params struct {
type simpleContract struct {
instance *contract.SimpleSwap
address common.Address
backend Backend
}

// Deploy deploys an instance of the underlying contract and returns its instance and the transaction identifier
func Deploy(auth *bind.TransactOpts, backend bind.ContractBackend, owner common.Address, harddepositTimeout time.Duration) (Contract, *types.Transaction, error) {
func Deploy(auth *bind.TransactOpts, backend Backend, owner common.Address, harddepositTimeout time.Duration) (Contract, *types.Transaction, error) {
addr, tx, instance, err := contract.DeploySimpleSwap(auth, backend, owner, big.NewInt(int64(harddepositTimeout)))
c := simpleContract{instance: instance, address: addr}
c := simpleContract{instance: instance, address: addr, backend: backend}
return c, tx, err
}

Expand All @@ -95,17 +93,17 @@ func InstanceAt(address common.Address, backend Backend) (Contract, error) {
if err != nil {
return nil, err
}
c := simpleContract{instance: instance, address: address}
c := simpleContract{instance: instance, address: address, backend: backend}
return c, err
}

// CashChequeBeneficiary cashes the cheque on the blockchain and blocks until the transaction is mined.
func (s simpleContract) CashChequeBeneficiary(auth *bind.TransactOpts, backend Backend, beneficiary common.Address, cumulativePayout *big.Int, ownerSig []byte) (*CashChequeResult, *types.Receipt, error) {
tx, err := s.instance.CashChequeBeneficiary(auth, beneficiary, cumulativePayout, ownerSig)
func (s simpleContract) CashChequeBeneficiary(opts *bind.TransactOpts, beneficiary common.Address, cumulativePayout *big.Int, ownerSig []byte) (*CashChequeResult, *types.Receipt, error) {
tx, err := s.instance.CashChequeBeneficiary(opts, beneficiary, cumulativePayout, ownerSig)
if err != nil {
return nil, nil, err
}
receipt, err := WaitFunc(auth, backend, tx)
receipt, err := WaitFunc(opts, s.backend, tx)
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -152,20 +150,6 @@ func (s simpleContract) PaidOut(opts *bind.CallOpts, addr common.Address) (*big.
return s.instance.PaidOut(opts, addr)
}

// ValidateCode checks that the on-chain code at address matches the expected swap
// contract code.
func ValidateCode(ctx context.Context, b bind.ContractBackend, address common.Address) error {
codeReadFromAddress, err := b.CodeAt(ctx, address, nil)
if err != nil {
return err
}
referenceCode := common.FromHex(contract.SimpleSwapDeployedCode)
if !bytes.Equal(codeReadFromAddress, referenceCode) {
return ErrNotASwapContract
}
return nil
}

// WaitFunc is the default function to wait for transactions
// We can overwrite this in tests so that we don't need to wait for mining
var WaitFunc = waitForTx
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ require (
github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c // indirect
github.com/elastic/gosigar v0.0.0-20180330100440-37f05ff46ffa // indirect
github.com/ethereum/go-ethereum v1.9.2
github.com/ethersphere/go-sw3 v0.1.0
github.com/ethersphere/go-sw3 v0.1.1
github.com/fatih/color v1.7.0 // indirect
github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc
github.com/gballet/go-libpcsclite v0.0.0-20190528105824-2fd9b619dd3c // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ github.com/elastic/gosigar v0.0.0-20180330100440-37f05ff46ffa/go.mod h1:cdorVVzy
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/ethereum/go-ethereum v1.9.2 h1:RMIHDO/diqXEgORSVzYx8xW9x2+S32PoAX5lQwya0Lw=
github.com/ethereum/go-ethereum v1.9.2/go.mod h1:PwpWDrCLZrV+tfrhqqF6kPknbISMHaJv9Ln3kPCZLwY=
github.com/ethersphere/go-sw3 v0.1.0 h1:XMaWxiBhFtrxfOel2tXmxuan3eCfFw/NcftV6sUVtIc=
github.com/ethersphere/go-sw3 v0.1.0/go.mod h1:HukT0aZ6QdW/d7zuD/0g5xlw6ewu9QeqHojxLDsaERQ=
github.com/ethersphere/go-sw3 v0.1.1 h1:czLnLSU0/XJLJt/GyPiEAds9YYnIgZZzfy+OQyiYQtk=
github.com/ethersphere/go-sw3 v0.1.1/go.mod h1:HukT0aZ6QdW/d7zuD/0g5xlw6ewu9QeqHojxLDsaERQ=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
Expand Down
2 changes: 1 addition & 1 deletion swap/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func (s *Swap) verifyHandshake(msg interface{}) error {
return ErrEmptyAddressInSignature
}

return contract.ValidateCode(context.Background(), s.backend, handshake.ContractAddress)
return s.chequebookFactory.VerifyContract(handshake.ContractAddress)
}

// run is the actual swap protocol run method
Expand Down
15 changes: 3 additions & 12 deletions swap/protocol_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,8 @@ it expects a handshake to take place between the two nodes
func TestHandshake(t *testing.T) {
var err error

testBackend := newTestBackend()
defer testBackend.Close()

// setup test swap object
swap, clean := newTestSwap(t, ownerKey, testBackend)
swap, clean := newTestSwap(t, ownerKey, nil)
defer clean()

ctx := context.Background()
Expand Down Expand Up @@ -214,11 +211,8 @@ func TestEmitCheque(t *testing.T) {
// when we reach the payment threshold
// It is the debitor who triggers cheques
func TestTriggerPaymentThreshold(t *testing.T) {
testBackend := newTestBackend()
defer testBackend.Close()

log.Debug("create test swap")
debitorSwap, clean := newTestSwap(t, ownerKey, testBackend)
debitorSwap, clean := newTestSwap(t, ownerKey, nil)
defer clean()

ctx := context.Background()
Expand Down Expand Up @@ -281,11 +275,8 @@ func TestTriggerPaymentThreshold(t *testing.T) {
// when we reach the disconnect threshold
// It is the creditor who triggers the disconnect from a overdraft creditor
func TestTriggerDisconnectThreshold(t *testing.T) {
testBackend := newTestBackend()
defer testBackend.Close()

log.Debug("create test swap")
creditorSwap, clean := newTestSwap(t, beneficiaryKey, testBackend)
creditorSwap, clean := newTestSwap(t, beneficiaryKey, nil)
defer clean()

// create a dummy pper
Expand Down
21 changes: 18 additions & 3 deletions swap/simulations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"testing"
"time"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
Expand All @@ -40,6 +41,8 @@ import (
"github.com/ethereum/go-ethereum/p2p/simulations"
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
"github.com/ethereum/go-ethereum/rpc"
contractFactory "github.com/ethersphere/go-sw3/contracts-v0-1-1/simpleswapfactory"
cswap "github.com/ethersphere/swarm/contracts/swap"
"github.com/ethersphere/swarm/network/simulation"
"github.com/ethersphere/swarm/p2p/protocols"
"github.com/ethersphere/swarm/state"
Expand Down Expand Up @@ -195,7 +198,7 @@ func newSharedBackendSwaps(t *testing.T, nodeCount int) (*swapSimulationParams,
}
keys[i] = key
addrs[i] = crypto.PubkeyToAddress(key.PublicKey)
alloc[addrs[i]] = core.GenesisAccount{Balance: big.NewInt(10000000000)}
alloc[addrs[i]] = core.GenesisAccount{Balance: big.NewInt(10000 * int64(RetrieveRequestPrice))}
dir, err := ioutil.TempDir("", fmt.Sprintf("swap_test_store_%x", addrs[i].Hex()))
if err != nil {
return nil, err
Expand All @@ -210,13 +213,25 @@ func newSharedBackendSwaps(t *testing.T, nodeCount int) (*swapSimulationParams,
// then create the single SimulatedBackend
gasLimit := uint64(8000000000)
defaultBackend := backends.NewSimulatedBackend(alloc, gasLimit)
testBackend := &swapTestBackend{SimulatedBackend: defaultBackend}
defaultBackend.Commit()

factoryAddress, _, _, err := contractFactory.DeploySimpleSwapFactory(bind.NewKeyedTransactor(keys[0]), defaultBackend)
if err != nil {
t.Fatalf("Error while deploying factory: %v", err)
}
defaultBackend.Commit()

testBackend := &swapTestBackend{SimulatedBackend: defaultBackend, factoryAddress: factoryAddress}
// finally, create all Swap instances for each node, which share the same backend
var owner *Owner
defParams := newDefaultParams(t)
for i := 0; i < nodeCount; i++ {
owner = createOwner(keys[i])
params.swaps[i] = newSwapInstance(stores[i], owner, testBackend, defParams)
factory, err := cswap.FactoryAt(testBackend.factoryAddress, testBackend)
if err != nil {
t.Fatal(err)
}
params.swaps[i] = newSwapInstance(stores[i], owner, testBackend, defParams, factory)
}

params.backend = testBackend
Expand Down
Loading