extern crate std;
use soroban_sdk::{
contract, symbol_short,
testutils::{Address as _, Ledger},
Address, Env, Symbol,
};
use stellar_event_assertion::EventAssertion;
use crate::access_control::{
accept_admin_transfer, add_to_role_enumeration, ensure_if_admin_or_admin_role, get_admin,
get_existing_roles, get_role_admin, get_role_member, get_role_member_count, grant_role,
grant_role_no_auth, has_role, remove_from_role_enumeration, remove_role_accounts_count_no_auth,
remove_role_admin_no_auth, renounce_admin, renounce_role, revoke_role, set_admin,
set_role_admin, set_role_admin_no_auth, transfer_admin_role,
};
#[contract]
struct MockContract;
const ADMIN_ROLE: Symbol = symbol_short!("admin");
const USER_ROLE: Symbol = symbol_short!("user");
const MANAGER_ROLE: Symbol = symbol_short!("manager");
#[test]
fn admin_functions_work() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let admin = Address::generate(&e);
let user = Address::generate(&e);
e.as_contract(&address, || {
set_admin(&e, &admin);
grant_role(&e, &user, &USER_ROLE, &admin);
assert!(has_role(&e, &user, &USER_ROLE).is_some());
let event_assert = EventAssertion::new(&e, address.clone());
event_assert.assert_event_count(1);
});
e.as_contract(&address, || {
revoke_role(&e, &user, &USER_ROLE, &admin);
assert!(has_role(&e, &user, &USER_ROLE).is_none());
let event_assert = EventAssertion::new(&e, address.clone());
event_assert.assert_event_count(1);
});
}
#[test]
fn role_management_works() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let admin = Address::generate(&e);
let user1 = Address::generate(&e);
let user2 = Address::generate(&e);
e.as_contract(&address, || {
set_admin(&e, &admin);
grant_role(&e, &user1, &USER_ROLE, &admin);
});
e.as_contract(&address, || {
grant_role(&e, &user2, &USER_ROLE, &admin);
assert_eq!(get_role_member_count(&e, &USER_ROLE), 2);
assert_eq!(get_role_member(&e, &USER_ROLE, 0), user1);
assert_eq!(get_role_member(&e, &USER_ROLE, 1), user2);
});
e.as_contract(&address, || {
revoke_role(&e, &user1, &USER_ROLE, &admin);
assert_eq!(get_role_member_count(&e, &USER_ROLE), 1);
assert_eq!(get_role_member(&e, &USER_ROLE, 0), user2);
});
}
#[test]
fn role_admin_management_works() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let admin = Address::generate(&e);
let manager = Address::generate(&e);
let user = Address::generate(&e);
e.as_contract(&address, || {
set_admin(&e, &admin);
set_role_admin(&e, &USER_ROLE, &MANAGER_ROLE);
});
e.as_contract(&address, || {
grant_role(&e, &manager, &MANAGER_ROLE, &admin);
grant_role(&e, &user, &USER_ROLE, &manager);
assert!(has_role(&e, &user, &USER_ROLE).is_some());
});
e.as_contract(&address, || {
revoke_role(&e, &user, &USER_ROLE, &manager);
assert!(has_role(&e, &user, &USER_ROLE).is_none());
});
}
#[test]
fn get_role_member_count_for_nonexistent_role_returns_zero() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let admin = Address::generate(&e);
let nonexistent_role = Symbol::new(&e, "nonexistent");
e.as_contract(&address, || {
set_admin(&e, &admin);
let count = get_role_member_count(&e, &nonexistent_role);
assert_eq!(count, 0);
});
}
#[test]
fn get_role_admin_returns_some_when_set() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let admin = Address::generate(&e);
e.as_contract(&address, || {
set_admin(&e, &admin);
set_role_admin(&e, &USER_ROLE, &ADMIN_ROLE);
let admin_role = get_role_admin(&e, &USER_ROLE);
assert_eq!(admin_role, Some(ADMIN_ROLE));
});
}
#[test]
fn get_role_admin_returns_none_when_not_set() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let admin = Address::generate(&e);
e.as_contract(&address, || {
set_admin(&e, &admin);
let admin_role = get_role_admin(&e, &USER_ROLE);
assert_eq!(admin_role, None);
});
}
#[test]
fn renounce_role_works() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let admin = Address::generate(&e);
let user = Address::generate(&e);
e.as_contract(&address, || {
set_admin(&e, &admin);
grant_role(&e, &user, &USER_ROLE, &admin);
assert!(has_role(&e, &user, &USER_ROLE).is_some());
renounce_role(&e, &USER_ROLE, &user);
assert!(has_role(&e, &user, &USER_ROLE).is_none());
});
}
#[test]
fn admin_transfer_works_with_admin_auth() {
let e = Env::default();
let address = e.register(MockContract, ());
let admin = Address::generate(&e);
let new_admin = Address::generate(&e);
e.mock_all_auths();
e.as_contract(&address, || {
set_admin(&e, &admin);
});
e.as_contract(&address, || {
transfer_admin_role(&e, &new_admin, 1000);
});
e.as_contract(&address, || {
accept_admin_transfer(&e);
assert_eq!(get_admin(&e), Some(new_admin));
let event_assert = EventAssertion::new(&e, address.clone());
event_assert.assert_event_count(1);
});
}
#[test]
fn admin_transfer_cancel_works() {
let e = Env::default();
let address = e.register(MockContract, ());
let admin = Address::generate(&e);
let new_admin = Address::generate(&e);
e.mock_all_auths();
e.as_contract(&address, || {
set_admin(&e, &admin);
});
e.as_contract(&address, || {
transfer_admin_role(&e, &new_admin, 1000);
let event_assert = EventAssertion::new(&e, address.clone());
event_assert.assert_event_count(1);
});
e.as_contract(&address, || {
transfer_admin_role(&e, &new_admin, 0);
assert_eq!(get_admin(&e), Some(admin));
let event_assert = EventAssertion::new(&e, address.clone());
event_assert.assert_event_count(1);
});
}
#[test]
#[should_panic(expected = "Error(Contract, #2000)")]
fn unauthorized_role_grant_panics() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let admin = Address::generate(&e);
let user = Address::generate(&e);
let other = Address::generate(&e);
e.as_contract(&address, || {
set_admin(&e, &admin);
grant_role(&e, &user, &USER_ROLE, &other);
});
}
#[test]
#[should_panic(expected = "Error(Contract, #2000)")]
fn unauthorized_role_revoke_panics() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let admin = Address::generate(&e);
let user = Address::generate(&e);
let other = Address::generate(&e);
e.as_contract(&address, || {
set_admin(&e, &admin);
grant_role(&e, &user, &USER_ROLE, &admin);
revoke_role(&e, &user, &USER_ROLE, &other);
});
}
#[test]
#[should_panic(expected = "Error(Contract, #2007)")]
fn renounce_nonexistent_role_panics() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let admin = Address::generate(&e);
let user = Address::generate(&e);
e.as_contract(&address, || {
set_admin(&e, &admin);
renounce_role(&e, &USER_ROLE, &user);
});
}
#[test]
fn get_admin_with_no_admin_set_works() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
e.as_contract(&address, || {
assert!(get_admin(&e).is_none());
});
}
#[test]
#[should_panic(expected = "Error(Contract, #2002)")]
fn get_role_member_with_out_of_bounds_index_panics() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let admin = Address::generate(&e);
let user = Address::generate(&e);
e.as_contract(&address, || {
set_admin(&e, &admin);
grant_role(&e, &user, &USER_ROLE, &admin);
assert_eq!(get_role_member_count(&e, &USER_ROLE), 1);
get_role_member(&e, &USER_ROLE, 1);
});
}
#[test]
#[should_panic(expected = "Error(Contract, #2001)")]
fn admin_transfer_fails_when_no_admin_set() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let new_admin = Address::generate(&e);
e.as_contract(&address, || {
transfer_admin_role(&e, &new_admin, 1000);
});
}
#[test]
fn add_to_role_enumeration_works() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let account = Address::generate(&e);
e.as_contract(&address, || {
let count_before = get_role_member_count(&e, &USER_ROLE);
assert_eq!(count_before, 0);
add_to_role_enumeration(&e, &account, &USER_ROLE);
let count_after = get_role_member_count(&e, &USER_ROLE);
assert_eq!(count_after, 1);
let retrieved = get_role_member(&e, &USER_ROLE, 0);
assert_eq!(retrieved, account);
let has_role = has_role(&e, &account, &USER_ROLE);
assert_eq!(has_role, Some(0));
});
}
#[test]
fn remove_from_role_enumeration_works() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let account1 = Address::generate(&e);
let account2 = Address::generate(&e);
e.as_contract(&address, || {
add_to_role_enumeration(&e, &account1, &USER_ROLE);
add_to_role_enumeration(&e, &account2, &USER_ROLE);
let count_before = get_role_member_count(&e, &USER_ROLE);
assert_eq!(count_before, 2);
remove_from_role_enumeration(&e, &account1, &USER_ROLE);
let count_after = get_role_member_count(&e, &USER_ROLE);
assert_eq!(count_after, 1);
let retrieved = get_role_member(&e, &USER_ROLE, 0);
assert_eq!(retrieved, account2);
let has_role1 = has_role(&e, &account1, &USER_ROLE);
assert_eq!(has_role1, None);
let has_role2 = has_role(&e, &account2, &USER_ROLE);
assert_eq!(has_role2, Some(0));
});
}
#[test]
fn remove_from_role_enumeration_for_last_account_works() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let account = Address::generate(&e);
e.as_contract(&address, || {
add_to_role_enumeration(&e, &account, &USER_ROLE);
let count_before = get_role_member_count(&e, &USER_ROLE);
assert_eq!(count_before, 1);
remove_from_role_enumeration(&e, &account, &USER_ROLE);
let count_after = get_role_member_count(&e, &USER_ROLE);
assert_eq!(count_after, 0);
let has_role = has_role(&e, &account, &USER_ROLE);
assert_eq!(has_role, None);
});
}
#[test]
#[should_panic(expected = "Error(Contract, #2008)")]
fn remove_from_role_enumeration_with_nonexistent_role_panics() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let account = Address::generate(&e);
let nonexistent_role = Symbol::new(&e, "nonexistent");
e.as_contract(&address, || {
remove_from_role_enumeration(&e, &account, &nonexistent_role);
});
}
#[test]
#[should_panic(expected = "Error(Contract, #2007)")]
fn remove_from_role_enumeration_with_account_not_in_role_panics() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let account1 = Address::generate(&e);
let account2 = Address::generate(&e);
e.as_contract(&address, || {
add_to_role_enumeration(&e, &account1, &USER_ROLE);
remove_from_role_enumeration(&e, &account2, &USER_ROLE);
});
}
#[test]
fn ensure_if_admin_or_admin_role_allows_role_admin_without_contract_admin() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let manager = Address::generate(&e);
e.as_contract(&address, || {
set_role_admin_no_auth(&e, &USER_ROLE, &MANAGER_ROLE);
grant_role_no_auth(&e, &manager, &MANAGER_ROLE, &manager);
ensure_if_admin_or_admin_role(&e, &USER_ROLE, &manager);
});
}
#[test]
fn remove_role_admin_no_auth_works() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let admin = Address::generate(&e);
e.as_contract(&address, || {
set_admin(&e, &admin);
set_role_admin(&e, &USER_ROLE, &ADMIN_ROLE);
let admin_role = get_role_admin(&e, &USER_ROLE);
assert_eq!(admin_role, Some(ADMIN_ROLE));
remove_role_admin_no_auth(&e, &USER_ROLE);
let admin_role_after = get_role_admin(&e, &USER_ROLE);
assert_eq!(admin_role_after, None);
});
}
#[test]
#[should_panic(expected = "Error(Contract, #2006)")]
fn set_admin_when_already_set_panics() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let admin1 = Address::generate(&e);
let admin2 = Address::generate(&e);
e.as_contract(&address, || {
set_admin(&e, &admin1);
let current_admin = get_admin(&e);
assert_eq!(current_admin, Some(admin1));
set_admin(&e, &admin2);
});
}
#[test]
#[should_panic(expected = "Error(Contract, #2003)")]
fn remove_role_admin_no_auth_panics_with_nonexistent_role() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let nonexistent_role = Symbol::new(&e, "nonexistent");
e.as_contract(&address, || {
remove_role_admin_no_auth(&e, &nonexistent_role);
});
}
#[test]
fn remove_role_accounts_count_no_auth_works() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let account = Address::generate(&e);
e.as_contract(&address, || {
add_to_role_enumeration(&e, &account, &USER_ROLE);
remove_from_role_enumeration(&e, &account, &USER_ROLE);
let count = get_role_member_count(&e, &USER_ROLE);
assert_eq!(count, 0);
remove_role_accounts_count_no_auth(&e, &USER_ROLE);
});
}
#[test]
#[should_panic(expected = "Error(Contract, #2004)")]
fn remove_role_accounts_count_no_auth_does_not_remove_nonzero_count() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let account = Address::generate(&e);
e.as_contract(&address, || {
add_to_role_enumeration(&e, &account, &USER_ROLE);
let count = get_role_member_count(&e, &USER_ROLE);
assert_eq!(count, 1);
remove_role_accounts_count_no_auth(&e, &USER_ROLE);
});
}
#[test]
#[should_panic(expected = "Error(Contract, #2005)")]
fn remove_role_accounts_count_no_auth_panics_with_nonexistent_role() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let nonexistent_role = Symbol::new(&e, "nonexistent");
e.as_contract(&address, || {
remove_role_accounts_count_no_auth(&e, &nonexistent_role);
});
}
#[test]
fn renounce_admin_works() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let admin = Address::generate(&e);
e.as_contract(&address, || {
set_admin(&e, &admin);
assert_eq!(get_admin(&e), Some(admin));
renounce_admin(&e);
});
}
#[test]
#[should_panic(expected = "Error(Contract, #2001)")]
fn renounce_admin_fails_when_no_admin_set() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
e.as_contract(&address, || {
renounce_admin(&e);
});
}
#[test]
#[should_panic(expected = "Error(Contract, #2009)")]
fn renounce_admin_fails_when_transfer_in_progress() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let admin = Address::generate(&e);
let new_admin = Address::generate(&e);
e.as_contract(&address, || {
set_admin(&e, &admin);
transfer_admin_role(&e, &new_admin, 1000);
});
e.as_contract(&address, || {
renounce_admin(&e);
});
}
#[test]
fn renounce_admin_succeeds_when_pending_transfer_expired() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let admin = Address::generate(&e);
let new_admin = Address::generate(&e);
e.as_contract(&address, || {
set_admin(&e, &admin);
transfer_admin_role(&e, &new_admin, 1000);
});
e.ledger().set_sequence_number(1001);
e.as_contract(&address, || {
renounce_admin(&e);
assert_eq!(get_admin(&e), None);
});
}
#[test]
fn get_existing_roles_returns_empty_when_no_roles_exist() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
e.as_contract(&address, || {
let roles = get_existing_roles(&e);
assert_eq!(roles.len(), 0);
});
}
#[test]
fn get_existing_roles_returns_roles_after_granting() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let admin = Address::generate(&e);
let user = Address::generate(&e);
e.as_contract(&address, || {
set_admin(&e, &admin);
grant_role(&e, &user, &USER_ROLE, &admin);
let roles = get_existing_roles(&e);
assert_eq!(roles.len(), 1);
assert_eq!(roles.get(0).unwrap(), USER_ROLE);
});
}
#[test]
fn get_existing_roles_removes_role_when_last_account_removed() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let admin = Address::generate(&e);
let user = Address::generate(&e);
e.as_contract(&address, || {
set_admin(&e, &admin);
grant_role(&e, &user, &USER_ROLE, &admin);
let roles_before = get_existing_roles(&e);
assert_eq!(roles_before.len(), 1);
});
e.as_contract(&address, || {
revoke_role(&e, &user, &USER_ROLE, &admin);
let roles_after = get_existing_roles(&e);
assert_eq!(roles_after.len(), 0);
});
}
#[test]
fn get_existing_roles_keeps_role_when_some_accounts_remain() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let admin = Address::generate(&e);
let user1 = Address::generate(&e);
let user2 = Address::generate(&e);
e.as_contract(&address, || {
set_admin(&e, &admin);
grant_role(&e, &user1, &USER_ROLE, &admin);
});
e.as_contract(&address, || {
grant_role(&e, &user2, &USER_ROLE, &admin);
let roles_before = get_existing_roles(&e);
assert_eq!(roles_before.len(), 1);
});
e.as_contract(&address, || {
revoke_role(&e, &user1, &USER_ROLE, &admin);
let roles_after = get_existing_roles(&e);
assert_eq!(roles_after.len(), 1);
assert_eq!(roles_after.get(0).unwrap(), USER_ROLE);
});
}
#[test]
fn get_existing_roles_does_not_create_duplicates() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let admin = Address::generate(&e);
let user1 = Address::generate(&e);
let user2 = Address::generate(&e);
e.as_contract(&address, || {
set_admin(&e, &admin);
grant_role(&e, &user1, &USER_ROLE, &admin);
});
e.as_contract(&address, || {
grant_role(&e, &user2, &USER_ROLE, &admin);
let roles = get_existing_roles(&e);
assert_eq!(roles.len(), 1);
assert_eq!(roles.get(0).unwrap(), USER_ROLE);
});
}
#[test]
#[should_panic(expected = "Error(Contract, #2010)")]
fn grant_role_fails_when_max_roles_exceeded() {
use crate::access_control::MAX_ROLES;
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let admin = Address::generate(&e);
let user = Address::generate(&e);
e.as_contract(&address, || {
set_admin(&e, &admin);
});
for i in 0..MAX_ROLES {
e.as_contract(&address, || {
let role = Symbol::new(&e, &std::format!("role_{}", i));
grant_role(&e, &user, &role, &admin);
});
}
e.as_contract(&address, || {
let roles = get_existing_roles(&e);
assert_eq!(roles.len(), MAX_ROLES);
let overflow_role = Symbol::new(&e, "overflow_role");
grant_role(&e, &user, &overflow_role, &admin);
});
}
#[test]
fn can_reuse_role_slot_after_removal() {
use crate::access_control::MAX_ROLES;
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let admin = Address::generate(&e);
let user = Address::generate(&e);
e.as_contract(&address, || {
set_admin(&e, &admin);
});
for i in 0..MAX_ROLES {
e.as_contract(&address, || {
let role = Symbol::new(&e, &std::format!("role_{}", i));
grant_role(&e, &user, &role, &admin);
});
}
e.as_contract(&address, || {
let roles_before = get_existing_roles(&e);
assert_eq!(roles_before.len(), MAX_ROLES);
let first_role = Symbol::new(&e, "role_0");
revoke_role(&e, &user, &first_role, &admin);
let roles_after_removal = get_existing_roles(&e);
assert_eq!(roles_after_removal.len(), MAX_ROLES - 1);
});
e.as_contract(&address, || {
let new_role = Symbol::new(&e, "new_role");
grant_role(&e, &user, &new_role, &admin);
let roles_final = get_existing_roles(&e);
assert_eq!(roles_final.len(), MAX_ROLES);
});
}