In the public testing thread, a tool satisfying the following 9 requirements was proposed as a compromise path:
This assessment evaluates the released repo against that nine-point standard.
Scope
This is a repo-state audit of the released CCF-DAO1-1/ccfdao-vote-auditor-rfc repository.
It is not an audit of:
- the whole DAO v1.1 stack across other repositories,
- a hypothetical future
docker-compose, - or a future self-hosted stack that does not exist in this repo yet.
The narrower question is: does the released reference implementation, as published, satisfy those 9 requirements?
Release status: Earlier public discussion described the fallback path as community-led in development, an internal version, or absent as a standalone audit tool. The later compromise thread led to this released reference implementation. Publishing it is a step forward for transparency, and this assessment covers this released state.
Verdict table
The table below is the short version. The sections that follow carry the detailed evidence behind each negative or partial verdict.
| Requirement | Verdict | Why |
|---|---|---|
| 1. Running locally | Partial | Local CLI, but remote data path remains. See section 1. |
| 2. Easy to set-up | No | User must provision three indexers. See section 2. |
| 3. Depending exclusively on on-chain data (via local full node) | No | Still uses public clients and external indexers. See section 1. |
| 4. Not depending on operator controlled services | No | Still depends on external indexers. See section 1. |
| 5. Allowing Whitelist generation | Partial | Internal rebuild exists, but it is only logged, not saved as an output artifact. See section 3. |
| 6. Allowing Whitelist audit | Partial | Root recomputation exists, but mismatch is non-fatal. See section 4. |
| 7. Allowing Whitelist proof of inclusion generation | Partial | Proof code exists, but no main CLI workflow. See section 3. |
| 8. Allowing Voting | No | This repo provides no qualifying independent fallback voting path. See section 5. |
| 9. Allow Vote auditing | Partial | Vote auditing exists, but key gaps remain. See sections 1 and 6. |
Findings
The sections below follow the table order: dependencies and setup first, then whitelist workflows, then voting and tally behavior.
1. The released tool still depends on public clients and indexers
Requirements 3 and 4 fail for the same reason: the released tool still depends on remote RPC and indexer infrastructure.
Indexer inputs: The CLI expects three separate indexer endpoints, either through CLI options or the corresponding environment-variable lookup:
--did-indexer--address-bind-indexer--dao-indexer
RPC path: The auditor instantiates the public mainnet client or public testnet client directly at the auditor call site instead of exposing a caller-provided local RPC endpoint in the CLI surface:
new ccc.ClientPublicMainnet()new ccc.ClientPublicTestnet()
Fetch path: The actual whitelist rebuild path then fetches data from those remote services:
- the DID set fetch
- the bind lookup
- the DAO stake lookup
Docs: The basic usage example documents the same external dependency model, the environment-variable section repeats those endpoints, and the notes section explicitly says:
Current implementation depends on 3 indexing services.
2. Setup still leaves indexer provisioning to the user
Requirement 2 fails because setup still assumes indexer infrastructure outside this repo.
Docs: The documented workflow expects the user to provide:
- three separate indexer URLs in basic usage,
- the same three services again in the environment-variable section,
- and, by the repo’s own notes section, a setup that depends on three indexing services.
Repo contents: The project structure section documents the auditor CLI and SMT tool, but no bundled indexer stack or self-contained setup path for those dependencies.
A bundled docker-compose, a self-hostable DAO UI, or an equivalent self-hostable voting path would change this section’s result only if it covered the dependent indexers and a usable voting path as well. Neither the released repo tree nor the documented project structure shows such a stack.
3. Whitelist generation and proof generation remain partial user-facing workflows
Requirements 5 and 7 are only partial: the repo contains the underlying logic, but the whitelist is only logged to stdout and proof generation is not exposed as a full end-user workflow.
Whitelist generation: The repo does rebuild a voter list internally:
The code logs voter map as part of the normal output stream, which is useful, but the repo’s own documented output files list vote CSVs and final results, not a saved whitelist artifact.
Proof generation: SMT proof generation also exists in code:
- the
generateSmtProof()helper - the Rust tool’s
Proofsubcommand and its proof output path
But this is not wired into the main CLI as a clear end-user workflow.
4. Whitelist audit exists, but whitelist failure is not fatal
Requirement 6 is only partial: the tool recomputes the whitelist root, but a mismatch does not stop the run.
- The audit loop recomputes the voter list SMT root and compares it against the
VoteMetaroot. - If the root matches, the loop breaks.
- If the root never matches, the program still proceeds into vote result processing and can still emit vote outputs.
5. The released repo does not provide an independent fallback voting path
Requirement 8 fails in this repo state: the released repo is an auditor, not a voting client.
The CLI exposes a single command:
audit
This is the entire CLI surface.
There is no command for:
- building a vote transaction,
- generating a vote witness for submission,
- or submitting a vote.
A voting UI exists elsewhere in the DAO v1.1 stack, but it is outside this repo and does not solve the same dependency problems identified in this assessment. That leaves this released repo without a qualifying independent fallback voting path.
6. Final vote weight calculation diverges from the documented DAO v1.1 flow
Requirement 9 is only partial for two reasons. One is the external-service dependency covered in section 1. The other is the end-height weight calculation, which still diverges from the documented DAO v1.1 flow in three places.
Self-exclusion bug
The end-height tally drops the voter’s own address whenever it appears in validVotes.
In the final tally stage, the auditor builds the end-height bind map from validVotes.keys(), queries DAO stake for the voting address plus its bound addresses in the end-height stake pass, then sums returned values only when !validVotes.has(addr).
Because every voteAddress in that second loop originated from validVotes, any stake returned for the voter’s own address is filtered out before accumulation. On successful responses, the current open-source DAO indexer’s /dao-stake-set handler iterates every requested address in ckb_list and inserts that address into the response map, so the queried voteAddress reaches daoResult before the auditor drops it.
This is not just a corner case. When a valid voter has no bound addresses, ckbList = [voteAddress, ...bindAddresses] collapses to the voter’s own address, so any successful end-height stake lookup becomes 0 after the self-address filter.
The bundled sample is consistent with that bug, but it does not isolate it. The only valid vote in c748442f-votes-valid.csv comes from the same address that has 20000000000000 in the earlier whitelist stake pass, but that whitelist pass runs at update height 20550000 while the final tally re-queries stake at end height 20630434. The later end-height stake pass logs 0 for that address, and the final c748442f-vote-results.csv plus console output both report 0 CKB, but those sample artifacts alone cannot distinguish a real stake change from the self-address filter. The static code path above is the direct proof.
Non-empty binding mismatch
The documented and open-source binding path returns objects, but the auditor treats them as strings.
The documented and open-source address-bind path returns /by_to_at_height response elements as {from, height, tx_index} objects, and the docs’ example response shows the same shape: [{"height":18861258, "tx_index":1, "from":"..."}]. The shipped app_view code extracts each record’s from before querying DAO stake.
The auditor instead types data as string[] and again as string[], then posts [voteAddress, ...bindAddresses] directly as ckb_list. The DAO indexer expects ckb_list: Vec<String>, so a non-empty binding result from the released service would be forwarded as objects unless the auditor first extracted each record’s from field. The bundled sample does not exercise this path because the only valid vote’s bind lookup reports 0 bind addresses.
pw lock omission
The released auditor omits pw lock weight even though the documented and shipped tally path includes it.
The docs say the end-height weight calculation includes bound addresses and pw lock. The shipped app_view get_weight() adds pw_lock_addr when present. The auditor has no corresponding step, and its own notes say: Current implementation does not handle pw lock automatic association.
The released auditor does not faithfully implement the documented tally flow. The docs say to aggregate stake to the voting address and then exclude only another address’s weight when that other address also voted. The shipped app_view tally code follows that narrower rule: get_weight() includes the queried address itself, and the final overlap filter preserves voter_ckb_addr == weight_addr.
This directly weakens requirement 9.
What the released repo does provide
The release still provides:
- an open-source standalone audit repository,
- on-chain
VoteMetaparsing, - whitelist reconstruction logic,
- SMT root recomputation,
- vote collection from chain data,
- CSV outputs for collected and filtered votes,
- and a Rust SMT implementation with a
Proofsubcommand and proof output.
These pieces let a reader parse VoteMeta, rebuild whitelist state, recompute the SMT root, collect votes, and inspect CSV outputs. They do not make this repo the locally runnable, operator-independent fallback stack described by the 9 requirements.
Bottom line
Even with those capabilities, the released reference auditor does not yet satisfy the 9-requirement compromise path.
The released repo still lacks:
- Voting: no qualifying independent fallback voting path in this repo; the separate DAO v1.1 UI has the same dependency problems
- RPC: continued dependence on the auditor call site for the public mainnet client or public testnet client
- Indexers: continued dependence on external indexers
- Workflows: whitelist, proof, and vote-audit flows remain partial
A bundled docker-compose, a self-hostable DAO UI, or another self-hostable voting path would change this assessment only if it covered the dependent indexers and a usable voting path.
Good step forward, Phroi