UNPKG

@hyperlane-xyz/sdk

Version:

The official SDK for the Hyperlane Network

173 lines 7.66 kB
import { BigNumber, constants } from 'ethers'; import { getAbiItem, parseEventLogs, toEventSelector, } from 'viem'; import { TimelockController__factory, } from '@hyperlane-xyz/core'; import { objFilter, objMap } from '@hyperlane-xyz/utils'; import { EvmEventLogsReader, } from '../../rpc/evm/EvmEventLogsReader.js'; import { viemLogFromGetEventLogsResponse } from '../../rpc/evm/utils.js'; import { CANCELLER_ROLE, EMPTY_BYTES_32, EXECUTOR_ROLE, PROPOSER_ROLE, } from './constants.js'; import { getTimelockExecutableTransactionFromBatch } from './utils.js'; const CALL_EXECUTED_EVENT_SELECTOR = toEventSelector(getAbiItem({ abi: TimelockController__factory.abi, name: 'CallExecuted', })); const CALL_SCHEDULED_EVENT_SELECTOR = toEventSelector(getAbiItem({ abi: TimelockController__factory.abi, name: 'CallScheduled', })); const CALL_CANCELLED_EVENT_SELECTOR = toEventSelector(getAbiItem({ abi: TimelockController__factory.abi, name: 'Cancelled', })); const CALL_SALT_EVENT_SELECTOR = toEventSelector(getAbiItem({ abi: TimelockController__factory.abi, name: 'CallSalt', })); export class EvmTimelockReader { chain; multiProvider; timelockInstance; evmLogReader; constructor(chain, multiProvider, timelockInstance, evmLogReader) { this.chain = chain; this.multiProvider = multiProvider; this.timelockInstance = timelockInstance; this.evmLogReader = evmLogReader; } static fromConfig(config) { const { chain, timelockAddress, multiProvider, useRPC, paginationBlockRange, } = config; const timelockInstance = TimelockController__factory.connect(timelockAddress, multiProvider.getProvider(chain)); const evmLogReader = EvmEventLogsReader.fromConfig({ chain, useRPC, paginationBlockRange }, multiProvider); return new EvmTimelockReader(chain, multiProvider, timelockInstance, evmLogReader); } async getOperationsSalt() { const logs = await this.evmLogReader.getLogsByTopic({ contractAddress: this.timelockInstance.address, eventTopic: CALL_SALT_EVENT_SELECTOR, }); const result = parseEventLogs({ abi: TimelockController__factory.abi, eventName: 'CallSalt', logs: logs.map(viemLogFromGetEventLogsResponse), }); return Object.fromEntries(result.map((parsedEvent) => [parsedEvent.args.id, parsedEvent.args.salt])); } async getScheduledOperations() { const [callScheduledEvents, callSaltByOperationId] = await Promise.all([ this.evmLogReader.getLogsByTopic({ contractAddress: this.timelockInstance.address, eventTopic: CALL_SCHEDULED_EVENT_SELECTOR, }), this.getOperationsSalt(), ]); return getScheduledTimelockOperationIdsFromLogs(callScheduledEvents, callSaltByOperationId); } async getCancelledOperationIds() { const cancelledOperationEvents = await this.evmLogReader.getLogsByTopic({ contractAddress: this.timelockInstance.address, eventTopic: CALL_CANCELLED_EVENT_SELECTOR, }); return getOperationIdFromEventLogs(cancelledOperationEvents, 'Cancelled'); } async getExecutedOperationIds() { const executedOperationEvents = await this.evmLogReader.getLogsByTopic({ contractAddress: this.timelockInstance.address, eventTopic: CALL_EXECUTED_EVENT_SELECTOR, }); return getOperationIdFromEventLogs(executedOperationEvents, 'CallExecuted'); } async getReadyOperationIds(operationIds) { const readyOperationIds = new Set(); for (const operationId of operationIds) { const isReady = await this.timelockInstance.isOperationReady(operationId); if (isReady) { readyOperationIds.add(operationId); } } return readyOperationIds; } async getScheduledExecutableTransactions() { const [scheduledOperations, cancelledOperations, executedOperations] = await Promise.all([ this.getScheduledOperations(), this.getCancelledOperationIds(), this.getExecutedOperationIds(), ]); // Remove the operations that have been cancelled or executed const maybeExecutableOperations = objFilter(scheduledOperations, (id, _operation) => !(cancelledOperations.has(id) || executedOperations.has(id))); const readyOperationIds = await this.getReadyOperationIds(Object.keys(maybeExecutableOperations)); const pendingExecutableOperations = objFilter(maybeExecutableOperations, (operationId, _operation) => readyOperationIds.has(operationId)); return objMap(pendingExecutableOperations, (_operationId, operationData) => { return { data: operationData.data, delay: operationData.delay, encodedExecuteTransaction: getTimelockExecutableTransactionFromBatch(operationData), id: operationData.id, predecessor: operationData.predecessor, salt: operationData.salt, }; }); } async hasRole(address, role) { // If the 0 address has the role anyone has the role const [hasRole, isOpenRole] = await Promise.all([ this.timelockInstance.hasRole(role, address), this.timelockInstance.hasRole(role, constants.AddressZero), ]); return hasRole || isOpenRole; } async canExecuteOperations(address) { return this.hasRole(address, EXECUTOR_ROLE); } async canCancelOperations(address) { return this.hasRole(address, CANCELLER_ROLE); } async canScheduleOperations(address) { return this.hasRole(address, PROPOSER_ROLE); } } function getScheduledTimelockOperationIdsFromLogs(callScheduledLogs, callSaltByOperationId) { const parsedLogs = parseEventLogs({ abi: TimelockController__factory.abi, eventName: 'CallScheduled', logs: callScheduledLogs.map(viemLogFromGetEventLogsResponse), }); return parsedLogs.reduce((operationsById, parsedLog) => { const { data, delay, id, index, predecessor, target, value } = parsedLog.args; if (!operationsById[id]) { operationsById[id] = { data: [ { data, to: target, value: BigNumber.from(value), }, ], delay: Number(delay), predecessor, // If no CallSalt event was emitted for this operation batch // it means that no salt was provided when proposing the transaction salt: callSaltByOperationId[id] ?? EMPTY_BYTES_32, id, }; } else { // it should be safe to convert a bigint to number // in this case as it is an array index for a Timelock // contract operation operationsById[id].data[Number(index.toString())] = { data, to: target, value: BigNumber.from(value), }; } return operationsById; }, {}); } function getOperationIdFromEventLogs(logs, eventName) { const result = parseEventLogs({ abi: TimelockController__factory.abi, eventName: eventName, logs: logs.map(viemLogFromGetEventLogsResponse), }); return new Set(result.map((parsedEvent) => parsedEvent.args.id)); } //# sourceMappingURL=EvmTimelockReader.js.map