diff --git a/src/dex/generic-rfq/e2e-test-config.ts b/src/dex/generic-rfq/e2e-test-config.ts index 42b289646..3731078af 100644 --- a/src/dex/generic-rfq/e2e-test-config.ts +++ b/src/dex/generic-rfq/e2e-test-config.ts @@ -8,16 +8,16 @@ export const testConfig: { amount: string; }>; } = { - [Network.ARBITRUM]: [ + [Network.MAINNET]: [ { - srcToken: 'WETH', + srcToken: 'USDT', destToken: 'USDC', amount: '10000', swapSide: SwapSide.BUY, }, { srcToken: 'USDC', - destToken: 'WETH', + destToken: 'USDT', amount: '10000', swapSide: SwapSide.SELL, }, diff --git a/src/dex/generic-rfq/generic-rfq-e2e.test.ts b/src/dex/generic-rfq/generic-rfq-e2e.test.ts index 54de508eb..64a08fc1d 100644 --- a/src/dex/generic-rfq/generic-rfq-e2e.test.ts +++ b/src/dex/generic-rfq/generic-rfq-e2e.test.ts @@ -115,21 +115,23 @@ describe(`GenericRFQ ${dexKey} E2E`, () => { `Please add "addBalance" and "addAllowance" functions for ${testCase.destToken} on ${Network[network]} (in constants-e2e.ts).`, ); } - srcToken = smartTokens[testCase.srcToken]; - destToken = smartTokens[testCase.destToken]; + srcToken = new SmartToken(tokens[testCase.srcToken]); + destToken = new SmartToken(tokens[testCase.destToken]); - srcToken.addBalance(testAccount.address, MAX_UINT); + const amount = (BigInt(MAX_UINT) / 10n ** 10n).toString(); + + srcToken.addBalance(testAccount.address, amount); srcToken.addAllowance( testAccount.address, config.augustusRFQAddress, - MAX_UINT, + amount, ); - destToken.addBalance(testAccount.address, MAX_UINT); + destToken.addBalance(testAccount.address, amount); destToken.addAllowance( testAccount.address, config.augustusRFQAddress, - MAX_UINT, + amount, ); } const contractMethod = diff --git a/tests/constants-e2e.ts b/tests/constants-e2e.ts index f9eec28c1..1f6c30739 100644 --- a/tests/constants-e2e.ts +++ b/tests/constants-e2e.ts @@ -128,7 +128,7 @@ export const Tokens: { address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', decimals: 6, symbol: 'USDC', - addBalance: balancesFn, + addBalance: balanceAndBlacklistStatesFn, addAllowance: allowedFn, }, INST: { diff --git a/tests/smart-tokens.ts b/tests/smart-tokens.ts index 5398e0e46..831b6f573 100644 --- a/tests/smart-tokens.ts +++ b/tests/smart-tokens.ts @@ -5,9 +5,7 @@ export type StateOverride = { }; export type StateSimulateApiOverride = { - storage: { - value: Record; - }; + storage: Record; }; export type StateOverrides = { diff --git a/tests/tenderly-simulation.ts b/tests/tenderly-simulation.ts index 63358876d..26e7ddb1f 100644 --- a/tests/tenderly-simulation.ts +++ b/tests/tenderly-simulation.ts @@ -1,17 +1,16 @@ /* eslint-disable no-console */ import axios from 'axios'; -import { Address } from '@paraswap/core'; import { TxObject } from '../src/types'; import { StateOverrides, StateSimulateApiOverride } from './smart-tokens'; -import { StaticJsonRpcProvider, Provider } from '@ethersproject/providers'; -import Web3 from 'web3'; +import { Provider, StaticJsonRpcProvider } from '@ethersproject/providers'; +import { ethers } from 'ethers'; +import { Network } from '../build/constants'; +import { Address } from '@paraswap/core'; const TENDERLY_TOKEN = process.env.TENDERLY_TOKEN; const TENDERLY_ACCOUNT_ID = process.env.TENDERLY_ACCOUNT_ID; const TENDERLY_PROJECT = process.env.TENDERLY_PROJECT; -const TENDERLY_FORK_ID = process.env.TENDERLY_FORK_ID; -const TENDERLY_TEST_NET_RPC = process.env.TENDERLY_TEST_NET_RPC; -const TENDERLY_FORK_LAST_TX_ID = process.env.TENDERLY_FORK_LAST_TX_ID; +const TENDERLY_VNET_ID = process.env.TENDERLY_VNET_ID; export type SimulationResult = { success: boolean; @@ -21,9 +20,11 @@ export type SimulationResult = { }; export interface TransactionSimulator { - forkId: string; + vnetId: string; setup(): Promise; + getChainNameByChainId(network: number): string; + simulate( params: TxObject, stateOverrides?: StateOverrides, @@ -31,12 +32,16 @@ export interface TransactionSimulator { } export class EstimateGasSimulation implements TransactionSimulator { - forkId: string = '0'; + vnetId: string = '0'; constructor(private provider: Provider) {} async setup() {} + getChainNameByChainId(network: number) { + return ''; + } + async simulate( params: TxObject, _: StateOverrides, @@ -57,59 +62,82 @@ export class EstimateGasSimulation implements TransactionSimulator { } export class TenderlySimulation implements TransactionSimulator { - testNetRPC: StaticJsonRpcProvider | null = null; - lastTx: string = ''; - forkId: string = ''; + vnetId: string = ''; + rpcURL: string = ''; maxGasLimit = 80000000; - constructor( - private network: Number = 1, - forkId?: string, - lastTransactionId?: string, - ) { - if (forkId && lastTransactionId) { - this.forkId = forkId; - this.lastTx = lastTransactionId; + private readonly chainIdToChainNameMap: { [key: number]: string } = { + [Network.MAINNET]: 'mainnet', + [Network.BSC]: 'bnb', + [Network.POLYGON]: 'polygon', + [Network.AVALANCHE]: 'avalanche-mainnet', + [Network.FANTOM]: 'fantom', + [Network.ARBITRUM]: 'arbitrum', + [Network.OPTIMISM]: 'optimistic', + [Network.GNOSIS]: 'gnosis-chain', + [Network.BASE]: 'base', + }; + + constructor(private network: number = 1, vnetId?: string) { + if (vnetId) { + this.vnetId = vnetId; } } + getChainNameByChainId(network: number): string { + return this.chainIdToChainNameMap[network]; + } + async setup() { - // Fork the mainnet if (!TENDERLY_TOKEN) throw new Error( `TenderlySimulation_setup: TENDERLY_TOKEN not found in the env`, ); - if (this.forkId && this.lastTx) return; + if (this.vnetId) return; - if (TENDERLY_FORK_ID) { - if (!TENDERLY_FORK_LAST_TX_ID) throw new Error('Always set last tx id'); - this.forkId = TENDERLY_FORK_ID; - this.lastTx = TENDERLY_FORK_LAST_TX_ID; - return; - } - - if (TENDERLY_TEST_NET_RPC) { - this.testNetRPC = new StaticJsonRpcProvider(TENDERLY_TEST_NET_RPC); + if (TENDERLY_VNET_ID) { + this.vnetId = TENDERLY_VNET_ID; return; } try { await process.nextTick(() => {}); // https://stackoverflow.com/questions/69169492/async-external-function-leaves-open-handles-jest-supertest-express let res = await axios.post( - `https://api.tenderly.co/api/v1/account/${TENDERLY_ACCOUNT_ID}/project/${TENDERLY_PROJECT}/fork`, + `https://api.tenderly.co/api/v1/account/${TENDERLY_ACCOUNT_ID}/project/${TENDERLY_PROJECT}/vnets`, { - network_id: this.network.toString(), + slug: `e2e-tests-testnetwork-${this.network.toString()}-${Date.now()}`, + fork_config: { + network_id: this.network, + }, + virtual_network_config: { + chain_config: { + chain_id: this.network, + }, + }, + sync_state_config: { + enabled: false, + }, + explorer_page_config: { + enabled: true, + verification_visibility: 'bytecode', + }, }, { - timeout: 20000, + timeout: 200000, headers: { - 'x-access-key': TENDERLY_TOKEN, + 'X-Access-Key': TENDERLY_TOKEN, }, }, ); - this.forkId = res.data.simulation_fork.id; - this.lastTx = res.data.root_transaction.id; + + const rpc: { name: string; url: string } = res.data.rpcs.find( + (rpc: { name: string; url: string }) => + rpc.name.toLowerCase() === 'Admin RPC'.toLowerCase(), + ); + + this.vnetId = res.data.id; + this.rpcURL = rpc.url; } catch (e) { console.error(`TenderlySimulation_setup:`, e); throw e; @@ -117,18 +145,8 @@ export class TenderlySimulation implements TransactionSimulator { } async simulate(params: TxObject, stateOverrides?: StateOverrides) { - let _params = { - from: params.from, - to: params.to, - save: true, - root: this.lastTx, - value: params.value || '0', - gas: this.maxGasLimit, - input: params.data, - state_objects: {}, - }; try { - if (this.testNetRPC) return this.executeTransactionOnTestnet(params); + let stateOverridesParams = {}; if (stateOverrides) { await process.nextTick(() => {}); // https://stackoverflow.com/questions/69169492/async-external-function-leaves-open-handles-jest-supertest-express @@ -143,7 +161,7 @@ export class TenderlySimulation implements TransactionSimulator { }, ); - _params.state_objects = Object.keys(result.data.stateOverrides).reduce( + stateOverridesParams = Object.keys(result.data.stateOverrides).reduce( (acc, contract) => { const _storage = result.data.stateOverrides[contract].value; @@ -154,12 +172,30 @@ export class TenderlySimulation implements TransactionSimulator { }, {} as Record, ); + + await this.executeStateOverrides(stateOverridesParams); } await process.nextTick(() => {}); // https://stackoverflow.com/questions/69169492/async-external-function-leaves-open-handles-jest-supertest-express const { data } = await axios.post( - `https://api.tenderly.co/api/v1/account/${TENDERLY_ACCOUNT_ID}/project/${TENDERLY_PROJECT}/fork/${this.forkId}/simulate`, - _params, + `https://api.tenderly.co/api/v1/account/${TENDERLY_ACCOUNT_ID}/project/${TENDERLY_PROJECT}/vnets/${this.vnetId}/transactions`, + { + callArgs: { + from: params.from, + to: params.to, + value: + params.value === '0' + ? '0x0' + : ethers.utils.hexStripZeros( + ethers.utils.hexlify(BigInt(params.value)), + ), + gas: ethers.utils.hexStripZeros( + ethers.utils.hexlify(BigInt(this.maxGasLimit)), + ), + data: params.data, + }, + blockNumber: 'pending', + }, { timeout: 30 * 1000, headers: { @@ -168,20 +204,22 @@ export class TenderlySimulation implements TransactionSimulator { }, ); - const lastTx = data.simulation.id; - if (data.transaction.status) { - this.lastTx = lastTx; + if (data.status === 'success') { return { success: true, - gasUsed: data.transaction.gas_used, - url: `https://dashboard.tenderly.co/${TENDERLY_ACCOUNT_ID}/${TENDERLY_PROJECT}/fork/${this.forkId}/simulation/${lastTx}`, - transaction: data.transaction, + gasUsed: data.gasUsed, + url: `https://dashboard.tenderly.co/${TENDERLY_ACCOUNT_ID}/${TENDERLY_PROJECT}/testnet/${ + this.vnetId + }/tx/${this.chainIdToChainNameMap[this.network]}/${data.id}`, + transaction: data.input, }; } else { return { success: false, - url: `https://dashboard.tenderly.co/${TENDERLY_ACCOUNT_ID}/${TENDERLY_PROJECT}/fork/${this.forkId}/simulation/${lastTx}`, - error: `Simulation failed: ${data.transaction.error_info.error_message} at ${data.transaction.error_info.address}`, + url: `https://dashboard.tenderly.co/${TENDERLY_ACCOUNT_ID}/${TENDERLY_PROJECT}/testnet/${ + this.vnetId + }/tx/${this.chainIdToChainNameMap[this.network]}/${data.id}`, + error: `Simulation failed ${data.error_reason}`, }; } } catch (e) { @@ -191,31 +229,27 @@ export class TenderlySimulation implements TransactionSimulator { } } - async executeTransactionOnTestnet(params: TxObject) { - const txParams = { - from: params.from, - to: params.to, - value: Web3.utils.toHex(params.value || '0'), - data: params.data, - gas: '0x4c4b40', // 5,000,000 - gasPrice: '0x0', // 0 - }; - const txHash = await this.testNetRPC!.send('eth_sendTransaction', [ - txParams, - ]); - const transaction = await this.testNetRPC!.waitForTransaction(txHash); - if (transaction.status) { - return { - success: true, - url: txHash, - gasUsed: transaction.gasUsed.toString(), - transaction, - }; - } else { - return { - success: false, - error: `Transaction on testnet failed, hash: ${txHash}`, - }; + async executeStateOverrides( + stateOverridesParams: Record, + ) { + const testNetRPC = new StaticJsonRpcProvider(this.rpcURL); + + // need to execute promises sequentially here + for await (const addr of Object.keys(stateOverridesParams)) { + const storage = stateOverridesParams[addr].storage; + + for await (const slot of Object.keys(storage)) { + const txHash = await testNetRPC!.send('tenderly_setStorageAt', [ + addr, + slot, + storage[slot], + ]); + + const transaction = await testNetRPC!.waitForTransaction(txHash); + if (!transaction.status) { + console.log(`Transaction failed: ${txHash}`); + } + } } } } diff --git a/tests/utils-e2e.ts b/tests/utils-e2e.ts index a0343d057..1354d82ee 100644 --- a/tests/utils-e2e.ts +++ b/tests/utils-e2e.ts @@ -446,7 +446,7 @@ export async function testE2E( formatDeployMessage( 'adapter', contractAddress, - ts.forkId, + ts.vnetId, testContractName || '', testContractRelativePath || '', ), @@ -486,7 +486,9 @@ export async function testE2E( if (paraswap.dexHelper?.replaceProviderWithRPC) { paraswap.dexHelper?.replaceProviderWithRPC( - `https://rpc.tenderly.co/fork/${ts.forkId}`, + `https://virtual.${ts.getChainNameByChainId(network)}.rpc.tenderly.co/${ + ts.vnetId + }`, ); } @@ -572,6 +574,7 @@ export async function testE2E( ); const swapTx = await ts.simulate(swapParams); + // Only log gas estimate if testing against API if (useAPI) { const gasUsed = swapTx.gasUsed || '0'; @@ -699,7 +702,7 @@ export async function newTestE2E({ stateOverrides: {}, }; - destToken.addBalance(GIFTER_ADDRESS, MAX_UINT); + destToken.addBalance(GIFTER_ADDRESS, (BigInt(MAX_UINT) / 4n).toString()); destToken.applyOverrides(stateOverrides); const giftTx = makeFakeTransferToSenderAddress(