From 735fd22ff6a3c82a9a5ac85e957154d1becb3edd Mon Sep 17 00:00:00 2001 From: vicistar-star Date: Mon, 13 Jul 2026 15:02:55 +0100 Subject: [PATCH] feat: add transfer_admin to task-registry and reward-engine Mirror the transfer_admin pattern from eco-token to RegistryContract and RewardEngine. Requires current admin auth, rejects same-address transfers, and updates the stored admin address. Tests: successful transfer, unauthorized attempt, same-address rejection. --- contracts/reward-engine/src/verification.rs | 44 +++++++++++++++++ contracts/task-registry/src/registry.rs | 54 +++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/contracts/reward-engine/src/verification.rs b/contracts/reward-engine/src/verification.rs index bde7ee9..1c15eba 100644 --- a/contracts/reward-engine/src/verification.rs +++ b/contracts/reward-engine/src/verification.rs @@ -347,6 +347,18 @@ impl RewardEngine { } pending } + + pub fn transfer_admin(e: Env, current_admin: Address, new_admin: Address) { + current_admin.require_auth(); + let stored_admin = storage::read_admin(&e); + if current_admin != stored_admin { + panic!("engine: unauthorized"); + } + if new_admin == current_admin { + panic!("engine: new admin must be different"); + } + storage::write_admin(&e, &new_admin); + } } #[cfg(test)] @@ -736,4 +748,36 @@ mod test { client.dispute_proof(&admin, &user3, &task_id); assert_eq!(client.get_pending_verifications().len(), 0); } + + #[test] + fn test_transfer_admin() { + let (e, admin, _oracle, _user, _task_id, client) = setup(); + e.mock_all_auths_allowing_non_root_auth(); + + let new_admin = Address::generate(&e); + client.transfer_admin(&admin, &new_admin); + + let new_oracle = Address::generate(&e); + client.set_oracle(&new_admin, &new_oracle); + } + + #[test] + #[should_panic(expected = "engine: unauthorized")] + fn test_transfer_admin_unauthorized() { + let (e, _admin, _oracle, _user, _task_id, client) = setup(); + e.mock_all_auths_allowing_non_root_auth(); + + let attacker = Address::generate(&e); + let new_admin = Address::generate(&e); + client.transfer_admin(&attacker, &new_admin); + } + + #[test] + #[should_panic(expected = "engine: new admin must be different")] + fn test_transfer_admin_same_address() { + let (e, admin, _oracle, _user, _task_id, client) = setup(); + e.mock_all_auths_allowing_non_root_auth(); + + client.transfer_admin(&admin, &admin); + } } diff --git a/contracts/task-registry/src/registry.rs b/contracts/task-registry/src/registry.rs index 2507723..4f5a157 100644 --- a/contracts/task-registry/src/registry.rs +++ b/contracts/task-registry/src/registry.rs @@ -211,6 +211,18 @@ impl RegistryContract { pub fn get_tasks_by_creator(e: Env, creator: Address) -> soroban_sdk::Vec { storage::read_creator_tasks(&e, &creator) } + + pub fn transfer_admin(e: Env, current_admin: Address, new_admin: Address) { + current_admin.require_auth(); + let stored_admin = storage::read_admin(&e); + if current_admin != stored_admin { + panic!("registry: unauthorized"); + } + if new_admin == current_admin { + panic!("registry: new admin must be different"); + } + storage::write_admin(&e, &new_admin); + } } #[cfg(test)] @@ -638,4 +650,46 @@ mod test { assert_eq!(ids.len(), 1); assert_eq!(ids.get(0).unwrap(), task_id); } + + #[test] + fn test_transfer_admin() { + let (e, admin, client) = setup(); + e.mock_all_auths(); + + let new_admin = Address::generate(&e); + client.transfer_admin(&admin, &new_admin); + + let loc_hash: BytesN<32> = BytesN::random(&e); + let task_id = client.create_task( + &new_admin, + &String::from_str(&e, "tree-planting"), + &loc_hash, + &1000, + &1, + &(e.ledger().timestamp() + 1000), + ); + + let task = client.get_task(&task_id); + assert_eq!(task.creator, new_admin); + } + + #[test] + #[should_panic(expected = "registry: unauthorized")] + fn test_transfer_admin_unauthorized() { + let (e, _admin, client) = setup(); + e.mock_all_auths(); + + let attacker = Address::generate(&e); + let new_admin = Address::generate(&e); + client.transfer_admin(&attacker, &new_admin); + } + + #[test] + #[should_panic(expected = "registry: new admin must be different")] + fn test_transfer_admin_same_address() { + let (e, admin, client) = setup(); + e.mock_all_auths(); + + client.transfer_admin(&admin, &admin); + } }