Integrating MACI
MACI can be used in any protocol that requires collusion resistance, for instance it has been proven to be quite efficient when integrated in quadratic funding applications such as clr.fund, qfi, MACI Platform, and the Gitcoin Allo Stack.
Here we will be looking at how the smart contracts can be integrated.
MACI Contract
The MACI contract is the core of the protocol. Contracts can inherit from MACI and thus expose the signUp
and deployPoll
functions. As with standalone MACI, one would need to deploy a sign up policy.
As an example, a contract could inherit from MACI and allows sign up via a custom signup function.
/// @inheritdoc IMACI
function signUp(PubKey memory _pubKey, bytes memory _signUpPolicyData) public virtual {
// ensure we do not have more signups than what the circuits support
if (leanIMTData.size >= maxSignups) revert TooManySignups();
// ensure that the public key is on the baby jubjub curve
if (!CurveBabyJubJub.isOnCurve(_pubKey.x, _pubKey.y)) {
revert InvalidPubKey();
}
// Register the user via the sign-up policy. This function should
// throw if the user has already registered or if ineligible to do so.
signUpPolicy.enforce(msg.sender, _signUpPolicyData);
// Hash the public key and insert it into the tree.
uint256 pubKeyHash = hashLeftRight(_pubKey.x, _pubKey.y);
uint256 stateRoot = InternalLeanIMT._insert(leanIMTData, pubKeyHash);
// Store the current state tree root in the array
stateRootsOnSignUp.push(stateRoot);
emit SignUp(leanIMTData.size - 1, block.timestamp, _pubKey.x, _pubKey.y);
}
InitialVoiceCreditProxy
If you'd like to extend the functionality of how votes are distributed among users, you need to extend InitialVoiceCreditProxy contract. You can see our basic example how it's implemented for constant distribution.
contract ConstantInitialVoiceCreditProxy is InitialVoiceCreditProxy {
/// @notice the balance to be returned by getVoiceCredits
uint256 internal balance;
/// @notice creates a new ConstantInitialVoiceCreditProxy
/// @param _balance the balance to be returned by getVoiceCredits
constructor(uint256 _balance) payable {
balance = _balance;
}
/// @notice Returns the constant balance for any new MACI's voter
/// @return balance
function getVoiceCredits(address, bytes memory) public view override returns (uint256) {
return balance;
}
}
Poll Contract
On the other hand, the Poll contract can be inherited to expand functionality such as publishing of messages/commands, or the logic that allows users to register for the poll (AKA poll joining).
function joinPoll(
uint256 _nullifier,
PubKey calldata _pubKey,
uint256 _stateRootIndex,
uint256[8] calldata _proof,
bytes memory _signUpPolicyData,
bytes memory _initialVoiceCreditProxyData
) external virtual isWithinVotingDeadline {
// Whether the user has already joined
if (pollNullifiers[_nullifier]) {
revert UserAlreadyJoined();
}
// Set nullifier for user's private key
pollNullifiers[_nullifier] = true;
// Verify user's proof
if (!verifyJoiningPollProof(_nullifier, _stateRootIndex, _pubKey, _proof)) {
revert InvalidPollProof();
}
// Check if the user is eligible to join the poll
extContracts.policy.enforce(msg.sender, _signUpPolicyData);
// Get the user's voice credit balance.
uint256 voiceCreditBalance = extContracts.initialVoiceCreditProxy.getVoiceCredits(
msg.sender,
_initialVoiceCreditProxyData
);
// Store user in the pollStateTree
uint256 stateLeaf = hashStateLeaf(StateLeaf(_pubKey, voiceCreditBalance, block.timestamp));
uint256 stateRoot = InternalLazyIMT._insert(pollStateTree, stateLeaf);
// Store the current state tree root in the array
pollStateRootsOnJoin.push(stateRoot);
uint256 pollStateIndex = pollStateTree.numberOfLeaves - 1;
emit PollJoined(_pubKey.x, _pubKey.y, voiceCreditBalance, block.timestamp, _nullifier, pollStateIndex);
}
Tally Contract
Given the verification functions being exposed by the Tally contract, quadratic funding protocols might require to extend the Tally contract to add distribution logic. Looking at this example the claimFunds
function is added to a contract inheriting from Tally
, and uses functions such as verifyPerVOSpentVoiceCredits
to distribute funds to projects part of a quadratic funding round.
SDK
Another important component of MACI is the SDK. This is a TypeScript library that allows you to interact with MACI.
You can find the following subdirectories, where functions are organised as follows:
deploy
- For deployment related codekeys
- For utilities related to MACI keysmaci
- For functions that allow to interact with the MACI contract, such as user signuppoll
- For functions that allow to interact with the Poll contract, such as poll joiningproof
- For functions related to proof generation, and submission on chainrelayer
- For functions that allow to interact with the relayer servicetally
- For functions that allow to interact with the Tally contracttrees
- Utility functions to interact with MACI's merkle trees structuresuser
- Function related to user activities such as sign up and poll joiningutils
- Various utility functions that are used across the SDK and morevote
- Functions that allow to create MACI encrypted votes and submit them on chain
You should find all you need to integrate MACI into your application inside this package. If anything is missing, feel free to open an issue on our repo.