Skip to content

RWA: balance and supply compliance modules#651

Open
pasevin wants to merge 1 commit intoOpenZeppelin:mainfrom
pasevin:feat/rwa-balance-supply-standalone
Open

RWA: balance and supply compliance modules#651
pasevin wants to merge 1 commit intoOpenZeppelin:mainfrom
pasevin:feat/rwa-balance-supply-standalone

Conversation

@pasevin
Copy link
Copy Markdown
Contributor

@pasevin pasevin commented Mar 23, 2026

Summary

Adds balance and supply enforcement modules for RWA tokens: one module enforces per-identity maximum holdings and one module caps total token supply.

Changes

  • Library: adds max_balance and supply_limit compliance modules with storage, hook logic, and focused unit coverage.
  • Examples: adds rwa-max-balance and rwa-supply-limit example crates.
  • Workspace: includes the new example crates in the workspace and module exports.

Test plan

  • cargo +nightly fmt --all -- --check
  • cargo test -p stellar-tokens --lib balance
  • cargo test -p rwa-max-balance --lib
  • cargo test -p rwa-supply-limit --lib

Transplant the reviewed max balance and supply limit modules plus their example
crates onto upstream/main so they can ship independently of the old stack.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 23, 2026

Walkthrough

Introduces two new Soroban compliance modules for Stellar RWA tokens: MaxBalance enforces per-token, per-identity balance caps using an Identity Registry Storage contract, while SupplyLimit enforces per-token mint supply limits. Both include trait definitions, storage implementations, example contract implementations, comprehensive tests, and documentation.

Changes

Cohort / File(s) Summary
Workspace and Build Configuration
Cargo.toml, examples/rwa-max-balance/Cargo.toml, examples/rwa-supply-limit/Cargo.toml
Updated workspace members to include two example crates; added Cargo manifests for each example defining library targets with cdylib and rlib crate types.
MaxBalance Compliance Module
packages/tokens/src/rwa/compliance/modules/max_balance/mod.rs, packages/tokens/src/rwa/compliance/modules/max_balance/storage.rs, packages/tokens/src/rwa/compliance/modules/max_balance/test.rs
Implements MaxBalance trait with identity-based per-token balance cap enforcement via on_transfer, on_created, on_destroyed hooks, identity-to-address mapping through IRS client, authorization-gated configuration, and storage key patterns for balance tracking.
MaxBalance Example Implementation
examples/rwa-max-balance/src/lib.rs, examples/rwa-max-balance/README.md
Concrete example contract implementing the MaxBalance compliance module with admin-based bootstrap and compliance-address handoff authorization patterns, plus usage documentation.
SupplyLimit Compliance Module
packages/tokens/src/rwa/compliance/modules/supply_limit/mod.rs, packages/tokens/src/rwa/compliance/modules/supply_limit/storage.rs, packages/tokens/src/rwa/compliance/modules/supply_limit/test.rs
Implements SupplyLimit trait with per-token mint cap enforcement, internal supply tracking, authorization-gated configuration, hook wiring verification, and storage patterns for cap and supply management.
SupplyLimit Example Implementation
examples/rwa-supply-limit/src/lib.rs, examples/rwa-supply-limit/README.md
Concrete example contract implementing the SupplyLimit compliance module with admin-based bootstrap and compliance-address handoff, including documentation of synchronization hooks and configuration flows.
Module Exports
packages/tokens/src/rwa/compliance/modules/mod.rs
Added public submodule declarations for max_balance and supply_limit to expose new compliance modules within the crate tree.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related PRs

  • Rwa reorg #620: Reorganizes RWA compliance modules namespace and trait structure that these modules implement and extend.
  • RWA: examples #614: Adjusts workspace member entries for example crates; overlaps with workspace configuration changes in root Cargo.toml.
  • feat(rwa): add compliance module base architecture #607: Introduces shared compliance module utilities (verify_required_hooks, set_compliance_address, IRS client patterns, arithmetic helpers) that both new modules depend on.

Suggested reviewers

  • brozorec
  • ozgunozerk

Poem

🐰 A rabbit hops through tokens bright,
Max balances and supplies tight,
Two modules check each mint and trade,
While identity-based caps are made!
Hooks verify what needs be true,
Compliance magic, tried and new! 🌟

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding two RWA compliance modules (max_balance and supply_limit) for managing token balances and supply limits.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description check ✅ Passed The PR description covers the main changes and includes a test plan checklist, but is missing references to the required template sections.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
examples/rwa-max-balance/src/lib.rs (1)

3-3: Remove unused String import.

The String type is imported but never used in this file.

🧹 Suggested cleanup
-use soroban_sdk::{contract, contractimpl, contracttype, vec, Address, Env, String, Vec};
+use soroban_sdk::{contract, contractimpl, contracttype, vec, Address, Env, Vec};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/rwa-max-balance/src/lib.rs` at line 3, The import list in the use
statement includes an unused symbol String; remove String from the
soroban_sdk::{contract, contractimpl, contracttype, vec, Address, Env, String,
Vec} import so the line only imports the actually used symbols (contract,
contractimpl, contracttype, vec, Address, Env, Vec), avoiding the unused-import
warning.
packages/tokens/src/rwa/compliance/modules/supply_limit/mod.rs (1)

87-102: Consider using checked arithmetic in can_create to avoid panicking on overflow.

can_create uses add_i128_or_panic which will panic if supply + amount overflows, rather than returning false. While this is consistent with how on_created handles overflow (also panics), a validation/capability check ideally shouldn't panic on extreme inputs.

This is a minor edge case since such large amounts would be impractical, but for robustness you could use checked_add and return false on overflow:

💡 Optional improvement
     fn can_create(e: &Env, _to: Address, amount: i128, token: Address) -> bool {
         assert!(
             hooks_verified(e),
             "SupplyLimitModule: not armed — call verify_hook_wiring() after wiring hooks \
              [CanCreate, Created, Destroyed]"
         );
         if amount < 0 {
             return false;
         }
         let limit = get_supply_limit(e, &token);
         if limit == 0 {
             return true;
         }
         let supply = get_internal_supply(e, &token);
-        add_i128_or_panic(e, supply, amount) <= limit
+        supply.checked_add(amount).map_or(false, |new_supply| new_supply <= limit)
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/tokens/src/rwa/compliance/modules/supply_limit/mod.rs` around lines
87 - 102, The can_create function currently uses add_i128_or_panic(supply,
amount) which may panic on overflow; change the logic to use checked arithmetic:
fetch supply via get_internal_supply(e, &token) and use
supply.checked_add(amount) (or equivalent checked_add for i128) and if the
result is None (overflow) return false, otherwise compare the sum to
get_supply_limit(e, &token); keep the existing hooks_verified assert and the
zero-limit fast path. This removes the panic on extreme inputs while preserving
the same limit semantics.
packages/tokens/src/rwa/compliance/modules/max_balance/mod.rs (1)

41-58: Consider using checked arithmetic to avoid panicking on overflow.

Similar to SupplyLimit::can_create, the can_increase_identity_balance helper uses add_i128_or_panic which will panic on overflow rather than returning false. For a validation helper, graceful handling of extreme inputs would be more robust.

💡 Optional improvement
 fn can_increase_identity_balance(
     e: &Env,
     token: &Address,
     identity: &Address,
     amount: i128,
 ) -> bool {
     if amount < 0 {
         return false;
     }

     let max = get_max_balance(e, token);
     if max == 0 {
         return true;
     }

     let current = get_id_balance(e, token, identity);
-    add_i128_or_panic(e, current, amount) <= max
+    current.checked_add(amount).map_or(false, |new_balance| new_balance <= max)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/tokens/src/rwa/compliance/modules/max_balance/mod.rs` around lines
41 - 58, can_increase_identity_balance currently uses add_i128_or_panic which
will panic on overflow; change it to perform checked addition (e.g. use
i128::checked_add or a helper like checked_add_i128) when combining current and
amount and return false if the checked operation returns None, keeping the
existing early return on amount < 0 and the max==0 shortcut; update references
in this function to use get_max_balance and get_id_balance and ensure the final
comparison uses the safely computed sum (or false on overflow) instead of
panicking.
examples/rwa-supply-limit/src/lib.rs (1)

3-3: Remove unused String import.

The String type is imported but never used in this file.

🧹 Suggested cleanup
-use soroban_sdk::{contract, contractimpl, contracttype, vec, Address, Env, String, Vec};
+use soroban_sdk::{contract, contractimpl, contracttype, vec, Address, Env, Vec};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/rwa-supply-limit/src/lib.rs` at line 3, The import list includes an
unused symbol `String`; remove `String` from the use statement that currently
reads use soroban_sdk::{contract, contractimpl, contracttype, vec, Address, Env,
String, Vec}; so update that line to exclude `String` (leaving contract,
contractimpl, contracttype, vec, Address, Env, Vec) to eliminate the unused
import.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@examples/rwa-max-balance/src/lib.rs`:
- Line 3: The import list in the use statement includes an unused symbol String;
remove String from the soroban_sdk::{contract, contractimpl, contracttype, vec,
Address, Env, String, Vec} import so the line only imports the actually used
symbols (contract, contractimpl, contracttype, vec, Address, Env, Vec), avoiding
the unused-import warning.

In `@examples/rwa-supply-limit/src/lib.rs`:
- Line 3: The import list includes an unused symbol `String`; remove `String`
from the use statement that currently reads use soroban_sdk::{contract,
contractimpl, contracttype, vec, Address, Env, String, Vec}; so update that line
to exclude `String` (leaving contract, contractimpl, contracttype, vec, Address,
Env, Vec) to eliminate the unused import.

In `@packages/tokens/src/rwa/compliance/modules/max_balance/mod.rs`:
- Around line 41-58: can_increase_identity_balance currently uses
add_i128_or_panic which will panic on overflow; change it to perform checked
addition (e.g. use i128::checked_add or a helper like checked_add_i128) when
combining current and amount and return false if the checked operation returns
None, keeping the existing early return on amount < 0 and the max==0 shortcut;
update references in this function to use get_max_balance and get_id_balance and
ensure the final comparison uses the safely computed sum (or false on overflow)
instead of panicking.

In `@packages/tokens/src/rwa/compliance/modules/supply_limit/mod.rs`:
- Around line 87-102: The can_create function currently uses
add_i128_or_panic(supply, amount) which may panic on overflow; change the logic
to use checked arithmetic: fetch supply via get_internal_supply(e, &token) and
use supply.checked_add(amount) (or equivalent checked_add for i128) and if the
result is None (overflow) return false, otherwise compare the sum to
get_supply_limit(e, &token); keep the existing hooks_verified assert and the
zero-limit fast path. This removes the panic on extreme inputs while preserving
the same limit semantics.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d51172fc-ccd4-4649-870a-416fe1873358

📥 Commits

Reviewing files that changed from the base of the PR and between ff17d24 and db21540.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (14)
  • Cargo.toml
  • examples/rwa-max-balance/Cargo.toml
  • examples/rwa-max-balance/README.md
  • examples/rwa-max-balance/src/lib.rs
  • examples/rwa-supply-limit/Cargo.toml
  • examples/rwa-supply-limit/README.md
  • examples/rwa-supply-limit/src/lib.rs
  • packages/tokens/src/rwa/compliance/modules/max_balance/mod.rs
  • packages/tokens/src/rwa/compliance/modules/max_balance/storage.rs
  • packages/tokens/src/rwa/compliance/modules/max_balance/test.rs
  • packages/tokens/src/rwa/compliance/modules/mod.rs
  • packages/tokens/src/rwa/compliance/modules/supply_limit/mod.rs
  • packages/tokens/src/rwa/compliance/modules/supply_limit/storage.rs
  • packages/tokens/src/rwa/compliance/modules/supply_limit/test.rs

Copy link
Copy Markdown
Collaborator

@brozorec brozorec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, we don't need an additional trait per module: we already have ComplianceModule that must be implemented by each.

In the current state, some of the logic is contained in the default functions from mod.rs and some in storage.rs. Our philosophy is to provide public storage functions to be composed by implementors, and only wherever it makes sense to provide defaults. Here, every module has its own logic so defaults won't work.

Following lib's general pattern, I'd rather suggest to:

  • move all the specific implementations from the traits to storage.rs as "free" functions
  • keep mod.rs for just events, constants and re-export
  • in "examples", every module implements trait ComplianceModule and comes with an additional contractimpl for its specific functions (no need to implement a trait), and use there the free functions from storage.rs.

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.

2 participants