Skip to content

Commit aaa0a3e

Browse files
authored
feat(gas): add tornado cash unshield + railgun unshield + transfer (#10)
1 parent 16b4a62 commit aaa0a3e

9 files changed

Lines changed: 221 additions & 9 deletions

File tree

gas-benchmarks/src/privacy-pools/constants.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,37 @@ export const DEPOSITED_EVENT_ABI = {
3333
* LeafInserted() - To notify the leaf insertion in the merkle tree
3434
* Deposited() - Emitted inside PrivacyPool.sol contract (internal)
3535
* Deposited() - Emitted inside Entrypoint.sol contract (external)
36+
*
37+
* Example:
38+
* https://etherscan.io/tx/0x87320aaae4868c6f5b7c8b31ba2fc82005bdd7522fdf85f9eb8dcc93a34cb475
3639
*/
3740
export const NUMBER_OF_SHIELD_EVENTS = 3;
41+
42+
/**
43+
* Event ABI for the WithdrawalRelayed event emitted by Entrypoint.relay()
44+
*/
45+
export const WITHDRAWAL_RELAYED_EVENT_ABI = {
46+
type: "event",
47+
name: "WithdrawalRelayed",
48+
inputs: [
49+
{ name: "_relayer", type: "address", indexed: true },
50+
{ name: "_recipient", type: "address", indexed: true },
51+
{ name: "_asset", type: "address", indexed: true },
52+
{ name: "_amount", type: "uint256", indexed: false },
53+
{ name: "_feeAmount", type: "uint256", indexed: false },
54+
],
55+
} as const satisfies AbiEvent;
56+
57+
/**
58+
* Entrypoint.relay (unshield) function call emits:
59+
* LeafInserted() - Emitted inside State.sol -> insert()
60+
* Transfer() - move funds to entrypoint (Emitted inside PrivacyPoolComplex.sol -> _push)
61+
* Withdrawn() - Emitted inside PrivacyPool.sol -> withdraw()
62+
* Transfer() - Net amount to recipient
63+
* Transfer() - Fee to fee recipient
64+
* WithdrawalRelayed() - Emitted inside Entrypoint.relay()
65+
*
66+
* Example:
67+
* https://etherscan.io/tx/0x47e918eda32bc332a5684aa986733eb4fde7a4f8189e21443f23adf0807974b7
68+
*/
69+
export const NUMBER_OF_UNSHIELD_EVENTS = 6;

gas-benchmarks/src/privacy-pools/index.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import {
55
DEPOSITED_EVENT_ABI,
66
MAX_OF_LOGS,
77
NUMBER_OF_SHIELD_EVENTS,
8+
NUMBER_OF_UNSHIELD_EVENTS,
89
PRIVACY_POOLS_ENTRYPOINT_PROXY,
10+
WITHDRAWAL_RELAYED_EVENT_ABI,
911
} from "./constants.js";
1012

1113
export class PrivacyPools {
@@ -15,6 +17,7 @@ export class PrivacyPools {
1517

1618
async benchmark(): Promise<void> {
1719
await this.benchmarkShield();
20+
await this.benchmarkUnshield();
1821
}
1922

2023
async benchmarkShield(): Promise<void> {
@@ -30,4 +33,18 @@ export class PrivacyPools {
3033

3134
await saveGasMetrics(metrics, `${this.name}_${this.version}`, "shield");
3235
}
36+
37+
async benchmarkUnshield(): Promise<void> {
38+
const logs = await getEventLogs({
39+
contractAddress: PRIVACY_POOLS_ENTRYPOINT_PROXY,
40+
event: WITHDRAWAL_RELAYED_EVENT_ABI,
41+
maxLogs: MAX_OF_LOGS,
42+
});
43+
const uniqueLogs = getUniqueLogs(logs);
44+
45+
const txs = await getTransactionsWithNEvents(uniqueLogs, NUMBER_OF_UNSHIELD_EVENTS);
46+
const metrics = getAverageMetrics(txs);
47+
48+
await saveGasMetrics(metrics, `${this.name}_${this.version}`, "unshield");
49+
}
3350
}

gas-benchmarks/src/railgun/constants.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,65 @@ export const SHIELD_EVENT_ABI = {
5959
* Transfer() - Tokens sent to shielded pool
6060
* Transfer() - Shield fee sent to vault
6161
* Shield() - To notify the shield
62+
*
63+
* Example:
64+
* https://etherscan.io/tx/0x5c9bb21f9aa22f636e468bfb19b17093ccc5de7d86aad601b5b4b607f07f1143
6265
*/
6366
export const NUMBER_OF_SHIELD_EVENTS = 3;
67+
68+
/**
69+
* Event ABI for the Unshield event emitted by RailgunLogic in function transferTokenOut
70+
*/
71+
export const UNSHIELD_EVENT_ABI = {
72+
type: "event",
73+
name: "Unshield",
74+
inputs: [
75+
{ name: "to", type: "address", indexed: false },
76+
{
77+
name: "token",
78+
type: "tuple",
79+
indexed: false,
80+
components: [
81+
{ name: "tokenType", type: "uint8" },
82+
{ name: "tokenAddress", type: "address" },
83+
{ name: "tokenSubID", type: "uint256" },
84+
],
85+
},
86+
{ name: "amount", type: "uint256", indexed: false },
87+
{ name: "fee", type: "uint256", indexed: false },
88+
],
89+
} as const satisfies AbiEvent;
90+
91+
/**
92+
* RailgunSmartWallet.transact function call when unshielding emits:
93+
* Nullified() - Nullifier being spent emitted in RailgunLogic.accumulateAndNullifyTransaction
94+
* Transfer() - Transfer unshielded tokens to recipient emitted in RailgunLogic.transferTokenOut
95+
* Transfer() - Transfer unshield fee to vault emitted in RailgunLogic.transferTokenOut
96+
* Unshield() - To notify the unshield process emitted in RailgunLogic.transferTokenOut
97+
*
98+
* Example:
99+
* https://etherscan.io/tx/0x2c3e8738f9d0d3e98b702a6dc375162fe21777e35f19d8b662088b2b6987e722
100+
*/
101+
export const NUMBER_OF_UNSHIELD_EVENTS = 4;
102+
103+
/**
104+
* Event ABI for the Nullified event emitted by RailgunLogic in function accumulateAndNullifyTransaction
105+
*/
106+
export const NULLIFIED_EVENT_ABI = {
107+
type: "event",
108+
name: "Nullified",
109+
inputs: [
110+
{ name: "treeNumber", type: "uint16", indexed: false },
111+
{ name: "nullifier", type: "bytes32[]", indexed: false },
112+
],
113+
} as const satisfies AbiEvent;
114+
115+
/**
116+
* RailgunSmartWallet.transact (private transfer) function call emits:
117+
* Nullified() - Nullifier being spent emitted in RailgunLogic.accumulateAndNullifyTransaction
118+
* Transact() - To notify the commitment state update emitted in RailgunSmartWallet.transact
119+
*
120+
* Example:
121+
* https://etherscan.io/tx/0xb010a8f2babfa78bb6945221817238c58234d06eb56177de6e002e761574fc53
122+
*/
123+
export const NUMBER_OF_TRANSFER_EVENTS = 2;

gas-benchmarks/src/railgun/index.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
import { getEventLogs, getTransactionsWithNEvents, getUniqueLogs } from "../utils/rpc.js";
22
import { getAverageMetrics, saveGasMetrics } from "../utils/utils.js";
33

4-
import { MAX_OF_LOGS, NUMBER_OF_SHIELD_EVENTS, RAILGUN_SMART_WALLET_PROXY, SHIELD_EVENT_ABI } from "./constants.js";
4+
import {
5+
MAX_OF_LOGS,
6+
NULLIFIED_EVENT_ABI,
7+
NUMBER_OF_SHIELD_EVENTS,
8+
NUMBER_OF_TRANSFER_EVENTS,
9+
NUMBER_OF_UNSHIELD_EVENTS,
10+
RAILGUN_SMART_WALLET_PROXY,
11+
SHIELD_EVENT_ABI,
12+
UNSHIELD_EVENT_ABI,
13+
} from "./constants.js";
514

615
export class Railgun {
716
readonly name = "railgun";
@@ -10,6 +19,8 @@ export class Railgun {
1019

1120
async benchmark(): Promise<void> {
1221
await this.benchmarkShield();
22+
await this.benchmarkUnshield();
23+
await this.benchmarkTransfer();
1324
}
1425

1526
async benchmarkShield(): Promise<void> {
@@ -25,4 +36,32 @@ export class Railgun {
2536

2637
await saveGasMetrics(metrics, `${this.name}_${this.version}`, "shield");
2738
}
39+
40+
async benchmarkUnshield(): Promise<void> {
41+
const logs = await getEventLogs({
42+
contractAddress: RAILGUN_SMART_WALLET_PROXY,
43+
event: UNSHIELD_EVENT_ABI,
44+
maxLogs: MAX_OF_LOGS,
45+
});
46+
const uniqueLogs = getUniqueLogs(logs);
47+
48+
const txs = await getTransactionsWithNEvents(uniqueLogs, NUMBER_OF_UNSHIELD_EVENTS);
49+
const metrics = getAverageMetrics(txs);
50+
51+
await saveGasMetrics(metrics, `${this.name}_${this.version}`, "unshield");
52+
}
53+
54+
async benchmarkTransfer(): Promise<void> {
55+
const logs = await getEventLogs({
56+
contractAddress: RAILGUN_SMART_WALLET_PROXY,
57+
event: NULLIFIED_EVENT_ABI,
58+
maxLogs: MAX_OF_LOGS,
59+
});
60+
const uniqueLogs = getUniqueLogs(logs);
61+
62+
const txs = await getTransactionsWithNEvents(uniqueLogs, NUMBER_OF_TRANSFER_EVENTS);
63+
const metrics = getAverageMetrics(txs);
64+
65+
await saveGasMetrics(metrics, `${this.name}_${this.version}`, "transfer");
66+
}
2867
}

gas-benchmarks/src/tornado-cash/constants.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const MAX_OF_LOGS = NUMBER_OF_TRANSACTIONS * 5;
1010

1111
/**
1212
* Tornado Cash Router contract that directs deposits to different pools (ETH, ERC20 0.1, ERC20 1, ERC20 10, ERC20 100):
13-
* https://github.com/tornadocash/tornado-anonymity-mining/blob/d93d7c8870fc3cd4cb1da698301e737e1606ba9c/contracts/TornadoProxy.sol
13+
* https://github.com/contractscan/etherscan.io-0xd90e2f925da726b50c4ed8d0fb90ad053324f31b/blob/main/TornadoRouter.sol
1414
*/
1515
export const TORNADO_CASH_ROUTER: Hex = "0xd90e2f925DA726b50C4Ed8D0Fb90Ad053324F31b";
1616

@@ -28,7 +28,39 @@ export const ENCRYPTED_NOTE_EVENT_ABI = {
2828

2929
/**
3030
* A deposit function call emits:
31-
* Deposit() - To notify the token public deposit into the contract
32-
* EncryptedNote() - To notify the encrypted note generation
31+
* Deposit() - To notify the token public deposit emitted by the pool contract
32+
* EncryptedNote() - To notify the encrypted note generation emitted by the router contract
33+
*
34+
* Example:
35+
* https://etherscan.io/tx/0x2e847019a164ebff78700fcd1d19b5ade27b78d3869770905e87eed38494b834
3336
*/
3437
export const NUMBER_OF_SHIELD_EVENTS = 2;
38+
39+
/**
40+
* Tornado Cash Relayer Registry contract that emits a StakeBurned event when a relayed withdrawal is executed.
41+
* After it, a pool (ETH, ERC20 0.1, ERC20 1, ERC20 10, ERC20 100) emits a Withdrawal event.
42+
* https://github.com/contractscan/etherscan.io-0xd90e2f925da726b50c4ed8d0fb90ad053324f31b/blob/main/RelayerRegistry.sol
43+
*/
44+
export const TORNADO_CASH_RELAYER_REGISTRY: Hex = "0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2";
45+
46+
/**
47+
* Event ABI for the StakeBurned event emitted by registry during withdraw
48+
*/
49+
export const STAKE_BURNED_EVENT_ABI = {
50+
type: "event",
51+
name: "StakeBurned",
52+
inputs: [
53+
{ name: "relayer", type: "address", indexed: false },
54+
{ name: "amountBurned", type: "uint256", indexed: false },
55+
],
56+
} as const satisfies AbiEvent;
57+
58+
/**
59+
* A withdraw function call emits:
60+
* StakeBurned() - To notify the stake burning of the withdrawal (emitted by the registry)
61+
* Withdrawal() - To notify the withdrawal (emitted by the specific pool)
62+
*
63+
* Example:
64+
* https://etherscan.io/tx/0x99e1f27a5d7e8bfcaadad216a6130f66eedeeca43c5d917acd6952414e388331
65+
*/
66+
export const NUMBER_OF_UNSHIELD_EVENTS = 2;

gas-benchmarks/src/tornado-cash/index.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import { getEventLogs, getTransactionsWithNEvents, getUniqueLogs } from "../utils/rpc.js";
22
import { getAverageMetrics, saveGasMetrics } from "../utils/utils.js";
33

4-
import { ENCRYPTED_NOTE_EVENT_ABI, MAX_OF_LOGS, NUMBER_OF_SHIELD_EVENTS, TORNADO_CASH_ROUTER } from "./constants.js";
4+
import {
5+
ENCRYPTED_NOTE_EVENT_ABI,
6+
MAX_OF_LOGS,
7+
NUMBER_OF_SHIELD_EVENTS,
8+
NUMBER_OF_UNSHIELD_EVENTS,
9+
STAKE_BURNED_EVENT_ABI,
10+
TORNADO_CASH_RELAYER_REGISTRY,
11+
TORNADO_CASH_ROUTER,
12+
} from "./constants.js";
513

614
export class TornadoCash {
715
readonly name = "tornado-cash";
@@ -10,6 +18,7 @@ export class TornadoCash {
1018

1119
async benchmark(): Promise<void> {
1220
await this.benchmarkShield();
21+
await this.benchmarkUnshield();
1322
}
1423

1524
async benchmarkShield(): Promise<void> {
@@ -25,4 +34,18 @@ export class TornadoCash {
2534

2635
await saveGasMetrics(metrics, `${this.name}_${this.version}`, "shield");
2736
}
37+
38+
async benchmarkUnshield(): Promise<void> {
39+
const logs = await getEventLogs({
40+
contractAddress: TORNADO_CASH_RELAYER_REGISTRY,
41+
event: STAKE_BURNED_EVENT_ABI,
42+
maxLogs: MAX_OF_LOGS,
43+
});
44+
const uniqueLogs = getUniqueLogs(logs);
45+
46+
const txs = await getTransactionsWithNEvents(uniqueLogs, NUMBER_OF_UNSHIELD_EVENTS);
47+
const metrics = getAverageMetrics(txs);
48+
49+
await saveGasMetrics(metrics, `${this.name}_${this.version}`, "unshield");
50+
}
2851
}

gas-benchmarks/src/utils/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ export const ETH_RPC_URL = String(process.env.ETH_RPC_URL);
66

77
export const BLOCK_RANGE = 1_000n;
88

9+
export const MAX_NUMBER_OF_RPC_TRIES = 10;
10+
911
export const NUMBER_OF_TRANSACTIONS = Number(process.env.NUMBER_OF_TRANSACTIONS);
1012

1113
export const BENCHMARKS_OUTPUT_PATH = "./benchmarks.json";

gas-benchmarks/src/utils/rpc.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
import { createPublicClient, http, type Hash, type Log, type TransactionReceipt } from "viem";
22
import { mainnet } from "viem/chains";
33

4-
import type { GetShieldEventLogs } from "./types.js";
4+
import type { GetEventLogs } from "./types.js";
55

6-
import { ETH_RPC_URL, NUMBER_OF_TRANSACTIONS } from "./constants.js";
6+
import { ETH_RPC_URL, MAX_NUMBER_OF_RPC_TRIES, NUMBER_OF_TRANSACTIONS } from "./constants.js";
77
import { getBlockInRange } from "./utils.js";
88

99
export const publicClient = createPublicClient({
1010
transport: http(ETH_RPC_URL),
1111
chain: mainnet,
1212
});
1313

14-
export const getEventLogs = async ({ contractAddress, event, maxLogs }: GetShieldEventLogs): Promise<Log[]> => {
14+
export const getEventLogs = async ({ contractAddress, event, maxLogs }: GetEventLogs): Promise<Log[]> => {
1515
const latestBlock = await publicClient.getBlockNumber();
1616
let toBlock = latestBlock;
1717
let fromBlock = getBlockInRange(toBlock);
1818

19+
let tries = 0;
1920
const logs: Log[] = [];
2021

2122
while (logs.length < maxLogs) {
@@ -35,6 +36,12 @@ export const getEventLogs = async ({ contractAddress, event, maxLogs }: GetShiel
3536

3637
toBlock = fromBlock - 1n;
3738
fromBlock = getBlockInRange(toBlock);
39+
40+
if (tries >= MAX_NUMBER_OF_RPC_TRIES) {
41+
break;
42+
}
43+
44+
tries += 1;
3845
}
3946

4047
return logs;

gas-benchmarks/src/utils/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export interface GasMetrics {
66
averageTxFee: bigint | "no-data";
77
}
88

9-
export interface GetShieldEventLogs {
9+
export interface GetEventLogs {
1010
contractAddress: Hex;
1111
event: AbiEvent;
1212
maxLogs: number;

0 commit comments

Comments
 (0)