RWA: balance and supply compliance modules#651
RWA: balance and supply compliance modules#651pasevin wants to merge 1 commit intoOpenZeppelin:mainfrom
Conversation
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.
WalkthroughIntroduces two new Soroban compliance modules for Stellar RWA tokens: Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~55 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (4)
examples/rwa-max-balance/src/lib.rs (1)
3-3: Remove unusedStringimport.The
Stringtype 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 incan_createto avoid panicking on overflow.
can_createusesadd_i128_or_panicwhich will panic ifsupply + amountoverflows, rather than returningfalse. While this is consistent with howon_createdhandles 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_addand returnfalseon 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, thecan_increase_identity_balancehelper usesadd_i128_or_panicwhich will panic on overflow rather than returningfalse. 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 unusedStringimport.The
Stringtype 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
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (14)
Cargo.tomlexamples/rwa-max-balance/Cargo.tomlexamples/rwa-max-balance/README.mdexamples/rwa-max-balance/src/lib.rsexamples/rwa-supply-limit/Cargo.tomlexamples/rwa-supply-limit/README.mdexamples/rwa-supply-limit/src/lib.rspackages/tokens/src/rwa/compliance/modules/max_balance/mod.rspackages/tokens/src/rwa/compliance/modules/max_balance/storage.rspackages/tokens/src/rwa/compliance/modules/max_balance/test.rspackages/tokens/src/rwa/compliance/modules/mod.rspackages/tokens/src/rwa/compliance/modules/supply_limit/mod.rspackages/tokens/src/rwa/compliance/modules/supply_limit/storage.rspackages/tokens/src/rwa/compliance/modules/supply_limit/test.rs
There was a problem hiding this comment.
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.rsas "free" functions - keep
mod.rsfor just events, constants and re-export - in "examples", every module implements
trait ComplianceModuleand comes with an additionalcontractimplfor its specific functions (no need to implement a trait), and use there the free functions fromstorage.rs.
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
max_balanceandsupply_limitcompliance modules with storage, hook logic, and focused unit coverage.rwa-max-balanceandrwa-supply-limitexample crates.Test plan
cargo +nightly fmt --all -- --checkcargo test -p stellar-tokens --lib balancecargo test -p rwa-max-balance --libcargo test -p rwa-supply-limit --lib