fea: add distributor blacklist to ListaVault - #108
Merged
Conversation
The deployed VeLista impl introduced a freePenaltyPeriodNotStart modifier on lock(); on a current-block fork the period is already started and the test setUp reverts. Push freePenaltyStartTime past the test horizon via the MANAGER role (vaultAdmin) so locks succeed under the upgraded impl. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MANAGER can flag a distributorId as blacklisted; blacklisted ids receive zero new emissions and cannot be set in setWeeklyDistributorPercent. Already-allocated balances are not affected. Two enforcement gates: - setWeeklyDistributorPercent: revert "distributor blacklisted" — loud failure for OPERATOR rather than silent zero - getDistributorWeeklyEmissions: short-circuit to 0 — covers both the percent and emissionVoting paths, and blocks downstream allocateNewEmissions Storage: append-only mapping at the end of ListaVault state, preserves UUPS layout. Tests: 10 fork-based + 15 pure unit (mocks for VeLista/EmissionVoting) — covers access control, validation, both gates across all three emission paths, and downstream allocateNewEmissions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Swap the single-id setter for batchSetDistributorBlacklist(uint16[] ids,
bool blacklisted) — uniform flag across the array. Semantics adjusted:
- Empty array reverts ("ids is empty")
- Unregistered id in the batch reverts ("distributor not registered")
- Ids already in the target state are skipped silently (no event)
The silent skip makes the call idempotent so OPERATOR can replay batches
without curating the input list. State changes still emit one
DistributorBlacklistUpdated event per id.
Tests: 30 total (12 fork + 18 unit) — adds appliesToAllIds,
revertsOnEmptyArray, partialNoOpEmitsOnlyForChanged, plus the renamed
batchSet variants of every prior case.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…utors Drop the if (distributorBlacklist[id]) return 0; short-circuit in getDistributorWeeklyEmissions. Reading past percent/voting weights and short-circuiting on current blacklist state would retroactively erase emissions a distributor already earned in earlier weeks — they could no longer be claimed via allocateNewEmissions. Blacklist enforcement now lives at the input only: - setWeeklyDistributorPercent reverts on blacklisted ids → no NEW percent gets recorded for them, so future weeks naturally yield 0 - existing weeklyDistributorPercent[week][id] entries from prior weeks are honored when the distributor calls allocateNewEmissions - voting-path emissions for blacklisted ids are NOT blocked here — voters' choice prevails (separate governance concern) Tests: drop the three retroactive-zero cases, add three preservation cases (percent path, voting path, downstream allocate) plus future-gate verification. 30/30 pass (12 fork + 18 unit). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Remove unused Withdraw event declaration (I04) - Add indexed week parameter to Deposit event (I05) - Emit WeeklyDistributorPercentSet on weekly percent updates (I05) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a MANAGER-controlled blacklist on
ListaVaultso that specificdistributorIds can be cut off from new emissions without un-registering them. Already-allocated balances and unclaimed historical entitlements remain claimable. Includes one independent test-only fix that unblocksEmissionVoting.t.solunder the deployed VeLista's newfreePenaltyPeriodNotStartmodifier. Also folds in event-level cleanups surfaced by the external audit (I04/I05).Change type
test/EmissionVoting.t.sol)Contracts changed
contracts/dao/ListaVault.soltest/dao/ListaVault.t.soltest/dao/ListaVaultUnit.t.soltest/EmissionVoting.t.solInterface changes
New external function (MANAGER-only, batch):
"ids is empty"ifids.length == 0"distributor not registered"if anyidToDistributor[ids[i]] == address(0)(also rejects id=0)New event:
Emitted once per id whose state actually changed.
New public storage getter:
distributorBlacklist(uint16) → bool.Modified behavior:
setWeeklyDistributorPercent(...)— additionally reverts with"distributor blacklisted"if any id in the input array is blacklisted. This is the only enforcement gate — it stops new percents from being recorded for blacklisted ids.getDistributorWeeklyEmissions(...)andallocateNewEmissions(...)— unchanged. HistoricalweeklyDistributorPercent[week][id]entries set before a blacklist remain claimable; the distributor can still callallocateNewEmissionsand receive past-week amounts. (Voting-path emissions are also preserved — voter-driven allocations are a governance concern, not blocked here.)Event changes (audit follow-up — I04 / I05)
event Withdraw(address indexed, uint256)— removed (was declared but never emitted; I04).event Deposit(address indexed account, uint256 amount)→event Deposit(address indexed account, uint16 indexed week, uint256 amount)— week added, indexed for filtering (I05). ABI breaking for off-chain consumers: topic0 hash changes; downstream subgraph / indexers / dashboards / frontends must redeploy the new ABI in the same window as the on-chain upgrade.event WeeklyDistributorPercentSet(uint16 indexed week, uint16[] ids, uint256[] percents)— new, emitted at the end ofsetWeeklyDistributorPercentso weekly percent updates are observable on-chain (I05).No function signatures, modifiers, or other events were changed.
Storage layout
Confirmed via
forge inspect contracts/dao/ListaVault.sol:ListaVault storage-layout:emissionVoting(existing tail)contract IEmissionVotingdistributorBlacklist(new)mapping(uint16 => bool)All pre-existing variable slots are unchanged. New variable is appended at the end of contract storage — safe for the live transparent proxy upgrade. No
__gapwas used in the original contract; no gap accounting needed. The event-level changes (I04/I05) do not touch storage.Access control
batchSetDistributorBlacklistMANAGERkeccak256("MANAGER")setWeeklyDistributorPercentOPERATORregisterDistributorMANAGERNo new roles introduced. No changes to
_authorizeUpgrade,DEFAULT_ADMIN_ROLE,PAUSER, orOPERATORsemantics.Risk assessment
forge inspect).MANAGERrole. Validation rejects empty arrays and unregistered ids; idempotent on no-ops.EmissionVotingis active and voters allocate weight to a blacklisted id, that id will still receive emissions for the voted week. By design — voter governance is the source of truth for the voting path. Document for ops if a stronger policy is needed.Depositevent signature changed (I05). Existing indexers stop receivingDeposituntil they pick up the new ABI. Coordinate downstream redeploy with the on-chain upgrade window.Deployment
Transparent proxy upgrade on BSC mainnet via the standard TimeLock SOP:
0x07D274a68393E8b8a2CCf19A2ce4Ba35187352530x307d13267f360f78005f476Fa913F8848F30292A0xd6cd036133cbf6a275b7700ff7b41887a9d5fcae0x29202d64986097a099575807ed8284b0fd457167TimeLock target =
ProxyAdmin; calldata =upgradeAndCall(proxy, newImpl, ""). Schedule → 1-day delay → execute. No new env vars. Noreinitializer— new state defaults to zero/false, no migration call. End-to-end flow simulated on an Anvil BSC mainnet fork (schedule → time-skip → execute → impl slot confirmed at the new address).Audit
External audit on this branch (PR #108) returned Informational overall, 5 findings, all on
ListaVault.sol:setEmissionVotingmay erase historical voting-based emissions if called with unsettled weekssetWeeklyDistributorPercenttotalPercent has no lower boundReentrancyGuardUpgradeableinherited but not initialized / appliedWithdrawevent declarationceeede1ceeede1Test plan
forge build— no errorsforge test --match-path 'test/dao/ListaVault.t.sol'— 12/12 pass (BSC fork)forge test --match-path 'test/dao/ListaVaultUnit.t.sol'— 18/18 pass (pure unit, ~2ms)forge test --match-path 'test/EmissionVoting.t.sol'— 3/3 pass (was failing on master withfree penalty period start; fix included)🤖 Generated with Claude Code