From a844f354d5eca8633f6bcb2583b0261bc29da587 Mon Sep 17 00:00:00 2001 From: Luiz Gustavo Abou Hatem de Liz Date: Wed, 21 Dec 2022 19:51:39 -0300 Subject: [PATCH 1/7] Using Goerli RPC Gateway for simulations Finished the implementation of RPC Gateway usage on tenderlyHelper; --- .gitignore | 1 + balancer-js/src/lib/utils/tenderlyHelper.ts | 69 ++++++++++++- balancer-js/src/types.ts | 108 ++++++++++++++++++++ 3 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..757fee31c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.idea \ No newline at end of file diff --git a/balancer-js/src/lib/utils/tenderlyHelper.ts b/balancer-js/src/lib/utils/tenderlyHelper.ts index ec850ebb8..bdfd17c9d 100644 --- a/balancer-js/src/lib/utils/tenderlyHelper.ts +++ b/balancer-js/src/lib/utils/tenderlyHelper.ts @@ -1,15 +1,31 @@ import axios from 'axios'; import { MaxInt256 } from '@ethersproject/constants'; import { networkAddresses } from '@/lib/constants/config'; -import { BalancerTenderlyConfig } from '@/types'; +import { + Address, + BalancerTenderlyConfig, + TenderlyRpcResponse, + TenderlyRpcSimulationBlockNumber, + TenderlyRpcStateOverridesParameters, + TenderlyRpcTransactionParameters, +} from '@/types'; type StateOverrides = { [address: string]: { value: { [key: string]: string } }; }; +type RpcStateDiff = { + [address: string]: { + stateDiff: { + [storageKey: string]: string; + }; + }; +}; + export default class TenderlyHelper { private vaultAddress; private tenderlyUrl; + private tenderlyRpcUrl = ''; private opts?; private blockNumber: number | undefined; @@ -26,9 +42,11 @@ export default class TenderlyHelper { } if (tenderlyConfig?.accessKey) { + this.tenderlyRpcUrl = `https://goerli.gateway.tenderly.co/${ tenderlyConfig.accessKey }`; this.opts = { headers: { 'X-Access-Key': tenderlyConfig.accessKey, + 'Content-Type': 'application/json', }, }; } @@ -54,7 +72,7 @@ export default class TenderlyHelper { ...tokensOverrides, ...relayerApprovalOverride, }; - return this.simulateTransaction( + return this.simulateTransactionRpc( to, data, userAddress, @@ -103,6 +121,53 @@ export default class TenderlyHelper { return simulatedTransactionOutput; }; + simulateTransactionRpc = async ( + to: Address, + data: string, + userAddress: Address, + encodedStateOverrides: StateOverrides + ) => { + const transactionParams: TenderlyRpcTransactionParameters = { + to, + data, + from: userAddress, + }; + const simulationBlockNumber: TenderlyRpcSimulationBlockNumber = `0x${this.blockNumber?.toString( + 16 + )}`; + + const overrides: TenderlyRpcStateOverridesParameters = Object.fromEntries( + Object.keys(encodedStateOverrides).map((address) => { + // Object.fromEntries require format [key, value] instead of {key: value} + return [address, { stateDiff: encodedStateOverrides[address].value }]; + }) + ); + const tenderlyParams = [ + transactionParams, + simulationBlockNumber, + overrides, + ]; + const response = await axios.post( + this.tenderlyRpcUrl, + { + id: 0, + jsonrpc: '2.0', + method: 'tenderly_simulateTransaction', + params: tenderlyParams, + }, + this.opts + ); + const responseBody: TenderlyRpcResponse = response.data; + const callTraces = responseBody.result.trace.filter( + ({ type, method }) => type === 'CALL' && method === 'multicall' + ); + const lastCallTrace = + callTraces.length > 0 + ? callTraces[callTraces.length - 1] + : { output: '0x' }; + return lastCallTrace.output; + }; + // Encode relayer approval state override encodeRelayerApprovalOverride = async ( userAddress: string, diff --git a/balancer-js/src/types.ts b/balancer-js/src/types.ts index 2dddff2c8..09f88896d 100644 --- a/balancer-js/src/types.ts +++ b/balancer-js/src/types.ts @@ -24,6 +24,8 @@ import type { import type { GraphQLArgs } from './lib/graphql'; import type { AprBreakdown } from '@/modules/pools/apr/apr'; import * as Queries from '@/modules/pools/queries/types'; +import { RpcAccessList } from 'hardhat/internal/core/jsonrpc/types/access-list'; + export * from '@/modules/data/types'; export { Network, AprBreakdown }; @@ -347,3 +349,109 @@ export interface GraphQLQuery { // eslint-disable-next-line @typescript-eslint/no-explicit-any attrs: any; } + +export interface TenderlyRpcTransactionParameters { + from?: Address; // hex encoded address "from" + to: Address; // hex encoded address "to" + gas?: number; + maxFeePerGas?: number; // max fee: The maximum total fee per gas the sender is willing to pay (includes the network / base fee and miner / priority fee) in wei + maxPriorityFeePerGas?: number; // max priority fee: Maximum fee per gas the sender is willing to pay to miners in wei + gasPrice?: number; // The gas price willing to be paid by the sender in wei + value?: number; + data?: string; + accessList?: AccessListTuple[]; +} + +export interface AccessListTuple { + address: Address; // hex encoded address + storageKeys: string[]; // Array of 32 byte hex encoded storage key +} + +export type TenderlyRpcSimulationBlockNumber = + | string // Block Number or... + | 'earliest' // Block tag + | 'finalized' + | 'safe' + | 'latest' + | 'pending'; + +export interface TenderlyRpcStateOverridesParameters { + [key: Address]: { + // the override specification + nonce?: string; // hex encoded 8 byte nonce override for the account + code?: string; // data of the code override for the account + balance?: string; // hex encoded 32 byte balance override for the account in wei + stateDiff: { + // mapping of storage key to storage value override + [key: string]: string; // key: the storage key -- value: the value override for the given storage key + }; + }; +} + +export interface TenderlyRpcResponse { + id: number | string; + jsonrpc: string; + result: { + status: boolean | number; + gasUsed: string; + cumulativeGasUsed: string; + blockNumber: string; + type: string; + logsBloom: string; + logs: TenderlyRpcLog[]; + trace: TenderlyRpcTrace[]; + }; +} + +export interface TenderlyRpcLog { + name: string; + anonymous: boolean | number; + input: { + name: string; + type: string; + value?: string; + }[]; + raw: { + address: string; + topics: string[]; + data: string; + }; +} + +export interface TenderlyRpcTrace { + type: + | string + | 'CALL' + | 'CALLCODE' + | 'STATICCALL' + | 'DELEGATECALL' + | 'CREATE' + | 'CREATE2' + | 'SELFDESTRUCT'; + from: Address; + to: Address; + gas: string; + gasUsed: string; + value: string; + error: string; + errorMessage: string; + input: string; + method: string | null; + decodedInput: + | { + value: string; + type: string; + name: string; + }[] + | null; + output: string; + decodedOutput: + | { + value: string; + type: string; + name: string; + }[] + | null; + subtraces: number; + traceAddress: number[]; +} From bced08ad6ff6d027ff4daaf0d0b7161daf12271e Mon Sep 17 00:00:00 2001 From: Luiz Gustavo Abou Hatem de Liz Date: Fri, 23 Dec 2022 09:55:06 -0300 Subject: [PATCH 2/7] Fixing ESLint requirements --- balancer-js/src/lib/utils/tenderlyHelper.ts | 2 +- balancer-js/src/types.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/balancer-js/src/lib/utils/tenderlyHelper.ts b/balancer-js/src/lib/utils/tenderlyHelper.ts index bdfd17c9d..29b5fe985 100644 --- a/balancer-js/src/lib/utils/tenderlyHelper.ts +++ b/balancer-js/src/lib/utils/tenderlyHelper.ts @@ -42,7 +42,7 @@ export default class TenderlyHelper { } if (tenderlyConfig?.accessKey) { - this.tenderlyRpcUrl = `https://goerli.gateway.tenderly.co/${ tenderlyConfig.accessKey }`; + this.tenderlyRpcUrl = `https://goerli.gateway.tenderly.co/${tenderlyConfig.accessKey}`; this.opts = { headers: { 'X-Access-Key': tenderlyConfig.accessKey, diff --git a/balancer-js/src/types.ts b/balancer-js/src/types.ts index 09f88896d..52b91075d 100644 --- a/balancer-js/src/types.ts +++ b/balancer-js/src/types.ts @@ -24,7 +24,6 @@ import type { import type { GraphQLArgs } from './lib/graphql'; import type { AprBreakdown } from '@/modules/pools/apr/apr'; import * as Queries from '@/modules/pools/queries/types'; -import { RpcAccessList } from 'hardhat/internal/core/jsonrpc/types/access-list'; export * from '@/modules/data/types'; export { Network, AprBreakdown }; From cb56399f0311b81671fed88f4f1ee8d9c240dd4a Mon Sep 17 00:00:00 2001 From: Luiz Gustavo Abou Hatem de Liz Date: Fri, 23 Dec 2022 10:00:31 -0300 Subject: [PATCH 3/7] Fixing 2 ESLint Warnings Unused variable; Missing function return type; --- balancer-js/src/lib/utils/tenderlyHelper.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/balancer-js/src/lib/utils/tenderlyHelper.ts b/balancer-js/src/lib/utils/tenderlyHelper.ts index 29b5fe985..69bba3422 100644 --- a/balancer-js/src/lib/utils/tenderlyHelper.ts +++ b/balancer-js/src/lib/utils/tenderlyHelper.ts @@ -14,14 +14,6 @@ type StateOverrides = { [address: string]: { value: { [key: string]: string } }; }; -type RpcStateDiff = { - [address: string]: { - stateDiff: { - [storageKey: string]: string; - }; - }; -}; - export default class TenderlyHelper { private vaultAddress; private tenderlyUrl; @@ -126,7 +118,7 @@ export default class TenderlyHelper { data: string, userAddress: Address, encodedStateOverrides: StateOverrides - ) => { + ): Promise => { const transactionParams: TenderlyRpcTransactionParameters = { to, data, From ac77226bef9a96185fd887651724c47b4cabc34b Mon Sep 17 00:00:00 2001 From: Luiz Gustavo Abou Hatem de Liz Date: Thu, 5 Jan 2023 18:50:34 -0300 Subject: [PATCH 4/7] Commenting tenderlyUrl with user and project; leaving only "https://api.balancer.fi/tenderly" endpoint --- balancer-js/src/lib/utils/tenderlyHelper.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/balancer-js/src/lib/utils/tenderlyHelper.ts b/balancer-js/src/lib/utils/tenderlyHelper.ts index 69bba3422..261544911 100644 --- a/balancer-js/src/lib/utils/tenderlyHelper.ts +++ b/balancer-js/src/lib/utils/tenderlyHelper.ts @@ -27,12 +27,11 @@ export default class TenderlyHelper { ) { const { contracts } = networkAddresses(this.chainId); this.vaultAddress = contracts.vault as string; - if (tenderlyConfig?.user && tenderlyConfig?.project) { - this.tenderlyUrl = `https://api.tenderly.co/api/v1/account/${tenderlyConfig.user}/project/${tenderlyConfig.project}/`; - } else { - this.tenderlyUrl = 'https://api.balancer.fi/tenderly/'; - } - + // if (tenderlyConfig?.user && tenderlyConfig?.project) { + // this.tenderlyUrl = `https://api.tenderly.co/api/v1/account/${tenderlyConfig.user}/project/${tenderlyConfig.project}/`; + // } else { + // } + this.tenderlyUrl = 'https://api.balancer.fi/tenderly/'; if (tenderlyConfig?.accessKey) { this.tenderlyRpcUrl = `https://goerli.gateway.tenderly.co/${tenderlyConfig.accessKey}`; this.opts = { From 0a14f809ca88455a5cad581c7a4d3ac4658f70f8 Mon Sep 17 00:00:00 2001 From: Luiz Gustavo Abou Hatem de Liz Date: Thu, 5 Jan 2023 19:25:01 -0300 Subject: [PATCH 5/7] Adding try catches to find the error; --- balancer-js/src/lib/utils/tenderlyHelper.ts | 137 +++++++++++--------- 1 file changed, 73 insertions(+), 64 deletions(-) diff --git a/balancer-js/src/lib/utils/tenderlyHelper.ts b/balancer-js/src/lib/utils/tenderlyHelper.ts index 261544911..cff7d8669 100644 --- a/balancer-js/src/lib/utils/tenderlyHelper.ts +++ b/balancer-js/src/lib/utils/tenderlyHelper.ts @@ -118,45 +118,52 @@ export default class TenderlyHelper { userAddress: Address, encodedStateOverrides: StateOverrides ): Promise => { - const transactionParams: TenderlyRpcTransactionParameters = { - to, - data, - from: userAddress, - }; - const simulationBlockNumber: TenderlyRpcSimulationBlockNumber = `0x${this.blockNumber?.toString( - 16 - )}`; + try { + const transactionParams: TenderlyRpcTransactionParameters = { + to, + data, + from: userAddress, + }; + const simulationBlockNumber: TenderlyRpcSimulationBlockNumber = `0x${this.blockNumber?.toString( + 16 + )}`; - const overrides: TenderlyRpcStateOverridesParameters = Object.fromEntries( - Object.keys(encodedStateOverrides).map((address) => { - // Object.fromEntries require format [key, value] instead of {key: value} - return [address, { stateDiff: encodedStateOverrides[address].value }]; - }) - ); - const tenderlyParams = [ - transactionParams, - simulationBlockNumber, - overrides, - ]; - const response = await axios.post( - this.tenderlyRpcUrl, - { - id: 0, - jsonrpc: '2.0', - method: 'tenderly_simulateTransaction', - params: tenderlyParams, - }, - this.opts - ); - const responseBody: TenderlyRpcResponse = response.data; - const callTraces = responseBody.result.trace.filter( - ({ type, method }) => type === 'CALL' && method === 'multicall' - ); - const lastCallTrace = - callTraces.length > 0 - ? callTraces[callTraces.length - 1] - : { output: '0x' }; - return lastCallTrace.output; + const overrides: TenderlyRpcStateOverridesParameters = Object.fromEntries( + Object.keys(encodedStateOverrides).map((address) => { + // Object.fromEntries require format [key, value] instead of {key: value} + return [address, { stateDiff: encodedStateOverrides[address].value }]; + }) + ); + const tenderlyParams = [ + transactionParams, + simulationBlockNumber, + overrides, + ]; + + const response = await axios.post( + this.tenderlyRpcUrl, + { + id: 0, + jsonrpc: '2.0', + method: 'tenderly_simulateTransaction', + params: tenderlyParams, + }, + this.opts + ); + const responseBody: TenderlyRpcResponse = response.data; + const callTraces = responseBody.result.trace.filter( + ({ type, method }) => type === 'CALL' && method === 'multicall' + ); + const lastCallTrace = + callTraces.length > 0 + ? callTraces[callTraces.length - 1] + : { output: '0x' }; + return lastCallTrace.output; + } catch (error) { + console.log('simulate transaction rpc'); + console.error(error); + throw error; + } }; // Encode relayer approval state override @@ -173,11 +180,7 @@ export default class TenderlyHelper { }, }; - const encodedStateOverrides = await this.requestStateOverrides( - stateOverrides - ); - - return encodedStateOverrides; + return await this.requestStateOverrides(stateOverrides); }; // Encode token balances and allowances overrides to max value @@ -228,29 +231,35 @@ export default class TenderlyHelper { private requestStateOverrides = async ( stateOverrides: StateOverrides ): Promise => { - const ENCODE_STATES_URL = this.tenderlyUrl + 'contracts/encode-states'; - const body = { - networkID: this.chainId.toString(), - stateOverrides, - }; - - const encodedStatesResponse = await axios.post( - ENCODE_STATES_URL, - body, - this.opts - ); - const encodedStateOverrides = encodedStatesResponse.data - .stateOverrides as StateOverrides; + try { + const ENCODE_STATES_URL = this.tenderlyUrl + 'contracts/encode-states'; + const body = { + networkID: this.chainId.toString(), + stateOverrides, + }; - if ( - !encodedStateOverrides || - Object.keys(encodedStateOverrides).length !== - Object.keys(stateOverrides).length - ) - throw new Error( - "Couldn't encode state overrides - contracts should be verified and whitelisted on Tenderly" + const encodedStatesResponse = await axios.post( + ENCODE_STATES_URL, + body, + this.opts ); + const encodedStateOverrides = encodedStatesResponse.data + .stateOverrides as StateOverrides; - return encodedStateOverrides; + if ( + !encodedStateOverrides || + Object.keys(encodedStateOverrides).length !== + Object.keys(stateOverrides).length + ) + throw new Error( + "Couldn't encode state overrides - contracts should be verified and whitelisted on Tenderly" + ); + + return encodedStateOverrides; + } catch (error) { + console.log('request state overdrives'); + console.error(error); + throw error; + } }; } From a1dd35dd99569f7514f24f82be9e97b2a9b9b9ad Mon Sep 17 00:00:00 2001 From: Luiz Gustavo Abou Hatem de Liz Date: Thu, 5 Jan 2023 19:39:21 -0300 Subject: [PATCH 6/7] Putting the old "tenderlyUrl" (with user and project parameters) back; To see if it affects in the CI tests; --- balancer-js/src/lib/utils/tenderlyHelper.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/balancer-js/src/lib/utils/tenderlyHelper.ts b/balancer-js/src/lib/utils/tenderlyHelper.ts index cff7d8669..15597a639 100644 --- a/balancer-js/src/lib/utils/tenderlyHelper.ts +++ b/balancer-js/src/lib/utils/tenderlyHelper.ts @@ -27,11 +27,11 @@ export default class TenderlyHelper { ) { const { contracts } = networkAddresses(this.chainId); this.vaultAddress = contracts.vault as string; - // if (tenderlyConfig?.user && tenderlyConfig?.project) { - // this.tenderlyUrl = `https://api.tenderly.co/api/v1/account/${tenderlyConfig.user}/project/${tenderlyConfig.project}/`; - // } else { - // } - this.tenderlyUrl = 'https://api.balancer.fi/tenderly/'; + if (tenderlyConfig?.user && tenderlyConfig?.project) { + this.tenderlyUrl = `https://api.tenderly.co/api/v1/account/${tenderlyConfig.user}/project/${tenderlyConfig.project}/`; + } else { + this.tenderlyUrl = 'https://api.balancer.fi/tenderly/'; + } if (tenderlyConfig?.accessKey) { this.tenderlyRpcUrl = `https://goerli.gateway.tenderly.co/${tenderlyConfig.accessKey}`; this.opts = { From e46449796dece77789009b530577ce5c8a85d36b Mon Sep 17 00:00:00 2001 From: Luiz Gustavo Abou Hatem de Liz Date: Thu, 5 Jan 2023 19:50:02 -0300 Subject: [PATCH 7/7] Removing try catch from encode state call; changing the console.error to show error.response.data.error, instead of error full object. --- balancer-js/src/lib/utils/tenderlyHelper.ts | 53 ++++++++++----------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/balancer-js/src/lib/utils/tenderlyHelper.ts b/balancer-js/src/lib/utils/tenderlyHelper.ts index 15597a639..550a1c5a7 100644 --- a/balancer-js/src/lib/utils/tenderlyHelper.ts +++ b/balancer-js/src/lib/utils/tenderlyHelper.ts @@ -143,7 +143,6 @@ export default class TenderlyHelper { const response = await axios.post( this.tenderlyRpcUrl, { - id: 0, jsonrpc: '2.0', method: 'tenderly_simulateTransaction', params: tenderlyParams, @@ -161,7 +160,9 @@ export default class TenderlyHelper { return lastCallTrace.output; } catch (error) { console.log('simulate transaction rpc'); - console.error(error); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + console.error(error.response.data.error); throw error; } }; @@ -231,35 +232,29 @@ export default class TenderlyHelper { private requestStateOverrides = async ( stateOverrides: StateOverrides ): Promise => { - try { - const ENCODE_STATES_URL = this.tenderlyUrl + 'contracts/encode-states'; - const body = { - networkID: this.chainId.toString(), - stateOverrides, - }; + const ENCODE_STATES_URL = this.tenderlyUrl + 'contracts/encode-states'; + const body = { + networkID: this.chainId.toString(), + stateOverrides, + }; - const encodedStatesResponse = await axios.post( - ENCODE_STATES_URL, - body, - this.opts - ); - const encodedStateOverrides = encodedStatesResponse.data - .stateOverrides as StateOverrides; + const encodedStatesResponse = await axios.post( + ENCODE_STATES_URL, + body, + this.opts + ); + const encodedStateOverrides = encodedStatesResponse.data + .stateOverrides as StateOverrides; - if ( - !encodedStateOverrides || - Object.keys(encodedStateOverrides).length !== - Object.keys(stateOverrides).length - ) - throw new Error( - "Couldn't encode state overrides - contracts should be verified and whitelisted on Tenderly" - ); + if ( + !encodedStateOverrides || + Object.keys(encodedStateOverrides).length !== + Object.keys(stateOverrides).length + ) + throw new Error( + "Couldn't encode state overrides - contracts should be verified and whitelisted on Tenderly" + ); - return encodedStateOverrides; - } catch (error) { - console.log('request state overdrives'); - console.error(error); - throw error; - } + return encodedStateOverrides; }; }