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
6 changes: 4 additions & 2 deletions crates/contracts/core/src/conditional_escrow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ impl SkillSyncContract {

let fee_bps = Self::get_platform_fee(env.clone());
let now = env.ledger().timestamp();
let dispute_window = Self::get_dispute_window(env.clone());
let dispute_window_ledgers = Self::get_dispute_window(env.clone());
let current_ledger = env.ledger().sequence();
let dispute_deadline = (current_ledger + dispute_window_ledgers) as u64;

let platform_fee = amount
.checked_mul(fee_bps as i128)
Expand Down Expand Up @@ -106,7 +108,7 @@ impl SkillSyncContract {
status: SessionStatus::Locked,
created_at: now,
updated_at: now,
dispute_deadline: now + dispute_window,
dispute_deadline,
expires_at: now + crate::ESCROW_DURATION_SECONDS,
deadline: env.ledger().sequence() as u64,
payer_approved: false,
Expand Down
15 changes: 15 additions & 0 deletions crates/contracts/core/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,18 @@ pub struct ReferrerFeePaid {
/// Ledger timestamp at the moment of claim.
pub timestamp: u64,
}

/// Emitted when the admin updates the dispute resolution window.
/// Shows the old and new window values in ledgers.
#[contracttype]
#[derive(Clone, Debug)]
pub struct DisputeWindowUpdated {
/// Previous dispute window value in ledgers.
pub old_window_ledgers: u32,
/// New dispute window value in ledgers.
pub new_window_ledgers: u32,
/// Address of the admin who performed the update.
pub updated_by: Address,
/// Ledger timestamp at the moment of the update.
pub timestamp: u64,
}
6 changes: 4 additions & 2 deletions crates/contracts/core/src/insurance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,9 @@ impl SkillSyncContract {

let fee_bps = Self::get_platform_fee(env.clone());
let now = env.ledger().timestamp();
let dispute_window = Self::get_dispute_window(env.clone());
let dispute_window_ledgers = Self::get_dispute_window(env.clone());
let current_ledger = env.ledger().sequence();
let dispute_deadline = (current_ledger + dispute_window_ledgers) as u64;

let platform_fee = amount
.checked_mul(fee_bps as i128)
Expand Down Expand Up @@ -189,7 +191,7 @@ impl SkillSyncContract {
status: SessionStatus::Locked,
created_at: now,
updated_at: now,
dispute_deadline: now + dispute_window,
dispute_deadline,
expires_at: now + crate::ESCROW_DURATION_SECONDS,
deadline: env.ledger().sequence() as u64,
payer_approved: false,
Expand Down
127 changes: 70 additions & 57 deletions crates/contracts/core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#![no_std]
#![no_std]

pub mod conditional_escrow;
pub mod dao_dispute;
Expand All @@ -8,16 +7,13 @@ pub mod storage_archive;

pub mod error_codes;

pub use error_codes::{
AuthError, FinancialError, InitError, SessionError, TimeoutDisputeError, UpgradeError,
};
pub use error_codes::{AuthError, FinancialError, InitError, ReentrancyError, SessionError, TimeoutDisputeError, UpgradeError};
pub mod errors;
// pub mod errors; // Not used - using Error enum in lib.rs instead
pub mod events;
pub mod oracle;

pub use events::{
ContractUpgraded, DisputeResolved, OffchainApprovalExecuted, ReferrerFeePaid,
ContractUpgraded, DisputeResolved, DisputeWindowUpdated, OffchainApprovalExecuted, ReferrerFeePaid,
SessionApprovedEvent, TreasuryUpdated,
};

Expand All @@ -29,6 +25,9 @@ use soroban_sdk::{
pub const DISPUTE_WINDOW_MIN_SECONDS: u64 = 60;
pub const DISPUTE_WINDOW_MAX_SECONDS: u64 = 30 * 24 * 60 * 60;
pub const DEFAULT_DISPUTE_WINDOW_SECONDS: u64 = 24 * 60 * 60;
pub const DEFAULT_DISPUTE_WINDOW_LEDGERS: u32 = 1000; // Default 1000 ledgers
pub const DISPUTE_WINDOW_MIN_LEDGERS: u32 = 10; // Minimum 10 ledgers
pub const DISPUTE_WINDOW_MAX_LEDGERS: u32 = 100_000; // Maximum 100,000 ledgers
pub const PLATFORM_FEE_MAX_BPS: u32 = 1000; // 10%
pub const MAX_FEE_BPS: u32 = 10_000; // 100% - absolute maximum
pub const ESCROW_DURATION_SECONDS: u64 = 7 * 24 * 60 * 60; // Default 7 days
Expand Down Expand Up @@ -345,43 +344,13 @@ pub enum Error {
ExtensionNotProposed = 37, // No extension has been proposed
CannotAcceptOwnExtension = 38, // The proposer cannot accept their own extension
InvalidSignature = 39, // Invalid cryptographic signature
Reentrancy = 40, // Reentrant call detected
Reentrancy = 40, // Reentrant call detected (Issue #209)
ContractPaused = 41, // Contract is paused
SessionNotExpired = 16,
RefundFailed = 17,
NothingToSweep = 18,
UpgradeNotProposed = 19,
UpgradeNotReady = 20,
UpgradeDeadlinePassed = 21,
InvalidTimelock = 22,
InvalidResolutionAmount = 23,
SessionNotDisputed = 24,
ResolutionFeeError = 25,
FeeCalculationOverflow = 26,
NonceAlreadyUsed = 27,
InvalidRating = 28,
ReputationOverflow = 29,
InvalidDisputeState = 30,
InvalidAddress = 31,
InvalidSessionId = 32,
InvalidNote = 33,
AmountTooLarge = 34,
InvalidExtensionDuration = 35,
ExtensionAlreadyProposed = 36,
ExtensionNotProposed = 37,
CannotAcceptOwnExtension = 38,
InvalidSignature = 39,
// Issue #209: Reentrancy detected (code 700 per spec, mapped here as 40)
Reentrancy = 40,
ContractPaused = 41,
// Issue #208: Session expired
SessionExpired = 42,
// Issue #210: Milestone errors
InvalidMilestones = 43,
SessionExpired = 42, // Session expired (Issue #208)
InvalidMilestones = 43, // Issue #210: Milestone errors
MilestoneAlreadyReleased = 44,
MilestoneIndexOutOfBounds = 45,
// Issue #211: Rating errors
AlreadyRated = 46,
AlreadyRated = 46, // Issue #211: Rating errors
SessionNotApproved = 47,
}

Expand All @@ -392,14 +361,14 @@ impl SkillSyncContract {
admin: Address,
platform_fee_bps: u32,
treasury_address: Address,
dispute_window_secs: u64,
dispute_window_ledgers: u32,
) -> Result<(), Error> {
if env.storage().instance().has(&DataKey::Admin) {
return Err(Error::AlreadyInitialized);
}

validate_platform_fee_bps(platform_fee_bps)?;
validate_dispute_window(dispute_window_secs)?;
validate_dispute_window_ledgers(dispute_window_ledgers)?;

env.storage().instance().set(&DataKey::Admin, &admin);
env.storage()
Expand All @@ -410,7 +379,7 @@ impl SkillSyncContract {
.set(&DataKey::Treasury, &treasury_address);
env.storage()
.instance()
.set(&DataKey::DisputeWindow, &dispute_window_secs);
.set(&DataKey::DisputeWindow, &dispute_window_ledgers);
env.storage().instance().set(&DataKey::Version, &VERSION);

env.events().publish(
Expand All @@ -419,7 +388,7 @@ impl SkillSyncContract {
admin,
platform_fee_bps,
treasury_address,
dispute_window_secs,
dispute_window_ledgers,
VERSION,
),
);
Expand Down Expand Up @@ -613,8 +582,9 @@ impl SkillSyncContract {
validate_different_addresses(&payer, &payee)?;

let now = env.ledger().timestamp();
let dispute_window = Self::get_dispute_window(env.clone());
let dispute_deadline = now + dispute_window;
let dispute_window_ledgers = Self::get_dispute_window(env.clone());
let current_ledger = env.ledger().sequence();
let dispute_deadline = (current_ledger + dispute_window_ledgers) as u64;
let expires_at = now + ESCROW_DURATION_SECONDS;
let fee_bps = Self::get_platform_fee(env.clone());

Expand Down Expand Up @@ -645,7 +615,6 @@ impl SkillSyncContract {
updated_at: now,
dispute_deadline,
expires_at,
deadline: env.ledger().sequence() as u64,
deadline: (env.ledger().sequence() as u64) + (Self::get_max_session_duration(env.clone()) as u64),
payer_approved: false,
payee_approved: false,
Expand Down Expand Up @@ -723,9 +692,10 @@ impl SkillSyncContract {
}

let now = env.ledger().timestamp();
let dispute_window = Self::get_dispute_window(env.clone());
let current_ledger = env.ledger().sequence();

if now <= session.updated_at + dispute_window {
// Check if dispute window has elapsed (using ledger-based deadline)
if current_ledger <= session.dispute_deadline as u32 {
return Err(Error::DisputeWindowNotElapsed);
}

Expand Down Expand Up @@ -1200,11 +1170,49 @@ impl SkillSyncContract {
id
}

pub fn get_dispute_window(env: Env) -> u64 {
pub fn get_dispute_window(env: Env) -> u32 {
env.storage()
.instance()
.get(&DataKey::DisputeWindow)
.unwrap_or(DEFAULT_DISPUTE_WINDOW_SECONDS)
.unwrap_or(DEFAULT_DISPUTE_WINDOW_LEDGERS)
}

/// Set the dispute resolution window in ledgers. Only callable by admin.
/// Emits DisputeWindowUpdated event.
pub fn set_dispute_window(env: Env, window_ledgers: u32) -> Result<(), Error> {
let admin = read_admin(&env)?;
admin.require_auth();
Self::require_not_paused(&env)?;

// Validate the window is within acceptable range
if window_ledgers < DISPUTE_WINDOW_MIN_LEDGERS || window_ledgers > DISPUTE_WINDOW_MAX_LEDGERS {
return Err(Error::InvalidDisputeWindow);
}

// Get the old value for the event
let old_window_ledgers: u32 = env
.storage()
.instance()
.get(&DataKey::DisputeWindow)
.unwrap_or(DEFAULT_DISPUTE_WINDOW_LEDGERS);

// Store the new value
env.storage()
.instance()
.set(&DataKey::DisputeWindow, &window_ledgers);

// Emit the event
env.events().publish(
(Symbol::new(&env, "DisputeWindowUpdated"),),
DisputeWindowUpdated {
old_window_ledgers,
new_window_ledgers: window_ledgers,
updated_by: admin,
timestamp: env.ledger().timestamp(),
},
);

Ok(())
}

pub fn get_treasury(env: Env) -> Address {
Expand Down Expand Up @@ -1375,7 +1383,9 @@ impl SkillSyncContract {
payer.require_auth();

let now = env.ledger().timestamp();
let dispute_window = Self::get_dispute_window(env.clone());
let dispute_window_ledgers = Self::get_dispute_window(env.clone());
let current_ledger = env.ledger().sequence();
let dispute_deadline = (current_ledger + dispute_window_ledgers) as u64;
let fee_bps = Self::get_platform_fee(env.clone());
let max_duration = Self::get_max_session_duration(env.clone());

Expand Down Expand Up @@ -1414,7 +1424,7 @@ impl SkillSyncContract {
status: SessionStatus::Locked,
created_at: now,
updated_at: now,
dispute_deadline: now + dispute_window,
dispute_deadline,
expires_at: now + ESCROW_DURATION_SECONDS,
deadline: (env.ledger().sequence() as u64) + (max_duration as u64),
payer_approved: false,
Expand Down Expand Up @@ -1636,9 +1646,6 @@ fn acquire_lock(env: &Env) -> Result<(), Error> {
.unwrap_or(false)
{
return Err(Error::Reentrancy);
if env.storage().instance().get(&DataKey::ReentrancyLock).unwrap_or(false) {
// Issue #209: ReentrancyDetected error code 700
panic_with_error!(env, ReentrancyError::ReentrancyDetected);
}
env.storage()
.instance()
Expand Down Expand Up @@ -1669,6 +1676,13 @@ fn validate_dispute_window(seconds: u64) -> Result<(), Error> {
Ok(())
}

fn validate_dispute_window_ledgers(ledgers: u32) -> Result<(), Error> {
if ledgers < DISPUTE_WINDOW_MIN_LEDGERS || ledgers > DISPUTE_WINDOW_MAX_LEDGERS {
return Err(Error::InvalidDisputeWindow);
}
Ok(())
}

fn validate_platform_fee_bps(bps: u32) -> Result<(), Error> {
if bps > PLATFORM_FEE_MAX_BPS {
return Err(Error::InvalidFeeBps);
Expand Down Expand Up @@ -1711,4 +1725,3 @@ mod test;

#[cfg(test)]
mod test_storage_persistence;

Loading