Skip to content

Commit

Permalink
feat: fork utils (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
sakulstra authored Jun 26, 2023
1 parent 82a2959 commit 338e0eb
Show file tree
Hide file tree
Showing 10 changed files with 289 additions and 23 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"json-bigint": "^1.0.0",
"node-fetch": "^2.6.9",
"object-hash": "^3.0.0",
"viem": "^1.0.00",
"viem": "^1.0.7",
"yargs": "^17.7.2",
"zod": "^3.21.4"
}
Expand Down
8 changes: 7 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,11 @@ import { hideBin } from 'yargs/helpers';
import * as ipfsCmd from './commands/ipfs-upload';
import * as diffSnapshot from './commands/diff-snaphots';
import * as simulateProposal from './commands/simulate-proposal';
import * as fork from './commands/fork';

yargs(hideBin(process.argv)).command(ipfsCmd).command(diffSnapshot).command(simulateProposal).demandCommand().argv;
yargs(hideBin(process.argv))
.command(ipfsCmd)
.command(diffSnapshot)
.command(simulateProposal)
.command(fork)
.demandCommand().argv;
112 changes: 112 additions & 0 deletions src/commands/fork.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { arbitrum, mainnet, optimism, polygon } from 'viem/chains';
import {
getProposalStateById,
getTenderlyActionSetCreationPayload,
getTenderlyActionSetExecutionPayload,
} from '../simulate/networks/commonL2';
import { polygonExecutorContract } from '../simulate/networks/polygon';
import { arbitrumClient, optimismClient, polygonClient } from '../utils/rpcClients';
import { tenderly } from '../utils/tenderlyClient';
import { arbitrumExecutorContract } from '../simulate/networks/arbitrum';
import { optimismExecutorContract } from '../simulate/networks/optimism';
import { polygon as modulePolygon } from '../simulate/networks/polygon';
import { arbitrum as moduleArbitrum } from '../simulate/networks/arbitrum';
import { optimism as moduleOptimism } from '../simulate/networks/optimism';
import { ActionSetState } from '../simulate/networks/types';

export type ForkOptions = {
chainId: number;
alias: string;
blockNumber?: number;
proposalId?: number;
payloadAddress?: string;
executor?: string;
};

export const command = 'fork';

export const describe = 'creates a tenderly fork';

export const builder = (yargs) =>
yargs
.option('chainId', {
type: 'number',
describe: 'the chainId to fork',
})
.option('blockNumber', {
type: 'number',
describe: 'the blocknumber to fork (latest if omitted)',
})
.option('alias', {
type: 'string',
describe: 'custom alias',
})
.option('proposalId', {
type: 'number',
describe: 'Proposal or actionSetId',
})
.option('payloadAddress', {
type: 'string',
describe: 'address of the payload to execute',
})
.option('executor', {
type: 'string',
describe: '(optional) address of the executor',
});

const getL2 = (chainId: number) => {
if (chainId === polygon.id) return [polygonExecutorContract, polygonClient, modulePolygon] as const;
if (chainId === arbitrum.id) return [arbitrumExecutorContract, arbitrumClient, moduleArbitrum] as const;
if (chainId === optimism.id) return [optimismExecutorContract, optimismClient, moduleOptimism] as const;
throw new Error(`ChainId: ${chainId} not supported`);
};

function getAlias(options: ForkOptions) {
const unix = Math.floor(new Date().getTime() / 1000);
if (options.alias) {
return `${unix}-${options.alias}`;
} else if (options.proposalId) {
return `${unix}-proposalId-${options.proposalId}`;
} else if (options.payloadAddress) {
return `${unix}-payloadAddress-${options.payloadAddress}`;
}
// } else if (options.artifactPath) {
// return `${unix}-artifact-${options.artifactPath.replace(/^.*[\\\/]/, '')}`;
// }
return 'vanilla-fork';
}

export const handler = async function (argv) {
const forkConfig = {
chainId: argv.chainId,
alias: getAlias(argv),
};
if (argv.chainId === mainnet.id) {
throw new Error('Mainnet proposal forking not yet supported');
} else {
const [executor, client, networkModule] = getL2(argv.chainId);
if (argv.proposalId) {
const { executedLogs, queuedLogs } = await networkModule.cacheLogs();
const response = await getProposalStateById({ proposalId: argv.proposalId, executedLogs, queuedLogs });
if (response.state === ActionSetState.NOT_FOUND) {
throw new Error(`ActionSet ${argv.proposalId} not found`);
} else if (response.state === ActionSetState.EXECUTED) {
throw new Error(`ActionSet ${argv.proposalId} already executed`);
} else {
const payload = await getTenderlyActionSetExecutionPayload(executor, client, response.queuedLog);
const fork = await tenderly.fork({ ...forkConfig, blockNumber: payload.block_number });
await tenderly.unwrapAndExecuteSimulationPayloadOnFork(fork, payload);
}
} else if (argv.payloadAddress) {
const payload = await getTenderlyActionSetCreationPayload(executor, client, {
calldatas: ['0x0'],
signatures: ['execute()'],
targets: [argv.payloadAddress],
values: [0n],
withDelegatecalls: [true],
});
const fork = await tenderly.fork(forkConfig);
await tenderly.unwrapAndExecuteSimulationPayloadOnFork(fork, payload);
}
}
};
2 changes: 1 addition & 1 deletion src/simulate/networks/arbitrum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { getProposalState, simulateNewActionSet, simulateQueuedActionSet } from

const ARBITRUM_INBOX = '0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f'; // TODO: should probably be on address-book

const arbitrumExecutorContract = getContract({
export const arbitrumExecutorContract = getContract({
address: AaveGovernanceV2.ARBITRUM_BRIDGE_EXECUTOR,
abi: ARBITRUM_BRIDGE_EXECUTOR_ABI,
publicClient: arbitrumClient,
Expand Down
48 changes: 42 additions & 6 deletions src/simulate/networks/commonL2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,23 @@ export function formatArgs(rawArgs: any): FormattedArgs {
};
}

export async function getProposalStateById({
queuedLogs,
executedLogs,
proposalId,
}: GetProposalStateProps<typeof executorABI> & { proposalId: number }) {
const queuedLog = queuedLogs.find((event) => Number(event.args.id) === Number(proposalId));
if (queuedLog) {
const executedLog = executedLogs.find((event) => Number(event.args.id) == Number(proposalId));
if (executedLog) {
return { executedLog: executedLog, queuedLog: queuedLog, state: ActionSetState.EXECUTED };
} else {
return { queuedLog: queuedLog, state: ActionSetState.QUEUED };
}
}
return { state: ActionSetState.NOT_FOUND };
}

export async function getProposalState<TAbi>({
queuedLogs,
executedLogs,
Expand Down Expand Up @@ -174,7 +191,7 @@ export async function getProposalState<TAbi>({
return { state: ActionSetState.NOT_FOUND, args };
}

export async function simulateQueuedActionSet(
export async function getTenderlyActionSetExecutionPayload(
executorContract: GetContractReturnType<typeof executorABI, PublicClient>,
client: PublicClient,
log
Expand Down Expand Up @@ -204,10 +221,19 @@ export async function simulateQueuedActionSet(
timestamp: toHex(BigInt(log.args.executionTime!)),
},
};
return tenderly.simulate(simulationPayload);
return simulationPayload;
}

export async function simulateNewActionSet(
export async function simulateQueuedActionSet(
executorContract: GetContractReturnType<typeof executorABI, PublicClient>,
client: PublicClient,
log
) {
const payload = await getTenderlyActionSetExecutionPayload(executorContract, client, log);
return tenderly.simulate(payload);
}

export async function getTenderlyActionSetCreationPayload(
executorContract: GetContractReturnType<typeof executorABI, PublicClient>,
client: PublicClient,
args: FormattedArgs
Expand Down Expand Up @@ -283,15 +309,15 @@ export async function simulateNewActionSet(
return acc;
}, {});

return tenderly.simulate({
const payload = {
network_id: String(client.chain!.id),
from: EOA,
to: executorContract.address,
state_objects: {
[executorContract.address]: {
storage: {
// _actionsSetCounter slot
[pad(toHex(5), { size: 32 })]: toHex(currentCount + 1n),
[pad(toHex(5), { size: 32 })]: pad(toHex(currentCount + 1n), { size: 32 }),
// _actionsSets
...proposalStorage,
// _queuedActions
Expand All @@ -308,5 +334,15 @@ export async function simulateNewActionSet(
timestamp: toHex(latestBlock.timestamp),
number: toHex(latestBlock.number!),
},
});
};
return payload;
}

export async function simulateNewActionSet(
executorContract: GetContractReturnType<typeof executorABI, PublicClient>,
client: PublicClient,
args: FormattedArgs
) {
const payload = await getTenderlyActionSetCreationPayload(executorContract, client, args);
return tenderly.simulate(payload);
}
2 changes: 1 addition & 1 deletion src/simulate/networks/optimism.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { getProposalState, simulateNewActionSet, simulateQueuedActionSet } from

const OPTIMISM_L1_CROSS_COMAIN_MESSENGER = '0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1';

const optimismExecutorContract = getContract({
export const optimismExecutorContract = getContract({
address: AaveGovernanceV2.OPTIMISM_BRIDGE_EXECUTOR,
abi: OPTIMISM_BRIDGE_EXECUTOR_ABI,
publicClient: optimismClient,
Expand Down
2 changes: 1 addition & 1 deletion src/simulate/networks/polygon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { formatArgs, simulateNewActionSet, simulateQueuedActionSet } from './com

const POLYGON_FX_ROOT = '0xfe5e5D361b2ad62c541bAb87C45a0B9B018389a2';

const polygonExecutorContract = getContract({
export const polygonExecutorContract = getContract({
address: AaveGovernanceV2.POLYGON_BRIDGE_EXECUTOR,
abi: POLYGON_BRIDGE_EXECUTOR_ABI,
publicClient: polygonClient,
Expand Down
2 changes: 1 addition & 1 deletion src/utils/constants.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// arbitrary from EOA for proposal executions
export const EOA = "0xD73a92Be73EfbFcF3854433A5FcbAbF9c1316073";
export const EOA = '0xD73a92Be73EfbFcF3854433A5FcbAbF9c1316073' as const;
Loading

0 comments on commit 338e0eb

Please sign in to comment.