Commit b9fa7a4
committed
feat(contracts): loyalty rewards contract for consecutive on-time renewals (#1045)
Add a new `loyalty_rewards` Soroban contract that accrues loyalty points
for subscribers who renew on time, with streak tracking and a redeem path
against fees.
## Contract mechanics
- **accrue(owner, sub_id, renewal_ledger)** — called by the authorised
renewal caller after a successful on-time renewal. Awards
`BASE_POINTS` (100) + `streak × STREAK_BONUS` (50), with the streak
multiplier capped at `MAX_STREAK_BONUS_LEVEL` (20) to bound the max
per-renewal award at 1 100 points.
- **miss(owner, sub_id)** — called when a renewal is missed or late.
Resets the consecutive streak to 0; does not deduct points.
- **redeem(owner, amount)** — burns `amount` points from the owner's
balance and returns the equivalent fee credit (1 point = 1 credit unit).
Enforces a minimum redemption of 100 points.
## Storage layout
| Key | Storage tier | Description |
|-----|-------------|-------------|
| `ConfigKey::Admin` | Persistent | Contract administrator |
| `ConfigKey::RenewalCaller` | Persistent | Authorised renewal contract address |
| `ConfigKey::Paused` | Persistent | Global pause flag |
| `UserKey::Account(addr)` | Instance | Per-user `LoyaltyAccount` struct |
## Errors
| Code | Name | Trigger |
|------|------|---------|
| #1 | NotInitialized | Contract not yet initialised |
| #2 | AlreadyInitialized | `init` called twice |
| #3 | Unauthorized | Auth check failed |
| #4 | Paused | Contract is paused |
| #5 | RedeemTooSmall | `amount < MIN_REDEEM` |
| #6 | InsufficientPoints | Balance < requested amount |
| #7 | Overflow | Arithmetic overflow guard |
## Test coverage (35 tests)
- init: success + double-init guard
- accrue: base points, streak increment, bonus arithmetic, balance
accumulation, lifetime points, last-renewal ledger recording
- accrue: streak bonus hard-capped at MAX_STREAK_BONUS_LEVEL
- miss: streak reset, no point deduction, zero-streak noop, restart
after miss
- redeem: burns points + returns credit, full-balance drain,
total_redeems counter, lifetime_points invariant
- redeem error paths: below minimum, insufficient balance, empty account
- pause/unpause: accrue / miss / redeem all reject when paused
- set_renewal_caller: admin can rotate the caller address
- multiple independent users: balances and streaks are isolated
- account / streak / balance queries
- uninitialized contract guard
Closes #10451 parent 635ce95 commit b9fa7a4
4 files changed
Lines changed: 839 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| 14 | + | |
14 | 15 | | |
15 | 16 | | |
16 | 17 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
0 commit comments