Skip to content

RWA: compliance integration example#653

Draft
pasevin wants to merge 1 commit intoOpenZeppelin:mainfrom
pasevin:feat/rwa-integration-standalone
Draft

RWA: compliance integration example#653
pasevin wants to merge 1 commit intoOpenZeppelin:mainfrom
pasevin:feat/rwa-integration-standalone

Conversation

@pasevin
Copy link
Copy Markdown
Contributor

@pasevin pasevin commented Mar 23, 2026

Summary

Adds a full-stack RWA compliance example with integration tests covering all seven compliance modules together.

Changes

  • examples/rwa-compliance/: adds a full-stack example composed of compliance, identity registry, verifier, and token contracts.
  • Integration tests: covers country allow/restrict, max balance, supply limit, time-based limits, transfer restrictions, initial lockup, wiring guards, and burn flows.
  • Workspace: includes the integration example and its dev-dependencies on the seven module example crates.

Test plan

  • cargo +nightly fmt --all -- --check
  • cargo test -p rwa-compliance-example --lib

Transplant the reviewed compliance integration example together with the
existing module and example crates it depends on so the PR stands alone.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 23, 2026

Walkthrough

This pull request introduces a comprehensive suite of RWA (Real World Assets) compliance modules for Stellar tokens, including country allowlisting/restricting, balance limits, supply caps, time-based transfer windows, transfer restrictions, and initial lockup periods. It adds trait definitions and storage layers in the packages directory alongside standalone example implementations and integration tests in the examples directory.

Changes

Cohort / File(s) Summary
Workspace & Root Configuration
Cargo.toml
Replaced wildcard examples/rwa/* with explicit list of 12 enumerated RWA example crate paths, changing workspace membership resolution from glob to explicit inclusion.
Country Allow Module
packages/tokens/src/rwa/compliance/modules/country_allow/..., examples/rwa-country-allow/...
Trait definition, storage layer (per-token country allowlist with TTL extension), unit tests, and deployable example contract. Transfers/mints gated by recipient country presence in token's allowlist via IRS lookup.
Country Restrict Module
packages/tokens/src/rwa/compliance/modules/country_restrict/..., examples/rwa-country-restrict/...
Trait definition, storage layer (per-token country restriction flags), and deployable example. Blocks transfers/mints when recipient country appears in token's restriction list.
Max Balance Module
packages/tokens/src/rwa/compliance/modules/max_balance/..., examples/rwa-max-balance/...
Per-identity balance cap enforcement via trait, storage (per-token max + per-identity balances), comprehensive unit tests including edge cases, and deployable example with IRS integration.
Supply Limit Module
packages/tokens/src/rwa/compliance/modules/supply_limit/..., examples/rwa-supply-limit/...
Token-level supply cap trait, storage for per-token limits/internal supply tracking, unit tests, and deployable example with overflow-safe arithmetic and hook wiring verification.
Initial Lockup Period Module
packages/tokens/src/rwa/compliance/modules/initial_lockup_period/..., examples/rwa-initial-lockup-period/...
Trait for minting-based lockup periods (peer-to-peer transfers unlock), storage with per-wallet lock records and internal balances, unit tests, and deployable example with timestamp-based release logic.
Time Transfers Limits Module
packages/tokens/src/rwa/compliance/modules/time_transfers_limits/..., examples/rwa-time-transfers-limits/...
Per-identity rolling window transfer volume limits trait, storage for configurable limits (max 4/token) and identity counters, unit tests, and deployable example with lazy counter reset on window expiry.
Transfer Restrict Module
packages/tokens/src/rwa/compliance/modules/transfer_restrict/..., examples/rwa-transfer-restrict/...
T-REX per-token address allowlist trait with sender/recipient gating semantics, storage for allowed users, unit tests, and deployable example.
Integrated RWA Compliance Example
examples/rwa-compliance/...
Standalone token contract (RWATokenContract) with pausable/freezable features, compliance registry (ComplianceContract) aggregating compliance modules, identity registry (IdentityRegistryContract) managing identity lifecycle and country data, and identity verifier (SimpleIdentityVerifier). Comprehensive integration test suite (1000+ lines) validating all modules in isolation and combined scenarios, including guard tests and error cases.
Compliance Modules Registry
packages/tokens/src/rwa/compliance/modules/mod.rs
Updated to publicly expose all seven new compliance module submodules.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Token as RWA Token
    participant Compliance as Compliance Registry
    participant Module as Compliance Module<br/>(e.g., Country Allow)
    participant IRS as Identity Registry
    
    User->>Token: transfer(to, amount)
    Token->>Compliance: get_modules_for_hook(CanTransfer)
    Compliance-->>Token: [Module]
    Token->>Module: can_transfer(from, to, amount, token)
    Module->>IRS: stored_identity(to)
    IRS-->>Module: country_data
    Module->>Module: check country in allowlist
    Module-->>Token: bool (allowed/denied)
    Token-->>User: success/error
Loading
sequenceDiagram
    participant Admin
    participant Module as Compliance Module<br/>(e.g., Max Balance)
    participant Storage as Module Storage
    participant Compliance as Compliance Contract
    
    Admin->>Module: set_max_balance(token, max=1000)
    Module->>Compliance: require_auth()
    Compliance-->>Module: authorized
    Module->>Storage: persist max_balance for token
    Storage-->>Module: done
    Module->>Module: emit MaxBalanceSet event
    
    Admin->>Module: batch_pre_set_module_state(token, [id1,id2], [500,600])
    Module->>Storage: set identity balances
    Storage-->>Module: done
    Module->>Module: emit IDBalancePreSet events
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly Related PRs

  • Rwa reorg #620: Introduces the RWA compliance modules namespace and trait organization structure that this PR directly extends with concrete module implementations and storage layers.
  • feat(rwa): add compliance module base architecture #607: Adds compliance module foundation (common helpers, error enums, TTL constants) that all seven modules in this PR depend on and use throughout their implementations.
  • RWA: Val for country data instead assoc type #623: Modifies IdentityRegistryStorage and CountryDataManager trait signatures (country data representation changes) which directly impact the country allow/restrict modules' IRS interactions.

Suggested Reviewers

  • ozgunozerk
  • brozorec

Poem

🐰 Seven modules hop in line,
Country gates and balance signs,
Lockups tick and limits dance,
Token compliance takes its chance.
Stellar tokens now can shine bright,
RWA rules done just right! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'RWA: compliance integration example' is concise and clearly summarizes the main change: adding a compliance integration example to the RWA token framework.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description check ✅ Passed The PR description clearly identifies the changes (full-stack RWA compliance example with integration tests), specifies files modified (examples/rwa-compliance/, workspace updates), and documents the test plan with completed checks.

✏️ 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.

Actionable comments posted: 2

🧹 Nitpick comments (10)
examples/rwa-supply-limit/README.md (1)

58-58: Polish sentence flow in the “historical minting” note.

Small readability tweak for the adverb placement.

✏️ Suggested wording update
-- If the module is attached after a token already has minted supply, seed the
+- If the module is attached after a token has already minted supply, seed the
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/rwa-supply-limit/README.md` at line 58, Summary: Improve sentence
flow in the "historical minting" note by moving the adverb for clearer English;
change the fragment "If the module is attached after a token already has minted
supply, seed the" to a smoother phrasing such as "If the module is attached
after a token has already minted supply, seed the" (or "If the module is
attached after a token already minted supply, seed the") so the adverb "already"
follows "has" and the sentence reads naturally.
packages/tokens/src/rwa/compliance/modules/max_balance/test.rs (1)

22-208: Extract the mock compliance/IRS harness before it drifts further.

MockIRSContract, MockComplianceContract, and arm_hooks are now copied across multiple module test files. Moving them into a shared test_support module would make future trait or hook-surface changes land once instead of being patched in parallel.

🤖 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/test.rs` around lines
22 - 208, Extract the duplicated test harness into a shared test_support module
by moving the MockIRSContract, MockIRSStorageKey, MockComplianceContract,
MockComplianceStorageKey, TestMaxBalanceContract trait impls, the helper methods
set_identity and register_hook, and arm_hooks into a single library file (e.g.,
test_support.rs) and re-export them for tests; then update this test file to
import those symbols instead of redefining them and delete the local copies to
avoid drift. Ensure the shared module implements the same contract trait impls
(TokenBinder, IdentityRegistryStorage, CountryDataManager, Compliance, and
TokenBinder for MockComplianceContract) and exposes set_identity and
register_hook so tests can set stored identities and register hooks the same way
as before. Finally, run cargo test to verify the moved symbols (MockIRSContract,
MockComplianceContract, MockIRSStorageKey, MockComplianceStorageKey, arm_hooks)
are referenced correctly and adjust any visibility (pub(crate)/pub) if needed.
examples/rwa-compliance/src/test.rs (1)

106-113: Keep the compliance handoff separate from hook registration.

Line 108 hands control to Compliance before the module-specific set_* calls in each test, so those calls only work because setup() globally mocks auths. Splitting this helper into “register hooks” and “handoff” steps would make the integration flow match the documented bootstrap-admin pattern and reduce false positives around auth/order regressions. Based on learnings, set_compliance_address is the auth handoff point and pre-bind auth is intentionally enforced at the public entrypoints.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/rwa-compliance/src/test.rs` around lines 106 - 113, The helper
wire_module mixes hook registration and the auth handoff
(ComplianceModuleClient::set_compliance_address) which masks order/auth bugs;
split it into two helpers such as register_module_hooks(ts, module_addr, hooks)
that loops ts.compliance_client.add_module_to(hook, module_addr, &ts.admin) and
handoff_compliance(ts, module_addr) that constructs
ComplianceModuleClient::new(&ts.env, module_addr) and calls
set_compliance_address(&ts.compliance); update tests to call
register_module_hooks(...) first and then handoff_compliance(...) so pre-bind
auth is enforced as in setup().
packages/tokens/src/rwa/compliance/modules/supply_limit/test.rs (1)

104-211: Please run the packages/tokens test target in CI.

These new module suites live outside rwa-compliance-example, but the stated PR plan only exercises that example crate. Adding the relevant packages/tokens test command(s) would catch compile/macro regressions in these new unit-test files before merge.

🤖 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/test.rs` around lines
104 - 211, CI is not running the new unit tests under the tokens package, so add
a CI step to run the tokens package test target (e.g., run cargo test -p tokens
or equivalent workspace-target invocation) so the tests like
verify_hook_wiring_sets_cache_when_registered,
hooks_update_internal_supply_and_cap_future_mints, and
pre_set_internal_supply_seeds_existing_supply_for_cap_checks in the supply_limit
test module are executed; modify the existing test job that only exercises the
rwa-compliance-example to also invoke the tokens package tests (or add a
separate job) to catch compile/macro regressions before merge.
examples/rwa-compliance/src/identity_verifier.rs (1)

7-14: Unused import: Vec

The Vec type is imported but not used in this file.

🔧 Proposed fix
 use soroban_sdk::{
-    contract, contractimpl, contracttype, panic_with_error, symbol_short, Address, Env, Symbol, Vec,
+    contract, contractimpl, contracttype, panic_with_error, symbol_short, Address, Env, Symbol,
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/rwa-compliance/src/identity_verifier.rs` around lines 7 - 14, Remove
the unused Vec import from the soroban_sdk use list to fix the dead import; edit
the use declaration that currently includes Vec (use soroban_sdk::{..., Vec,};)
and delete only Vec so the module imports (e.g., Address, Env, Symbol) remain
unchanged.
examples/rwa-time-transfers-limits/src/lib.rs (1)

3-20: Unused import: String

The String type is imported but not used in this contract.

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

In `@examples/rwa-time-transfers-limits/src/lib.rs` around lines 3 - 20, The
import list at the top unnecessarily includes the unused symbol String from
soroban_sdk; remove String from the use declaration (the line importing
contract, contractimpl, contracttype, panic_with_error, vec, Address, Env,
String, Vec) so only actually used symbols remain (e.g., keep Address, Env, Vec,
vec, contract, contractimpl, contracttype, panic_with_error).
examples/rwa-supply-limit/src/lib.rs (1)

3-18: Unused import: String

The String type is imported but not used in this contract.

🔧 Proposed fix
-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` around lines 3 - 18, The import list
includes an unused type `String` from soroban_sdk (see the use statement that
currently contains "String"); remove `String` from the use declaration so the
line becomes something like the existing import list without `String` to
eliminate the unused import warning and keep only needed symbols like Address,
Env, Vec, contract, contractimpl, contracttype, and vec.
examples/rwa-initial-lockup-period/src/lib.rs (1)

3-19: Unused import: String

The String type is imported but not used in this contract.

🔧 Proposed fix
-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-initial-lockup-period/src/lib.rs` around lines 3 - 19, The
import list in the top-level use from soroban_sdk includes an unused symbol
`String`; remove `String` from the import list (the use that currently reads
soroban_sdk::{contract, contractimpl, contracttype, vec, Address, Env, String,
Vec}) so only actually used types/macros remain (contract, contractimpl,
contracttype, vec, Address, Env, Vec), ensuring no other references to `String`
exist in this module (if any, replace with the appropriate type).
examples/rwa-country-allow/src/lib.rs (1)

3-10: Unused import: String

The String type is imported but not used in this contract.

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

In `@examples/rwa-country-allow/src/lib.rs` around lines 3 - 10, The import list
includes an unused type `String` from soroban_sdk; remove `String` from the use
statement that currently reads soroban_sdk::{contract, contractimpl,
contracttype, Address, Env, String, Vec} so the module only imports types
actually used (e.g., contract, contractimpl, contracttype, Address, Env, Vec),
ensuring no unused-import warnings; update the use line where `String` appears
to eliminate the unused symbol.
examples/rwa-country-restrict/src/lib.rs (1)

3-10: Unused import: String

The String type is imported but not used in this contract.

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

In `@examples/rwa-country-restrict/src/lib.rs` around lines 3 - 10, Remove the
unused String import from the soroban_sdk use statement; edit the import line
that currently reads use soroban_sdk::{contract, contractimpl, contracttype,
Address, Env, String, Vec}; and drop String so only the actually used symbols
(contract, contractimpl, contracttype, Address, Env, Vec) remain, ensuring no
other references to String exist in the module (e.g., in functions or types like
CountryRestrict/CountryRestricted/CountryUnrestricted).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/tokens/src/rwa/compliance/modules/time_transfers_limits/mod.rs`:
- Around line 76-87: In can_transfer, avoid resolving the IRS client and
identity when there are no limits by checking limits.is_empty() immediately
after let limits = get_limits(e, &token) and returning true (or false per logic)
as appropriate; move the get_irs_client(e, &token) and
irs.stored_identity(&from) calls to after that emptiness check. Apply the same
short-circuit change to the Transferred handler (the function handling the
Transferred hook) around lines 217-222 so it also checks limits.is_empty()
before calling get_irs_client or resolving identities.
- Around line 213-223: The on_transfer handler mutates counters even if the
module's hooks aren't fully wired; add the same armed-state guard used in
can_transfer by returning early unless hooks_verified(e) is true so history
doesn't accrue before verify_hook_wiring runs—i.e., at the top of on_transfer
(and the analogous handler around lines 236-238) check hooks_verified(e) and
exit before calling get_compliance_address().require_auth(),
require_non_negative_amount(), get_irs_client(), increasing counters, or
invoking increase_counters; keep the existing auth and validation calls but only
execute them after the hooks_verified(e) guard.

---

Nitpick comments:
In `@examples/rwa-compliance/src/identity_verifier.rs`:
- Around line 7-14: Remove the unused Vec import from the soroban_sdk use list
to fix the dead import; edit the use declaration that currently includes Vec
(use soroban_sdk::{..., Vec,};) and delete only Vec so the module imports (e.g.,
Address, Env, Symbol) remain unchanged.

In `@examples/rwa-compliance/src/test.rs`:
- Around line 106-113: The helper wire_module mixes hook registration and the
auth handoff (ComplianceModuleClient::set_compliance_address) which masks
order/auth bugs; split it into two helpers such as register_module_hooks(ts,
module_addr, hooks) that loops ts.compliance_client.add_module_to(hook,
module_addr, &ts.admin) and handoff_compliance(ts, module_addr) that constructs
ComplianceModuleClient::new(&ts.env, module_addr) and calls
set_compliance_address(&ts.compliance); update tests to call
register_module_hooks(...) first and then handoff_compliance(...) so pre-bind
auth is enforced as in setup().

In `@examples/rwa-country-allow/src/lib.rs`:
- Around line 3-10: The import list includes an unused type `String` from
soroban_sdk; remove `String` from the use statement that currently reads
soroban_sdk::{contract, contractimpl, contracttype, Address, Env, String, Vec}
so the module only imports types actually used (e.g., contract, contractimpl,
contracttype, Address, Env, Vec), ensuring no unused-import warnings; update the
use line where `String` appears to eliminate the unused symbol.

In `@examples/rwa-country-restrict/src/lib.rs`:
- Around line 3-10: Remove the unused String import from the soroban_sdk use
statement; edit the import line that currently reads use soroban_sdk::{contract,
contractimpl, contracttype, Address, Env, String, Vec}; and drop String so only
the actually used symbols (contract, contractimpl, contracttype, Address, Env,
Vec) remain, ensuring no other references to String exist in the module (e.g.,
in functions or types like
CountryRestrict/CountryRestricted/CountryUnrestricted).

In `@examples/rwa-initial-lockup-period/src/lib.rs`:
- Around line 3-19: The import list in the top-level use from soroban_sdk
includes an unused symbol `String`; remove `String` from the import list (the
use that currently reads soroban_sdk::{contract, contractimpl, contracttype,
vec, Address, Env, String, Vec}) so only actually used types/macros remain
(contract, contractimpl, contracttype, vec, Address, Env, Vec), ensuring no
other references to `String` exist in this module (if any, replace with the
appropriate type).

In `@examples/rwa-supply-limit/README.md`:
- Line 58: Summary: Improve sentence flow in the "historical minting" note by
moving the adverb for clearer English; change the fragment "If the module is
attached after a token already has minted supply, seed the" to a smoother
phrasing such as "If the module is attached after a token has already minted
supply, seed the" (or "If the module is attached after a token already minted
supply, seed the") so the adverb "already" follows "has" and the sentence reads
naturally.

In `@examples/rwa-supply-limit/src/lib.rs`:
- Around line 3-18: The import list includes an unused type `String` from
soroban_sdk (see the use statement that currently contains "String"); remove
`String` from the use declaration so the line becomes something like the
existing import list without `String` to eliminate the unused import warning and
keep only needed symbols like Address, Env, Vec, contract, contractimpl,
contracttype, and vec.

In `@examples/rwa-time-transfers-limits/src/lib.rs`:
- Around line 3-20: The import list at the top unnecessarily includes the unused
symbol String from soroban_sdk; remove String from the use declaration (the line
importing contract, contractimpl, contracttype, panic_with_error, vec, Address,
Env, String, Vec) so only actually used symbols remain (e.g., keep Address, Env,
Vec, vec, contract, contractimpl, contracttype, panic_with_error).

In `@packages/tokens/src/rwa/compliance/modules/max_balance/test.rs`:
- Around line 22-208: Extract the duplicated test harness into a shared
test_support module by moving the MockIRSContract, MockIRSStorageKey,
MockComplianceContract, MockComplianceStorageKey, TestMaxBalanceContract trait
impls, the helper methods set_identity and register_hook, and arm_hooks into a
single library file (e.g., test_support.rs) and re-export them for tests; then
update this test file to import those symbols instead of redefining them and
delete the local copies to avoid drift. Ensure the shared module implements the
same contract trait impls (TokenBinder, IdentityRegistryStorage,
CountryDataManager, Compliance, and TokenBinder for MockComplianceContract) and
exposes set_identity and register_hook so tests can set stored identities and
register hooks the same way as before. Finally, run cargo test to verify the
moved symbols (MockIRSContract, MockComplianceContract, MockIRSStorageKey,
MockComplianceStorageKey, arm_hooks) are referenced correctly and adjust any
visibility (pub(crate)/pub) if needed.

In `@packages/tokens/src/rwa/compliance/modules/supply_limit/test.rs`:
- Around line 104-211: CI is not running the new unit tests under the tokens
package, so add a CI step to run the tokens package test target (e.g., run cargo
test -p tokens or equivalent workspace-target invocation) so the tests like
verify_hook_wiring_sets_cache_when_registered,
hooks_update_internal_supply_and_cap_future_mints, and
pre_set_internal_supply_seeds_existing_supply_for_cap_checks in the supply_limit
test module are executed; modify the existing test job that only exercises the
rwa-compliance-example to also invoke the tokens package tests (or add a
separate job) to catch compile/macro regressions before merge.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b4cbb9be-e50e-4085-a561-2f58c348bbd9

📥 Commits

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

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (49)
  • Cargo.toml
  • examples/rwa-compliance/Cargo.toml
  • examples/rwa-compliance/src/compliance.rs
  • examples/rwa-compliance/src/identity_registry.rs
  • examples/rwa-compliance/src/identity_verifier.rs
  • examples/rwa-compliance/src/lib.rs
  • examples/rwa-compliance/src/test.rs
  • examples/rwa-compliance/src/token.rs
  • examples/rwa-country-allow/Cargo.toml
  • examples/rwa-country-allow/README.md
  • examples/rwa-country-allow/src/lib.rs
  • examples/rwa-country-restrict/Cargo.toml
  • examples/rwa-country-restrict/README.md
  • examples/rwa-country-restrict/src/lib.rs
  • examples/rwa-initial-lockup-period/Cargo.toml
  • examples/rwa-initial-lockup-period/README.md
  • examples/rwa-initial-lockup-period/src/lib.rs
  • 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
  • examples/rwa-time-transfers-limits/Cargo.toml
  • examples/rwa-time-transfers-limits/README.md
  • examples/rwa-time-transfers-limits/src/lib.rs
  • examples/rwa-transfer-restrict/Cargo.toml
  • examples/rwa-transfer-restrict/README.md
  • examples/rwa-transfer-restrict/src/lib.rs
  • packages/tokens/src/rwa/compliance/modules/country_allow/mod.rs
  • packages/tokens/src/rwa/compliance/modules/country_allow/storage.rs
  • packages/tokens/src/rwa/compliance/modules/country_restrict/mod.rs
  • packages/tokens/src/rwa/compliance/modules/country_restrict/storage.rs
  • packages/tokens/src/rwa/compliance/modules/initial_lockup_period/mod.rs
  • packages/tokens/src/rwa/compliance/modules/initial_lockup_period/storage.rs
  • packages/tokens/src/rwa/compliance/modules/initial_lockup_period/test.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
  • packages/tokens/src/rwa/compliance/modules/time_transfers_limits/mod.rs
  • packages/tokens/src/rwa/compliance/modules/time_transfers_limits/storage.rs
  • packages/tokens/src/rwa/compliance/modules/time_transfers_limits/test.rs
  • packages/tokens/src/rwa/compliance/modules/transfer_restrict/mod.rs
  • packages/tokens/src/rwa/compliance/modules/transfer_restrict/storage.rs
  • packages/tokens/src/rwa/compliance/modules/transfer_restrict/test.rs

Comment on lines +76 to +87
fn can_transfer(e: &Env, from: Address, _to: Address, amount: i128, token: Address) -> bool {
assert!(
hooks_verified(e),
"TimeTransfersLimitsModule: not armed — call verify_hook_wiring() after wiring hooks \
[CanTransfer, Transferred]"
);
if amount < 0 {
return false;
}
let irs = get_irs_client(e, &token);
let from_id = irs.stored_identity(&from);
let limits = get_limits(e, &token);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Skip IRS resolution when no limits are configured.

get_limits defaults to an empty Vec, so the empty/default state is reachable. Both transfer paths resolve IRS/identity before checking whether there is anything to enforce, which means removing the last limit still leaves this module coupled to IRS state and paying for an unnecessary cross-contract lookup on every transfer. Please short-circuit on limits.is_empty() before touching IRS here.

Also applies to: 217-222

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/tokens/src/rwa/compliance/modules/time_transfers_limits/mod.rs`
around lines 76 - 87, In can_transfer, avoid resolving the IRS client and
identity when there are no limits by checking limits.is_empty() immediately
after let limits = get_limits(e, &token) and returning true (or false per logic)
as appropriate; move the get_irs_client(e, &token) and
irs.stored_identity(&from) calls to after that emptiness check. Apply the same
short-circuit change to the Transferred handler (the function handling the
Transferred hook) around lines 217-222 so it also checks limits.is_empty()
before calling get_irs_client or resolving identities.

Comment on lines +213 to +223
fn verify_hook_wiring(e: &Env) {
verify_required_hooks(e, Self::required_hooks(e));
}

fn on_transfer(e: &Env, from: Address, _to: Address, amount: i128, token: Address) {
get_compliance_address(e).require_auth();
require_non_negative_amount(e, amount);
let irs = get_irs_client(e, &token);
let from_id = irs.stored_identity(&from);
increase_counters(e, &token, &from_id, amount);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Gate on_transfer on the same armed-state check as can_transfer.

required_hooks() says this module needs both CanTransfer and Transferred, but on_transfer will still mutate counters before verify_hook_wiring() has armed the module. If only Transferred is wired, history starts accruing and later gets enforced retroactively once CanTransfer is added. Mirror the hooks_verified(e) guard here so the module stays fail-closed until wiring is verified.

Proposed fix
     fn on_transfer(e: &Env, from: Address, _to: Address, amount: i128, token: Address) {
         get_compliance_address(e).require_auth();
+        assert!(
+            hooks_verified(e),
+            "TimeTransfersLimitsModule: not armed — call verify_hook_wiring() after wiring hooks \
+             [CanTransfer, Transferred]"
+        );
         require_non_negative_amount(e, amount);
         let irs = get_irs_client(e, &token);
         let from_id = irs.stored_identity(&from);
         increase_counters(e, &token, &from_id, amount);
     }

Also applies to: 236-238

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/tokens/src/rwa/compliance/modules/time_transfers_limits/mod.rs`
around lines 213 - 223, The on_transfer handler mutates counters even if the
module's hooks aren't fully wired; add the same armed-state guard used in
can_transfer by returning early unless hooks_verified(e) is true so history
doesn't accrue before verify_hook_wiring runs—i.e., at the top of on_transfer
(and the analogous handler around lines 236-238) check hooks_verified(e) and
exit before calling get_compliance_address().require_auth(),
require_non_negative_amount(), get_irs_client(), increasing counters, or
invoking increase_counters; keep the existing auth and validation calls but only
execute them after the hooks_verified(e) guard.

@pasevin pasevin marked this pull request as draft March 23, 2026 19:30
@pasevin
Copy link
Copy Markdown
Contributor Author

pasevin commented Mar 23, 2026

Marking this draft for now. This PR is intentionally self-contained against current main, which makes the diff larger than ideal. The plan is to merge the module PRs first, then re-spin this as a slimmer follow-up on top of updated main so review can focus only on the integration layer.

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.

1 participant