Skip to content

Commit

Permalink
fix: better event tracing (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
sakulstra authored Jun 8, 2023
1 parent c5817b9 commit 6432ffe
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 88 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
"@types/node-fetch": "^2.6.4",
"@types/yargs": "^17.0.24",
"tsup": "^6.7.0",
"typescript": "^5.0.4",
"vitest": "^0.31.1"
"typescript": "^5.1.3",
"vitest": "^0.32.0"
},
"exports": {
"default": "./dist/index.cjs",
Expand All @@ -50,7 +50,7 @@
"json-bigint": "^1.0.0",
"node-fetch": "^2.6.9",
"object-hash": "^3.0.0",
"viem": "^0.3.50",
"viem": "^1.0.00",
"yargs": "^17.7.2",
"zod": "^3.21.4"
}
Expand Down
16 changes: 12 additions & 4 deletions src/simulate/networks/commonL2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
getSolidityStorageSlotBytes,
getSolidityStorageSlotUint,
} from '../../utils/storageSlots';
import { ActionSetState, FormattedArgs } from './types';
import { ActionSetState, FilterLogWithTimestamp, FormattedArgs } from './types';

/**
* The executors are slightly different, but the execute signature is always the same
Expand Down Expand Up @@ -127,9 +127,11 @@ const executorABI = [
] as const;

export interface GetProposalStateProps<TAbi extends Abi> {
queuedLogs: GetFilterLogsReturnType<TAbi, 'ActionsSetQueued'>;
executedLogs: GetFilterLogsReturnType<TAbi, 'ActionsSetExecuted'>;
queuedLogs: Array<FilterLogWithTimestamp<TAbi, 'ActionsSetQueued'>>;
executedLogs: Array<FilterLogWithTimestamp<TAbi, 'ActionsSetExecuted'>>;
dataValue: Hex;
// earliest timestamp to look for events (latest mainnet event)
fromTimestamp: number;
}

export function formatArgs(rawArgs: any): FormattedArgs {
Expand All @@ -146,14 +148,20 @@ export async function getProposalState<TAbi>({
queuedLogs,
executedLogs,
dataValue,
fromTimestamp,
}: GetProposalStateProps<typeof executorABI>) {
const { args: rawArgs } = decodeFunctionData({
abi: executorABI,
data: dataValue,
});
if (!rawArgs) throw new Error('Error: cannot decode trace');
const args = formatArgs(rawArgs);
const queuedLog = queuedLogs.find((event) => JSON.stringify(event.args.targets) == JSON.stringify(args.targets));
const queuedLog = queuedLogs.find(
(event) =>
event.timestamp > fromTimestamp &&
JSON.stringify(event.args.targets) == JSON.stringify(args.targets) &&
JSON.stringify(event.args.calldatas) == JSON.stringify(args.calldatas)
);
if (queuedLog) {
args.proposalId = queuedLog.args.id;
const executedLog = executedLogs.find((event) => event.args.id == queuedLog.args.id);
Expand Down
2 changes: 1 addition & 1 deletion src/simulate/networks/mainnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const aaveGovernanceV2Contract = getContract({
publicClient: mainnetClient,
});

export const mainnet: MainnetModule<'ProposalCreated', 'ProposalQueued', 'ProposalExecuted'> = {
export const mainnet: MainnetModule = {
name: 'Mainnet',
async cacheLogs() {
const createdLogs = await getLogs(mainnetClient, (fromBlock, toBlock) =>
Expand Down
46 changes: 23 additions & 23 deletions src/simulate/networks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,34 @@ export enum ActionSetState {
EXECUTED,
}

export type FilterLogWithTimestamp<TAbi extends Abi, TEventName extends string> = ArrayElement<
GetFilterLogsReturnType<TAbi, TEventName>
> & { timestamp: number };

export interface L2NetworkModule<TAbi extends Abi, TQueuedEventName extends string, TExecutedEventName extends string> {
name: 'Optimism' | 'Polygon' | 'Arbitrum' | 'Metis' | 'Arc';
cacheLogs: () => Promise<{
queuedLogs: GetFilterLogsReturnType<TAbi, TQueuedEventName>;
executedLogs: GetFilterLogsReturnType<TAbi, TExecutedEventName>;
queuedLogs: Array<FilterLogWithTimestamp<TAbi, TQueuedEventName>>;
executedLogs: Array<FilterLogWithTimestamp<TAbi, TExecutedEventName>>;
}>;
findBridgeInMainnetCalls: (
calls: TenderlySimulationResponse['transaction']['transaction_info']['call_trace']['calls']
) => Array<Trace>;
getProposalState: (
args: Omit<GetProposalStateProps<TAbi>, 'dataValue'> & { trace: Trace }
args: Omit<GetProposalStateProps<TAbi>, 'dataValue'> & { trace: Trace; fromTimestamp: number }
) => ReturnType<typeof getProposalState<TAbi>>;
simulateOnTenderly?: (
args: {
args: FormattedArgs;
} & (
| {
state: ActionSetState.EXECUTED;
executedLog: ArrayElement<GetFilterLogsReturnType<TAbi, TExecutedEventName>>;
queuedLog: ArrayElement<GetFilterLogsReturnType<TAbi, TQueuedEventName>>;
executedLog: FilterLogWithTimestamp<TAbi, TExecutedEventName>;
queuedLog: FilterLogWithTimestamp<TAbi, TQueuedEventName>;
}
| {
state: ActionSetState.QUEUED;
queuedLog: ArrayElement<GetFilterLogsReturnType<TAbi, TQueuedEventName>>;
queuedLog: FilterLogWithTimestamp<TAbi, TQueuedEventName>;
executedLog?: undefined;
}
| { state: ActionSetState.NOT_FOUND; queuedLog?: undefined; executedLog?: undefined }
Expand All @@ -60,50 +64,46 @@ export enum ProposalState {
EXECUTED,
}

export interface MainnetModule<
TCreatedEventName extends string | undefined,
TQueuedEventName extends string | undefined,
TExecutedEventName extends string | undefined
> {
export interface MainnetModule {
name: string;
cacheLogs: () => Promise<{
createdLogs: GetFilterLogsReturnType<typeof AAVE_GOVERNANCE_V2_ABI, TCreatedEventName>;
queuedLogs: GetFilterLogsReturnType<typeof AAVE_GOVERNANCE_V2_ABI, TQueuedEventName>;
executedLogs: GetFilterLogsReturnType<typeof AAVE_GOVERNANCE_V2_ABI, TExecutedEventName>;
createdLogs: Array<FilterLogWithTimestamp<typeof AAVE_GOVERNANCE_V2_ABI, 'ProposalCreated'>>;
queuedLogs: Array<FilterLogWithTimestamp<typeof AAVE_GOVERNANCE_V2_ABI, 'ProposalQueued'>>;
executedLogs: Array<FilterLogWithTimestamp<typeof AAVE_GOVERNANCE_V2_ABI, 'ProposalExecuted'>>;
}>;
getProposalState: (args: {
proposalId: bigint;
createdLogs: GetFilterLogsReturnType<typeof AAVE_GOVERNANCE_V2_ABI, TCreatedEventName>;
queuedLogs: GetFilterLogsReturnType<typeof AAVE_GOVERNANCE_V2_ABI, TQueuedEventName>;
executedLogs: GetFilterLogsReturnType<typeof AAVE_GOVERNANCE_V2_ABI, TExecutedEventName>;
createdLogs: Array<FilterLogWithTimestamp<typeof AAVE_GOVERNANCE_V2_ABI, 'ProposalCreated'>>;
queuedLogs: Array<FilterLogWithTimestamp<typeof AAVE_GOVERNANCE_V2_ABI, 'ProposalQueued'>>;
executedLogs: Array<FilterLogWithTimestamp<typeof AAVE_GOVERNANCE_V2_ABI, 'ProposalExecuted'>>;
}) =>
| {
state: ProposalState.EXECUTED;
log: ArrayElement<GetFilterLogsReturnType<typeof AAVE_GOVERNANCE_V2_ABI, TExecutedEventName>>;
log: FilterLogWithTimestamp<typeof AAVE_GOVERNANCE_V2_ABI, 'ProposalExecuted'>;
}
| {
state: ProposalState.QUEUED;
log: ArrayElement<GetFilterLogsReturnType<typeof AAVE_GOVERNANCE_V2_ABI, TQueuedEventName>>;
log: FilterLogWithTimestamp<typeof AAVE_GOVERNANCE_V2_ABI, 'ProposalQueued'>;
}
| {
state: ProposalState.CREATED;
log: ArrayElement<GetFilterLogsReturnType<typeof AAVE_GOVERNANCE_V2_ABI, TCreatedEventName>>;
log: FilterLogWithTimestamp<typeof AAVE_GOVERNANCE_V2_ABI, 'ProposalCreated'>;
};
simulateOnTenderly: (
args: {
proposalId: bigint;
} & (
| {
state: ProposalState.EXECUTED;
log: ArrayElement<GetFilterLogsReturnType<typeof AAVE_GOVERNANCE_V2_ABI, TExecutedEventName>>;
log: FilterLogWithTimestamp<typeof AAVE_GOVERNANCE_V2_ABI, 'ProposalExecuted'>;
}
| {
state: ProposalState.QUEUED;
log: ArrayElement<GetFilterLogsReturnType<typeof AAVE_GOVERNANCE_V2_ABI, TQueuedEventName>>;
log: FilterLogWithTimestamp<typeof AAVE_GOVERNANCE_V2_ABI, 'ProposalQueued'>;
}
| {
state: ProposalState.CREATED;
log: ArrayElement<GetFilterLogsReturnType<typeof AAVE_GOVERNANCE_V2_ABI, TCreatedEventName>>;
log: FilterLogWithTimestamp<typeof AAVE_GOVERNANCE_V2_ABI, 'ProposalCreated'>;
}
)
) => Promise<{
Expand Down
1 change: 1 addition & 0 deletions src/simulate/simulate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export async function simulateProposal(proposalId: bigint) {
// any casts needed as types are slightly different on polygon vs others
const moduleState = await module.getProposalState({
trace: trace,
fromTimestamp: mainnetState.log.timestamp,
...moduleCache,
} as any);
if (module.simulateOnTenderly) {
Expand Down
18 changes: 13 additions & 5 deletions src/utils/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@ import type { Abi } from 'abitype';
import fs from 'fs';
import path from 'path';
import { logInfo } from './logger';
import { FilterLogWithTimestamp } from '../simulate/networks/types';

/**
* Fetches the logs and stores them in a cache folder.
* @param client
* @param filter
* @returns logs
*/
export async function getLogs<TAbi extends Abi | readonly unknown[], TEventName extends string | undefined>(
export async function getLogs<TAbi extends Abi, TEventName extends string>(
client: PublicClient,
filterFn: (from, to) => Promise<GetFilterLogsParameters<TAbi, TEventName>['filter']>
): Promise<GetFilterLogsReturnType<TAbi, TEventName>> {
): Promise<Array<FilterLogWithTimestamp<TAbi, TEventName>>> {
// create cache folder if doesn't exist yet
const cachePath = path.join(process.cwd(), 'cache', client.chain!.id.toString());
const currentBlock = await client.getBlockNumber();
Expand All @@ -23,7 +24,7 @@ export async function getLogs<TAbi extends Abi | readonly unknown[], TEventName
fs.mkdirSync(cachePath, { recursive: true });
}
// read stale cache if it exists
const cache: GetFilterLogsReturnType<TAbi, TEventName> = fs.existsSync(filePath)
const cache: Array<FilterLogWithTimestamp<TAbi, TEventName>> = fs.existsSync(filePath)
? JSON.parse(fs.readFileSync(filePath, 'utf8'))
: [];
const logs = await getPastLogsRecursive(
Expand All @@ -32,8 +33,15 @@ export async function getLogs<TAbi extends Abi | readonly unknown[], TEventName
currentBlock,
filterFn
);
const newLogs = logs.filter(
(l) => !cache.find((c) => l.transactionHash === c.transactionHash && Number(l.logIndex) === Number(c.logIndex))
const newLogs = await Promise.all(
logs
.filter(
(l) => !cache.find((c) => l.transactionHash === c.transactionHash && Number(l.logIndex) === Number(c.logIndex))
)
.map(async (l) => ({
...l,
timestamp: Number((await client.getBlock({ blockNumber: l.blockNumber as bigint })).timestamp),
}))
);
if (newLogs.length) {
const combinedCache = [...cache, ...newLogs].sort((a, b) =>
Expand Down
4 changes: 2 additions & 2 deletions src/utils/rpcClients.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// import 'dotenv/config';
import { createPublicClient, http } from 'viem';
import { createPublicClient, http, fallback } from 'viem';
import { mainnet, arbitrum, polygon, optimism, metis } from 'viem/chains';

export const mainnetClient = createPublicClient({
chain: mainnet,
transport: http(process.env.RPC_MAINNET),
transport: fallback([http(), http(process.env.RPC_MAINNET)]),
});

export const arbitrumClient = createPublicClient({
Expand Down
1 change: 1 addition & 0 deletions src/utils/tenderlyClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ export type TransactionInfo = {

type Transaction = {
transaction_info: TransactionInfo;
block_number: number;
status: boolean;
addresses: Hex[];
};
Expand Down
Loading

0 comments on commit 6432ffe

Please sign in to comment.