feat: affiliate revenue share (REVSHARE)

  • Swap-path attribution (x/thorchain/swap_current.go) — Swapper.Swap calls AddAffiliateLiquidityFee for the swap memo's first affiliate when it resolves to a live thorname (streaming sub-swaps roll up under the parent's first affiliate). Internal protocol-managed swaps are explicitly skipped (see Fix #2 (closed)).
  • End-of-block payout (processRevShare in manager_network_current.go) — drains the accumulator, emits exactly one rev_share event per entry (even when bps=0 or the payout rounds to zero, so indexers never undercount), credits the AffiliateCollector, transfers RUNE from the Reserve, mirrors the preferred-asset flush trigger (see Fix #1 (closed)), and returns totalRevShare which calcBlockRewards subtracts from systemIncome before splits. | affiliate-revshare-preferred-asset.yaml | End-to-end: REVSHARE RUNE → conversion swap → BTC outbound to thorname's BTC alias (single swap, processRevShare triggers the flush directly after Fix #1 (closed)) |

Summary

Introduces a per-thorname affiliate revenue share of swap liquidity fees, gated by a new REVSHARE-<thorname> operational mimir (range [0, RevShareMaxBps], capped at 5000 bps). Payouts route through the existing AffiliateCollector module and are deducted off the top of end-of-block systemIncome before the standard splits (dev fund / burn / TCY / marketing / POL / bond/LP).

This is the complementary lever to ADR-016's ProtocolAffiliateFeeBasisPoints: where ADR-016 lets the protocol take a cut of the affiliate's user-side fee, REVSHARE lets the protocol give back a share of its own swap revenue.

See docs/architecture/adr-027-affiliate-revshare.md for the full design.

What the MR adds

  • Per-block accumulator (x/thorchain/keeper/v1/keeper_affiliate_liquidity_fee.go) — a thorname-keyed running sum of RUNE liquidity fees, with overflow guards on both the per-entry value and the running total. Returns a deterministically-ordered slice (lex by thorname) so the end-of-block payout loop is consensus-safe; the slice ordering contract is documented on the keeper interface itself.
  • Swap attribution — credits the first memo affiliate's accrued fees in swap_current.go; resolved thornames are upper-cased so the accumulator and rev_share event always carry the canonical key. Internal conversion swaps (preferred-asset flush, etc.) are skipped under both the PA and non-PA paths so protocol-initiated swaps don't double-count as user attribution.
  • Mimir validation (handler_mimir.go) — REVSHARE-<thorname> rejected when the thorname doesn't exist or value is out of [0, RevShareMaxBps]; classified operational so it churns out via PurgeOperationalNodeMimirs.
  • rev_share event type(thorname, owner, accrued_fee, bps, payout), emitted unconditionally per accumulator entry so indexers can track gross attribution independent of payout state (disabled REVSHARE, unresolvable thorname, rounded-to-zero payout).
  • End-of-block deduction with preferred-asset flushprocessRevShare deducts payouts before the standard reward splits, credits AffiliateCollector, and triggers the preferred-asset conversion flush so REVSHARE-funded balances convert and pay out the same way as ADR-016 affiliate fees.
  • ADR-027 plus updates to docs/concepts/fees.md, docs/affiliate-guide/, docs/concepts/economic-model.md, and docs/mimir.md.

Regression suites

Suite What it locks in
affiliate-revshare.yaml Happy path: REVSHARE-t=2500, single swap, rev_share event with rounded payout, AffiliateCollector credited
affiliate-revshare-disable.yaml Toggle on→off→on; bps=0 still emits the event with payout=0 (gross attribution preserved)
affiliate-revshare-streaming.yaml Streaming swap (0/1/3): per-block attribution rolls up under parent's first affiliate
affiliate-revshare-mimir-validation.yaml Set-time validation: nonexistent thorname rejected, value > RevShareMaxBps rejected, negative rejected, boundary accepted
affiliate-revshare-strict-first.yaml Memo order drives REVSHARE eligibility (not bps magnitude) — exactly one rev_share event for the first memo affiliate per phase
affiliate-revshare-multi-thorname.yaml Two thornames same-block produce events in deterministic lex order; AffiliateCollector module balance grows by sum of payouts
affiliate-revshare-preferred-asset.yaml REVSHARE payout into a thorname with a preferred asset triggers the conversion flush and BTC outbound end-to-end

Test plan

  • make test — unit tests for keeper accumulator (add/get/delete/overflow/corruption/ordering), swap attribution (resolution / internal-swap skip for both PA and non-PA cases), mimir validation, and the network-manager paths through processRevShare (deduction, expiry, missing thorname, explicit disable, no-mimir, multi-thorname-same-owner, Reserve-insufficient rollback, preferred-asset flush trigger, below-threshold no-flush, no-preferred-asset skip, rounded-to-zero event emission, drain-before-early-return).
  • CI=1 EXPORT=1 make test-regression — all seven REVSHARE suites plus the cross-suite check that the new rev_share event hasn't disturbed any existing fixture (api/quotes/swap, swaps/affiliates-preferred-asset, swaps/multiple-affiliates, swaps/preferred-asset-streaming-swaps, adv-swap-queue/preferred-asset-swaps, thorname-affiliate suites).
  • Confirm a rev_share event is emitted for an expired or unresolvable thorname (event with empty owner, payout=0) so indexers still see the attribution.
Edited by Huginn Ai

Merge request reports

Loading