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
39 changes: 39 additions & 0 deletions contracts/claims-processor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2067,6 +2067,45 @@ impl ClaimsProcessor {
Self::claim_deadline(&env)
}

/// Submit evidence files for a claim, rejecting zero hashes and enforcing max file count (issue #728, #747).
pub fn submit_claim_evidence(env: Env, claim_id: u128, claimant: Address, evidence_hashes: Vec<Bytes>) {
Self::require_not_paused(&env);
claimant.require_auth();
let claim: Claim = env.storage().persistent()
.get(&StorageKey::Claim(claim_id))
.unwrap_or_else(|| panic_with_error!(&env, Error::ClaimNotFound));
if claim.claimant != claimant {
panic_with_error!(&env, Error::Unauthorized);
}
if evidence_hashes.len() > MAX_EVIDENCE_FILES {
panic_with_error!(&env, Error::EvidenceLimitExceeded);
}
for i in 0..evidence_hashes.len() {
let hash = evidence_hashes.get_unchecked(i);
if hash.is_empty() {
panic_with_error!(&env, Error::InvalidEvidence);
}
let mut all_zeros = true;
for b in hash.iter() {
if b != 0 {
all_zeros = false;
break;
}
}
if all_zeros {
panic_with_error!(&env, Error::InvalidEvidence);
}
}
let key = StorageKey::ClaimEvidence(claim_id);
env.storage().persistent().set(&key, &evidence_hashes);
env.storage().persistent().extend_ttl(&key, TTL_THRESHOLD, TTL_EXTEND_TO);
}

/// Retrieve attached evidence hashes for a claim if available (issue #728, #747).
pub fn get_claim_evidence(env: Env, claim_id: u128) -> Vec<Bytes> {
env.storage().persistent().get(&StorageKey::ClaimEvidence(claim_id)).unwrap_or_else(|| Vec::new(&env))
}

// ── Payout Delay (issue #432) ─────────────────────────────────────────────

/// Set the delay in seconds between claim approval and actual payout.
Expand Down
38 changes: 37 additions & 1 deletion contracts/governance-dao/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ enum StorageKey {
Participation,
/// Who an address has delegated its voting power to — Address → Address.
DelegateOf(Address),
/// Expiration timestamp of a vote delegation — Address -> u64 (issue #726).
DelegationExpiry(Address),
/// Addresses currently delegating to an account — Address → Vec<Address>.
Delegators(Address),
/// Marks that a delegator's weight was already counted in a proposal, so
Expand Down Expand Up @@ -675,6 +677,12 @@ impl GovernanceDao {
.persistent()
.set(&StorageKey::Delegators(delegate.clone()), &delegators);

// Set delegation expiry (default 180 days) (issue #726)
let expiry = env.ledger().timestamp().saturating_add(180 * 24 * 60 * 60);
env.storage()
.persistent()
.set(&StorageKey::DelegationExpiry(delegator.clone()), &expiry);

let config: DaoConfig = env.storage().instance().get(&StorageKey::Config).unwrap();
let weight = token::Client::new(&env, &config.gov_token).balance(&delegator);

Expand All @@ -699,6 +707,28 @@ impl GovernanceDao {
);
}

/// Delegate voting power with an explicit expiration duration in seconds (issue #726).
pub fn delegate_with_expiry(env: Env, delegator: Address, delegate: Address, duration_seconds: u64) {
if duration_seconds == 0 {
panic_with_error!(&env, Error::InvalidInput);
}
Self::delegate(env.clone(), delegator.clone(), delegate);
let expiry = env.ledger().timestamp().saturating_add(duration_seconds);
env.storage()
.persistent()
.set(&StorageKey::DelegationExpiry(delegator), &expiry);
}

/// Check if a delegation is expired (issue #726).
pub fn is_delegation_expired(env: Env, delegator: Address) -> bool {
let now = env.ledger().timestamp();
if let Some(expiry) = env.storage().persistent().get::<_, u64>(&StorageKey::DelegationExpiry(delegator)) {
now > expiry
} else {
false
}
}

/// Withdraw a delegation, returning voting authority to the delegator.
///
/// Effective immediately for any proposal the delegate has not already
Expand All @@ -717,6 +747,9 @@ impl GovernanceDao {
env.storage()
.persistent()
.remove(&StorageKey::DelegateOf(delegator.clone()));
env.storage()
.persistent()
.remove(&StorageKey::DelegationExpiry(delegator.clone()));
Self::remove_delegator(&env, &delegate, &delegator);

env.events().publish(
Expand All @@ -739,8 +772,11 @@ impl GovernanceDao {
);
}

/// Who this address has delegated to, if anyone.
/// Who this address has delegated to, if anyone (respects expiration).
pub fn get_delegate(env: Env, delegator: Address) -> Option<Address> {
if Self::is_delegation_expired(env.clone(), delegator.clone()) {
return None;
}
env.storage()
.persistent()
.get(&StorageKey::DelegateOf(delegator))
Expand Down
9 changes: 5 additions & 4 deletions contracts/oracle-verifier/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ use parashield_common::{TTL_THRESHOLD, TTL_EXTEND_TO, ADMIN_TRANSFER_TIMELOCK};
/// cannot overflow i128.
#[allow(dead_code)]
const MAX_ORACLES: u32 = 100;
/// Maximum number of registered oracles per key or data type (issue #727).
pub const MAX_ORACLES_PER_KEY: u32 = 10;
/// Maximum number of data points stored per (data_type, key).
const MAX_DATA_POINTS: u32 = 100;

Expand Down Expand Up @@ -339,11 +341,10 @@ impl OracleVerifier {
.get(&StorageKey::OracleList(data_type.clone()))
.unwrap_or_else(|| Vec::new(&env));

// Count how many oracles are already registered for this data_type
// If close to max, require admin to review (this is a simplified check).
// In a more sophisticated system, you'd track controller pubkeys.
// Count how many oracles are already registered for this data_type / key (issue #727).
// Limit to MAX_ORACLES_PER_KEY to prevent Sybil attacks and oracle domination.
let oracle_count = list.len();
if oracle_count >= MAX_ORACLES {
if oracle_count >= MAX_ORACLES_PER_KEY {
panic_with_error!(&env, Error::TooManyOracles);
}

Expand Down
9 changes: 8 additions & 1 deletion contracts/risk-pool/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -662,10 +662,17 @@ impl RiskPool {
if amount == 0 { panic_with_error!(&env, Error::ZeroAmount); }
if amount > available_liquidity { panic_with_error!(&env, Error::Undercollateralized); }

// Issue #725: Update cumulative premium per share and settle yield during withdrawal
let pending_yield = Self::settle_yield(&env, &mut position);
let acc_per_share: i128 = env
.storage()
.instance()
.get(&StorageKey::AccumulatedPerShare)
.unwrap_or(0);
position.deposited = position.deposited.saturating_sub(amount);
position.shares -= shares;
position.yield_debt = (env.storage().instance().get(&StorageKey::AccumulatedPerShare).unwrap_or(0) * position.shares) / 1_000_000_000_000;
position.yield_debt = (acc_per_share * position.shares) / 1_000_000_000_000;
env.storage().instance().set(&StorageKey::AccumulatedPerShare, &acc_per_share);
env.storage().persistent().set(&lp_key, &position);
Self::extend_to_max(&env, &lp_key);
env.storage().instance().set(&StorageKey::TotalDeposited, &total_deposited.checked_sub(amount).unwrap_or_else(|| panic_with_error!(&env, Error::Overflow)));
Expand Down
Loading