From 3373c00fcd70df3eb4bb3114a90285add22a71a0 Mon Sep 17 00:00:00 2001 From: Amie Date: Tue, 19 Mar 2024 12:40:31 -0700 Subject: [PATCH] Introduce a simulation task using Alchemy (#731) * simulation task using alchemy * update lockfile --------- Co-authored-by: amiecorso --- package.json | 1 + tasks/index.ts | 2 + tasks/simulate-txn-alchemy.ts | 157 ++++++++++++++++++++++++++++++++++ types/node/process.d.ts | 1 + yarn.lock | 52 ++++++++++- 5 files changed, 209 insertions(+), 4 deletions(-) create mode 100644 tasks/simulate-txn-alchemy.ts diff --git a/package.json b/package.json index 3de8a449f..dd19cb2d5 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@types/proper-lockfile": "^4.1.2", "@types/sinon": "^10.0.13", "@types/sinon-chai": "^3.2.9", + "alchemy-sdk": "^3.1.2", "chai": "^4.3.7", "chai-as-promised": "^7.1.1", "chalk": "^4.0", diff --git a/tasks/index.ts b/tasks/index.ts index e84b0b163..6924f503b 100644 --- a/tasks/index.ts +++ b/tasks/index.ts @@ -29,6 +29,7 @@ import { GET_LIST_MIGRATED_REMOVALS_TASK } from './list-remaining-migrated-remov import { TASK as FORCE_UPGRADE_TASK } from './force-ugrade'; import { TASK as SIGN_MESSAGE_TASK } from './sign-message'; import { TASK as TEST_SIGN_TYPED_TASK } from './test-sign-typed'; +import { GET_SIMULATE_TXN_TASK } from './simulate-txn-alchemy'; export interface Task { run: ActionType< @@ -88,4 +89,5 @@ export const TASKS = { [FORCE_UPGRADE_TASK.name]: { ...FORCE_UPGRADE_TASK }, [SIGN_MESSAGE_TASK.name]: { ...SIGN_MESSAGE_TASK }, [TEST_SIGN_TYPED_TASK.name]: { ...TEST_SIGN_TYPED_TASK }, + [GET_SIMULATE_TXN_TASK().name]: { ...GET_SIMULATE_TXN_TASK() }, } as const; diff --git a/tasks/simulate-txn-alchemy.ts b/tasks/simulate-txn-alchemy.ts new file mode 100644 index 000000000..d3b35e13b --- /dev/null +++ b/tasks/simulate-txn-alchemy.ts @@ -0,0 +1,157 @@ +/* eslint-disable no-await-in-loop -- need to submit transactions synchronously to avoid nonce collisions */ + +import { BigNumber, FixedNumber } from 'ethers'; +import { task } from 'hardhat/config'; + +import { getLogger } from '@/utils/log'; +import { getMarket, getRemoval } from '@/utils/contracts'; +import { Alchemy, Network, Utils } from 'alchemy-sdk'; + +export const GET_SIMULATE_TXN_TASK = () => + ({ + name: 'simulate-txn', + description: 'Utility to simulate a transaction with the Alchemy API', + run: async ( + options: {}, + _: CustomHardHatRuntimeEnvironment + ): Promise => { + const logger = getLogger({ + prefix: undefined, + hre, + }); + const network = hre.network.name; + if (![`localhost`, `mumbai`, `polygon`].includes(network)) { + throw new Error( + `Network ${network} is not supported. Please use localhost, mumbai, or polygon.` + ); + } + const [signer] = await hre.getSigners(); + const signerAddress = await signer.getAddress(); + + const settings = { + apiKey: process.env.ALCHEMY_API_KEY, + network: Network.MATIC_MAINNET, + }; + + const alchemy = new Alchemy(settings); + + const marketContract = await getMarket({ + hre, + signer, + }); + const removalContract = await getRemoval({ + hre, + signer, + }); + console.log('MARKET CONTRACT ADDRESS', marketContract.address); + + const polygonRelayerAddress = + '0x6fcF5C3E43bE33F4B14BB25B550adb6887C1E48c'; + + const marketBalance = await removalContract.getMarketBalance(); + const marketBalanceInEth = FixedNumber.fromValue(marketBalance, 18); + console.log( + 'CURRENT MARKET BALANCE (TONNES): ', + marketBalanceInEth.toString() + ); + + const hasRole = await marketContract.hasRole( + marketContract.MARKET_ADMIN_ROLE(), + polygonRelayerAddress + ); + console.log('RELAYER HAS `MARKET_ADMIN_ROLE`? ', hasRole); + + const latestBlock = await hre.ethers.provider.getBlock('latest'); + const latestBlockGasLimit = Utils.hexStripZeros( + latestBlock.gasLimit.toHexString() + ); + const latestFastGasPrice = await hre.ethers.provider.getGasPrice(); + const fastGasPriceHexString = Utils.hexStripZeros( + latestFastGasPrice.toHexString() + ); + console.log('LATEST BLOCK GAS LIMIT: ', latestBlockGasLimit); + console.log('LATEST FAST GAS PRICE: ', fastGasPriceHexString); + + const purchaseAmountEth = 9_000; + const purchaseAmountWei = ethers.utils.parseUnits( + purchaseAmountEth.toString(), + 18 + ); + console.log('PURCHASE AMOUNT (ETH): ', purchaseAmountEth); + console.log('PURCHASE AMOUNT (WEI): ', purchaseAmountWei); + const gasEstimation = + await marketContract.estimateGas.swapWithoutFeeSpecialOrder( + signerAddress, // recipient + polygonRelayerAddress, // purchaser (doesn't matter if they have USDC the price is 0) + purchaseAmountWei, + 0, + 0, + ethers.constants.AddressZero, + [] + ); + console.log('GAS ESTIMATION', gasEstimation); + + const transactionData = marketContract.interface.encodeFunctionData( + 'swapWithoutFeeSpecialOrder', + [ + signerAddress, // recipient + polygonRelayerAddress, // purchaser (doesn't matter if they have USDC the price is 0) + purchaseAmountWei, + 0, + 0, + ethers.constants.AddressZero, + [], + ] + ); + + const transactionInformation = { + /** The address the transaction is directed to. */ + to: marketContract.address, + /** The address the transaction is sent from. (This is what's spoofed) */ + from: polygonRelayerAddress, + /** The gas provided for the transaction execution, as a hex string. */ + gas: latestBlockGasLimit, + // gas: '0x1e8480', // 2,000,000 + // gas: '0x1312D00', // 20,000,000 + /** The gas price to use as a hex string. */ + gasPrice: fastGasPriceHexString, + /** The value associated with the transaction as a hex string. */ + value: '0x0', + /** The data associated with the transaction. */ + data: transactionData, + }; + + const alchemyGasEstimation = await alchemy.transact.estimateGas( + transactionInformation + ); + console.log('ALCHEMY GAS ESTIMATION', alchemyGasEstimation.toString()); + + const response = await alchemy.transact.simulateExecution( + transactionInformation + ); + + // const response = await alchemy.transact.simulateAssetChanges({ + // /** The address the transaction is directed to. */ + // to: marketContract.address, + // /** The address the transaction is sent from. (This is what's spoofed) */ + // from: polygonRelayerAddress, + // /** The gas provided for the transaction execution, as a hex string. */ + // // gas: latestBlockGasLimit, + // // gas: '0x1e8480', // 2,000,000 + // gas: '0x1312D00', // 20,000,000 + // /** The gas price to use as a hex string. */ + // gasPrice: fastGasPriceHexString, + // /** The value associated with the transaction as a hex string. */ + // value: '0x0', + // /** The data associated with the transaction. */ + // data: transactionData, + // }); + + console.log('RESPONSE', response); + }, + } as const); + +(() => { + const { name, description, run } = GET_SIMULATE_TXN_TASK(); + task(name, description, run); +})(); diff --git a/types/node/process.d.ts b/types/node/process.d.ts index 573a11b79..4838ccc6d 100644 --- a/types/node/process.d.ts +++ b/types/node/process.d.ts @@ -2,6 +2,7 @@ declare namespace NodeJS { interface ProcessEnv { MNEMONIC?: string; + ALCHEMY_API_KEY?: string; ETHERNAL_EMAIL?: string; ETHERNAL_PASSWORD?: string; ETHERNAL: boolean; diff --git a/yarn.lock b/yarn.lock index 921eae5cc..418c3381b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -545,7 +545,7 @@ dependencies: "@ethersproject/bignumber" "^5.6.2" -"@ethersproject/contracts@5.7.0": +"@ethersproject/contracts@5.7.0", "@ethersproject/contracts@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.7.0.tgz#c305e775abd07e48aa590e1a877ed5c316f8bd1e" integrity sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg== @@ -716,7 +716,7 @@ dependencies: "@ethersproject/logger" "^5.6.0" -"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.7.1", "@ethersproject/providers@^5.7.2": +"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.7.0", "@ethersproject/providers@^5.7.1", "@ethersproject/providers@^5.7.2": version "5.7.2" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== @@ -903,7 +903,7 @@ "@ethersproject/constants" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/wallet@5.7.0": +"@ethersproject/wallet@5.7.0", "@ethersproject/wallet@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.7.0.tgz#4e5d0790d96fe21d61d38fb40324e6c7ef350b2d" integrity sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA== @@ -3380,6 +3380,26 @@ ajv@^8.0.0, ajv@^8.0.1, ajv@^8.6.3: require-from-string "^2.0.2" uri-js "^4.2.2" +alchemy-sdk@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/alchemy-sdk/-/alchemy-sdk-3.1.2.tgz#0808aeed7fcbbed9c516021ce9d4aa0e33e5ccf9" + integrity sha512-xpCgQRLektp6imKdGdHyuVHvbMGpaSe22+qvg9jjGx0Wwkh0XgPzSfKwAzFDlkCGMMdazhKCsHu22XP0xh1noQ== + dependencies: + "@ethersproject/abi" "^5.7.0" + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/contracts" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/providers" "^5.7.0" + "@ethersproject/units" "^5.7.0" + "@ethersproject/wallet" "^5.7.0" + "@ethersproject/web" "^5.7.0" + axios "^1.6.5" + sturdy-websocket "^0.2.1" + websocket "^1.0.34" + amazon-cognito-identity-js@^6.0.1: version "6.1.2" resolved "https://registry.yarnpkg.com/amazon-cognito-identity-js/-/amazon-cognito-identity-js-6.1.2.tgz#975df21b0590098c2d3f455f48dbba255560bbf5" @@ -3869,6 +3889,15 @@ axios@^0.27.2: follow-redirects "^1.14.9" form-data "^4.0.0" +axios@^1.6.5: + version "1.6.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7" + integrity sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA== + dependencies: + follow-redirects "^1.15.4" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + axobject-query@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" @@ -8200,6 +8229,11 @@ follow-redirects@^1.14.9: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== +follow-redirects@^1.15.4: + version "1.15.5" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" + integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== + for-each@^0.3.3, for-each@~0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -12926,6 +12960,11 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -15152,6 +15191,11 @@ strip-json-comments@~2.0.1: resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== +sturdy-websocket@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/sturdy-websocket/-/sturdy-websocket-0.2.1.tgz#20a58fd53372ef96eaa08f3c61c91a10b07c7c05" + integrity sha512-NnzSOEKyv4I83qbuKw9ROtJrrT6Z/Xt7I0HiP/e6H6GnpeTDvzwGIGeJ8slai+VwODSHQDooW2CAilJwT9SpRg== + supports-color@8.1.1: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" @@ -17172,7 +17216,7 @@ websocket@1.0.32: utf-8-validate "^5.0.2" yaeti "^0.0.6" -websocket@^1.0.31, websocket@^1.0.32: +websocket@^1.0.31, websocket@^1.0.32, websocket@^1.0.34: version "1.0.34" resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.34.tgz#2bdc2602c08bf2c82253b730655c0ef7dcab3111" integrity sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==