Skip to content

Commit

Permalink
test(topup): implement test cases for the topup feature
Browse files Browse the repository at this point in the history
  • Loading branch information
ctrlc03 committed Jan 5, 2024
1 parent 16f5205 commit 7775a8b
Show file tree
Hide file tree
Showing 11 changed files with 916 additions and 625 deletions.
26 changes: 13 additions & 13 deletions circuits/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@
"watch": "tsc --watch",
"build": "tsc",
"test": "ts-mocha --exit ts/__tests__/*.test.ts",
"test-hasher": "ts-mocha --exit ts/__tests__/Hasher.test.ts",
"test-unpackElement": "ts-mocha --exit ts/__tests__/UnpackElement.test.ts",
"test-slAndBallotTransformer": "ts-mocha --exit ts/__tests__/StateLeafAndBallotTransformer.test.ts",
"test-messageToCommand": "ts-mocha --exit ts/__tests__/MessageToCommand.test.ts",
"test-messageValidator": "ts-mocha --exit ts/__tests__/MessageValidator.test.ts",
"test-verifySignature": "ts-mocha --exit ts/__tests__/VerifySignature.test.ts",
"test-splicer": "ts-mocha --exit ts/__tests__/Splicer.test.ts",
"test-ecdh": "ts-mocha --exit ts/__tests__/Ecdh.test.ts",
"test-privToPubKey": "ts-mocha --exit ts/__tests__/PrivToPubKey.test.ts",
"test-calculateTotal": "ts-mocha --exit ts/__tests__/CalculateTotal.test.ts",
"test-processMessages": "NODE_OPTIONS=--max-old-space-size=4096 ts-mocha --exit ts/__tests__/ProcessMessages.test.ts",
"test-tallyVotes": "NODE_OPTIONS=--max-old-space-size=4096 ts-mocha --exit ts/__tests__/TallyVotes.test.ts",
"test-ceremonyParams": "ts-mocha --exit ts/__tests__/CeremonyParams.test.ts"
"test:hasher": "ts-mocha --exit ts/__tests__/Hasher.test.ts",
"test:unpackElement": "ts-mocha --exit ts/__tests__/UnpackElement.test.ts",
"test:slAndBallotTransformer": "ts-mocha --exit ts/__tests__/StateLeafAndBallotTransformer.test.ts",
"test:messageToCommand": "ts-mocha --exit ts/__tests__/MessageToCommand.test.ts",
"test:messageValidator": "ts-mocha --exit ts/__tests__/MessageValidator.test.ts",
"test:verifySignature": "ts-mocha --exit ts/__tests__/VerifySignature.test.ts",
"test:splicer": "ts-mocha --exit ts/__tests__/Splicer.test.ts",
"test:ecdh": "ts-mocha --exit ts/__tests__/Ecdh.test.ts",
"test:privToPubKey": "ts-mocha --exit ts/__tests__/PrivToPubKey.test.ts",
"test:calculateTotal": "ts-mocha --exit ts/__tests__/CalculateTotal.test.ts",
"test:processMessages": "NODE_OPTIONS=--max-old-space-size=4096 ts-mocha --exit ts/__tests__/ProcessMessages.test.ts",
"test:tallyVotes": "NODE_OPTIONS=--max-old-space-size=4096 ts-mocha --exit ts/__tests__/TallyVotes.test.ts",
"test:ceremonyParams": "ts-mocha --exit ts/__tests__/CeremonyParams.test.ts"
},
"dependencies": {
"circomlib": "https://github.com/weijiekoh/circomlib#ac85e82c1914d47789e2032fb11ceb2cfdd38a2b",
Expand Down
70 changes: 70 additions & 0 deletions circuits/ts/__tests__/ProcessMessages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,4 +427,74 @@ describe("ProcessMessage circuit", function test() {
});
});
});

describe("1 user, 1 topup, 2 messages", () => {
const maciState = new MaciState(STATE_TREE_DEPTH);
const voteOptionIndex = BigInt(0);
let stateIndex: bigint;
let pollId: number;
let poll: Poll;
const userKeypair = new Keypair();

before(() => {
// Sign up and publish
stateIndex = BigInt(
maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))),
);

pollId = maciState.deployPoll(
BigInt(Math.floor(Date.now() / 1000) + duration),
maxValues,
treeDepths,
messageBatchSize,
coordinatorKeypair,
);

poll = maciState.polls[pollId];
});

it("should work when publishing 2 vote messages and a topup (the second vote uses more than initial voice credit balance)", async () => {
// First command (valid)
const command1 = new PCommand(
stateIndex, // BigInt(1),
userKeypair.pubKey,
voteOptionIndex + 1n, // voteOptionIndex,
5n, // vote weight
BigInt(2), // nonce
BigInt(pollId),
);

const signature1 = command1.sign(userKeypair.privKey);

const ecdhKeypair1 = new Keypair();
const sharedKey1 = Keypair.genEcdhSharedKey(ecdhKeypair1.privKey, coordinatorKeypair.pubKey);
const message1 = command1.encrypt(signature1, sharedKey1);

poll.publishMessage(message1, ecdhKeypair1.pubKey);

poll.topupMessage(new Message(2n, [BigInt(stateIndex), 50n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n]));

// First command (valid)
const command = new PCommand(
stateIndex, // BigInt(1),
userKeypair.pubKey,
voteOptionIndex, // voteOptionIndex,
10n, // vote weight
BigInt(1), // nonce
BigInt(pollId),
);

const signature = command.sign(userKeypair.privKey);

const ecdhKeypair = new Keypair();
const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey);
const message = command.encrypt(signature, sharedKey);

poll.publishMessage(message, ecdhKeypair.pubKey);

const inputs = poll.processMessages(pollId);
const witness = await circuit.calculateWitness(inputs, true);
await circuit.checkConstraints(witness);
});
});
});
53 changes: 53 additions & 0 deletions cli/testScript.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#! /bin/bash
# rm -r ./proofs
# rm tally.json
HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js deployVkRegistry
HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js setVerifyingKeys \
--state-tree-depth 10 \
--int-state-tree-depth 1 \
--msg-tree-depth 2 \
--vote-option-tree-depth 2 \
--msg-batch-depth 1 \
--process-messages-zkey ./zkeys/ProcessMessages_10-2-1-2_test.0.zkey \
--tally-votes-zkey ./zkeys/TallyVotes_10-1-2_test.0.zkey
HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js create -s 10
HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js deployPoll \
--pubkey macipk.ea638a3366ed91f2e955110888573861f7c0fc0bb5fb8b8dca9cd7a08d7d6b93 \
-t 30 -g 25 -mv 25 -i 1 -m 2 -b 1 -v 2
HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js signup \
--pubkey macipk.e743ffb5298ef0f5c1f63b6464a48fea19ea7ee5a885c67ae1b24a1d04f03f07
HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js publish \
--pubkey macipk.e743ffb5298ef0f5c1f63b6464a48fea19ea7ee5a885c67ae1b24a1d04f03f07 \
--privkey macisk.0ab0281365e01cff60afc62310daec765e590487bf989a7c4986ebc3fd49895e \
--state-index 1 \
--vote-option-index 0 \
--new-vote-weight 9 \
--nonce 1 \
--poll-id 0
HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js publish \
--pubkey macipk.e743ffb5298ef0f5c1f63b6464a48fea19ea7ee5a885c67ae1b24a1d04f03f07 \
--privkey macisk.0ab0281365e01cff60afc62310daec765e590487bf989a7c4986ebc3fd49895e \
--state-index 1 \
--vote-option-index 1 \
--new-vote-weight 9 \
--nonce 2 \
--poll-id 0
HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js timeTravel -s 100
HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js mergeSignups --poll-id 0
HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js mergeMessages --poll-id 0
HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js genProofs \
--privkey macisk.1751146b59d32e3c0d7426de411218172428263f93b2fc4d981c036047a4d8c0 \
--poll-id 0 \
--process-zkey ./zkeys/ProcessMessages_10-2-1-2_test.0.zkey \
--tally-zkey ./zkeys/TallyVotes_10-1-2_test.0.zkey \
--tally-file tally.json \
--output proofs/ \
-tw ./zkeys/TallyVotes_10-1-2_test_js/TallyVotes_10-1-2_test.wasm \
-pw ./zkeys/ProcessMessages_10-2-1-2_test_js/ProcessMessages_10-2-1-2_test.wasm \
-w true
HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js proveOnChain \
--poll-id 0 \
--proof-dir proofs/
HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js verify \
--poll-id 0 \
--tally-file tally.json
82 changes: 82 additions & 0 deletions cli/tests/e2e.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { existsSync, unlinkSync } from "fs";
import {
airdrop,
checkVerifyingKeys,
deploy,
deployPoll,
Expand All @@ -13,6 +14,7 @@ import {
setVerifyingKeys,
signup,
timeTravel,
topup,
verify,
} from "../ts/commands";
import {
Expand Down Expand Up @@ -1236,4 +1238,84 @@ describe("e2e tests", function () {
await verify("0", testTallyFilePath);
});
});

describe("topup message", () => {
const user = new Keypair();
const tokenAmount = 100;
let stateIndex: number | undefined;

after(() => {
cleanVanilla();
});

before(async () => {
// deploy the smart contracts
maciAddresses = await deploy(STATE_TREE_DEPTH);
// deploy a poll contract
pollAddresses = await deployPoll(
pollDuration,
maxMessages,
maxVoteOptions,
INT_STATE_TREE_DEPTH,
MSG_BATCH_DEPTH,
MSG_TREE_DEPTH,
VOTE_OPTION_TREE_DEPTH,
coordinatorPubKey,
);
});

it("should signup one user", async () => {
stateIndex = Number.parseInt(await signup(user.pubKey.serialize()), 10);
});

it("should airdrop topup tokens to the coordinator user", async () => {
await airdrop(tokenAmount, maciAddresses.topupCreditAddress, 0, maciAddresses.maciAddress, true);
});

it("should publish one topup message", async () => {
await topup(tokenAmount, stateIndex!, 0, maciAddresses.maciAddress, true);
});

it("should publish one vote message", async () => {
await publish(
user.pubKey.serialize(),
1,
0,
1,
0,
9,
maciAddresses.maciAddress,
genRandomSalt().toString(),
user.privKey.serialize(),
);
});

it("should generate proofs and verify them", async () => {
await timeTravel(pollDuration, true);
await mergeMessages(0);
await mergeSignups(0);
await genProofs(
testProofsDirPath,
testTallyFilePath,
tallyVotesTestZkeyPath,
processMessageTestZkeyPath,
0,
undefined,
undefined,
testRapidsnarkPath,
testProcessMessagesWitnessPath,
testTallyVotesWitnessPath,
undefined,
coordinatorPrivKey,
undefined,
undefined,
testProcessMessagesWasmPath,
testTallyVotesWasmPath,
undefined,
useWasm,
);
await proveOnChain("0", testProofsDirPath);
await verify("0", testTallyFilePath);
});
});
});
7 changes: 4 additions & 3 deletions cli/ts/commands/airdrop.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { readContractAddress } from "../utils/storage";
import { Contract, MaxUint256 } from "ethers";
import { Contract } from "ethers";
import { contractExists } from "../utils/contracts";
import { getDefaultSigner, parseArtifact } from "maci-contracts";
import { logError, logGreen, success } from "../utils/theme";
Expand Down Expand Up @@ -63,7 +63,7 @@ export const airdrop = async (

// if there is a poll id provided, we can pre-approve all of the tokens
// so there is no need to do it afterwards
if (pollId) {
if (pollId !== undefined) {
maciAddress = readContractAddress("MACI") ? readContractAddress("MACI") : maciAddress;
if (!maciAddress) logError("Please provide a MACI contract address");

Expand All @@ -73,7 +73,8 @@ export const airdrop = async (

const pollAddr = await maciContract.getPoll(pollId);
try {
const tx = await tokenContract.approve(pollAddr, MaxUint256, { gasLimit: 1000000 });
const tx = await tokenContract.approve(pollAddr, amount, { gasLimit: 1000000 });

await tx.wait();

logGreen(quiet, success(`Approved ${pollAddr} to spend ${amount} credits`));
Expand Down
46 changes: 44 additions & 2 deletions contracts/tests/Poll.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { NOTHING_UP_MY_SLEEVE } from "maci-crypto";
import { Keypair, Message, PCommand, PubKey } from "maci-domainobjs";

import { parseArtifact } from "../ts/abi";
import { getDefaultSigner } from "../ts/utils";
import { AccQueue, MACI, Poll as PollContract } from "../typechain-types";
import { getDefaultSigner, getSigners } from "../ts/utils";
import { AccQueue, MACI, Poll as PollContract, TopupCredit } from "../typechain-types";

import {
MESSAGE_TREE_DEPTH,
Expand All @@ -26,6 +26,7 @@ describe("Poll", () => {
let maciContract: MACI;
let pollId: number;
let pollContract: PollContract;
let topupCreditContract: TopupCredit;
let signer: Signer;
let deployTime: number;
const coordinator = new Keypair();
Expand All @@ -39,6 +40,7 @@ describe("Poll", () => {
signer = await getDefaultSigner();
const r = await deployTestContracts(initialVoiceCreditBalance, STATE_TREE_DEPTH, signer, true);
maciContract = r.maciContract;
topupCreditContract = r.topupCreditContract;

// deploy on chain poll
const tx = await maciContract.deployPoll(duration, maxValues, treeDepths, coordinator.pubKey.asContractParam(), {
Expand Down Expand Up @@ -126,6 +128,46 @@ describe("Poll", () => {
});
});

describe("topup", () => {
let voter: Signer;

before(async () => {
// transfer tokens to a user and pre-approve
[, voter] = await getSigners();
await topupCreditContract.airdropTo(voter.getAddress(), 200n);
await topupCreditContract.connect(voter).approve(await pollContract.getAddress(), 1000n);
});

it("should allow to publish a topup message when the caller has enough topup tokens", async () => {
const tx = await pollContract.connect(voter).topup(1n, 50n);
const receipt = await tx.wait();
expect(receipt?.status).to.eq(1);

// publish on local maci state
maciState.polls[pollId].topupMessage(new Message(2n, [1n, 50n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n]));
});

it("should throw when the user does not have enough tokens", async () => {
await expect(pollContract.connect(signer).topup(1n, 50n)).to.be.revertedWith("ERC20: insufficient allowance");
});

it("should emit an event when publishing a topup message", async () => {
expect(await pollContract.connect(voter).topup(1n, 50n)).to.emit(pollContract, "TopupMessage");
// publish on local maci state
maciState.polls[pollId].topupMessage(new Message(2n, [1n, 50n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n]));
});

it("should successfully increase the number of messages", async () => {
const numMessages = await pollContract.numMessages();
await pollContract.connect(voter).topup(1n, 50n);
const newNumMessages = await pollContract.numMessages();
expect(newNumMessages.toString()).to.eq((numMessages + 1n).toString());

// publish on local maci state
maciState.polls[pollId].topupMessage(new Message(2n, [1n, 50n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n]));
});
});

describe("publishMessage", () => {
it("should publish a message to the Poll contract", async () => {
const keypair = new Keypair();
Expand Down
1 change: 1 addition & 0 deletions contracts/tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -570,5 +570,6 @@ export const deployTestContracts = async (
mpContract,
tallyContract,
subsidyContract,
topupCreditContract,
};
};
2 changes: 2 additions & 0 deletions contracts/ts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {
PoseidonT6,
Subsidy,
Tally,
TopupCredit,
VkRegistry,
} from "../typechain-types";
import type { BigNumberish, Fragment, JsonFragment } from "ethers";
Expand Down Expand Up @@ -68,6 +69,7 @@ export interface IDeployedTestContracts {
vkRegistryContract: VkRegistry;
mpContract: MessageProcessor;
tallyContract: Tally;
topupCreditContract: TopupCredit;
subsidyContract?: Subsidy;
}

Expand Down
4 changes: 2 additions & 2 deletions core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"scripts": {
"watch": "tsc --watch",
"build": "tsc",
"test-processMessage": "ts-mocha --exit ts/__tests__/ProcessMessage.test.ts",
"test-maciState": "ts-mocha --exit ts/__tests__/MaciState.test.ts",
"test:e2e": "ts-mocha --exit ts/__tests__/e2e.test.ts",
"test:unit": "ts-mocha --exit ts/__tests__/MaciState.test.ts",
"test": "nyc ts-mocha --exit ts/__tests__/*.test.ts"
},
"dependencies": {
Expand Down
Loading

0 comments on commit 7775a8b

Please sign in to comment.