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
17 changes: 17 additions & 0 deletions contracts/payment-vault-contract/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,23 @@ pub fn reclaim_stale_session(env: &Env, user: &Address, booking_id: u64) -> Resu
Ok(())
}

pub fn transfer_admin(env: &Env, new_admin: &Address) -> Result<(), VaultError> {
let current_admin = storage::get_admin(env).ok_or(VaultError::NotInitialized)?;
current_admin.require_auth();
storage::set_admin(env, new_admin);
events::admin_transferred(env, &current_admin, new_admin);
Ok(())
}

pub fn set_oracle(env: &Env, new_oracle: &Address) -> Result<(), VaultError> {
let admin = storage::get_admin(env).ok_or(VaultError::NotInitialized)?;
admin.require_auth();
let old_oracle = storage::get_oracle(env);
storage::set_oracle(env, new_oracle);
events::oracle_updated(env, &old_oracle, new_oracle);
Ok(())
}

pub fn reject_session(env: &Env, expert: &Address, booking_id: u64) -> Result<(), VaultError> {
if storage::is_paused(env) {
return Err(VaultError::ContractPaused);
Expand Down
14 changes: 14 additions & 0 deletions contracts/payment-vault-contract/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,17 @@ pub fn expert_rate_updated(env: &Env, expert: &Address, rate: i128) {
let topics = (symbol_short!("rate_upd"), expert.clone());
env.events().publish(topics, rate);
}

/// Emitted when admin is transferred to a new address
pub fn admin_transferred(env: &Env, old_admin: &Address, new_admin: &Address) {
let topics = (symbol_short!("adm_xfer"),);
env.events()
.publish(topics, (old_admin.clone(), new_admin.clone()));
}

/// Emitted when the oracle address is updated
pub fn oracle_updated(env: &Env, old_oracle: &Address, new_oracle: &Address) {
let topics = (symbol_short!("orc_upd"),);
env.events()
.publish(topics, (old_oracle.clone(), new_oracle.clone()));
}
12 changes: 12 additions & 0 deletions contracts/payment-vault-contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ impl PaymentVaultContract {
contract::unpause(&env)
}

/// Transfer admin rights to a new address (Admin-only)
/// Old admin instantly loses all privileges
pub fn transfer_admin(env: Env, new_admin: Address) -> Result<(), VaultError> {
contract::transfer_admin(&env, &new_admin)
}

/// Update the oracle address (Admin-only)
/// Old oracle instantly loses authorization to finalize sessions
pub fn set_oracle(env: Env, new_oracle: Address) -> Result<(), VaultError> {
contract::set_oracle(&env, &new_oracle)
}

/// Set an expert's own rate per second
pub fn set_my_rate(env: Env, expert: Address, rate_per_second: i128) -> Result<(), VaultError> {
contract::set_my_rate(&env, &expert, rate_per_second)
Expand Down
133 changes: 133 additions & 0 deletions contracts/payment-vault-contract/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,139 @@ fn test_reject_nonexistent_booking() {
assert!(result.is_err());
}

// ==================== Key Rotation Tests ====================

#[test]
fn test_transfer_admin_success() {
let env = Env::default();
env.mock_all_auths();

let admin_a = Address::generate(&env);
let admin_b = Address::generate(&env);
let token = Address::generate(&env);
let oracle = Address::generate(&env);

let client = create_client(&env);
client.init(&admin_a, &token, &oracle);

// Admin A transfers to Admin B
let result = client.try_transfer_admin(&admin_b);
assert!(result.is_ok());
}

#[test]
fn test_new_admin_can_pause_after_transfer() {
let env = Env::default();
env.mock_all_auths();

let admin_a = Address::generate(&env);
let admin_b = Address::generate(&env);
let token = Address::generate(&env);
let oracle = Address::generate(&env);

let client = create_client(&env);
client.init(&admin_a, &token, &oracle);
client.transfer_admin(&admin_b);

// New admin B can pause and unpause
assert!(client.try_pause().is_ok());
assert!(client.try_unpause().is_ok());
}

#[test]
fn test_old_admin_loses_privileges_after_transfer() {
let env = Env::default();
env.mock_all_auths();

let admin_a = Address::generate(&env);
let admin_b = Address::generate(&env);
let token = Address::generate(&env);
let oracle = Address::generate(&env);

let client = create_client(&env);
client.init(&admin_a, &token, &oracle);
client.transfer_admin(&admin_b);

// Remove all mocked auths β€” now only explicit auth will pass
env.set_auths(&[]);

// Without any valid auth for admin_b, pause should fail
// (admin_b is now the required auth, but no auth is mocked)
let result = client.try_pause();
assert!(result.is_err());
}

#[test]
fn test_set_oracle_success() {
let env = Env::default();
env.mock_all_auths();

let admin = Address::generate(&env);
let token = Address::generate(&env);
let oracle_old = Address::generate(&env);
let oracle_new = Address::generate(&env);

let token_admin = Address::generate(&env);
let token_contract = create_token_contract(&env, &token_admin);
let user = Address::generate(&env);
let expert = Address::generate(&env);
token_contract.mint(&user, &10_000);

let client = create_client(&env);
client.init(&admin, &token_contract.address, &oracle_old);

// Book a session
client.set_my_rate(&expert, &10_i128);
let booking_id = client.book_session(&user, &expert, &100);

// Rotate oracle to new address
let result = client.try_set_oracle(&oracle_new);
assert!(result.is_ok());

// New oracle can finalize
let result = client.try_finalize_session(&booking_id, &50);
assert!(result.is_ok());
}

#[test]
fn test_non_admin_cannot_transfer_admin() {
let env = Env::default();
env.mock_all_auths();

let admin = Address::generate(&env);
let attacker = Address::generate(&env);
let token = Address::generate(&env);
let oracle = Address::generate(&env);

let client = create_client(&env);
client.init(&admin, &token, &oracle);

// Clear auths so attacker has no authorization
env.set_auths(&[]);

let result = client.try_transfer_admin(&attacker);
assert!(result.is_err());
}

#[test]
fn test_non_admin_cannot_set_oracle() {
let env = Env::default();
env.mock_all_auths();

let admin = Address::generate(&env);
let attacker = Address::generate(&env);
let token = Address::generate(&env);
let oracle = Address::generate(&env);

let client = create_client(&env);
client.init(&admin, &token, &oracle);

env.set_auths(&[]);

let result = client.try_set_oracle(&attacker);
assert!(result.is_err());
}

// ==================== Expert Rate Tests ====================

#[test]
Expand Down
Loading