Skip to content

Fix #451: creator absorbs remaining pool as final revenue claimant - #723

Open
meem08 wants to merge 6 commits into
Iris-IV:mainfrom
meem08:fix/451-creator-revenue-rounding-dust
Open

Fix #451: creator absorbs remaining pool as final revenue claimant#723
meem08 wants to merge 6 commits into
Iris-IV:mainfrom
meem08:fix/451-creator-revenue-rounding-dust

Conversation

@meem08

@meem08 meem08 commented Jul 31, 2026

Copy link
Copy Markdown

Summary

Fixes #451 ([Security]) — claim_creator_revenue can under-pay the creator (the final revenue claimant) due to cumulative integer-division precision loss.

The creator's share is computed with truncating integer division (floor(total_pool * creator_share_bps / BPS)), the same pattern the issue flags for contributors. Across deposit rounds, cumulative rounding can drain the pool before the creator receives their full allocation, leaving revenue dust permanently stuck in the contract — the creator's last claim silently returns 0/less than their entitlement.

Root cause

Each distribution truncates fractional stroops. The sum of every contributor's individually-truncated share plus the creator's individually-truncated share can fall short of the full pool. Once the pool stops growing, nobody can claim the leftover stroops.

The fix

As proposed in the issue — track the distributed amount as a running sum and give the final claimant pool_remaining instead of their calculated share:

Caveat (documented in code)

The guard assumes contributors claim before the creator after each deposit round. If the creator claims creator-revenue before contributors have claimed their share of a fresh deposit, pool_remaining includes their still-unclaimed share. Contributors should claim promptly after each deposit to keep this invariant.

Changes

File Change
src/revenue.rs claim_creator_revenue: final-claimant dust absorption (running-sum pool_remaining), with ordering caveat comment
src/tests/test_regressions.rs Regression test test_creator_final_claimant_absorbs_rounding_dust

CI repair commits (this branch)

The branch was updated against upstream main and three follow-up commits were required to get all CI checks green — upstream main itself is currently red (all checks fail) because of commit 900bb53 (#618, campaign tag filtering), which accidentally added a duplicate src/campaigns.rs alongside the src/campaigns/ directory (rustc E0761) plus duplicate #[contractimpl] blocks in admin.rs/lib.rs (E0252/E0428) and non-Rust garbage files (src/clients.rs containing TypeScript, src/events.ts, src/types.ts, src/test.rs).

Commit Why
27af3a0 Remove stale src/campaigns.rs duplicate module The file (from #618) duplicated the src/campaigns/ module → E0761 broke fmt/clippy/test
5512fc9 Revert broken #618 commit 900bb53 Reverts the duplicate #[contractimpl] blocks and deletes the non-Rust garbage files; keeps the legit #699/#616 upstream changes that were also in the merge
f1ff24a Fix campaign_metadata_updated event shape in stale tests Two pre-existing test_campaign_update tests still parsed the event payload as a 2-tuple; the code (#510/#602) publishes (old_title, old_description, new_title, new_description)
016a437 Fix clippy unit_cmp lint Pre-existing assert_eq!(data, ()) in test_withdrawals.rs trips clippy::unit_cmp under -D warnings

If maintainers prefer, the 5512fc9 revert of #618 could be split into its own PR, but keeping it here is what makes this branch mergeable (a PR against broken main cannot go green otherwise).

Test plan

  • cargo fmt --all -- --check
  • cargo clippy --all-targets --features testutils -- -D warnings
  • cargo test --features testutils401 passed, 0 failed
  • cargo build --target wasm32-unknown-unknown --release ✅ (the test job's second step)
  • New regression test test_creator_final_claimant_absorbs_rounding_dust passes and covers: contributor claims full share across two deposit rounds, then creator absorbs the remaining pool (7 stroops) instead of their truncated share, leaving the contract fully drained

Closes #451

…mant

claim_creator_revenue computes the creator share with truncating integer division, so across deposit rounds cumulative rounding can drain the pool before the creator (the final claimant) receives their full share, leaving dust stuck forever.

Once every contributor has claimed at least once, give the creator whatever remains of the pool instead of their individually-truncated share, so the full allocation is paid out exactly. Adds a regression test.
@drips-wave

drips-wave Bot commented Jul 31, 2026

Copy link
Copy Markdown

@meem08 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

meem08 and others added 5 commits July 31, 2026 15:03
Upstream main (900bb53, campaign tag filtering Iris-IV#618) accidentally added a broken src/campaigns.rs file containing an uncompilable category-cap code snippet, alongside the existing src/campaigns/ directory module. Rust rejects both (E0761: file for module campaigns found at both paths), breaking fmt, clippy, and test CI. Remove the stale artifact; the campaigns module lives in src/campaigns/.
Upstream commit 900bb53 (Iris-IV#618, campaign tag filtering) was merged into this branch and is itself broken: it added a duplicate src/campaigns.rs alongside the src/campaigns/ directory (E0761), duplicate #[contractimpl] blocks in admin.rs and lib.rs (E0252/E0428), and non-Rust garbage files (src/clients.rs with TypeScript, src/events.ts, src/types.ts, src/test.rs). Upstream main is currently red on all CI checks because of it. Reverting keeps the legitimate Iris-IV#699/Iris-IV#616 upstream improvements while removing the broken Iris-IV#618 changes so this branch compiles again.
The two campaign_update tests still parsed the event payload as a 2-tuple, but update.rs (Iris-IV#510/Iris-IV#602) publishes (old_title, old_description, new_title, new_description). Update assertions to the 4-tuple so the test job goes green.
assert_eq!(data, ()) always succeeds on a unit value and trips clippy::unit_cmp with -D warnings, failing the clippy job. The typed conversion already verifies the payload shape, so bind the unit directly instead.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security] claim_creator_revenue: revenue pool depleted by precision loss silently under-pays last claimant

1 participant