Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Static site after finalize #613

Merged
merged 66 commits into from
Jan 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
1863b1b
script to fetch round information
yuetloo Dec 15, 2022
7b91f1e
round static data
yuetloo Dec 15, 2022
325e3f1
fix error when endBlock is not provided
yuetloo Dec 15, 2022
572ba0a
refactor script to pull historical round data
yuetloo Dec 19, 2022
d27dbb9
refactor fetchRound
yuetloo Dec 20, 2022
572f4bc
handle file not found error
yuetloo Dec 20, 2022
a9a5d8c
Merge remote-tracking branch 'origin/develop' into feat/static-leader…
yuetloo Dec 20, 2022
5684fb2
allow previous rounds to be displayed using static JSON data
yuetloo Dec 2, 2022
f897521
remove unused imports
yuetloo Dec 30, 2022
26c18fa
fix error about tally can be null
yuetloo Dec 30, 2022
67e5d42
only show links if available
yuetloo Dec 31, 2022
8e961b1
add getProjects
yuetloo Dec 31, 2022
7ea2f0e
fix build error caused by console.error
yuetloo Dec 31, 2022
b36a723
app loads based on round status
yuetloo Jan 4, 2023
9617610
remove unused import
yuetloo Jan 4, 2023
d289295
get current round from subgraph
yuetloo Jan 4, 2023
2803736
remove unused import
yuetloo Jan 4, 2023
50e4168
assign default matching pool size if missing
yuetloo Jan 4, 2023
a04635e
add menu item for rounds
yuetloo Jan 4, 2023
0d71219
add comment to tell vue-i18n-extract about dynamic key
yuetloo Jan 4, 2023
150051a
throw if missing critical information
yuetloo Jan 6, 2023
6051c91
remove const warning
yuetloo Jan 6, 2023
c689129
add token info to factory and funding round
yuetloo Jan 6, 2023
fec277e
add token ABI
yuetloo Jan 6, 2023
5496cb0
wait for round information to load before rendering
yuetloo Jan 6, 2023
a665c47
add tokenInfo to subgraph
yuetloo Jan 6, 2023
4933bf2
add missing mapping for token address
yuetloo Jan 6, 2023
48c1bcf
use etherscan api from hardhat config and throw on missing critical i…
yuetloo Jan 6, 2023
1359413
add script to generate the subgraph uml file
yuetloo Jan 6, 2023
3c8eca8
add script to generate the subgraph uml file
yuetloo Jan 6, 2023
c1f8c01
added nativeTokenInfo to factory and round
yuetloo Jan 6, 2023
349978d
code refracting to read from subgraph instead of contract and update …
yuetloo Jan 6, 2023
35ff7c4
refactor get maci info
yuetloo Jan 7, 2023
a880692
redirect to project lists if cannot show leaderboard view
yuetloo Jan 7, 2023
843090a
subgraph query keys are lowercase
yuetloo Jan 7, 2023
b28e879
more project details from newer rounds
yuetloo Jan 7, 2023
bf6ba69
fix missing translation keys
yuetloo Jan 7, 2023
a07ad5f
remove unused imports and variables
yuetloo Jan 7, 2023
6926701
revert back to local time to match maci timestamp
yuetloo Jan 9, 2023
72f0c30
styling leaderboard for light mode
yuetloo Jan 9, 2023
ca8ebf0
load tally file if not loaded already
yuetloo Jan 9, 2023
af4c2ee
only show image on desktop
yuetloo Jan 10, 2023
b766be2
contract address can be different case if read from subgraph
yuetloo Jan 10, 2023
25606d0
script to merge allocations for cancelled round
yuetloo Jan 10, 2023
71dfbad
refactor code
yuetloo Jan 10, 2023
9518da0
show leaderboard page for cancelled rounds if allocations were issued
yuetloo Jan 10, 2023
8cddc95
show the claim amount even for cancelled rounds
yuetloo Jan 10, 2023
386f9ea
fix eslint warning
yuetloo Jan 10, 2023
849012d
add blog url to round
yuetloo Jan 11, 2023
f4d2d27
use default link color
yuetloo Jan 11, 2023
eb65e8b
cleanup unused environment vars
yuetloo Jan 11, 2023
bc1e81f
add messages and max messages to static round data to avoid showing n…
yuetloo Jan 11, 2023
625974b
Merge branch 'develop' into feat/static-leaderboard
yuetloo Jan 11, 2023
ed97467
set totalSpent from tally file if not available from the contract
yuetloo Jan 11, 2023
1a1f946
get proof file for debugging
yuetloo Jan 12, 2023
97a5b80
export key before running test
yuetloo Jan 12, 2023
ebacd56
disable known host check
yuetloo Jan 12, 2023
3d79860
use openssl to export key
yuetloo Jan 12, 2023
f0fb469
another export
yuetloo Jan 12, 2023
438a159
cleanup test file
yuetloo Jan 12, 2023
e48fec4
try export key again
yuetloo Jan 12, 2023
5c74bb9
revert changes
yuetloo Jan 12, 2023
b584b2b
remove the push trigger on test-e2e.yml
yuetloo Jan 13, 2023
bdd4f28
update round information review url
yuetloo Jan 13, 2023
82deb31
updated leaderboard heading
yuetloo Jan 13, 2023
98a39cf
updated leaderboard notice
yuetloo Jan 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/finalize-round.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
name: Finalize a test round

on: workflow_dispatch
on:
workflow_dispatch:
push:
paths:
- '.github/workflows/finalize-round.yml'

env:
NODE_VERSION: 16.x
Expand Down
3 changes: 0 additions & 3 deletions .github/workflows/test-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ on:
push:
branches:
- 'develop'
pull_request:
paths:
- 'contracts/**'

env:
NODE_VERSION: 16.x
Expand Down
2 changes: 1 addition & 1 deletion codegen.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
overwrite: true
schema: 'http://localhost:8000/subgraphs/name/daodesigner/clrfund'
schema: 'https://api.thegraph.com/subgraphs/name/clrfund/clrfund'
documents: 'vue-app/src/graphql/**/*.graphql'
generates:
vue-app/src/graphql/API.ts:
Expand Down
7 changes: 5 additions & 2 deletions contracts/tasks/auditTally.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,10 @@ async function genProjectRecords({
)
}

if (isRemoval(args._type)) {
if (args._rejected) {
requests[recipientId].removedAt = args._timestamp.toString()
requests[recipientId].state = 'Rejected'
} else if (isRemoval(args._type)) {
requests[recipientId].removedAt = args._timestamp.toString()
requests[recipientId].state = 'Removed'
} else {
Expand Down Expand Up @@ -170,7 +173,7 @@ task('audit-tally', 'Audit the tally result for a round')
.addOptionalParam(
'endBlock',
'Last block to process from the recipient registry',
null,
undefined,
types.int
)
.addOptionalParam(
Expand Down
341 changes: 341 additions & 0 deletions contracts/tasks/fetchRound.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,341 @@
import { task, types } from 'hardhat/config'
import { HardhatConfig } from 'hardhat/types'
import { utils, Contract, BigNumber } 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 { writeToFile } from '../utils/file'
import path from 'path'
import fs from 'fs'

type RoundListEntry = {
network: string
address: string
startTime: number
isFinalized: boolean
}

const toUndefined = () => undefined
const toString = (val: BigNumber) => BigNumber.from(val).toString()
const toZero = () => BigNumber.from(0)

function roundFileName(directory: string, address: string): string {
return path.join(directory, `${address}.json`)
}

function roundListFileName(directory: string): string {
return path.join(directory, 'rounds.json')
}

function getEtherscanApiKey(config: HardhatConfig, 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}`
}

async function updateRoundList(filePath: string, round: RoundListEntry) {
const roundMap = new Map<string, RoundListEntry>()
let json = ''
try {
json = fs.readFileSync(filePath, 'utf8')
} catch {
json = '[]'
}
const previousRounds = JSON.parse(json)
for (let i = 0; i < previousRounds.length; i++) {
const previous = previousRounds[i]
roundMap.set(roundMapKey(previous), previous)
}
roundMap.set(roundMapKey(round), round)

const rounds: RoundListEntry[] = Array.from(roundMap.values())

// sort in ascending start time order
rounds.sort((round1, round2) => round1.startTime - round2.startTime)
writeToFile(filePath, rounds)
}

async function mergeRecipientTally({
round,
roundContract,
recipientRegistry,
projectRecords,
tally,
}: {
round: Round
roundContract: Contract
recipientRegistry: Contract
projectRecords: Record<string, Project>
tally: any
}): Promise<Project[]> {
console.log('Merging projects and tally results...')
const { startTime, endTime, voiceCreditFactor, nativeTokenDecimals } = round

round.totalSpent = round.totalSpent || tally.totalVoiceCredits.spent

const projectAddresses = Object.values(projectRecords).reduce(
(addresses: Record<string, Project>, project) => {
if (project.recipientAddress) {
const lowerCaseAddress = project.recipientAddress.toLowerCase()
addresses[lowerCaseAddress] = project
}
return addresses
},
{}
)

const projectIndices = Object.values(projectRecords).reduce(
(indices: Record<number, Project>, project) => {
if (project.recipientIndex) {
indices[project.recipientIndex] = project
}
return indices
},
{}
)

const projects: Project[] = []
for (let i = 0; i < tally.results.tally.length; i++) {
let address = ''

try {
address = await recipientRegistry.getRecipientAddress(
i,
startTime,
endTime
)
} catch {
// some older recipient registry contract does not have
// the getRecipientAddress function, ignore error
}

const tallyResult = tally.results.tally[i]
const spentVoiceCredits = tally.totalVoiceCreditsPerVoteOption.tally[i]
const formattedDonationAmount = utils.formatUnits(
BigNumber.from(spentVoiceCredits).mul(voiceCreditFactor),
nativeTokenDecimals
)

const allocatedAmount = await roundContract
.getAllocatedAmount(tallyResult, spentVoiceCredits)
.then(toString)

const project = projectIndices[i] || projectAddresses[address.toLowerCase()]
if (project) {
projects.push({
...project,
tallyResult,
spentVoiceCredits,
formattedDonationAmount,
allocatedAmount,
})
}
}

return projects
}

async function getRoundInfo(
roundContract: Contract,
ethers: any
): Promise<Round> {
console.log('Fetching round data...')
const round: any = { address: roundContract.address }
round.nativeTokenAddress = await roundContract.nativeToken()

try {
const token = await ethers.getContractAt('ERC20', round.nativeTokenAddress)
round.nativeTokenDecimals = await token.decimals().catch(toUndefined)
round.nativeTokenSymbol = await token.symbol().catch(toUndefined)
console.log(
'Fetched token data',
round.nativeTokenAddress,
round.nativeTokenSymbol,
round.nativeTokenDecimals
)
} catch (err) {
const errorMessage = err instanceof Error ? err.message : ''
throw new Error(`Failed to fetch token data: ${errorMessage}`)
}

const contributorCount = await roundContract.contributorCount().catch(toZero)
round.contributorCount = contributorCount.toNumber()

const matchingPoolSize = await roundContract.matchingPoolSize().catch(toZero)
round.matchingPoolSize = matchingPoolSize.toString()

round.totalSpent = await roundContract
.totalSpent()
.then(toString)
.catch(toUndefined)

const voiceCreditFactor = await roundContract.voiceCreditFactor()
round.voiceCreditFactor = voiceCreditFactor.toString()

round.isFinalized = await roundContract.isFinalized()
round.isCancelled = await roundContract.isCancelled()
round.tallyHash = await roundContract.tallyHash()

try {
round.maciAddress = await roundContract.maci().catch(toUndefined)
const maci = await ethers.getContractAt('MACI', round.maciAddress)
const startTime = await maci.signUpTimestamp().catch(toZero)
round.startTime = startTime.toNumber()
const signUpDuration = await maci.signUpDurationSeconds().catch(toZero)
const votingDuration = await maci.votingDurationSeconds().catch(toZero)
const endTime = startTime.add(signUpDuration).add(votingDuration)
round.endTime = endTime.toNumber()
round.signUpDuration = signUpDuration.toNumber()
round.votingDuration = votingDuration.toNumber()

const maciTreeDepths = await maci.treeDepths()
const messages = await maci.numMessages()

round.messages = messages.toNumber()
round.maxMessages = 2 ** maciTreeDepths.messageTreeDepth - 1
round.maxRecipients = 5 ** maciTreeDepths.voteOptionTreeDepth - 1
} catch (err) {
const errorMessage = err instanceof Error ? err.message : ''
throw new Error(`Failed to get MACI data ${errorMessage}`)
}

round.userRegistryAddress = await roundContract
.userRegistry()
.catch(toUndefined)

round.recipientRegistryAddress = await roundContract.recipientRegistry()

console.log('Round', round)
return round
}

/**
* Fetch all the round data for static site
*/
task('fetch-round', 'Fetch round data')
.addParam('roundAddress', 'Funding round contract address')
.addParam('outputDir', 'Output directory')
.addOptionalParam(
'startBlock',
'First block to process from the recipient registry contract',
0,
types.int
)
.addOptionalParam(
'endBlock',
'Last block to process from the recipient registry',
undefined,
types.int
)
.addOptionalParam(
'blocksPerBatch',
'Number of blocks of logs to process per batch',
50000,
types.int
)
.setAction(
async (
{ roundAddress, outputDir, startBlock, endBlock, blocksPerBatch },
{ ethers, network, config }
) => {
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)
} catch {
// exit script if failed to create directory
fs.mkdirSync(outputSubDir, { recursive: true })
}

const roundContract = await ethers.getContractAt(
'FundingRound',
roundAddress
)
const round = await getRoundInfo(roundContract, ethers)
const recipientRegistry = new Contract(
round.recipientRegistryAddress,
getRecipientAddressAbi,
ethers.provider
)

const logProcessor = new RecipientRegistryLogProcessor(recipientRegistry)
const logs = await logProcessor.fetchLogs({
recipientRegistry,
startBlock,
endBlock,
blocksPerBatch,
network: network.name,
etherscanApiKey,
})

console.log('Parsing logs...')
const projectRecords = await logProcessor.parseLogs(logs)

let tally: any = undefined
if (round.isFinalized && !round.isCancelled) {
if (!round.tallyHash) {
throw new Error('Missing tallyHash')
}

try {
tally = await Ipfs.fetchJson(round.tallyHash)
} catch (err) {
console.log('Failed to get tally file', round.tallyHash, err)
throw err
}
}

let projects = Object.values(projectRecords)
if (round.isFinalized && !round.isCancelled && projects.length > 0) {
projects = await mergeRecipientTally({
round,
roundContract,
recipientRegistry,
projectRecords,
tally,
})
}

// set totalSpent to 0 if not set
if (!round.totalSpent) {
round.totalSpent = '0'
}

// write to round file
const filename = roundFileName(outputSubDir, round.address)
const roundData: RoundFileContent = {
round,
projects,
tally,
}
writeToFile(filename, roundData)

// update round list
const listFilename = roundListFileName(outputDir)
await updateRoundList(listFilename, {
network: network.name,
address: round.address,
startTime: round.startTime,
isFinalized: round.isFinalized && !round.isCancelled,
})
}
)
2 changes: 2 additions & 0 deletions contracts/tasks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ import './verifyAll'
import './cancelRound'
import './evmIncreaseTime'
import './auditTally'
import './fetchRound'
import './mergeAllocations'
Loading