Skip to content

Commit 0ec763e

Browse files
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 #1045
1 parent 794d762 commit 0ec763e

4 files changed

Lines changed: 839 additions & 0 deletions

File tree

contracts/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ members = [
1111
"contracts/payment-channel",
1212
"contracts/contract-upgrade",
1313
"contracts/allowance",
14+
"contracts/loyalty_rewards",
1415
]
1516

1617
[workspace.dependencies]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "loyalty_rewards"
3+
version = "0.0.1"
4+
edition = "2021"
5+
publish = false
6+
7+
[lib]
8+
crate-type = ["cdylib"]
9+
10+
[dependencies]
11+
soroban-sdk = { workspace = true }
12+
13+
[dev-dependencies]
14+
soroban-sdk = { workspace = true, features = ["testutils"] }

0 commit comments

Comments
 (0)