Skip to content

Commit

Permalink
feat: compound borrow gas profiling (#94)
Browse files Browse the repository at this point in the history
* feat: compound borrow gas profiling

Signed-off-by: Reinis Martinsons <reinis@umaproject.org>

* fix

Signed-off-by: Reinis Martinsons <reinis@umaproject.org>

* fix

Signed-off-by: Reinis Martinsons <reinis@umaproject.org>

---------

Signed-off-by: Reinis Martinsons <reinis@umaproject.org>
  • Loading branch information
Reinis-FRP authored Oct 31, 2023
1 parent 35d5f7c commit d580565
Show file tree
Hide file tree
Showing 30 changed files with 827 additions and 59 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ lcov.info
# Scripts
scripts/node_modules
scripts/dist
scripts/contract-types
4 changes: 4 additions & 0 deletions scripts/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copy this template to .env and fill in the values below
TENDERLY_USER=
TENDERLY_PROJECT=
TENDERLY_ACCESS_KEY=
38 changes: 38 additions & 0 deletions scripts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# OEVShare scripts

This package contains scripts for gas profiling of OEVShare.

## Installation

```bash
yarn
```

## Build

Start with generating contract typings:

```bash
yarn generate-contract-types
```

Then compile the scripts:

```bash
yarn build
```

## Usage

Make sure to copy `.env.example` to `.env` and fill in the required values.

Run gas profiling:

```bash
yarn gas-profiling
```

The script will create Tenderly forks that have Note starting with `Generated: 0x...`. This is used by the script to
identify the forks that it had created and delete them when creating the same type of simulation. If you are sharing
Tenderly forks with other people, it is better to remove the `Generated: 0x...` Note from the fork through Tenderly
UI.
7 changes: 6 additions & 1 deletion scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@
"license": "AGPL-3.0-only",
"private": true,
"scripts": {
"build": "tsc"
"generate-contract-types": "rm -rf contract-types && typechain --target ethers-v5 --out-dir contract-types '../out/**/*.json'",
"build": "tsc",
"gas-profiling": "node dist/src/gasProfiling/index.js",
"clean": "rm -rf contract-types && rm -rf dist && rm -rf node_modules"
},
"devDependencies": {
"@typechain/ethers-v5": "^11.1.2",
"@types/node": "^20.8.6",
"typechain": "^8.3.2",
"typescript": "^5.2.2"
},
"dependencies": {
Expand Down
98 changes: 84 additions & 14 deletions scripts/src/TenderlyHelpers/TenderlyFork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,27 +88,38 @@ const createForkRequestBody = (
return body;
};

function isTenderlySimulationFork(
simulationFork: any
): simulationFork is TenderlyForkAPIResponse["simulation_fork"] {
if (
typeof simulationFork.id === "string" &&
typeof simulationFork.block_number === "number" &&
typeof simulationFork.transaction_index === "number" &&
typeof simulationFork.accounts === "object" &&
Object.keys(simulationFork.accounts).every(
(key) => typeof key === "string"
) &&
Object.values(simulationFork.accounts).every(
(value) => typeof value === "string"
) &&
typeof simulationFork.rpc_url === "string" &&
("global_head" in simulationFork
? typeof simulationFork.global_head === "string"
: true) // Optional property
) {
return true;
}
return false;
}

// Type guard function to check if the API response conforms to the required TenderlyForkAPIResponse interface
function isTenderlyForkAPIResponse(
response: any
): response is TenderlyForkAPIResponse {
if (
response &&
response.simulation_fork &&
typeof response.simulation_fork.id === "string" &&
typeof response.simulation_fork.block_number === "number" &&
typeof response.simulation_fork.transaction_index === "number" &&
typeof response.simulation_fork.accounts === "object" &&
Object.keys(response.simulation_fork.accounts).every(
(key) => typeof key === "string"
) &&
Object.values(response.simulation_fork.accounts).every(
(value) => typeof value === "string"
) &&
typeof response.simulation_fork.rpc_url === "string" &&
("global_head" in response.simulation_fork
? typeof response.simulation_fork.global_head === "string"
: true) && // Optional property
isTenderlySimulationFork(response.simulation_fork) &&
("root_transaction" in response
? typeof response.root_transaction === "object" &&
typeof response.root_transaction.id === "string"
Expand Down Expand Up @@ -274,3 +285,62 @@ export const setTenderlyBalance = async (
throw new Error(`Failed to get updated fork head ID`);
return updatedFork.headId;
};

export const findForkByDescription = async (
description: string
): Promise<TenderlyForkResult | undefined> => {
// Will throw if required environment variables are not set.
const tenderlyEnv = processEnvironment();

// Construct Tenderly fork API request.
const url = `https://api.tenderly.co/api/v1/account/${tenderlyEnv.user}/project/${tenderlyEnv.project}/forks`;
const headers = { "X-Access-Key": tenderlyEnv.apiKey };

// Send Tenderly fork API request (Axios will throw if the HTTP response is not valid).
const response = await axiosInstance.get(url, { headers });

// If the HTTP response was valid, we expect the response body should be a JSON object containing expected Tenderly fork
// response properties.
if (
typeof response.data !== "object" ||
!Array.isArray(response.data.simulation_forks) ||
!response.data.simulation_forks.every((simulationFork) =>
isTenderlySimulationFork(simulationFork)
)
) {
throw new Error(
`Failed to parse Tenderly fork API response: ${JSON.stringify(
response.data
)}`
);
}

// Find the fork with the matching description.
const matchingFork = response.data.simulation_forks.find(
(simulationFork) => simulationFork.description === description
);

// If we found a matching fork, return the translated result.
if (matchingFork !== undefined)
return forkAPIResponseToResult({ simulation_fork: matchingFork });

// Otherwise, return undefined.
return undefined;
};

export const setForkSimulationDescription = async (
forkId: string,
simulationId: string,
description: string
): Promise<void> => {
// Will throw if required environment variables are not set.
const tenderlyEnv = processEnvironment();

// Construct Tenderly fork API request.
const url = `https://api.tenderly.co/api/v1/account/${tenderlyEnv.user}/project/${tenderlyEnv.project}/fork/${forkId}/transaction/${simulationId}`;
const body = { description };
const headers = { "X-Access-Key": tenderlyEnv.apiKey };

// Send Tenderly fork API request (Axios will throw if the HTTP response is not valid).
await axiosInstance.put(url, body, { headers });
}
10 changes: 9 additions & 1 deletion scripts/src/TenderlyHelpers/TenderlySimulation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export interface TenderlySimulationParams {
from?: string; // If not provided, the zero address is used in the simulation.
timestampOverride?: number;
fork?: ForkParams;
description?: string;
}

interface ResultUrl {
Expand All @@ -42,6 +43,7 @@ interface ResultUrl {
export interface TenderlySimulationResult {
id: string;
status: boolean; // True if the simulation succeeded, false if it reverted.
gasUsed: number;
resultUrl: ResultUrl;
}

Expand All @@ -59,13 +61,15 @@ interface TenderlyRequestBody {
block_header?: {
timestamp: string;
};
description?: string;
}

// We only type Tenderly simulation API response properties that we use.
interface TenderlyAPIResponse {
simulation: {
id: string;
status: boolean;
receipt: { gasUsed: string };
};
}

Expand Down Expand Up @@ -134,6 +138,7 @@ const createRequestBody = (
value: simulationParams.value,
from: simulationParams.from || constants.AddressZero,
root: simulationParams.fork?.root,
description: simulationParams.description,
};

if (simulationParams.timestampOverride !== undefined) {
Expand All @@ -153,7 +158,9 @@ function isTenderlyAPIResponse(response: any): response is TenderlyAPIResponse {
response &&
response.simulation &&
typeof response.simulation.id === "string" &&
typeof response.simulation.status === "boolean"
typeof response.simulation.status === "boolean" &&
response.simulation.receipt &&
typeof response.simulation.receipt.gasUsed === "string"
) {
return true;
}
Expand Down Expand Up @@ -246,6 +253,7 @@ export const simulateTenderlyTx = async (
return {
id: simulationResponse.simulation.id,
status: simulationResponse.simulation.status,
gasUsed: parseInt(simulationResponse.simulation.receipt.gasUsed),
resultUrl,
};
};
Loading

0 comments on commit d580565

Please sign in to comment.