From 9abd6fabee4119df1674fc7a9763db8b533bc90f Mon Sep 17 00:00:00 2001 From: YazarAyobami Date: Sat, 25 Jul 2026 23:11:51 +0000 Subject: [PATCH 1/2] feat(admin): check RoleAlreadyGranted in grant_role --- contracts/admin/src/lib.rs | 69 ++- ...ole_fails_when_role_already_granted.1.json | 267 +++++++++++ ...st_grant_role_succeeds_after_revoke.1.json | 422 ++++++++++++++++++ ..._set_admin_emits_role_revoked_event.1.json | 218 +++++++++ contracts/token/src/test.rs | 2 +- ...ints_into_contract_and_emits_events.1.json | 59 --- ...evocable_schedule_cannot_be_revoked.1.json | 59 --- ...es_per_beneficiary_release_together.1.json | 59 --- ...vesting_and_prevents_double_release.1.json | 59 --- ...revoke_returns_only_unvested_tokens.1.json | 59 --- 10 files changed, 967 insertions(+), 306 deletions(-) create mode 100644 contracts/admin/test_snapshots/tests/test_grant_role_fails_when_role_already_granted.1.json create mode 100644 contracts/admin/test_snapshots/tests/test_grant_role_succeeds_after_revoke.1.json create mode 100644 contracts/admin/test_snapshots/tests/test_set_admin_emits_role_revoked_event.1.json diff --git a/contracts/admin/src/lib.rs b/contracts/admin/src/lib.rs index 8b57ad51..9fd96062 100644 --- a/contracts/admin/src/lib.rs +++ b/contracts/admin/src/lib.rs @@ -18,6 +18,8 @@ pub enum AdminError { RoleNotHeld = 2, /// `require_role_guard` failed: the caller is not authorized for this role. UnauthorizedRole = 3, + /// `grant_role` was called for an (role, address) pair that was already granted. + RoleAlreadyGranted = 4, } /// Storage keys for the access-control layer. @@ -113,7 +115,7 @@ pub fn set_admin(env: &Env, admin: &Address) { } env.storage().instance().set(&AdminKey::Admin, admin); extend_instance_ttl(env); - _grant_role(env, admin, Role::Admin, admin); + _grant_role(env, admin, Role::Admin, admin).ok(); } pub fn migrate_admin(env: &Env) { @@ -143,7 +145,7 @@ pub fn has_admin(env: &Env) -> bool { has } -pub fn grant_role(env: &Env, role: Role, address: &Address) { +pub fn grant_role(env: &Env, role: Role, address: &Address) -> Result<(), AdminError> { let admin = if has_admin(env) { let admin = get_admin(env); admin.require_auth(); @@ -151,16 +153,24 @@ pub fn grant_role(env: &Env, role: Role, address: &Address) { } else { panic!("contract not initialized: admin not set"); }; - _grant_role(env, &admin, role, address); + _grant_role(env, &admin, role, address) } -fn _grant_role(env: &Env, admin: &Address, role: Role, address: &Address) { +fn _grant_role( + env: &Env, + admin: &Address, + role: Role, + address: &Address, +) -> Result<(), AdminError> { require_non_zero_address(env, address); - env.storage() - .persistent() - .set(&AdminKey::Role(role, address.clone()), &true); - extend_storage_ttl_for_key(env, &AdminKey::Role(role, address.clone())); + let key = AdminKey::Role(role, address.clone()); + if env.storage().persistent().has(&key) { + return Err(AdminError::RoleAlreadyGranted); + } + env.storage().persistent().set(&key, &true); + extend_storage_ttl_for_key(env, &key); events::emit_role_granted(env, admin, role, address); + Ok(()) } pub fn revoke_role(env: &Env, role: Role, address: &Address) -> Result<(), AdminError> { @@ -377,8 +387,8 @@ mod tests { super::set_admin(&env, &admin); } - pub fn grant_role(env: Env, role: Role, address: Address) { - super::grant_role(&env, role, &address); + pub fn grant_role(env: Env, role: Role, address: Address) -> Result<(), AdminError> { + super::grant_role(&env, role, &address) } pub fn revoke_role(env: Env, role: Role, address: Address) -> Result<(), AdminError> { @@ -857,4 +867,43 @@ mod tests { assert!(!client.has_role(&Role::Minter, &zero_address(&env))); assert!(!client.has_role(&Role::SuperAdmin, &zero_address(&env))); } + + #[test] + fn test_grant_role_fails_when_role_already_granted() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register(AdminContract, ()); + let client = AdminContractClient::new(&env, &contract_id); + let admin = Address::generate(&env); + let role_holder = Address::generate(&env); + + client.set_admin(&admin); + client.grant_role(&Role::Minter, &role_holder); + + let result = client.try_grant_role(&Role::Minter, &role_holder); + assert!( + result.is_err(), + "expected try_grant_role to fail when role is already granted" + ); + } + + #[test] + fn test_grant_role_succeeds_after_revoke() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register(AdminContract, ()); + let client = AdminContractClient::new(&env, &contract_id); + let admin = Address::generate(&env); + let role_holder = Address::generate(&env); + + client.set_admin(&admin); + client.grant_role(&Role::Minter, &role_holder); + assert!(client.has_role(&Role::Minter, &role_holder)); + + client.revoke_role(&Role::Minter, &role_holder); + assert!(!client.has_role(&Role::Minter, &role_holder)); + + client.grant_role(&Role::Minter, &role_holder); + assert!(client.has_role(&Role::Minter, &role_holder)); + } } diff --git a/contracts/admin/test_snapshots/tests/test_grant_role_fails_when_role_already_granted.1.json b/contracts/admin/test_snapshots/tests/test_grant_role_fails_when_role_already_granted.1.json new file mode 100644 index 00000000..ebf176b9 --- /dev/null +++ b/contracts/admin/test_snapshots/tests/test_grant_role_fails_when_role_already_granted.1.json @@ -0,0 +1,267 @@ +{ + "generators": { + "address": 3, + "nonce": 0 + }, + "auth": [ + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "grant_role", + "args": [ + { + "vec": [ + { + "symbol": "Minter" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 22, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Minter" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Minter" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/admin/test_snapshots/tests/test_grant_role_succeeds_after_revoke.1.json b/contracts/admin/test_snapshots/tests/test_grant_role_succeeds_after_revoke.1.json new file mode 100644 index 00000000..03adc9e9 --- /dev/null +++ b/contracts/admin/test_snapshots/tests/test_grant_role_succeeds_after_revoke.1.json @@ -0,0 +1,422 @@ +{ + "generators": { + "address": 3, + "nonce": 0 + }, + "auth": [ + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "grant_role", + "args": [ + { + "vec": [ + { + "symbol": "Minter" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "revoke_role", + "args": [ + { + "vec": [ + { + "symbol": "Minter" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "grant_role", + "args": [ + { + "vec": [ + { + "symbol": "Minter" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 22, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Minter" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Minter" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "role_chk" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "vec": [ + { + "symbol": "Minter" + } + ] + }, + { + "bool": true + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/admin/test_snapshots/tests/test_set_admin_emits_role_revoked_event.1.json b/contracts/admin/test_snapshots/tests/test_set_admin_emits_role_revoked_event.1.json new file mode 100644 index 00000000..8e0d5caf --- /dev/null +++ b/contracts/admin/test_snapshots/tests/test_set_admin_emits_role_revoked_event.1.json @@ -0,0 +1,218 @@ +{ + "generators": { + "address": 3, + "nonce": 0 + }, + "auth": [ + [], + [], + [] + ], + "ledger": { + "protocol_version": 22, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "role_rvk" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "role_grnt" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/token/src/test.rs b/contracts/token/src/test.rs index a8ba7247..ef2ed9c7 100644 --- a/contracts/token/src/test.rs +++ b/contracts/token/src/test.rs @@ -152,7 +152,7 @@ fn test_upgrade_permits_super_admin_role_holder_past_the_guard() { let new_wasm_hash = BytesN::from_array(&env, &[0u8; 32]); env.as_contract(&contract_id, || { - bc_forge_admin::grant_role(&env, bc_forge_admin::Role::SuperAdmin, &upgrader); + bc_forge_admin::grant_role(&env, bc_forge_admin::Role::SuperAdmin, &upgrader).unwrap(); }); // The guard passes for a SuperAdmin holder, so execution reaches diff --git a/contracts/vesting/test_snapshots/test/test_create_vesting_mints_into_contract_and_emits_events.1.json b/contracts/vesting/test_snapshots/test/test_create_vesting_mints_into_contract_and_emits_events.1.json index 05bed92f..25b86d31 100644 --- a/contracts/vesting/test_snapshots/test/test_create_vesting_mints_into_contract_and_emits_events.1.json +++ b/contracts/vesting/test_snapshots/test/test_create_vesting_mints_into_contract_and_emits_events.1.json @@ -223,65 +223,6 @@ 4105 ] ], - [ - { - "contract_data": { - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", - "key": { - "vec": [ - { - "symbol": "Role" - }, - { - "vec": [ - { - "symbol": "Admin" - } - ] - }, - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" - } - ] - }, - "durability": "persistent" - } - }, - [ - { - "last_modified_ledger_seq": 0, - "data": { - "contract_data": { - "ext": "v0", - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", - "key": { - "vec": [ - { - "symbol": "Role" - }, - { - "vec": [ - { - "symbol": "Admin" - } - ] - }, - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" - } - ] - }, - "durability": "persistent", - "val": { - "bool": true - } - } - }, - "ext": "v0" - }, - 4105 - ] - ], [ { "contract_data": { diff --git a/contracts/vesting/test_snapshots/test/test_irrevocable_schedule_cannot_be_revoked.1.json b/contracts/vesting/test_snapshots/test/test_irrevocable_schedule_cannot_be_revoked.1.json index b2d3bc37..229a4ceb 100644 --- a/contracts/vesting/test_snapshots/test/test_irrevocable_schedule_cannot_be_revoked.1.json +++ b/contracts/vesting/test_snapshots/test/test_irrevocable_schedule_cannot_be_revoked.1.json @@ -222,65 +222,6 @@ 4105 ] ], - [ - { - "contract_data": { - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", - "key": { - "vec": [ - { - "symbol": "Role" - }, - { - "vec": [ - { - "symbol": "Admin" - } - ] - }, - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" - } - ] - }, - "durability": "persistent" - } - }, - [ - { - "last_modified_ledger_seq": 0, - "data": { - "contract_data": { - "ext": "v0", - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", - "key": { - "vec": [ - { - "symbol": "Role" - }, - { - "vec": [ - { - "symbol": "Admin" - } - ] - }, - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" - } - ] - }, - "durability": "persistent", - "val": { - "bool": true - } - } - }, - "ext": "v0" - }, - 4105 - ] - ], [ { "contract_data": { diff --git a/contracts/vesting/test_snapshots/test/test_multiple_schedules_per_beneficiary_release_together.1.json b/contracts/vesting/test_snapshots/test/test_multiple_schedules_per_beneficiary_release_together.1.json index 81111f75..7f4e9d1e 100644 --- a/contracts/vesting/test_snapshots/test/test_multiple_schedules_per_beneficiary_release_together.1.json +++ b/contracts/vesting/test_snapshots/test/test_multiple_schedules_per_beneficiary_release_together.1.json @@ -426,65 +426,6 @@ 4105 ] ], - [ - { - "contract_data": { - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", - "key": { - "vec": [ - { - "symbol": "Role" - }, - { - "vec": [ - { - "symbol": "Admin" - } - ] - }, - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" - } - ] - }, - "durability": "persistent" - } - }, - [ - { - "last_modified_ledger_seq": 0, - "data": { - "contract_data": { - "ext": "v0", - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", - "key": { - "vec": [ - { - "symbol": "Role" - }, - { - "vec": [ - { - "symbol": "Admin" - } - ] - }, - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" - } - ] - }, - "durability": "persistent", - "val": { - "bool": true - } - } - }, - "ext": "v0" - }, - 4105 - ] - ], [ { "contract_data": { diff --git a/contracts/vesting/test_snapshots/test/test_release_respects_cliff_linear_vesting_and_prevents_double_release.1.json b/contracts/vesting/test_snapshots/test/test_release_respects_cliff_linear_vesting_and_prevents_double_release.1.json index 4c827306..bb06804e 100644 --- a/contracts/vesting/test_snapshots/test/test_release_respects_cliff_linear_vesting_and_prevents_double_release.1.json +++ b/contracts/vesting/test_snapshots/test/test_release_respects_cliff_linear_vesting_and_prevents_double_release.1.json @@ -571,65 +571,6 @@ 4105 ] ], - [ - { - "contract_data": { - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", - "key": { - "vec": [ - { - "symbol": "Role" - }, - { - "vec": [ - { - "symbol": "Admin" - } - ] - }, - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" - } - ] - }, - "durability": "persistent" - } - }, - [ - { - "last_modified_ledger_seq": 0, - "data": { - "contract_data": { - "ext": "v0", - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", - "key": { - "vec": [ - { - "symbol": "Role" - }, - { - "vec": [ - { - "symbol": "Admin" - } - ] - }, - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" - } - ] - }, - "durability": "persistent", - "val": { - "bool": true - } - } - }, - "ext": "v0" - }, - 4105 - ] - ], [ { "contract_data": { diff --git a/contracts/vesting/test_snapshots/test/test_revoke_returns_only_unvested_tokens.1.json b/contracts/vesting/test_snapshots/test/test_revoke_returns_only_unvested_tokens.1.json index f01fc795..21fe0972 100644 --- a/contracts/vesting/test_snapshots/test/test_revoke_returns_only_unvested_tokens.1.json +++ b/contracts/vesting/test_snapshots/test/test_revoke_returns_only_unvested_tokens.1.json @@ -514,65 +514,6 @@ 4105 ] ], - [ - { - "contract_data": { - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", - "key": { - "vec": [ - { - "symbol": "Role" - }, - { - "vec": [ - { - "symbol": "Admin" - } - ] - }, - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" - } - ] - }, - "durability": "persistent" - } - }, - [ - { - "last_modified_ledger_seq": 0, - "data": { - "contract_data": { - "ext": "v0", - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", - "key": { - "vec": [ - { - "symbol": "Role" - }, - { - "vec": [ - { - "symbol": "Admin" - } - ] - }, - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" - } - ] - }, - "durability": "persistent", - "val": { - "bool": true - } - } - }, - "ext": "v0" - }, - 4105 - ] - ], [ { "contract_data": { From e185779c83d9a843bd1d839b7a8874b107707171 Mon Sep 17 00:00:00 2001 From: YazarAyobami Date: Wed, 29 Jul 2026 19:45:02 +0000 Subject: [PATCH 2/2] fix(admin): add RoleAlreadyGranted variant to AdminError enum --- contracts/admin/src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/contracts/admin/src/lib.rs b/contracts/admin/src/lib.rs index 76c48424..5c72211f 100644 --- a/contracts/admin/src/lib.rs +++ b/contracts/admin/src/lib.rs @@ -34,6 +34,10 @@ //! | `1` | `RoleNotGranted` | unused (ABI-stable; revoke now uses `RoleNotHeld`) | //! | `2` | `RoleNotHeld` | `revoke_role` / `require_role` when the role is missing | //! | `3` | `UnauthorizedRole` | `require_role_guard` failure (caller not authorized) | +//! | `4` | `InvalidAddress` | operation attempted with the canonical zero address | +//! | `5` | `InvalidRole` | unrecognized role discriminant supplied | +//! | `6` | `AlreadyInitialized` | `init_storage` called on an already-initialized contract | +//! | `7` | `RoleAlreadyGranted` | role has already been granted to the target address | //! //! ## Event Emissions //! @@ -149,6 +153,9 @@ pub enum AdminError { /// The contract has already been initialized; calling `init_storage` again /// is not allowed. AlreadyInitialized = 6, + /// The role has already been granted to the target address. + #[allow(dead_code)] + RoleAlreadyGranted = 7, } /// Storage keys for the access-control layer.