|
| 1 | +import { ethers } from "hardhat"; |
| 2 | +import { BigNumber } from "ethers"; |
| 3 | +import axios from "axios"; |
| 4 | +import qs from "qs"; |
| 5 | + |
| 6 | +const MAX_RETRIES = 40; |
| 7 | +const RETRY_STATUSES = [503, 429]; |
| 8 | +const API_QUOTE_URL = "https://api.0x.org/swap/v1/quote"; |
| 9 | + |
| 10 | +export const sleep = (ms: number) => { |
| 11 | + return new Promise((resolve) => setTimeout(resolve, ms)); |
| 12 | +}; |
| 13 | + |
| 14 | +async function getQuote(params: any, retryCount: number = 0): Promise<any> { |
| 15 | + try { |
| 16 | + const url = `${API_QUOTE_URL}?${qs.stringify(params)}`; |
| 17 | + const response = await axios(url); |
| 18 | + return response.data; |
| 19 | + } catch (error) { |
| 20 | + if (RETRY_STATUSES.includes(error.response?.status) && retryCount < MAX_RETRIES) { |
| 21 | + await sleep(1000); |
| 22 | + return await getQuote(params, retryCount + 1); |
| 23 | + } else { |
| 24 | + throw error; |
| 25 | + } |
| 26 | + } |
| 27 | +} |
| 28 | + |
| 29 | +async function getQuotes( |
| 30 | + positions: any[], |
| 31 | + inputToken: string, |
| 32 | + setAmount: number, |
| 33 | + wethStage: boolean, |
| 34 | +) { |
| 35 | + const componentSwapInputToken = wethStage ? "WETH" : inputToken; |
| 36 | + const componentInputTokenAddress = TOKEN_ADDRESSES[componentSwapInputToken]; |
| 37 | + const quotes = await getPositionQuotes(positions, componentInputTokenAddress, setAmount); |
| 38 | + if (wethStage) { |
| 39 | + const wethBuyAmount = quotes.reduce( |
| 40 | + (sum: BigNumber | number, quote: any) => BigNumber.from(quote.sellAmount).add(sum), |
| 41 | + 0, |
| 42 | + ); |
| 43 | + const wethQuote = await getQuote({ |
| 44 | + buyToken: TOKEN_ADDRESSES["WETH"], |
| 45 | + sellToken: TOKEN_ADDRESSES[inputToken], |
| 46 | + buyAmount: wethBuyAmount.toString(), |
| 47 | + }); |
| 48 | + quotes.push(wethQuote); |
| 49 | + } |
| 50 | + return quotes; |
| 51 | +} |
| 52 | + |
| 53 | +async function getPositionQuotes( |
| 54 | + positions: any[], |
| 55 | + inputTokenAddress: string, |
| 56 | + setAmount: number, |
| 57 | +): Promise<any[]> { |
| 58 | + const promises = positions.map((position: any) => { |
| 59 | + if ( |
| 60 | + ethers.utils.getAddress(position.component) === ethers.utils.getAddress(inputTokenAddress) |
| 61 | + ) { |
| 62 | + console.log("No swap needed"); |
| 63 | + return Promise.resolve({ gas: "0", sellAmount: position.unit.mul(setAmount).toString() }); |
| 64 | + } else { |
| 65 | + const params = { |
| 66 | + buyToken: position.component, |
| 67 | + sellToken: inputTokenAddress, |
| 68 | + buyAmount: position.unit.mul(setAmount).toString(), |
| 69 | + }; |
| 70 | + return getQuote(params); |
| 71 | + } |
| 72 | + }); |
| 73 | + return await Promise.all(promises); |
| 74 | +} |
| 75 | + |
| 76 | +type GasCostRow = { |
| 77 | + setToken: string; |
| 78 | + inputToken: string; |
| 79 | + setAmount: number; |
| 80 | + wethStage: boolean; |
| 81 | + gas: number; |
| 82 | +}; |
| 83 | + |
| 84 | +const TOKEN_ADDRESSES: Record<string, string> = { |
| 85 | + DPI: "0x1494ca1f11d487c2bbe4543e90080aeba4ba3c2b", |
| 86 | + DAI: "0x6b175474e89094c44da98b954eedeac495271d0f", |
| 87 | + UNI: "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", |
| 88 | + WETH: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", |
| 89 | +}; |
| 90 | + |
| 91 | +async function calculateTotalGas( |
| 92 | + setToken: string, |
| 93 | + inputToken: string, |
| 94 | + setAmount: number, |
| 95 | + wethStage: boolean, |
| 96 | +): Promise<GasCostRow> { |
| 97 | + const setAddress = TOKEN_ADDRESSES[setToken]; |
| 98 | + const setContract = await ethers.getContractAt("ISetToken", setAddress); |
| 99 | + const positions = await setContract.getPositions(); |
| 100 | + const positionQuotes = await getQuotes(positions, inputToken, setAmount, wethStage); |
| 101 | + const gas = positionQuotes.reduce((sum: number, quote: any) => sum + parseInt(quote.gas), 0); |
| 102 | + return { setToken, inputToken, setAmount, wethStage, gas }; |
| 103 | +} |
| 104 | + |
| 105 | +//@ts-ignore |
| 106 | +const f = (a, b) => [].concat(...a.map((d) => b.map((e) => [].concat(d, e)))); |
| 107 | +//@ts-ignore |
| 108 | +const cartesian = (a, b, ...c) => (b ? cartesian(f(a, b), ...c) : a); |
| 109 | + |
| 110 | +async function main() { |
| 111 | + const setTokens = ["DPI"]; |
| 112 | + const inputTokens = ["DAI"]; |
| 113 | + const setAmounts = [100, 1000, 10000]; |
| 114 | + const wethStage = [false, true]; |
| 115 | + const scenarios = cartesian(setTokens, inputTokens, setAmounts, wethStage); |
| 116 | + const promises = scenarios.map((params: [string, string, number, boolean]) => |
| 117 | + calculateTotalGas(params[0], params[1], params[2], params[3]), |
| 118 | + ); |
| 119 | + const results = await Promise.all(promises); |
| 120 | + console.table(results); |
| 121 | +} |
| 122 | +main() |
| 123 | + .then(() => process.exit(0)) |
| 124 | + .catch((error) => { |
| 125 | + console.error(error); |
| 126 | + process.exit(1); |
| 127 | + }); |
0 commit comments