Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(topup): check valid new state leaf balance #1225

Merged
merged 1 commit into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions circuits/circom/processMessages.circom
Original file line number Diff line number Diff line change
Expand Up @@ -401,8 +401,8 @@ template ProcessTopup(stateTreeDepth) {
// msgType of topup command is 2
amt <== amount * (msgType - 1);
index <== stateTreeIndex * (msgType - 1);
component validCreditBalance = LessEqThan(N_BITS);
// check stateIndex, if invalid index, set index and amount to zero

// check stateIndex, if invalid index, set index to zero
component validStateLeafIndex = LessEqThan(N_BITS);
validStateLeafIndex.in[0] <== index;
validStateLeafIndex.in[1] <== numSignUps;
Expand All @@ -419,17 +419,27 @@ template ProcessTopup(stateTreeDepth) {
amtMux.c[0] <== 0;
amtMux.c[1] <== amt;

// check less than field size
// check new balance is valid
signal newCreditBalance;
// add either 0 or the actual amount (if the state index is valid)
newCreditBalance <== stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] + amtMux.out;

// we need to ensure it did not overflow (so previous must be <= new)
component validCreditBalance = SafeLessEqThan(N_BITS);
validCreditBalance.in[0] <== stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX];
validCreditBalance.in[1] <== newCreditBalance;

// if the new one is <= the old one, then we have a valid topup
component creditBalanceMux = Mux1();
creditBalanceMux.s <== validCreditBalance.out;
creditBalanceMux.c[0] <== stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX];
creditBalanceMux.c[1] <== newCreditBalance;

// update credit voice balance
component newStateLeafHasher = Hasher4();
newStateLeafHasher.in[STATE_LEAF_PUB_X_IDX] <== stateLeaf[STATE_LEAF_PUB_X_IDX];
newStateLeafHasher.in[STATE_LEAF_PUB_Y_IDX] <== stateLeaf[STATE_LEAF_PUB_Y_IDX];
newStateLeafHasher.in[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] <== newCreditBalance;
newStateLeafHasher.in[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] <== creditBalanceMux.out;
newStateLeafHasher.in[STATE_LEAF_TIMESTAMP_IDX] <== stateLeaf[STATE_LEAF_TIMESTAMP_IDX];

component stateLeafPathIndices = QuinGeneratePathIndices(stateTreeDepth);
Expand Down
8 changes: 8 additions & 0 deletions core/ts/Poll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,14 @@ export class Poll implements IPoll {
const newStateLeaf = this.stateLeaves[stateIndex].copy();
// update the voice credit balance
newStateLeaf.voiceCreditBalance += amount;

// we should not be in this state as it means we are dealing with very large numbers which will cause problems in the circuits
if (newStateLeaf.voiceCreditBalance > SNARK_FIELD_SIZE) {
throw new Error(
"State leaf voice credit balance exceeds SNARK_FIELD_SIZE. This should not be a state MACI should find itself in, as it will cause complications in the circuits. Rounds should not accept topups with large values.",
);
}

// save it
this.stateLeaves[stateIndex] = newStateLeaf;
// update the state tree
Expand Down
43 changes: 43 additions & 0 deletions website/versioned_docs/version-v1.x/topup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
title: MACI Topup
description: How the Topup feature works
sidebar_label: Topup
sidebar_position: 19
---

# Topup

MACI v1.1.1 introduced the topup feature. This was developed by [chaosma](https://github.com/chaosma): you can find more information on their [post](https://hackmd.io/@chaosma/rkyPfI7Iq) and the original idea post [here](https://hackmd.io/@ef-zkp/rk6uaQBrI).

In a nutshell, this feature was added to support some application like quadratic funding, where once a user uses all of their voice credits when voting, they would need to signup again to be able to vote again. This is not ideal, and thus with this feature, a user can topup their voice credits, and continue to vote for a poll/quadratic funding round.

## How topup messages are processed

Topup messages are submitted on chain using the same smart contract used for voting, [`Poll`](https://github.com/privacy-scaling-explorations/maci/blob/dev/contracts/contracts/Poll.sol). With the current design, a TopUpCredit token smart contract is required. In this implementation of MACI, there is an example token, which is an ERC20 contract with 1 decimal, of which the coordinator (or MACI's operator) has ownership of. This privileged figure can mint tokens to users (at their discretion) and users will need to deposit such tokens to the Poll contract in order to topup their voice credits.

:::info
MACI is not concerned with the way of distributing such tokens. The operators of the MACI instance should implement their own way of distributing such tokens, as well as withdrawing them from the Poll contract. For instance, if the operator chooses to use a token with a monetary value, the Poll balance could be withdrawn and sent to fund public goods.
:::

To submit a topup message, a user will then need to call the TopUpCredit token approve function and approve the Poll contract to spend their tokens. Then, they will need to call the `topup` function in the Poll contract, passing the amount of tokens they want to topup and their state index.

## Considerations

### Voice credits

:::danger
As a MACI operator, you should be aware that voice credits should ideally be small values (< 2 \*\* 32) to avoid overflows or issues in the circuits operations. It is recommended to use a factor to scale down voice credits in the smart contract.
:::
ctrlc03 marked this conversation as resolved.
Show resolved Hide resolved

### Reverse processing

As with normal vote messages (or key changes), topup messages are processed in [reverse order](https://maci.pse.dev/docs/key-change/#why-are-messages-processed-in-reverse-order), you should be aware that as a user, you will first need to spend your voice credits, then topup.

Here is an [extract from Chao's post](https://hackmd.io/@chaosma/rkyPfI7Iq#Remarks):

> With above design, the order of vote type and topup type message matters. MACI process the message queue in reverse order. Suppose the initial > credit balance for any user is 100. Consider the following two scenarios in message queue:
>
> **Case 1** > [topup(balance=350), vote(weight=20,nonce=2), vote(weight=9,nonce=1)]
>
> **case 2** > [vote(weight=20,nonce=2), topup(balance=350), vote(weight=9,nonce=1)]
> The first case, the topup message is processed at last, so the vote(weight=20,nonce=2) will fail because 20 \* 20 > 100. vote(weight=9,nonce=1) is the final result. In the second case, the topup message is processed before the second vote, so vote(weight=20,nonce=2) will invalidate the first vote and become the final result.
Loading