Skip to content
Open
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
19 changes: 18 additions & 1 deletion contracts/dividend/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
#![no_std]

use soroban_sdk::{
contract, contractimpl, contracttype, token, Address, Env, Symbol, Vec,
contract, contractimpl, contracttype, token, Address, Env, String, Symbol, Vec,
};

/// ─── TTL Constants ──────────────────────────────────────────────────────────
const LEDGERS_PER_DAY: u32 = 17_280;
const INSTANCE_TTL_THRESHOLD: u32 = 30 * LEDGERS_PER_DAY;
const INSTANCE_TTL_EXTEND_TO: u32 = 180 * LEDGERS_PER_DAY;

#[contracttype]
#[derive(Clone)]
pub enum DataKey {
Expand Down Expand Up @@ -33,6 +38,7 @@ pub struct DividendContract;
impl DividendContract {
pub fn initialize(env: Env, admin: Address, asset: Address, treasury: Address) {
admin.require_auth();
Self::bump_instance(&env);
env.storage().instance().set(&DataKey::Admin, &admin);
env.storage().instance().set(&DataKey::AssetAddress, &asset);
env.storage().instance().set(&DataKey::TreasuryContract, &treasury);
Expand All @@ -55,6 +61,7 @@ impl DividendContract {
) -> u32 {
admin.require_auth();
Self::require_admin(&env, &admin);
Self::bump_instance(&env);

if recipients.len() != shares.len() {
panic!("recipients and shares length mismatch");
Expand Down Expand Up @@ -115,6 +122,16 @@ impl DividendContract {
.unwrap_or(Vec::new(&env))
}

/// Bump the instance storage TTL to prevent the contract from expiring.
/// Called at the start of every state-changing function.
///
/// Threshold: 30 days (518,400 ledgers); Extend to: 180 days (3,110,400 ledgers).
fn bump_instance(env: &Env) {
env.storage()
.instance()
.extend_ttl(INSTANCE_TTL_THRESHOLD, INSTANCE_TTL_EXTEND_TO);
}

fn require_admin(env: &Env, caller: &Address) {
let admin: Address = env.storage().instance().get(&DataKey::Admin).unwrap();
if admin != *caller { panic!("unauthorized"); }
Expand Down
19 changes: 18 additions & 1 deletion contracts/governance/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
#![no_std]

use soroban_sdk::{
contract, contractimpl, contracttype, Address, Env, Symbol, Vec,
contract, contractimpl, contracttype, Address, Env, Symbol,
};

/// ─── TTL Constants ──────────────────────────────────────────────────────────
const LEDGERS_PER_DAY: u32 = 17_280;
const INSTANCE_TTL_THRESHOLD: u32 = 30 * LEDGERS_PER_DAY;
const INSTANCE_TTL_EXTEND_TO: u32 = 180 * LEDGERS_PER_DAY;

#[contracttype]
#[derive(Clone)]
pub enum DataKey {
Expand Down Expand Up @@ -39,6 +44,7 @@ impl GovernanceContract {
treasury: Address,
) {
admin.require_auth();
Self::bump_instance(&env);
env.storage().instance().set(&DataKey::Admin, &admin);
env.storage().instance().set(&DataKey::VotingContract, &voting);
env.storage().instance().set(&DataKey::LoanContract, &loan);
Expand All @@ -60,6 +66,7 @@ impl GovernanceContract {
pub fn update_rules(env: Env, admin: Address, rules: CoopRules) {
admin.require_auth();
Self::require_admin(&env, &admin);
Self::bump_instance(&env);
env.storage().instance().set(&DataKey::Rules, &rules);
env.events().publish((Symbol::new(&env, "rules_updated"),), ());
}
Expand All @@ -68,6 +75,16 @@ impl GovernanceContract {
env.storage().instance().get(&DataKey::Rules).unwrap()
}

/// Bump the instance storage TTL to prevent the contract from expiring.
/// Called at the start of every state-changing function.
///
/// Threshold: 30 days (518,400 ledgers); Extend to: 180 days (3,110,400 ledgers).
fn bump_instance(env: &Env) {
env.storage()
.instance()
.extend_ttl(INSTANCE_TTL_THRESHOLD, INSTANCE_TTL_EXTEND_TO);
}

fn require_admin(env: &Env, caller: &Address) {
let admin: Address = env.storage().instance().get(&DataKey::Admin).unwrap();
if admin != *caller { panic!("unauthorized"); }
Expand Down
27 changes: 27 additions & 0 deletions contracts/loan/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ use soroban_sdk::{
contract, contractimpl, contracttype, token, Address, Env, Symbol, Vec, String,
};

/// ─── TTL Constants ──────────────────────────────────────────────────────────
/// Number of ledgers in one day (approximate, based on ~5s ledger close time).
const LEDGERS_PER_DAY: u32 = 17_280;

/// Extend instance storage TTL when it drops below this threshold (30 days).
const INSTANCE_TTL_THRESHOLD: u32 = 30 * LEDGERS_PER_DAY;

/// Extend instance storage TTL to this many ledgers (180 days ≈ 6 months).
const INSTANCE_TTL_EXTEND_TO: u32 = 180 * LEDGERS_PER_DAY;

#[contracttype]
#[derive(Clone)]
pub enum DataKey {
Expand Down Expand Up @@ -46,6 +56,7 @@ pub struct LoanContract;
impl LoanContract {
pub fn initialize(env: Env, admin: Address, treasury: Address, asset: Address) {
admin.require_auth();
Self::bump_instance(&env);
if env.storage().instance().has(&DataKey::Admin) {
panic!("already initialized");
}
Expand All @@ -65,6 +76,7 @@ impl LoanContract {
repayment_days: u32,
) -> u32 {
borrower.require_auth();
Self::bump_instance(&env);
if amount <= 0 { panic!("amount must be positive"); }

let counter: u32 = env.storage().instance()
Expand Down Expand Up @@ -104,6 +116,7 @@ impl LoanContract {
pub fn approve_loan(env: Env, admin: Address, loan_id: u32) {
admin.require_auth();
Self::require_admin(&env, &admin);
Self::bump_instance(&env);

let mut loans: Vec<Loan> = env.storage().instance()
.get(&DataKey::Loans).unwrap();
Expand Down Expand Up @@ -138,6 +151,7 @@ impl LoanContract {
/// Borrower repays (partial or full).
pub fn repay(env: Env, borrower: Address, loan_id: u32, amount: i128) {
borrower.require_auth();
Self::bump_instance(&env);

let mut loans: Vec<Loan> = env.storage().instance()
.get(&DataKey::Loans).unwrap();
Expand Down Expand Up @@ -187,6 +201,19 @@ impl LoanContract {
loans.get(idx).unwrap()
}

// ── TTL helpers ──────────────────────────────────────────────────────────

/// Bump the instance storage TTL to prevent the contract from expiring.
/// Called at the start of every state-changing function.
///
/// Threshold: 30 days (518,400 ledgers) — only extend when TTL falls below.
/// Extend to: 180 days (3,110,400 ledgers) — ~6 months of ledger lifetime.
fn bump_instance(env: &Env) {
env.storage()
.instance()
.extend_ttl(INSTANCE_TTL_THRESHOLD, INSTANCE_TTL_EXTEND_TO);
}

fn require_admin(env: &Env, caller: &Address) {
let admin: Address = env.storage().instance().get(&DataKey::Admin).unwrap();
if admin != *caller { panic!("unauthorized"); }
Expand Down
45 changes: 45 additions & 0 deletions contracts/treasury/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ use soroban_sdk::{
contract, contractimpl, contracttype, token, Address, Env, Symbol, Vec, String,
};

/// ─── TTL Constants ──────────────────────────────────────────────────────────
/// Number of ledgers in one day (approximate, based on ~5s ledger close time).
const LEDGERS_PER_DAY: u32 = 17_280;

/// Extend instance storage TTL when it drops below this threshold (30 days).
const INSTANCE_TTL_THRESHOLD: u32 = 30 * LEDGERS_PER_DAY;

/// Extend instance storage TTL to this many ledgers (180 days ≈ 6 months).
const INSTANCE_TTL_EXTEND_TO: u32 = 180 * LEDGERS_PER_DAY;

/// Extend persistent entry TTL when it drops below this threshold (30 days).
const PERSISTENT_TTL_THRESHOLD: u32 = 30 * LEDGERS_PER_DAY;

/// Extend persistent entry TTL to this many ledgers (180 days ≈ 6 months).
const PERSISTENT_TTL_EXTEND_TO: u32 = 180 * LEDGERS_PER_DAY;

/// ─── Storage Keys ────────────────────────────────────────────────────────────

#[contracttype]
Expand Down Expand Up @@ -69,6 +85,7 @@ impl TreasuryContract {
asset: Address,
) -> GroupInfo {
admin.require_auth();
Self::bump_instance(&env);

if env.storage().instance().has(&DataKey::Admin) {
panic!("already initialized");
Expand All @@ -95,6 +112,7 @@ impl TreasuryContract {
pub fn add_member(env: Env, admin: Address, member: Address) {
admin.require_auth();
Self::require_admin(&env, &admin);
Self::bump_instance(&env);

let mut members: Vec<Address> = env
.storage().instance()
Expand All @@ -115,6 +133,7 @@ impl TreasuryContract {
pub fn contribute(env: Env, member: Address, amount: i128, period: u32) {
member.require_auth();
Self::require_member(&env, &member);
Self::bump_instance(&env);

if amount <= 0 {
panic!("amount must be positive");
Expand Down Expand Up @@ -142,6 +161,18 @@ impl TreasuryContract {
env.storage().persistent()
.set(&DataKey::Contributions(member.clone()), &history);

// Extend the TTL of this member's contribution history so it isn't
// silently deleted by the network. Members who contribute regularly
// keep their history alive; stale entries from inactive members will
// naturally expire.
env.storage()
.persistent()
.extend_ttl(
&DataKey::Contributions(member.clone()),
PERSISTENT_TTL_THRESHOLD,
PERSISTENT_TTL_EXTEND_TO,
);

// Update total
let total: i128 = env.storage().instance()
.get(&DataKey::TotalContributions).unwrap_or(0);
Expand All @@ -158,6 +189,7 @@ impl TreasuryContract {
pub fn withdraw(env: Env, admin: Address, to: Address, amount: i128) {
admin.require_auth();
Self::require_admin(&env, &admin);
Self::bump_instance(&env);

let asset: Address = env.storage().instance().get(&DataKey::AssetAddress).unwrap();
let token_client = token::Client::new(&env, &asset);
Expand Down Expand Up @@ -252,6 +284,19 @@ impl TreasuryContract {
}
}

// ── TTL helpers ──────────────────────────────────────────────────────────

/// Bump the instance storage TTL to prevent the contract from expiring.
/// Called at the start of every state-changing function.
///
/// Threshold: 30 days (518,400 ledgers) — only extend when TTL falls below.
/// Extend to: 180 days (3,110,400 ledgers) — ~6 months of ledger lifetime.
fn bump_instance(env: &Env) {
env.storage()
.instance()
.extend_ttl(INSTANCE_TTL_THRESHOLD, INSTANCE_TTL_EXTEND_TO);
}

// ── Internal helpers ─────────────────────────────────────────────────────

fn require_admin(env: &Env, caller: &Address) {
Expand Down
53 changes: 53 additions & 0 deletions contracts/voting/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ use soroban_sdk::{
contract, contractimpl, contracttype, Address, Env, Map, Symbol, Vec, String,
};

/// ─── TTL Constants ──────────────────────────────────────────────────────────
/// Number of ledgers in one day (approximate, based on ~5s ledger close time).
const LEDGERS_PER_DAY: u32 = 17_280;

/// Extend instance storage TTL when it drops below this threshold (30 days).
const INSTANCE_TTL_THRESHOLD: u32 = 30 * LEDGERS_PER_DAY;

/// Extend instance storage TTL to this many ledgers (180 days ≈ 6 months).
const INSTANCE_TTL_EXTEND_TO: u32 = 180 * LEDGERS_PER_DAY;

/// Extend persistent entry TTL when it drops below this threshold (30 days).
const PERSISTENT_TTL_THRESHOLD: u32 = 30 * LEDGERS_PER_DAY;

/// Extend persistent entry TTL to this many ledgers (180 days ≈ 6 months).
const PERSISTENT_TTL_EXTEND_TO: u32 = 180 * LEDGERS_PER_DAY;

#[contracttype]
#[derive(Clone)]
pub enum DataKey {
Expand Down Expand Up @@ -58,6 +74,7 @@ pub struct VotingContract;
impl VotingContract {
pub fn initialize(env: Env, admin: Address, treasury: Address) {
admin.require_auth();
Self::bump_instance(&env);
env.storage().instance().set(&DataKey::Admin, &admin);
env.storage().instance().set(&DataKey::TreasuryContract, &treasury);
env.storage().instance().set(&DataKey::ProposalCounter, &0u32);
Expand All @@ -76,6 +93,7 @@ impl VotingContract {
payload: String,
) -> u32 {
proposer.require_auth();
Self::bump_instance(&env);

let counter: u32 = env.storage().instance()
.get(&DataKey::ProposalCounter).unwrap_or(0);
Expand Down Expand Up @@ -109,6 +127,16 @@ impl VotingContract {
env.storage().persistent()
.set(&DataKey::Votes(id), &Map::<Address, bool>::new(&env));

// Extend TTL for the new vote entry so it survives the full
// voting period plus a grace window after finalization.
env.storage()
.persistent()
.extend_ttl(
&DataKey::Votes(id),
PERSISTENT_TTL_THRESHOLD,
PERSISTENT_TTL_EXTEND_TO,
);

env.events().publish(
(Symbol::new(&env, "proposal_created"),),
(id, proposer),
Expand All @@ -119,6 +147,7 @@ impl VotingContract {
/// Member casts a vote on a proposal.
pub fn vote(env: Env, voter: Address, proposal_id: u32, approve: bool) {
voter.require_auth();
Self::bump_instance(&env);

let mut proposals: Vec<Proposal> = env.storage().instance()
.get(&DataKey::Proposals).unwrap();
Expand All @@ -143,6 +172,16 @@ impl VotingContract {
votes.set(voter.clone(), approve);
env.storage().persistent().set(&DataKey::Votes(proposal_id), &votes);

// Extend TTL for the vote map so tally records survive through
// the voting window and the post-finalization query period.
env.storage()
.persistent()
.extend_ttl(
&DataKey::Votes(proposal_id),
PERSISTENT_TTL_THRESHOLD,
PERSISTENT_TTL_EXTEND_TO,
);

if approve {
proposal.votes_for += 1;
} else {
Expand All @@ -160,6 +199,7 @@ impl VotingContract {

/// Finalize a proposal after deadline.
pub fn finalize(env: Env, proposal_id: u32) -> ProposalStatus {
Self::bump_instance(&env);
let mut proposals: Vec<Proposal> = env.storage().instance()
.get(&DataKey::Proposals).unwrap();
let idx = Self::find_proposal_idx(&proposals, proposal_id);
Expand Down Expand Up @@ -204,6 +244,19 @@ impl VotingContract {
.unwrap_or(Map::new(&env))
}

// ── TTL helpers ──────────────────────────────────────────────────────────

/// Bump the instance storage TTL to prevent the contract from expiring.
/// Called at the start of every state-changing function.
///
/// Threshold: 30 days (518,400 ledgers) — only extend when TTL falls below.
/// Extend to: 180 days (3,110,400 ledgers) — ~6 months of ledger lifetime.
fn bump_instance(env: &Env) {
env.storage()
.instance()
.extend_ttl(INSTANCE_TTL_THRESHOLD, INSTANCE_TTL_EXTEND_TO);
}

fn find_proposal_idx(proposals: &Vec<Proposal>, id: u32) -> u32 {
for i in 0..proposals.len() {
if proposals.get(i).unwrap().id == id { return i; }
Expand Down