Making contributions to a funding round

@auryn and I were discussing the contibutor’s UI in Figma, and this came up:

There are four possible states here:

  1. No funds contributed, no projects in cart
  2. No funds contributed, projects in cart
  3. Funds contributed, no projects in cart
  4. Funds contributed, projects in cart

In 1 and 3, there is no need to show sliders because funds haven’t been contributed and/or there are no projects to display a slider on.
In 2 and 4, we should display the sliders, whether or not the user has already deposited funds to the round or sent the message containing their vote.

If the user hasn’t contributed any funds to the round yet (2), then the “fund” button should trigger a transaction that sends funds to the round and submits their message.

If the user has contributed funds already(4), then the “fund” button should just send a new message to replace their old message.

If the user is in state (4) but allocates more funding than they have deposited, then the “fund” button should trigger a transaction to deposit the additional funds and send the updated message.

In this post I want to address some of these points in more detail.

If the user hasn’t contributed any funds to the round yet (2), then the “fund” button should trigger a transaction that sends funds to the round and submits their message.

There are three stages in MACI lifecycle:

  1. Sign-up period. It starts immediately after the deployment of MACI contract. During the sign-up period voters should call signUp() method, which will register their public key and calculate the amount of voice credits they have (the amount of voice credits can not be changed later). Users can’t submit messages during this period.
  2. Voting period. It starts immediately after the sign-up period. During that period users (or someone on their behalf) can submit messages to MACI by calling the publishMessage() method. Sign-ups are not allowed anymore during this period.
  3. Processing period. It starts immediately after the voting period and lasts until the coordinator completes all the necessary operations.

As we can see, the sign-up and voting periods don’t overlap, so it’s not possible to sign up and submit message at the same time, unless we store the message somewhere and submit it later on behalf of a user (but we should avoid it, as it could require a lot of gas). This means that users will be probably required to interact with our system at least twice: when they contribute money (during the sign-up period) and when they vote (during the voting period).

Deploying the MACI at the end of the round doesn’t help, but adds even more friction, as users will need to interact with our system three times (again, unless we sign up and publish messages on behalf of a user):

  1. During the funding period, when they just contribute the amount they want.
  2. During the signup period, after the instantiation of MACI.
  3. During the voting period.

If the user is in state (4) but allocates more funding than they have deposited, then the “fund” button should trigger a transaction to deposit the additional funds and send the updated message.

It’s not possible to add voice credits once the user has been signed up. We can introduce the “funding period”, during which users will be able to deposit additional funds (we don’t actually need to instantiate MACI at the end of the round to do that), but that would require an additional interaction with our system, as I showed in a previous paragraph.

2 Likes

Now we’re talking :slight_smile: This is very useful to know, now we have several scenarios and user flows to consider which I wasn’t aware earlier (hence my questions in Figma).
I’ll review all above and your feedback in Figma and make revisions accordingly.

2 Likes

Following up our discussion from Figma here’s a quick diagram on the user flow. Feel free to comment in file directly https://www.figma.com/file/HotZCx3zNptdm0wCzXtZKf/clr.fund?node-id=223%3A137

1 Like

This is exactly what we had planned in the original build. Grant it is gas intensive on whoever signs up, contributes, and submits votes on behalf of the user (we had envisioned this as part of the coordinator’s responsibilities), but it allows for a much better user experience; users only have to interact once and can make additional contributions throughout the round if they like.

If the signup period is and voting period cannot overlap, it will nerf some the virility of the Quadratic Funding mechanism, because contributors can’t complete the viral loop in one sitting.

In my opinion, the UX improvement is worth the additional gas cost on the coordinator.

@weijiekoh I know we talked about this in the past. If I recall correctly, we had discussed the ability to have the signup and voting periods overlap, allowing users to:

  1. signup at any point before the round closes
  2. contribute and top up at any point between signing up and the round closing
  3. submit vote messages at any point between signing up and the round closing

Was there ever any progress made on this?

If 500 users each contribute to 5 projects, coordinator will have to make 2500 publishMessage() calls. If we take into account current gas prices, that would roughly translate into $2500 in fees. The system will become less autonomous and more dependent on the coordinator and their funding. It also allows coordinator to censor votes, because the MACI contract can not know whether all messages have been submitted or not.

I think you’re baking in an assumption on that these are all separate transactions. I’m sure we could amortize the transaction costs by batching function calls into larger transactions (constrained by the block gas limit, of course).

Also, a message can contain multiple votes, no? So 500 users contributing to 5 projects each would only require 500 messages, so far as I’m aware.

I think the dependence on the coordinator is kind of a moot point because the system is already quite dependent on them.

Yes, we can batch them. I think we need to do that even in case when contributors submit votes by themselves, to avoid asking them to confirm each publishMessage() call separately. I created a new issue for that: https://github.com/clrfund/monorepo/issues/59. I’m going to implement it and measure the gas costs.

No, according to MACI docs a message can contain only a single command.

I am against the idea of giving them even more power. But for the PoC we can make this tradeoff, in hope that future versions of MACI would allow signups and voting at the same time:

The sign-up period ends after a predefined deadline. A later version of MACI will allow ongoing sign-ups where state trees will be merged once per week.

(source)

1 Like

Hi all! Following up from the User Flow diagram and discussion in Figma comments here’s a demo prototype covering main scenarios we identified. https://share.getcloudapp.com/NQugYog0

You can try it yourself here https://www.figma.com/proto/HotZCx3zNptdm0wCzXtZKf/clr.fund?node-id=273%3A2&viewport=342%2C185%2C0.14486059546470642&scaling=scale-down-width

Let me know (in Figma) if you have any comments. Thanks!

1 Like

@markop, this is looking really great!

I implemented the submitMessageBatch() method and did the measurements.

Direct call to publishMessage(): ~756000 gas

Batch submission:

  • 1: 688190 gas (no idea why it costs less than direct call :smile: )
  • 2: 1307320 gas
  • 3: 1921400 gas
  • 10: 6220909 gas (~18% cheaper than doing 10 direct calls).

Submitting 100 messages (100 users 1 vote each) today would cost us: 6220909 gas * 10 * 40 Gwei = 2.4883636 ETH = 573.4 USD.
Submitting 2500 messages (500 users 5 votes each) today would cost us 14335 USD.

What I’m hearing is that we should be building this whole thing on Optimistic Rollup, lol.

Seriously though that’s super expensive.

2 Likes

clr.fund cart flow

Here is my latest thoughts on how our cart should look.
I’ve done away with the sliders and anything else fancy, it is now much more reminiscent of a typical eCommerce shopping cart, where you add items to the cart, modify their quantities once they are in the cart, then checkout and pay the total.

There seems to be some conflicting opinions on whether or not the signup and voting periods can overlap. If they can, they should both happen concurrently, as this will greatly simplify the UX and its implementation.

If not, I suggest that we create a natural feeling UX by having the signup period for the following round run concurrently with the voting period of the current round.

If Signup and Voting can overlap

In the case the Signup and Voting can overlap, then the checkout flow should look something like this.

  1. User adds items to cart and inputs DAI values to contribute to each cart item

    1. Cart state should be stored in local storage and/or 3Box private storage
  2. User hits “submit”

    1. This triggers a single batch transaction, using the contract proxy kit, that calls contribute() for the total DAI value in the cart, then signUp(), and then publishMessage() for each of the items in the cart.

    2. Contribution history should be stored in local storage or 3Box private storage

  3. After submitting, users can return and continue to modify their cart; adding and removing items and changing contribution amounts.

  4. If user hits “submit” again, a new batch transaction is created, including a contribute() call for the difference in DAI if they have now spent more than they contributed, and a publishMessage() call for each new or updated cart item.

If Signup and Voting cannot overlap

In the case that Signup and Voting can overlap, then the signup period for the next round should happen concurrently with the voting period of the current round. In this way, any new contributors can immediately sign up and start adding items to the cart.

In this case, we will make use of Ethereum Alarm Clock so that contributors can perform all of their actions in one step; signing up, contributing, and then providing a batch of messages that can be executed once the voting period has started.

The flow will look something like this.

  1. User adds items to cart and inputs DAI values to contribute to each cart item

    1. Cart state should be stored in local storage and/or 3Box private storage
  2. User hits “submit”

    1. This triggers a single batch transaction, using the contract proxy kit, that calls contribute() for the total DAI value in the cart, then signUp(), and then an Ethereum Alarm Clock call (to be triggered shortly after the block when the voting period starts) to publishMessage() for each of the items in the cart, along with an Ethereum Alarm Clock call to signup() for the following round.
    2. Contribution history should be stored in local storage or 3Box private storage
  3. After submitting, users can return and continue to modify their cart; adding and removing items and changing contribution amounts.

  4. If user hits “submit” again prior to the voting period, a new batch transaction is created, including a contribute() call for the difference in DAI if they have now spent more than they contributed, and a Ethereum Alarm Clock call to publishMessage() for each new or updated cart item.

  5. If user hits “submit” again after the voting period has started, a new batch transaction is created, including a contribute() call for the difference in DAI if they have now spent more than they contributed, and a publishMessage() call for each new or updated cart item.

cc. @xuhcc, @weijiekoh, @proofoftom, @spengrah, and @markop

1 Like

That’s great!

Currently the process consists of two non-overlapping stages. But we discussed this issue with Wei Jie and it seems that MACI could be changed to allow signup and voting at the same time. Here’s the corresponding issue in our tracker: https://github.com/clrfund/monorepo/issues/63. It’s a relatively simple change and I think we’ll do that for PoC, so in this post I’ll focus on If Signup and Voting can overlap scenario.

No need to call signUp() separately, it is done automatically when user calls contribute().

We’ll have a method for batching messages, it is already implemented here: https://github.com/clrfund/monorepo/pull/69. If signup and voting periods will overlap, then we can do everything in one call, so contract proxy kit is redundant. However, it means that all submitted messages will be linked to user’s account, and we need to think of how that could be exploited by the bribers.

Currently it is not possible to change the amount of voice credits after signup. There’s a corresponding issue in the MACI repo: https://github.com/appliedzkp/maci/issues/103, so maybe it will be possible in the future, but I don’t think that we need this functionality in PoC.

1 Like

Thanks for the writeup @auryn.
My question may be uninformed but I don’t understand why or how a user can return and modify the cart after submitting the first time? Why is this functionality needed? What are the benefits?

Anyhow, on the frontend, this action and what happens/happened in the background needs to be clearly communicated to the user. It adds complexity to the whole process, is doable of course, I’m just questioning the purpose of it.

Whatever the outcome of this decision will be, please let me know.

Thanks.

I think this is a good thing to call out. It’s something that we had planned for since it’s possible on the contract level.

But just because it is possible on the contract level doesn’t mean we have to expose it in the frontend.

In this case, rather than users being able to return to and continue to modify their cart after checkout, the cart would be cleared after checkout. Any additional contributions after this would feel like a new batch of contributions, always involving an additional DAI contribution.

I see two challenges with this option:

  1. We’ll probably also need to create a “contribution history” screen, so users can tell which projects they have already contributed to, since this information won’t be readily available in their cart.
  2. This might introduce some UI challenges when we start thinking about key switching at a later date. For anti-collusion purposes, the user needs to be able to switch their signing keys and post a new batch of messages that invalidate the old messages. If updating/modifying your already checked-out cart is already part of the UX, then this will feel more natural.

Hhhmmm, there is still two separate calls, contribute() and submitBatchMessage(), so CPK would still improve the UX.

I think that might be out of scope for PoC and MVP.
Beyond MVP, I’d imagine this is resolved allowing users to switch out their eddsa key and then submit their votes from another account.

@proofoftom seemed quite confident that this was already possible, or at least that this is what they are designing for in the MACI UI.
But yeah, I agree that this is probably not necessary for PoC.

There are two calls because https://github.com/clrfund/monorepo/issues/63 is not implemented yet. We can do it in one call, it’ll require a small change to the contract.

I don’t think that bribery is out of scope. If it is, there’s no reason to use MACI at all in MVP.

If I undestand correctly the vote-buying attack described here, if we do signup and voting in one call, the attacker may ask the voter to submit exactly one message, so he can infer that this message is a first message and therefore not a key-changing message.

I think ideally we need to submit all messages through a relayer, so attackers couldn’t figure out the number of messages submitted by a particular user by looking at on-chain data.

I agree with that, we need the cart to remain after submission until the end of the voting period to enable key-changing operations.

1 Like

I’m not saying that bribery protection is out of scope, that’s obviously why we are using MACI.
What I’m suggesting is that the MVP UI doesn’t necessarily need to support these things, so long as they can be accomplished by interacting with the chain directly.

I think this is only an issue if the batch message is the ONLY way to submit a message. This may be an argument for using something like CPK; so that batching is handled outside of our contracts and MACI.

Yeah, we’ve talked about the idea of relayers before. I think it’s definitely something we should explore in future. But to start with, I think the only thing we need is for any ethereum address to be able to call publishmessage(), so that users can create messages and publish them with a different address to the one they used to deposit funds. This way they can make key changes and new votes and a briber has no way of knowing.

Regarding the question of a user modifying their cart after submitting:

This is a pretty important feature, for a couple reasons:

First, as @auryn already mentioned, it will be required to support key switching once we enable that for anti-bribery/collusion purposes.

Second, one of the more powerful aspects of QF is its viral nature. Gitcoin Grants has succeeded largely on the fact that people tweet the hell out of it, including when they make contributions – “I gave one DAI but got matched 50 DAI!!!”.

In order for us to fully capitalize on that viral effect, we’ll want contributors to contribute (and tweet) early; but they will be hesitant to do so if that means giving up the flexibility to make more contributions later.

That said, allowing contributors to add contributions is definitely not required for the various mvp rounds.

2 Likes