Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
|
||
| constructor( | ||
| initialOwner: Either<ZswapCoinPublicKey, ContractAddress>, | ||
| signers: Vector<3, Either<ZswapCoinPublicKey, ContractAddress>>, |
There was a problem hiding this comment.
Why are we forcing the size of this vector to be 3?
There was a problem hiding this comment.
The initializer is generic in the module so it can support n signers in a for loop. The compiler needs fully determined types and circuit shapes so it can define the constraints. On the contract layer, we can't leave it as generic
There was a problem hiding this comment.
any workaround to be flexible and not have to create different contracts for 3,4,5... signers?
Maybe creating the size to be relatively big (ej 32 signers), and any extra signers not required to be initialized to a null address of some sort?
The initialize circuit in the module can then keep control of this null address and stop iterating to save on gas
There was a problem hiding this comment.
Soooo yes and no to your question. We can’t use a null address bc this assumes that all Signers will bottom out to a Bytes<32> type. The workaround for this workaround is to wrap the signers as Maybe<T> and hardcode 32 or w/e max amount in the module's initialize. This is what it looks like
export circuit initializePad32(
signers: Vector<32, Maybe<T>>,
thresh: Uint<8>
): [] {
assert(thresh > 0, "SignerManager: threshold must be > 0");
_threshold = disclose(thresh);
for (const signer of signers) {
// No early exit capability in compact
if (signer.is_some) {
_addSigner(signer.value);
}
}
assert(_signerCount >= thresh, "SignerManager: threshold exceeds signer count");
}
Compact does not support early exiting a loop ATM so there are no savings to be had (the break keyword is reserved though). I benchmarked both approaches:
circuit "initialize" (k=11, rows=1817)
circuit "initializePad32" (k=15, rows=23229) // 16x domain size, 12.8x constraints
If we wanted to consider this flexibility, it should not be enforced. The options as I see it:
- Have two initialize circuits that users can choose from
- And perhaps support a smaller amount of potential signers to bring down the cost
- Have the contract not use the initialize circuit and instead insert this logic directly in the preset contract's constructor. This undermines the module design, but we're not bloating the module code
- Accept the current tech stack limitation with generics. If users c/p the contract code, they can insert the appropriate number of signers in their contract. It's not genius engineering, it's just simple and efficient:
constructor(
signers: Vector<5, Either<ZswapCoinPublicKey, ContractAddress>>,
thresh: Uint<8>
) {
Signer_initialize<5>(signers, thresh);
}
|
The basic multisig proposed is just for shielded tokens, maybe worth changing the name/clearly document this |
Fixed 1b979e6 |
0xisk
left a comment
There was a problem hiding this comment.
Looking good @andrew-fleming Thank you! Left comments, questions, and suggestions. Also we need to add @circuitInfo for all.
| @@ -0,0 +1,310 @@ | |||
| pragma language_version >= 0.21.0; | |||
There was a problem hiding this comment.
| pragma language_version >= 0.21.0; | |
| // SPDX-License-Identifier: MIT | |
| // OpenZeppelin Compact Contracts v0.0.1-alpha.1 (access/ProposalManager.compact) | |
| pragma language_version >= 0.21.0; |
| @@ -0,0 +1,207 @@ | |||
| pragma language_version >= 0.21.0; | |||
There was a problem hiding this comment.
| pragma language_version >= 0.21.0; | |
| // SPDX-License-Identifier: MIT | |
| // OpenZeppelin Compact Contracts v0.0.1-alpha.1 (access/ShieldedTreasury.compact) | |
| pragma language_version >= 0.21.0; |
| @@ -0,0 +1,195 @@ | |||
| pragma language_version >= 0.21.0; | |||
There was a problem hiding this comment.
| pragma language_version >= 0.21.0; | |
| // SPDX-License-Identifier: MIT | |
| // OpenZeppelin Compact Contracts v0.0.1-alpha.1 (access/SignerManager.compact) | |
| pragma language_version >= 0.21.0; |
| @@ -0,0 +1,113 @@ | |||
| pragma language_version >= 0.21.0; | |||
There was a problem hiding this comment.
| pragma language_version >= 0.21.0; | |
| // SPDX-License-Identifier: MIT | |
| // OpenZeppelin Compact Contracts v0.0.1-alpha.1 (access/UnshieldedTreasury.compact) | |
| pragma language_version >= 0.21.0; |
| // ─── State ────────────────────────────────────────────────────── | ||
|
|
||
| ledger _nextProposalId: Counter; | ||
| ledger _proposals: Map<Uint<64>, Proposal>; |
There was a problem hiding this comment.
Why is it private? I think that should be exported so that the developer could easily fetch the whole state without only needing to use the getters. Because with the getters, the user would actually need to do txs with paying gas to call them and also wait for the generated proof, so the dev would simply fetch the public state and then be able to do all the getters but from the client-side, which will be for free and instant without any proofing time. Similar to this example from Lunarswap frontend to fetch all the pool data from the ledger: https://github.com/OpenZeppelin/midnight-apps/blob/7063bc0fb81b6d93d54c7953deb3f363f94dbcf5/apps/lunarswap-ui/lib/lunarswap-integration.ts#L188
There was a problem hiding this comment.
If I recall correctly, wasn't there discussion about having the getters be free (if they're not already somehow)? Either way, agreed for now 👍 will update
|
|
||
| // ─── Constant ─────────────────────────────────────────────────── | ||
|
|
||
| export circuit UINT128_MAX(): Uint<128> { |
There was a problem hiding this comment.
That is repeated in the ShieldedTreasury, that should be replaced by just one, maybe for now that can be added in the Utils.compact with a TODO to be removed once math lib is available.
| receiveUnshielded(disclose(color), disclose(amount)); | ||
|
|
||
| const bal = getTokenBalance(color); | ||
| _balances.insert(disclose(color), disclose(bal + amount as Uint<128>)); |
There was a problem hiding this comment.
Maybe that is a dump question, but is it possible that the user sends tokens to the contract directly without using _deposit(), which has the receiveUnshielded(). Just need to double check this, my understanding is that it is not possible, but I might be wrong. As this might cause out-sync of the balances map.
There was a problem hiding this comment.
My understanding is that this is not possible. From the docs (ref):
Sending tokens to contracts is only possible if contract claims the tokens, which means an explicit contract interaction needs to be involved.
This was something we tried and confirmed a long time ago, didn't we? It wouldn't hurt to reconfirm
| ledger _shieldedReceived: Map<Bytes<32>, Uint<128>>; | ||
| ledger _shieldedSent: Map<Bytes<32>, Uint<128>>; |
There was a problem hiding this comment.
| ledger _shieldedReceived: Map<Bytes<32>, Uint<128>>; | |
| ledger _shieldedSent: Map<Bytes<32>, Uint<128>>; | |
| ledger _received: Map<Bytes<32>, Uint<128>>; | |
| ledger _sent: Map<Bytes<32>, Uint<128>>; |
nit: I think that's better
| const currentSent = getSentTotal(color); | ||
| assert(currentSent <= UINT128_MAX() - amount, "ShieldedTreasury: overflow"); |
There was a problem hiding this comment.
I think it is better to check this overflow from the beginning before the send operation.
| const newCount = _signerCount - 1 as Uint<8>; | ||
| assert(newCount >= _threshold, "SignerManager: removal would breach threshold"); | ||
|
|
||
| _signers.remove(disclose(signer)); |
There was a problem hiding this comment.
If we mean to use ledger _signers: Map<T, Boolean>; then shouldn't it be just an update with false here.
No description provided.