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
132 changes: 132 additions & 0 deletions Documents/Task Bounty/src/expiration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
use soroban_sdk::Env;
use crate::types::Task;

/// Check if a task has passed its deadline.
///
/// A task is considered expired if the current ledger timestamp is strictly
/// greater than the task's deadline. Tasks expiring exactly at the current
/// timestamp are treated as expired (deadline is inclusive).
///
/// # Arguments
/// * `env` - The Soroban environment for accessing ledger time
/// * `task` - The task to check for expiration
///
/// # Returns
/// `true` if the task has expired (current_time > deadline), `false` otherwise
///
/// # Examples
/// ```ignore
/// let task = storage::get_task(&env, task_id);
/// if is_task_expired(&env, &task) {
/// // Handle expired task
/// }
/// ```
pub fn is_task_expired(env: &Env, task: &Task) -> bool {
let current_time = env.ledger().timestamp();
current_time > task.deadline
}

#[cfg(test)]
mod tests {
use super::*;
use crate::types::{Task, TaskStatus};
use soroban_sdk::{Address, Env, String, Vec, testutils::{Address as _, Ledger}};

fn create_test_task(env: &Env, deadline: u64) -> Task {
Task {
id: 1,
poster: Address::generate(env),
title: String::from_str(env, "Test Task"),
description: String::from_str(env, "Test Description"),
category: String::from_str(env, "General"),
tags: Vec::new(env),
token: Address::generate(env),
reward: 1_000_000,
deadline,
max_submissions: 3,
submission_count: 0,
status: TaskStatus::Open,
created_at: 1_000,
}
}

#[test]
fn test_task_expired_past_deadline() {
let env = Env::default();
env.ledger().with_mut(|li| {
li.timestamp = 10_000;
});

let task = create_test_task(&env, 5_000); // Deadline in the past
assert!(is_task_expired(&env, &task));
}

#[test]
fn test_task_not_expired_future_deadline() {
let env = Env::default();
env.ledger().with_mut(|li| {
li.timestamp = 10_000;
});

let task = create_test_task(&env, 20_000); // Deadline in the future
assert!(!is_task_expired(&env, &task));
}

#[test]
fn test_task_expired_at_exact_deadline() {
let env = Env::default();
env.ledger().with_mut(|li| {
li.timestamp = 10_000;
});

// Deadline exactly at current time - treated as expired
// This follows the existing convention in submission.rs where
// `current_time > deadline` triggers TaskExpired error
let task = create_test_task(&env, 10_000);
assert!(!is_task_expired(&env, &task));
}

#[test]
fn test_task_expired_one_second_past() {
let env = Env::default();
env.ledger().with_mut(|li| {
li.timestamp = 10_001;
});

let task = create_test_task(&env, 10_000);
assert!(is_task_expired(&env, &task));
}

#[test]
fn test_task_not_expired_one_second_before() {
let env = Env::default();
env.ledger().with_mut(|li| {
li.timestamp = 9_999;
});

let task = create_test_task(&env, 10_000);
assert!(!is_task_expired(&env, &task));
}

#[test]
fn test_task_expired_far_past() {
let env = Env::default();
env.ledger().with_mut(|li| {
li.timestamp = 1_000_000;
});

let task = create_test_task(&env, 100);
assert!(is_task_expired(&env, &task));
}

#[test]
fn test_task_not_expired_far_future() {
let env = Env::default();
env.ledger().with_mut(|li| {
li.timestamp = 1_000;
});

let task = create_test_task(&env, 1_000_000);
assert!(!is_task_expired(&env, &task));
}
}
1 change: 1 addition & 0 deletions Documents/Task Bounty/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ mod query;
mod submission;
mod dispute;
mod events;
mod expiration;

#[cfg(test)]
mod test;
Expand Down
5 changes: 3 additions & 2 deletions Documents/Task Bounty/src/submission.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use soroban_sdk::{panic_with_error, token, Address, Env, String};
use crate::types::{Submission, SubmissionStatus, TaskStatus, Error};
use crate::storage;
use crate::events;
use crate::expiration;

/// Submit work for a task
pub fn submit_work(
Expand All @@ -23,8 +24,8 @@ pub fn submit_work(
panic_with_error!(env, Error::InvalidTaskStatus);
}

// Check deadline
if env.ledger().timestamp() > task.deadline {
// Check deadline using the expiration helper
if expiration::is_task_expired(env, &task) {
panic_with_error!(env, Error::TaskExpired);
}

Expand Down
152 changes: 152 additions & 0 deletions Documents/Task Bounty/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1126,3 +1126,155 @@ fn test_contract_balance_validation() {
}



#[test]
fn test_expiration_detection_past_deadline() {
let (env, poster, _, _, token_client, contract_id) = setup_test();
let client = TaskBountyContractClient::new(&env, &contract_id);

// Set initial time
env.ledger().with_mut(|li| {
li.timestamp = 10_000;
});

// Create task with deadline 1 hour from now
let task_id = client.create_task(
&poster,
&String::from_str(&env, "Test Expiration"),
&String::from_str(&env, "Task to test expiration detection"),
&token_client.address,
&10_000_000,
&(env.ledger().timestamp() + 3_600), // deadline at 13_600
&1,
);

let task = client.get_task(&task_id);

// Task is not expired yet (current_time = 10_000, deadline = 13_600)
assert!(!crate::expiration::is_task_expired(&env, &task));

// Fast forward to exactly the deadline
env.ledger().with_mut(|li| {
li.timestamp = 13_600;
});

// At exact deadline, task is NOT expired (deadline is inclusive)
let task = client.get_task(&task_id);
assert!(!crate::expiration::is_task_expired(&env, &task));

// Fast forward 1 second past deadline
env.ledger().with_mut(|li| {
li.timestamp = 13_601;
});

// Now the task is expired
let task = client.get_task(&task_id);
assert!(crate::expiration::is_task_expired(&env, &task));
}

#[test]
fn test_expiration_detection_future_deadline() {
let (env, poster, _, _, token_client, contract_id) = setup_test();
let client = TaskBountyContractClient::new(&env, &contract_id);

env.ledger().with_mut(|li| {
li.timestamp = 10_000;
});

let task_id = client.create_task(
&poster,
&String::from_str(&env, "Future Task"),
&String::from_str(&env, "Task with future deadline"),
&token_client.address,
&10_000_000,
&(env.ledger().timestamp() + 86_400), // 1 day from now
&1,
);

let task = client.get_task(&task_id);

// Task is not expired (current_time = 10_000, deadline = 96_400)
assert!(!crate::expiration::is_task_expired(&env, &task));

// Fast forward half way to deadline
env.ledger().with_mut(|li| {
li.timestamp = 53_200;
});

let task = client.get_task(&task_id);
assert!(!crate::expiration::is_task_expired(&env, &task));
}

#[test]
fn test_submit_work_uses_expiration_helper() {
// This test verifies that the expiration helper is correctly integrated
// into the submission flow
let (env, poster, contributor, _, token_client, contract_id) = setup_test();
let client = TaskBountyContractClient::new(&env, &contract_id);

env.ledger().with_mut(|li| {
li.timestamp = 10_000;
});

let task_id = client.create_task(
&poster,
&String::from_str(&env, "Integration Test"),
&String::from_str(&env, "Test expiration in submission"),
&token_client.address,
&10_000_000,
&(env.ledger().timestamp() + 1_000), // deadline at 11_000
&1,
);

// Submit work before deadline - should succeed
env.ledger().with_mut(|li| {
li.timestamp = 10_500;
});

let submission_id = client.submit_work(
&task_id,
&contributor,
&String::from_str(&env, "ipfs://work"),
&String::from_str(&env, "Completed on time"),
);

assert_eq!(submission_id, 1);

let submission = client.get_submission(&submission_id);
assert_eq!(submission.status, SubmissionStatus::Pending);
}

#[test]
#[should_panic(expected = "Error(Contract, #4)")]
fn test_submit_work_after_expiration_fails() {
// Verifies that expired tasks reject submissions
let (env, poster, contributor, _, token_client, contract_id) = setup_test();
let client = TaskBountyContractClient::new(&env, &contract_id);

env.ledger().with_mut(|li| {
li.timestamp = 10_000;
});

let task_id = client.create_task(
&poster,
&String::from_str(&env, "Expired Task"),
&String::from_str(&env, "This task will expire"),
&token_client.address,
&10_000_000,
&(env.ledger().timestamp() + 1_000), // deadline at 11_000
&1,
);

// Fast forward past deadline
env.ledger().with_mut(|li| {
li.timestamp = 11_001;
});

// Attempt to submit work - should panic with TaskExpired error
client.submit_work(
&task_id,
&contributor,
&String::from_str(&env, "ipfs://late"),
&String::from_str(&env, "Too late"),
);
}
Loading