Skip to content

Commit

Permalink
feat: add gas reports simulations (#95)
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>

* feat: add compound liquidiation report

Signed-off-by: Pablo Maldonado <pablo@umaproject.org>

* feat: add aave v2 liquidation gas report

Signed-off-by: Pablo Maldonado <pablo@umaproject.org>

* feat: aave v3 liquidation

Signed-off-by: Pablo Maldonado <pablo@umaproject.org>

* refactor: format

Signed-off-by: Pablo Maldonado <pablo@umaproject.org>

* feat: aave v3 borrow

Signed-off-by: Pablo Maldonado <pablo@umaproject.org>

---------

Signed-off-by: Reinis Martinsons <reinis@umaproject.org>
Signed-off-by: Pablo Maldonado <pablo@umaproject.org>
Co-authored-by: Reinis Martinsons <reinis@umaproject.org>
  • Loading branch information
md0x and Reinis-FRP authored Nov 29, 2023
1 parent d580565 commit 9404cf7
Show file tree
Hide file tree
Showing 8 changed files with 980 additions and 2 deletions.
2 changes: 1 addition & 1 deletion scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"license": "AGPL-3.0-only",
"private": true,
"scripts": {
"generate-contract-types": "rm -rf contract-types && typechain --target ethers-v5 --out-dir contract-types '../out/**/*.json'",
"generate-contract-types": "rm -rf contract-types && typechain --target ethers-v5 --out-dir contract-types $(find ../out -name '*.json' ! -name 'Common.json')",
"build": "tsc",
"gas-profiling": "node dist/src/gasProfiling/index.js",
"clean": "rm -rf contract-types && rm -rf dist && rm -rf node_modules"
Expand Down
175 changes: 175 additions & 0 deletions scripts/src/gasProfiling/aaveV2Borrow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import { providers, utils } from "ethers";
import {
createTenderlyFork,
deleteTenderlyFork,
findForkByDescription,
getTenderlyFork,
setForkSimulationDescription,
shareTenderlyFork,
} from "../TenderlyHelpers/TenderlyFork";
import {
TenderlySimulationResult,
simulateTenderlyTx,
} from "../TenderlyHelpers/TenderlySimulation";
// Have to import TestedOevShare manually since it is not unique.
import { TestedOevShare__factory } from "../../contract-types/factories/AaveV2.Liquidation.sol/TestedOevShare__factory";

// Common constants.
const blockNumber = 18426914;
const chainId = 1;

// Used ABIs.
const aaveOracleAbi = [
"function setAssetSources(address[] memory assets, address[] memory sources)",
];

const borrowCall = async (
forkTimestamp: number,
forkId: string,
rootId?: string // If not provided, the simulation starts off the fork initial state.
): Promise<TenderlySimulationResult> => {
// Borrow tx
// https://etherscan.io/tx/0x99751e7c2114bfc74d7effb6114a7d752c7573396a5d6456ee7b56497132d474
let simulation: TenderlySimulationResult;
simulation = await simulateTenderlyTx({
chainId,
from: "0x2a111934d990668e705c85da0e976db06281ef0a",
to: "0x7d2768de32b0b80b7a3454c06bdac94a69ddc7a9",
value: "0",
input:
"0xa415bcad000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000077359400000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a111934d990668e705c85da0e976db06281ef0a",
timestampOverride: forkTimestamp,
fork: { id: forkId, root: rootId },
description: "borrowCall",
});

return simulation;
};

const regularAaveV2Borrow = async (): Promise<number> => {
// Create and share new fork (delete the old one if it exists).
const alias = "Regular AAVE V2 Borrow";
const description = "Generated: " + utils.keccak256(utils.toUtf8Bytes(alias));
const existingFork = await findForkByDescription(description);
if (existingFork) await deleteTenderlyFork(existingFork.id);
const fork = await createTenderlyFork({
chainId,
alias,
description,
blockNumber,
txIndex: 125,
});
const forkUrl = await shareTenderlyFork(fork.id);

// Get provider, accounts and start time of the fork.
const provider = new providers.StaticJsonRpcProvider(fork.rpcUrl);
const forkTimestamp = (await provider.getBlock(blockNumber)).timestamp;

// Open user position.
const simulation = await borrowCall(forkTimestamp, fork.id); // Start off the initial fork state.

console.log("Simulated " + alias + " in " + simulation.resultUrl.url);
console.log(" Consumed gas: " + simulation.gasUsed);
console.log(" Fork URL: " + forkUrl + "\n");

return simulation.gasUsed;
};

const oevShareAaveV2Borrow = async (): Promise<number> => {
// Create and share new fork (delete the old one if it exists).
const alias = "OEVShare AAVE V2 Borrow";
const description =
"Genereated: " + utils.keccak256(utils.toUtf8Bytes(alias));
const existingFork = await findForkByDescription(description);
if (existingFork) await deleteTenderlyFork(existingFork.id);
let fork = await createTenderlyFork({
chainId,
alias,
description,
blockNumber,
txIndex: 125,
});
const forkUrl = await shareTenderlyFork(fork.id);

// Get provider, accounts and start time of the fork.
const provider = new providers.StaticJsonRpcProvider(fork.rpcUrl);
const [ownerAddress, userAddress, unlockerAddress] =
await provider.listAccounts(); // These should have 100 ETH balance.
const ownerSigner = provider.getSigner(ownerAddress);
const forkTimestamp = (await provider.getBlock(blockNumber)).timestamp;

// Deploy OevShare.
const testedOevShareFactory = new TestedOevShare__factory(ownerSigner);
const testedOevShare = await testedOevShareFactory.deploy(
"0xEe9F2375b4bdF6387aa8265dD4FB8F16512A1d46",
18
);
await testedOevShare.deployTransaction.wait();
fork = await getTenderlyFork(fork.id); // Refresh to get head id since we submitted tx through RPC.
if (!fork.headId) throw new Error("Fork head id not found.");
await setForkSimulationDescription(fork.id, fork.headId, "Deploy OevShare");

// Enable unlocker on TestedOevShare.
const setUnlockerInput = testedOevShareFactory.interface.encodeFunctionData(
"setUnlocker",
[unlockerAddress, true]
);
let simulation = await simulateTenderlyTx({
chainId,
from: ownerAddress,
to: testedOevShare.address,
input: setUnlockerInput,
timestampOverride: forkTimestamp,
fork: { id: fork.id, root: fork.headId },
description: "Enable unlocker on OEVShare",
});

// setOevShareAsAaveSource
const aaveOracleInterface = new utils.Interface(aaveOracleAbi);
const aaveOracleCallData = aaveOracleInterface.encodeFunctionData(
"setAssetSources",
[["0xdac17f958d2ee523a2206206994597c13d831ec7"], [testedOevShare.address]]
);

simulation = await simulateTenderlyTx({
chainId,
from: "0xEE56e2B3D491590B5b31738cC34d5232F378a8D5", //aaveOracle owner
to: "0xA50ba011c48153De246E5192C8f9258A2ba79Ca9", //aaveOracle
input: aaveOracleCallData,
timestampOverride: forkTimestamp,
fork: { id: fork.id, root: simulation.id },
description: "Change OEVShare as Aave source",
});

// Unlock latest value.
const unlockLatestValueInput =
testedOevShareFactory.interface.encodeFunctionData("unlockLatestValue");
simulation = await simulateTenderlyTx({
chainId,
from: unlockerAddress,
to: testedOevShare.address,
input: unlockLatestValueInput,
timestampOverride: forkTimestamp,
fork: { id: fork.id, root: simulation.id },
description: "Unlock latest value on OEVShare",
});

// Open user position.
simulation = await borrowCall(forkTimestamp, fork.id, simulation.id);

console.log("Simulated " + alias + " in " + simulation.resultUrl.url);
console.log(" Consumed gas: " + simulation.gasUsed);
console.log(" Fork URL: " + forkUrl + "\n");

return simulation.gasUsed;
};

export const aaveV2Borrow = async () => {
console.log("AAVE V2 Borrow gas comparison with unlock:\n");

const regularAaveV2BorrowGas = await regularAaveV2Borrow();
const oevShareAaveV2BorrowGas = await oevShareAaveV2Borrow();
const gasDiff = oevShareAaveV2BorrowGas - regularAaveV2BorrowGas;

console.log("Gas difference: " + gasDiff);
};
191 changes: 191 additions & 0 deletions scripts/src/gasProfiling/aaveV2Liquidation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import { Contract, providers, utils } from "ethers";
import {
createTenderlyFork,
deleteTenderlyFork,
findForkByDescription,
getTenderlyFork,
setForkSimulationDescription,
shareTenderlyFork,
} from "../TenderlyHelpers/TenderlyFork";
import {
simulateTenderlyTx,
TenderlySimulationResult,
} from "../TenderlyHelpers/TenderlySimulation";
import { UniswapAnchoredViewDestinationAdapter__factory } from "../../contract-types";
// Have to import TestedOevShare manually since it is not unique.
import { TestedOevShare__factory } from "../../contract-types/factories/AaveV2.Liquidation.sol/TestedOevShare__factory";

// Common constants.
const blockNumber = 17937311;
const chainId = 1;

// Used ABIs.
const aaveV2LendingPoolAbi = [
"function liquidationCall(address collateralAsset, address debtAsset, address user, uint256 debtToCover, bool receiveAToken)",
"function getReserveData(address asset) returns (uint256 totalLiquidity, uint256 availableLiquidity, uint256 totalBorrowsStable, uint256 totalBorrowsVariable, uint256 liquidityRate, uint256 variableBorrowRate, uint256 stableBorrowRate, uint256 averageStableBorrowRate, uint256 utilizationRate, uint256 liquidityIndex, uint256 variableBorrowIndex, address aTokenAddress, uint40 lastUpdateTimestamp)",
"function getUserAccountData(address user) returns (uint256 totalCollateralETH, uint256 totalDebtETH, uint256 availableBorrowsETH, uint256 currentLiquidationThreshold, uint256 ltv, uint256 healthFactor)",
];

const aaveOracleAbi = [
"function setAssetSources(address[] memory assets, address[] memory sources)",
];

const liquidationCall = async (
forkTimestamp: number,
forkId: string,
rootId?: string // If not provided, the simulation starts off the fork initial state.
): Promise<TenderlySimulationResult> => {
// Post collateral.
let simulation: TenderlySimulationResult;
const lendingPoolInterface = new utils.Interface(aaveV2LendingPoolAbi);
const liquidationCallData = lendingPoolInterface.encodeFunctionData(
"liquidationCall",
[
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"0x6e3aa85db95bba36276a37ed93b12b7ab0782afb",
"148912478614",
true,
]
);
simulation = await simulateTenderlyTx({
chainId,
from: "0x796d37daf7cdc455e023be793d0daa6240707069",
to: "0x7d2768de32b0b80b7a3454c06bdac94a69ddc7a9",
value: "0",
input: liquidationCallData,
timestampOverride: forkTimestamp,
fork: { id: forkId, root: rootId },
description: "liquidationCall",
});

return simulation;
};

const regularAaveV2Liquidation = async (): Promise<number> => {
// Create and share new fork (delete the old one if it exists).
const alias = "Regular AaveV2 Liquidation";
const description = "Generated: " + utils.keccak256(utils.toUtf8Bytes(alias));
const existingFork = await findForkByDescription(description);
if (existingFork) await deleteTenderlyFork(existingFork.id);
const fork = await createTenderlyFork({
chainId,
alias,
description,
blockNumber,
txIndex:1,
});
const forkUrl = await shareTenderlyFork(fork.id);

// Get provider, accounts and start time of the fork.
const provider = new providers.StaticJsonRpcProvider(fork.rpcUrl);
const forkTimestamp = (await provider.getBlock(blockNumber)).timestamp;

// Open user position.
const simulation = await liquidationCall(forkTimestamp, fork.id); // Start off the initial fork state.

console.log("Simulated " + alias + " in " + simulation.resultUrl.url);
console.log(" Consumed gas: " + simulation.gasUsed);
console.log(" Fork URL: " + forkUrl + "\n");

return simulation.gasUsed;
};

const oevShareAaveV2Liquidation = async (): Promise<number> => {
// Create and share new fork (delete the old one if it exists).
const alias = "OEVShare AAVE V2 Liquidation";
const description =
"Genereated: " + utils.keccak256(utils.toUtf8Bytes(alias));
const existingFork = await findForkByDescription(description);
if (existingFork) await deleteTenderlyFork(existingFork.id);
let fork = await createTenderlyFork({
chainId,
alias,
description,
blockNumber,
txIndex:1,
});
const forkUrl = await shareTenderlyFork(fork.id);

// Get provider, accounts and start time of the fork.
const provider = new providers.StaticJsonRpcProvider(fork.rpcUrl);
const [ownerAddress, userAddress, unlockerAddress] =
await provider.listAccounts(); // These should have 100 ETH balance.
const ownerSigner = provider.getSigner(ownerAddress);
const forkTimestamp = (await provider.getBlock(blockNumber)).timestamp;

// Deploy OevShare.
const testedOevShareFactory = new TestedOevShare__factory(ownerSigner);
const testedOevShare = await testedOevShareFactory.deploy(
"0x8e0b7e6062272B5eF4524250bFFF8e5Bd3497757",
18
);
await testedOevShare.deployTransaction.wait();
fork = await getTenderlyFork(fork.id); // Refresh to get head id since we submitted tx through RPC.
if (!fork.headId) throw new Error("Fork head id not found.");
await setForkSimulationDescription(fork.id, fork.headId, "Deploy OevShare");

// Enable unlocker on TestedOevShare.
const setUnlockerInput = testedOevShareFactory.interface.encodeFunctionData(
"setUnlocker",
[unlockerAddress, true]
);
let simulation = await simulateTenderlyTx({
chainId,
from: ownerAddress,
to: testedOevShare.address,
input: setUnlockerInput,
timestampOverride: forkTimestamp,
fork: { id: fork.id, root: fork.headId },
description: "Enable unlocker on OEVShare",
});

// setOevShareAsAaveSource
const aaveOracleInterface = new utils.Interface(aaveOracleAbi);
const aaveOracleCallData = aaveOracleInterface.encodeFunctionData(
"setAssetSources",
[["0x57Ab1ec28D129707052df4dF418D58a2D46d5f51"], [testedOevShare.address]]
);

simulation = await simulateTenderlyTx({
chainId,
from: "0xEE56e2B3D491590B5b31738cC34d5232F378a8D5", //aaveOracle owner
to: "0xA50ba011c48153De246E5192C8f9258A2ba79Ca9", //aaveOracle
input: aaveOracleCallData,
timestampOverride: forkTimestamp,
fork: { id: fork.id, root: simulation.id },
description: "Change OEVShare as Aave source",
});

// Unlock latest value.
const unlockLatestValueInput =
testedOevShareFactory.interface.encodeFunctionData("unlockLatestValue");
simulation = await simulateTenderlyTx({
chainId,
from: unlockerAddress,
to: testedOevShare.address,
input: unlockLatestValueInput,
timestampOverride: forkTimestamp,
fork: { id: fork.id, root: simulation.id },
description: "Unlock latest value on OEVShare",
});

// Open user position.
simulation = await liquidationCall(forkTimestamp, fork.id, simulation.id);

console.log("Simulated " + alias + " in " + simulation.resultUrl.url);
console.log(" Consumed gas: " + simulation.gasUsed);
console.log(" Fork URL: " + forkUrl + "\n");

return simulation.gasUsed;
};

export const aaveV2Liquidation = async () => {
console.log("AAVE V2 Liquidation gas comparison with unlock:\n");

const regularAaveV2LiquidationGas = await regularAaveV2Liquidation();
const oevShareAaveV2LiquidationGas = await oevShareAaveV2Liquidation();
const gasDiff = oevShareAaveV2LiquidationGas - regularAaveV2LiquidationGas;

console.log("Gas difference: " + gasDiff);
};
Loading

0 comments on commit 9404cf7

Please sign in to comment.