-
Current
AssetStatusenum (asset_registry.rs, line ~95):Active— asset is active and operationalPaused— monitoring paused but recoverableDeprecated— read-only, not restorablePendingReview— awaiting activation- Missing:
Deactivatedstatus variant needed for restore functionality
-
Lifecycle transition rules (asset_registry.rs, lines ~623–631):
- Valid transitions:
PendingReview → Active,Active ↔ Paused,Active|Paused → Deprecated - Invalid: Deprecated → any other state (permanent)
- To be added:
Active → DeactivatedandDeactivated → Activetransitions
- Valid transitions:
-
AssetMetadata struct (asset_registry.rs, lines ~136–160):
- Fields to preserve during deactivation/restoration:
asset_code,name,symbol,issuer,decimals: immutable identifiers (preserved)category,description,url,registered_at,registered_by: historical (preserved)compliance,risk_rating,risk_score_bps: asset properties (preserved)version,updated_at: versioned on update (modify on each operation)- No explicit
deactivated_atfield exists — will useupdated_atfor timestamp tracking - Status field:
status— transitions betweenActive ↔ Deactivated
- Fields to preserve during deactivation/restoration:
-
Metadata versioning (MetadataVersion struct, lines ~266–274):
- Every state change is tracked:
versionincrements,changed_byrecorded,change_reasonstored,timestamprecorded - Restoration will append a new version entry with reason "Asset restored"
- Every state change is tracked:
- DataKey enum (asset_registry.rs, lines ~300–321):
- Asset core data:
DataKey::AssetMeta(String) - Associated lists:
DataKey::Versions(String),DataKey::StatusIndex(AssetStatus), etc. - Storage tier:
env.storage().persistent()for all asset data - No explicit TTL management observed in asset registry (distinct from relay contract which bumps TTL)
- Access pattern:
env.storage().persistent().set/get/has(&key)
- Asset core data:
- Admin check helper (asset_registry.rs, lines ~1202–1215):
require_admin(&env, &caller) → Result<(), RegistryError>- Caller must be the stored admin (pattern:
caller.require_auth()) - Used by: all write operations (register_asset, update_status, freeze_asset, etc.)
- Deactivation and restoration: will require admin permission, consistent with other lifecycle operations
- RegistryError enum (asset_registry.rs, lines ~49–69):
- Current codes: 1–20, sequential without gaps
- Pattern:
#[contracterror] #[derive(...)] #[repr(u32)] pub enum RegistryError { Variant = code, } - Variant 20 =
AssetFrozen, so next available codes: 21, 22, 23, ... - New errors needed:
AssetAlreadyActive = 21— attempted to deactivate an already-active asset (redundant case)AssetNotDeactivated = 22— attempting to restore an asset that is not in Deactivated status- Note:
AssetNotFoundalready exists (code 4) and will be reused
- Event publishing (asset_registry.rs, examples at lines ~659, ~727, etc.):
- Pattern:
env.events().publish((symbol_short!("topic"), asset_code), data) - Topic is a tuple:
(Symbol, String)where Symbol is short identifier - Event topics in use:
ar_stat(status),ar_cat(category),ar_risk(risk), etc. - Data payload: varies (u32, 1u32, etc.)
- New events needed:
("asset_deactivated", asset_code)with data: admin address (for audit trail)("asset_restored", asset_code)with data: admin address (for audit trail)
- Pattern:
- Test framework: Soroban #[test] with testutils (soroban_sdk::testutils)
- Test pattern (asset_registry.rs, lines ~1318+):
- Setup helper:
fn setup() → (Env, AssetRegistryContractClient, Address) - Register helper:
fn register_usdc(...) → String - Assertions:
assert_eq!,assert!,.unwrap(),.is_err() - Mock auth:
env.mock_all_auths() - Tests are marked
#[cfg(test)]and grouped by feature area
- Setup helper:
- File:
contracts/soroban/src/asset_registry.rs, AssetStatus enum - Change: Add variant
Deactivated,with doc comment "Asset is deactivated; awaiting restoration"
- File:
contracts/soroban/src/asset_registry.rs, RegistryError enum - Add:
AssetAlreadyActive = 21— attempted to deactivate an asset that is already active (defensive)AssetNotDeactivated = 22— attempted to restore an asset that is not in deactivated state
- File:
contracts/soroban/src/asset_registry.rs, AssetRegistryContract impl - Function signature:
pub fn deactivate_asset( env: Env, admin: Address, asset_code: String, reason: String, ) -> Result<(), RegistryError>
- Logic:
- Step 1:
Self::require_admin(&env, &admin)?— auth check - Step 2: Load metadata, fail if not found (
AssetNotFound) - Step 3: Verify asset is currently
Active(not already deactivated/deprecated), elseAssetAlreadyActive - Step 4: Update status to
Deactivated, increment version, update timestamp - Step 5: Remove from
StatusIndex(Active), add toStatusIndex(Deactivated) - Step 6: Call
save_with_versionto record version entry - Step 7: Emit event:
publish((symbol_short!("asset_deact"), asset_code), admin) - Step 8: Return
Ok(())
- Step 1:
- File:
contracts/soroban/src/asset_registry.rs, AssetRegistryContract impl - Function signature:
pub fn restore_asset( env: Env, admin: Address, asset_code: String, ) -> Result<(), RegistryError>
- Logic:
- Step 1:
Self::require_admin(&env, &admin)?— auth check - Step 2: Load metadata, fail if not found (
AssetNotFound) - Step 3: Verify asset is in
Deactivatedstatus, elseAssetNotDeactivated - Step 4: Restore status to
Active, increment version, update timestamp - Step 5: Remove from
StatusIndex(Deactivated), add toStatusIndex(Active) - Step 6: Call
save_with_versionto record version entry with reason "Asset restored" - Step 7: Emit event:
publish((symbol_short!("asset_rest"), asset_code), admin) - Step 8: Return
Ok(())
- Step 1:
- File:
asset_registry.rs, update_status function validation logic - Current rule (lines ~623–631): transitions validated in matches! macro
- Addition: Add rules:
(Active, Deactivated)— allowed (via dedicated deactivate_asset function, but allow in update_status too for consistency)(Deactivated, Active)— NOT allowed here (only via dedicated restore_asset function to ensure proper version tracking)
- Rationale: deactivate/restore are intentional operations with dedicated functions; update_status remains for standard lifecycle
- Happy path: Deactivate active asset → verify status = Deactivated, version incremented, event emitted
- State continuity: Before deactivation, record all fields; after restoration, verify all non-status fields unchanged
- Error: restore non-deactivated: Try to restore an Active asset → AssetNotDeactivated
- Error: deactivate non-active: Try to deactivate a Paused or Deprecated asset → AssetAlreadyActive
- Error: unknown asset: Deactivate/restore non-existent asset code → AssetNotFound
- Error: unauthorized: Deactivate/restore from non-admin address → auth error, no state change
- Idempotency: Deactivate→restore→deactivate→restore yields same consistent state each time
- Version history: 3 version entries: registration, deactivation, restoration
- File:
docs/asset-lifecycle.md(if exists, or to be created)- Document
Deactivatedstatus and when it's used - Document
deactivate_assetandrestore_assetfunctions - Add permission requirements (admin only)
- Confirm state continuity guarantee
- Document
- Inline doc comments: Full /// doc comments on new functions and error variants
contracts/soroban/src/asset_registry.rs— main implementation (enum, functions, errors, tests)docs/asset-lifecycle.md(or create) — lifecycle documentation
- None (all changes in existing asset_registry.rs)
- None — specification is clear from codebase patterns
cargo fmt --all -- --check— formattingcargo clippy -- -D warnings— lintingcargo build --verbose— compilationcargo test --verbose— all tests including new restoration tests
High — all patterns from existing code are well-established and the implementation closely mirrors freeze_asset/unfreeze_asset and update_status patterns.