57885 sc high dynamic share drift in royaltiesreceiverv2

Submitted on Oct 29th 2025 at 11:31:04 UTC by @komane007 for Audit Comp | Belongarrow-up-right

  • Report ID: #57885

  • Report Type: Smart Contract

  • Report severity: High

  • Target: https://github.com/immunefi-team/audit-comp-belong/blob/main/contracts/v2/platform/extensions/ReferralSystemV2.sol

  • Impacts: Permanent freezing of funds

Description

Brief / Intro

RoyaltiesReceiverV2 recalculates royalty shares on every payout using live values from Factory (creator/platform split and referral tier). Its accounting (PaymentSplitter-style) assumes shares are fixed over time. When shares change after any past releases (e.g., referral tier auto-increases), the newly computed entitlements implicitly apply the new shares to all historical inflows, causing over-allocation. As a result, release / releaseAll can revert and royalties can be frozen until substantial new funds arrive—sometimes indefinitely.

Vulnerability Details

  • Dynamic shares are read at payout time:

// contracts/v2/periphery/RoyaltiesReceiverV2.sol
function shares(address account) public view returns (uint256) {
    Factory.RoyaltiesParameters memory p = factory.royaltiesParameters();
    if (account == royaltiesReceivers.creator) {
        return p.amountToCreator;                   // dynamic creator share
    } else {
        uint256 platformShare = p.amountToPlatform; // dynamic platform share
        uint256 referralShare;
        if (royaltiesReceivers.referral != address(0)) {
            referralShare = factory.getReferralRate(
                royaltiesReceivers.creator, referralCode, p.amountToPlatform
            );                                      // dynamic referral share (tier-based)
            if (referralShare > 0) {
                unchecked { platformShare -= referralShare; }
            }
        }
        return account == royaltiesReceivers.platform
            ? platformShare
            : account == royaltiesReceivers.referral ? referralShare : 0;
    }
}
  • PaymentSplitter-style accounting assumes static shares:

  • This formula is correct only if shares(account) has been constant since inception. If shares(account) increases later, the recomputed cumulative entitlement exceeds the funds actually received/retained by the contract, producing an oversized payment.

  • Referral tiers auto-increase with normal usage (no admin needed):

  • Therefore, shares can drift organically as the referral is exercised—no upgrade or admin action required.

Simple scenario (numbers)

  • T0: 100 ETH received. Shares = Creator 80%, Platform 20%, Referral 0%.

    • releaseAll pays 80/20. Balance = 0, totalReleased = 100.

  • T1: Referral tier rises to 5% (Platform → 15%, Referral → 5%).

    • Referral pending = (0+100) × 5% − 0 = 5 ETH > balance(=0) → release reverts.

  • T2: 10 ETH arrive. Creator pending = (10+100) × 80% − 80 = 8 ETH (ok), Referral pending = (10+100) × 5% − 0 = 5.5 ETH. If creator withdraws first, remaining balance may be < 5.5 ETH → referral withdrawal reverts; releaseAll can fully revert.

Impact Details

  • Permanent / long-lived freezing of royalties

    • When shares increase after earlier payouts, newly computed entitlements can exceed the contract balance, causing release / releaseAll to revert. Future small inflows may still be insufficient to cover the recomputed arrears, keeping royalties frozen.

  • Over/under-payment drift

    • If shares increase, affected payees are over-allocated relative to the actual historical split; if shares decrease, others are underpaid. The design no longer conserves balances across time, breaking accounting accuracy.

  • Severity (program scope mapping)

    • High/Critical: Permanent freezing of funds; asset accuracy/accounting drift; DoS of payout flows.

References

  • Dynamic share computation: contracts/v2/periphery/RoyaltiesReceiverV2.solshares()

  • Pending payment formula assuming static shares: contracts/v2/periphery/RoyaltiesReceiverV2.sol_pendingPayment()

  • Auto-increment referral tiers: contracts/v2/platform/extensions/ReferralSystemV2.sol_setReferralUser(); called from contracts/v2/platform/Factory.solproduce()

Proof of Concept

Add this test to /home/jo/audit-comp-belong/test/v2/platform/factory.test.ts and run:

LEDGER_ADDRESS=0x0000000000000000000000000000000000000001 PK=0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef npx hardhat test test/v2/platform/factory.test.ts --grep "Security: royalties receiver drift"


If you want, I can:

  • Produce suggested remediations (e.g., snapshotting fixed shares per release, storing historical totals per-account, or converting to per-inflow accounting), or

  • Draft a minimal patch suggestion to make shares static for accounting purposes while preserving referral mechanics.

Was this helpful?