Skip to content

Commit dcc0ef0

Browse files
authored
Merge pull request #494 from Josue19-08/feat/insurance-policy-lifecycle
[Contract] Build Insurance Policy Lifecycle
2 parents 9a71f2a + dfa6f1a commit dcc0ef0

6 files changed

Lines changed: 792 additions & 12 deletions

File tree

contracts/assetsup/src/detokenization.rs

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,13 @@ pub fn propose_detokenization(env: &Env, asset_id: u64, proposer: Address) -> Re
4040
}
4141

4242
/// Execute detokenization if vote passed
43+
/// This will remove all tokens from circulation and clear tokenization records
4344
pub fn execute_detokenization(env: &Env, asset_id: u64, proposal_id: u64) -> Result<(), Error> {
4445
let store = env.storage().persistent();
4546

4647
// Verify asset is tokenized
4748
let key = TokenDataKey::TokenizedAsset(asset_id);
48-
let _: TokenizedAsset = store.get(&key).ok_or(Error::AssetNotTokenized)?;
49+
let tokenized_asset: TokenizedAsset = store.get(&key).ok_or(Error::AssetNotTokenized)?;
4950

5051
// Check if proposal is active
5152
let proposal_key = TokenDataKey::DetokenizationProposal(asset_id);
@@ -64,6 +65,65 @@ pub fn execute_detokenization(env: &Env, asset_id: u64, proposal_id: u64) -> Res
6465
return Err(Error::DetokenizationNotApproved);
6566
}
6667

68+
// Save total supply for event before clearing
69+
let total_supply = tokenized_asset.total_supply;
70+
71+
// Clear all votes BEFORE removing TokenizedAsset (voting module needs it)
72+
voting::clear_proposal_votes(env, asset_id, proposal_id)?;
73+
74+
// Get list of all token holders before clearing
75+
let holders_list_key = TokenDataKey::TokenHoldersList(asset_id);
76+
let holders = store.get::<_, soroban_sdk::Vec<Address>>(&holders_list_key)
77+
.ok_or(Error::AssetNotTokenized)?;
78+
79+
// Remove all token holder records
80+
for holder in holders.iter() {
81+
let holder_key = TokenDataKey::TokenHolder(asset_id, holder.clone());
82+
if store.has(&holder_key) {
83+
store.remove(&holder_key);
84+
}
85+
86+
// Remove any token locks
87+
let lock_key = TokenDataKey::TokenLockedUntil(asset_id, holder.clone());
88+
if store.has(&lock_key) {
89+
store.remove(&lock_key);
90+
}
91+
92+
// Remove unclaimed dividends
93+
let dividend_key = TokenDataKey::UnclaimedDividend(asset_id, holder);
94+
if store.has(&dividend_key) {
95+
store.remove(&dividend_key);
96+
}
97+
}
98+
99+
// Remove token holders list
100+
if store.has(&holders_list_key) {
101+
store.remove(&holders_list_key);
102+
}
103+
104+
// Remove transfer restrictions
105+
let restriction_key = TokenDataKey::TransferRestriction(asset_id);
106+
if store.has(&restriction_key) {
107+
store.remove(&restriction_key);
108+
}
109+
110+
// Remove whitelist
111+
let whitelist_key = TokenDataKey::Whitelist(asset_id);
112+
if store.has(&whitelist_key) {
113+
store.remove(&whitelist_key);
114+
}
115+
116+
// Remove token metadata
117+
let metadata_key = TokenDataKey::TokenMetadata(asset_id);
118+
if store.has(&metadata_key) {
119+
store.remove(&metadata_key);
120+
}
121+
122+
// Remove the tokenized asset record (this eliminates all tokens from circulation)
123+
if store.has(&key) {
124+
store.remove(&key);
125+
}
126+
67127
// Update proposal to executed
68128
let timestamp = env.ledger().timestamp();
69129
let executed_proposal = DetokenizationProposal::Executed(ExecutedProposal {
@@ -72,13 +132,10 @@ pub fn execute_detokenization(env: &Env, asset_id: u64, proposal_id: u64) -> Res
72132
});
73133
store.set(&proposal_key, &executed_proposal);
74134

75-
// Clear all votes
76-
voting::clear_proposal_votes(env, asset_id, proposal_id)?;
77-
78-
// Emit event: (asset_id, proposal_id)
135+
// Emit event: (asset_id, proposal_id, total_supply_removed)
79136
env.events().publish(
80137
("detokenization", "asset_detokenized"),
81-
(asset_id, proposal_id),
138+
(asset_id, proposal_id, total_supply),
82139
);
83140

84141
Ok(())

contracts/assetsup/src/insurance.rs

Lines changed: 154 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,10 @@ pub enum ClaimStatus {
2626
#[contracttype]
2727
#[derive(Clone, Debug, Eq, PartialEq)]
2828
pub enum PolicyType {
29-
Comprehensive,
30-
Theft,
31-
Damage,
3229
Liability,
33-
BusinessInterruption,
30+
Property,
31+
Comprehensive,
32+
Custom,
3433
}
3534

3635
#[contracttype]
@@ -50,6 +49,7 @@ pub struct InsurancePolicy {
5049
pub holder: Address,
5150
pub insurer: Address,
5251
pub asset_id: BytesN<32>,
52+
pub policy_type: PolicyType,
5353
pub coverage_amount: i128,
5454
pub deductible: i128,
5555
pub premium: i128,
@@ -81,22 +81,41 @@ pub enum DataKey {
8181
AssetPolicies(BytesN<32>),
8282
}
8383

84+
/// Create a new insurance policy with date validation and asset indexing
8485
pub fn create_policy(env: Env, policy: InsurancePolicy) -> Result<(), Error> {
85-
policy.insurer.require_auth();
86-
86+
// Validate coverage and deductible
8787
if policy.coverage_amount <= 0 || policy.deductible >= policy.coverage_amount {
8888
return Err(Error::InvalidPayment);
8989
}
9090

91+
// Validate premium
92+
if policy.premium <= 0 {
93+
return Err(Error::InvalidPayment);
94+
}
95+
96+
// Validate dates: start_date must be before end_date
97+
if policy.start_date >= policy.end_date {
98+
return Err(Error::InvalidPayment);
99+
}
100+
101+
// Validate that start_date is not in the past (allow current timestamp)
102+
let current_time = env.ledger().timestamp();
103+
if policy.start_date < current_time {
104+
return Err(Error::InvalidPayment);
105+
}
106+
91107
let key = DataKey::Policy(policy.policy_id.clone());
92108
let store = env.storage().persistent();
93109

110+
// Check if policy already exists
94111
if store.has(&key) {
95112
return Err(Error::AssetAlreadyExists);
96113
}
97114

115+
// Store the policy
98116
store.set(&key, &policy);
99117

118+
// Maintain asset index: add policy to asset's policy list
100119
let mut list: Vec<BytesN<32>> = store
101120
.get(&DataKey::AssetPolicies(policy.asset_id.clone()))
102121
.unwrap_or_else(|| Vec::new(&env));
@@ -108,6 +127,135 @@ pub fn create_policy(env: Env, policy: InsurancePolicy) -> Result<(), Error> {
108127
Ok(())
109128
}
110129

130+
/// Cancel a policy (authorized by holder or insurer)
131+
pub fn cancel_policy(env: Env, policy_id: BytesN<32>, caller: Address) -> Result<(), Error> {
132+
let store = env.storage().persistent();
133+
let key = DataKey::Policy(policy_id.clone());
134+
135+
let mut policy: InsurancePolicy = store.get(&key).ok_or(Error::AssetNotFound)?;
136+
137+
// Only holder or insurer can cancel
138+
if caller != policy.holder && caller != policy.insurer {
139+
return Err(Error::Unauthorized);
140+
}
141+
142+
// Validate status transition: only Active or Suspended policies can be cancelled
143+
if policy.status != PolicyStatus::Active && policy.status != PolicyStatus::Suspended {
144+
return Err(Error::Unauthorized);
145+
}
146+
147+
policy.status = PolicyStatus::Cancelled;
148+
store.set(&key, &policy);
149+
150+
log!(&env, "PolicyCancelled: {:?}", policy_id);
151+
Ok(())
152+
}
153+
154+
/// Suspend a policy (insurer only)
155+
pub fn suspend_policy(env: Env, policy_id: BytesN<32>, insurer: Address) -> Result<(), Error> {
156+
let store = env.storage().persistent();
157+
let key = DataKey::Policy(policy_id.clone());
158+
159+
let mut policy: InsurancePolicy = store.get(&key).ok_or(Error::AssetNotFound)?;
160+
161+
// Only insurer can suspend
162+
if insurer != policy.insurer {
163+
return Err(Error::Unauthorized);
164+
}
165+
166+
// Validate status transition: only Active policies can be suspended
167+
if policy.status != PolicyStatus::Active {
168+
return Err(Error::Unauthorized);
169+
}
170+
171+
policy.status = PolicyStatus::Suspended;
172+
store.set(&key, &policy);
173+
174+
log!(&env, "PolicySuspended: {:?}", policy_id);
175+
Ok(())
176+
}
177+
178+
/// Expire a policy (permissionless, but requires end_date < current timestamp)
179+
pub fn expire_policy(env: Env, policy_id: BytesN<32>) -> Result<(), Error> {
180+
let store = env.storage().persistent();
181+
let key = DataKey::Policy(policy_id.clone());
182+
183+
let mut policy: InsurancePolicy = store.get(&key).ok_or(Error::AssetNotFound)?;
184+
185+
let current_time = env.ledger().timestamp();
186+
187+
// Require that end_date has passed
188+
if policy.end_date >= current_time {
189+
return Err(Error::Unauthorized);
190+
}
191+
192+
// Validate status transition: only Active or Suspended policies can expire
193+
if policy.status != PolicyStatus::Active && policy.status != PolicyStatus::Suspended {
194+
return Err(Error::Unauthorized);
195+
}
196+
197+
policy.status = PolicyStatus::Expired;
198+
store.set(&key, &policy);
199+
200+
log!(&env, "PolicyExpired: {:?}", policy_id);
201+
Ok(())
202+
}
203+
204+
/// Renew a policy (insurer only)
205+
pub fn renew_policy(
206+
env: Env,
207+
policy_id: BytesN<32>,
208+
new_end_date: u64,
209+
new_premium: i128,
210+
insurer: Address,
211+
) -> Result<(), Error> {
212+
let store = env.storage().persistent();
213+
let key = DataKey::Policy(policy_id.clone());
214+
215+
let mut policy: InsurancePolicy = store.get(&key).ok_or(Error::AssetNotFound)?;
216+
217+
// Only insurer can renew
218+
if insurer != policy.insurer {
219+
return Err(Error::Unauthorized);
220+
}
221+
222+
// Validate status transition: only Active or Expired policies can be renewed
223+
if policy.status != PolicyStatus::Active && policy.status != PolicyStatus::Expired {
224+
return Err(Error::Unauthorized);
225+
}
226+
227+
let current_time = env.ledger().timestamp();
228+
229+
// Validate new end date is in the future
230+
if new_end_date <= current_time {
231+
return Err(Error::InvalidPayment);
232+
}
233+
234+
// Validate new premium is positive
235+
if new_premium <= 0 {
236+
return Err(Error::InvalidPayment);
237+
}
238+
239+
// Update policy
240+
policy.end_date = new_end_date;
241+
policy.premium = new_premium;
242+
policy.status = PolicyStatus::Active;
243+
policy.last_payment = current_time;
244+
245+
store.set(&key, &policy);
246+
247+
log!(&env, "PolicyRenewed: {:?}", policy_id);
248+
Ok(())
249+
}
250+
251+
/// Get all policies for a specific asset
252+
pub fn get_asset_policies(env: Env, asset_id: BytesN<32>) -> Vec<BytesN<32>> {
253+
env.storage()
254+
.persistent()
255+
.get(&DataKey::AssetPolicies(asset_id))
256+
.unwrap_or_else(|| Vec::new(&env))
257+
}
258+
111259
pub fn file_claim(env: Env, claim: InsuranceClaim) -> Result<(), Error> {
112260
claim.claimant.require_auth();
113261

contracts/assetsup/src/lib.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,4 +770,64 @@ impl AssetUpContract {
770770
pub fn is_detokenization_active(env: Env, asset_id: u64) -> Result<bool, Error> {
771771
detokenization::is_detokenization_active(&env, asset_id)
772772
}
773+
774+
// =====================
775+
// Insurance Policy Management
776+
// =====================
777+
778+
/// Create a new insurance policy
779+
pub fn create_insurance_policy(
780+
env: Env,
781+
policy: insurance::InsurancePolicy,
782+
) -> Result<(), Error> {
783+
policy.insurer.require_auth();
784+
insurance::create_policy(env, policy)
785+
}
786+
787+
/// Cancel a policy (holder or insurer)
788+
pub fn cancel_insurance_policy(
789+
env: Env,
790+
policy_id: BytesN<32>,
791+
caller: Address,
792+
) -> Result<(), Error> {
793+
caller.require_auth();
794+
insurance::cancel_policy(env, policy_id, caller)
795+
}
796+
797+
/// Suspend a policy (insurer only)
798+
pub fn suspend_insurance_policy(
799+
env: Env,
800+
policy_id: BytesN<32>,
801+
insurer: Address,
802+
) -> Result<(), Error> {
803+
insurer.require_auth();
804+
insurance::suspend_policy(env, policy_id, insurer)
805+
}
806+
807+
/// Expire a policy (permissionless)
808+
pub fn expire_insurance_policy(env: Env, policy_id: BytesN<32>) -> Result<(), Error> {
809+
insurance::expire_policy(env, policy_id)
810+
}
811+
812+
/// Renew a policy (insurer only)
813+
pub fn renew_insurance_policy(
814+
env: Env,
815+
policy_id: BytesN<32>,
816+
new_end_date: u64,
817+
new_premium: i128,
818+
insurer: Address,
819+
) -> Result<(), Error> {
820+
insurer.require_auth();
821+
insurance::renew_policy(env, policy_id, new_end_date, new_premium, insurer)
822+
}
823+
824+
/// Get a specific policy
825+
pub fn get_insurance_policy(env: Env, policy_id: BytesN<32>) -> Option<insurance::InsurancePolicy> {
826+
insurance::get_policy(env, policy_id)
827+
}
828+
829+
/// Get all policies for an asset
830+
pub fn get_asset_insurance_policies(env: Env, asset_id: BytesN<32>) -> Vec<BytesN<32>> {
831+
insurance::get_asset_policies(env, asset_id)
832+
}
773833
}

0 commit comments

Comments
 (0)