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 }