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
95 changes: 75 additions & 20 deletions contracts/inheritance-contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ use soroban_sdk::{contract, contracterror, contractimpl, contracttype, Address,
const MAX_BENEFICIARIES: u32 = 100;
const PLAN_TTL_THRESHOLD: u32 = 500;
const PLAN_TTL_LEEWAY: u32 = 100;
const TEMP_TTL_THRESHOLD: u32 = 100;
const TEMP_TTL_LEEWAY: u32 = 50;

#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
Expand All @@ -18,6 +16,8 @@ pub enum Error {
NegativeAmount = 6,
InsufficientBalance = 7,
TooManyBeneficiaries = 8,
TimelockNotExpired = 9,
PayoutNotTriggered = 10,
}

#[contracttype]
Expand All @@ -40,6 +40,7 @@ pub struct Plan {
pub earn_yield: bool,
pub yield_rate_bps: u32,
pub is_active: bool,
pub timelock_duration: u64,
}

pub type InheritancePlan = Plan;
Expand All @@ -66,12 +67,6 @@ impl InheritanceContract {
.persistent()
.extend_ttl(key, PLAN_TTL_LEEWAY, PLAN_TTL_THRESHOLD);
}

fn extend_temp_ttl(env: &Env, key: &DataKey) {
env.storage()
.temporary()
.extend_ttl(key, TEMP_TTL_LEEWAY, TEMP_TTL_THRESHOLD);
}
}

#[contractimpl]
Expand All @@ -89,6 +84,7 @@ impl InheritanceContract {
grace_period: u64,
earn_yield: bool,
yield_rate_bps: u32,
timelock_duration: u64,
) -> Result<(), Error> {
owner.require_auth();

Expand Down Expand Up @@ -131,6 +127,7 @@ impl InheritanceContract {
earn_yield,
yield_rate_bps,
is_active: true,
timelock_duration,
};

env.storage().persistent().set(&key, &plan);
Expand Down Expand Up @@ -163,11 +160,11 @@ impl InheritanceContract {
/// emit payout events, and trigger anchor event emissions for fiat recipients.
pub fn claim(env: Env, owner: Address) -> Result<(), Error> {
let key = DataKey::Plan(owner.clone());
if !env.storage().persistent().has(&key) {
return Err(Error::PlanNotFound);
}

let plan: Plan = env.storage().persistent().get(&key).unwrap();
let plan: Plan = env
.storage()
.persistent()
.get(&key)
.ok_or(Error::PlanNotFound)?;

if plan.is_active {
return Err(Error::InactivityPeriodNotMet);
Expand All @@ -179,8 +176,38 @@ impl InheritanceContract {
}

let claim_key = DataKey::ClaimStatus(owner.clone());
env.storage().temporary().set(&claim_key, &true);
Self::extend_temp_ttl(&env, &claim_key);
if env.storage().persistent().has(&claim_key) {
return Ok(()); // Already claimed
}

env.storage().persistent().set(&claim_key, &current_time);
Self::extend_plan_ttl(&env, &claim_key);

Ok(())
}

/// Cancel a triggered payout during the timelock window.
pub fn cancel_claim(env: Env, owner: Address) -> Result<(), Error> {
owner.require_auth();

let key = DataKey::Plan(owner.clone());
let mut plan: Plan = env
.storage()
.persistent()
.get(&key)
.ok_or(Error::PlanNotFound)?;

let claim_key = DataKey::ClaimStatus(owner.clone());
if !env.storage().persistent().has(&claim_key) {
return Err(Error::PayoutNotTriggered);
}

env.storage().persistent().remove(&claim_key);

plan.is_active = true;
plan.last_ping = env.ledger().timestamp();
env.storage().persistent().set(&key, &plan);
Self::extend_plan_ttl(&env, &key);

Ok(())
}
Expand Down Expand Up @@ -212,18 +239,22 @@ impl InheritanceContract {
.get(&key)
.ok_or(Error::PlanNotFound)?;

if plan.is_active {
return Err(Error::InactivityPeriodNotMet);
}
let claim_key = DataKey::ClaimStatus(owner.clone());
let claim_time: u64 = env
.storage()
.persistent()
.get(&claim_key)
.ok_or(Error::PayoutNotTriggered)?;

let current_time = env.ledger().timestamp();
if current_time < plan.last_ping + plan.grace_period {
return Err(Error::InactivityPeriodNotMet);
if current_time < claim_time + plan.timelock_duration {
return Err(Error::TimelockNotExpired);
}

// Checks-effects-interactions: remove plan before transfers
// to prevent double payout and guard against re-entrancy
env.storage().persistent().remove(&key);
env.storage().persistent().remove(&claim_key);

let token_client = soroban_sdk::token::Client::new(&env, &plan.token);
let n = plan.beneficiaries.len();
Expand Down Expand Up @@ -265,6 +296,30 @@ impl InheritanceContract {

Ok(())
}

/// Reclaim the locked assets and delete the plan.
pub fn reclaim(env: Env, owner: Address) -> Result<(), Error> {
owner.require_auth();

let key = DataKey::Plan(owner.clone());
let plan: Plan = env
.storage()
.persistent()
.get(&key)
.ok_or(Error::PlanNotFound)?;

let claim_key = DataKey::ClaimStatus(owner.clone());
if env.storage().persistent().has(&claim_key) {
env.storage().persistent().remove(&claim_key);
}

env.storage().persistent().remove(&key);

let token_client = soroban_sdk::token::Client::new(&env, &plan.token);
token_client.transfer(&env.current_contract_address(), &owner, &plan.amount);

Ok(())
}
}

#[cfg(test)]
Expand Down
Loading
Loading