diff --git a/contracts/assetsup/src/detokenization.rs b/contracts/assetsup/src/detokenization.rs
index dc16c43..c6f2078 100644
--- a/contracts/assetsup/src/detokenization.rs
+++ b/contracts/assetsup/src/detokenization.rs
@@ -40,12 +40,13 @@ pub fn propose_detokenization(env: &Env, asset_id: u64, proposer: Address) -> Re
}
/// Execute detokenization if vote passed
+/// This will remove all tokens from circulation and clear tokenization records
pub fn execute_detokenization(env: &Env, asset_id: u64, proposal_id: u64) -> Result<(), Error> {
let store = env.storage().persistent();
// Verify asset is tokenized
let key = TokenDataKey::TokenizedAsset(asset_id);
- let _: TokenizedAsset = store.get(&key).ok_or(Error::AssetNotTokenized)?;
+ let tokenized_asset: TokenizedAsset = store.get(&key).ok_or(Error::AssetNotTokenized)?;
// Check if proposal is active
let proposal_key = TokenDataKey::DetokenizationProposal(asset_id);
@@ -64,6 +65,65 @@ pub fn execute_detokenization(env: &Env, asset_id: u64, proposal_id: u64) -> Res
return Err(Error::DetokenizationNotApproved);
}
+ // Save total supply for event before clearing
+ let total_supply = tokenized_asset.total_supply;
+
+ // Clear all votes BEFORE removing TokenizedAsset (voting module needs it)
+ voting::clear_proposal_votes(env, asset_id, proposal_id)?;
+
+ // Get list of all token holders before clearing
+ let holders_list_key = TokenDataKey::TokenHoldersList(asset_id);
+ let holders = store.get::<_, soroban_sdk::Vec
>(&holders_list_key)
+ .ok_or(Error::AssetNotTokenized)?;
+
+ // Remove all token holder records
+ for holder in holders.iter() {
+ let holder_key = TokenDataKey::TokenHolder(asset_id, holder.clone());
+ if store.has(&holder_key) {
+ store.remove(&holder_key);
+ }
+
+ // Remove any token locks
+ let lock_key = TokenDataKey::TokenLockedUntil(asset_id, holder.clone());
+ if store.has(&lock_key) {
+ store.remove(&lock_key);
+ }
+
+ // Remove unclaimed dividends
+ let dividend_key = TokenDataKey::UnclaimedDividend(asset_id, holder);
+ if store.has(÷nd_key) {
+ store.remove(÷nd_key);
+ }
+ }
+
+ // Remove token holders list
+ if store.has(&holders_list_key) {
+ store.remove(&holders_list_key);
+ }
+
+ // Remove transfer restrictions
+ let restriction_key = TokenDataKey::TransferRestriction(asset_id);
+ if store.has(&restriction_key) {
+ store.remove(&restriction_key);
+ }
+
+ // Remove whitelist
+ let whitelist_key = TokenDataKey::Whitelist(asset_id);
+ if store.has(&whitelist_key) {
+ store.remove(&whitelist_key);
+ }
+
+ // Remove token metadata
+ let metadata_key = TokenDataKey::TokenMetadata(asset_id);
+ if store.has(&metadata_key) {
+ store.remove(&metadata_key);
+ }
+
+ // Remove the tokenized asset record (this eliminates all tokens from circulation)
+ if store.has(&key) {
+ store.remove(&key);
+ }
+
// Update proposal to executed
let timestamp = env.ledger().timestamp();
let executed_proposal = DetokenizationProposal::Executed(ExecutedProposal {
@@ -72,13 +132,10 @@ pub fn execute_detokenization(env: &Env, asset_id: u64, proposal_id: u64) -> Res
});
store.set(&proposal_key, &executed_proposal);
- // Clear all votes
- voting::clear_proposal_votes(env, asset_id, proposal_id)?;
-
- // Emit event: (asset_id, proposal_id)
+ // Emit event: (asset_id, proposal_id, total_supply_removed)
env.events().publish(
("detokenization", "asset_detokenized"),
- (asset_id, proposal_id),
+ (asset_id, proposal_id, total_supply),
);
Ok(())
diff --git a/contracts/assetsup/src/insurance.rs b/contracts/assetsup/src/insurance.rs
index 73cb731..592c9be 100644
--- a/contracts/assetsup/src/insurance.rs
+++ b/contracts/assetsup/src/insurance.rs
@@ -26,11 +26,10 @@ pub enum ClaimStatus {
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum PolicyType {
- Comprehensive,
- Theft,
- Damage,
Liability,
- BusinessInterruption,
+ Property,
+ Comprehensive,
+ Custom,
}
#[contracttype]
@@ -50,6 +49,7 @@ pub struct InsurancePolicy {
pub holder: Address,
pub insurer: Address,
pub asset_id: BytesN<32>,
+ pub policy_type: PolicyType,
pub coverage_amount: i128,
pub deductible: i128,
pub premium: i128,
@@ -81,22 +81,41 @@ pub enum DataKey {
AssetPolicies(BytesN<32>),
}
+/// Create a new insurance policy with date validation and asset indexing
pub fn create_policy(env: Env, policy: InsurancePolicy) -> Result<(), Error> {
- policy.insurer.require_auth();
-
+ // Validate coverage and deductible
if policy.coverage_amount <= 0 || policy.deductible >= policy.coverage_amount {
return Err(Error::InvalidPayment);
}
+ // Validate premium
+ if policy.premium <= 0 {
+ return Err(Error::InvalidPayment);
+ }
+
+ // Validate dates: start_date must be before end_date
+ if policy.start_date >= policy.end_date {
+ return Err(Error::InvalidPayment);
+ }
+
+ // Validate that start_date is not in the past (allow current timestamp)
+ let current_time = env.ledger().timestamp();
+ if policy.start_date < current_time {
+ return Err(Error::InvalidPayment);
+ }
+
let key = DataKey::Policy(policy.policy_id.clone());
let store = env.storage().persistent();
+ // Check if policy already exists
if store.has(&key) {
return Err(Error::AssetAlreadyExists);
}
+ // Store the policy
store.set(&key, &policy);
+ // Maintain asset index: add policy to asset's policy list
let mut list: Vec> = store
.get(&DataKey::AssetPolicies(policy.asset_id.clone()))
.unwrap_or_else(|| Vec::new(&env));
@@ -108,6 +127,135 @@ pub fn create_policy(env: Env, policy: InsurancePolicy) -> Result<(), Error> {
Ok(())
}
+/// Cancel a policy (authorized by holder or insurer)
+pub fn cancel_policy(env: Env, policy_id: BytesN<32>, caller: Address) -> Result<(), Error> {
+ let store = env.storage().persistent();
+ let key = DataKey::Policy(policy_id.clone());
+
+ let mut policy: InsurancePolicy = store.get(&key).ok_or(Error::AssetNotFound)?;
+
+ // Only holder or insurer can cancel
+ if caller != policy.holder && caller != policy.insurer {
+ return Err(Error::Unauthorized);
+ }
+
+ // Validate status transition: only Active or Suspended policies can be cancelled
+ if policy.status != PolicyStatus::Active && policy.status != PolicyStatus::Suspended {
+ return Err(Error::Unauthorized);
+ }
+
+ policy.status = PolicyStatus::Cancelled;
+ store.set(&key, &policy);
+
+ log!(&env, "PolicyCancelled: {:?}", policy_id);
+ Ok(())
+}
+
+/// Suspend a policy (insurer only)
+pub fn suspend_policy(env: Env, policy_id: BytesN<32>, insurer: Address) -> Result<(), Error> {
+ let store = env.storage().persistent();
+ let key = DataKey::Policy(policy_id.clone());
+
+ let mut policy: InsurancePolicy = store.get(&key).ok_or(Error::AssetNotFound)?;
+
+ // Only insurer can suspend
+ if insurer != policy.insurer {
+ return Err(Error::Unauthorized);
+ }
+
+ // Validate status transition: only Active policies can be suspended
+ if policy.status != PolicyStatus::Active {
+ return Err(Error::Unauthorized);
+ }
+
+ policy.status = PolicyStatus::Suspended;
+ store.set(&key, &policy);
+
+ log!(&env, "PolicySuspended: {:?}", policy_id);
+ Ok(())
+}
+
+/// Expire a policy (permissionless, but requires end_date < current timestamp)
+pub fn expire_policy(env: Env, policy_id: BytesN<32>) -> Result<(), Error> {
+ let store = env.storage().persistent();
+ let key = DataKey::Policy(policy_id.clone());
+
+ let mut policy: InsurancePolicy = store.get(&key).ok_or(Error::AssetNotFound)?;
+
+ let current_time = env.ledger().timestamp();
+
+ // Require that end_date has passed
+ if policy.end_date >= current_time {
+ return Err(Error::Unauthorized);
+ }
+
+ // Validate status transition: only Active or Suspended policies can expire
+ if policy.status != PolicyStatus::Active && policy.status != PolicyStatus::Suspended {
+ return Err(Error::Unauthorized);
+ }
+
+ policy.status = PolicyStatus::Expired;
+ store.set(&key, &policy);
+
+ log!(&env, "PolicyExpired: {:?}", policy_id);
+ Ok(())
+}
+
+/// Renew a policy (insurer only)
+pub fn renew_policy(
+ env: Env,
+ policy_id: BytesN<32>,
+ new_end_date: u64,
+ new_premium: i128,
+ insurer: Address,
+) -> Result<(), Error> {
+ let store = env.storage().persistent();
+ let key = DataKey::Policy(policy_id.clone());
+
+ let mut policy: InsurancePolicy = store.get(&key).ok_or(Error::AssetNotFound)?;
+
+ // Only insurer can renew
+ if insurer != policy.insurer {
+ return Err(Error::Unauthorized);
+ }
+
+ // Validate status transition: only Active or Expired policies can be renewed
+ if policy.status != PolicyStatus::Active && policy.status != PolicyStatus::Expired {
+ return Err(Error::Unauthorized);
+ }
+
+ let current_time = env.ledger().timestamp();
+
+ // Validate new end date is in the future
+ if new_end_date <= current_time {
+ return Err(Error::InvalidPayment);
+ }
+
+ // Validate new premium is positive
+ if new_premium <= 0 {
+ return Err(Error::InvalidPayment);
+ }
+
+ // Update policy
+ policy.end_date = new_end_date;
+ policy.premium = new_premium;
+ policy.status = PolicyStatus::Active;
+ policy.last_payment = current_time;
+
+ store.set(&key, &policy);
+
+ log!(&env, "PolicyRenewed: {:?}", policy_id);
+ Ok(())
+}
+
+/// Get all policies for a specific asset
+pub fn get_asset_policies(env: Env, asset_id: BytesN<32>) -> Vec> {
+ env.storage()
+ .persistent()
+ .get(&DataKey::AssetPolicies(asset_id))
+ .unwrap_or_else(|| Vec::new(&env))
+}
+
pub fn file_claim(env: Env, claim: InsuranceClaim) -> Result<(), Error> {
claim.claimant.require_auth();
diff --git a/contracts/assetsup/src/lib.rs b/contracts/assetsup/src/lib.rs
index 2cfe454..8639201 100644
--- a/contracts/assetsup/src/lib.rs
+++ b/contracts/assetsup/src/lib.rs
@@ -770,4 +770,64 @@ impl AssetUpContract {
pub fn is_detokenization_active(env: Env, asset_id: u64) -> Result {
detokenization::is_detokenization_active(&env, asset_id)
}
+
+ // =====================
+ // Insurance Policy Management
+ // =====================
+
+ /// Create a new insurance policy
+ pub fn create_insurance_policy(
+ env: Env,
+ policy: insurance::InsurancePolicy,
+ ) -> Result<(), Error> {
+ policy.insurer.require_auth();
+ insurance::create_policy(env, policy)
+ }
+
+ /// Cancel a policy (holder or insurer)
+ pub fn cancel_insurance_policy(
+ env: Env,
+ policy_id: BytesN<32>,
+ caller: Address,
+ ) -> Result<(), Error> {
+ caller.require_auth();
+ insurance::cancel_policy(env, policy_id, caller)
+ }
+
+ /// Suspend a policy (insurer only)
+ pub fn suspend_insurance_policy(
+ env: Env,
+ policy_id: BytesN<32>,
+ insurer: Address,
+ ) -> Result<(), Error> {
+ insurer.require_auth();
+ insurance::suspend_policy(env, policy_id, insurer)
+ }
+
+ /// Expire a policy (permissionless)
+ pub fn expire_insurance_policy(env: Env, policy_id: BytesN<32>) -> Result<(), Error> {
+ insurance::expire_policy(env, policy_id)
+ }
+
+ /// Renew a policy (insurer only)
+ pub fn renew_insurance_policy(
+ env: Env,
+ policy_id: BytesN<32>,
+ new_end_date: u64,
+ new_premium: i128,
+ insurer: Address,
+ ) -> Result<(), Error> {
+ insurer.require_auth();
+ insurance::renew_policy(env, policy_id, new_end_date, new_premium, insurer)
+ }
+
+ /// Get a specific policy
+ pub fn get_insurance_policy(env: Env, policy_id: BytesN<32>) -> Option {
+ insurance::get_policy(env, policy_id)
+ }
+
+ /// Get all policies for an asset
+ pub fn get_asset_insurance_policies(env: Env, asset_id: BytesN<32>) -> Vec> {
+ insurance::get_asset_policies(env, asset_id)
+ }
}
diff --git a/contracts/assetsup/src/tests/detokenization_new.rs b/contracts/assetsup/src/tests/detokenization_new.rs
index ac98fa1..ba7a0b9 100644
--- a/contracts/assetsup/src/tests/detokenization_new.rs
+++ b/contracts/assetsup/src/tests/detokenization_new.rs
@@ -188,3 +188,83 @@ fn test_detokenization_majority_threshold() {
assert!(first_execute_err);
assert!(second_execute_ok);
}
+
+#[test]
+fn test_token_elimination_on_execution() {
+ let env = Env::default();
+ let contract_id = env.register(AssetUpContract, ());
+ let tokenizer = Address::generate(&env);
+ let holder2 = Address::generate(&env);
+ let proposer = Address::generate(&env);
+ let asset_id = 1000u64;
+
+ let (before_exists, after_exists, balance_cleared, holders_cleared) =
+ env.as_contract(&contract_id, || {
+ setup_tokenized_asset(&env, asset_id, &tokenizer);
+
+ // Transfer some tokens to create multiple holders
+ tokenization::transfer_tokens(&env, asset_id, tokenizer.clone(), holder2.clone(), 300)
+ .unwrap();
+
+ // Verify asset exists before detokenization
+ let before_exists = tokenization::get_tokenized_asset(&env, asset_id).is_ok();
+
+ // Propose detokenization
+ let proposal_id =
+ detokenization::propose_detokenization(&env, asset_id, proposer.clone()).unwrap();
+
+ // Both holders vote (100%)
+ voting::cast_vote(&env, asset_id, proposal_id, tokenizer.clone()).unwrap();
+ voting::cast_vote(&env, asset_id, proposal_id, holder2.clone()).unwrap();
+
+ // Execute detokenization
+ detokenization::execute_detokenization(&env, asset_id, proposal_id).unwrap();
+
+ // Verify tokens are removed from circulation
+ let after_exists = tokenization::get_tokenized_asset(&env, asset_id).is_ok();
+
+ // Verify balances are cleared
+ let balance1 = tokenization::get_token_balance(&env, asset_id, tokenizer.clone());
+ let balance2 = tokenization::get_token_balance(&env, asset_id, holder2.clone());
+ let balance_cleared = balance1.unwrap_or(0) == 0 && balance2.unwrap_or(0) == 0;
+
+ // Verify holders list is cleared
+ let holders_result = tokenization::get_token_holders(&env, asset_id);
+ let holders_cleared = holders_result.is_err();
+
+ (before_exists, after_exists, balance_cleared, holders_cleared)
+ });
+
+ // Assert asset existed before
+ assert!(before_exists);
+ // Assert asset no longer exists after detokenization
+ assert!(!after_exists);
+ // Assert balances are cleared
+ assert!(balance_cleared);
+ // Assert holders list is cleared
+ assert!(holders_cleared);
+}
+
+#[test]
+fn test_cannot_propose_after_execution() {
+ let env = Env::default();
+ let contract_id = env.register(AssetUpContract, ());
+ let tokenizer = Address::generate(&env);
+ let proposer = Address::generate(&env);
+ let asset_id = 1000u64;
+
+ let second_proposal_err = env.as_contract(&contract_id, || {
+ setup_tokenized_asset(&env, asset_id, &tokenizer);
+
+ // Propose and execute detokenization
+ let proposal_id =
+ detokenization::propose_detokenization(&env, asset_id, proposer.clone()).unwrap();
+ voting::cast_vote(&env, asset_id, proposal_id, tokenizer.clone()).unwrap();
+ detokenization::execute_detokenization(&env, asset_id, proposal_id).unwrap();
+
+ // Try to propose again after execution - should fail because asset is not tokenized
+ detokenization::propose_detokenization(&env, asset_id, proposer.clone()).is_err()
+ });
+
+ assert!(second_proposal_err);
+}
diff --git a/contracts/assetsup/src/tests/insurance_new.rs b/contracts/assetsup/src/tests/insurance_new.rs
new file mode 100644
index 0000000..6f98605
--- /dev/null
+++ b/contracts/assetsup/src/tests/insurance_new.rs
@@ -0,0 +1,434 @@
+#![cfg(test)]
+
+extern crate std;
+
+use soroban_sdk::testutils::{Address as _, Ledger};
+use soroban_sdk::{Address, BytesN, Env};
+
+use crate::insurance::{self, InsurancePolicy, PolicyStatus, PolicyType};
+use crate::AssetUpContract;
+
+fn create_test_policy(
+ env: &Env,
+ policy_id: BytesN<32>,
+ holder: Address,
+ insurer: Address,
+ asset_id: BytesN<32>,
+) -> InsurancePolicy {
+ let current_time = env.ledger().timestamp();
+ InsurancePolicy {
+ policy_id,
+ holder,
+ insurer,
+ asset_id,
+ policy_type: PolicyType::Comprehensive,
+ coverage_amount: 100000,
+ deductible: 5000,
+ premium: 1000,
+ start_date: current_time,
+ end_date: current_time + 365 * 24 * 60 * 60, // 1 year from now
+ status: PolicyStatus::Active,
+ auto_renew: false,
+ last_payment: current_time,
+ }
+}
+
+#[test]
+fn test_create_policy_success() {
+ let env = Env::default();
+ let contract_id = env.register(AssetUpContract, ());
+ let holder = Address::generate(&env);
+ let insurer = Address::generate(&env);
+ let asset_id = BytesN::from_array(&env, &[1u8; 32]);
+ let policy_id = BytesN::from_array(&env, &[2u8; 32]);
+
+ let result = env.as_contract(&contract_id, || {
+ let policy = create_test_policy(&env, policy_id.clone(), holder, insurer, asset_id);
+ insurance::create_policy(env.clone(), policy)
+ });
+
+ assert!(result.is_ok());
+}
+
+#[test]
+fn test_create_policy_invalid_dates() {
+ let env = Env::default();
+ let contract_id = env.register(AssetUpContract, ());
+ let holder = Address::generate(&env);
+ let insurer = Address::generate(&env);
+ let asset_id = BytesN::from_array(&env, &[1u8; 32]);
+ let policy_id = BytesN::from_array(&env, &[2u8; 32]);
+
+ let result = env.as_contract(&contract_id, || {
+ let mut policy = create_test_policy(&env, policy_id.clone(), holder, insurer, asset_id);
+ // Set end_date same as start_date (invalid - must be after)
+ policy.end_date = policy.start_date;
+ insurance::create_policy(env.clone(), policy)
+ });
+
+ assert!(result.is_err());
+}
+
+#[test]
+fn test_create_policy_invalid_coverage() {
+ let env = Env::default();
+ let contract_id = env.register(AssetUpContract, ());
+ let holder = Address::generate(&env);
+ let insurer = Address::generate(&env);
+ let asset_id = BytesN::from_array(&env, &[1u8; 32]);
+ let policy_id = BytesN::from_array(&env, &[2u8; 32]);
+
+ let result = env.as_contract(&contract_id, || {
+ let mut policy = create_test_policy(&env, policy_id.clone(), holder, insurer, asset_id);
+ // Set deductible >= coverage_amount (invalid)
+ policy.deductible = policy.coverage_amount;
+ insurance::create_policy(env.clone(), policy)
+ });
+
+ assert!(result.is_err());
+}
+
+#[test]
+fn test_cancel_policy_by_holder() {
+ let env = Env::default();
+ let contract_id = env.register(AssetUpContract, ());
+ let holder = Address::generate(&env);
+ let insurer = Address::generate(&env);
+ let asset_id = BytesN::from_array(&env, &[1u8; 32]);
+ let policy_id = BytesN::from_array(&env, &[2u8; 32]);
+
+ let (create_ok, cancel_ok, final_status) = env.as_contract(&contract_id, || {
+ let policy = create_test_policy(&env, policy_id.clone(), holder.clone(), insurer, asset_id);
+ let create_result = insurance::create_policy(env.clone(), policy).is_ok();
+
+ let cancel_result =
+ insurance::cancel_policy(env.clone(), policy_id.clone(), holder.clone()).is_ok();
+
+ let final_policy = insurance::get_policy(env.clone(), policy_id.clone());
+ let status = final_policy.map(|p| p.status);
+
+ (create_result, cancel_result, status)
+ });
+
+ assert!(create_ok);
+ assert!(cancel_ok);
+ assert_eq!(final_status, Some(PolicyStatus::Cancelled));
+}
+
+#[test]
+fn test_cancel_policy_by_insurer() {
+ let env = Env::default();
+ let contract_id = env.register(AssetUpContract, ());
+ let holder = Address::generate(&env);
+ let insurer = Address::generate(&env);
+ let asset_id = BytesN::from_array(&env, &[1u8; 32]);
+ let policy_id = BytesN::from_array(&env, &[2u8; 32]);
+
+ let (create_ok, cancel_ok, final_status) = env.as_contract(&contract_id, || {
+ let policy = create_test_policy(&env, policy_id.clone(), holder, insurer.clone(), asset_id);
+ let create_result = insurance::create_policy(env.clone(), policy).is_ok();
+
+ let cancel_result =
+ insurance::cancel_policy(env.clone(), policy_id.clone(), insurer.clone()).is_ok();
+
+ let final_policy = insurance::get_policy(env.clone(), policy_id.clone());
+ let status = final_policy.map(|p| p.status);
+
+ (create_result, cancel_result, status)
+ });
+
+ assert!(create_ok);
+ assert!(cancel_ok);
+ assert_eq!(final_status, Some(PolicyStatus::Cancelled));
+}
+
+#[test]
+fn test_cancel_policy_unauthorized() {
+ let env = Env::default();
+ let contract_id = env.register(AssetUpContract, ());
+ let holder = Address::generate(&env);
+ let insurer = Address::generate(&env);
+ let unauthorized = Address::generate(&env);
+ let asset_id = BytesN::from_array(&env, &[1u8; 32]);
+ let policy_id = BytesN::from_array(&env, &[2u8; 32]);
+
+ let cancel_err = env.as_contract(&contract_id, || {
+ let policy = create_test_policy(&env, policy_id.clone(), holder, insurer, asset_id);
+ insurance::create_policy(env.clone(), policy).unwrap();
+
+ insurance::cancel_policy(env.clone(), policy_id.clone(), unauthorized.clone()).is_err()
+ });
+
+ assert!(cancel_err);
+}
+
+#[test]
+fn test_suspend_policy() {
+ let env = Env::default();
+ let contract_id = env.register(AssetUpContract, ());
+ let holder = Address::generate(&env);
+ let insurer = Address::generate(&env);
+ let asset_id = BytesN::from_array(&env, &[1u8; 32]);
+ let policy_id = BytesN::from_array(&env, &[2u8; 32]);
+
+ let (create_ok, suspend_ok, final_status) = env.as_contract(&contract_id, || {
+ let policy = create_test_policy(&env, policy_id.clone(), holder, insurer.clone(), asset_id);
+ let create_result = insurance::create_policy(env.clone(), policy).is_ok();
+
+ let suspend_result =
+ insurance::suspend_policy(env.clone(), policy_id.clone(), insurer.clone()).is_ok();
+
+ let final_policy = insurance::get_policy(env.clone(), policy_id.clone());
+ let status = final_policy.map(|p| p.status);
+
+ (create_result, suspend_result, status)
+ });
+
+ assert!(create_ok);
+ assert!(suspend_ok);
+ assert_eq!(final_status, Some(PolicyStatus::Suspended));
+}
+
+#[test]
+fn test_suspend_policy_unauthorized() {
+ let env = Env::default();
+ let contract_id = env.register(AssetUpContract, ());
+ let holder = Address::generate(&env);
+ let insurer = Address::generate(&env);
+ let unauthorized = Address::generate(&env);
+ let asset_id = BytesN::from_array(&env, &[1u8; 32]);
+ let policy_id = BytesN::from_array(&env, &[2u8; 32]);
+
+ let suspend_err = env.as_contract(&contract_id, || {
+ let policy = create_test_policy(&env, policy_id.clone(), holder, insurer, asset_id);
+ insurance::create_policy(env.clone(), policy).unwrap();
+
+ insurance::suspend_policy(env.clone(), policy_id.clone(), unauthorized.clone()).is_err()
+ });
+
+ assert!(suspend_err);
+}
+
+#[test]
+fn test_expire_policy_before_end_date() {
+ let env = Env::default();
+ env.ledger().with_mut(|li| {
+ li.timestamp = 1000;
+ });
+ let contract_id = env.register(AssetUpContract, ());
+ let holder = Address::generate(&env);
+ let insurer = Address::generate(&env);
+ let asset_id = BytesN::from_array(&env, &[1u8; 32]);
+ let policy_id = BytesN::from_array(&env, &[2u8; 32]);
+
+ let expire_err = env.as_contract(&contract_id, || {
+ let policy = create_test_policy(&env, policy_id.clone(), holder, insurer, asset_id);
+ insurance::create_policy(env.clone(), policy).unwrap();
+
+ // Try to expire before end_date (should fail)
+ insurance::expire_policy(env.clone(), policy_id.clone()).is_err()
+ });
+
+ assert!(expire_err);
+}
+
+#[test]
+fn test_expire_policy_after_end_date() {
+ let env = Env::default();
+ let contract_id = env.register(AssetUpContract, ());
+ let holder = Address::generate(&env);
+ let insurer = Address::generate(&env);
+ let asset_id = BytesN::from_array(&env, &[1u8; 32]);
+ let policy_id = BytesN::from_array(&env, &[2u8; 32]);
+
+ let (expire_ok, final_status) = env.as_contract(&contract_id, || {
+ // Set initial time
+ env.ledger().with_mut(|li| {
+ li.timestamp = 1000;
+ });
+
+ let mut policy = create_test_policy(&env, policy_id.clone(), holder, insurer, asset_id);
+ // Set policy to expire at timestamp 2000
+ policy.start_date = 1000;
+ policy.end_date = 2000;
+ insurance::create_policy(env.clone(), policy).unwrap();
+
+ // Advance time past end_date
+ env.ledger().with_mut(|li| {
+ li.timestamp = 2500;
+ });
+
+ let expire_result = insurance::expire_policy(env.clone(), policy_id.clone()).is_ok();
+
+ let final_policy = insurance::get_policy(env.clone(), policy_id.clone());
+ let status = final_policy.map(|p| p.status);
+
+ (expire_result, status)
+ });
+
+ assert!(expire_ok);
+ assert_eq!(final_status, Some(PolicyStatus::Expired));
+}
+
+#[test]
+fn test_renew_policy() {
+ let env = Env::default();
+ env.ledger().with_mut(|li| {
+ li.timestamp = 1000;
+ });
+ let contract_id = env.register(AssetUpContract, ());
+ let holder = Address::generate(&env);
+ let insurer = Address::generate(&env);
+ let asset_id = BytesN::from_array(&env, &[1u8; 32]);
+ let policy_id = BytesN::from_array(&env, &[2u8; 32]);
+
+ let (renew_ok, final_status, new_end_date, new_premium) = env.as_contract(&contract_id, || {
+ let policy = create_test_policy(&env, policy_id.clone(), holder, insurer.clone(), asset_id);
+ insurance::create_policy(env.clone(), policy).unwrap();
+
+ let new_end = 2000u64;
+ let new_prem = 1500i128;
+ let renew_result = insurance::renew_policy(
+ env.clone(),
+ policy_id.clone(),
+ new_end,
+ new_prem,
+ insurer.clone(),
+ )
+ .is_ok();
+
+ let final_policy = insurance::get_policy(env.clone(), policy_id.clone()).unwrap();
+
+ (
+ renew_result,
+ final_policy.status,
+ final_policy.end_date,
+ final_policy.premium,
+ )
+ });
+
+ assert!(renew_ok);
+ assert_eq!(final_status, PolicyStatus::Active);
+ assert_eq!(new_end_date, 2000u64);
+ assert_eq!(new_premium, 1500i128);
+}
+
+#[test]
+fn test_renew_expired_policy() {
+ let env = Env::default();
+ let contract_id = env.register(AssetUpContract, ());
+ let holder = Address::generate(&env);
+ let insurer = Address::generate(&env);
+ let asset_id = BytesN::from_array(&env, &[1u8; 32]);
+ let policy_id = BytesN::from_array(&env, &[2u8; 32]);
+
+ let (renew_ok, final_status) = env.as_contract(&contract_id, || {
+ // Set initial time
+ env.ledger().with_mut(|li| {
+ li.timestamp = 1000;
+ });
+
+ let mut policy = create_test_policy(&env, policy_id.clone(), holder, insurer.clone(), asset_id);
+ // Set policy to expire at timestamp 2000
+ policy.start_date = 1000;
+ policy.end_date = 2000;
+ insurance::create_policy(env.clone(), policy).unwrap();
+
+ // Advance time past end_date
+ env.ledger().with_mut(|li| {
+ li.timestamp = 2500;
+ });
+
+ // Expire the policy first
+ insurance::expire_policy(env.clone(), policy_id.clone()).unwrap();
+
+ // Now renew it to timestamp 3500
+ let renew_result = insurance::renew_policy(
+ env.clone(),
+ policy_id.clone(),
+ 3500,
+ 1500,
+ insurer.clone(),
+ )
+ .is_ok();
+
+ let final_policy = insurance::get_policy(env.clone(), policy_id.clone()).unwrap();
+
+ (renew_result, final_policy.status)
+ });
+
+ assert!(renew_ok);
+ assert_eq!(final_status, PolicyStatus::Active);
+}
+
+#[test]
+fn test_get_asset_policies() {
+ let env = Env::default();
+ let contract_id = env.register(AssetUpContract, ());
+ let holder = Address::generate(&env);
+ let insurer = Address::generate(&env);
+ let asset_id = BytesN::from_array(&env, &[1u8; 32]);
+ let policy_id1 = BytesN::from_array(&env, &[2u8; 32]);
+ let policy_id2 = BytesN::from_array(&env, &[3u8; 32]);
+
+ let policy_count = env.as_contract(&contract_id, || {
+ // Create two policies for the same asset
+ let policy1 = create_test_policy(
+ &env,
+ policy_id1.clone(),
+ holder.clone(),
+ insurer.clone(),
+ asset_id.clone(),
+ );
+ let policy2 = create_test_policy(
+ &env,
+ policy_id2.clone(),
+ holder.clone(),
+ insurer.clone(),
+ asset_id.clone(),
+ );
+
+ insurance::create_policy(env.clone(), policy1).unwrap();
+ insurance::create_policy(env.clone(), policy2).unwrap();
+
+ let policies = insurance::get_asset_policies(env.clone(), asset_id.clone());
+ policies.len()
+ });
+
+ assert_eq!(policy_count, 2);
+}
+
+#[test]
+fn test_status_transition_validation() {
+ let env = Env::default();
+ let contract_id = env.register(AssetUpContract, ());
+ let holder = Address::generate(&env);
+ let insurer = Address::generate(&env);
+ let asset_id = BytesN::from_array(&env, &[1u8; 32]);
+ let policy_id = BytesN::from_array(&env, &[2u8; 32]);
+
+ let (suspend_ok, cancel_after_suspend_ok, suspend_cancelled_err) =
+ env.as_contract(&contract_id, || {
+ let policy =
+ create_test_policy(&env, policy_id.clone(), holder.clone(), insurer.clone(), asset_id);
+ insurance::create_policy(env.clone(), policy).unwrap();
+
+ // Active -> Suspended (should work)
+ let suspend_result =
+ insurance::suspend_policy(env.clone(), policy_id.clone(), insurer.clone()).is_ok();
+
+ // Suspended -> Cancelled (should work)
+ let cancel_result =
+ insurance::cancel_policy(env.clone(), policy_id.clone(), holder.clone()).is_ok();
+
+ // Cancelled -> Suspended (should fail)
+ let suspend_again_result =
+ insurance::suspend_policy(env.clone(), policy_id.clone(), insurer.clone()).is_err();
+
+ (suspend_result, cancel_result, suspend_again_result)
+ });
+
+ assert!(suspend_ok);
+ assert!(cancel_after_suspend_ok);
+ assert!(suspend_cancelled_err);
+}
diff --git a/contracts/assetsup/src/tests/mod.rs b/contracts/assetsup/src/tests/mod.rs
index 35d6170..fd8a933 100644
--- a/contracts/assetsup/src/tests/mod.rs
+++ b/contracts/assetsup/src/tests/mod.rs
@@ -1,5 +1,6 @@
mod detokenization_new;
mod dividends_new;
+mod insurance_new;
mod integration;
mod tokenization_new;
mod transfer_restrictions_new;