Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multi: Add ETHSwapV0. #1019

Merged
merged 6 commits into from
Aug 3, 2021
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
59 changes: 53 additions & 6 deletions client/asset/eth/eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"math/big"
"strings"
Expand All @@ -16,10 +18,12 @@ import (

"decred.org/dcrdex/client/asset"
"decred.org/dcrdex/dex"
swap "decred.org/dcrdex/dex/networks/eth"
dexeth "decred.org/dcrdex/server/asset/eth"
"github.com/decred/dcrd/dcrutil/v4"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/node"
Expand Down Expand Up @@ -104,14 +108,15 @@ type rawWallet struct {
type ethFetcher interface {
accounts() []*accounts.Account
addPeer(ctx context.Context, peer string) error
balance(ctx context.Context, acct *accounts.Account) (*big.Int, error)
balance(ctx context.Context, addr common.Address) (*big.Int, error)
bestBlockHash(ctx context.Context) (common.Hash, error)
bestHeader(ctx context.Context) (*types.Header, error)
block(ctx context.Context, hash common.Hash) (*types.Block, error)
blockNumber(ctx context.Context) (uint64, error)
connect(ctx context.Context, node *node.Node, contractAddr common.Address) error
importAccount(pw string, privKeyB []byte) (*accounts.Account, error)
listWallets(ctx context.Context) ([]rawWallet, error)
initiate(opts *bind.TransactOpts, netID int64, refundTimestamp int64, secretHash [32]byte, participant common.Address) (*types.Transaction, error)
lock(ctx context.Context, acct *accounts.Account) error
nodeInfo(ctx context.Context) (*p2p.NodeInfo, error)
pendingTransactions(ctx context.Context) ([]*types.Transaction, error)
Expand All @@ -120,6 +125,9 @@ type ethFetcher interface {
sendTransaction(ctx context.Context, tx map[string]string) (common.Hash, error)
shutdown()
syncProgress(ctx context.Context) (*ethereum.SyncProgress, error)
redeem(opts *bind.TransactOpts, netID int64, secret, secretHash [32]byte) (*types.Transaction, error)
refund(opts *bind.TransactOpts, netID int64, secretHash [32]byte) (*types.Transaction, error)
swap(ctx context.Context, from *accounts.Account, secretHash [32]byte) (*swap.ETHSwapSwap, error)
unlock(ctx context.Context, pw string, acct *accounts.Account) error
}

Expand Down Expand Up @@ -172,7 +180,6 @@ func NewWallet(assetCFG *asset.WalletConfig, logger dex.Logger, network dex.Netw
log: logger,
tipChange: assetCFG.TipChange,
internalNode: node,
acct: new(accounts.Account),
}, nil
}

Expand All @@ -185,12 +192,18 @@ func (eth *ExchangeWallet) shutdown() {
// Connect connects to the node RPC server. A dex.Connector.
func (eth *ExchangeWallet) Connect(ctx context.Context) (*sync.WaitGroup, error) {
c := rpcclient{}
if err := c.connect(ctx, eth.internalNode, mainnetContractAddr); err != nil {
err := c.connect(ctx, eth.internalNode, mainnetContractAddr)
if err != nil {
return nil, err
}
eth.node = &c
eth.ctx = ctx

eth.acct, err = eth.initAccount()
if err != nil {
return nil, err
}

// Initialize the best block.
bestHash, err := eth.node.bestBlockHash(ctx)
if err != nil {
Expand All @@ -217,6 +230,37 @@ func (eth *ExchangeWallet) Connect(ctx context.Context) (*sync.WaitGroup, error)
return &wg, nil
}

// initAccount checks to see if the internal client has an account. If found
// returns the account. If not it imports the account via private key.
//
// Currently this only imports a test account. However, in the future this will
// need to be created deterministically from the app seed.
func (eth *ExchangeWallet) initAccount() (*accounts.Account, error) {
testAcctAddr := common.HexToAddress("b6de8bb5ed28e6be6d671975cad20c03931be981")
accts := eth.node.accounts()
for _, acct := range accts {
if bytes.Equal(acct.Address[:], testAcctAddr[:]) {
return acct, nil
}
}
testAcctPrivHex := "0695b9347a4dc096ae5c6f1935380ceba550c70b112f1323c211bade4d11651b"
pw := "abc"
privB, err := hex.DecodeString(testAcctPrivHex)
if err != nil {
return nil, err
}
acct, err := eth.node.importAccount(pw, privB)
if err != nil {
return nil, err
}
// core expects an account to be unlocked during initialization.
err = eth.node.unlock(eth.ctx, pw, acct)
if err != nil {
return nil, err
}
return acct, nil
}

// OwnsAddress indicates if an address belongs to the wallet.
//
// In Ethereum, an address is an account.
Expand All @@ -229,7 +273,10 @@ func (eth *ExchangeWallet) OwnsAddress(address string) (bool, error) {
//
// TODO: Return Immature and Locked values.
func (eth *ExchangeWallet) Balance() (*asset.Balance, error) {
bigBal, err := eth.node.balance(eth.ctx, eth.acct)
if eth.acct == nil {
return nil, errors.New("account not set")
}
bigBal, err := eth.node.balance(eth.ctx, eth.acct.Address)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -394,12 +441,12 @@ func (*ExchangeWallet) PayFee(address string, regFee uint64) (asset.Coin, error)
}

// sendToAddr sends funds from acct to addr.
func (eth *ExchangeWallet) sendToAddr(addr common.Address, amt, gasFee *big.Int) (common.Hash, error) {
func (eth *ExchangeWallet) sendToAddr(addr common.Address, amt, gasPrice *big.Int) (common.Hash, error) {
tx := map[string]string{
"from": fmt.Sprintf("0x%x", eth.acct.Address),
"to": fmt.Sprintf("0x%x", addr),
"value": fmt.Sprintf("0x%x", amt),
"gasPrice": fmt.Sprintf("0x%x", gasFee),
"gasPrice": fmt.Sprintf("0x%x", gasPrice),
}
return eth.node.sendTransaction(eth.ctx, tx)
}
Expand Down
17 changes: 16 additions & 1 deletion client/asset/eth/eth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import (
"time"

"decred.org/dcrdex/dex"
swap "decred.org/dcrdex/dex/networks/eth"
dexeth "decred.org/dcrdex/server/asset/eth"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/node"
Expand Down Expand Up @@ -60,7 +62,7 @@ func (n *testNode) block(ctx context.Context, hash common.Hash) (*types.Block, e
func (n *testNode) accounts() []*accounts.Account {
return nil
}
func (n *testNode) balance(ctx context.Context, acct *accounts.Account) (*big.Int, error) {
func (n *testNode) balance(ctx context.Context, acct common.Address) (*big.Int, error) {
return n.bal, n.balErr
}
func (n *testNode) sendTransaction(ctx context.Context, tx map[string]string) (common.Hash, error) {
Expand Down Expand Up @@ -96,6 +98,18 @@ func (n *testNode) syncProgress(ctx context.Context) (*ethereum.SyncProgress, er
func (n *testNode) pendingTransactions(ctx context.Context) ([]*types.Transaction, error) {
return nil, nil
}
func (n *testNode) initiate(opts *bind.TransactOpts, netID int64, refundTimestamp int64, secretHash [32]byte, participant common.Address) (*types.Transaction, error) {
return nil, nil
}
func (n *testNode) redeem(opts *bind.TransactOpts, netID int64, secret, secretHash [32]byte) (*types.Transaction, error) {
return nil, nil
}
func (n *testNode) refund(opts *bind.TransactOpts, netID int64, secretHash [32]byte) (*types.Transaction, error) {
return nil, nil
}
func (n *testNode) swap(ctx context.Context, from *accounts.Account, secretHash [32]byte) (*swap.ETHSwapSwap, error) {
return nil, nil
}
func (n *testNode) transactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
return nil, nil
}
Expand Down Expand Up @@ -316,6 +330,7 @@ func TestBalance(t *testing.T) {
node: node,
ctx: ctx,
log: tLogger,
acct: new(accounts.Account),
}
bal, err := eth.Balance()
cancel()
Expand Down
4 changes: 2 additions & 2 deletions client/asset/eth/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (el *ethLogger) New(ctx ...interface{}) log.Logger {
// to avoid null pointer errors in case geth ever uses that function.
type dummyHandler struct{}

// Log does nothing and return nil.
// Log does nothing and returns nil.
func (dummyHandler) Log(r *log.Record) error {
return nil
}
Expand Down Expand Up @@ -119,7 +119,7 @@ func (el *ethLogger) Error(msg string, ctx ...interface{}) {

// Crit logs at critical level.
func (el *ethLogger) Crit(msg string, ctx ...interface{}) {
el.dl.Critical(formatEthLog(msg, ctx...))
el.dl.Critical(formatEthLog(msg, ctx...)...)
}

// Check that *ethLogger satisfies the log.Logger interface.
Expand Down
82 changes: 78 additions & 4 deletions client/asset/eth/rpcclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import (
"fmt"
"math/big"

swap "decred.org/dcrdex/dex/networks/eth"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
Expand All @@ -31,18 +33,23 @@ type rpcclient struct {
// ec wraps the client with some useful calls.
ec *ethclient.Client
n *node.Node
es *swap.ETHSwap
}

// connect connects to a node. It then wraps ethclient's client and
// bundles commands in a form we can easily use.
func (c *rpcclient) connect(ctx context.Context, node *node.Node, contractAddr common.Address) error { // contractAddr will be used soonTM
func (c *rpcclient) connect(ctx context.Context, node *node.Node, contractAddr common.Address) error {
client, err := node.Attach()
if err != nil {
return fmt.Errorf("unable to dial rpc: %v", err)
}
c.c = client
c.ec = ethclient.NewClient(client)
c.n = node
c.es, err = swap.NewETHSwap(contractAddr, c.ec)
if err != nil {
return fmt.Errorf("unable to find swap contract: %v", err)
}
return nil
}

Expand Down Expand Up @@ -96,9 +103,9 @@ func (c *rpcclient) accounts() []*accounts.Account {
return accts
}

// balance gets the current balance of an account.
func (c *rpcclient) balance(ctx context.Context, acct *accounts.Account) (*big.Int, error) {
return c.ec.BalanceAt(ctx, acct.Address, nil)
// balance gets the current balance of an address.
func (c *rpcclient) balance(ctx context.Context, addr common.Address) (*big.Int, error) {
return c.ec.BalanceAt(ctx, addr, nil)
}

// unlock uses a raw request to unlock an account indefinitely.
Expand Down Expand Up @@ -203,3 +210,70 @@ func (c *rpcclient) peers(ctx context.Context) ([]*p2p.PeerInfo, error) {
}
return peers, nil
}

// swap gets a swap keyed by secretHash in the contract.
func (c *rpcclient) swap(ctx context.Context, from *accounts.Account, secretHash [32]byte) (*swap.ETHSwapSwap, error) {
callOpts := &bind.CallOpts{
Pending: true,
From: from.Address,
Context: ctx,
}
swap, err := c.es.Swap(callOpts, secretHash)
if err != nil {
return nil, err
}
return &swap, nil
}

// wallet returns a wallet that owns acct from an ethereum wallet.
func (c *rpcclient) wallet(acct accounts.Account) (accounts.Wallet, error) {
wallet, err := c.n.AccountManager().Find(acct)
if err != nil {
return nil, fmt.Errorf("error finding wallet for account %s: %v \n", acct.Address, err)
}
return wallet, nil
}

func (c *rpcclient) addSignerToOpts(txOpts *bind.TransactOpts, netID int64) error {
wallet, err := c.wallet(accounts.Account{Address: txOpts.From})
if err != nil {
return err
}
txOpts.Signer = func(addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
return wallet.SignTx(accounts.Account{Address: addr}, tx, big.NewInt(netID))
}
return nil
}

// initiate creates a swap contract. The initiator will be the account at
// txOpts.From. Any on-chain failure, such as this secret hash already existing
// in the swaps map, will not cause this to error.
func (c *rpcclient) initiate(txOpts *bind.TransactOpts, netID int64, refundTimestamp int64, secretHash [32]byte, participant common.Address) (*types.Transaction, error) {
err := c.addSignerToOpts(txOpts, netID)
if err != nil {
return nil, err
}
return c.es.Initiate(txOpts, big.NewInt(refundTimestamp), secretHash, participant)
}

// redeem redeems a swap contract. The redeemer will be the account at txOpts.From.
// Any on-chain failure, such as this secret not matching the hash, will not cause
// this to error.
func (c *rpcclient) redeem(txOpts *bind.TransactOpts, netID int64, secret, secretHash [32]byte) (*types.Transaction, error) {
err := c.addSignerToOpts(txOpts, netID)
if err != nil {
return nil, err
}
return c.es.Redeem(txOpts, secret, secretHash)
}

// refund refunds a swap contract. The refunder will be the account at txOpts.From.
// Any on-chain failure, such as the locktime not being past, will not cause
// this to error.
func (c *rpcclient) refund(txOpts *bind.TransactOpts, netID int64, secretHash [32]byte) (*types.Transaction, error) {
err := c.addSignerToOpts(txOpts, netID)
if err != nil {
return nil, err
}
return c.es.Refund(txOpts, secretHash)
}
Loading