Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 63 additions & 6 deletions contracts/assetsup/src/detokenization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<Address>>(&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(&dividend_key) {
store.remove(&dividend_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 {
Expand All @@ -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(())
Expand Down
160 changes: 154 additions & 6 deletions contracts/assetsup/src/insurance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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,
Expand Down Expand Up @@ -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<BytesN<32>> = store
.get(&DataKey::AssetPolicies(policy.asset_id.clone()))
.unwrap_or_else(|| Vec::new(&env));
Expand All @@ -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<BytesN<32>> {
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();

Expand Down
60 changes: 60 additions & 0 deletions contracts/assetsup/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -770,4 +770,64 @@ impl AssetUpContract {
pub fn is_detokenization_active(env: Env, asset_id: u64) -> Result<bool, Error> {
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::InsurancePolicy> {
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<BytesN<32>> {
insurance::get_asset_policies(env, asset_id)
}
}
Loading
Loading