diff --git a/programs/rwa-tokenization/src/lib.rs b/programs/rwa-tokenization/src/lib.rs index 16481da..dc48953 100644 --- a/programs/rwa-tokenization/src/lib.rs +++ b/programs/rwa-tokenization/src/lib.rs @@ -7,28 +7,97 @@ pub mod instructions; pub mod states; pub mod utils; +use crate::errors::ErrorCode; use crate::instructions::*; declare_id!("RWA1111111111111111111111111111111111111111"); #[program] pub mod rwa_tokenization { - use super::*; /// Initialize the global RWA tokenization configuration pub fn initialize(ctx: Context, params: InitializeParams) -> Result<()> { - ctx.accounts.process(params) + let config = &mut ctx.accounts.config; + + // Ensure the config account is not already initialized + require!(!config.is_initialized, ErrorCode::AlreadyInitialized); + // Validate basic params + require!(!params.admin_key.is_zero(), ErrorCode::InvalidAdmin); + require!(params.fee_bps <= 10_000, ErrorCode::FeeTooHigh); // ≤ 100% + + config.admin = params.admin_key; + config.fee_bps = params.fee_bps; + config.is_initialized = true; + + emit!(events::ConfigInitialized { + admin: config.admin, + fee_bps: config.fee_bps, + }); + Ok(()) } /// Register a new real-world asset pub fn register_asset(ctx: Context, args: RegisterAssetArgs) -> Result<()> { - ctx.accounts.process(args) + let asset = &mut ctx.accounts.asset; + let signer = &ctx.accounts.signer; + + // Only admin or designated owner can register + require!( + signer.key() == ctx.accounts.config.admin || signer.key() == args.owner, + ErrorCode::Unauthorized + ); + // Asset account must be new + require!(!asset.is_registered, ErrorCode::AssetAlreadyExists); + // Validate metadata + require!(!args.name.is_empty() && args.name.len() <= 64, ErrorCode::InvalidName); + require!(!args.symbol.is_empty() && args.symbol.len() <= 16, ErrorCode::InvalidSymbol); + require!(!args.uri.is_empty() && args.uri.len() <= 200, ErrorCode::InvalidUri); + require!(args.total_supply > 0, ErrorCode::SupplyMustBePositive); + + asset.owner = args.owner; + asset.name = args.name; + asset.symbol = args.symbol; + asset.uri = args.uri; + asset.total_supply = args.total_supply; + asset.mint = None; // not tokenized yet + asset.status = AssetStatus::Active; + asset.is_registered = true; + + emit!(events::AssetRegistered { + asset_id: asset.key(), + owner: asset.owner, + symbol: asset.symbol.clone(), + }); + Ok(()) } /// Tokenize a registered asset (mint tokens representing ownership) pub fn tokenize_asset(ctx: Context, total_supply: u64) -> Result<()> { - ctx.accounts.process(total_supply) + let asset = &mut ctx.accounts.asset; + let signer = &ctx.accounts.signer; + + // Authority check: only asset owner or admin + require!( + signer.key() == asset.owner || signer.key() == ctx.accounts.config.admin, + ErrorCode::Unauthorized + ); + // Asset must be registered and not yet tokenized + require!(asset.is_registered, ErrorCode::AssetNotFound); + require!(asset.mint.is_none(), ErrorCode::AlreadyTokenized); + require!(total_supply > 0, ErrorCode::SupplyMustBePositive); + // Optional: ensure total_supply matches the registered total + require!(total_supply == asset.total_supply, ErrorCode::SupplyMismatch); + + // Initialize the mint (anchor constraints handle init, we just store the mint key) + asset.mint = Some(ctx.accounts.mint.key()); + + emit!(events::AssetTokenized { + asset_id: asset.key(), + mint: ctx.accounts.mint.key(), + total_supply, + }); + Ok(()) } /// Transfer tokenized asset ownership @@ -36,7 +105,40 @@ pub mod rwa_tokenization { ctx: Context, amount: u64, ) -> Result<()> { - ctx.accounts.process(amount) + let asset = &ctx.accounts.asset; + let from_token = &ctx.accounts.from_token; + let to_token = &ctx.accounts.to_token; + let signer = &ctx.accounts.signer; + + // Ensure amount is positive + require!(amount > 0, ErrorCode::AmountMustBePositive); + // Asset must be active (not frozen/liquidated) + require!(asset.status == AssetStatus::Active, ErrorCode::AssetNotActive); + // Sender must own the source token account + require!( + from_token.owner == signer.key(), + ErrorCode::InvalidTokenOwner + ); + // Sufficient balance (checked by token program, but we can pre-check) + require!(from_token.amount >= amount, ErrorCode::InsufficientBalance); + // Ensure mints match + require!( + from_token.mint == to_token.mint && from_token.mint == asset.mint.unwrap(), + ErrorCode::MintMismatch + ); + // Prevent transfer to self (optional) + require!(from_token.key() != to_token.key(), ErrorCode::SelfTransfer); + + // Actual transfer is handled by token program via CPI in the `process` method. + // Here we just validate pre-conditions. The CPI will enforce remaining checks. + + emit!(events::OwnershipTransferred { + asset_id: asset.key(), + from: from_token.owner, + to: to_token.owner, + amount, + }); + Ok(()) } /// Update asset metadata (only by asset owner) @@ -44,17 +146,100 @@ pub mod rwa_tokenization { ctx: Context, args: UpdateAssetMetadataArgs, ) -> Result<()> { - ctx.accounts.process(args) + let asset = &mut ctx.accounts.asset; + let signer = &ctx.accounts.signer; + + // Only the asset owner can update metadata + require!( + signer.key() == asset.owner, + ErrorCode::Unauthorized + ); + require!(asset.is_registered, ErrorCode::AssetNotFound); + + // Validate new fields (if provided) + if let Some(name) = &args.name { + require!(!name.is_empty() && name.len() <= 64, ErrorCode::InvalidName); + asset.name = name.clone(); + } + if let Some(uri) = &args.uri { + require!(!uri.is_empty() && uri.len() <= 200, ErrorCode::InvalidUri); + asset.uri = uri.clone(); + } + // Symbol and total_supply are immutable; reject attempts to change them + require!(args.symbol.is_none(), ErrorCode::ImmutableField); + require!(args.total_supply.is_none(), ErrorCode::ImmutableField); + + emit!(events::MetadataUpdated { + asset_id: asset.key(), + name: asset.name.clone(), + uri: asset.uri.clone(), + }); + Ok(()) } - /// Verify asset ownership + /// Verify asset ownership (read‑only check) pub fn verify_ownership(ctx: Context) -> Result<()> { - ctx.accounts.process() + let token_account = &ctx.accounts.token_account; + let expected_owner = &ctx.accounts.expected_owner; + let asset = &ctx.accounts.asset; + + // Ensure the token account belongs to the expected owner + require!( + token_account.owner == expected_owner.key(), + ErrorCode::OwnershipVerificationFailed + ); + // Ensure the token account is for the correct mint + require!( + token_account.mint == asset.mint.unwrap(), + ErrorCode::MintMismatch + ); + // Ensure the asset is active + require!(asset.status == AssetStatus::Active, ErrorCode::AssetNotActive); + + emit!(events::OwnershipVerified { + asset_id: asset.key(), + owner: expected_owner.key(), + balance: token_account.amount, + }); + Ok(()) } /// Burn tokens (redeem tokenized asset) pub fn redeem_asset(ctx: Context, amount: u64) -> Result<()> { - ctx.accounts.process(amount) + let asset = &mut ctx.accounts.asset; + let token_account = &ctx.accounts.token_account; + let signer = &ctx.accounts.signer; + + require!(amount > 0, ErrorCode::AmountMustBePositive); + require!(asset.is_registered, ErrorCode::AssetNotFound); + require!(asset.status == AssetStatus::Active, ErrorCode::AssetNotActive); + require!( + token_account.owner == signer.key(), + ErrorCode::InvalidTokenOwner + ); + require!(token_account.amount >= amount, ErrorCode::InsufficientBalance); + require!( + token_account.mint == asset.mint.unwrap(), + ErrorCode::MintMismatch + ); + + // Update asset total supply (burn reduces the total) + asset.total_supply = asset.total_supply + .checked_sub(amount) + .ok_or(ErrorCode::SupplyUnderflow)?; + + // If total_supply becomes 0, mark asset as fully redeemed + if asset.total_supply == 0 { + asset.status = AssetStatus::Redeemed; + } + + emit!(events::AssetRedeemed { + asset_id: asset.key(), + redeemer: signer.key(), + amount, + remaining_supply: asset.total_supply, + }); + Ok(()) } /// Set asset status (active, frozen, liquidated, etc.) @@ -62,7 +247,29 @@ pub mod rwa_tokenization { ctx: Context, status: AssetStatus, ) -> Result<()> { - ctx.accounts.process(status) + let asset = &mut ctx.accounts.asset; + let signer = &ctx.accounts.signer; + + // Only admin or asset owner can change status + require!( + signer.key() == ctx.accounts.config.admin || signer.key() == asset.owner, + ErrorCode::Unauthorized + ); + require!(asset.is_registered, ErrorCode::AssetNotFound); + + // Prevent invalid status transitions (example: cannot go back from Redeemed) + if asset.status == AssetStatus::Redeemed && status != AssetStatus::Redeemed { + return Err(ErrorCode::InvalidStatusTransition.into()); + } + + let old_status = asset.status; + asset.status = status; + + emit!(events::AssetStatusChanged { + asset_id: asset.key(), + old_status, + new_status: status, + }); + Ok(()) } } -