From 5e4729e94eee25b60e32d4c4e6d77e45b59337d7 Mon Sep 17 00:00:00 2001 From: Ed Noepel Date: Thu, 20 Jul 2023 16:54:38 -0400 Subject: [PATCH 1/8] started work on tracking voting delegation --- abis/AjnaToken.json | 366 ++++++++++++++++++++++++++++++++++++++ add-commands.txt | 3 + networks.json | 11 ++ schema.graphql | 27 ++- src/ajna-token.ts | 44 +++++ src/grant-fund.ts | 2 + src/utils/grants/voter.ts | 2 +- subgraph.yaml | 22 +++ tests/ajna-token.test.ts | 11 ++ tests/utils/ajna-token.ts | 66 +++++++ 10 files changed, 551 insertions(+), 3 deletions(-) create mode 100644 abis/AjnaToken.json create mode 100644 src/ajna-token.ts create mode 100644 tests/ajna-token.test.ts create mode 100644 tests/utils/ajna-token.ts diff --git a/abis/AjnaToken.json b/abis/AjnaToken.json new file mode 100644 index 0000000..ce4a58c --- /dev/null +++ b/abis/AjnaToken.json @@ -0,0 +1,366 @@ +[ + { + "inputs": [ + { "internalType": "address", "name": "tokenReceiver_", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "AjnaTokenApproval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "delegator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "fromDelegate", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "toDelegate", + "type": "address" + } + ], + "name": "DelegateChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "delegate", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "previousBalance", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newBalance", + "type": "uint256" + } + ], + "name": "DelegateVotesChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "AjnaTokenTransfer", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" } + ], + "name": "allowance", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "approve", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "balanceOf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "burnFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" }, + { "internalType": "uint32", "name": "pos", "type": "uint32" } + ], + "name": "checkpoints", + "outputs": [ + { + "components": [ + { "internalType": "uint32", "name": "fromBlock", "type": "uint32" }, + { "internalType": "uint224", "name": "votes", "type": "uint224" } + ], + "internalType": "struct ERC20Votes.Checkpoint", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "delegatee", "type": "address" } + ], + "name": "delegate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "delegatee", "type": "address" }, + { "internalType": "uint256", "name": "nonce", "type": "uint256" }, + { "internalType": "uint256", "name": "expiry", "type": "uint256" }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "name": "delegateBySig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "delegates", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "blockNumber", "type": "uint256" } + ], + "name": "getPastTotalSupply", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" }, + { "internalType": "uint256", "name": "blockNumber", "type": "uint256" } + ], + "name": "getPastVotes", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "getVotes", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "addedValue", "type": "uint256" } + ], + "name": "increaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" } + ], + "name": "nonces", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "numCheckpoints", + "outputs": [{ "internalType": "uint32", "name": "", "type": "uint32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "value", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transfer", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "from", "type": "address" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transferFrom", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "from_", "type": "address" }, + { "internalType": "address", "name": "to_", "type": "address" }, + { "internalType": "address", "name": "spender_", "type": "address" }, + { "internalType": "uint256", "name": "value_", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline_", "type": "uint256" }, + { "internalType": "uint8", "name": "v_", "type": "uint8" }, + { "internalType": "bytes32", "name": "r_", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s_", "type": "bytes32" } + ], + "name": "transferFromWithPermit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/add-commands.txt b/add-commands.txt index 0904d7e..f57635c 100644 --- a/add-commands.txt +++ b/add-commands.txt @@ -15,3 +15,6 @@ graph add 0xEd6890d748e62ddbb3f80e7256Deeb2fBb853476 --abi ../contracts/forge_ou # Goerli GrantFund graph add 0xaeB91e664A49829FaBf06BE35d4447938d83A271 --abi ../ecosystem-coordination/out/GrantFund.sol/GrantFund.json --contract-name GrantFund + +# Goerli AjnaToken +graph add 0xaadebCF61AA7Da0573b524DE57c67aDa797D46c5 --abi ../ecosystem-coordination/out/AjnaToken.sol/AjnaToken.json --contract-name AjnaToken \ No newline at end of file diff --git a/networks.json b/networks.json index 77f82b3..d9b3320 100644 --- a/networks.json +++ b/networks.json @@ -19,6 +19,10 @@ "GrantFund": { "address": "0x0000000000000000000000000000000000000000", "startBlock": 17622995 + }, + "AjnaToken": { + "address": "0x9a96ec9B57Fb64FbC60B423d1f4da7691Bd35079", + "startBlock": 17622995 } }, "goerli": { @@ -41,6 +45,10 @@ "GrantFund": { "address": "0x881b4dFF6C72babA6f5eA60f34A61410c1EA1ec2", "startBlock": 9297080 + }, + "AjnaToken": { + "address": "0xaadebCF61AA7Da0573b524DE57c67aDa797D46c5", + "startBlock": 9297080 } }, "ganache": { @@ -63,6 +71,9 @@ "GrantFund": { "address": "0xE340B87CEd1af1AbE1CE8D617c84B7f168e3b18b", "startBlock": 0 + }, + "AjnaToken": { + "address": "0xaadebCF61AA7Da0573b524DE57c67aDa797D46c5" } } } \ No newline at end of file diff --git a/schema.graphql b/schema.graphql index 0820059..807a43f 100644 --- a/schema.graphql +++ b/schema.graphql @@ -189,6 +189,9 @@ type Account @entity { # reserve auctions which the account has interacted in reserveAuctions: [ReserveAuction!]! + # voting delegation + delegate: Voter + # total number of transactions sent by the account txCount: BigInt! } @@ -815,6 +818,7 @@ type Voter @entity { proposalsCreated: [Proposal!]! # Proposal[] proposalsExecuted: [Proposal!]! # Proposal[] distributionPeriodVotes: [DistributionPeriodVote!]! # DistributionPeriodVote[] + rewardsClaimed: BigDecimal! } # TODO: record voting power used? @@ -857,12 +861,31 @@ type FundedSlate @entity { # GRANT FUND EVENTS # # # # # # # # # # # # -# TODO: should we be able to track the distribution period for which the rewards have accrued? +type DelegateChanged @entity(immutable: true) { + id: Bytes! + delegator: Bytes! # address + fromDelegate: Bytes! # address + toDelegate: Bytes! # address + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +type DelegateVotesChanged @entity(immutable: true) { + id: Bytes! + delegate: Bytes! # address + previousBalance: BigInt! # uint256 + newBalance: BigInt! # uint256 + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + type DelegateRewardClaimed @entity(immutable: true) { id: Bytes! delegateeAddress_: Bytes! # address distribution: DistributionPeriod! # distribution period the rewards were claimed in - rewardClaimed_: BigInt! # uint256 + rewardClaimed_: BigInt! # uint256 TODO: remove trailing underscore blockNumber: BigInt! blockTimestamp: BigInt! transactionHash: Bytes! diff --git a/src/ajna-token.ts b/src/ajna-token.ts new file mode 100644 index 0000000..6f48380 --- /dev/null +++ b/src/ajna-token.ts @@ -0,0 +1,44 @@ +import { + DelegateChanged as DelegateChangedEvent, + DelegateVotesChanged as DelegateVotesChangedEvent, +} from "../generated/AjnaToken/AjnaToken" +import { + DelegateChanged, + DelegateVotesChanged, +} from "../generated/schema" + +export function handleDelegateChanged(event: DelegateChangedEvent): void { + let entity = new DelegateChanged( + event.transaction.hash.concatI32(event.logIndex.toI32()) + ) + entity.delegator = event.params.delegator + entity.fromDelegate = event.params.fromDelegate + entity.toDelegate = event.params.toDelegate + + entity.blockNumber = event.block.number + entity.blockTimestamp = event.block.timestamp + entity.transactionHash = event.transaction.hash + + // TODO: update Account.delegate to point to Voter + + entity.save() +} + +export function handleDelegateVotesChanged( + event: DelegateVotesChangedEvent +): void { + let entity = new DelegateVotesChanged( + event.transaction.hash.concatI32(event.logIndex.toI32()) + ) + entity.delegate = event.params.delegate + entity.previousBalance = event.params.previousBalance + entity.newBalance = event.params.newBalance + + entity.blockNumber = event.block.number + entity.blockTimestamp = event.block.timestamp + entity.transactionHash = event.transaction.hash + + // TODO: update Voter entity + + entity.save() +} diff --git a/src/grant-fund.ts b/src/grant-fund.ts index b6e119a..6bc313a 100644 --- a/src/grant-fund.ts +++ b/src/grant-fund.ts @@ -59,6 +59,8 @@ export function handleDelegateRewardClaimed( delegateRewardClaimed.distribution = distributionId + // TODO: update Voter.rewardsClaimed + // save entities to the store grantFund.save() delegateRewardClaimed.save() diff --git a/src/utils/grants/voter.ts b/src/utils/grants/voter.ts index 3ee6570..de61e60 100644 --- a/src/utils/grants/voter.ts +++ b/src/utils/grants/voter.ts @@ -31,7 +31,7 @@ export function loadOrCreateVoter(voterId: Bytes): Voter { if (voter == null) { // create new voter if one hasn't already been stored voter = new Voter(voterId) as Voter - + voter.rewardsClaimed = ZERO_BD } return voter } diff --git a/subgraph.yaml b/subgraph.yaml index d917407..31005fd 100644 --- a/subgraph.yaml +++ b/subgraph.yaml @@ -170,6 +170,28 @@ dataSources: handler: handleVoteCast file: ./src/grant-fund.ts network: ganache + - kind: ethereum + name: AjnaToken + network: ganache + source: + address: "0xaadebCF61AA7Da0573b524DE57c67aDa797D46c5" + abi: AjnaToken + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - DelegateChanged + - DelegateVotesChanged + abis: + - name: AjnaToken + file: ./abis/AjnaToken.json + eventHandlers: + - event: DelegateChanged(indexed address,indexed address,indexed address) + handler: handleDelegateChanged + - event: DelegateVotesChanged(indexed address,uint256,uint256) + handler: handleDelegateVotesChanged + file: ./src/ajna-token.ts templates: - kind: ethereum name: ERC20Pool diff --git a/tests/ajna-token.test.ts b/tests/ajna-token.test.ts new file mode 100644 index 0000000..ed9208a --- /dev/null +++ b/tests/ajna-token.test.ts @@ -0,0 +1,11 @@ +import { + assert, + describe, + test, + clearStore, + beforeAll, + afterAll +} from "matchstick-as/assembly/index" +import { Address, BigInt } from "@graphprotocol/graph-ts" + +// TODO: implement tests \ No newline at end of file diff --git a/tests/utils/ajna-token.ts b/tests/utils/ajna-token.ts new file mode 100644 index 0000000..057a41b --- /dev/null +++ b/tests/utils/ajna-token.ts @@ -0,0 +1,66 @@ +import { newMockEvent } from "matchstick-as" +import { ethereum, Address, BigInt } from "@graphprotocol/graph-ts" +import { + AjnaTokenApproval, + DelegateChanged, + DelegateVotesChanged, + AjnaTokenTransfer +} from "../../generated/AjnaToken/AjnaToken" + +export function createDelegateChangedEvent( + delegator: Address, + fromDelegate: Address, + toDelegate: Address +): DelegateChanged { + let delegateChangedEvent = changetype(newMockEvent()) + + delegateChangedEvent.parameters = new Array() + + delegateChangedEvent.parameters.push( + new ethereum.EventParam("delegator", ethereum.Value.fromAddress(delegator)) + ) + delegateChangedEvent.parameters.push( + new ethereum.EventParam( + "fromDelegate", + ethereum.Value.fromAddress(fromDelegate) + ) + ) + delegateChangedEvent.parameters.push( + new ethereum.EventParam( + "toDelegate", + ethereum.Value.fromAddress(toDelegate) + ) + ) + + return delegateChangedEvent +} + +export function createDelegateVotesChangedEvent( + delegate: Address, + previousBalance: BigInt, + newBalance: BigInt +): DelegateVotesChanged { + let delegateVotesChangedEvent = changetype( + newMockEvent() + ) + + delegateVotesChangedEvent.parameters = new Array() + + delegateVotesChangedEvent.parameters.push( + new ethereum.EventParam("delegate", ethereum.Value.fromAddress(delegate)) + ) + delegateVotesChangedEvent.parameters.push( + new ethereum.EventParam( + "previousBalance", + ethereum.Value.fromUnsignedBigInt(previousBalance) + ) + ) + delegateVotesChangedEvent.parameters.push( + new ethereum.EventParam( + "newBalance", + ethereum.Value.fromUnsignedBigInt(newBalance) + ) + ) + + return delegateVotesChangedEvent +} From 35f6410db066343b1b59c470081c3a04b421b5ed Mon Sep 17 00:00:00 2001 From: Ed Noepel Date: Fri, 21 Jul 2023 10:21:30 -0400 Subject: [PATCH 2/8] moved Voter fields into Account --- schema.graphql | 39 +++++++++++++++++++++------------------ src/ajna-token.ts | 13 +++++++++++-- src/grant-fund.ts | 7 ++++--- src/utils/account.ts | 5 +++++ src/utils/grants/voter.ts | 12 +----------- 5 files changed, 42 insertions(+), 34 deletions(-) diff --git a/schema.graphql b/schema.graphql index 807a43f..c81501a 100644 --- a/schema.graphql +++ b/schema.graphql @@ -183,14 +183,24 @@ type Account @entity { lends: [Lend!]! # loans which the account has taken from pools loans: [Loan!]! + # liquidation auctions settled settles: [Settle!]! + # liquidation auctions taken takes: [Take!]! # reserve auctions which the account has interacted in reserveAuctions: [ReserveAuction!]! + # ecosystem coordination proposals created + proposalsCreated: [Proposal!]! + # ecosystem coordination proposals passed and funded + proposalsExecuted: [Proposal!]! # voting delegation - delegate: Voter + delegate: Account + # delegation rewards claimed across all distribution periods + rewardsClaimed: BigDecimal! + # voting state for each distribution period + distributionPeriodVotes: [DistributionPeriodVote!]! # total number of transactions sent by the account txCount: BigInt! @@ -772,8 +782,8 @@ type UpdateExchangeRates @entity(immutable: true) { type GrantFund @entity { id: Bytes! # address of the grant fund treasury: BigDecimal! # Total ajna tokens in the grant fund treasure - proposals: [Proposal!]! # List of standard proposals submitted to the grant fund - proposalsExecuted: [Proposal!]! # List of standard proposals executed by the grant fund + proposals: [Proposal!]! # List of standard proposals submitted to the grant fund # TODO: remove, redundant + proposalsExecuted: [Proposal!]! # List of standard proposals executed by the grant fund # TODO: remove, redundant distributionPeriods: [DistributionPeriod!]! # List of distribution periods totalDelegationRewardsClaimed: BigDecimal! # Total delegation rewards claimed across all distribution periods } @@ -813,17 +823,10 @@ type ProposalParams @entity { tokensRequested: BigDecimal! # uint256 } -type Voter @entity { - id: Bytes! # voter address - proposalsCreated: [Proposal!]! # Proposal[] - proposalsExecuted: [Proposal!]! # Proposal[] - distributionPeriodVotes: [DistributionPeriodVote!]! # DistributionPeriodVote[] - rewardsClaimed: BigDecimal! -} - -# TODO: record voting power used? +# TODO: record voting power used? - shouldn't that be in Voter, not here? +# It's not per-distribution period. type DistributionPeriodVote @entity { - id: Bytes! # $voterId + '|'' + $distributionId + id: Bytes! # $accountId + '|'' + $distributionId distribution: DistributionPeriod! # uint256 screeningStageVotingPower: BigDecimal! # uint256 fundingStageVotingPower: BigDecimal! # uint256 @@ -832,18 +835,18 @@ type DistributionPeriodVote @entity { } type ScreeningVote @entity { - id: Bytes! # $proposalId + 'screening' + $Voter.id + $logIndex + id: Bytes! # $proposalId + 'screening' + $account.id + $logIndex distribution: DistributionPeriod! # distribution period the vote was cast in - voter: Voter! # Voter + voter: Account! # actor who cast votes proposal: Proposal! # proposal being voted on votesCast: BigDecimal! # uint256 blockNumber: BigInt! # block number the vote was cast } type FundingVote @entity { - id: Bytes! # $proposalId + 'funding' + $Voter.id + $logIndex + id: Bytes! # $proposalId + 'funding' + $account.id + $logIndex distribution: DistributionPeriod! # distribution period the vote was cast in - voter: Voter! # Voter + voter: Account! # actor who cast votes proposal: Proposal! # proposal being voted on votesCast: BigDecimal! # uint256 # TODO: can this be negative? votingPowerUsed: BigDecimal! # uint256 @@ -945,7 +948,7 @@ type DistributionPeriodStarted @entity(immutable: true) { type VoteCast @entity(immutable: true) { id: Bytes! - voter: Bytes! # address + voter: Bytes! # address # TODO: should be Account proposalId: BigInt! # uint256 support: Int! # uint8 weight: BigInt! # uint256 diff --git a/src/ajna-token.ts b/src/ajna-token.ts index 6f48380..8b37dfc 100644 --- a/src/ajna-token.ts +++ b/src/ajna-token.ts @@ -6,6 +6,8 @@ import { DelegateChanged, DelegateVotesChanged, } from "../generated/schema" +import { loadOrCreateAccount } from "./utils/account" +import { addressToBytes } from "./utils/convert" export function handleDelegateChanged(event: DelegateChangedEvent): void { let entity = new DelegateChanged( @@ -19,8 +21,12 @@ export function handleDelegateChanged(event: DelegateChangedEvent): void { entity.blockTimestamp = event.block.timestamp entity.transactionHash = event.transaction.hash - // TODO: update Account.delegate to point to Voter + // update Account.delegate to point to the new voting delegate + const delegatorId = addressToBytes(event.params.delegator) + const delegator = loadOrCreateAccount(delegatorId) + delegator.delegate = addressToBytes(event.params.toDelegate) + delegator.save() entity.save() } @@ -38,7 +44,10 @@ export function handleDelegateVotesChanged( entity.blockTimestamp = event.block.timestamp entity.transactionHash = event.transaction.hash - // TODO: update Voter entity + // TODO: update entities; unsure what to do here + const delegateId = addressToBytes(event.params.delegate) + const delegate = loadOrCreateAccount(delegateId) + // ??? entity.save() } diff --git a/src/grant-fund.ts b/src/grant-fund.ts index 6bc313a..9e6623d 100644 --- a/src/grant-fund.ts +++ b/src/grant-fund.ts @@ -29,8 +29,9 @@ import { 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 { getCurrentDistributionId, getCurrentStage, loadOrCreateDistributionPeriod } from './utils/grants/distribution' -import { getFundingStageVotingPower, getFundingVoteId, getScreeningStageVotingPower, getScreeningVoteId, loadOrCreateDistributionPeriodVote, loadOrCreateVoter } from './utils/grants/voter' +import { getFundingStageVotingPower, getFundingVoteId, getScreeningStageVotingPower, getScreeningVoteId, loadOrCreateDistributionPeriodVote } from './utils/grants/voter' import { loadOrCreateGrantFund } from './utils/grants/fund' +import { loadOrCreateAccount } from './utils/account' export function handleDelegateRewardClaimed( event: DelegateRewardClaimedEvent @@ -59,7 +60,7 @@ export function handleDelegateRewardClaimed( delegateRewardClaimed.distribution = distributionId - // TODO: update Voter.rewardsClaimed + // TODO: update Account.rewardsClaimed // save entities to the store grantFund.save() @@ -281,7 +282,7 @@ export function handleVoteCast(event: VoteCastEvent): void { voteCast.transactionHash = event.transaction.hash // load voter entity - const voter = loadOrCreateVoter(addressToBytes(event.params.voter)) + const voter = loadOrCreateAccount(addressToBytes(event.params.voter)) // update proposal entity const proposalId = bigIntToBytes(event.params.proposalId) diff --git a/src/utils/account.ts b/src/utils/account.ts index 82935cd..ba3e9e2 100644 --- a/src/utils/account.ts +++ b/src/utils/account.ts @@ -18,6 +18,11 @@ export function loadOrCreateAccount(accountId: Bytes): Account { account.settles = [] account.takes = [] + account.proposalsCreated = [] + account.proposalsExecuted = [] + account.rewardsClaimed = ZERO_BD + account.distributionPeriodVotes = [] + account.txCount = ZERO_BI } diff --git a/src/utils/grants/voter.ts b/src/utils/grants/voter.ts index de61e60..4c25974 100644 --- a/src/utils/grants/voter.ts +++ b/src/utils/grants/voter.ts @@ -1,6 +1,6 @@ import { Address, BigDecimal, BigInt, Bytes, dataSource } from "@graphprotocol/graph-ts" -import { DistributionPeriodVote, Voter } from "../../../generated/schema" +import { DistributionPeriodVote } from "../../../generated/schema" import { GrantFund } from "../../../generated/GrantFund/GrantFund" import { ZERO_BD, ZERO_BI } from "../constants" @@ -26,16 +26,6 @@ export function getScreeningVoteId(proposalId: Bytes, voterId: Bytes, logIndex: .concat(Bytes.fromUTF8(logIndex.toString())) } -export function loadOrCreateVoter(voterId: Bytes): Voter { - let voter = Voter.load(voterId) - if (voter == null) { - // create new voter if one hasn't already been stored - voter = new Voter(voterId) as Voter - voter.rewardsClaimed = ZERO_BD - } - return voter -} - export function loadOrCreateDistributionPeriodVote(distributionPeriodId: Bytes, voterId: Bytes): DistributionPeriodVote { const distributionPeriodVotesId = getDistributionPeriodVoteId(distributionPeriodId, voterId) let distributionPeriodVotes = DistributionPeriodVote.load(distributionPeriodVotesId) From 5e946a66983448542e512cba9bcd01cf740ae244 Mon Sep 17 00:00:00 2001 From: Ed Noepel Date: Fri, 21 Jul 2023 13:03:31 -0400 Subject: [PATCH 3/8] update account with rewards claimed --- src/ajna-token.ts | 9 +++++++-- src/grant-fund.ts | 6 +++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/ajna-token.ts b/src/ajna-token.ts index 8b37dfc..7433fe5 100644 --- a/src/ajna-token.ts +++ b/src/ajna-token.ts @@ -7,7 +7,9 @@ import { DelegateVotesChanged, } from "../generated/schema" import { loadOrCreateAccount } from "./utils/account" -import { addressToBytes } from "./utils/convert" +import { addressToBytes, bigIntToBytes } from "./utils/convert" +import { getCurrentDistributionId } from "./utils/grants/distribution" +import { loadOrCreateDistributionPeriodVote } from "./utils/grants/voter" export function handleDelegateChanged(event: DelegateChangedEvent): void { let entity = new DelegateChanged( @@ -47,7 +49,10 @@ export function handleDelegateVotesChanged( // TODO: update entities; unsure what to do here const delegateId = addressToBytes(event.params.delegate) const delegate = loadOrCreateAccount(delegateId) - // ??? + const distributionId = bigIntToBytes(getCurrentDistributionId(event.address)) + // const distributionPeriod = DistributionPeriod.load(distributionId) as DistributionPeriod + const distributionPeriodVotes = loadOrCreateDistributionPeriodVote(distributionId, event.params.delegate) + // distributionPeriodVotes.??? entity.save() } diff --git a/src/grant-fund.ts b/src/grant-fund.ts index 9e6623d..e43ff0a 100644 --- a/src/grant-fund.ts +++ b/src/grant-fund.ts @@ -60,12 +60,16 @@ export function handleDelegateRewardClaimed( delegateRewardClaimed.distribution = distributionId - // TODO: update Account.rewardsClaimed + // update Account entity + const accountId = addressToBytes(event.params.delegateeAddress) + const account = loadOrCreateAccount(accountId) + account.rewardsClaimed = account.rewardsClaimed.plus(rewardsClaimed) // save entities to the store grantFund.save() delegateRewardClaimed.save() distributionPeriod.save() + account.save() } export function handleFundTreasury(event: FundTreasuryEvent): void { From 5743bc36c47d114cd7e9c5c1c5cf38b69d0aeea9 Mon Sep 17 00:00:00 2001 From: Ed Noepel Date: Mon, 24 Jul 2023 17:00:56 -0400 Subject: [PATCH 4/8] added tokensDelegated to Account --- schema.graphql | 7 +++++-- src/ajna-token.ts | 10 ++++------ src/grant-fund.ts | 14 ++++++++++++-- src/utils/account.ts | 1 + src/utils/grants/distribution.ts | 5 +++-- src/utils/grants/fund.ts | 19 +++++++++++++++++++ src/utils/grants/voter.ts | 2 ++ 7 files changed, 46 insertions(+), 12 deletions(-) diff --git a/schema.graphql b/schema.graphql index c81501a..cdb2b21 100644 --- a/schema.graphql +++ b/schema.graphql @@ -201,6 +201,8 @@ type Account @entity { rewardsClaimed: BigDecimal! # voting state for each distribution period distributionPeriodVotes: [DistributionPeriodVote!]! + # amount of tokens delegated to this account + tokensDelegated: BigDecimal! # total number of transactions sent by the account txCount: BigInt! @@ -782,6 +784,7 @@ type UpdateExchangeRates @entity(immutable: true) { type GrantFund @entity { id: Bytes! # address of the grant fund treasury: BigDecimal! # Total ajna tokens in the grant fund treasure + # TODO: delete these two redundant collections proposals: [Proposal!]! # List of standard proposals submitted to the grant fund # TODO: remove, redundant proposalsExecuted: [Proposal!]! # List of standard proposals executed by the grant fund # TODO: remove, redundant distributionPeriods: [DistributionPeriod!]! # List of distribution periods @@ -798,6 +801,7 @@ type DistributionPeriod @entity { 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 proposals: [Proposal!]! # List of proposals submitted in the distribution period totalTokensRequested: BigDecimal! # Total ajna tokens requested by all proposals in the distribution period } @@ -823,10 +827,9 @@ type ProposalParams @entity { tokensRequested: BigDecimal! # uint256 } -# TODO: record voting power used? - shouldn't that be in Voter, not here? -# It's not per-distribution period. type DistributionPeriodVote @entity { id: Bytes! # $accountId + '|'' + $distributionId + voter: Account! distribution: DistributionPeriod! # uint256 screeningStageVotingPower: BigDecimal! # uint256 fundingStageVotingPower: BigDecimal! # uint256 diff --git a/src/ajna-token.ts b/src/ajna-token.ts index 7433fe5..f9e6eb5 100644 --- a/src/ajna-token.ts +++ b/src/ajna-token.ts @@ -7,7 +7,7 @@ import { DelegateVotesChanged, } from "../generated/schema" import { loadOrCreateAccount } from "./utils/account" -import { addressToBytes, bigIntToBytes } from "./utils/convert" +import { addressToBytes, bigIntToBytes, wadToDecimal } from "./utils/convert" import { getCurrentDistributionId } from "./utils/grants/distribution" import { loadOrCreateDistributionPeriodVote } from "./utils/grants/voter" @@ -41,18 +41,16 @@ export function handleDelegateVotesChanged( entity.delegate = event.params.delegate entity.previousBalance = event.params.previousBalance entity.newBalance = event.params.newBalance + const changeInBalance = wadToDecimal(event.params.newBalance.minus(event.params.previousBalance)) entity.blockNumber = event.block.number entity.blockTimestamp = event.block.timestamp entity.transactionHash = event.transaction.hash - // TODO: update entities; unsure what to do here const delegateId = addressToBytes(event.params.delegate) const delegate = loadOrCreateAccount(delegateId) - const distributionId = bigIntToBytes(getCurrentDistributionId(event.address)) - // const distributionPeriod = DistributionPeriod.load(distributionId) as DistributionPeriod - const distributionPeriodVotes = loadOrCreateDistributionPeriodVote(distributionId, event.params.delegate) - // distributionPeriodVotes.??? + delegate.tokensDelegated = delegate.tokensDelegated.plus(changeInBalance) + delegate.save() entity.save() } diff --git a/src/grant-fund.ts b/src/grant-fund.ts index e43ff0a..35069d4 100644 --- a/src/grant-fund.ts +++ b/src/grant-fund.ts @@ -22,7 +22,8 @@ import { ProposalParams, DistributionPeriodStarted, ScreeningVote, - VoteCast + VoteCast, + DistributionPeriodVote } from "../generated/schema" import { ZERO_ADDRESS, ZERO_BD } from './utils/constants' @@ -30,7 +31,7 @@ import { addressArrayToBytesArray, addressToBytes, bigIntToBytes, bytesToBigInt, import { getProposalParamsId, getProposalsInSlate, 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 { getVotesFunding, getVotesScreening, loadOrCreateGrantFund } from './utils/grants/fund' import { loadOrCreateAccount } from './utils/account' export function handleDelegateRewardClaimed( @@ -261,6 +262,15 @@ 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 Date: Mon, 24 Jul 2023 17:09:29 -0400 Subject: [PATCH 5/8] discovered voting power methods were already implemented in voter.ts --- src/grant-fund.ts | 6 +++--- src/utils/grants/fund.ts | 18 ------------------ 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/src/grant-fund.ts b/src/grant-fund.ts index 35069d4..9470d93 100644 --- a/src/grant-fund.ts +++ b/src/grant-fund.ts @@ -31,7 +31,7 @@ import { addressArrayToBytesArray, addressToBytes, bigIntToBytes, bytesToBigInt, import { getProposalParamsId, getProposalsInSlate, removeProposalFromList } from './utils/grants/proposal' import { getCurrentDistributionId, getCurrentStage, loadOrCreateDistributionPeriod } from './utils/grants/distribution' import { getFundingStageVotingPower, getFundingVoteId, getScreeningStageVotingPower, getScreeningVoteId, loadOrCreateDistributionPeriodVote } from './utils/grants/voter' -import { getVotesFunding, getVotesScreening, loadOrCreateGrantFund } from './utils/grants/fund' +import { loadOrCreateGrantFund } from './utils/grants/fund' import { loadOrCreateAccount } from './utils/account' export function handleDelegateRewardClaimed( @@ -266,8 +266,8 @@ export function handleDistributionPeriodStarted( const votes = distributionPeriod.votes for (var i=0; i Date: Mon, 24 Jul 2023 17:24:49 -0400 Subject: [PATCH 6/8] remove redundant collections on GrantFund entity, --- schema.graphql | 3 --- src/grant-fund.ts | 13 ------------- src/utils/grants/fund.ts | 2 -- 3 files changed, 18 deletions(-) diff --git a/schema.graphql b/schema.graphql index cdb2b21..19c85d9 100644 --- a/schema.graphql +++ b/schema.graphql @@ -784,9 +784,6 @@ type UpdateExchangeRates @entity(immutable: true) { type GrantFund @entity { id: Bytes! # address of the grant fund treasury: BigDecimal! # Total ajna tokens in the grant fund treasure - # TODO: delete these two redundant collections - proposals: [Proposal!]! # List of standard proposals submitted to the grant fund # TODO: remove, redundant - proposalsExecuted: [Proposal!]! # List of standard proposals executed by the grant fund # TODO: remove, redundant distributionPeriods: [DistributionPeriod!]! # List of distribution periods totalDelegationRewardsClaimed: BigDecimal! # Total delegation rewards claimed across all distribution periods } diff --git a/src/grant-fund.ts b/src/grant-fund.ts index 9470d93..c1f6c86 100644 --- a/src/grant-fund.ts +++ b/src/grant-fund.ts @@ -192,9 +192,6 @@ export function handleProposalCreated(event: ProposalCreatedEvent): void { proposalCreated.proposal = proposal.id - // load GrantFund entity - const grantFund = loadOrCreateGrantFund(event.address) - // update distribution entity const distributionId = bigIntToBytes(getCurrentDistributionId(event.address)) const distributionPeriod = DistributionPeriod.load(distributionId)! @@ -204,12 +201,8 @@ export function handleProposalCreated(event: ProposalCreatedEvent): void { // record proposals distributionId proposal.distribution = distributionPeriod.id - // record proposal in GrantFund entity - grantFund.proposals = grantFund.proposals.concat([proposal.id]) - // save entities to the store distributionPeriod.save() - grantFund.save() proposal.save() proposalCreated.save() } @@ -230,13 +223,7 @@ export function handleProposalExecuted(event: ProposalExecutedEvent): void { proposal.executed = true proposal.successful = true - // record proposal in GrantFund entity - const grantFund = loadOrCreateGrantFund(event.address) - grantFund.proposalsExecuted = grantFund.proposalsExecuted.concat([proposal.id]) - grantFund.proposals = removeProposalFromList(proposal.id, grantFund.proposals) - // save entities to the store - grantFund.save() proposal.save() } proposalExecuted.save() diff --git a/src/utils/grants/fund.ts b/src/utils/grants/fund.ts index 3827104..3d76224 100644 --- a/src/utils/grants/fund.ts +++ b/src/utils/grants/fund.ts @@ -11,8 +11,6 @@ export function loadOrCreateGrantFund(grantFundAddress: Address): GrantFund { // create new grantFund if one hasn't already been stored grantFund = new GrantFund(addressToBytes(grantFundAddress)) as GrantFund grantFund.treasury = ZERO_BD - grantFund.proposals = [] - grantFund.proposalsExecuted = [] grantFund.distributionPeriods = [] grantFund.totalDelegationRewardsClaimed = ZERO_BD } From 44af8717a49603f695743e4cb4ab489451c0ab97 Mon Sep 17 00:00:00 2001 From: Ed Noepel Date: Mon, 24 Jul 2023 17:25:23 -0400 Subject: [PATCH 7/8] add DistributionPeriodVote to DistributionPeriod upon creation --- src/utils/grants/voter.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/utils/grants/voter.ts b/src/utils/grants/voter.ts index 3103117..4d8d6cf 100644 --- a/src/utils/grants/voter.ts +++ b/src/utils/grants/voter.ts @@ -5,6 +5,7 @@ import { GrantFund } from "../../../generated/GrantFund/GrantFund" import { ZERO_BD, ZERO_BI } from "../constants" import { wadToDecimal } from "../convert" +import { loadOrCreateDistributionPeriod } from "./distribution" export function getDistributionPeriodVoteId(distributionPeriodId: Bytes, voterId: Bytes): Bytes { return voterId @@ -38,7 +39,11 @@ export function loadOrCreateDistributionPeriodVote(distributionPeriodId: Bytes, distributionPeriodVotes.fundingStageVotingPower = ZERO_BD distributionPeriodVotes.screeningVotes = [] distributionPeriodVotes.fundingVotes = [] - // TODO: add to DistributionPeriod entity + + // add to DistributionPeriod entity + const distributionPeriod = loadOrCreateDistributionPeriod(distributionPeriodId) + distributionPeriod.votes = distributionPeriod.votes.concat([distributionPeriodVotesId]) + distributionPeriod.save() } return distributionPeriodVotes } From 91e0728618a7f2db8749e09d3c8746393c29ba4d Mon Sep 17 00:00:00 2001 From: Mike Hathaway Date: Thu, 27 Jul 2023 14:15:50 -0400 Subject: [PATCH 8/8] 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 }