Skip to content

Commit

Permalink
feat(subgraph): add tally event handlers
Browse files Browse the repository at this point in the history
- [x] Support deposit event
- [x] Support claim event
- [x] Support add result event
- [x] Add subgraph build workflow
  • Loading branch information
0xmad committed Oct 29, 2024
1 parent 4e1a3a5 commit 52f0b15
Show file tree
Hide file tree
Showing 22 changed files with 494 additions and 60 deletions.
58 changes: 58 additions & 0 deletions .github/workflows/subgraph.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Subgraph

on:
push:
branches: [main]
pull_request:

env:
NEXT_PUBLIC_CHAIN_NAME: ${{ vars.NEXT_PUBLIC_CHAIN_NAME }}
NEXT_PUBLIC_ADMIN_ADDRESS: ${{ vars.NEXT_PUBLIC_ADMIN_ADDRESS }}
NEXT_PUBLIC_APPROVAL_SCHEMA: ${{ vars.NEXT_PUBLIC_APPROVAL_SCHEMA }}
NEXT_PUBLIC_METADATA_SCHEMA: ${{ vars.NEXT_PUBLIC_METADATA_SCHEMA }}
NEXT_PUBLIC_ROUND_ID: ${{ vars.NEXT_PUBLIC_ROUND_ID }}
NEXT_PUBLIC_MACI_ADDRESS: ${{ vars.NEXT_PUBLIC_MACI_ADDRESS }}
NEXT_PUBLIC_TALLY_URL: ${{ vars.NEXT_PUBLIC_TALLY_URL }}
NEXT_PUBLIC_WALLETCONNECT_ID: ${{ secrets.NEXT_PUBLIC_WALLETCONNECT_ID }}

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-22.04

steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9

- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"

- name: Install
run: |
pnpm install --frozen-lockfile --prefer-offline
- name: Install
run: |
pnpm install --frozen-lockfile --prefer-offline
- name: Build contracts
run: |
pnpm run build
working-directory: packages/contracts

- name: Build subgraph
run: |
pnpm run build
working-directory: packages/subgraph

- name: Test
run: pnpm run test
working-directory: packages/subgraph
2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ packages/subgraph/templates/subgraph.template.yaml
packages/subgraph/subgraph.yaml
packages/subgraph/generated/
packages/interface/metamask/

packages/subgraph/abi/
11 changes: 11 additions & 0 deletions packages/contracts/contracts/maci/Tally.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ contract Tally is TallyBase, IPayoutStrategy, Pausable {
/// @notice Initialized or not
bool internal initialized;

/// @notice events
event Deposited(address indexed sender, uint256 indexed amount);
event Claimed(uint256 indexed index, address indexed receiver, uint256 indexed amount);
event ResultAdded(uint256 indexed index, uint256 indexed result);

/// @notice custom errors
error CooldownPeriodNotOver();
error InvalidBudget();
Expand Down Expand Up @@ -138,6 +143,8 @@ contract Tally is TallyBase, IPayoutStrategy, Pausable {

/// @inheritdoc IPayoutStrategy
function deposit(uint256 amount) public isInitialized whenNotPaused afterTallying {
emit Deposited(msg.sender, amount);

token.safeTransferFrom(msg.sender, address(this), amount);
}

Expand Down Expand Up @@ -212,6 +219,8 @@ contract Tally is TallyBase, IPayoutStrategy, Pausable {
);

totalVotesSquares += tallyResult ** 2;

emit ResultAdded(voteOptionIndex, tallyResult);
}

/// @inheritdoc IPayoutStrategy
Expand All @@ -225,6 +234,8 @@ contract Tally is TallyBase, IPayoutStrategy, Pausable {

IRecipientRegistry.Recipient memory recipient = registry.getRecipient(params.index);

emit Claimed(params.index, recipient.recipient, amount);

if (claimed[params.index]) {
revert AlreadyClaimed();
}
Expand Down
43 changes: 28 additions & 15 deletions packages/contracts/tests/Tally.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ describe("Tally", () => {
let project: Signer;

let ownerAddress: string;
let userAddress: string;
let projectAddress: string;

const maxRecipients = TALLY_RESULTS.tally.length;
Expand All @@ -74,7 +75,11 @@ describe("Tally", () => {

before(async () => {
[owner, user, project] = await getSigners();
[ownerAddress, , projectAddress] = await Promise.all([owner.getAddress(), user.getAddress(), project.getAddress()]);
[ownerAddress, userAddress, projectAddress] = await Promise.all([
owner.getAddress(),
user.getAddress(),
project.getAddress(),
]);

payoutToken = await deployContract("MockERC20", owner, true, "Payout token", "PT");

Expand Down Expand Up @@ -351,7 +356,7 @@ describe("Tally", () => {
"NoProjectHasMoreThanOneVote",
);

const firstReceipt = await tally
const promise = await tally
.addTallyResults({
voteOptionIndices: indices.slice(1),
tallyResults: tallyResults.slice(1),
Expand All @@ -365,7 +370,10 @@ describe("Tally", () => {
})
.then((tx) => tx.wait());

expect(firstReceipt?.status).to.equal(1);
for (let index = 1; index < indices.length; index += 1) {
// eslint-disable-next-line no-await-in-loop
await expect(promise).to.emit(tally, "ResultAdded").withArgs(index, tallyResults[index]);
}

const voiceCreditFactor = await tally.voiceCreditFactor();

Expand All @@ -389,8 +397,8 @@ describe("Tally", () => {
}),
).to.be.revertedWithCustomError(tally, "TooManyResults");

const lastReceipt = await tally
.addTallyResults({
await expect(
tally.addTallyResults({
voteOptionIndices: indices.slice(0, 1),
tallyResults: tallyResults.slice(0, 1),
tallyResultProofs: tallyResultProofs.slice(0, 1),
Expand All @@ -400,10 +408,10 @@ describe("Tally", () => {
newResultsCommitment: TALLY_RESULTS.commitment,
spentVoiceCreditsHash: TOTAL_SPENT_VOICE_CREDITS.commitment,
perVOSpentVoiceCreditsHash: PER_VO_SPENT_VOICE_CREDITS.commitment,
})
.then((tx) => tx.wait());

expect(lastReceipt?.status).to.equal(1);
}),
)
.to.emit(tally, "ResultAdded")
.withArgs(0, tallyResults[0]);
});

it("should deposit funds properly", async () => {
Expand All @@ -415,16 +423,13 @@ describe("Tally", () => {
await payoutToken.transfer(user, userAmount);

await payoutToken.approve(tally, ownerAmount).then((tx) => tx.wait());
await tally.deposit(ownerAmount).then((tx) => tx.wait());
await expect(tally.deposit(ownerAmount)).to.emit(tally, "Deposited").withArgs(ownerAddress, ownerAmount);

await payoutToken
.connect(user)
.approve(tally, userAmount)
.then((tx) => tx.wait());
await tally
.connect(user)
.deposit(userAmount)
.then((tx) => tx.wait());
await expect(tally.connect(user).deposit(userAmount)).to.emit(tally, "Deposited").withArgs(userAddress, userAmount);

const [tokenBalance, totalAmount] = await Promise.all([payoutToken.balanceOf(tally), tally.totalAmount()]);

Expand Down Expand Up @@ -538,7 +543,15 @@ describe("Tally", () => {
};

// eslint-disable-next-line no-await-in-loop
await tally.claim(params).then((tx) => tx.wait());
const promise = await tally.claim(params);
// eslint-disable-next-line no-await-in-loop
const receipt = await promise.wait();
// eslint-disable-next-line no-await-in-loop
const amount = await tally.getAllocatedAmount(params.index, params.voiceCreditsPerOption);

expect(receipt?.status).to.equal(1);
// eslint-disable-next-line no-await-in-loop
await expect(promise).to.emit(tally, "Claimed").withArgs(index, projectAddress, amount);
}
});

Expand Down
1 change: 1 addition & 0 deletions packages/subgraph/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ schema.graphql
config/network.json
tests/.bin/
tests/.latest.json
abi/
7 changes: 5 additions & 2 deletions packages/subgraph/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"README.md"
],
"scripts": {
"precodegen": "rm -rf ./generated && pnpm generate:schema && pnpm generate:yaml",
"precodegen": "rm -rf ./generated && pnpm clean-abi && pnpm generate:schema && pnpm generate:yaml",
"clean-abi": "ts-node ./scripts/cleanAbi.ts",
"codegen": "graph codegen",
"generate:yaml": "mustache ./config/${NETWORK:-network}.json ./templates/subgraph.template.yaml > subgraph.yaml",
"generate:schema": "cp ./schemas/schema.${VERSION:-v1}.graphql schema.graphql",
Expand All @@ -26,14 +27,16 @@
"test:coverage": "graph test && graph test -c"
},
"dependencies": {
"@graphprotocol/graph-cli": "^0.80.0",
"@graphprotocol/graph-cli": "^0.85.0",
"@graphprotocol/graph-ts": "^0.35.1",
"maci-platform-contracts": "workspace:^0.1.0"
},
"devDependencies": {
"@types/node": "^22.7.5",
"assemblyscript": "0.19.23",
"matchstick-as": "^0.6.0",
"mustache": "^4.2.0",
"ts-node": "^10.9.1",
"wabt": "^1.0.36"
}
}
40 changes: 38 additions & 2 deletions packages/subgraph/schemas/schema.v1.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ enum Status {
type Request @entity {
id: String!
requestType: RequestType!
index: BigInt
index: BigInt!
recipientIndex: BigInt!
status: Status!

"relations"
Expand Down Expand Up @@ -84,7 +85,7 @@ type Poll @entity {
maxMessages: BigInt!
maxVoteOption: BigInt!
messageProcessor: Bytes! # address
tally: Bytes! # address
tally: Tally!
initTime: BigInt
createdAt: BigInt!
updatedAt: BigInt!
Expand All @@ -104,6 +105,41 @@ type Poll @entity {
votes: [Vote!]! @derivedFrom(field: "poll")
}

type Tally @entity {
id: Bytes! # tally address
results: [TallyResult!]! @derivedFrom(field: "tally")
claims: [Claim!]! @derivedFrom(field: "tally")
deposits: [Deposit!]! @derivedFrom(field: "tally")

"relations"
poll: Poll!
}

type TallyResult @entity(immutable: true) {
id: ID! # index
result: BigInt!

"relations"
tally: Tally!
}

type Claim @entity(immutable: true) {
id: ID!
amount: BigInt!

"relations"
tally: Tally!
recipient: Recipient!
}

type Deposit @entity(immutable: true) {
id: Bytes! # address
amount: BigInt!

"relations"
tally: Tally!
}

type Vote @entity(immutable: true) {
id: Bytes!
data: [BigInt!]! # uint256[10]
Expand Down
44 changes: 44 additions & 0 deletions packages/subgraph/scripts/cleanAbi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import fs from "fs";
import path from "path";

const INPUT_ABI_FILENAME = path.resolve(
__dirname,
"../node_modules/maci-platform-contracts/build/artifacts/contracts/maci/Tally.sol/Tally.json",
);
const OUTPUT_FOLDER_ABI = path.resolve(__dirname, "../abi");
const OUTPUT_ABI_FILENAME = path.resolve(OUTPUT_FOLDER_ABI, "./Tally.json");

interface TInput {
internalType: string;
name: string;
type: string;
components?: TInput[];
}

interface AbiItem {
inputs: TInput[];
}

// Need this script to clean abi because subgraph doesn't support multi-dimensional arrays
async function cleanAbi() {
const data = await fs.promises
.readFile(INPUT_ABI_FILENAME, "utf8")
.then((json) => JSON.parse(json) as { abi: AbiItem[] });

const abi = data.abi.filter(
(item) =>
!item.inputs.some(
(input) =>
input.internalType === "uint256[][][]" ||
input.components?.some((component) => component.internalType === "uint256[][][]"),
),
);

if (!fs.existsSync(OUTPUT_FOLDER_ABI)) {
await fs.promises.mkdir(OUTPUT_FOLDER_ABI);
}

await fs.promises.writeFile(OUTPUT_ABI_FILENAME, JSON.stringify({ ...data, abi }, null, 2));
}

cleanAbi();
1 change: 1 addition & 0 deletions packages/subgraph/src/registryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export function handleRequestSent(event: RequestSent): void {

request.status = "Pending";
request.index = event.params.index;
request.recipientIndex = event.params.recipientIndex;
request.recipient = recipient.id;
request.registryManager = registryManager.id;
request.registry = event.params.registry;
Expand Down
19 changes: 19 additions & 0 deletions packages/subgraph/src/tally.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {
Claimed as ClaimedEvent,
Deposited as DepositedEvent,
ResultAdded as ResultAddedEvent,
} from "../generated/templates/Tally/Tally";

import { createOrLoadClaim, createOrLoadDeposit, createOrLoadTallyResult } from "./utils/entity";

export function handleAddResult(event: ResultAddedEvent): void {
createOrLoadTallyResult(event.params.index, event.params.result, event.address);
}

export function handleAddClaim(event: ClaimedEvent): void {
createOrLoadClaim(event.params.index, event.params.receiver, event.params.amount, event.address);
}

export function handleAddDeposit(event: DepositedEvent): void {
createOrLoadDeposit(event.params.sender, event.params.amount, event.address);
}
Loading

0 comments on commit 52f0b15

Please sign in to comment.