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
157 changes: 155 additions & 2 deletions contracts/events/src/tests/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
#![cfg(test)]

use soroban_sdk::{
testutils::{BytesN as _, Ledger},
BytesN, String,
testutils::{Address as _, BytesN as _, Ledger},
Address, BytesN, String,
};

use super::common::setup;
Expand Down Expand Up @@ -170,3 +170,156 @@ fn migrate_marks_current_version_and_blocks_replay() {
.unwrap();
assert_eq!(err, Error::MigrationAlreadyApplied);
}

// AUTH REGRESSION GUARDS (#73)
//
// setup() mocks all auths for every address, so a call succeeding is not
// proof that require_admin() ran — it succeeds identically whether the
// check is present or was deleted. These tests replace the mock with an
// empty auth set so the call can only succeed if the contract explicitly
// requests and receives the admin's authorization. If require_admin() is
// ever removed from one of these entrypoints, the call stops requesting
// auth altogether and runs to completion instead of failing here,
// turning the test red.

#[test]
fn pause_reverts_without_admin_auth() {
let ctx = setup(250);
ctx.env.mock_auths(&[]);
let err = ctx.client.try_pause();
assert!(err.is_err(), "pause must require admin auth");
}

#[test]
fn unpause_reverts_without_admin_auth() {
let ctx = setup(250);
ctx.client.pause();
ctx.env.mock_auths(&[]);
let err = ctx.client.try_unpause();
assert!(err.is_err(), "unpause must require admin auth");
}

#[test]
fn set_admin_reverts_without_admin_auth() {
let ctx = setup(250);
let new_admin = Address::generate(&ctx.env);
ctx.env.mock_auths(&[]);
let err = ctx.client.try_set_admin(&new_admin);
assert!(err.is_err(), "set_admin must require admin auth");
}

#[test]
fn set_fee_bps_reverts_without_admin_auth() {
let ctx = setup(250);
ctx.env.mock_auths(&[]);
let err = ctx.client.try_set_fee_bps(&300);
assert!(err.is_err(), "set_fee_bps must require admin auth");
}

#[test]
fn set_fee_account_reverts_without_admin_auth() {
let ctx = setup(250);
let new_account = Address::generate(&ctx.env);
ctx.env.mock_auths(&[]);
let err = ctx.client.try_set_fee_account(&new_account);
assert!(err.is_err(), "set_fee_account must require admin auth");
}

#[test]
fn set_profile_contract_reverts_without_admin_auth() {
let ctx = setup(250);
let new_profile = Address::generate(&ctx.env);
ctx.env.mock_auths(&[]);
let err = ctx.client.try_set_profile_contract(&new_profile);
assert!(err.is_err(), "set_profile_contract must require admin auth");
}

#[test]
fn propose_upgrade_reverts_without_admin_auth() {
let ctx = setup(250);
let new_hash: BytesN<32> = BytesN::random(&ctx.env);
let new_version = String::from_str(&ctx.env, "0.3.0");
ctx.env.mock_auths(&[]);
let err = ctx.client.try_propose_upgrade(&new_hash, &new_version);
assert!(err.is_err(), "propose_upgrade must require admin auth");
}

#[test]
fn apply_upgrade_reverts_without_admin_auth() {
let ctx = setup(250);
let new_hash: BytesN<32> = BytesN::random(&ctx.env);
let new_version = String::from_str(&ctx.env, "0.3.0");
ctx.client.propose_upgrade(&new_hash, &new_version);

ctx.env.ledger().with_mut(|li| {
li.sequence_number += UPGRADE_TIMELOCK_LEDGERS;
});

ctx.env.mock_auths(&[]);
let err = ctx.client.try_apply_upgrade();
assert!(err.is_err(), "apply_upgrade must require admin auth");
}

#[test]
fn cancel_pending_upgrade_reverts_without_admin_auth() {
let ctx = setup(250);
let new_hash: BytesN<32> = BytesN::random(&ctx.env);
let new_version = String::from_str(&ctx.env, "0.3.0");
ctx.client.propose_upgrade(&new_hash, &new_version);

ctx.env.mock_auths(&[]);
let err = ctx.client.try_cancel_pending_upgrade();
assert!(
err.is_err(),
"cancel_pending_upgrade must require admin auth"
);
}

#[test]
fn migrate_reverts_without_admin_auth() {
let ctx = setup(250);
ctx.env.mock_auths(&[]);
let err = ctx.client.try_migrate();
assert!(err.is_err(), "migrate must require admin auth");
}

// ============================================================
// ACCEPT_ADMIN — target-auth guard
//
// accept_admin does not call require_admin(); it authorizes against the
// pending target address instead (pending.target.require_auth()). These
// two tests prove that guard independently of the require_admin() guards
// above: the empty-mock test shows *some* auth is demanded, and the
// auths() check shows it is demanded from the pending target
// specifically, not just any address mock_all_auths() happens to cover.
// ============================================================

#[test]
fn accept_admin_reverts_without_targets_auth() {
let ctx = setup(250);
let new_admin = Address::generate(&ctx.env);
ctx.client.set_admin(&new_admin);

ctx.env.mock_auths(&[]);
let err = ctx.client.try_accept_admin();
assert!(
err.is_err(),
"accept_admin must require the pending target's auth"
);
}

#[test]
fn accept_admin_demands_pending_targets_auth_specifically() {
let ctx = setup(250);
let new_admin = Address::generate(&ctx.env);
ctx.client.set_admin(&new_admin);

ctx.client.accept_admin();

let auths = ctx.env.auths();
let target_required = auths.iter().any(|(addr, _)| *addr == new_admin);
assert!(
target_required,
"accept_admin must demand the pending target's own auth"
);
}
183 changes: 183 additions & 0 deletions contracts/profile/src/tests/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,3 +250,186 @@ fn migrate_marks_version_and_blocks_replay_profile() {
.unwrap();
assert_eq!(err, Error::MigrationAlreadyApplied);
}

// ============================================================
// AUTH REGRESSION GUARDS (#73)
//
// Mirror of the events-contract guards in
// contracts/events/src/tests/admin.rs — see that file for the rationale.
// ============================================================

#[test]
fn pause_reverts_without_admin_auth() {
let ctx = setup();
ctx.env.mock_auths(&[]);
let err = ctx.client.try_pause();
assert!(err.is_err(), "pause must require admin auth");
}

#[test]
fn unpause_reverts_without_admin_auth() {
let ctx = setup();
ctx.client.pause();
ctx.env.mock_auths(&[]);
let err = ctx.client.try_unpause();
assert!(err.is_err(), "unpause must require admin auth");
}

#[test]
fn set_admin_reverts_without_admin_auth() {
let ctx = setup();
let new_admin = Address::generate(&ctx.env);
ctx.env.mock_auths(&[]);
let err = ctx.client.try_set_admin(&new_admin);
assert!(err.is_err(), "set_admin must require admin auth");
}

#[test]
fn set_events_contract_reverts_without_admin_auth() {
let ctx = setup();
let events = Address::generate(&ctx.env);
ctx.env.mock_auths(&[]);
let err = ctx.client.try_set_events_contract(&events);
assert!(err.is_err(), "set_events_contract must require admin auth");
}

#[test]
fn propose_events_contract_reverts_without_admin_auth() {
let ctx = setup();
let events_a = Address::generate(&ctx.env);
ctx.client.set_events_contract(&events_a);

let events_b = Address::generate(&ctx.env);
ctx.env.mock_auths(&[]);
let err = ctx.client.try_propose_events_contract(&events_b);
assert!(
err.is_err(),
"propose_events_contract must require admin auth"
);
}

#[test]
fn accept_events_contract_reverts_without_admin_auth() {
let ctx = setup();
let events_a = Address::generate(&ctx.env);
ctx.client.set_events_contract(&events_a);
let events_b = Address::generate(&ctx.env);
let start = ctx.env.ledger().sequence();
ctx.client.propose_events_contract(&events_b);
ctx.env.ledger().with_mut(|li| {
li.sequence_number = start + EVENTS_CONTRACT_TIMELOCK_LEDGERS + 1;
});

ctx.env.mock_auths(&[]);
let err = ctx.client.try_accept_events_contract();
assert!(
err.is_err(),
"accept_events_contract must require admin auth"
);
}

#[test]
fn cancel_pending_events_contract_reverts_without_admin_auth() {
let ctx = setup();
let events_a = Address::generate(&ctx.env);
ctx.client.set_events_contract(&events_a);
let events_b = Address::generate(&ctx.env);
ctx.client.propose_events_contract(&events_b);

ctx.env.mock_auths(&[]);
let err = ctx.client.try_cancel_pending_events_contract();
assert!(
err.is_err(),
"cancel_pending_events_contract must require admin auth"
);
}

#[test]
fn propose_upgrade_reverts_without_admin_auth() {
let ctx = setup();
let new_hash: BytesN<32> = BytesN::random(&ctx.env);
let new_version = String::from_str(&ctx.env, "0.3.0");
ctx.env.mock_auths(&[]);
let err = ctx.client.try_propose_upgrade(&new_hash, &new_version);
assert!(err.is_err(), "propose_upgrade must require admin auth");
}

#[test]
fn apply_upgrade_reverts_without_admin_auth() {
let ctx = setup();
let new_hash: BytesN<32> = BytesN::random(&ctx.env);
let new_version = String::from_str(&ctx.env, "0.3.0");
ctx.client.propose_upgrade(&new_hash, &new_version);

ctx.env.ledger().with_mut(|li| {
li.sequence_number += UPGRADE_TIMELOCK_LEDGERS;
});

ctx.env.mock_auths(&[]);
let err = ctx.client.try_apply_upgrade();
assert!(err.is_err(), "apply_upgrade must require admin auth");
}

#[test]
fn cancel_pending_upgrade_reverts_without_admin_auth() {
let ctx = setup();
let new_hash: BytesN<32> = BytesN::random(&ctx.env);
let new_version = String::from_str(&ctx.env, "0.3.0");
ctx.client.propose_upgrade(&new_hash, &new_version);

ctx.env.mock_auths(&[]);
let err = ctx.client.try_cancel_pending_upgrade();
assert!(
err.is_err(),
"cancel_pending_upgrade must require admin auth"
);
}

#[test]
fn migrate_reverts_without_admin_auth() {
let ctx = setup();
ctx.env.mock_auths(&[]);
let err = ctx.client.try_migrate();
assert!(err.is_err(), "migrate must require admin auth");
}

// ============================================================
// ACCEPT_ADMIN — target-auth guard
//
// accept_admin does not call require_admin(); it authorizes against the
// pending target address instead (pending.target.require_auth()). These
// two tests prove that guard independently of the require_admin() guards
// above: the empty-mock test shows *some* auth is demanded, and the
// auths() check shows it is demanded from the pending target
// specifically, not just any address mock_all_auths() happens to cover.
// ============================================================

#[test]
fn accept_admin_reverts_without_targets_auth() {
let ctx = setup();
let new_admin = Address::generate(&ctx.env);
ctx.client.set_admin(&new_admin);

ctx.env.mock_auths(&[]);
let err = ctx.client.try_accept_admin();
assert!(
err.is_err(),
"accept_admin must require the pending target's auth"
);
}

#[test]
fn accept_admin_demands_pending_targets_auth_specifically() {
let ctx = setup();
let new_admin = Address::generate(&ctx.env);
ctx.client.set_admin(&new_admin);

ctx.client.accept_admin();

let auths = ctx.env.auths();
let target_required = auths.iter().any(|(addr, _)| *addr == new_admin);
assert!(
target_required,
"accept_admin must demand the pending target's own auth"
);
}
16 changes: 16 additions & 0 deletions contracts/profile/src/tests/reputation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,3 +418,19 @@ fn admin_slash_rejects_non_admin_caller() {
.try_admin_slash_reputation(&user, &1, &admin_reason(&ctx), &op_id(&ctx));
assert!(res.is_err(), "non-admin admin_slash must be rejected");
}

#[test]
fn admin_slash_demands_admins_auth_specifically() {
// Complements admin_slash_rejects_non_admin_caller above (already in
// the codebase since #60): that test proves *some* auth is required;
// this one proves the auth demanded under a normal call is
// specifically the admin's, not just any address mock_all_auths()
// happens to approve.
let (ctx, user) = setup_with_user();
ctx.client
.admin_slash_reputation(&user, &1, &admin_reason(&ctx), &op_id(&ctx));

let auths = ctx.env.auths();
let admin_required = auths.iter().any(|(addr, _)| *addr == ctx.admin);
assert!(admin_required, "admin_slash must demand the admin's auth");
}
Loading