Skip to content

Commit

Permalink
fix(topup): check valid new state leaf balance
Browse files Browse the repository at this point in the history
  • Loading branch information
ctrlc03 committed Feb 22, 2024
1 parent c255879 commit c12d79c
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 4 deletions.
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
1 change: 1 addition & 0 deletions core/ts/Poll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,7 @@ export class Poll implements IPoll {
const newStateLeaf = this.stateLeaves[stateIndex].copy();
// update the voice credit balance
newStateLeaf.voiceCreditBalance += amount;

// 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), ands 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 ownerhsip of. This privilieged 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 choses 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.
:::

### Reverse processing

As with normal vote messages (or key changes), topup messages are processed in revere 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.

0 comments on commit c12d79c

Please sign in to comment.