decred.org/dcrdex@v1.0.3/dex/networks/eth/tokens.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  	"fmt"
     8  	"math/big"
     9  	"os"
    10  	"os/user"
    11  	"path/filepath"
    12  
    13  	"decred.org/dcrdex/dex"
    14  	"github.com/ethereum/go-ethereum/common"
    15  )
    16  
    17  // Token is the definition of an ERC20 token, including all of its network and
    18  // version variants.
    19  type Token struct {
    20  	*dex.Token
    21  	// NetTokens is a mapping of token addresses for each network available.
    22  	NetTokens map[dex.Network]*NetToken `json:"netAddrs"`
    23  	// EVMFactor allows for arbitrary ERC20 decimals. For an ERC20 contract,
    24  	// the relation
    25  	//    math.Log10(UnitInfo.Conventional.ConversionFactor) + Token.EVMFactor = decimals
    26  	// should hold true.
    27  	// Since most assets will use a value of 9 here, a default value of 9 will
    28  	// be used in AtomicToEVM and EVMToAtomic if EVMFactor is not set.
    29  	EVMFactor *int64 `json:"evmFactor"` // default 9
    30  }
    31  
    32  // factor calculates the conversion factor to and from DEX atomic units to the
    33  // units used for EVM operations.
    34  func (t *Token) factor() *big.Int {
    35  	var evmFactor int64 = 9
    36  	if t.EVMFactor != nil {
    37  		evmFactor = *t.EVMFactor
    38  	}
    39  	return new(big.Int).Exp(big.NewInt(10), big.NewInt(evmFactor), nil)
    40  }
    41  
    42  // AtomicToEVM converts from DEX atomic units to EVM units.
    43  func (t *Token) AtomicToEVM(v uint64) *big.Int {
    44  	return new(big.Int).Mul(big.NewInt(int64(v)), t.factor())
    45  }
    46  
    47  // EVMToAtomic converts from raw EVM units to DEX atomic units.
    48  func (t *Token) EVMToAtomic(v *big.Int) uint64 {
    49  	vDEX := new(big.Int).Div(v, t.factor())
    50  	if vDEX.IsUint64() {
    51  		return vDEX.Uint64()
    52  	}
    53  	return 0
    54  }
    55  
    56  // NetToken are the addresses associated with the token and its versioned swap
    57  // contracts.
    58  type NetToken struct {
    59  	// Address is the token contract address.
    60  	Address common.Address `json:"address"`
    61  	// SwapContracts is the versioned swap contracts bound to the token address.
    62  	SwapContracts map[uint32]*SwapContract `json:"swapContracts"`
    63  }
    64  
    65  // SwapContract represents a single swap contract instance.
    66  type SwapContract struct {
    67  	Address common.Address
    68  	Gas     Gases
    69  }
    70  
    71  var Tokens = map[uint32]*Token{
    72  	usdcTokenID: {
    73  		EVMFactor: new(int64),
    74  		Token: &dex.Token{
    75  			ParentID: EthBipID,
    76  			Name:     "USDC",
    77  			UnitInfo: dex.UnitInfo{
    78  				AtomicUnit: "µUSD",
    79  				Conventional: dex.Denomination{
    80  					Unit:             "USDC",
    81  					ConversionFactor: 1e6,
    82  				},
    83  				Alternatives: []dex.Denomination{
    84  					{
    85  						Unit:             "cents",
    86  						ConversionFactor: 1e2,
    87  					},
    88  				},
    89  				FeeRateDenom: "gas",
    90  			},
    91  		},
    92  		NetTokens: map[dex.Network]*NetToken{
    93  			dex.Mainnet: {
    94  				Address: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), // https://etherscan.io/address/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
    95  				SwapContracts: map[uint32]*SwapContract{
    96  					0: { // https://etherscan.io/address/0x1bbd020ddd6dc01f974aa74d2d727b2a6782f32d#code
    97  						Address: common.HexToAddress("0x1bbd020DDD6dc01f974Aa74D2D727B2A6782F32D"),
    98  						// USDC's contract is upgradable, using a proxy call, so
    99  						// gas cost could change without notice, so we do not
   100  						// want to set limits too low, even with live estimates.
   101  						Gas: Gases{
   102  							Swap:      242_000,
   103  							SwapAdd:   146_400,
   104  							Redeem:    102_700,
   105  							RedeemAdd: 31_600,
   106  							Refund:    77_000,
   107  							Approve:   78_400,
   108  							Transfer:  85_100,
   109  						},
   110  					},
   111  				},
   112  			},
   113  			dex.Testnet: {
   114  				Address: common.HexToAddress("0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"),
   115  				SwapContracts: map[uint32]*SwapContract{
   116  					0: {
   117  						Address: common.HexToAddress("0xFDEF71277d4518Ca3373CA06562100FDB0050211"), // tx 0x29e0e146e8c956156f61e86b970de97eb8958bf10671018085dd4816c566a2a3
   118  						Gas: Gases{
   119  							// Results from client's GetGasEstimates.
   120  							//
   121  							// First swap used 184413 gas Recommended Gases.Swap = 239736
   122  							// 	2 additional swaps averaged 112591 gas each. Recommended Gases.SwapAdd = 146368
   123  							// 	[184413 297004 409595]
   124  							// First redeem used 75659 gas. Recommended Gases.Redeem = 98356
   125  							// 	2 additional redeems averaged 31617 gas each. recommended Gases.RedeemAdd = 41102
   126  							// 	[75659 107276 138893]
   127  							// Average of 3 refunds: 61022. Recommended Gases.Refund = 79328
   128  							// 	[60829 61410 60829]
   129  							// Average of 2 approvals: 55773. Recommended Gases.Approve = 72504
   130  							// 	[55773 55773]
   131  							// Average of 1 transfers: 62135. Recommended Gases.Transfer = 80775
   132  							// 	[62135]
   133  							//
   134  							// Approve is the gas used to call the approve
   135  							// method of the contract. For Approve transactions,
   136  							// the very first approval for an account-spender
   137  							// pair takes more than subsequent approvals. The
   138  							// results are repeated for a different account's
   139  							// first approvals on the same contract, so it's not
   140  							// just the global first.
   141  							// Average of 5 approvals: 46222
   142  							//   [59902 42802 42802 42802 42802]
   143  							//
   144  							//
   145  							// The first transfer to an address the contract has
   146  							// not seen before will insert a new key into the
   147  							// contract's token map. The amount of extra gas
   148  							// this consumes seems to depend on the size of the
   149  							// map.
   150  							// Average of 5 transfers: 51820
   151  							//   [65500 48400 48400 48400 48400]
   152  							//
   153  							// Then buffered by about 30%...
   154  							Swap:      242_000, // actual ~187,880 -- https://goerli.etherscan.io/tx/0x352baccafa96bb09d5c118f8dcce26e34267beb8bcda9c026f8d5353abea50fd, verified on mainnet at 188,013 gas
   155  							SwapAdd:   146_400, // actual ~112,639 (300,519 for 2) -- https://goerli.etherscan.io/tx/0x97f9a1ed69883a6e701f37883ef74d79a709e0edfc4a45987fa659700663f40e
   156  							Redeem:    109_000, // actual ~83,850 (initial receive, subsequent ~79,012) -- https://goerli.etherscan.io/tx/0x96f007036b01eb2e44615dc67d3e99748bc133496187348b2af26834f46bfdc8, verified on mainnet at 79,113 gas for subsequent
   157  							RedeemAdd: 41_102,  // actual ~31,641 (110,653 for 2) -- https://goerli.etherscan.io/tx/0xcf717512796868273ed93c37fa139973c9b8305a736c4a3b50ac9f35ae747f99
   158  							Refund:    79_328,  // actual ~59,152 -- https://goerli.etherscan.io/tx/0xc5692ad0e6d86b721af75ff3b4b7c2e17d939918db030ebf5444ccf840c7a90b
   159  							Approve:   78_400,  // actual ~60,190 (initial) -- https://goerli.etherscan.io/tx/0xd695fd174dede7bb798488ead7fed5ef33bcd79932b0fa35db0d17c84c97a8a1, verified on mainnet at 60,311
   160  							Transfer:  85_100,  // actual ~65,524 (initial receive, subsequent 48,424)
   161  						},
   162  					},
   163  				},
   164  			},
   165  			dex.Simnet: {
   166  				Address: common.Address{},
   167  				SwapContracts: map[uint32]*SwapContract{
   168  					0: {
   169  						Address: common.Address{},
   170  						Gas: Gases{
   171  							Swap:      242_000,
   172  							SwapAdd:   146_400,
   173  							Redeem:    109_000,
   174  							RedeemAdd: 31_600,
   175  							Refund:    77_000,
   176  							Approve:   78_400,
   177  							Transfer:  85_100,
   178  						}},
   179  				},
   180  			},
   181  		},
   182  	},
   183  	usdtTokenID: {
   184  		EVMFactor: new(int64),
   185  		Token: &dex.Token{
   186  			ParentID: EthBipID,
   187  			Name:     "Tether",
   188  			UnitInfo: dex.UnitInfo{
   189  				AtomicUnit: "microUSD",
   190  				Conventional: dex.Denomination{
   191  					Unit:             "USDT",
   192  					ConversionFactor: 1e6,
   193  				},
   194  			},
   195  		},
   196  		NetTokens: map[dex.Network]*NetToken{
   197  			dex.Mainnet: {
   198  				Address: common.HexToAddress("0xdac17f958d2ee523a2206206994597c13d831ec7"), // https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7
   199  				SwapContracts: map[uint32]*SwapContract{
   200  					0: {
   201  						// swap contract: https://etherscan.io/address/0x97a53fEF7854f4CB846F2eaCCf847229F1E10e4f
   202  						Address: common.HexToAddress("0x97a53fEF7854f4CB846F2eaCCf847229F1E10e4f"),
   203  						// USDT's contract is upgradable, using a proxy call, so
   204  						// gas cost could change without notice, so we do not
   205  						// want to set limits too low, even with live estimates.
   206  						Gas: Gases{
   207  							// Results from client's GetGasEstimates.
   208  							//
   209  							// First swap used 181405 gas Recommended Gases.Swap = 235826
   210  							//  1 additional swaps averaged 112591 gas each. Recommended Gases.SwapAdd = 146368
   211  							//  [181405 293996]
   212  							// First redeem used 76487 gas. Recommended Gases.Redeem = 99433
   213  							//  1 additional redeems averaged 31617 gas each. recommended Gases.RedeemAdd = 41102
   214  							//  [76487 108104]
   215  							// Average of 2 refunds: 61963. Recommended Gases.Refund = 80551
   216  							//  [61684 62242]
   217  							// Average of 2 approvals: 48897. Recommended Gases.Approve = 63566
   218  							//  [48897 48897]
   219  							// Average of 1 transfers: 63173. Recommended Gases.Transfer = 82124
   220  							//  [63173]
   221  							Swap:      235_826,
   222  							SwapAdd:   146_368,
   223  							Redeem:    99_433,
   224  							RedeemAdd: 41_102,
   225  							Refund:    80_551,
   226  							Approve:   63_566,
   227  							Transfer:  82_124,
   228  						},
   229  					},
   230  				},
   231  			},
   232  			dex.Testnet: {
   233  				Address: common.HexToAddress("0x7169D38820dfd117C3FA1f22a697dBA58d90BA06"),
   234  				SwapContracts: map[uint32]*SwapContract{
   235  					0: {
   236  						Address: common.HexToAddress("0x97a53fEF7854f4CB846F2eaCCf847229F1E10e4f"),
   237  						Gas: Gases{
   238  							// Results from client's GetGasEstimates.
   239  							//
   240  							// First swap used 181441 gas Recommended Gases.Swap = 235873
   241  							//   4 additional swaps averaged 112591 gas each. Recommended Gases.SwapAdd = 146368
   242  							//   [181441 294032 406623 519202 631805]
   243  							// First redeem used 76530 gas. Recommended Gases.Redeem = 99489
   244  							//   4 additional redeems averaged 31626 gas each. recommended Gases.RedeemAdd = 41113
   245  							//   [76530 108159 139800 171418 203035]
   246  							// Average of 5 refunds: 62183. Recommended Gases.Refund = 80837
   247  							//   [61739 62297 62297 62285 62297]
   248  							// Average of 2 approvals: 48930. Recommended Gases.Approve = 63609
   249  							//   [48930 48930]
   250  							// Average of 1 transfers: 63228. Recommended Gases.Transfer = 82196
   251  							//   [63228]
   252  							Swap:      235_873,
   253  							SwapAdd:   146_368,
   254  							Redeem:    99_489,
   255  							RedeemAdd: 41_113,
   256  							Refund:    80_837,
   257  							Approve:   63_609,
   258  							Transfer:  82_196,
   259  						},
   260  					},
   261  				},
   262  			},
   263  			dex.Simnet: {
   264  				Address: common.Address{},
   265  				SwapContracts: map[uint32]*SwapContract{
   266  					0: {
   267  						Address: common.Address{},
   268  						Gas: Gases{
   269  							Swap:      242_000,
   270  							SwapAdd:   146_400,
   271  							Redeem:    109_000,
   272  							RedeemAdd: 31_600,
   273  							Refund:    77_000,
   274  							Approve:   78_400,
   275  							Transfer:  85_100,
   276  						}},
   277  				},
   278  			},
   279  		},
   280  	},
   281  	maticTokenID: {
   282  		Token: &dex.Token{
   283  			ParentID: EthBipID,
   284  			Name:     "MATIC",
   285  			UnitInfo: dex.UnitInfo{
   286  				AtomicUnit: "gwei",
   287  				Conventional: dex.Denomination{
   288  					Unit:             "MATIC",
   289  					ConversionFactor: 1e9,
   290  				},
   291  				Alternatives: []dex.Denomination{
   292  					{
   293  						Unit:             "Szabos",
   294  						ConversionFactor: 1e6,
   295  					},
   296  					{
   297  						Unit:             "Finneys",
   298  						ConversionFactor: 1e3,
   299  					},
   300  				},
   301  				FeeRateDenom: "gas",
   302  			},
   303  		},
   304  		NetTokens: map[dex.Network]*NetToken{
   305  			dex.Mainnet: {
   306  				Address: common.HexToAddress("0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0"), // https://etherscan.io/address/0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0
   307  				SwapContracts: map[uint32]*SwapContract{
   308  					0: {
   309  						// swap contract: https://etherscan.io/address/0x9572727D79FD074D3Ac731c584bf51dCF7459C12
   310  						Address: common.HexToAddress("0x9572727D79FD074D3Ac731c584bf51dCF7459C12"),
   311  						Gas: Gases{
   312  							// Results from client's GetGasEstimates.
   313  							//
   314  							//	First swap used 178058 gas Recommended Gases.Swap = 231475
   315  							//	 1 additional swaps averaged 112627 gas each. Recommended Gases.SwapAdd = 146415
   316  							//		 178058 290685]
   317  							// First redeem used 67456 gas. Recommended Gases.Redeem = 87692
   318  							//	 1 additional redeems averaged 31641 gas each. recommended Gases.RedeemAdd = 41133
   319  							//		 67456 99097]
   320  							// Average of 2 refunds: 52875. Recommended Gases.Refund = 68737
   321  							//		 52563 53187]
   322  							// Average of 2 approvals: 48764. Recommended Gases.Approve = 63393
   323  							//		 48764 48764]
   324  							// Average of 1 transfers: 53944. Recommended Gases.Transfer = 70127
   325  							//		 53944]
   326  
   327  							Swap:      231_475,
   328  							SwapAdd:   146_415,
   329  							Redeem:    87_692,
   330  							RedeemAdd: 41_133,
   331  							Refund:    68_737,
   332  							Approve:   63_393,
   333  							Transfer:  70_127,
   334  						},
   335  					},
   336  				},
   337  			},
   338  		},
   339  	},
   340  }
   341  
   342  // MaybeReadSimnetAddrs attempts to read the info files generated by the eth
   343  // simnet harness to populate swap contract and token addresses in
   344  // ContractAddresses and Tokens.
   345  func MaybeReadSimnetAddrs() {
   346  	MaybeReadSimnetAddrsDir("eth", ContractAddresses, MultiBalanceAddresses, Tokens[usdcTokenID].NetTokens[dex.Simnet], Tokens[usdtTokenID].NetTokens[dex.Simnet])
   347  }
   348  
   349  func MaybeReadSimnetAddrsDir(
   350  	dir string,
   351  	contractsAddrs map[uint32]map[dex.Network]common.Address,
   352  	multiBalandAddresses map[dex.Network]common.Address,
   353  	usdcToken *NetToken,
   354  	usdtToken *NetToken,
   355  ) {
   356  
   357  	usr, err := user.Current()
   358  	if err != nil {
   359  		return
   360  	}
   361  
   362  	harnessDir := filepath.Join(usr.HomeDir, "dextest", dir)
   363  	fi, err := os.Stat(harnessDir)
   364  	if err != nil {
   365  		return
   366  	}
   367  	if !fi.IsDir() {
   368  		return
   369  	}
   370  
   371  	ethSwapContractAddrFile := filepath.Join(harnessDir, "eth_swap_contract_address.txt")
   372  	testUSDCSwapContractAddrFile := filepath.Join(harnessDir, "usdc_swap_contract_address.txt")
   373  	testUSDCContractAddrFile := filepath.Join(harnessDir, "test_usdc_contract_address.txt")
   374  	testUSDTSwapContractAddrFile := filepath.Join(harnessDir, "usdt_swap_contract_address.txt")
   375  	testUSDTContractAddrFile := filepath.Join(harnessDir, "test_usdt_contract_address.txt")
   376  	multiBalanceContractAddrFile := filepath.Join(harnessDir, "multibalance_address.txt")
   377  
   378  	contractsAddrs[0][dex.Simnet] = maybeGetContractAddrFromFile(ethSwapContractAddrFile)
   379  	multiBalandAddresses[dex.Simnet] = maybeGetContractAddrFromFile(multiBalanceContractAddrFile)
   380  
   381  	usdcToken.SwapContracts[0].Address = maybeGetContractAddrFromFile(testUSDCSwapContractAddrFile)
   382  	usdcToken.Address = maybeGetContractAddrFromFile(testUSDCContractAddrFile)
   383  
   384  	usdtToken.SwapContracts[0].Address = maybeGetContractAddrFromFile(testUSDTSwapContractAddrFile)
   385  	usdtToken.Address = maybeGetContractAddrFromFile(testUSDTContractAddrFile)
   386  }
   387  
   388  func maybeGetContractAddrFromFile(fileName string) (addr common.Address) {
   389  	addrBytes, err := os.ReadFile(fileName)
   390  	if err != nil {
   391  		if os.IsNotExist(err) {
   392  			return
   393  		}
   394  		fmt.Printf("error reading contract address: %v \n", err)
   395  		return
   396  	}
   397  	addrLen := len(addrBytes)
   398  	if addrLen == 0 {
   399  		fmt.Printf("no contract address found at %v \n", fileName)
   400  		return
   401  	}
   402  	addrStr := string(addrBytes[:addrLen-1])
   403  	return common.HexToAddress(addrStr)
   404  }