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

Project page #115

Merged
merged 14 commits into from
Sep 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions contracts/e2e/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,12 +167,14 @@ describe('End-to-end Tests', function () {

// Claim funds
tally.claims = {}
const recipientTreeDepth = (await maci.treeDepths()).voteOptionTreeDepth
for (const recipientIndex of [1, 2]) {
const recipient = recipientIndex === 1 ? recipient1 : recipient2
const recipientAddress = await recipient.getAddress()
const recipientClaimData = getRecipientClaimData(
recipientAddress,
recipientIndex,
recipientTreeDepth,
tally,
)
const claimTx = await fundingRound.connect(recipient).claimFunds(...recipientClaimData)
Expand Down
12 changes: 6 additions & 6 deletions contracts/scripts/claim.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import fs from 'fs'
import { ethers } from '@nomiclabs/buidler'

import MACIArtifact from '../build/contracts/MACI.json'
import { getEventArg } from '../utils/contracts'
import { getRecipientClaimData } from '../utils/maci'

async function main() {
const [,,, recipient1, recipient2] = await ethers.getSigners()
// Finalize the round
const state = JSON.parse(fs.readFileSync('state.json').toString())
const factory = await ethers.getContractAt('FundingRoundFactory', state.factory)
const tally = JSON.parse(fs.readFileSync('tally.json').toString())
const totalSpent = parseInt(tally.totalVoiceCredits.spent)
const totalSpentSalt = tally.totalVoiceCredits.salt
await factory.transferMatchingFunds(totalSpent, totalSpentSalt)
console.log('Round finalized, totals verified.')

const fundingRound = await ethers.getContractAt('FundingRound', state.fundingRound)
const maciAddress = await fundingRound.maci()
const maci = await ethers.getContractAt(MACIArtifact.abi, maciAddress)
const recipientTreeDepth = (await maci.treeDepths()).voteOptionTreeDepth

// Claim funds
for (const recipientIndex of [1, 2]) {
const recipient = recipientIndex === 1 ? recipient1 : recipient2
const recipientClaimData = getRecipientClaimData(
await recipient.getAddress(),
recipientIndex,
recipientTreeDepth,
tally,
)
const fundingRoundAsRecipient = fundingRound.connect(recipient)
Expand Down
8 changes: 8 additions & 0 deletions contracts/scripts/tally.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ async function main() {
yarn maci-cli verify --tally-file tally.json
`)
console.log(verifyCmdOutput)

// Finalize the round
const factory = await ethers.getContractAt('FundingRoundFactory', state.factory)
const tally = JSON.parse(fs.readFileSync('tally.json').toString())
const totalSpent = parseInt(tally.totalVoiceCredits.spent)
const totalSpentSalt = tally.totalVoiceCredits.salt
await factory.transferMatchingFunds(totalSpent, totalSpentSalt)
console.log('Round finalized, totals verified.')
}

main()
Expand Down
6 changes: 3 additions & 3 deletions contracts/utils/maci.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,21 +82,21 @@ export function createMessage(
export function getRecipientClaimData(
recipientAddress: string,
recipientIndex: number,
recipientTreeDepth: number,
tally: any,
): any[] {
const TREE_DEPTH = 2
// Create proof for tally result
const result = tally.results.tally[recipientIndex]
const resultSalt = tally.results.salt
const resultTree = new IncrementalQuinTree(TREE_DEPTH, bigInt(0))
const resultTree = new IncrementalQuinTree(recipientTreeDepth, bigInt(0))
for (const leaf of tally.results.tally) {
resultTree.insert(leaf)
}
const resultProof = resultTree.genMerklePath(recipientIndex)
// Create proof for total amount of spent voice credits
const spent = tally.totalVoiceCreditsPerVoteOption.tally[recipientIndex]
const spentSalt = tally.totalVoiceCreditsPerVoteOption.salt
const spentTree = new IncrementalQuinTree(TREE_DEPTH, bigInt(0))
const spentTree = new IncrementalQuinTree(recipientTreeDepth, bigInt(0))
for (const leaf of tally.totalVoiceCreditsPerVoteOption.tally) {
spentTree.insert(leaf)
}
Expand Down
1 change: 1 addition & 0 deletions vue-app/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module.exports = {
'error',
'never',
],
'@typescript-eslint/no-explicit-any': 'off',
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
},
Expand Down
69 changes: 60 additions & 9 deletions vue-app/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,26 @@
</template>

<script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component'

import Cart from '@/components/Cart.vue'
import Profile from '@/components/Profile.vue'
import { LOAD_ROUND_INFO } from '@/store/action-types'

export default {
@Component({
name: 'clr.fund',
components: {
Cart,
Profile,
},
})
export default class App extends Vue {

created() {
this.$store.dispatch(LOAD_ROUND_INFO)
}

}
</script>

Expand All @@ -43,6 +54,22 @@ body {
html {
background-color: $bg-primary-color;
color: $text-color;
font-family: Inter, sans-serif;
font-size: 14px;
}

a {
color: $text-color;
text-decoration: none;
}

.input {
background-color: $bg-light-color;
border: 2px solid $button-color;
border-radius: 2px;
box-sizing: border-box;
color: $text-color;
padding: 7px;
}

.btn {
Expand All @@ -69,9 +96,7 @@ html {

#app {
display: flex;
font-family: Inter, sans-serif;
font-size: 14px;
height: 100%;
min-height: 100%;
}

#nav-bar {
Expand Down Expand Up @@ -130,24 +155,50 @@ html {
flex-grow: 1;
padding: $content-space;

h1 {
border-bottom: $border;
.content-heading {
display: block;
font-family: 'Glacial Indifference', sans-serif;
font-size: 14px;
font-weight: normal;
letter-spacing: 6px;
margin: 0 0 $content-space;
margin: 0;
padding-bottom: $content-space;
text-transform: uppercase;
}
}

#user-bar {
background-color: $bg-light-color;
display: flex;
flex-direction: column;
flex-shrink: 0;
min-width: 300px;
width: 20%;
}

.loader {
display: block;
width: 40px;
height: 40px;
margin: 20px auto;
}

.loader:after {
content: " ";
display: block;
width: 32px;
height: 32px;
margin: 4px;
border-radius: 50%;
border: 6px solid #fff;
border-color: #fff transparent #fff transparent;
animation: loader 1.2s linear infinite;
}

@keyframes loader {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
23 changes: 23 additions & 0 deletions vue-app/src/api/claims.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { BigNumber, Contract } from 'ethers'

import { FundingRound } from './abi'
import { provider } from './core'

export async function getClaimedAmount(
fundingRoundAddress: string,
recipientAddress: string,
): Promise<BigNumber | null> {
const fundingRound = new Contract(fundingRoundAddress, FundingRound, provider)
// TODO: filter by recipient
const eventFilter = fundingRound.filters.FundsClaimed()
const events = await fundingRound.queryFilter(eventFilter, 0)
for (const event of events) {
if (!event.args) {
continue
}
if (event.args._recipient.toLowerCase() === recipientAddress.toLowerCase()) {
return event.args._amount
}
}
return null
}
3 changes: 3 additions & 0 deletions vue-app/src/api/contributions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { FundingRound } from './abi'
import { provider } from './core'
import { Project } from './projects'

// A size of message batch
export const CART_MAX_SIZE = 10

export interface CartItem extends Project {
amount: number;
}
Expand Down
43 changes: 30 additions & 13 deletions vue-app/src/api/projects.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { Event } from 'ethers'
import { isAddress } from '@ethersproject/address'

import { factory, ipfsGatewayUrl } from './core'

export interface Project {
Expand All @@ -8,22 +11,36 @@ export interface Project {
index: number;
}

function decodeRecipientAdded(event: Event): Project {
const args = event.args as any // eslint-disable-line @typescript-eslint/no-explicit-any
const metadata = JSON.parse(args._metadata)
return {
address: args._fundingAddress,
name: metadata.name,
description: metadata.description,
imageUrl: `${ipfsGatewayUrl}${metadata.imageHash}`,
index: args._index.toNumber(),
}
}

export async function getProjects(): Promise<Project[]> {
const recipientFilter = factory.filters.RecipientAdded()
const events = await factory.queryFilter(recipientFilter, 0)
const projects: Project[] = []
events.forEach(event => {
if (!event.args) {
return
}
const metadata = JSON.parse(event.args._metadata)
projects.push({
address: event.args._fundingAddress,
name: metadata.name,
description: metadata.description,
imageUrl: `${ipfsGatewayUrl}${metadata.imageHash}`,
index: event.args._index.toNumber(),
})
})
for (const event of events) {
projects.push(decodeRecipientAdded(event))
}
return projects
}

export async function getProject(address: string): Promise<Project | null> {
if (!isAddress(address)) {
return null
}
const recipientFilter = factory.filters.RecipientAdded(address)
const events = await factory.queryFilter(recipientFilter, 0)
if (events.length === 1) {
return decodeRecipientAdded(events[0])
}
return null
}
12 changes: 8 additions & 4 deletions vue-app/src/api/round.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { ethers, BigNumber, FixedNumber } from 'ethers'
import { BigNumber, Contract, FixedNumber } from 'ethers'
import { DateTime } from 'luxon'
import { bigInt } from 'maci-crypto'
import { PubKey } from 'maci-domainobjs'

import { FundingRound, ERC20 } from './abi'
import { FundingRound, MACI, ERC20 } from './abi'
import { provider, factory } from './core'

export interface RoundInfo {
fundingRoundAddress: string;
maciAddress: string;
recipientTreeDepth: number;
coordinatorPubKey: PubKey;
nativeTokenAddress: string;
nativeTokenSymbol: string;
Expand All @@ -34,19 +35,21 @@ export async function getRoundInfo(): Promise<RoundInfo | null> {
if (fundingRoundAddress === '0x0000000000000000000000000000000000000000') {
return null
}
const fundingRound = new ethers.Contract(
const fundingRound = new Contract(
fundingRoundAddress,
FundingRound,
provider,
)
const maciAddress = await fundingRound.maci()
const maci = new Contract(maciAddress, MACI, provider)
const recipientTreeDepth = (await maci.treeDepths()).voteOptionTreeDepth
const coordinatorPubKeyRaw = await fundingRound.coordinatorPubKey()
const coordinatorPubKey = new PubKey([
bigInt(coordinatorPubKeyRaw.x),
bigInt(coordinatorPubKeyRaw.y),
])
const nativeTokenAddress = await fundingRound.nativeToken()
const nativeToken = new ethers.Contract(
const nativeToken = new Contract(
nativeTokenAddress,
ERC20,
provider,
Expand Down Expand Up @@ -96,6 +99,7 @@ export async function getRoundInfo(): Promise<RoundInfo | null> {
return {
fundingRoundAddress,
maciAddress,
recipientTreeDepth,
coordinatorPubKey,
nativeTokenAddress,
nativeTokenSymbol,
Expand Down
Loading