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..550a1c5a7 100644 --- a/balancer-js/src/lib/utils/tenderlyHelper.ts +++ b/balancer-js/src/lib/utils/tenderlyHelper.ts @@ -1,7 +1,14 @@ 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 } }; @@ -10,6 +17,7 @@ type StateOverrides = { export default class TenderlyHelper { private vaultAddress; private tenderlyUrl; + private tenderlyRpcUrl = ''; private opts?; private blockNumber: number | undefined; @@ -24,11 +32,12 @@ export default class TenderlyHelper { } else { this.tenderlyUrl = 'https://api.balancer.fi/tenderly/'; } - 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 +63,7 @@ export default class TenderlyHelper { ...tokensOverrides, ...relayerApprovalOverride, }; - return this.simulateTransaction( + return this.simulateTransactionRpc( to, data, userAddress, @@ -103,6 +112,61 @@ export default class TenderlyHelper { return simulatedTransactionOutput; }; + simulateTransactionRpc = async ( + to: Address, + data: string, + userAddress: Address, + encodedStateOverrides: StateOverrides + ): Promise => { + 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, + { + 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'); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + console.error(error.response.data.error); + throw error; + } + }; + // Encode relayer approval state override encodeRelayerApprovalOverride = async ( userAddress: string, @@ -117,11 +181,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 diff --git a/balancer-js/src/types.ts b/balancer-js/src/types.ts index 2dddff2c8..52b91075d 100644 --- a/balancer-js/src/types.ts +++ b/balancer-js/src/types.ts @@ -24,6 +24,7 @@ import type { import type { GraphQLArgs } from './lib/graphql'; import type { AprBreakdown } from '@/modules/pools/apr/apr'; import * as Queries from '@/modules/pools/queries/types'; + export * from '@/modules/data/types'; export { Network, AprBreakdown }; @@ -347,3 +348,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[]; +}