decred.org/dcrdex@v1.0.5/server/asset/eth/tokener.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package eth
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"math/big"
    10  
    11  	"decred.org/dcrdex/dex"
    12  	"decred.org/dcrdex/dex/networks/erc20"
    13  	erc20v0 "decred.org/dcrdex/dex/networks/erc20/contracts/v0"
    14  	dexeth "decred.org/dcrdex/dex/networks/eth"
    15  	swapv0 "decred.org/dcrdex/dex/networks/eth/contracts/v0"
    16  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    17  	"github.com/ethereum/go-ethereum/common"
    18  )
    19  
    20  // swapContract is a generic source of swap contract data.
    21  type swapContract interface {
    22  	Swap(context.Context, [32]byte) (*dexeth.SwapState, error)
    23  }
    24  
    25  // erc2Contract exposes methods of a token's ERC20 contract.
    26  type erc20Contract interface {
    27  	BalanceOf(*bind.CallOpts, common.Address) (*big.Int, error)
    28  }
    29  
    30  // tokener is a contract data manager for a token.
    31  type tokener struct {
    32  	*VersionedToken
    33  	swapContract
    34  	erc20Contract
    35  	contractAddr, tokenAddr common.Address
    36  }
    37  
    38  // newTokener is a constructor for a tokener.
    39  func newTokener(ctx context.Context, vToken *VersionedToken, net dex.Network, be bind.ContractBackend) (*tokener, error) {
    40  	netToken, swapContract, err := networkToken(vToken, net)
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  
    45  	if vToken.Ver != 0 {
    46  		return nil, fmt.Errorf("only version 0 contracts supported")
    47  	}
    48  
    49  	es, err := erc20v0.NewERC20Swap(swapContract.Address, be)
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  
    54  	erc20, err := erc20.NewIERC20(netToken.Address, be)
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  
    59  	boundAddr, err := es.TokenAddress(readOnlyCallOpts(ctx, false))
    60  	if err != nil {
    61  		return nil, fmt.Errorf("error retrieving bound address for %s version %d contract: %w",
    62  			vToken.Name, vToken.Ver, err)
    63  	}
    64  
    65  	if boundAddr != netToken.Address {
    66  		return nil, fmt.Errorf("wrong bound address for %s version %d contract. wanted %s, got %s",
    67  			vToken.Name, vToken.Ver, netToken.Address, boundAddr)
    68  	}
    69  
    70  	tkn := &tokener{
    71  		VersionedToken: vToken,
    72  		swapContract:   &swapSourceV0{es},
    73  		erc20Contract:  erc20,
    74  		contractAddr:   swapContract.Address,
    75  		tokenAddr:      netToken.Address,
    76  	}
    77  
    78  	return tkn, nil
    79  }
    80  
    81  // transferred calculates the value transferred using the token contract's
    82  // transfer method.
    83  func (t *tokener) transferred(txData []byte) *big.Int {
    84  	_, out, err := erc20.ParseTransferData(txData)
    85  	if err != nil {
    86  		return nil
    87  	}
    88  	return out
    89  }
    90  
    91  // swapped calculates the value sent to the swap contracts initiate method.
    92  func (t *tokener) swapped(txData []byte) *big.Int {
    93  	inits, err := dexeth.ParseInitiateData(txData, t.Ver)
    94  	if err != nil {
    95  		return nil
    96  	}
    97  	v := new(big.Int)
    98  	for _, init := range inits {
    99  		v.Add(v, init.Value)
   100  	}
   101  	return v
   102  }
   103  
   104  // balanceOf checks the account's token balance.
   105  func (t *tokener) balanceOf(ctx context.Context, addr common.Address) (*big.Int, error) {
   106  	return t.BalanceOf(readOnlyCallOpts(ctx, false), addr)
   107  }
   108  
   109  // swapContractV0 represents a version 0 swap contract for ETH or a token.
   110  type swapContractV0 interface {
   111  	Swap(opts *bind.CallOpts, secretHash [32]byte) (swapv0.ETHSwapSwap, error)
   112  }
   113  
   114  // swapSourceV0 wraps a swapContractV0 and translates the swap data to satisfy
   115  // swapSource.
   116  type swapSourceV0 struct {
   117  	contract swapContractV0 // *swapv0.ETHSwap or *erc20v0.ERCSwap
   118  }
   119  
   120  // Swap translates the version 0 swap data to the more general SwapState to
   121  // satisfy the swapSource interface.
   122  func (s *swapSourceV0) Swap(ctx context.Context, secretHash [32]byte) (*dexeth.SwapState, error) {
   123  	state, err := s.contract.Swap(readOnlyCallOpts(ctx, true), secretHash)
   124  	if err != nil {
   125  		return nil, fmt.Errorf("Swap error: %w", err)
   126  	}
   127  	return dexeth.SwapStateFromV0(&state), nil
   128  }
   129  
   130  // readOnlyCallOpts is the CallOpts used for read-only contract method calls.
   131  func readOnlyCallOpts(ctx context.Context, includePending bool) *bind.CallOpts {
   132  	return &bind.CallOpts{
   133  		Pending: includePending,
   134  		Context: ctx,
   135  	}
   136  }