decred.org/dcrdex@v1.0.5/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 }