feat(torn): per-account voting power + Vault lock custody (gap #6)#2007
feat(torn): per-account voting power + Vault lock custody (gap #6)#2007Zeugh-eth wants to merge 3 commits into
Conversation
Tornado voting power = lockedBalance (no DelegateVotesChanged). Derive it from lock/unlock TORN Transfers in erc20.ts: - Treat governor (pre-v2) AND TornadoVault (post-v2) as lock sinks; the non-sink side is the user whose voting power changes. Fixes delegatedSupply which previously tracked governor-only and missed ~2.6M TORN in the Vault. - New lockedVotingPowerChanged handler populates accountPower.votingPower + votingPowerHistory (mirrors delegatedVotesChanged); deterministic, no hot-path RPC calls (indexer-skill safe). Requires a full reindex. Re-vote tally fix deferred (needs indexer test) — see INTEGRATION.md. Verify with the reconciliation smoke test.
|
This PR was not deployed automatically as @Zeugh-eth does not have access to the Railway project. In order to get automatic PR deploys, please add @Zeugh-eth to your workspace on Railway. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
TreasuryAddresses[TORN] is empty; governor commingles DAO treasury with pre-v2 user locks so its balance isn't a clean treasury figure. Document the correct approach + flag to confirm attack-profitability uses the on-chain liquid-treasury call vs the indexed metric.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f317b16d39
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // into either is a lock; out of either is an unlock. Pre-v2 used the governor. | ||
| // https://etherscan.io/address/0x2F50508a8a3D323B91336FA3eA6Ae50e55f32185 | ||
| const vaultAddress = getAddress( | ||
| "0x2F50508a8a3D323B91336FA3eA6Ae50e55f32185", |
There was a problem hiding this comment.
Use the valid checksum for the vault address
When DAO_ID=TORN, TORNTokenIndexer evaluates this getAddress() during handler registration. This mixed-case literal has an invalid checksum (the repository's existing vault spelling is ...A6ae50E55...), and viem rejects invalid checksummed addresses, so the TORN indexer fails before it can start. Reuse the existing constant or correct the checksum before passing it to getAddress.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8848fae7fe
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (fromIsSink && !toIsSink) { | ||
| await updateDelegatedSupply(context, daoId, address, -value, timestamp); | ||
| await lockedVotingPowerChanged(context, daoId, { | ||
| account: to, | ||
| delta: -value, |
There was a problem hiding this comment.
Don't classify every custody outflow as an unlock
When a proposal transfers TORN out of the governor/vault, this is not necessarily a user unlock: Tornado proposals execute by delegatecall and can move treasury/attack-recovery funds, and the 2023 exploit also changed locked balances in storage before draining custody. In those cases this branch debits the recipient with delta: -value even though the recipient was never credited by a lock Transfer, so a full TORN reindex can write negative accountPower.votingPower and corrupt active-supply/history views. Key this off an actual lockedBalance change or governance event/read rather than raw Transfer direction.
Useful? React with 👍 / 👎.
What
Makes TORN voting power real (per-account) and fixes locked-supply accounting — the central data gaps for the holders/delegates and attack-profitability views. Targets
feat/tornado-cash-integration.Built per the
dao-integration/anticapture-indexerskills: surgical, additive, deterministic, no hot-path RPC calls.1. Per-account voting power (was missing)
TORN voting power =
lockedBalancein the governor; there is noDelegateVotesChanged. We now derive each account's voting power from lock/unlock TORN Transfers:lockedVotingPowerChangedhandler (eventHandlers/delegation.ts) populatesaccountPower.votingPower+votingPowerHistory— mirrorsdelegatedVotesChanged.2. Vault lock custody — gap #6 (correctness)
Lock detection treated only the governor as custody, but post-v2
lockroutes TORN to the TornadoVault (GovernanceVaultUpgrade._transferTokens). We now treat governor + Vault as lock sinks (ignoring internal governor↔vault moves like the v2 migration). This recovers ~2.6M TORN of locked supply the governor-only logic missed (verified on-chain: governor 4.73M + vault 2.59M).Verification
API_BASE=...) and require 0 FAIL — specificallydelegatedSupply ≈ Σ lockedBalanceand sampledaccountPower.votingPower == lockedBalance(account).pnpm --filter=@anticapture/indexer typecheck && lintand a backfill.Deferred (notes in INTEGRATION.md)
VotedusesonConflictDoNothing(avoids Ponder's batch-flush crash); a correct re-vote fix must reverse+reapply while staying replay-idempotent and needs an indexer test. Re-votes appear infrequent.RewardUpdate*+readContract— only if a Transfer-derived discrepancy is found.Relationship to other PRs
feat/tornado-cash-integration.