Skip to content

Commit

Permalink
refactor etherscan provider to extract API key from custom config
Browse files Browse the repository at this point in the history
  • Loading branch information
yuetloo committed Apr 16, 2024
1 parent 00cb623 commit e7ded06
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 44 deletions.
35 changes: 12 additions & 23 deletions contracts/tasks/runners/exportRound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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}`
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -383,7 +372,7 @@ task('export-round', 'Export round data for the leaderboard')
endBlock,
blocksPerBatch,
network: network.name,
etherscanApiKey,
config,
})

console.log('Parsing logs...')
Expand Down
1 change: 0 additions & 1 deletion contracts/tasks/runners/verifyAll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
1 change: 0 additions & 1 deletion contracts/tasks/runners/verifyDeployer.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand Down
7 changes: 4 additions & 3 deletions contracts/utils/RecipientRegistryLogProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down Expand Up @@ -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<Log[]> {
// fetch event logs containing project information
Expand All @@ -64,7 +65,7 @@ export class RecipientRegistryLogProcessor {

const logProvider = ProviderFactory.createProvider({
network,
etherscanApiKey,
config,
})

let logs: Log[] = []
Expand Down
11 changes: 11 additions & 0 deletions contracts/utils/abi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)`,
Expand Down
57 changes: 47 additions & 10 deletions contracts/utils/providers/EtherscanProvider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { BaseProvider, FetchLogArgs, Log } from './BaseProvider'
import { FetchRequest } from 'ethers'
import { HardhatConfig } from 'hardhat/types'

const EtherscanApiUrl: Record<string, string> = {
xdai: 'https://api.gnosisscan.io',
Expand All @@ -10,32 +11,68 @@ const EtherscanApiUrl: Record<string, string> = {
'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<string, string> = {
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({
filter,
startBlock,
lastBlock,
}: FetchLogArgs): Promise<Log[]> {
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)
Expand Down
10 changes: 4 additions & 6 deletions contracts/utils/providers/ProviderFactory.ts
Original file line number Diff line number Diff line change
@@ -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)
}
}

0 comments on commit e7ded06

Please sign in to comment.