From 91e0728618a7f2db8749e09d3c8746393c29ba4d Mon Sep 17 00:00:00 2001 From: Mike Hathaway Date: Thu, 27 Jul 2023 14:15:50 -0400 Subject: [PATCH] update FundingVote entity (#33) * update FundingVote entity * fix todo * update FundingVote entity * fix todo * expand vote unit testing * fix fundingVote flow * fix votingPowerUsed calculation * cleanups * expand test assertions * wip distributionPeriod start screening power tracking * expand fundedSlate entity * add funds available to DistributionPeriod; improve treasury tracking * remove screeningStageVotingPower; rename fundingStage votingPower attributes * delete unused file * use wmul for fundsAvailable * fix FundedSlate bugs --------- Co-authored-by: Mike --- schema.graphql | 12 +- src/grant-fund.ts | 84 ++++--- src/utils/constants.ts | 17 +- src/utils/grants/distribution.ts | 12 +- src/utils/grants/fund.ts | 7 + src/utils/grants/proposal.ts | 4 +- src/utils/grants/voter.ts | 62 ++++- tests/grant-fund.test.ts | 413 ++++++++++++++++++++++++++++++- tests/utils/common.ts | 40 +++ tests/utils/grant-fund-utils.ts | 8 +- 10 files changed, 599 insertions(+), 60 deletions(-) diff --git a/schema.graphql b/schema.graphql index 19c85d9..b7fa6df 100644 --- a/schema.graphql +++ b/schema.graphql @@ -794,8 +794,8 @@ type DistributionPeriod @entity { endBlock: BigInt! # block number the distribution period ends topSlate: FundedSlate # The current top FundedSlate slatesSubmitted: [FundedSlate!]! # FundedSlate[] slates submitted in the distribution period + fundsAvailable: BigDecimal! # Total ajna tokens available for distribution in the distribution period delegationRewardsClaimed: BigDecimal! # Total delegation rewards claimed in the distribution period - fundingVotesCast: BigDecimal! # number of funding votes cast fundingVotePowerUsed: BigDecimal! # Total funding vote power used screeningVotesCast: BigDecimal! # number of screening votes cast votes: [DistributionPeriodVote!]! # Voter info for the distribution period @@ -828,8 +828,8 @@ type DistributionPeriodVote @entity { id: Bytes! # $accountId + '|'' + $distributionId voter: Account! distribution: DistributionPeriod! # uint256 - screeningStageVotingPower: BigDecimal! # uint256 - fundingStageVotingPower: BigDecimal! # uint256 + estimatedInitialFundingStageVotingPowerForCalculatingRewards: BigDecimal! # uint256 + estimatedRemainingFundingStageVotingPowerForCalculatingRewards: BigDecimal! # uint256 screeningVotes: [ScreeningVote!]! # ScreeningVote[] fundingVotes: [FundingVote!]! # FundingVote[] } @@ -848,8 +848,8 @@ type FundingVote @entity { distribution: DistributionPeriod! # distribution period the vote was cast in voter: Account! # actor who cast votes proposal: Proposal! # proposal being voted on - votesCast: BigDecimal! # uint256 # TODO: can this be negative? - votingPowerUsed: BigDecimal! # uint256 + votesCast: BigDecimal! # uint256 + votingPowerUsed: BigDecimal! # uint256 # cost of the incremetnal funding vote to the voter's voting power blockNumber: BigInt! # block number the vote was cast } @@ -857,6 +857,8 @@ type FundedSlate @entity { id: Bytes! # bytes32 hash of slate proposalIds distribution: DistributionPeriod! # distribution period the vote was cast in proposals: [Proposal!]! # uint256[] list of proposalIds + totalTokensRequested: BigDecimal! # total number of tokens requested by all proposals in the slate + totalFundingVotesReceived: BigDecimal! # net funding votes received by all proposals in the slate updateBlock: BigInt! # block number the slate was updated } diff --git a/src/grant-fund.ts b/src/grant-fund.ts index c1f6c86..0c41a9c 100644 --- a/src/grant-fund.ts +++ b/src/grant-fund.ts @@ -1,4 +1,4 @@ -import { Address, Bytes, ethereum, log } from '@graphprotocol/graph-ts' +import { Address, BigInt, Bytes, ethereum } from '@graphprotocol/graph-ts' import { DelegateRewardClaimed as DelegateRewardClaimedEvent, @@ -26,13 +26,14 @@ import { DistributionPeriodVote } from "../generated/schema" -import { ZERO_ADDRESS, ZERO_BD } from './utils/constants' +import { ONE_BI, THREE_PERCENT_BI, ZERO_ADDRESS, ZERO_BD } from './utils/constants' import { addressArrayToBytesArray, addressToBytes, bigIntToBytes, bytesToBigInt, wadToDecimal } from "./utils/convert" -import { getProposalParamsId, getProposalsInSlate, removeProposalFromList } from './utils/grants/proposal' +import { getProposalParamsId, getProposalsInSlate, loadOrCreateProposal, removeProposalFromList } from './utils/grants/proposal' import { getCurrentDistributionId, getCurrentStage, loadOrCreateDistributionPeriod } from './utils/grants/distribution' -import { getFundingStageVotingPower, getFundingVoteId, getScreeningStageVotingPower, getScreeningVoteId, loadOrCreateDistributionPeriodVote } from './utils/grants/voter' -import { loadOrCreateGrantFund } from './utils/grants/fund' +import { getFundingStageVotingPower, getFundingVoteId, getFundingVotingPowerUsed, getScreeningStageVotingPower, getScreeningVoteId, loadOrCreateDistributionPeriodVote } from './utils/grants/voter' +import { getTreasury, loadOrCreateGrantFund } from './utils/grants/fund' import { loadOrCreateAccount } from './utils/account' +import { wmul } from './utils/math' export function handleDelegateRewardClaimed( event: DelegateRewardClaimedEvent @@ -86,8 +87,7 @@ export function handleFundTreasury(event: FundTreasuryEvent): void { // update GrantFund entity const grantFund = loadOrCreateGrantFund(event.address) - // TODO: simply set this to treasuryBalance? - grantFund.treasury = grantFund.treasury.plus(wadToDecimal(event.params.amount)) + grantFund.treasury = wadToDecimal(getTreasury(event.address)) // save entities to the store grantFund.save() @@ -111,17 +111,30 @@ export function handleFundedSlateUpdated(event: FundedSlateUpdatedEvent): void { distributionPeriod.topSlate = event.params.fundedSlateHash // create FundedSlate entity - const fundedSlate = new FundedSlate(distributionId) as FundedSlate + const fundedSlate = new FundedSlate(fundedSlateUpdated.fundedSlateHash_) as FundedSlate fundedSlate.distribution = distributionId fundedSlate.updateBlock = event.block.number // get the list of proposals in the slate - const proposalsInSlate = getProposalsInSlate(event.address, fundedSlateUpdated.distributionId_) - const proposals = fundedSlate.proposals + const proposalsInSlate = getProposalsInSlate(event.address, fundedSlateUpdated.fundedSlateHash_) + const proposals: Bytes[] = [] + let totalTokensRequested = ZERO_BD + let totalFundingVotesReceived = ZERO_BD + for (let i = 0; i < proposalsInSlate.length; i++) { const proposalId = proposalsInSlate[i] + const proposal = loadOrCreateProposal(bigIntToBytes(proposalId)) + + totalTokensRequested = totalTokensRequested.plus(proposal.totalTokensRequested) + totalFundingVotesReceived = totalFundingVotesReceived.plus(proposal.fundingVotesReceived) + + // add proposal information to fundedSlate proposals.push(bigIntToBytes(proposalId)) } + + // record proposal information in fundedSlate entity + fundedSlate.totalTokensRequested = totalTokensRequested + fundedSlate.totalFundingVotesReceived = totalFundingVotesReceived fundedSlate.proposals = proposals // save entities to the store @@ -249,18 +262,13 @@ export function handleDistributionPeriodStarted( distributionPeriod.startBlock = distributionStarted.startBlock distributionPeriod.endBlock = distributionStarted.endBlock - // loop through DistributionPeriodVotes for the current period and update voting power - const votes = distributionPeriod.votes - for (var i=0; i { +export function getProposalsInSlate(grantFundAddress: Address, slateHash: Bytes): Array { const grantFundContract = GrantFund.bind(grantFundAddress) - const getProposalsInSlateResult = grantFundContract.getTopTenProposals(distributionId.toI32()) + const getProposalsInSlateResult = grantFundContract.getFundedProposalSlate(slateHash) return getProposalsInSlateResult } diff --git a/src/utils/grants/voter.ts b/src/utils/grants/voter.ts index 4d8d6cf..8841d5a 100644 --- a/src/utils/grants/voter.ts +++ b/src/utils/grants/voter.ts @@ -1,9 +1,9 @@ -import { Address, BigDecimal, BigInt, Bytes, dataSource } from "@graphprotocol/graph-ts" +import { Address, BigDecimal, BigInt, Bytes, dataSource, log } from "@graphprotocol/graph-ts" -import { DistributionPeriodVote } from "../../../generated/schema" +import { DistributionPeriodVote, FundingVote } from "../../../generated/schema" import { GrantFund } from "../../../generated/GrantFund/GrantFund" -import { ZERO_BD, ZERO_BI } from "../constants" +import { EXP_18_BD, ZERO_BD, ZERO_BI } from "../constants" import { wadToDecimal } from "../convert" import { loadOrCreateDistributionPeriod } from "./distribution" @@ -27,6 +27,58 @@ export function getScreeningVoteId(proposalId: Bytes, voterId: Bytes, logIndex: .concat(Bytes.fromUTF8(logIndex.toString())) } +export function getFundingVotesByProposalId(distributionPeriodVote: DistributionPeriodVote, proposalId: Bytes): Bytes[] { + const filteredVotes: Bytes[] = []; + const fundingVotes = distributionPeriodVote.fundingVotes; + + log.info("getFundingVotesByProposalId: {} {}", [distributionPeriodVote.fundingVotes.length.toString(), proposalId.toString()]) + + for (let i = 0; i < fundingVotes.length; i++) { + const proposal = loadOrCreateFundingVote(fundingVotes[i]).proposal; + if (proposal.equals(proposalId)) { + filteredVotes.push(fundingVotes[i]); + } + } + return filteredVotes; +} + +// calculate the amount of funding voting power used on an individual FundingVote +export function getFundingVotingPowerUsed(distributionPeriodVote: DistributionPeriodVote, proposalId: Bytes): BigDecimal { + const votes = getFundingVotesByProposalId(distributionPeriodVote, proposalId); + + // accumulate the squared votes from each separate vote on the proposal + const squaredAmount: BigDecimal[] = []; + for (let i = 0; i < votes.length; i++) { + const vote = loadOrCreateFundingVote(votes[i]); + // convert back from wad before squaring + const decimalVotesCast = vote.votesCast.times(EXP_18_BD) + squaredAmount.push(decimalVotesCast.times(decimalVotesCast).div(EXP_18_BD)); + } + + // sum the squared amounts + let sum = ZERO_BD; + for (let i = 0; i < squaredAmount.length; i++) { + sum = sum.plus(squaredAmount[i]); + } + + return sum; +} + +export function loadOrCreateFundingVote(fundingVoteId: Bytes): FundingVote { + let fundingVote = FundingVote.load(fundingVoteId) + if (fundingVote == null) { + // create new fundingVote if one hasn't already been stored + fundingVote = new FundingVote(fundingVoteId) as FundingVote + fundingVote.distribution = Bytes.empty() + fundingVote.voter = Bytes.empty() + fundingVote.proposal = Bytes.empty() + fundingVote.votesCast = ZERO_BD + fundingVote.votingPowerUsed = ZERO_BD + fundingVote.blockNumber = ZERO_BI + } + return fundingVote +} + export function loadOrCreateDistributionPeriodVote(distributionPeriodId: Bytes, voterId: Bytes): DistributionPeriodVote { const distributionPeriodVotesId = getDistributionPeriodVoteId(distributionPeriodId, voterId) let distributionPeriodVotes = DistributionPeriodVote.load(distributionPeriodVotesId) @@ -35,8 +87,8 @@ export function loadOrCreateDistributionPeriodVote(distributionPeriodId: Bytes, distributionPeriodVotes = new DistributionPeriodVote(distributionPeriodVotesId) as DistributionPeriodVote distributionPeriodVotes.voter = voterId distributionPeriodVotes.distribution = distributionPeriodId - distributionPeriodVotes.screeningStageVotingPower = ZERO_BD - distributionPeriodVotes.fundingStageVotingPower = ZERO_BD + distributionPeriodVotes.estimatedInitialFundingStageVotingPowerForCalculatingRewards = ZERO_BD + distributionPeriodVotes.estimatedRemainingFundingStageVotingPowerForCalculatingRewards = ZERO_BD distributionPeriodVotes.screeningVotes = [] distributionPeriodVotes.fundingVotes = [] diff --git a/tests/grant-fund.test.ts b/tests/grant-fund.test.ts index 5cecae9..0660d1e 100644 --- a/tests/grant-fund.test.ts +++ b/tests/grant-fund.test.ts @@ -5,6 +5,7 @@ import { clearStore, afterEach, log, + logStore, } from "matchstick-as/assembly/index"; import { Address, BigInt, Bytes, dataSource } from "@graphprotocol/graph-ts"; import { @@ -13,6 +14,8 @@ import { handleProposalCreated, handleProposalExecuted, handleDistributionPeriodStarted, + handleVoteCast, + handleFundedSlateUpdated, } from "../src/grant-fund"; import { createDelegateRewardClaimedEvent, @@ -20,16 +23,20 @@ import { createProposalCreatedEvent, createProposalExecutedEvent, createDistributionPeriodStartedEvent, + createVoteCastEvent, + createFundedSlateUpdatedEvent, } from "./utils/grant-fund-utils"; import { DISTRIBUTION_PERIOD_LENGTH, ONE_BI, ONE_WAD_BI, + SCREENING_PERIOD_LENGTH, ZERO_BD, ZERO_BI, } from "../src/utils/constants"; -import { bigIntToBytes, wadToDecimal } from "../src/utils/convert"; -import { mockGetDistributionId } from "./utils/common"; +import { addressToBytes, bigIntToBytes, decimalToWad, wadToDecimal } from "../src/utils/convert"; +import { mockGetDistributionId, mockGetFundedProposalSlate, mockGetTreasury, mockGetVotesFunding, mockGetVotesScreening } from "./utils/common"; +import { getDistributionPeriodVoteId, getFundingVoteId, getScreeningVoteId } from "../src/utils/grants/voter"; // Tests structure (matchstick-as >=0.5.0) // https://thegraph.com/docs/en/developer/matchstick/#tests-structure-0-5-0 @@ -89,6 +96,9 @@ describe("Grant Fund assertions", () => { const distributionId = ONE_BI; const startBlock = ONE_BI; const endBlock = startBlock.plus(DISTRIBUTION_PERIOD_LENGTH); + const grantFundAddress = Address.fromString("0xa16081f360e3847006db660bae1c6d1b2e17ec2a") + + mockGetTreasury(grantFundAddress, ONE_WAD_BI); const newDistributionPeriodStartedEvent = createDistributionPeriodStartedEvent( distributionId, @@ -143,6 +153,9 @@ describe("Grant Fund assertions", () => { // mock parameters const amount = ONE_WAD_BI; const treasuryBalance = ONE_WAD_BI; + const grantFundAddress = Address.fromString("0xa16081f360e3847006db660bae1c6d1b2e17ec2a") + + mockGetTreasury(grantFundAddress, treasuryBalance) const newFundTreasuryEvent = createFundTreasuryEvent( amount, @@ -192,6 +205,8 @@ describe("Grant Fund assertions", () => { endBlock ); newDistributionPeriodStartedEvent.address = grantFundAddress + mockGetTreasury(grantFundAddress, ONE_WAD_BI); + handleDistributionPeriodStarted(newDistributionPeriodStartedEvent); mockGetDistributionId(grantFundAddress, distributionId); @@ -254,6 +269,9 @@ describe("Grant Fund assertions", () => { endBlock ); newDistributionPeriodStartedEvent.address = grantFundAddress + + mockGetTreasury(grantFundAddress, ONE_WAD_BI); + handleDistributionPeriodStarted(newDistributionPeriodStartedEvent); mockGetDistributionId(grantFundAddress, distributionId); @@ -294,5 +312,394 @@ describe("Grant Fund assertions", () => { assert.entityCount("ProposalExecuted", 1); }); - test("FundedSlateUpdated", () => {}); + test("ScreeningVote", () => { + /***********************/ + /*** Submit Proposal ***/ + /***********************/ + + // mock parameters + const ajnaTokenAddress = Address.fromString("0x0000000000000000000000000000000000000035"); + const grantFundAddress = Address.fromString("0xa16081f360e3847006db660bae1c6d1b2e17ec2a") + const proposalId = BigInt.fromI32(234); + const proposer = Address.fromString( + "0x0000000000000000000000000000000000000025" + ); + const targets = [ajnaTokenAddress, ajnaTokenAddress]; + const values = [ZERO_BI, ZERO_BI]; + const signatures = [ + "transfer(address,uint256)", + "transfer(address,uint256)", + ]; + const calldatas = [ + Bytes.fromHexString("0x000000"), + Bytes.fromHexString("0x000000"), + ]; + const distributionId = ONE_BI; + const startBlock = ONE_BI; + const endBlock = startBlock.plus(DISTRIBUTION_PERIOD_LENGTH); + const description = "test proposal"; + + // start distribution period + // mock GrantFund contract calls + const newDistributionPeriodStartedEvent = createDistributionPeriodStartedEvent( + distributionId, + startBlock, + endBlock + ); + newDistributionPeriodStartedEvent.address = grantFundAddress + handleDistributionPeriodStarted(newDistributionPeriodStartedEvent); + mockGetDistributionId(grantFundAddress, distributionId); + + // submit proposal + // create mock event + const newProposalCreatedEvent = createProposalCreatedEvent( + proposalId, + proposer, + targets, + values, + signatures, + calldatas, + startBlock, + endBlock, + description + ); + newProposalCreatedEvent.address = grantFundAddress + handleProposalCreated(newProposalCreatedEvent); + + /*******************************/ + /*** Screening Vote Proposal ***/ + /*******************************/ + + // mock parameters + const voter = Address.fromString("0x0000000000000000000000000000000000000050"); + const votesCast = BigInt.fromI32(234); + const reason = "" + + // mock contract calls + mockGetVotesScreening(grantFundAddress, distributionId, voter, votesCast); + + const screeningVoteCastEvent = createVoteCastEvent(voter, proposalId, 1, votesCast, reason, startBlock, BigInt.fromI32(1)); + handleVoteCast(screeningVoteCastEvent); + + /********************/ + /*** Assert State ***/ + /********************/ + + // check GrantFund attributes + assert.entityCount("GrantFund", 1); + + // check Proposal attributes + assert.entityCount("Proposal", 1); + + // check Proposal attributes + assert.entityCount("DistributionPeriodVote", 1); + + assert.entityCount("ScreeningVote", 1); + + const distributionPeriodVoteId = getDistributionPeriodVoteId(bigIntToBytes(distributionId), addressToBytes(voter)); + const screeningVoteId = getScreeningVoteId(bigIntToBytes(proposalId), addressToBytes(voter), BigInt.fromI32(1)); + + assert.fieldEquals( + "DistributionPeriodVote", + `${distributionPeriodVoteId.toHexString()}`, + "distribution", + `${bigIntToBytes(distributionId).toHexString()}` + ); + + assert.fieldEquals( + "ScreeningVote", + `${screeningVoteId.toHexString()}`, + "votesCast", + `${wadToDecimal(votesCast)}` + ); + }); + + test("FundingVote", () => { + /***********************/ + /*** Submit Proposal ***/ + /***********************/ + + // mock parameters + const ajnaTokenAddress = Address.fromString("0x0000000000000000000000000000000000000035"); + const grantFundAddress = Address.fromString("0xa16081f360e3847006db660bae1c6d1b2e17ec2a") + const proposalId = BigInt.fromI32(234); + const proposer = Address.fromString( + "0x0000000000000000000000000000000000000025" + ); + const targets = [ajnaTokenAddress, ajnaTokenAddress]; + const values = [ZERO_BI, ZERO_BI]; + const signatures = [ + "transfer(address,uint256)", + "transfer(address,uint256)", + ]; + const calldatas = [ + Bytes.fromHexString("0x000000"), + Bytes.fromHexString("0x000000"), + ]; + const distributionId = ONE_BI; + const startBlock = ONE_BI; + const endBlock = startBlock.plus(DISTRIBUTION_PERIOD_LENGTH); + const description = "test proposal"; + + // start distribution period + // mock GrantFund contract calls + const newDistributionPeriodStartedEvent = createDistributionPeriodStartedEvent( + distributionId, + startBlock, + endBlock + ); + newDistributionPeriodStartedEvent.address = grantFundAddress + handleDistributionPeriodStarted(newDistributionPeriodStartedEvent); + mockGetDistributionId(grantFundAddress, distributionId); + + // submit proposal + // create mock event + const newProposalCreatedEvent = createProposalCreatedEvent( + proposalId, + proposer, + targets, + values, + signatures, + calldatas, + startBlock, + endBlock, + description + ); + newProposalCreatedEvent.address = grantFundAddress + handleProposalCreated(newProposalCreatedEvent); + + /*******************************/ + /*** Screening Vote Proposal ***/ + /*******************************/ + + // mock parameters + const voter = Address.fromString("0x0000000000000000000000000000000000000050"); + let votesCast = BigInt.fromI32(234); + const reason = "" + + // mock contract calls + mockGetVotesScreening(grantFundAddress, distributionId, voter, votesCast); + + const screeningVoteCastEvent = createVoteCastEvent(voter, proposalId, 1, votesCast, reason, startBlock, BigInt.fromI32(1)); + handleVoteCast(screeningVoteCastEvent); + + /*****************************/ + /*** Funding Vote Proposal ***/ + /*****************************/ + + // TODO: need to convert back from WAD + const fundingVotingPower = votesCast.times(votesCast); + + mockGetVotesFunding(grantFundAddress, distributionId, voter, fundingVotingPower); + + votesCast = BigInt.fromI32(-234); + const fundingVoteCastEvent = createVoteCastEvent(voter, proposalId, 0, votesCast, reason, startBlock.plus(SCREENING_PERIOD_LENGTH).plus(BigInt.fromI32(1)), BigInt.fromI32(2)); + handleVoteCast(fundingVoteCastEvent); + + /********************/ + /*** Assert State ***/ + /********************/ + + // check GrantFund attributes + assert.entityCount("GrantFund", 1); + + // check Proposal attributes + assert.entityCount("Proposal", 1); + + assert.entityCount("DistributionPeriod", 1); + assert.entityCount("DistributionPeriodVote", 1); + assert.entityCount("FundingVote", 1); + assert.entityCount("ScreeningVote", 1); + assert.entityCount("VoteCast", 2); + + const distributionPeriodVoteId = getDistributionPeriodVoteId(bigIntToBytes(distributionId), addressToBytes(voter)); + const fundingVoteId = getFundingVoteId(bigIntToBytes(proposalId), addressToBytes(voter), BigInt.fromI32(2)); + const screeningVoteId = getScreeningVoteId(bigIntToBytes(proposalId), addressToBytes(voter), BigInt.fromI32(1)); + const expectedDistributionId = bigIntToBytes(distributionId).toHexString(); + const expectedVotingPowerUsed = wadToDecimal(votesCast.times(votesCast)); + + assert.fieldEquals( + "DistributionPeriodVote", + `${distributionPeriodVoteId.toHexString()}`, + "distribution", + `${expectedDistributionId}` + ); + + // access ScreeningVote entity and attributes + assert.fieldEquals( + "ScreeningVote", + `${screeningVoteId.toHexString()}`, + "votesCast", + `${wadToDecimal(votesCast.times(BigInt.fromI32(-1)))}` + ); + + // check DistributionPeriodVote attributes + assert.fieldEquals( + "DistributionPeriodVote", + `${distributionPeriodVoteId.toHexString()}`, + "estimatedInitialFundingStageVotingPowerForCalculatingRewards", + `${expectedVotingPowerUsed}` + ); + + assert.fieldEquals( + "DistributionPeriodVote", + `${distributionPeriodVoteId.toHexString()}`, + "estimatedRemainingFundingStageVotingPowerForCalculatingRewards", + `${0}` + ); + + // check FundingVote attributes + assert.fieldEquals( + "FundingVote", + `${fundingVoteId.toHexString()}`, + "distribution", + `${expectedDistributionId}` + ); + assert.fieldEquals( + "FundingVote", + `${fundingVoteId.toHexString()}`, + "voter", + `${voter.toHexString()}` + ); + assert.fieldEquals( + "FundingVote", + `${fundingVoteId.toHexString()}`, + "votesCast", + `${wadToDecimal(votesCast)}` + ); + assert.fieldEquals( + "FundingVote", + `${fundingVoteId.toHexString()}`, + "votingPowerUsed", + `${expectedVotingPowerUsed}` + ); + + // check DistributionPeriod attributes + assert.fieldEquals( + "DistributionPeriod", + `${expectedDistributionId}`, + "startBlock", + `${startBlock}` + ); + // check DistributionPeriod attributes + assert.fieldEquals( + "DistributionPeriod", + `${expectedDistributionId}`, + "fundingVotePowerUsed", + `${expectedVotingPowerUsed}` + ); + + }); + + test("FundedSlateUpdated", () => { + + /***********************/ + /*** Submit Proposal ***/ + /***********************/ + + // mock parameters + const ajnaTokenAddress = Address.fromString("0x0000000000000000000000000000000000000035"); + const grantFundAddress = Address.fromString("0xa16081f360e3847006db660bae1c6d1b2e17ec2a") + const proposalId = BigInt.fromI32(234); + const proposer = Address.fromString( + "0x0000000000000000000000000000000000000025" + ); + const targets = [ajnaTokenAddress, ajnaTokenAddress]; + const values = [ZERO_BI, ZERO_BI]; + const signatures = [ + "transfer(address,uint256)", + "transfer(address,uint256)", + ]; + const calldatas = [ + Bytes.fromHexString("0x000000"), + Bytes.fromHexString("0x000000"), + ]; + const distributionId = ONE_BI; + const startBlock = ONE_BI; + const endBlock = startBlock.plus(DISTRIBUTION_PERIOD_LENGTH); + const description = "test proposal"; + + // start distribution period + // mock GrantFund contract calls + const newDistributionPeriodStartedEvent = createDistributionPeriodStartedEvent( + distributionId, + startBlock, + endBlock + ); + newDistributionPeriodStartedEvent.address = grantFundAddress + handleDistributionPeriodStarted(newDistributionPeriodStartedEvent); + mockGetDistributionId(grantFundAddress, distributionId); + + // submit proposal + // create mock event + const newProposalCreatedEvent = createProposalCreatedEvent( + proposalId, + proposer, + targets, + values, + signatures, + calldatas, + startBlock, + endBlock, + description + ); + newProposalCreatedEvent.address = grantFundAddress + handleProposalCreated(newProposalCreatedEvent); + + /*****************************/ + /*** Funding Vote Proposal ***/ + /*****************************/ + + // mock parameters + const voter = Address.fromString("0x0000000000000000000000000000000000000050"); + let votesCast = BigInt.fromI32(-234); + const reason = "" + + // TODO: need to convert back from WAD + const fundingVotingPower = votesCast.times(votesCast); + + mockGetVotesFunding(grantFundAddress, distributionId, voter, fundingVotingPower); + + votesCast = BigInt.fromI32(-234); + const fundingVoteCastEvent = createVoteCastEvent(voter, proposalId, 0, votesCast, reason, startBlock.plus(SCREENING_PERIOD_LENGTH).plus(BigInt.fromI32(1)), BigInt.fromI32(1)); + handleVoteCast(fundingVoteCastEvent); + + /********************/ + /*** Update Slate ***/ + /********************/ + + const fundedProposalSlate = [proposalId] + + // TODO: need to determine how to best hash the proposalId array + const fundedSlateHash = Bytes.fromHexString("0x000010") + + mockGetFundedProposalSlate(grantFundAddress, fundedSlateHash, fundedProposalSlate); + + const updateSlateEvent = createFundedSlateUpdatedEvent(distributionId, fundedSlateHash) + handleFundedSlateUpdated(updateSlateEvent); + + /********************/ + /*** Assert State ***/ + /********************/ + + // check GrantFund attributes + assert.entityCount("GrantFund", 1); + + // check Proposal attributes + assert.entityCount("Proposal", 1); + + assert.entityCount("DistributionPeriod", 1); + assert.entityCount("FundedSlate", 1); + + logStore(); + + // // check FundedSlate attributes + // assert.fieldEquals( + // "FundedSlate", + // `${fundedSlateHash.toHexString()}`, + // "totalFundingVotesReceived", + // `${votesCast}` + // ); + + }); }); diff --git a/tests/utils/common.ts b/tests/utils/common.ts index b96f371..336163a 100644 --- a/tests/utils/common.ts +++ b/tests/utils/common.ts @@ -259,6 +259,46 @@ export function mockGetDistributionId(grantFund: Address, expectedDistributionId ]) } +export function mockGetVotesScreening(grantFund: Address, distributionId: BigInt, voter: Address, expectedVotes: BigInt): void { + createMockedFunction(grantFund, 'getVotesScreening', 'getVotesScreening(uint24,address):(uint256)') + .withArgs([ + ethereum.Value.fromUnsignedBigInt(distributionId), + ethereum.Value.fromAddress(voter) + ]) + .returns([ + ethereum.Value.fromUnsignedBigInt(expectedVotes), + ]) +} + +export function mockGetVotesFunding(grantFund: Address, distributionId: BigInt, voter: Address, expectedVotes: BigInt): void { + createMockedFunction(grantFund, 'getVotesFunding', 'getVotesFunding(uint24,address):(uint256)') + .withArgs([ + ethereum.Value.fromUnsignedBigInt(distributionId), + ethereum.Value.fromAddress(voter) + ]) + .returns([ + ethereum.Value.fromUnsignedBigInt(expectedVotes), + ]) +} + +export function mockGetTreasury(grantFund: Address, expectedTreasury: BigInt): void { + createMockedFunction(grantFund, 'treasury', 'treasury():(uint256)') + .withArgs([]) + .returns([ + ethereum.Value.fromUnsignedBigInt(expectedTreasury), + ]) +} + +export function mockGetFundedProposalSlate(grantFund: Address, slateHash: Bytes, expectedProposals: Array): void { + createMockedFunction(grantFund, 'getFundedProposalSlate', 'getFundedProposalSlate(bytes32):(uint256[])') + .withArgs([ + ethereum.Value.fromFixedBytes(slateHash) + ]) + .returns([ + ethereum.Value.fromUnsignedBigIntArray(expectedProposals), + ]) +} + /*******************************/ /*** Position Mock Functions ***/ /*******************************/ diff --git a/tests/utils/grant-fund-utils.ts b/tests/utils/grant-fund-utils.ts index aeaa0c8..10502da 100644 --- a/tests/utils/grant-fund-utils.ts +++ b/tests/utils/grant-fund-utils.ts @@ -210,7 +210,9 @@ export function createVoteCastEvent( proposalId: BigInt, support: i32, weight: BigInt, - reason: string + reason: string, + blockNumber: BigInt, + logIndex: BigInt ): VoteCast { let voteCastEvent = changetype(newMockEvent()) @@ -238,5 +240,9 @@ export function createVoteCastEvent( new ethereum.EventParam("reason", ethereum.Value.fromString(reason)) ) + // override event logIndex as well to multiple voting in single test as well as moving between stages + voteCastEvent.block.number = blockNumber + voteCastEvent.logIndex = logIndex + return voteCastEvent }