-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
- [x] Customize Poll and PollFactory - [x] Customize MACI - [x] Add tests
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
const { buildPoseidonT3, buildPoseidonT4, buildPoseidonT5, buildPoseidonT6 } = require("maci-contracts"); | ||
const fs = require("fs"); | ||
const path = require("path"); | ||
|
||
const PATHS = [ | ||
path.resolve(__dirname, "..", "artifacts"), | ||
path.resolve(__dirname, "..", "cache"), | ||
path.resolve(__dirname, "..", "typechain-types"), | ||
]; | ||
|
||
module.exports = { | ||
onPreCompile: async () => { | ||
await Promise.all( | ||
PATHS.map((filepath) => fs.existsSync(filepath) && fs.promises.rm(filepath, { recursive: true })), | ||
); | ||
}, | ||
onCompileComplete: async () => { | ||
await Promise.all([buildPoseidonT3(), buildPoseidonT4(), buildPoseidonT5(), buildPoseidonT6()]); | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import { IPoll as IPollBase } from "maci-contracts/contracts/interfaces/IPoll.sol"; | ||
|
||
/// @title IOwnable | ||
/// @notice Ownable interface | ||
interface IOwnable { | ||
/** | ||
* @dev Transfers ownership of the contract to a new account (`newOwner`). | ||
* Can only be called by the current owner. | ||
*/ | ||
function transferOwnership(address newOwner) external; | ||
|
||
/** | ||
* @dev Leaves the contract without owner. It will not be possible to call | ||
* `onlyOwner` functions. Can only be called by the current owner. | ||
* | ||
* NOTE: Renouncing ownership will leave the contract without an owner, | ||
* thereby disabling any functionality that is only available to the owner. | ||
*/ | ||
function renounceOwnership() external; | ||
|
||
/** | ||
* @dev Returns the address of the current owner. | ||
*/ | ||
function owner() external view returns (address); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import { IPoll as IPollBase } from "maci-contracts/contracts/interfaces/IPoll.sol"; | ||
|
||
import { IOwnable } from "./IOwnable.sol"; | ||
|
||
/// @title IPollBase | ||
/// @notice Poll interface | ||
interface IPoll is IPollBase, IOwnable { | ||
/// @notice The initialization function. | ||
function init() external; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; | ||
import { MACI as BaseMACI } from "maci-contracts/contracts/MACI.sol"; | ||
import { IPollFactory } from "maci-contracts/contracts/interfaces/IPollFactory.sol"; | ||
import { IMessageProcessorFactory } from "maci-contracts/contracts/interfaces/IMPFactory.sol"; | ||
import { ITallyFactory } from "maci-contracts/contracts/interfaces/ITallyFactory.sol"; | ||
import { InitialVoiceCreditProxy } from "maci-contracts/contracts/initialVoiceCreditProxy/InitialVoiceCreditProxy.sol"; | ||
import { SignUpGatekeeper } from "maci-contracts/contracts/gatekeepers/SignUpGatekeeper.sol"; | ||
|
||
import { IPoll } from "../interfaces/IPoll.sol"; | ||
|
||
/// @title MACI - Minimum Anti-Collusion Infrastructure | ||
/// @notice A contract which allows users to sign up, and deploy new polls | ||
contract MACI is Ownable, BaseMACI { | ||
/// @notice Create a new instance of the MACI contract. | ||
/// @param pollFactory The PollFactory contract | ||
/// @param messageProcessorFactory The MessageProcessorFactory contract | ||
/// @param tallyFactory The TallyFactory contract | ||
/// @param signUpGatekeeper The SignUpGatekeeper contract | ||
/// @param initialVoiceCreditProxy The InitialVoiceCreditProxy contract | ||
/// @param stateTreeDepth The depth of the state tree | ||
/// @param emptyBallotRoots The roots of the empty ballot trees | ||
constructor( | ||
IPollFactory pollFactory, | ||
IMessageProcessorFactory messageProcessorFactory, | ||
ITallyFactory tallyFactory, | ||
SignUpGatekeeper signUpGatekeeper, | ||
InitialVoiceCreditProxy initialVoiceCreditProxy, | ||
uint8 stateTreeDepth, | ||
uint256[5] memory emptyBallotRoots | ||
) | ||
payable | ||
Ownable(msg.sender) | ||
BaseMACI( | ||
pollFactory, | ||
messageProcessorFactory, | ||
tallyFactory, | ||
signUpGatekeeper, | ||
initialVoiceCreditProxy, | ||
stateTreeDepth, | ||
emptyBallotRoots | ||
) | ||
{} | ||
|
||
/// @notice Initialize the poll by given poll id and transfer poll ownership to the caller. | ||
/// @param pollId The poll id | ||
function initPoll(uint256 pollId) public onlyOwner { | ||
PollContracts memory pollAddresses = polls[pollId]; | ||
IPoll poll = IPoll(pollAddresses.poll); | ||
|
||
poll.init(); | ||
poll.transferOwnership(msg.sender); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; | ||
import { Poll as BasePoll } from "maci-contracts/contracts/Poll.sol"; | ||
|
||
/// @title Poll | ||
/// @notice A Poll contract allows voters to submit encrypted messages | ||
/// which can be either votes or key change messages. | ||
/// @dev Do not deploy this directly. Use PollFactory.deploy() which performs some | ||
/// checks on the Poll constructor arguments. | ||
contract Poll is Ownable, BasePoll { | ||
/// @notice Each MACI instance can have multiple Polls. | ||
/// When a Poll is deployed, its voting period starts immediately. | ||
/// @param duration The duration of the voting period, in seconds | ||
/// @param treeDepths The depths of the merkle trees | ||
/// @param coordinatorPubKey The coordinator's public key | ||
/// @param extContracts The external contracts | ||
constructor( | ||
uint256 duration, | ||
TreeDepths memory treeDepths, | ||
PubKey memory coordinatorPubKey, | ||
ExtContracts memory extContracts, | ||
uint256 emptyBallotRoot | ||
) | ||
payable | ||
Ownable(address(extContracts.maci)) | ||
BasePoll(duration, treeDepths, coordinatorPubKey, extContracts, emptyBallotRoot) | ||
{} | ||
|
||
/// @notice The initialization function. | ||
function init() public override onlyOwner { | ||
super.init(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import { PollFactory as BasePollFactory } from "maci-contracts/contracts/PollFactory.sol"; | ||
import { IMACI } from "maci-contracts/contracts/interfaces/IMACI.sol"; | ||
import { AccQueue } from "maci-contracts/contracts/trees/AccQueue.sol"; | ||
import { AccQueueQuinaryMaci } from "maci-contracts/contracts/trees/AccQueueQuinaryMaci.sol"; | ||
import { Poll } from "./Poll.sol"; | ||
|
||
/// @title PollFactory | ||
/// @notice A factory contract which deploys Poll contracts. It allows the MACI contract | ||
/// size to stay within the limit set by EIP-170. | ||
contract PollFactory is BasePollFactory { | ||
/// @inheritdoc BasePollFactory | ||
function deploy( | ||
uint256 duration, | ||
TreeDepths calldata treeDepths, | ||
PubKey calldata coordinatorPubKey, | ||
address maci, | ||
uint256 emptyBallotRoot | ||
) public virtual override returns (address pollAddr) { | ||
/// @notice deploy a new AccQueue contract to store messages | ||
AccQueue messageAq = new AccQueueQuinaryMaci(treeDepths.messageTreeSubDepth); | ||
|
||
/// @notice the smart contracts that a Poll would interact with | ||
ExtContracts memory extContracts = ExtContracts({ maci: IMACI(maci), messageAq: messageAq }); | ||
|
||
Poll poll = new Poll(duration, treeDepths, coordinatorPubKey, extContracts, emptyBallotRoot); | ||
|
||
messageAq.transferOwnership(address(poll)); | ||
|
||
pollAddr = address(poll); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import "maci-contracts/contracts/crypto/Hasher.sol"; | ||
Check warning on line 4 in packages/contracts/contracts/mocks/Mocker.sol GitHub Actions / check (lint:sol)
|
||
import "maci-contracts/contracts/crypto/MockVerifier.sol"; | ||
Check warning on line 5 in packages/contracts/contracts/mocks/Mocker.sol GitHub Actions / check (lint:sol)
|
||
import "maci-contracts/contracts/gatekeepers/FreeForAllSignUpGatekeeper.sol"; | ||
Check warning on line 6 in packages/contracts/contracts/mocks/Mocker.sol GitHub Actions / check (lint:sol)
|
||
import "maci-contracts/contracts/initialVoiceCreditProxy/ConstantInitialVoiceCreditProxy.sol"; | ||
Check warning on line 7 in packages/contracts/contracts/mocks/Mocker.sol GitHub Actions / check (lint:sol)
|
||
import "maci-contracts/contracts/VkRegistry.sol"; | ||
Check warning on line 8 in packages/contracts/contracts/mocks/Mocker.sol GitHub Actions / check (lint:sol)
|
||
import "maci-contracts/contracts/TallyFactory.sol"; | ||
Check warning on line 9 in packages/contracts/contracts/mocks/Mocker.sol GitHub Actions / check (lint:sol)
|
||
import "maci-contracts/contracts/MessageProcessorFactory.sol"; | ||
Check warning on line 10 in packages/contracts/contracts/mocks/Mocker.sol GitHub Actions / check (lint:sol)
|
||
|
||
/// @title Mocker | ||
/// @notice import all MACI protocol related contract for tests | ||
contract Mocker { | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import { expect } from "chai"; | ||
import { Signer } from "ethers"; | ||
import { Verifier, VkRegistry, EMode, getSigners } from "maci-contracts"; | ||
import { MaciState } from "maci-core"; | ||
import { Keypair, Message, PubKey } from "maci-domainobjs"; | ||
|
||
import { MACI, Poll__factory as PollFactory, Poll as PollContract } from "../typechain-types"; | ||
|
||
import { | ||
NOTHING_UP_MY_SLEEVE, | ||
STATE_TREE_DEPTH, | ||
duration, | ||
initialVoiceCreditBalance, | ||
messageBatchSize, | ||
treeDepths, | ||
} from "./constants"; | ||
import { deployTestContracts } from "./utils"; | ||
|
||
describe("Poll", () => { | ||
let maciContract: MACI; | ||
let pollId: bigint; | ||
let pollContract: PollContract; | ||
let verifierContract: Verifier; | ||
let vkRegistryContract: VkRegistry; | ||
let owner: Signer; | ||
let user: Signer; | ||
let deployTime: number; | ||
const coordinator = new Keypair(); | ||
|
||
const maciState = new MaciState(STATE_TREE_DEPTH); | ||
|
||
describe("deployment", () => { | ||
before(async () => { | ||
[owner, user] = await getSigners(); | ||
|
||
const contracts = await deployTestContracts({ | ||
initialVoiceCreditBalance, | ||
stateTreeDepth: STATE_TREE_DEPTH, | ||
signer: owner, | ||
}); | ||
maciContract = contracts.maciContract; | ||
verifierContract = contracts.mockVerifierContract as Verifier; | ||
vkRegistryContract = contracts.vkRegistryContract; | ||
|
||
// deploy on chain poll | ||
const tx = await maciContract.deployPoll( | ||
duration, | ||
treeDepths, | ||
coordinator.pubKey.asContractParam(), | ||
verifierContract, | ||
vkRegistryContract, | ||
EMode.QV, | ||
); | ||
const receipt = await tx.wait(); | ||
|
||
const block = await owner.provider!.getBlock(receipt!.blockHash); | ||
deployTime = block!.timestamp; | ||
|
||
expect(receipt?.status).to.eq(1); | ||
|
||
pollId = (await maciContract.nextPollId()) - 1n; | ||
|
||
const pollContracts = await maciContract.getPoll(pollId); | ||
pollContract = PollFactory.connect(pollContracts.poll, owner); | ||
|
||
// deploy local poll | ||
const p = maciState.deployPoll(BigInt(deployTime + duration), treeDepths, messageBatchSize, coordinator); | ||
expect(p.toString()).to.eq(pollId.toString()); | ||
// publish the NOTHING_UP_MY_SLEEVE message | ||
const messageData = [NOTHING_UP_MY_SLEEVE]; | ||
for (let i = 1; i < 10; i += 1) { | ||
messageData.push(BigInt(0)); | ||
} | ||
const message = new Message(messageData); | ||
const padKey = new PubKey([ | ||
BigInt("10457101036533406547632367118273992217979173478358440826365724437999023779287"), | ||
BigInt("19824078218392094440610104313265183977899662750282163392862422243483260492317"), | ||
]); | ||
maciState.polls.get(pollId)?.publishMessage(message, padKey); | ||
}); | ||
|
||
it("should fail if unauthorized user tries to init the poll", async () => { | ||
await expect(maciContract.initPoll(pollId)).not.to.be.revertedWithCustomError(pollContract, "PollAlreadyInit"); | ||
await expect(maciContract.connect(user).initPoll(pollId)).to.be.revertedWithCustomError( | ||
pollContract, | ||
"OwnableUnauthorizedAccount", | ||
); | ||
await expect(pollContract.init()).to.be.revertedWithCustomError(pollContract, "PollAlreadyInit"); | ||
}); | ||
|
||
it("should not be possible to init the Poll contract twice", async () => { | ||
await expect(maciContract.initPoll(pollId)).not.to.be.revertedWithCustomError(pollContract, "PollAlreadyInit"); | ||
await expect(maciContract.initPoll(pollId)).to.be.revertedWithCustomError( | ||
pollContract, | ||
"OwnableUnauthorizedAccount", | ||
); | ||
await expect(pollContract.init()).to.be.revertedWithCustomError(pollContract, "PollAlreadyInit"); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { TreeDepths, STATE_TREE_ARITY, MESSAGE_TREE_ARITY } from "maci-core"; | ||
|
||
export const duration = 2_000; | ||
|
||
export const STATE_TREE_DEPTH = 10; | ||
export const MESSAGE_TREE_DEPTH = 2; | ||
export const MESSAGE_TREE_SUBDEPTH = 1; | ||
export const messageBatchSize = MESSAGE_TREE_ARITY ** MESSAGE_TREE_SUBDEPTH; | ||
export const NOTHING_UP_MY_SLEEVE = 8370432830353022751713833565135785980866757267633941821328460903436894336785n; | ||
|
||
export const initialVoiceCreditBalance = 100; | ||
|
||
export const treeDepths: TreeDepths = { | ||
intStateTreeDepth: 1, | ||
messageTreeDepth: MESSAGE_TREE_DEPTH, | ||
messageTreeSubDepth: MESSAGE_TREE_SUBDEPTH, | ||
voteOptionTreeDepth: 2, | ||
}; | ||
|
||
export const tallyBatchSize = STATE_TREE_ARITY ** treeDepths.intStateTreeDepth; |