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
7 changes: 7 additions & 0 deletions src/contracts/proxy_entry/entry_tasks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,11 @@ impl VeroContract {
pub fn get_archived_task(env: Env, task_id: u64) -> Option<crate::types::Task> {
storage::get_archived_task(&env, task_id)
}

/// Returns the current monotonic task counter.
///
/// Tasks must be registered with IDs incrementing strictly by 1.
pub fn get_task_counter(env: Env) -> u64 {
task::get_task_counter(&env)
}
}
3 changes: 3 additions & 0 deletions src/contracts/storage_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ pub enum DataKey {
/// Vector/list of all task IDs.
/// Singleton key holding chronological vector of all created task IDs.
AllTasks,
/// Counter tracking the highest assigned task ID to ensure monotonic sequential IDs.
/// Singleton key storing the current task sequence counter (u64).
TaskCounter,
/// Vector/list of all recorded votes.
/// Singleton key holding global sequence of vote records.
AllVotes,
Expand Down
81 changes: 81 additions & 0 deletions src/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,16 @@ pub fn register_tasks(
return Err(ContractError::BatchTooLarge);
}

let mut current_counter = get_task_counter(env);
let mut seen_task_ids = Vec::new(env);
for task_id in task_ids.iter() {
validation::validate_task_id(task_id)?;
let expected_id = current_counter
.checked_add(1)
.ok_or(ContractError::InvalidConfig)?;
if task_id != expected_id {
return Err(ContractError::InvalidConfig);
}
if seen_task_ids.contains(task_id) {
return Err(ContractError::InvalidConfig);
}
Expand All @@ -41,6 +48,7 @@ pub fn register_tasks(
return Err(ContractError::InvalidConfig);
}
seen_task_ids.push_back(task_id);
current_counter = expected_id;
}

reentrancy::lock(env)?;
Expand Down Expand Up @@ -92,6 +100,9 @@ pub fn register_tasks(
}

env.storage().instance().set(&DataKey::AllTasks, &all_tasks);
env.storage()
.instance()
.set(&DataKey::TaskCounter, &current_counter);

reentrancy::unlock(env);
Ok(())
Expand Down Expand Up @@ -131,6 +142,14 @@ pub fn get_all_tasks(env: &Env) -> Vec<u64> {
.unwrap_or(Vec::new(env))
}

/// Retrieves the current monotonic task counter.
pub fn get_task_counter(env: &Env) -> u64 {
env.storage()
.instance()
.get(&DataKey::TaskCounter)
.unwrap_or(0)
}

/// Purge a terminal task (done or cancelled) from contract storage.
///
/// Removes:
Expand Down Expand Up @@ -220,3 +239,65 @@ pub fn purge_task(env: &Env, _admin: Address, task_id: u64) -> Result<(), Contra

Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
use soroban_sdk::testutils::Address as _;
use soroban_sdk::vec;

#[test]
fn test_register_tasks_batch_monotonic() {
let env = Env::default();
env.mock_all_auths();
let admin = Address::generate(&env);

let contract_id = env.register_contract(None, crate::VeroContract);
env.as_contract(&contract_id, || {
// Counter starts at 0
assert_eq!(get_task_counter(&env), 0);

// Register batch [1, 2, 3]
let batch1 = vec![&env, 1u64, 2u64, 3u64];
assert!(register_tasks(&env, admin.clone(), batch1, 1).is_ok());
assert_eq!(get_task_counter(&env), 3);
assert!(get_task(&env, 1).is_some());
assert!(get_task(&env, 2).is_some());
assert!(get_task(&env, 3).is_some());

// Register batch [4, 5]
let batch2 = vec![&env, 4u64, 5u64];
assert!(register_tasks(&env, admin.clone(), batch2, 1).is_ok());
assert_eq!(get_task_counter(&env), 5);

// Internal gap [6, 8] rejected
let bad_gap = vec![&env, 6u64, 8u64];
assert_eq!(
register_tasks(&env, admin.clone(), bad_gap, 1),
Err(ContractError::InvalidConfig)
);
assert_eq!(get_task_counter(&env), 5);

// Out-of-order [7, 6] rejected
let bad_order = vec![&env, 7u64, 6u64];
assert_eq!(
register_tasks(&env, admin.clone(), bad_order, 1),
Err(ContractError::InvalidConfig)
);
assert_eq!(get_task_counter(&env), 5);

// Duplicate in batch [6, 6] rejected
let bad_dup = vec![&env, 6u64, 6u64];
assert_eq!(
register_tasks(&env, admin.clone(), bad_dup, 1),
Err(ContractError::InvalidConfig)
);
assert_eq!(get_task_counter(&env), 5);

// Correct next batch [6, 7]
let batch3 = vec![&env, 6u64, 7u64];
assert!(register_tasks(&env, admin.clone(), batch3, 1).is_ok());
assert_eq!(get_task_counter(&env), 7);
});
}
}
10 changes: 5 additions & 5 deletions tests/archive_task_auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,18 @@ fn test_taskmanager_can_archive_task() {
client.add_guardian(&admin, &guardian);
client.set_reputation(&admin, &guardian, &100u64);
client.set_weight_threshold(&admin, &1u64);
client.register_task(&admin, &2u64, &1u32);
client.register_task(&admin, &1u64, &1u32);

let sac = soroban_sdk::token::StellarAssetClient::new(&env, &token);
sac.mint(&guardian, &101i128);
client.lock_tokens(&guardian, &101i128);

env.ledger().set_timestamp(1_000);
client.vote(&guardian, &2u64);
client.vote(&guardian, &1u64);
env.ledger()
.set_timestamp(1_000 + ARCHIVE_AFTER_SECONDS + 1);

client.archive_task(&admin, &2u64);
assert!(client.get_task(&2u64).is_none());
assert!(client.get_archived_task(&2u64).is_some());
client.archive_task(&admin, &1u64);
assert!(client.get_task(&1u64).is_none());
assert!(client.get_archived_task(&1u64).is_some());
}
6 changes: 3 additions & 3 deletions tests/consensus_delegation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@ fn test_vote_respects_dynamically_configured_weight_threshold() {
let guardian = add_voter(&env, &client, &admin, &token);
client.set_reputation(&admin, &guardian, &200);

client.register_task(&admin, &10, &1);
client.vote(&guardian, &10);
client.register_task(&admin, &1, &1);
client.vote(&guardian, &1);

let task = client.get_task(&10).unwrap();
let task = client.get_task(&1).unwrap();
assert!(task.is_done);
assert_eq!(task.total_weight_accrued, 200);
}
2 changes: 1 addition & 1 deletion tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ fn test_end_to_end_happy_path() {
client.lock_tokens(&guardian, &105i128);

// 4. Register a task
let task_id = 42u64;
let task_id = 1u64;
client.register_task(&admin, &task_id, &1u32);

let task = client.get_task(&task_id).unwrap();
Expand Down
Loading