From e7ded06adb82128bf59f694d6f76442baf20c324 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Tue, 16 Apr 2024 17:07:27 -0400 Subject: [PATCH] refactor etherscan provider to extract API key from custom config --- contracts/tasks/runners/exportRound.ts | 35 ++++-------- contracts/tasks/runners/verifyAll.ts | 1 - contracts/tasks/runners/verifyDeployer.ts | 1 - .../utils/RecipientRegistryLogProcessor.ts | 7 ++- contracts/utils/abi.ts | 11 ++++ .../utils/providers/EtherscanProvider.ts | 57 +++++++++++++++---- contracts/utils/providers/ProviderFactory.ts | 10 ++-- 7 files changed, 78 insertions(+), 44 deletions(-) diff --git a/contracts/tasks/runners/exportRound.ts b/contracts/tasks/runners/exportRound.ts index 4eadf9818..fa3e332b1 100644 --- a/contracts/tasks/runners/exportRound.ts +++ b/contracts/tasks/runners/exportRound.ts @@ -16,7 +16,7 @@ import { Contract, formatUnits, getNumber } from 'ethers' import { Ipfs } from '../../utils/ipfs' import { Project, Round, RoundFileContent } from '../../utils/types' import { RecipientRegistryLogProcessor } from '../../utils/RecipientRegistryLogProcessor' -import { getRecipientAddressAbi } from '../../utils/abi' +import { getRecipientAddressAbi, MaciV0Abi } from '../../utils/abi' import { JSONFile } from '../../utils/JSONFile' import path from 'path' import fs from 'fs' @@ -41,19 +41,6 @@ function roundListFileName(directory: string): string { return path.join(directory, 'rounds.json') } -function getEtherscanApiKey(config: any, network: string): string { - let etherscanApiKey = '' - if (config.etherscan?.apiKey) { - if (typeof config.etherscan.apiKey === 'string') { - etherscanApiKey = config.etherscan.apiKey - } else { - etherscanApiKey = config.etherscan.apiKey[network] - } - } - - return etherscanApiKey -} - function roundMapKey(round: RoundListEntry): string { return `${round.network}.${round.address}` } @@ -76,7 +63,10 @@ async function updateRoundList(filePath: string, round: RoundListEntry) { const rounds: RoundListEntry[] = Array.from(roundMap.values()) // sort in ascending start time order - rounds.sort((round1, round2) => round1.startTime - round2.startTime) + rounds.sort( + (round1, round2) => + getNumber(round1.startTime) - getNumber(round2.startTime) + ) JSONFile.write(filePath, rounds) console.log('Finished writing to', filePath) } @@ -137,7 +127,11 @@ async function mergeRecipientTally({ } const tallyResult = tally.results.tally[i] - const spentVoiceCredits = tally.perVOSpentVoiceCredits.tally[i] + + // In MACI V1, totalVoiceCreditsPerVoteOption is called perVOSpentVoiceCredits + const spentVoiceCredits = tally.perVOSpentVoiceCredits + ? tally.perVOSpentVoiceCredits.tally[i] + : tally.totalVoiceCreditsPerVoteOption.tally[i] const formattedDonationAmount = formatUnits( BigInt(spentVoiceCredits) * BigInt(voiceCreditFactor), nativeTokenDecimals @@ -237,7 +231,7 @@ async function getRoundInfo( maxMessages = maxValues.maxMessages maxRecipients = maxValues.maxVoteOptions } else { - const maci = await ethers.getContractAt('MACI', maciAddress) + const maci = await ethers.getContractAt(MaciV0Abi, maciAddress) startTime = await maci.signUpTimestamp().catch(toZero) signUpDuration = await maci.signUpDurationSeconds().catch(toZero) votingDuration = await maci.votingDurationSeconds().catch(toZero) @@ -352,11 +346,6 @@ task('export-round', 'Export round data for the leaderboard') console.log('Processing on ', network.name) console.log('Funding round address', roundAddress) - const etherscanApiKey = getEtherscanApiKey(config, network.name) - if (!etherscanApiKey) { - throw new Error('Etherscan API key not set') - } - const outputSubDir = path.join(outputDir, network.name) try { fs.statSync(outputSubDir) @@ -383,7 +372,7 @@ task('export-round', 'Export round data for the leaderboard') endBlock, blocksPerBatch, network: network.name, - etherscanApiKey, + config, }) console.log('Parsing logs...') diff --git a/contracts/tasks/runners/verifyAll.ts b/contracts/tasks/runners/verifyAll.ts index 6425bf5eb..c010df8ff 100644 --- a/contracts/tasks/runners/verifyAll.ts +++ b/contracts/tasks/runners/verifyAll.ts @@ -132,7 +132,6 @@ async function getUserRegistryName( * Get the list of contracts to verify * @param clrfund The ClrFund contract address * @param ethers The Hardhat Ethers helper - * @param etherscanProvider The Etherscan provider * @returns The list of contracts to verify */ async function getContractList( diff --git a/contracts/tasks/runners/verifyDeployer.ts b/contracts/tasks/runners/verifyDeployer.ts index a2fad0cf6..284689063 100644 --- a/contracts/tasks/runners/verifyDeployer.ts +++ b/contracts/tasks/runners/verifyDeployer.ts @@ -1,6 +1,5 @@ import { task } from 'hardhat/config' import { EContracts } from '../../utils/types' -import { EtherscanProvider } from '../../utils/providers/EtherscanProvider' import { ContractVerifier } from '../helpers/ContractVerifier' import { ConstructorArguments } from '../helpers/ConstructorArguments' diff --git a/contracts/utils/RecipientRegistryLogProcessor.ts b/contracts/utils/RecipientRegistryLogProcessor.ts index 615ab1cb0..6d74a4334 100644 --- a/contracts/utils/RecipientRegistryLogProcessor.ts +++ b/contracts/utils/RecipientRegistryLogProcessor.ts @@ -7,6 +7,7 @@ import { Log } from './providers/BaseProvider' import { toDate } from './date' import { EVENT_ABIS } from './abi' import { AbiInfo } from './types' +import { HardhatConfig } from 'hardhat/types' function getFilter(address: string, abiInfo: AbiInfo): EventFilter { const eventInterface = new Interface([abiInfo.abi]) @@ -41,14 +42,14 @@ export class RecipientRegistryLogProcessor { endBlock, startBlock, blocksPerBatch, - etherscanApiKey, + config, network, }: { recipientRegistry: Contract startBlock: number endBlock: number blocksPerBatch: number - etherscanApiKey: string + config: HardhatConfig network: string }): Promise { // fetch event logs containing project information @@ -64,7 +65,7 @@ export class RecipientRegistryLogProcessor { const logProvider = ProviderFactory.createProvider({ network, - etherscanApiKey, + config, }) let logs: Log[] = [] diff --git a/contracts/utils/abi.ts b/contracts/utils/abi.ts index 98101978a..dc3c0c004 100644 --- a/contracts/utils/abi.ts +++ b/contracts/utils/abi.ts @@ -6,6 +6,17 @@ type EventAbiEntry = { remove: AbiInfo } +/** + * MACI v0 ABI used in exportRound.ts + */ +export const MaciV0Abi = [ + 'function signUpTimestamp() view returns (uint256)', + 'function signUpDurationSeconds() view returns (uint256)', + 'function votingDurationSeconds() view returns (uint256)', + `function treeDepths() view returns ((uint8 stateTreeDepth, uint8 messageTreeDepth, uint8 voteOptionTreeDepth))`, + 'function numMessages() view returns (uint256)', +] + export const getRecipientAddressAbi = [ `function getRecipientAddress(uint256 _index, uint256 _startTime, uint256 _endTime)` + ` external view returns (address)`, diff --git a/contracts/utils/providers/EtherscanProvider.ts b/contracts/utils/providers/EtherscanProvider.ts index 5a35ac557..8534156ae 100644 --- a/contracts/utils/providers/EtherscanProvider.ts +++ b/contracts/utils/providers/EtherscanProvider.ts @@ -1,5 +1,6 @@ import { BaseProvider, FetchLogArgs, Log } from './BaseProvider' import { FetchRequest } from 'ethers' +import { HardhatConfig } from 'hardhat/types' const EtherscanApiUrl: Record = { xdai: 'https://api.gnosisscan.io', @@ -10,14 +11,57 @@ const EtherscanApiUrl: Record = { 'optimism-sepolia': 'https://api-sepolia-optimistic.etherscan.io', } +/** + * Mapping of the hardhat network name to the Etherscan network name in the hardhat.config + */ +const EtherscanNetworks: Record = { + arbitrum: 'arbitrumOne', + optimism: 'optimisticEthereum', +} + +/** + * The the Etherscan API key from the hardhat.config file + * @param config The Hardhat config object + * @param network The Hardhat network name + * @returns The Etherscan API key + */ +function getEtherscanApiKey(config: HardhatConfig, network: string): string { + let etherscanApiKey = '' + if (config.etherscan?.apiKey) { + if (typeof config.etherscan.apiKey === 'string') { + etherscanApiKey = config.etherscan.apiKey + } else { + const etherscanNetwork = EtherscanNetworks[network] ?? network + etherscanApiKey = config.etherscan.apiKey[etherscanNetwork] + } + } + + return etherscanApiKey +} + export class EtherscanProvider extends BaseProvider { apiKey: string network: string + baseUrl: string - constructor(apiKey: string, network: string) { + constructor(config: HardhatConfig, network: string) { super() - this.apiKey = apiKey + + const etherscanApiKey = getEtherscanApiKey(config, network) + if (!etherscanApiKey) { + throw new Error(`Etherscan API key is not found for ${network}`) + } + + const etherscanBaseUrl = EtherscanApiUrl[network] + if (!etherscanBaseUrl) { + throw new Error( + `Network ${network} is not supported in etherscan fetch log api` + ) + } + this.network = network + this.apiKey = etherscanApiKey + this.baseUrl = etherscanBaseUrl } async fetchLogs({ @@ -25,17 +69,10 @@ export class EtherscanProvider extends BaseProvider { startBlock, lastBlock, }: FetchLogArgs): Promise { - const baseUrl = EtherscanApiUrl[this.network] - if (!baseUrl) { - throw new Error( - `Network ${this.network} is not supported in etherscan fetch log api` - ) - } - const topic0 = filter.topics?.[0] || '' const toBlockQuery = lastBlock ? `&toBlock=${lastBlock}` : '' const url = - `${baseUrl}/api?module=logs&action=getLogs&address=${filter.address}` + + `${this.baseUrl}/api?module=logs&action=getLogs&address=${filter.address}` + `&topic0=${topic0}&fromBlock=${startBlock}${toBlockQuery}&apikey=${this.apiKey}` const req = new FetchRequest(url) diff --git a/contracts/utils/providers/ProviderFactory.ts b/contracts/utils/providers/ProviderFactory.ts index 6a79ad0ec..b7c5474d2 100644 --- a/contracts/utils/providers/ProviderFactory.ts +++ b/contracts/utils/providers/ProviderFactory.ts @@ -1,17 +1,15 @@ +import { HardhatConfig } from 'hardhat/types' import { BaseProvider } from './BaseProvider' import { EtherscanProvider } from './EtherscanProvider' export type CreateProviderArgs = { network: string - etherscanApiKey: string + config: HardhatConfig } export class ProviderFactory { - static createProvider({ - network, - etherscanApiKey, - }: CreateProviderArgs): BaseProvider { + static createProvider({ network, config }: CreateProviderArgs): BaseProvider { // use etherscan provider only as JsonRpcProvider is not reliable - return new EtherscanProvider(etherscanApiKey, network) + return new EtherscanProvider(config, network) } }