Add script to calculate relayer losses during the hack

This commit is contained in:
Theo 2023-10-25 06:03:13 -07:00
parent 739c28414c
commit 8c97665093
6 changed files with 99 additions and 23 deletions

4
data/relayerLosses.txt Normal file

@ -0,0 +1,4 @@
0x864DF9CD806D58341f13602103Bf853066ff962a = 625894225496155734516 // ~ 625 TORN
0x5555555731006f71f121144534Ca7C8799F66AA3 = 43970301082908267318 // ~ 43 TORN
0x2Ee39Ff05643bC7cc9ed31B71e142429044A425C = 189640345451160934437 // ~ 189 TORN
0x03392600086874456E08D2bAc104380BCdEBCfC0 = 129757783603193410350 // ~ 129 TORN

@ -1,22 +1,26 @@
{ {
"name": "forge-proposal-template", "name": "proposal-33-compensate-relayer-losses",
"version": "1.0.0", "version": "1.0.0",
"repository": "https://git.tornado.ws/Theo/forge-proposal-template", "repository": "https://git.tornado.ws/Theo/proposal-33-compensate-relayer-losses",
"author": "Theo", "author": "Theo",
"license": "MIT", "license": "MIT",
"type": "module",
"private": false, "private": false,
"scripts": { "scripts": {
"init": "cd lib && git clone --recurse-submodules https://github.com/foundry-rs/forge-std", "init": "cd lib && git clone --recurse-submodules https://github.com/foundry-rs/forge-std",
"test:windows": ".\\.env.bat && forge test", "test:windows": ".\\.env.bat && forge test",
"test:linux": ". .env && forge test", "test:linux": ". .env && forge test",
"test:gas:windows": ".\\.env.bat && forge test --gas-report", "test:gas:windows": ".\\.env.bat && forge test --gas-report",
"test:gas:linux": ". .env && forge test --gas-report" "test:gas:linux": ". .env && forge test --gas-report",
"calculate": "npx ts-node scripts/calculateLosses.ts"
}, },
"dependencies": { "dependencies": {
"@ensdomains/ens-contracts": "^0.0.21", "@ensdomains/ens-contracts": "^0.0.21",
"@openzeppelin/contracts": "^4.9.0", "@openzeppelin/contracts": "^4.9.0",
"@openzeppelin/upgrades-core": "^1.26.2" "@openzeppelin/upgrades-core": "^1.26.2",
"axios": "^1.5.1",
"dotenv": "^16.3.1",
"web3": "^4.2.0",
"web3-utils": "^4.0.7"
}, },
"optionalDependencies": { "optionalDependencies": {
"@gnosis.pm/ido-contracts": "^0.5.0", "@gnosis.pm/ido-contracts": "^0.5.0",

@ -0,0 +1,67 @@
import * as dotenv from "dotenv";
import { Web3 } from "web3";
import fs from "fs";
import path from "path";
import axios from "axios";
import { RelayerLosses, TxReceipt } from "./types";
dotenv.config();
const web3 = new Web3(process.env.MAINNET_RPC_URL);
const routerAddress = "0xd90e2f925DA726b50C4Ed8D0Fb90Ad053324F31b";
async function fetchFailedWithdrawals() {
const attackStartBlock = 18363087;
const attackEndBlock = 18366622;
let failedWithdrawalTransactions = [];
for (let blockNumber = attackStartBlock; blockNumber <= attackEndBlock; blockNumber++) {
let block = null;
while (block == null)
try {
block = await web3.eth.getBlock(blockNumber);
} catch (e) {}
const results = await Promise.allSettled(block.transactions.map((tx) => web3.eth.getTransactionReceipt(tx as string)));
const transactions = results.filter((r) => r.status === "fulfilled").map((r) => (r as PromiseFulfilledResult<TxReceipt>).value);
failedWithdrawalTransactions.push(...transactions.filter((tx) => tx.to === routerAddress.toLowerCase() && tx.status == 0));
}
return failedWithdrawalTransactions;
}
async function calculateLosses(failedTransactions: Array<TxReceipt>): Promise<RelayerLosses> {
return failedTransactions.reduce((acc, tx) => {
const gasExpenses = BigInt(tx.gasUsed) * BigInt(tx.effectiveGasPrice!);
acc[tx.from] = acc[tx.from] ? acc[tx.from] + gasExpenses : gasExpenses;
return acc;
}, {} as RelayerLosses);
}
async function getEthRateInTorn(): Promise<bigint> {
const api = "https://api.binance.com/api/v3/ticker/price?symbol=";
const fetchPrice = async (symbol: string) => Number((await axios.get(api + symbol)).data.price);
const tornInUsd = await fetchPrice("TORNBUSD");
const ethInUsd = await fetchPrice("ETHUSDT");
return BigInt(Math.floor(ethInUsd / tornInUsd));
}
function writeData(relayerLossesInTorn: RelayerLosses) {
const data = Object.entries(relayerLossesInTorn)
.map(([relayer, amount]) => `${web3.utils.toChecksumAddress(relayer)} = ${amount.toString()} // ~ ${amount / 10n ** 18n} TORN`)
.join("\n");
fs.writeFileSync(path.join(".", "data", "relayerLosses.txt"), data);
}
async function main() {
const failedWithdrawals = await fetchFailedWithdrawals();
const relayerLosses = await calculateLosses(failedWithdrawals);
console.log(relayerLosses);
const ethRateInTorn = await getEthRateInTorn();
const relayerLossesInTorn = Object.fromEntries(
Object.entries(relayerLosses).map(([relayer, amounthInEth]) => [relayer, amounthInEth * ethRateInTorn])
);
writeData(relayerLossesInTorn);
console.log("All data about relayer losses written successfully.");
}
main();

4
scripts/types.ts Normal file

@ -0,0 +1,4 @@
import { Web3Eth } from "web3";
export type TxReceipt = Awaited<ReturnType<Web3Eth["getTransactionReceipt"]>>;
export type RelayerLosses = Record<string, bigint>;

@ -1,34 +1,31 @@
type NodeVarObject = { [key: string]: string | number }; type NodeVarObject = { [key: string]: string };
type NodeVarArray = [string, string];
const solidityCodePadding = " ".repeat(8); const solidityCodePadding = " ".repeat(8);
const pad = (decl: string, padding: string = solidityCodePadding) => padding + decl + "\n"; const pad = (decl: string, padding: string = solidityCodePadding) => padding + decl + "\n";
class DeclCalculator { class DeclCalculator {
declType!: string | number;
padding!: string;
transformator!: Function;
public constructor( public constructor(
declType: string | number, private declType: string,
padding: string = solidityCodePadding, private padding: string = solidityCodePadding,
transformator: Function = ( private transformator: Function = (
() => (x: any) =>
x
)(),
private variableNameChanger: Function = (
() => (x: any) => () => (x: any) =>
x x
)() )()
) { ) {}
this.declType = declType;
this.padding = padding || solidityCodePadding;
this.transformator = transformator;
}
private displayVariableName(varObj: NodeVarObject) { private displayVariableName(varObj: NodeVarObject) {
return Object.keys(varObj)[0]; return Object.keys(varObj)[0];
} }
public calculateDecl = (varObj: NodeVarObject, type: string = "bytes32") => { public calculateDecl = (varInfo: NodeVarObject | NodeVarArray, type: string = "bytes32") => {
const solidityVariableName = this.displayVariableName(varObj); const solidityVariableName = this.variableNameChanger(Array.isArray(varInfo) ? varInfo[0] : this.displayVariableName(varInfo));
const solidityVariableValue = this.transformator(Object.values(varObj)[0]); const solidityVariableValue = this.transformator(Array.isArray(varInfo) ? varInfo[1] : Object.values(varInfo)[0]);
const solidityDeclaration = `${this.declType || type} ${solidityVariableName} = ${solidityVariableValue.toString()};`; const solidityDeclaration = `${this.declType || type} ${solidityVariableName} = ${solidityVariableValue};`;
return pad(solidityDeclaration, this.padding); return pad(solidityDeclaration, this.padding);
}; };

@ -27,7 +27,7 @@
/* Modules */ /* Modules */
"module": "NodeNext" /* Specify what module code is generated. */, "module": "NodeNext" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */ // "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ "moduleResolution": "NodeNext" /* Specify how TypeScript looks up a file from a given module specifier. */,
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */