diff --git a/scripts/package.json b/scripts/package.json index 48b77e1..0737ce2 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -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" diff --git a/scripts/src/gasProfiling/aaveV2Borrow.ts b/scripts/src/gasProfiling/aaveV2Borrow.ts new file mode 100644 index 0000000..7e37b01 --- /dev/null +++ b/scripts/src/gasProfiling/aaveV2Borrow.ts @@ -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 => { + // 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 => { + // 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 => { + // 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); +}; diff --git a/scripts/src/gasProfiling/aaveV2Liquidation.ts b/scripts/src/gasProfiling/aaveV2Liquidation.ts new file mode 100644 index 0000000..b8c6e25 --- /dev/null +++ b/scripts/src/gasProfiling/aaveV2Liquidation.ts @@ -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 => { + // 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 => { + // 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 => { + // 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); +}; diff --git a/scripts/src/gasProfiling/aaveV3Borrow.ts b/scripts/src/gasProfiling/aaveV3Borrow.ts new file mode 100644 index 0000000..c570585 --- /dev/null +++ b/scripts/src/gasProfiling/aaveV3Borrow.ts @@ -0,0 +1,174 @@ +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/AaveV3.Liquidation.sol/TestedOevShare__factory"; + +// Common constants. +const blockNumber = 18427678; +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 => { + let simulation: TenderlySimulationResult; + // Original borrow https://etherscan.io/tx/0x20092fee7e5574443f19fc2fd0e2aaa4b73226252e428e11e068e2a1930b9a67 + simulation = await simulateTenderlyTx({ + chainId, + from: "0xec4a9460200017cf196da530a8b2ad5904e54796", + to: "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2", + value: "0", + input: + "0xa415bcad000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000012a05f20000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ec4a9460200017cf196da530a8b2ad5904e54796", + timestampOverride: forkTimestamp, + fork: { id: forkId, root: rootId }, + description: "borrowCall", + }); + + return simulation; +}; + +const regularAaveV3Borrow = async (): Promise => { + // Create and share new fork (delete the old one if it exists). + const alias = "Regular AAVE V3 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: 104, + }); + 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 oevShareAaveV3Borrow = async (): Promise => { + // Create and share new fork (delete the old one if it exists). + const alias = "OEVShare AAVE V3 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: 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( + "0x3E7d1eAB13ad0104d2750B8863b489D65364e32D", + 8 + ); + 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: "0x54586be62e3c3580375ae3723c145253060ca0c2", //aaveOracle v3 + 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 aaveV3Borrow = async () => { + console.log("AAVE V3 Borrow gas comparison with unlock:\n"); + + const regularAaveV3BorrowGas = await regularAaveV3Borrow(); + const oevShareAaveV3BorrowGas = await oevShareAaveV3Borrow(); + const gasDiff = oevShareAaveV3BorrowGas - regularAaveV3BorrowGas; + + console.log("Gas difference: " + gasDiff); +}; diff --git a/scripts/src/gasProfiling/aaveV3Liquidation.ts b/scripts/src/gasProfiling/aaveV3Liquidation.ts new file mode 100644 index 0000000..aec8560 --- /dev/null +++ b/scripts/src/gasProfiling/aaveV3Liquidation.ts @@ -0,0 +1,175 @@ +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/AaveV3.Liquidation.sol/TestedOevShare__factory"; + +// Common constants. +const blockNumber = 18018927; +const chainId = 1; + +// Used ABIs. +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 => { + let simulation: TenderlySimulationResult; + // Original liquidation https://etherscan.io/tx/0x33ada9fb50abfbf29b59647328bd5fff5121ec04ec43a64f1540de0c898dfd6f + simulation = await simulateTenderlyTx({ + chainId, + from: "0x0177ffdf6b5c00ff8eab1a498ea10191ebc965db", + to: "0x681d0d7196a036661b354fa2a7e3b73c2adc43ec", + value: "0", + input: + "0x00000001283a64c5030288e6a0c2ddd26feeb64f039a2c41296fcb3f56400001f400c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000408a84a78ed977af000000000000000000000001c7e716ab0101004f011bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48b8618d9d13e2baa299bb726b413ff66418efbbd0000000000000000000000001c7e716ab0008000000000000000002e6fd523ca61a7d07000000000000000002c1a048a5db4c89", + timestampOverride: forkTimestamp, + fork: { id: forkId, root: rootId }, + description: "liquidationCall", + }); + + return simulation; +}; + +const regularAaveV3Liquidation = async (): Promise => { + // Create and share new fork (delete the old one if it exists). + const alias = "Regular AAVE V3 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 oevShareAaveV3Liquidation = async (): Promise => { + // Create and share new fork (delete the old one if it exists). + const alias = "OEVShare AAVE V3 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( + "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419", + 8 + ); + 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", + [["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"], [testedOevShare.address]] + ); + + simulation = await simulateTenderlyTx({ + chainId, + from: "0xEE56e2B3D491590B5b31738cC34d5232F378a8D5", //aaveOracle owner + to: "0x54586bE62E3c3580375aE3723C145253060Ca0C2", //aaveOracle v3 + 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 aaveV3Liquidation = async () => { + console.log("AAVE V3 Liquidation gas comparison with unlock:\n"); + + const regularAaveV3LiquidationGas = await regularAaveV3Liquidation(); + const oevShareAaveV3LiquidationGas = await oevShareAaveV3Liquidation(); + const gasDiff = oevShareAaveV3LiquidationGas - regularAaveV3LiquidationGas; + + console.log("Gas difference: " + gasDiff); +}; diff --git a/scripts/src/gasProfiling/compoundLiquidation.ts b/scripts/src/gasProfiling/compoundLiquidation.ts new file mode 100644 index 0000000..de39f06 --- /dev/null +++ b/scripts/src/gasProfiling/compoundLiquidation.ts @@ -0,0 +1,253 @@ +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/CompoundV2.Liquidation.sol/TestedOevShare__factory"; + +// Common constants. +// Compound liquidation https://etherscan.io/tx/0xb955a078b9b2a73e111033a3e77142b5768f5729285279d56eff641e43060555 +const blockNumber = 18115157; +const chainId = 1; +const cETHAddress = "0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5"; +const comptrollerAddress = "0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B"; +const uniswapAnchoredViewSourceAddress = + "0x50ce56A3239671Ab62f185704Caedf626352741e"; + +// Used ABIs. +const cTokenAbi = [ + "function mint() payable", + "function borrow(uint borrowAmount) returns (uint)", + "function liquidateBorrow(address borrower, uint256 repayAmount, address cTokenCollateral) returns (uint256)", +]; +const comptrollerAbi = [ + "function enterMarkets(address[] memory cTokens) returns (uint[] memory)", + "function admin() returns (address)", + "function _setPriceOracle(address newOracle) returns (uint)", +]; +const accessControlledOffchainAggregatorAbi = [ + "function owner() returns (address)", + "function addAccess(address)", +]; + +const liquidateBorrow = async ( + forkTimestamp: number, + forkId: string, + rootId?: string // If not provided, the simulation starts off the fork initial state. +): Promise => { + // Post collateral. + let simulation: TenderlySimulationResult; + const cTokenInterface = new utils.Interface(cTokenAbi); + const liquidateInput = cTokenInterface.encodeFunctionData("liquidateBorrow", [ + "0xFeECA8db8b5f4Efdb16BA43d3D06ad2F568a52E3", + "14528113530", + cETHAddress, + ]); + simulation = await simulateTenderlyTx({ + chainId, + from: "0x50A77BA863dBaA84269133e5625EF80072f1884a", + to: "0x39aa39c021dfbae8fac545936693ac917d5e7563", + value: "0", + input: liquidateInput, + timestampOverride: forkTimestamp, + fork: { id: forkId, root: rootId }, + description: "Liquidate Borrow", + }); + + return simulation; +}; + +const regularCompoundLiquidation = async (): Promise => { + // Create and share new fork (delete the old one if it exists). + const alias = "Regular Compound 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, + }); + 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 liquidateBorrow(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 oevShareCompoundLiquidation = async (): Promise => { + // Create and share new fork (delete the old one if it exists). + const alias = "OEVShare Compound 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, + }); + 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 UniswapAnchoredViewDestinationAdapter. + const uavDestinationAdapterFactory = + new UniswapAnchoredViewDestinationAdapter__factory(ownerSigner); + const uavDestinationAdapter = await uavDestinationAdapterFactory.deploy( + uniswapAnchoredViewSourceAddress + ); + await uavDestinationAdapter.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 UniswapAnchoredViewDestinationAdapter" + ); + + // Deploy OevShare. + const testedOevShareFactory = new TestedOevShare__factory(ownerSigner); + const testedOevShare = await testedOevShareFactory.deploy( + uniswapAnchoredViewSourceAddress, + cETHAddress + ); + 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"); + + // Set TestedOevShare on UniswapAnchoredViewDestinationAdapter. + const setOevShareInput = + uavDestinationAdapterFactory.interface.encodeFunctionData("setOevShare", [ + cETHAddress, + testedOevShare.address, + ]); + let simulation = await simulateTenderlyTx({ + chainId, + from: ownerAddress, + to: uavDestinationAdapter.address, + input: setOevShareInput, + timestampOverride: forkTimestamp, + fork: { id: fork.id, root: fork.headId }, + description: "Set OEVShare", + }); + + // Enable unlocker on TestedOevShare. + const setUnlockerInput = testedOevShareFactory.interface.encodeFunctionData( + "setUnlocker", + [unlockerAddress, true] + ); + simulation = await simulateTenderlyTx({ + chainId, + from: ownerAddress, + to: testedOevShare.address, + input: setUnlockerInput, + timestampOverride: forkTimestamp, + fork: { id: fork.id, root: simulation.id }, + description: "Enable unlocker on OEVShare", + }); + + // Whitelist TestedOevShare on chainlink + const sourceChainlinkOracleAddress = + await testedOevShare.callStatic.aggregator(); + const sourceChainlinkOracle = new Contract( + sourceChainlinkOracleAddress, + accessControlledOffchainAggregatorAbi, + provider + ); + const sourceChainlinkOracleOwner = + await sourceChainlinkOracle.callStatic.owner(); + const addAccessInput = sourceChainlinkOracle.interface.encodeFunctionData( + "addAccess", + [testedOevShare.address] + ); + simulation = await simulateTenderlyTx({ + chainId, + from: sourceChainlinkOracleOwner, + to: sourceChainlinkOracleAddress, + input: addAccessInput, + timestampOverride: forkTimestamp, + fork: { id: fork.id, root: simulation.id }, + description: "Whitelist OEVShare on Chainlink", + }); + + // Point Comptroller to UniswapAnchoredViewDestinationAdapter. + const comptroller = new Contract( + comptrollerAddress, + comptrollerAbi, + provider + ); + const comptrollerAdmin = await comptroller.callStatic.admin(); + const setPriceOracleInput = comptroller.interface.encodeFunctionData( + "_setPriceOracle", + [uavDestinationAdapter.address] + ); + simulation = await simulateTenderlyTx({ + chainId, + from: comptrollerAdmin, + to: comptrollerAddress, + input: setPriceOracleInput, + timestampOverride: forkTimestamp, + fork: { id: fork.id, root: simulation.id }, + description: "Switch Oracle on Comptroller", + }); + + // 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 liquidateBorrow(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 compoundLiquidation = async () => { + console.log("Compound Liquidation gas comparison with unlock:\n"); + + const regularCompoundBorrowGas = await regularCompoundLiquidation(); + const oevShareCompoundBorrowGas = await oevShareCompoundLiquidation(); + const gasDiff = oevShareCompoundBorrowGas - regularCompoundBorrowGas; + + console.log("Gas difference: " + gasDiff); +}; diff --git a/scripts/src/gasProfiling/index.ts b/scripts/src/gasProfiling/index.ts index bacf21e..e9f7db2 100644 --- a/scripts/src/gasProfiling/index.ts +++ b/scripts/src/gasProfiling/index.ts @@ -1,9 +1,19 @@ import { compoundBorrow } from "./compoundBorrow"; +import { compoundLiquidation } from "./compoundLiquidation"; +import { aaveV2Liquidation } from "./aaveV2Liquidation"; +import { aaveV3Liquidation } from "./aaveV3Liquidation"; +import { aaveV2Borrow } from "./aaveV2Borrow"; +import { aaveV3Borrow } from "./aaveV3Borrow"; const main = async () => { console.log("Running gas profiling ...\n"); await compoundBorrow(); + await compoundLiquidation(); + await aaveV2Liquidation(); + await aaveV3Liquidation(); + await aaveV2Borrow(); + await aaveV3Borrow(); }; main().then( diff --git a/src/adapters/destination-adapters/PythDestinationAdapter.sol b/src/adapters/destination-adapters/PythDestinationAdapter.sol index 5dcdffa..d64c9b7 100644 --- a/src/adapters/destination-adapters/PythDestinationAdapter.sol +++ b/src/adapters/destination-adapters/PythDestinationAdapter.sol @@ -75,7 +75,7 @@ contract PythDestinationAdapter is Ownable, IPyth { // Internal function to get absolute difference between two numbers. This implementation replicates diff function // logic from AbstractPyth contract used in Pyth oracle: // https://github.com/pyth-network/pyth-sdk-solidity/blob/c24b3e0173a5715c875ae035c20e063cb900f481/AbstractPyth.sol#L79 - function _diff(uint x, uint y) internal pure returns (uint) { + function _diff(uint256 x, uint256 y) internal pure returns (uint256) { if (x > y) return x - y; return y - x; }