From e659eaf88024d1586098b5f64dee7e51657a2893 Mon Sep 17 00:00:00 2001 From: Lukas Date: Wed, 24 Jan 2024 22:26:10 +0100 Subject: [PATCH] Feat/update caching (#100) * feat: update caching * fix: remoe obsolete stuff * fix: add export * fix: migrate to new js-utils * fix: remove ipfs cache from governance --- .gitignore | 2 +- package.json | 6 +- src/commands/fork.ts | 4 +- src/commands/governance.ts | 42 +++-- src/govv3/cache/modules/governance.ts | 70 ++++++++ src/govv3/cache/modules/payloadsController.ts | 78 +++++++++ src/govv3/cache/updateCache.ts | 162 ++++++++++++++++++ src/govv3/checks/logs.ts | 4 +- src/govv3/checks/selfDestruct.ts | 25 +-- src/govv3/checks/state.ts | 6 +- src/govv3/checks/targetsVerified.ts | 21 +-- src/govv3/checks/types.ts | 8 +- src/govv3/generatePayloadReport.spec.ts | 2 +- src/govv3/generatePayloadReport.ts | 18 +- src/govv3/generateProposalReport.ts | 23 +-- src/govv3/governance.ts | 118 ++++--------- src/govv3/payloadsController.ts | 79 +++------ src/govv3/proofs.ts | 9 +- src/govv3/simulate.ts | 28 +-- src/govv3/utils/checkAddress.ts | 3 +- src/govv3/utils/markdownUtils.ts | 6 +- src/govv3/utils/stateDiffInterpreter.ts | 10 +- src/index.ts | 1 + src/utils/constants.ts | 3 +- src/utils/logs.ts | 64 +------ src/utils/tenderlyClient.ts | 22 +-- yarn.lock | 40 +++-- 27 files changed, 512 insertions(+), 342 deletions(-) create mode 100644 src/govv3/cache/modules/governance.ts create mode 100644 src/govv3/cache/modules/payloadsController.ts create mode 100644 src/govv3/cache/updateCache.ts diff --git a/.gitignore b/.gitignore index 82232cb..52cfe72 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,5 @@ node_modules yarn-error.log # proposal cache -cache +/cache .idea \ No newline at end of file diff --git a/package.json b/package.json index dc34abf..2331a20 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,8 @@ "access": "public" }, "dependencies": { - "@bgd-labs/aave-address-book": "2.15.3-89c77b1d7ee3574250b9bef36599806dd7a3bb94.0", - "@bgd-labs/js-utils": "^1.0.3", + "@bgd-labs/aave-address-book": "2.18.0", + "@bgd-labs/js-utils": "^1.1.1", "@commander-js/extra-typings": "^11.1.0", "@inquirer/prompts": "^3.3.0", "chalk": "^4.1.2", @@ -63,7 +63,7 @@ "ipfs-only-hash": "^4.0.0", "json-bigint": "^1.0.0", "object-hash": "^3.0.0", - "viem": "^2.0.6", + "viem": "^2.5.0", "zod": "^3.22.4" } } diff --git a/src/commands/fork.ts b/src/commands/fork.ts index b0723d5..59fc965 100644 --- a/src/commands/fork.ts +++ b/src/commands/fork.ts @@ -42,7 +42,7 @@ export function addCommand(program: Command) { alias: getAlias(), blockNumber: Number(blockNumber), }; - const governance = getGovernance({ address: DEFAULT_GOVERNANCE, publicClient: DEFAULT_GOVERNANCE_CLIENT }); + const governance = getGovernance({ address: DEFAULT_GOVERNANCE, client: DEFAULT_GOVERNANCE_CLIENT }); if (proposalId) { const payload = await governance.getSimulationPayloadForExecution(BigInt(proposalId)); const fork = await tenderly.fork({ @@ -54,7 +54,7 @@ export function addCommand(program: Command) { if (!payloadsControllerAddress) throw new Error('you need to provide a payloadsController'); const payloadsController = getPayloadsController( payloadsControllerAddress as Hex, - CHAIN_ID_CLIENT_MAP[forkConfig.chainId as keyof typeof CHAIN_ID_CLIENT_MAP] as PublicClient + CHAIN_ID_CLIENT_MAP[forkConfig.chainId as keyof typeof CHAIN_ID_CLIENT_MAP] ); const payload = await payloadsController.getSimulationPayloadForExecution(Number(payloadId)); const fork = await tenderly.fork({ diff --git a/src/commands/governance.ts b/src/commands/governance.ts index 50df417..f9d258b 100644 --- a/src/commands/governance.ts +++ b/src/commands/governance.ts @@ -4,13 +4,19 @@ import { IDataWarehouse_ABI, IVotingMachineWithProofs_ABI, IVotingPortal_ABI } f import { HUMAN_READABLE_STATE, getGovernance } from '../govv3/governance'; import { CHAIN_ID_CLIENT_MAP } from '@bgd-labs/js-utils'; import { logError, logInfo, logSuccess } from '../utils/logger'; -import { Hex, PublicClient, encodeAbiParameters, encodeFunctionData, getContract } from 'viem'; +import { Address, Hex, encodeAbiParameters, encodeFunctionData, getContract } from 'viem'; import { confirm, input, select } from '@inquirer/prompts'; import { getCachedIpfs } from '../ipfs/getCachedProposalMetaData'; import { toAddressLink, toTxLink } from '../govv3/utils/markdownUtils'; import { getAccountRPL, getBlockRLP } from '../govv3/proofs'; import { DEFAULT_GOVERNANCE, DEFAULT_GOVERNANCE_CLIENT, FORMAT } from '../utils/constants'; import { getPayloadsController } from '../govv3/payloadsController'; +import { + cacheGovernance, + cachePayloadsController, + readBookKeepingCache, + writeBookKeepingCache, +} from '../govv3/cache/updateCache'; enum DialogOptions { DETAILS, @@ -41,12 +47,12 @@ export function addCommand(program: Command) { .option('--payloadsController ', 'PayloadsController address') .action(async ({ payloadId: _payloadId, payloadsController: payloadsControllerAddress, chainId }, options) => { const payloadId = Number(_payloadId); - const payloadsController = getPayloadsController( - payloadsControllerAddress as Hex, - CHAIN_ID_CLIENT_MAP[Number(chainId) as keyof typeof CHAIN_ID_CLIENT_MAP] as PublicClient - ); - const logs = await payloadsController.cacheLogs(); - const config = await payloadsController.getPayload(payloadId, logs); + const client = CHAIN_ID_CLIENT_MAP[Number(chainId) as keyof typeof CHAIN_ID_CLIENT_MAP]; + const payloadsController = getPayloadsController(payloadsControllerAddress as Hex, client); + const cache = readBookKeepingCache(); + const { eventsCache } = await cachePayloadsController(client, payloadsControllerAddress as Address, cache); + writeBookKeepingCache(cache); + const config = await payloadsController.getPayload(payloadId, eventsCache); await payloadsController.simulatePayloadExecutionOnTenderly(Number(payloadId), config); }); @@ -56,10 +62,12 @@ export function addCommand(program: Command) { .action(async (opts) => { const governance = getGovernance({ address: DEFAULT_GOVERNANCE, - publicClient: DEFAULT_GOVERNANCE_CLIENT, + client: DEFAULT_GOVERNANCE_CLIENT, blockCreated: 9640498n, }); - const logs = await governance.cacheLogs(); + const cache = readBookKeepingCache(); + const { eventsCache } = await cacheGovernance(DEFAULT_GOVERNANCE_CLIENT, DEFAULT_GOVERNANCE, cache); + writeBookKeepingCache(cache); const count = await governance.governanceContract.read.getProposalsCount(); const proposalIds = [...Array(Number(count)).keys()].reverse(); const selectedProposalId = BigInt( @@ -77,7 +85,7 @@ export function addCommand(program: Command) { ), }) ); - const { proposal, ...proposalLogs } = await governance.getProposalAndLogs(selectedProposalId, logs); + const { proposal, ...proposalLogs } = await governance.getProposalAndLogs(selectedProposalId, eventsCache); let exitLvl2 = false; while (!exitLvl2) { const moreInfo = await select({ @@ -174,11 +182,7 @@ export function addCommand(program: Command) { else { logSuccess( 'VotingMachine', - toAddressLink( - machine, - false, - CHAIN_ID_CLIENT_MAP[Number(chainId) as keyof typeof CHAIN_ID_CLIENT_MAP] as PublicClient - ) + toAddressLink(machine, false, CHAIN_ID_CLIENT_MAP[Number(chainId) as keyof typeof CHAIN_ID_CLIENT_MAP]) ); if (FORMAT === 'raw') { logSuccess('Method', 'submitVote'); @@ -214,7 +218,7 @@ export function addCommand(program: Command) { const machineContract = getContract({ address: machine, abi: IVotingMachineWithProofs_ABI, - client: CHAIN_ID_CLIENT_MAP[Number(chainId) as keyof typeof CHAIN_ID_CLIENT_MAP] as PublicClient, + client: CHAIN_ID_CLIENT_MAP[Number(chainId) as keyof typeof CHAIN_ID_CLIENT_MAP], }); const dataWarehouse = await machineContract.read.DATA_WAREHOUSE(); const roots = await governance.getStorageRoots(selectedProposalId); @@ -223,7 +227,7 @@ export function addCommand(program: Command) { toAddressLink( dataWarehouse, false, - CHAIN_ID_CLIENT_MAP[Number(chainId) as keyof typeof CHAIN_ID_CLIENT_MAP] as PublicClient + CHAIN_ID_CLIENT_MAP[Number(chainId) as keyof typeof CHAIN_ID_CLIENT_MAP] ) ); const block = await DEFAULT_GOVERNANCE_CLIENT.getBlock({ blockHash: proposal.snapshotBlockHash }); @@ -265,7 +269,7 @@ export function addCommand(program: Command) { .action(async (name, options) => { const governance = getGovernance({ address: DEFAULT_GOVERNANCE, - publicClient: DEFAULT_GOVERNANCE_CLIENT, + client: DEFAULT_GOVERNANCE_CLIENT, }); const proposalId = BigInt(options.getOptionValue('proposalId')); const proposal = await governance.getProposal(proposalId); @@ -313,7 +317,7 @@ export function addCommand(program: Command) { .action(async (name, options) => { const governance = getGovernance({ address: DEFAULT_GOVERNANCE, - publicClient: DEFAULT_GOVERNANCE_CLIENT, + client: DEFAULT_GOVERNANCE_CLIENT, }); const proposalId = BigInt(options.getOptionValue('proposalId')); const voter = options.getOptionValue('voter') as Hex; diff --git a/src/govv3/cache/modules/governance.ts b/src/govv3/cache/modules/governance.ts new file mode 100644 index 0000000..fe4a505 --- /dev/null +++ b/src/govv3/cache/modules/governance.ts @@ -0,0 +1,70 @@ +import { IGovernanceCore_ABI } from '@bgd-labs/aave-address-book'; +import { strategicGetLogs } from '@bgd-labs/js-utils'; +import { Address, Client, getAbiItem } from 'viem'; +import type { ExtractAbiEvent } from 'abitype'; +import { getBlock } from 'viem/actions'; +import { LogWithTimestamp } from '../../../utils/logs'; + +export type ProposalCreatedEvent = LogWithTimestamp>; +export type ProposalQueuedEvent = LogWithTimestamp>; +export type ProposalCanceledEvent = LogWithTimestamp>; +export type ProposalExecutedEvent = LogWithTimestamp>; +export type ProposalPayloadSentEvent = LogWithTimestamp>; +export type ProposalVotingActivatedEvent = LogWithTimestamp< + ExtractAbiEvent +>; + +export enum ProposalState { + Null, // proposal does not exists + Created, // created, waiting for a cooldown to initiate the balances snapshot + Active, // balances snapshot set, voting in progress + Queued, // voting results submitted, but proposal is under grace period when guardian can cancel it + Executed, // results sent to the execution chain(s) + Failed, // voting was not successful + Cancelled, // got cancelled by guardian, or because proposition power of creator dropped below allowed minimum + Expired, +} + +export function isProposalFinal(state: ProposalState) { + return [ProposalState.Executed, ProposalState.Failed, ProposalState.Cancelled, ProposalState.Expired].includes(state); +} + +export async function getGovernanceEvents( + governanceAddress: Address, + client: Client, + fromBlockNumber: bigint, + toBlockNumber: bigint +) { + const logs = await strategicGetLogs({ + client, + events: [ + getAbiItem({ abi: IGovernanceCore_ABI, name: 'ProposalCreated' }), + getAbiItem({ abi: IGovernanceCore_ABI, name: 'ProposalQueued' }), + getAbiItem({ abi: IGovernanceCore_ABI, name: 'ProposalExecuted' }), + getAbiItem({ abi: IGovernanceCore_ABI, name: 'PayloadSent' }), + getAbiItem({ abi: IGovernanceCore_ABI, name: 'VotingActivated' }), + getAbiItem({ abi: IGovernanceCore_ABI, name: 'ProposalCanceled' }), + ], + address: governanceAddress, + fromBlock: fromBlockNumber, + toBlock: toBlockNumber, + }); + return await Promise.all( + logs.map(async (l) => ({ + ...l, + timestamp: Number((await getBlock(client, { blockNumber: l.blockNumber as bigint })).timestamp), + })) + ); +} + +export async function findProposalLogs(logs: Awaited>, proposalId: bigint) { + const proposalLogs = logs.filter((log) => String(log.args.proposalId) === String(proposalId)); + return { + createdLog: proposalLogs.find((log) => log.eventName === 'ProposalCreated') as ProposalCreatedEvent, + votingActivatedLog: proposalLogs.find((log) => log.eventName === 'VotingActivated') as ProposalVotingActivatedEvent, + queuedLog: proposalLogs.find((log) => log.eventName === 'ProposalQueued') as ProposalQueuedEvent, + executedLog: proposalLogs.find((log) => log.eventName === 'ProposalExecuted') as ProposalExecutedEvent, + payloadSentLog: proposalLogs.filter((log) => log.eventName === 'PayloadSent') as ProposalPayloadSentEvent[], + canceledLog: proposalLogs.find((log) => log.eventName === 'ProposalCanceled') as ProposalCanceledEvent, + }; +} diff --git a/src/govv3/cache/modules/payloadsController.ts b/src/govv3/cache/modules/payloadsController.ts new file mode 100644 index 0000000..1c9001f --- /dev/null +++ b/src/govv3/cache/modules/payloadsController.ts @@ -0,0 +1,78 @@ +import { IPayloadsControllerCore_ABI } from '@bgd-labs/aave-address-book'; +import { strategicGetLogs } from '@bgd-labs/js-utils'; +import { Address, Client, getAbiItem } from 'viem'; +import { getBlock } from 'viem/actions'; +import type { ExtractAbiEvent } from 'abitype'; +import { LogWithTimestamp } from '../../../utils/logs'; + +export type PayloadCreatedEvent = LogWithTimestamp< + ExtractAbiEvent +>; +export type PayloadQueuedEvent = LogWithTimestamp>; +export type PayloadExecutedEvent = LogWithTimestamp< + ExtractAbiEvent +>; + +export enum PayloadState { + None, + Created, + Queued, + Executed, + Cancelled, + Expired, +} + +export const HUMAN_READABLE_PAYLOAD_STATE = { + [PayloadState.None]: 'None', + [PayloadState.Created]: 'Created', + [PayloadState.Queued]: 'Queued', + [PayloadState.Executed]: 'Executed', + [PayloadState.Cancelled]: 'Cancelled', + [PayloadState.Expired]: 'Expired', +}; + +export function isPayloadFinal(state: number) { + return [ + PayloadState.Cancelled, + PayloadState.Executed, + PayloadState.Expired, + // -1, // error + ].includes(state); +} + +export async function getPayloadsControllerEvents( + payloadsControllerAddress: Address, + client: Client, + fromBlockNumber: bigint, + toBlockNumber: bigint +) { + const logs = await strategicGetLogs({ + client, + events: [ + getAbiItem({ abi: IPayloadsControllerCore_ABI, name: 'PayloadCreated' }), + getAbiItem({ abi: IPayloadsControllerCore_ABI, name: 'PayloadQueued' }), + getAbiItem({ abi: IPayloadsControllerCore_ABI, name: 'PayloadExecuted' }), + ], + address: payloadsControllerAddress, + fromBlock: fromBlockNumber, + toBlock: toBlockNumber, + }); + return await Promise.all( + logs.map(async (l) => ({ + ...l, + timestamp: Number((await getBlock(client, { blockNumber: l.blockNumber as bigint })).timestamp), + })) + ); +} + +export async function findPayloadLogs( + logs: Awaited>, + payloadId: number +) { + const proposalLogs = logs.filter((log) => String(log.args.payloadId) === String(payloadId)); + return { + createdLog: proposalLogs.find((log) => log.eventName === 'PayloadCreated') as PayloadCreatedEvent, + queuedLog: proposalLogs.find((log) => log.eventName === 'PayloadQueued') as PayloadQueuedEvent, + executedLog: proposalLogs.find((log) => log.eventName === 'PayloadExecuted') as PayloadExecutedEvent, + }; +} diff --git a/src/govv3/cache/updateCache.ts b/src/govv3/cache/updateCache.ts new file mode 100644 index 0000000..73269a0 --- /dev/null +++ b/src/govv3/cache/updateCache.ts @@ -0,0 +1,162 @@ +import { + CHAIN_ID_CLIENT_MAP, + ProposalMetadata, + getBlockAtTimestamp, + getProposalMetadata, + readJSONCache, + writeJSONCache, +} from '@bgd-labs/js-utils'; +import { getGovernanceEvents, isProposalFinal } from './modules/governance'; +import { getPayloadsControllerEvents, isPayloadFinal } from './modules/payloadsController'; +import { AbiStateMutability, Address, Client, ContractFunctionReturnType, getContract } from 'viem'; +import { IGovernanceCore_ABI, IPayloadsControllerCore_ABI } from '@bgd-labs/aave-address-book'; +import { getBlockNumber } from 'viem/actions'; + +export async function cacheGovernance( + client: Client, + governanceAddress: Address, + bookKeepingCache: BookKeepingCache +): Promise<{ + proposalsCache: Record< + string, + ContractFunctionReturnType + >; + eventsCache: Awaited>; +}> { + const bookKeepingCacheId = 'governance'; + const currentBlockOnGovernanceChain = await getBlockNumber(client); + const contract = getContract({ + abi: IGovernanceCore_ABI, + address: governanceAddress, + client, + }); + const proposalsCount = await contract.read.getProposalsCount(); + if (proposalsCount == BigInt(0)) { + bookKeepingCache[bookKeepingCacheId] = currentBlockOnGovernanceChain.toString(); + return { + proposalsCache: {}, + eventsCache: [], + }; + } + // cache data + const proposalsPath = `${client.chain!.id.toString()}/proposals`; + const proposalsCache = + readJSONCache< + Record> + >(proposalsPath, governanceAddress) || {}; + const proposalsToCheck = [...Array(Number(proposalsCount)).keys()]; + for (let i = 0; i < proposalsToCheck.length; i++) { + if (!proposalsCache[i] || !isProposalFinal(proposalsCache[i].state)) { + proposalsCache[i] = await contract.read.getProposal([BigInt(i)]); + } + } + writeJSONCache(proposalsPath, governanceAddress, proposalsCache); + + // cache events + const eventsPath = `${client.chain!.id.toString()}/events`; + const governanceEvents = + readJSONCache>>(eventsPath, governanceAddress) || []; + const lastSeenBlock = bookKeepingCache[bookKeepingCacheId] + ? BigInt(bookKeepingCache[bookKeepingCacheId]) + : ( + await getBlockAtTimestamp({ + client: client, + timestamp: BigInt(proposalsCache[0].creationTime), + fromBlock: BigInt(0), + toBlock: currentBlockOnGovernanceChain, + maxDelta: BigInt(60 * 60 * 24), + }) + ).number; + const logs = await getGovernanceEvents( + governanceAddress, + client, + BigInt(lastSeenBlock) + BigInt(1), + currentBlockOnGovernanceChain + ); + const eventsCache = [...governanceEvents, ...logs]; + writeJSONCache(eventsPath, governanceAddress, eventsCache); + bookKeepingCache[bookKeepingCacheId] = currentBlockOnGovernanceChain.toString(); + return { proposalsCache, eventsCache }; +} + +export async function cachePayloadsController( + client: Client, + payloadsControllerAddress: Address, + bookKeepingCache: BookKeepingCache +) { + const payloadsPath = `${client.chain!.id}/payloads`; + const contract = getContract({ + abi: IPayloadsControllerCore_ABI, + client, + address: payloadsControllerAddress, + }); + const payloadsCount = await contract.read.getPayloadsCount(); + + const currentBlockOnPayloadsControllerChain = await getBlockNumber(client); + if (payloadsCount == 0) { + bookKeepingCache[payloadsPath] = currentBlockOnPayloadsControllerChain.toString(); + return { eventsCache: [] }; + } + // cache data + const payloadsCache = + readJSONCache< + Record< + number, + ContractFunctionReturnType + > + >(payloadsPath, payloadsControllerAddress) || {}; + const payloadsToCheck = [...Array(Number(payloadsCount)).keys()]; + for (let i = 0; i < payloadsToCheck.length; i++) { + if (!payloadsCache[i] || !isPayloadFinal(payloadsCache[i].state)) { + payloadsCache[i] = await contract.read.getPayloadById([i]); + } + } + writeJSONCache(payloadsPath, payloadsControllerAddress, payloadsCache); + // cache events + const eventsPath = `${client.chain!.id}/events`; + const eventsCache = + readJSONCache>>(eventsPath, payloadsControllerAddress) || []; + const lastSeenBlock = bookKeepingCache[payloadsPath] + ? BigInt(bookKeepingCache[payloadsPath]) + : ( + await getBlockAtTimestamp({ + client: client, + timestamp: BigInt(payloadsCache[0].createdAt), + fromBlock: BigInt(0), + toBlock: currentBlockOnPayloadsControllerChain, + maxDelta: BigInt(60 * 60), // 1h + }) + ).number; + const logs = await getPayloadsControllerEvents( + payloadsControllerAddress, + client, + BigInt(lastSeenBlock) + BigInt(1), + currentBlockOnPayloadsControllerChain + ); + const updatedEventsCache = [...eventsCache, ...logs]; + writeJSONCache(eventsPath, payloadsControllerAddress, updatedEventsCache); + bookKeepingCache[payloadsPath] = currentBlockOnPayloadsControllerChain.toString(); + return { eventsCache: updatedEventsCache }; +} + +export async function cachePayloadsControllers(controllers: Map, bookKeepingCache: BookKeepingCache) { + return await Promise.all( + Array.from(controllers).map(async ([address, chainId]) => + cachePayloadsController(CHAIN_ID_CLIENT_MAP[chainId], address, bookKeepingCache) + ) + ); +} + +/** + * simple cache mapping: + * filename:blockNumber with the last used block for caching + */ +type BookKeepingCache = Record; + +export function readBookKeepingCache() { + return readJSONCache('bookKeeping', 'lastFetchedBlocks') || {}; +} + +export function writeBookKeepingCache(cache: BookKeepingCache) { + writeJSONCache('bookKeeping', 'lastFetchedBlocks', cache); +} diff --git a/src/govv3/checks/logs.ts b/src/govv3/checks/logs.ts index a9eacc9..28d67ec 100644 --- a/src/govv3/checks/logs.ts +++ b/src/govv3/checks/logs.ts @@ -10,7 +10,7 @@ import { getContractName } from '../utils/solidityUtils'; */ export const checkLogs: ProposalCheck = { name: 'Reports all events emitted from the proposal', - async checkProposal(proposal, sim, publicClient) { + async checkProposal(proposal, sim, client) { let info = []; const events = sim.transaction.transaction_info.logs?.reduce((logs, log) => { const addr = getAddress(log.raw.address); @@ -25,7 +25,7 @@ export const checkLogs: ProposalCheck = { // Parse each event for (const [address, logs] of Object.entries(events)) { // Use contracts array to get contract name of address - info.push(`- ${getContractName(sim.contracts, address as Address, publicClient.chain!.id)}`); + info.push(`- ${getContractName(sim.contracts, address as Address, client.chain!.id)}`); // Format log data for report logs.forEach((log) => { diff --git a/src/govv3/checks/selfDestruct.ts b/src/govv3/checks/selfDestruct.ts index d72c3f7..461e657 100644 --- a/src/govv3/checks/selfDestruct.ts +++ b/src/govv3/checks/selfDestruct.ts @@ -1,20 +1,21 @@ // Based on https://github.com/Uniswap/governance-seatbelt/blob/main/checks/check-targets-no-selfdestruct.ts // adjusted for viem & aave governance v3 -import { Hex, PublicClient } from 'viem'; +import { Client, Hex } from 'viem'; import { ProposalCheck } from './types'; import { flagKnownAddress, toAddressLink } from '../utils/markdownUtils'; import { PayloadsController } from '../payloadsController'; import { isKnownAddress } from '../utils/checkAddress'; +import { getBytecode, getTransactionCount } from 'viem/actions'; /** * Check all targets with code if they contain selfdestruct. */ export const checkTargetsNoSelfdestruct: ProposalCheck>> = { name: 'Check all targets do not contain selfdestruct', - async checkProposal(proposal, sim, publicClient) { + async checkProposal(proposal, sim, client) { const allTargets = proposal.payload.actions.map((action) => action.target); const uniqueTargets = allTargets.filter((addr, i, targets) => targets.indexOf(addr) === i); - const { info, warn, error } = await checkNoSelfdestructs([], uniqueTargets, publicClient); + const { info, warn, error } = await checkNoSelfdestructs([], uniqueTargets, client); return { info, warnings: warn, errors: error }; }, }; @@ -24,8 +25,8 @@ export const checkTargetsNoSelfdestruct: ProposalCheck = { name: 'Check all touched contracts do not contain selfdestruct', - async checkProposal(proposal, sim, publicClient) { - const { info, warn, error } = await checkNoSelfdestructs([], sim.transaction.addresses, publicClient); + async checkProposal(proposal, sim, client) { + const { info, warn, error } = await checkNoSelfdestructs([], sim.transaction.addresses, client); return { info, warnings: warn, errors: error }; }, }; @@ -36,15 +37,15 @@ export const checkTouchedContractsNoSelfdestruct: ProposalCheck = { async function checkNoSelfdestructs( trustedAddrs: Hex[], addresses: Hex[], - provider: PublicClient + client: Client ): Promise<{ info: string[]; warn: string[]; error: string[] }> { const info: string[] = []; const warn: string[] = []; const error: string[] = []; for (const addr of addresses) { - const status = await checkNoSelfdestruct(trustedAddrs, addr, provider); - const isAddrKnown = isKnownAddress(addr, provider.chain!.id); - const address = toAddressLink(addr, true, provider); + const status = await checkNoSelfdestruct(trustedAddrs, addr, client); + const isAddrKnown = isKnownAddress(addr, client.chain!.id); + const address = toAddressLink(addr, true, client); if (status === 'eoa') info.push(`- ${address}: EOA${flagKnownAddress(isAddrKnown)}`); else if (status === 'empty') warn.push(`- ${address}: EOA (may have code later)${flagKnownAddress(isAddrKnown)}`); else if (status === 'safe') info.push(`- ${address}: Contract (looks safe)${flagKnownAddress(isAddrKnown)}`); @@ -76,13 +77,13 @@ const isPUSH = (opcode: number): boolean => opcode >= PUSH1 && opcode <= PUSH32; async function checkNoSelfdestruct( trustedAddrs: Hex[], addr: Hex, - provider: PublicClient + client: Client ): Promise<'safe' | 'eoa' | 'empty' | 'selfdestruct' | 'delegatecall' | 'trusted'> { if (trustedAddrs.map((addr) => addr.toLowerCase()).includes(addr.toLowerCase())) return 'trusted'; const [code, nonce] = await Promise.all([ - provider.getBytecode({ address: addr }), - provider.getTransactionCount({ address: addr }), + getBytecode(client, { address: addr }), + getTransactionCount(client, { address: addr }), ]); // If there is no code and nonce is > 0 then it's an EOA. diff --git a/src/govv3/checks/state.ts b/src/govv3/checks/state.ts index c54fe4b..4ec5341 100644 --- a/src/govv3/checks/state.ts +++ b/src/govv3/checks/state.ts @@ -9,7 +9,7 @@ import { interpretStateChange } from '../utils/stateDiffInterpreter'; export const checkStateChanges: ProposalCheck = { name: 'Reports all state changes', - async checkProposal(proposal, simulation, publicClient) { + async checkProposal(proposal, simulation, client) { const info: string[] = []; const warnings: string[] = []; const errors: string[] = []; @@ -39,7 +39,7 @@ export const checkStateChanges: ProposalCheck = { stateChanges += `\n${getContractName( simulation.contracts, address as Address, - publicClient.chain!.id + client.chain!.id )}\n\`\`\`diff\n`; // Parse each diff. A single diff may involve multiple storage changes, e.g. a proposal that @@ -77,7 +77,7 @@ export const checkStateChanges: ProposalCheck = { original[k], dirty[k], k, - publicClient + client ); if (interpretation) stateChanges += `\n${interpretation}`; stateChanges += '\n'; diff --git a/src/govv3/checks/targetsVerified.ts b/src/govv3/checks/targetsVerified.ts index 9e17929..132de2d 100644 --- a/src/govv3/checks/targetsVerified.ts +++ b/src/govv3/checks/targetsVerified.ts @@ -1,21 +1,22 @@ // Based on https://github.com/Uniswap/governance-seatbelt/blob/main/checks/check-targets-verified-etherscan.ts // adjusted for viem & aave governance v3 -import { Hex, PublicClient } from 'viem'; +import { Client, Hex } from 'viem'; import { ProposalCheck } from './types'; import { TenderlySimulationResponse, tenderly } from '../../utils/tenderlyClient'; import { PayloadsController } from '../payloadsController'; import { isKnownAddress } from '../utils/checkAddress'; import { flagKnownAddress } from '../utils/markdownUtils'; +import { getBytecode } from 'viem/actions'; /** * Check all targets with code are verified on Etherscan */ export const checkTargetsVerifiedEtherscan: ProposalCheck>> = { name: 'Check all targets are verified on Etherscan', - async checkProposal(proposal, sim, publicClient) { + async checkProposal(proposal, sim, client) { const allTargets = proposal.payload.actions.map((action) => action.target); const uniqueTargets = allTargets.filter((addr, i, targets) => targets.indexOf(addr) === i); - const info = await checkVerificationStatuses(sim, uniqueTargets, publicClient); + const info = await checkVerificationStatuses(sim, uniqueTargets, client); return { info, warnings: [], errors: [] }; }, }; @@ -25,8 +26,8 @@ export const checkTargetsVerifiedEtherscan: ProposalCheck = { name: 'Check all touched contracts are verified on Etherscan', - async checkProposal(proposal, sim, publicClient) { - const info = await checkVerificationStatuses(sim, sim.transaction.addresses, publicClient); + async checkProposal(proposal, sim, client) { + const info = await checkVerificationStatuses(sim, sim.transaction.addresses, client); return { info, warnings: [], errors: [] }; }, }; @@ -37,12 +38,12 @@ export const checkTouchedContractsVerifiedEtherscan: ProposalCheck = { async function checkVerificationStatuses( sim: TenderlySimulationResponse, addresses: Hex[], - provider: PublicClient + client: Client ): Promise { let info: string[] = []; // prepare output for (const addr of addresses) { - const isAddrKnown = isKnownAddress(addr, provider.chain!.id); - const status = await checkVerificationStatus(sim, addr, provider); + const isAddrKnown = isKnownAddress(addr, client.chain!.id); + const status = await checkVerificationStatus(sim, addr, client); if (status === 'eoa') { info.push(`- ${addr}: EOA (verification not applicable)`); } else if (status === 'verified') { @@ -68,7 +69,7 @@ async function checkVerificationStatuses( async function checkVerificationStatus( sim: TenderlySimulationResponse, addr: Hex, - provider: PublicClient + client: Client ): Promise<'verified' | 'eoa' | 'unverified'> { // If an address exists in the contracts array, it's verified on Etherscan const contract = getContract(sim, addr); @@ -76,7 +77,7 @@ async function checkVerificationStatus( const stateDiff = getStateDiff(sim, addr); if (stateDiff) return 'unverified'; // Otherwise, check if there's code at the address. Addresses with code not in the contracts array are not verified - const code = await provider.getBytecode({ address: addr }); + const code = await getBytecode(client, { address: addr }); return code === undefined ? 'eoa' : 'unverified'; } diff --git a/src/govv3/checks/types.ts b/src/govv3/checks/types.ts index 870d86a..403e721 100644 --- a/src/govv3/checks/types.ts +++ b/src/govv3/checks/types.ts @@ -1,4 +1,4 @@ -import { PublicClient } from 'viem'; +import { Client } from 'viem'; import { TenderlySimulationResponse } from '../../utils/tenderlyClient'; export type CheckResult = { @@ -9,9 +9,5 @@ export type CheckResult = { export interface ProposalCheck { name: string; - checkProposal( - proposalInfo: T, - simulation: TenderlySimulationResponse, - publicClient: PublicClient - ): Promise; + checkProposal(proposalInfo: T, simulation: TenderlySimulationResponse, client: Client): Promise; } diff --git a/src/govv3/generatePayloadReport.spec.ts b/src/govv3/generatePayloadReport.spec.ts index 4f8c994..1385c0e 100644 --- a/src/govv3/generatePayloadReport.spec.ts +++ b/src/govv3/generatePayloadReport.spec.ts @@ -9,7 +9,7 @@ describe('generatePayloadReport', () => { async () => { const report = await generateReport({ ...(MOCK_PAYLOAD as any), - publicClient: CHAIN_ID_CLIENT_MAP[MOCK_PAYLOAD.simulation.transaction.network_id], + client: CHAIN_ID_CLIENT_MAP[MOCK_PAYLOAD.simulation.transaction.network_id], }); expect(report).toMatchSnapshot(); }, diff --git a/src/govv3/generatePayloadReport.ts b/src/govv3/generatePayloadReport.ts index d0de99d..9141d36 100644 --- a/src/govv3/generatePayloadReport.ts +++ b/src/govv3/generatePayloadReport.ts @@ -1,6 +1,6 @@ -import { PublicClient } from 'viem'; +import { Client } from 'viem'; import { TenderlySimulationResponse } from '../utils/tenderlyClient'; -import { HUMAN_READABLE_PAYLOAD_STATE, PayloadsController, getPayloadsController } from './payloadsController'; +import { HUMAN_READABLE_PAYLOAD_STATE, PayloadsController } from './payloadsController'; import { renderCheckResult, renderUnixTime, toTxLink } from './utils/markdownUtils'; import { checkTargetsNoSelfdestruct, checkTouchedContractsNoSelfdestruct } from './checks/selfDestruct'; import { checkLogs } from './checks/logs'; @@ -11,13 +11,13 @@ type GenerateReportRequest = { payloadId: number; payloadInfo: Awaited>; simulation: TenderlySimulationResponse; - publicClient: PublicClient; + client: Client; }; -export async function generateReport({ payloadId, payloadInfo, simulation, publicClient }: GenerateReportRequest) { +export async function generateReport({ payloadId, payloadInfo, simulation, client }: GenerateReportRequest) { const { payload, executedLog, queuedLog, createdLog } = payloadInfo; // generate file header - let report = `## Payload ${payloadId} on ${publicClient.chain!.name} + let report = `## Payload ${payloadId} on ${client.chain!.name} - Simulation: [https://dashboard.tenderly.co/me/simulator/${ simulation.simulation.id @@ -26,18 +26,18 @@ export async function generateReport({ payloadId, payloadInfo, simulation, publi - maximumAccessLevelRequired: ${payload.maximumAccessLevelRequired} - state: ${payload.state}(${(HUMAN_READABLE_PAYLOAD_STATE as any)[payload.state]}) - actions: ${JSON.stringify(payload.actions, (key, value) => (typeof value === 'bigint' ? value.toString() : value))} -- createdAt: [${renderUnixTime(payload.createdAt)}](${toTxLink(createdLog.transactionHash, false, publicClient)})\n`; +- createdAt: [${renderUnixTime(payload.createdAt)}](${toTxLink(createdLog.transactionHash, false, client)})\n`; if (queuedLog) { report += `- queuedAt: [${renderUnixTime(payload.queuedAt)}](${toTxLink( queuedLog.transactionHash, false, - publicClient + client )})\n`; if (executedLog) { report += `- executedAt: [${renderUnixTime(payload.executedAt)}](${toTxLink( executedLog.transactionHash, false, - publicClient + client )})\n`; } else { report += `- earliest execution at: [${renderUnixTime( @@ -58,7 +58,7 @@ export async function generateReport({ payloadId, payloadInfo, simulation, publi ]; for (const check of checks) { - const result = await check.checkProposal(payloadInfo, simulation, publicClient); + const result = await check.checkProposal(payloadInfo, simulation, client); report += renderCheckResult(check, result); } diff --git a/src/govv3/generateProposalReport.ts b/src/govv3/generateProposalReport.ts index b4b6227..c33a1a3 100644 --- a/src/govv3/generateProposalReport.ts +++ b/src/govv3/generateProposalReport.ts @@ -1,4 +1,4 @@ -import { PublicClient } from 'viem'; +import { Client } from 'viem'; import { TenderlySimulationResponse } from '../utils/tenderlyClient'; import { renderCheckResult, renderUnixTime, toTxLink } from './utils/markdownUtils'; import { checkTouchedContractsNoSelfdestruct } from './checks/selfDestruct'; @@ -12,15 +12,10 @@ type GenerateReportRequest = { proposalId: bigint; proposalInfo: Awaited>; simulation: TenderlySimulationResponse; - publicClient: PublicClient; + client: Client; }; -export async function generateProposalReport({ - proposalId, - proposalInfo, - simulation, - publicClient, -}: GenerateReportRequest) { +export async function generateProposalReport({ proposalId, proposalInfo, simulation, client }: GenerateReportRequest) { const { proposal, executedLog, queuedLog, createdLog, payloadSentLog, votingActivatedLog } = proposalInfo; // generate file header let report = `## Proposal ${proposalId} @@ -32,23 +27,19 @@ export async function generateProposalReport({ - creator: ${proposal.creator} - maximumAccessLevelRequired: ${proposal.accessLevel} - payloads: ${JSON.stringify(proposal.payloads, (key, value) => (typeof value === 'bigint' ? value.toString() : value))} -- createdAt: [${renderUnixTime(proposal.creationTime)}](${toTxLink( - createdLog.transactionHash, - false, - publicClient - )})\n`; +- createdAt: [${renderUnixTime(proposal.creationTime)}](${toTxLink(createdLog.transactionHash, false, client)})\n`; if (queuedLog) { report += `- queuedAt: [${renderUnixTime(proposal.queuingTime)}](${toTxLink( queuedLog.transactionHash, false, - publicClient + client )})\n`; } if (executedLog) { report += `- executedAt: [${renderUnixTime(executedLog.timestamp)}](${toTxLink( executedLog.transactionHash, false, - publicClient + client )})\n`; } report += '\n'; @@ -72,7 +63,7 @@ export async function generateProposalReport({ ]; for (const check of checks) { - const result = await check.checkProposal(proposalInfo, simulation, publicClient); + const result = await check.checkProposal(proposalInfo, simulation, client); report += renderCheckResult(check, result); } diff --git a/src/govv3/governance.ts b/src/govv3/governance.ts index 6a088f2..e93bd64 100644 --- a/src/govv3/governance.ts +++ b/src/govv3/governance.ts @@ -1,17 +1,15 @@ import { AbiStateMutability, + Client, ContractFunctionReturnType, GetContractReturnType, Hex, - PublicClient, encodeFunctionData, fromHex, - getAbiItem, getContract, toHex, } from 'viem'; import merge from 'deepmerge'; -import { LogWithTimestamp, getAndCacheLogs } from '../utils/logs'; import { AaveSafetyModule, AaveV3Ethereum, @@ -29,22 +27,17 @@ import { setBits } from '../utils/storageSlots'; import { VOTING_SLOTS, WAREHOUSE_SLOTS, getAccountRPL, getProof } from './proofs'; import { logInfo } from '../utils/logger'; import { GetProofReturnType } from 'viem/_types/actions/public/getProof'; -import type { ExtractAbiEvent } from 'abitype'; import { readJSONCache, writeJSONCache } from '@bgd-labs/js-utils'; - -type CreatedEvent = ExtractAbiEvent; -type QueuedEvent = ExtractAbiEvent; -type CanceledEvent = ExtractAbiEvent; -type ExecutedEvent = ExtractAbiEvent; -type PayloadSentEvent = ExtractAbiEvent; -type VotingActivatedEvent = ExtractAbiEvent; - -type CreatedLog = LogWithTimestamp; -type QueuedLog = LogWithTimestamp; -type CanceledLog = LogWithTimestamp; -type ExecutedLog = LogWithTimestamp; -type PayloadSentLog = LogWithTimestamp; -type VotingActivatedLog = LogWithTimestamp; +import { + ProposalCreatedEvent, + ProposalExecutedEvent, + ProposalPayloadSentEvent, + ProposalQueuedEvent, + ProposalVotingActivatedEvent, + findProposalLogs, + getGovernanceEvents, +} from './cache/modules/governance'; +import { getBlock, getStorageAt, getTransaction } from 'viem/actions'; export enum ProposalState { Null, // proposal does not exists @@ -62,15 +55,7 @@ function isStateFinal(state: ProposalState) { } export interface Governance { - governanceContract: GetContractReturnType; - cacheLogs: (searchStartBlock?: bigint) => Promise<{ - createdLogs: CreatedLog[]; - queuedLogs: QueuedLog[]; - executedLogs: ExecutedLog[]; - payloadSentLogs: PayloadSentLog[]; - votingActivatedLogs: VotingActivatedLog[]; - canceledLogs: CanceledLog[]; - }>; + governanceContract: GetContractReturnType; /** * Thin caching wrapper on top of getProposal. * If the proposal state is final, the proposal will be stored in json and fetched from there. @@ -82,19 +67,19 @@ export interface Governance { ) => Promise>; getProposalAndLogs: ( proposalId: bigint, - logs: Awaited> + logs: Awaited> ) => Promise<{ proposal: ContractFunctionReturnType; - createdLog: CreatedLog; - queuedLog?: QueuedLog; - executedLog?: ExecutedLog; - votingActivatedLog?: VotingActivatedLog; - payloadSentLog: PayloadSentLog[]; + createdLog: ProposalCreatedEvent; + queuedLog?: ProposalQueuedEvent; + executedLog?: ProposalExecutedEvent; + votingActivatedLog?: ProposalVotingActivatedEvent; + payloadSentLog: ProposalPayloadSentEvent[]; }>; getSimulationPayloadForExecution: (proposalId: bigint) => Promise; simulateProposalExecutionOnTenderly: ( proposalId: bigint, - params: { executedLog?: ExecutedLog } + params: { executedLog?: ProposalExecutedEvent } ) => Promise; getStorageRoots(proposalId: bigint): Promise; /** @@ -127,19 +112,19 @@ export const HUMAN_READABLE_STATE = { interface GetGovernanceParams { address: Hex; - publicClient: PublicClient; + client: Client; blockCreated?: bigint; } -export const getGovernance = ({ address, publicClient }: GetGovernanceParams): Governance => { +export const getGovernance = ({ address, client }: GetGovernanceParams): Governance => { const governanceContract = getContract({ abi: IGovernanceCore_ABI, address, - client: publicClient, + client, }); async function getProposal(proposalId: bigint) { - const filePath = publicClient.chain!.id.toString() + `/proposals`; + const filePath = client.chain!.id.toString() + `/proposals`; const fileName = proposalId; const cache = readJSONCache(filePath, fileName.toString()); if (cache) return cache; @@ -149,9 +134,9 @@ export const getGovernance = ({ address, publicClient }: GetGovernanceParams): G } async function getSimulationPayloadForExecution(proposalId: bigint) { - const currentBlock = await publicClient.getBlock(); + const currentBlock = await getBlock(client); const proposalSlot = getSolidityStorageSlotUint(SLOTS.PROPOSALS_MAPPING, proposalId); - const data = await publicClient.getStorageAt({ + const data = await getStorageAt(client, { address: governanceContract.address, slot: proposalSlot, }); @@ -167,7 +152,7 @@ export const getGovernance = ({ address, publicClient }: GetGovernanceParams): G currentBlock.timestamp - (await governanceContract.read.PROPOSAL_EXPIRATION_TIME()) ); const simulationPayload: TenderlyRequest = { - network_id: String(publicClient.chain!.id), + network_id: String(client.chain!.id), from: EOA, to: governanceContract.address, input: encodeFunctionData({ @@ -193,71 +178,40 @@ export const getGovernance = ({ address, publicClient }: GetGovernanceParams): G return { governanceContract, - async cacheLogs(searchStartBlock) { - const logs = await getAndCacheLogs( - publicClient, - [ - getAbiItem({ abi: IGovernanceCore_ABI, name: 'ProposalCreated' }), - getAbiItem({ abi: IGovernanceCore_ABI, name: 'ProposalQueued' }), - getAbiItem({ abi: IGovernanceCore_ABI, name: 'ProposalExecuted' }), - getAbiItem({ abi: IGovernanceCore_ABI, name: 'PayloadSent' }), - getAbiItem({ abi: IGovernanceCore_ABI, name: 'VotingActivated' }), - getAbiItem({ abi: IGovernanceCore_ABI, name: 'ProposalCanceled' }), - ], - address, - searchStartBlock - ); - const createdLogs = logs.filter((log) => log.eventName === 'ProposalCreated'); - const queuedLogs = logs.filter((log) => log.eventName === 'ProposalQueued'); - const executedLogs = logs.filter((log) => log.eventName === 'ProposalExecuted'); - const payloadSentLogs = logs.filter((log) => log.eventName === 'PayloadSent'); - const votingActivatedLogs = logs.filter((log) => log.eventName === 'VotingActivated'); - const canceledLogs = logs.filter((log) => log.eventName === 'ProposalCanceled'); - - return { createdLogs, queuedLogs, executedLogs, payloadSentLogs, votingActivatedLogs, canceledLogs } as any; - }, getProposal, async getProposalAndLogs(proposalId, logs) { const proposal = await getProposal(proposalId); - const createdLog = logs.createdLogs.find((log) => String(log.args.proposalId) === proposalId.toString())!; - const votingActivatedLog = logs.votingActivatedLogs.find( - (log) => String(log.args.proposalId) === proposalId.toString() - )!; - const queuedLog = logs.queuedLogs.find((log) => String(log.args.proposalId) === proposalId.toString()); - const executedLog = logs.executedLogs.find((log) => String(log.args.proposalId) === proposalId.toString()); - const payloadSentLog = logs.payloadSentLogs.filter( - (log) => String(log.args.proposalId) === proposalId.toString() - ); - return { proposal, createdLog, votingActivatedLog, queuedLog, executedLog, payloadSentLog }; + const proposalLogs = await findProposalLogs(logs, proposalId); + return { proposal, ...proposalLogs }; }, getSimulationPayloadForExecution, async simulateProposalExecutionOnTenderly(proposalId, { executedLog }) { // if successfully executed just replay the txn if (executedLog) { - const tx = await publicClient.getTransaction({ hash: executedLog.transactionHash! }); - return tenderly.simulateTx(publicClient.chain!.id, tx); + const tx = await getTransaction(client, { hash: executedLog.transactionHash! }); + return tenderly.simulateTx(client.chain!.id, tx); } const payload = await getSimulationPayloadForExecution(proposalId); - return tenderly.simulate(payload, publicClient); + return tenderly.simulate(payload, client); }, async getVotingProofs(proposalId: bigint, voter: Hex, votingChainId: bigint) { const proposal = await getProposal(proposalId); const [stkAaveProof, aaveProof, aAaveProof, representativeProof] = await Promise.all([ getProof( - publicClient, + client, AaveSafetyModule.STK_AAVE, [getSolidityStorageSlotAddress(VOTING_SLOTS[AaveSafetyModule.STK_AAVE].balance, voter)], proposal.snapshotBlockHash ), getProof( - publicClient, + client, AaveV3Ethereum.ASSETS.AAVE.UNDERLYING, [getSolidityStorageSlotAddress(VOTING_SLOTS[AaveV3Ethereum.ASSETS.AAVE.UNDERLYING].balance, voter)], proposal.snapshotBlockHash ), getProof( - publicClient, + client, AaveV3Ethereum.ASSETS.AAVE.A_TOKEN, [ getSolidityStorageSlotAddress(VOTING_SLOTS[AaveV3Ethereum.ASSETS.AAVE.A_TOKEN].balance, voter), @@ -266,7 +220,7 @@ export const getGovernance = ({ address, publicClient }: GetGovernanceParams): G proposal.snapshotBlockHash ), getProof( - publicClient, + client, GovernanceV3Ethereum.GOVERNANCE, [ getSolidityStorageSlotBytes( @@ -310,7 +264,7 @@ export const getGovernance = ({ address, publicClient }: GetGovernanceParams): G const proofs = await Promise.all( (Object.keys(addresses) as (keyof typeof addresses)[]).map((address) => getProof( - publicClient, + client, address, Object.keys(addresses[address]).map((slotKey) => toHex((addresses[address] as any)[slotKey])), proposal.snapshotBlockHash diff --git a/src/govv3/payloadsController.ts b/src/govv3/payloadsController.ts index dbe0bc4..1b61b38 100644 --- a/src/govv3/payloadsController.ts +++ b/src/govv3/payloadsController.ts @@ -3,26 +3,23 @@ import { ContractFunctionReturnType, GetContractReturnType, Hex, - PublicClient, + Client, encodeFunctionData, encodePacked, - getAbiItem, getContract, } from 'viem'; -import { LogWithTimestamp, getAndCacheLogs } from '../utils/logs'; import { TenderlyRequest, tenderly, TenderlySimulationResponse } from '../utils/tenderlyClient'; import { EOA } from '../utils/constants'; import { getSolidityStorageSlotUint } from '../utils/storageSlots'; import { IPayloadsControllerCore_ABI } from '@bgd-labs/aave-address-book'; -import type { ExtractAbiEvent } from 'abitype'; - -type PayloadCreatedEvent = ExtractAbiEvent; -type PayloadQueuedEvent = ExtractAbiEvent; -type PayloadExecutedEvent = ExtractAbiEvent; - -type PayloadCreatedLog = LogWithTimestamp; -type PayloadQueuedLog = LogWithTimestamp; -type PayloadExecutedLog = LogWithTimestamp; +import { + PayloadCreatedEvent, + PayloadExecutedEvent, + PayloadQueuedEvent, + findPayloadLogs, + getPayloadsControllerEvents, +} from './cache/modules/payloadsController'; +import { getBlock, getTransaction } from 'viem/actions'; export enum PayloadState { None, @@ -43,22 +40,16 @@ export const HUMAN_READABLE_PAYLOAD_STATE = { }; export interface PayloadsController { - controllerContract: GetContractReturnType; - // cache created / queued / Executed logs - cacheLogs: (searchStartBlock?: bigint) => Promise<{ - createdLogs: PayloadCreatedLog[]; - queuedLogs: PayloadQueuedLog[]; - executedLogs: PayloadExecutedLog[]; - }>; + controllerContract: GetContractReturnType; // executes an existing payload getPayload: ( id: number, - logs: Awaited> + logs: Awaited> ) => Promise<{ payload: ContractFunctionReturnType; - createdLog: PayloadCreatedLog; - queuedLog?: PayloadQueuedLog; - executedLog?: PayloadExecutedLog; + createdLog: PayloadCreatedEvent; + queuedLog?: PayloadQueuedEvent; + executedLog?: PayloadExecutedEvent; }>; getSimulationPayloadForExecution: (id: number) => Promise; simulatePayloadExecutionOnTenderly: ( @@ -73,14 +64,14 @@ const SLOTS = { PAYLOADS_MAPPING: 3n, }; -export const getPayloadsController = (address: Hex, publicClient: PublicClient): PayloadsController => { - const controllerContract = getContract({ abi: IPayloadsControllerCore_ABI, address, client: publicClient }); +export const getPayloadsController = (address: Hex, client: Client): PayloadsController => { + const controllerContract = getContract({ abi: IPayloadsControllerCore_ABI, address, client }); const getSimulationPayloadForExecution = async (id: number) => { const payload = await controllerContract.read.getPayloadById([id]); - const currentBlock = await publicClient.getBlock(); + const currentBlock = await getBlock(client); const simulationPayload: TenderlyRequest = { - network_id: String(publicClient.chain!.id), + network_id: String(client.chain!.id), from: EOA, to: controllerContract.address, input: encodeFunctionData({ @@ -113,45 +104,21 @@ export const getPayloadsController = (address: Hex, publicClient: PublicClient): return { controllerContract, - cacheLogs: async (searchStartBlock) => { - const logs = await getAndCacheLogs( - publicClient, - [ - getAbiItem({ abi: IPayloadsControllerCore_ABI, name: 'PayloadCreated' }), - getAbiItem({ abi: IPayloadsControllerCore_ABI, name: 'PayloadQueued' }), - getAbiItem({ abi: IPayloadsControllerCore_ABI, name: 'PayloadExecuted' }), - ], - address, - searchStartBlock - ); - const createdLogs = logs.filter((log) => log.eventName === 'PayloadCreated') as PayloadCreatedLog[]; - const queuedLogs = logs.filter((log) => log.eventName === 'PayloadQueued') as PayloadQueuedLog[]; - const executedLogs = logs.filter((log) => log.eventName === 'PayloadExecuted') as PayloadExecutedLog[]; - - return { - createdLogs, - queuedLogs, - executedLogs, - }; - }, getPayload: async (id, logs) => { - const createdLog = logs.createdLogs.find((l) => l.args.payloadId === id)!; - if (!createdLog) throw new Error(`Could not find payload ${id} on ${publicClient.chain!.id}`); - const queuedLog = logs.queuedLogs.find((l) => l.args.payloadId === id); - const executedLog = logs.executedLogs.find((l) => l.args.payloadId === id); const payload = await controllerContract.read.getPayloadById([id]); - return { createdLog, queuedLog, executedLog, payload }; + const payloadLogs = await findPayloadLogs(logs, id); + return { ...payloadLogs, payload }; }, getSimulationPayloadForExecution, simulatePayloadExecutionOnTenderly: async (id, { executedLog }) => { // if successfully executed just replay the txn if (executedLog) { - const tx = await publicClient.getTransaction({ hash: executedLog.transactionHash! }); - return tenderly.simulateTx(publicClient.chain!.id, tx); + const tx = await getTransaction(client, { hash: executedLog.transactionHash! }); + return tenderly.simulateTx(client.chain!.id, tx); } const payload = await getSimulationPayloadForExecution(id); - return tenderly.simulate(payload, publicClient); + return tenderly.simulate(payload, client); }, }; }; diff --git a/src/govv3/proofs.ts b/src/govv3/proofs.ts index 4a60c76..fcf37ad 100644 --- a/src/govv3/proofs.ts +++ b/src/govv3/proofs.ts @@ -1,5 +1,6 @@ import { AaveSafetyModule, AaveV3Ethereum, GovernanceV3Ethereum } from '@bgd-labs/aave-address-book'; -import { Block, Chain, GetBlockReturnType, Hex, PublicClient, fromRlp, toHex, toRlp } from 'viem'; +import { Chain, Client, GetBlockReturnType, Hex, fromRlp, toHex, toRlp } from 'viem'; +import { getBlock, getProof as viemGetProof } from 'viem/actions'; /** * Slots that represent configuration values relevant for all accounts @@ -23,9 +24,9 @@ export const VOTING_SLOTS = { [GovernanceV3Ethereum.GOVERNANCE]: { representative: 9n }, // representative } as const; -export async function getProof(publicClient: PublicClient, address: Hex, slots: readonly Hex[], blockHash: Hex) { - const block = await publicClient.getBlock({ blockHash }); - return publicClient.getProof({ +export async function getProof(client: Client, address: Hex, slots: readonly Hex[], blockHash: Hex) { + const block = await getBlock(client, { blockHash }); + return viemGetProof(client, { address, storageKeys: slots.map((slot) => slot), blockNumber: block.number, diff --git a/src/govv3/simulate.ts b/src/govv3/simulate.ts index 874b5fc..9cc0411 100644 --- a/src/govv3/simulate.ts +++ b/src/govv3/simulate.ts @@ -1,31 +1,33 @@ import { logInfo } from '../utils/logger'; import { TenderlySimulationResponse } from '../utils/tenderlyClient'; import { getGovernance } from './governance'; -import { Hex, PublicClient } from 'viem'; +import { Client, Hex } from 'viem'; import { PayloadsController, getPayloadsController } from './payloadsController'; import { CHAIN_ID_CLIENT_MAP } from '@bgd-labs/js-utils'; import { generateReport } from './generatePayloadReport'; import { generateProposalReport } from './generateProposalReport'; +import { cacheGovernance, cachePayloadsController, readBookKeepingCache } from './cache/updateCache'; /** * Reference implementation, unused * @param governanceAddress - * @param publicClient + * @param client * @param proposalId * @returns */ -export async function simulateProposal(governanceAddress: Hex, publicClient: PublicClient, proposalId: bigint) { +export async function simulateProposal(governanceAddress: Hex, client: Client, proposalId: bigint) { + const cache = readBookKeepingCache(); logInfo('General', `Running simulation for ${proposalId}`); - const governance = getGovernance({ address: governanceAddress, publicClient }); - const logs = await governance.cacheLogs(); - const proposal = await governance.getProposalAndLogs(proposalId, logs); + const governance = getGovernance({ address: governanceAddress, client }); + const { eventsCache } = await cacheGovernance(client, governanceAddress, cache); + const proposal = await governance.getProposalAndLogs(proposalId, eventsCache); const result = await governance.simulateProposalExecutionOnTenderly(proposalId, proposal); console.log( await generateProposalReport({ simulation: result, proposalId: proposalId, proposalInfo: proposal, - publicClient: publicClient, + client, }) ); const payloads: { @@ -33,12 +35,10 @@ export async function simulateProposal(governanceAddress: Hex, publicClient: Pub simulation: TenderlySimulationResponse; }[] = []; for (const payload of proposal.proposal.payloads) { - const controllerContract = getPayloadsController( - payload.payloadsController, - CHAIN_ID_CLIENT_MAP[Number(payload.chain) as keyof typeof CHAIN_ID_CLIENT_MAP] - ); - const logs = await controllerContract.cacheLogs(); - const config = await controllerContract.getPayload(payload.payloadId, logs); + const client = CHAIN_ID_CLIENT_MAP[Number(payload.chain) as keyof typeof CHAIN_ID_CLIENT_MAP]; + const controllerContract = getPayloadsController(payload.payloadsController, client); + const { eventsCache } = await cachePayloadsController(client, payload.payloadsController, cache); + const config = await controllerContract.getPayload(payload.payloadId, eventsCache); try { const result = await controllerContract.simulatePayloadExecutionOnTenderly(payload.payloadId, config); console.log( @@ -46,7 +46,7 @@ export async function simulateProposal(governanceAddress: Hex, publicClient: Pub simulation: result, payloadId: payload.payloadId, payloadInfo: config, - publicClient: CHAIN_ID_CLIENT_MAP[Number(payload.chain) as keyof typeof CHAIN_ID_CLIENT_MAP], + client: CHAIN_ID_CLIENT_MAP[Number(payload.chain) as keyof typeof CHAIN_ID_CLIENT_MAP], }) ); payloads.push({ payload: config, simulation: result }); diff --git a/src/govv3/utils/checkAddress.ts b/src/govv3/utils/checkAddress.ts index 40804c6..e53dc93 100644 --- a/src/govv3/utils/checkAddress.ts +++ b/src/govv3/utils/checkAddress.ts @@ -1,7 +1,6 @@ import { findObjectPaths } from 'find-object-paths'; import { Address, getAddress } from 'viem'; import * as addresses from '@bgd-labs/aave-address-book'; -import { ChainId } from '@bgd-labs/js-utils'; /** * Checks if address is listed on address-book @@ -9,7 +8,7 @@ import { ChainId } from '@bgd-labs/js-utils'; * @param chainId * @returns string[] found paths to address-book addresses */ -export function isKnownAddress(value: Address, chainId: ChainId): string[] | void { +export function isKnownAddress(value: Address, chainId: number): string[] | void { // glob imports have no object properties // therefore we recreate the object via spread & remove addresses unrelated to the chain we are checking const transformedAddresses = Object.keys(addresses).reduce((acc, key) => { diff --git a/src/govv3/utils/markdownUtils.ts b/src/govv3/utils/markdownUtils.ts index 2b4c39d..caca8a1 100644 --- a/src/govv3/utils/markdownUtils.ts +++ b/src/govv3/utils/markdownUtils.ts @@ -1,4 +1,4 @@ -import { Hex, PublicClient } from 'viem'; +import { Client, Hex } from 'viem'; import { CheckResult } from '../checks/types'; export function boolToMarkdown(value: boolean) { @@ -11,7 +11,7 @@ export function boolToMarkdown(value: boolean) { * @param address to be linked * @param code whether to link to the code tab */ -export function toAddressLink(address: Hex, md: boolean, client?: PublicClient): string { +export function toAddressLink(address: Hex, md: boolean, client?: Client): string { if (!client) return address; const link = `${client.chain?.blockExplorers?.default.url}/address/${address}`; if (md) return toMarkdownLink(link, address); @@ -23,7 +23,7 @@ export function toAddressLink(address: Hex, md: boolean, client?: PublicClient): * @param address to be linked * @param code whether to link to the code tab */ -export function toTxLink(txn: Hex, md: boolean, client?: PublicClient): string { +export function toTxLink(txn: Hex, md: boolean, client?: Client): string { if (!client) return txn; const link = `${client.chain?.blockExplorers?.default.url}/tx/${txn}`; if (md) return toMarkdownLink(link, txn); diff --git a/src/govv3/utils/stateDiffInterpreter.ts b/src/govv3/utils/stateDiffInterpreter.ts index 62ba336..06e6657 100644 --- a/src/govv3/utils/stateDiffInterpreter.ts +++ b/src/govv3/utils/stateDiffInterpreter.ts @@ -1,4 +1,4 @@ -import { Hex, PublicClient, getContract } from 'viem'; +import { Client, Hex, getContract } from 'viem'; import { tenderlyDeepDiff } from './tenderlyDeepDiff'; import { ERC20_ABI } from '../abis/ERC20'; import * as pools from '@bgd-labs/aave-address-book'; @@ -10,10 +10,10 @@ export async function interpretStateChange( original: Record, dirty: Record, key: Hex, - publicClient: PublicClient + client: Client ) { if (name === '_reserves' && (original.configuration.data || dirty.configuration.data)) - return await reserveConfigurationChanged(contractAddress, original, dirty, key, publicClient); + return await reserveConfigurationChanged(contractAddress, original, dirty, key, client); return undefined; } @@ -22,13 +22,13 @@ async function reserveConfigurationChanged( original: Record, dirty: Record, key: Hex, - publicClient: PublicClient + client: Client ) { const configurationBefore = getDecodedReserveData(contractAddress, original.configuration.data); const configurationAfter = getDecodedReserveData(contractAddress, dirty.configuration.data); let symbol = 'unknown'; try { - const erc20Contract = getContract({ client: publicClient, address: key, abi: ERC20_ABI }); + const erc20Contract = getContract({ client, address: key, abi: ERC20_ABI }); symbol = await erc20Contract.read.symbol(); } catch (e) {} // const symbol = diff --git a/src/index.ts b/src/index.ts index 628136e..944fbb0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,3 +6,4 @@ export * from './govv3/generatePayloadReport'; export * from './govv3/generateProposalReport'; export * from './utils/tenderlyClient'; export * from './utils/logger'; +export * from './govv3/cache/updateCache'; diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 2dc2d29..b1c3dcd 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1,6 +1,5 @@ import { GovernanceV3Ethereum } from '@bgd-labs/aave-address-book'; import { mainnetClient } from '@bgd-labs/js-utils'; -import { PublicClient } from 'viem'; // arbitrary from EOA for proposal executions export const EOA = '0xD73a92Be73EfbFcF3854433A5FcbAbF9c1316073' as const; @@ -13,4 +12,4 @@ export const VERBOSE = process.env.VERBOSE; export const FORMAT = (process.env.FORMAT || 'raw') as 'raw' | 'encoded'; export const DEFAULT_GOVERNANCE = GovernanceV3Ethereum.GOVERNANCE; -export const DEFAULT_GOVERNANCE_CLIENT = mainnetClient as PublicClient; +export const DEFAULT_GOVERNANCE_CLIENT = mainnetClient; diff --git a/src/utils/logs.ts b/src/utils/logs.ts index 01d64ad..ae0ed2f 100644 --- a/src/utils/logs.ts +++ b/src/utils/logs.ts @@ -1,7 +1,5 @@ -import { GetLogsReturnType, Hex, Log, PublicClient } from 'viem'; -import type { Abi, AbiEvent } from 'abitype'; -import { logInfo } from './logger'; -import { getContractDeploymentBlock, getLogs, readJSONCache, writeJSONCache } from '@bgd-labs/js-utils'; +import { GetLogsReturnType } from 'viem'; +import type { AbiEvent } from 'abitype'; type ArrayElement = ArrayType extends readonly (infer ElementType)[] ? ElementType @@ -13,61 +11,3 @@ export type LogWithTimestamp< ? [TAbiEvent] : undefined > = ArrayElement> & { timestamp: number }; - -/** - * Fetches the logs and stores them in a cache folder. - * @param client - * @param filter - * @returns logs - */ -export async function getAndCacheLogs( - client: PublicClient, - events: TAbiEvents, - address: Hex, - searchStartBlock?: bigint -): Promise>> { - const currentBlock = await client.getBlockNumber(); - /** - * need to specify range as some node prividers (e.g. default on base) throw range error on filter creation - */ - const filePath = client.chain!.id.toString(); - const fileName = address; - // read stale cache if it exists - const cache: Array> = readJSONCache(filePath, fileName) || []; - const startBlock = - cache.length > 0 - ? BigInt(cache[cache.length - 1].blockNumber as bigint) + 1n - : searchStartBlock - ? searchStartBlock - : (await getContractDeploymentBlock({ - client, - fromBlock: 0n, - toBlock: currentBlock, - contractAddress: address, - maxDelta: 10000n, - })) || 0n; - const logs = await getLogs({ client, fromBlock: startBlock, toBlock: currentBlock, events, address }); - 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) => - a.blockNumber !== b.blockNumber - ? Number(a.blockNumber) - Number(b.blockNumber) - : Number(a.logIndex) - Number(b.logIndex) - ); - logInfo(client.chain?.name!, `Store ${newLogs.length} logs for event: ${address} on chainId: ${client.chain!.id}`); - writeJSONCache(filePath, fileName, combinedCache); - return combinedCache; - } else { - logInfo(client.chain?.name!, 'no new logs found'); - } - return cache; -} diff --git a/src/utils/tenderlyClient.ts b/src/utils/tenderlyClient.ts index 7327198..1d14290 100644 --- a/src/utils/tenderlyClient.ts +++ b/src/utils/tenderlyClient.ts @@ -8,14 +8,14 @@ import { parseEther, fromHex, pad, - zeroAddress, - PublicClient, Chain, Address, + Client, } from 'viem'; import { EOA } from './constants'; import { logError, logInfo, logSuccess, logWarning } from './logger'; import { GetTransactionReturnType } from 'viem'; +import { getBlock } from 'viem/actions'; export type StateObject = { balance?: string; code?: string; @@ -295,7 +295,7 @@ class Tenderly { return result; }; - simulate = async (request: TenderlyRequest, publicClient?: PublicClient): Promise => { + simulate = async (request: TenderlyRequest, client?: Client): Promise => { if (!request.state_objects) { request.state_objects = {}; } @@ -307,8 +307,8 @@ class Tenderly { let apiUrl = `${this.TENDERLY_BASE}/account/${this.ACCOUNT}/project/${this.PROJECT}/simulate`; - if (publicClient) { - const url = publicClient.transport.url! as string; + if (client) { + const url = client.transport.url! as string; const tenderlyForkRegex = new RegExp(/https:\/\/rpc.tenderly.co\/fork\/(.*)/); if (tenderlyForkRegex.test(url)) { const matches = url.match(tenderlyForkRegex); @@ -462,16 +462,16 @@ class Tenderly { }; warpTime = async (fork: Fork, timestamp: bigint) => { - const publicProvider = createPublicClient({ + const client = createPublicClient({ chain: { id: fork.forkNetworkId } as any, transport: http(fork.forkUrl), }); - const currentBlock = await publicProvider.getBlock(); + const currentBlock = await getBlock(client); // warping forward in time if (timestamp > currentBlock.timestamp) { logInfo('tenderly', `warping time from ${currentBlock.timestamp} to ${timestamp}`); - await publicProvider.request({ + await client.request({ method: 'evm_increaseTime' as any, params: [toHex(timestamp - currentBlock.timestamp)], }); @@ -484,14 +484,14 @@ class Tenderly { }; warpBlocks = async (fork: Fork, blockNumber: bigint) => { - const publicProvider = createPublicClient({ + const client = createPublicClient({ chain: { id: fork.forkNetworkId } as any, transport: http(fork.forkUrl), }); - const currentBlock = await publicProvider.getBlock(); + const currentBlock = await getBlock(client); if (blockNumber > currentBlock.number) { logInfo('tenderly', `warping blocks from ${currentBlock.number} to ${blockNumber}`); - await publicProvider.request({ + await client.request({ method: 'evm_increaseBlocks' as any, params: [toHex(blockNumber - currentBlock.number)], }); diff --git a/yarn.lock b/yarn.lock index 0dd2834..752d680 100644 --- a/yarn.lock +++ b/yarn.lock @@ -33,16 +33,17 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@bgd-labs/aave-address-book@2.15.3-89c77b1d7ee3574250b9bef36599806dd7a3bb94.0": - version "2.15.3-89c77b1d7ee3574250b9bef36599806dd7a3bb94.0" - resolved "https://registry.yarnpkg.com/@bgd-labs/aave-address-book/-/aave-address-book-2.15.3-89c77b1d7ee3574250b9bef36599806dd7a3bb94.0.tgz#bf60390fcd003a38dbcce1f4e5267f4e80198fda" - integrity sha512-XaHIcMFt2dbAVjD2oJGYY7EGhh+iumQWhmmZjRwERsBadWJtYfMlfsHw+nShPFuHp1AR0F8Xvb3phRVlicRibg== +"@bgd-labs/aave-address-book@2.18.0": + version "2.18.0" + resolved "https://registry.yarnpkg.com/@bgd-labs/aave-address-book/-/aave-address-book-2.18.0.tgz#f0e62da3935246c70d0f6dd07d394448dd505852" + integrity sha512-REfTbwYLnazYALXOC1eiuBELirYypk3vXPv2Ale5ZnyD+uQhemc7ya6y75iKGm3BuYC6jLIsGwSHczX8D10UEQ== -"@bgd-labs/js-utils@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@bgd-labs/js-utils/-/js-utils-1.0.3.tgz#8f641b5581ba0fac23b80ab80fbd7299a4bfcb98" - integrity sha512-/YlB7vyDibLazOfDCgGCRmjyU9ehjfrBDGiqbt/OOiKhXk6B8N/kuMcMNpnA9MWsLkWymolwB9ihl7dA2gzavQ== +"@bgd-labs/js-utils@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@bgd-labs/js-utils/-/js-utils-1.1.1.tgz#0e400061169cd38d1dd44b401dc3c05b904cbd76" + integrity sha512-CWzoY+3IWpu/YhDgVYbnZhBEGQ8kGJwAgqZexrIkD9+GJh5raEcOWByV8psWapOwttWO4ppxS/RgmMdJQ5Hhxg== dependencies: + "@supercharge/promise-pool" "^3.1.0" bs58 "^5.0.0" gray-matter "^4.0.3" tsx "^4.4.0" @@ -601,6 +602,11 @@ resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== +"@supercharge/promise-pool@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@supercharge/promise-pool/-/promise-pool-3.1.0.tgz#308b9f4d4bf1d607695f916d9454a3556cd4c2b4" + integrity sha512-gB3NukbIcYzRtPoE6dx9svQYPodxvnfQlaaQd8N/z87E6WaMfRE7o5HwB+LZ+KeM0nsNAq1n4TmBtfz1VCUR+Q== + "@types/estree@1.0.5", "@types/estree@^1.0.0": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" @@ -699,10 +705,10 @@ loupe "^2.3.7" pretty-format "^29.7.0" -abitype@0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.10.0.tgz#d3504747cc81df2acaa6c460250ef7bc9219a77c" - integrity sha512-QvMHEUzgI9nPj9TWtUGnS2scas80/qaL5PBxGdwWhhvzqXfOph+IEiiiWrzuisu3U3JgDQVruW9oLbJoQ3oZ3A== +abitype@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.0.tgz#237176dace81d90d018bebf3a45cb42f2a2d9e97" + integrity sha512-NMeMah//6bJ56H5XRj8QCV4AwuW6hB6zqz2LnhhLdcWVQOsXki6/Pn3APeqxCma62nXIcmZWdu1DlHWS74umVQ== acorn-walk@^8.3.1: version "8.3.2" @@ -2626,17 +2632,17 @@ varint@^6.0.0: resolved "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz" integrity sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg== -viem@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/viem/-/viem-2.0.6.tgz#1472054eb767c8e74db2e6fab11a8ad8db7387d0" - integrity sha512-u7P/RCHufWZW2x2d1MB9O9S9xAopXlWpWEThxfD7FKKzyFGw0lN3QYC3FG0KHKziRDieeu4lkVzAkdag7W+S6g== +viem@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.5.0.tgz#1d7bd5333a6b9387d42c1c2d368d0b88c2961ee1" + integrity sha512-ytHXIWtlgPs4mcsGxXjJrQ25v+N4dE2hBzgCU8CVv4iXNh3PRFRgyYa7igZlmxiMVzkfSHHADOtivS980JhilA== dependencies: "@adraffy/ens-normalize" "1.10.0" "@noble/curves" "1.2.0" "@noble/hashes" "1.3.2" "@scure/bip32" "1.3.2" "@scure/bip39" "1.2.1" - abitype "0.10.0" + abitype "1.0.0" isows "1.0.3" ws "8.13.0"