diff --git a/Cargo.toml b/Cargo.toml index d186cba..ff32452 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,9 +2,11 @@ resolver = "2" members = [ - "contracts/*", + "contracts/access", + "contracts/token", ] + [profile.release] opt-level = "z" overflow-checks = true diff --git a/contracts/admin/Cargo.toml b/contracts/access/Cargo.toml similarity index 95% rename from contracts/admin/Cargo.toml rename to contracts/access/Cargo.toml index 3078a95..cb669ae 100644 --- a/contracts/admin/Cargo.toml +++ b/contracts/access/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "bc-forge-admin" +name = "bc-forge-access" version = "0.1.0" edition = "2021" publish = false diff --git a/contracts/admin/src/lib.rs b/contracts/access/src/lib.rs similarity index 81% rename from contracts/admin/src/lib.rs rename to contracts/access/src/lib.rs index e76d173..783f076 100644 --- a/contracts/admin/src/lib.rs +++ b/contracts/access/src/lib.rs @@ -2,7 +2,7 @@ #![no_std] -use soroban_sdk::{contracttype, vec, Address, Env, String, Vec}; +use soroban_sdk::{contracttype, vec, Address, Env, String, Vec, Symbol}; #[derive(Clone)] #[contracttype] @@ -27,6 +27,12 @@ pub enum Role { Admin, /// Account authorized to mint tokens. Minter, + /// Account authorized to freeze contract interactions. + Pauser, + /// Account authorized to upgrade contract Wasm code. + Upgrader, + /// Account authorized to burn tokens explicitly. + Burner, } #[derive(Clone, Debug, PartialEq)] @@ -63,13 +69,42 @@ pub fn grant_role(env: &Env, role: Role, address: &Address) { env.storage() .persistent() .set(&AdminKey::Role(role, address.clone()), &true); + + // Emit explicit role granted event + env.events().publish( + (Symbol::new(env, "role_granted"), role), + address.clone(), + ); } pub fn revoke_role(env: &Env, role: Role, address: &Address) { require_admin(env); - env.storage() - .persistent() - .remove(&AdminKey::Role(role, address.clone())); + if env.storage().persistent().has(&AdminKey::Role(role, address.clone())) { + env.storage() + .persistent() + .remove(&AdminKey::Role(role, address.clone())); + } + + // Emit explicit role revoked event + env.events().publish( + (Symbol::new(env, "role_revoked"), role), + address.clone(), + ); +} + +pub fn renounce_role(env: &Env, address: &Address, role: Role) { + address.require_auth(); + let key = AdminKey::Role(role, address.clone()); + if !env.storage().persistent().has(&key) { + panic!("AccessControl: cannot renounce an unassigned role"); + } + env.storage().persistent().remove(&key); + + // Emit explicit role renounced event + env.events().publish( + (Symbol::new(env, "role_renounced"), role), + address.clone(), + ); } pub fn has_role(env: &Env, role: Role, address: &Address) -> bool { @@ -154,7 +189,6 @@ pub fn create_proposal(env: &Env, creator: Address, description: String) -> u64 let proposal = Proposal { creator: creator.clone(), - action_type, description, approvals: vec![env, creator], executed: false, @@ -198,7 +232,9 @@ pub fn is_proposal_ready(env: &Env, proposal_id: u64) -> bool { .instance() .get(&AdminKey::Proposal(proposal_id)) .expect("proposal not found"); - proposal.approvals.len() >= get_threshold(env) + + let threshold = get_threshold(env); + proposal.approvals.len() >= threshold } pub fn mark_executed(env: &Env, proposal_id: u64) { @@ -219,4 +255,4 @@ pub fn mark_executed(env: &Env, proposal_id: u64) { env.storage() .instance() .set(&AdminKey::Proposal(proposal_id), &proposal); -} +} \ No newline at end of file diff --git a/contracts/admin/test_snapshots/tests/test_has_admin.1.json b/contracts/access/test_snapshots/tests/test_has_admin.1.json similarity index 100% rename from contracts/admin/test_snapshots/tests/test_has_admin.1.json rename to contracts/access/test_snapshots/tests/test_has_admin.1.json diff --git a/contracts/admin/test_snapshots/tests/test_set_and_get_admin.1.json b/contracts/access/test_snapshots/tests/test_set_and_get_admin.1.json similarity index 100% rename from contracts/admin/test_snapshots/tests/test_set_and_get_admin.1.json rename to contracts/access/test_snapshots/tests/test_set_and_get_admin.1.json diff --git a/contracts/token/Cargo.toml b/contracts/token/Cargo.toml index 636f458..c11db34 100644 --- a/contracts/token/Cargo.toml +++ b/contracts/token/Cargo.toml @@ -14,7 +14,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] soroban-sdk = "22.0.0" -bc-forge-admin = { path = "../admin" } +bc-forge-access = { path = "../access" } bc-forge-lifecycle = { path = "../lifecycle" } [dev-dependencies] diff --git a/contracts/token/src/lib.rs b/contracts/token/src/lib.rs index 5faad34..b3cf241 100644 --- a/contracts/token/src/lib.rs +++ b/contracts/token/src/lib.rs @@ -11,7 +11,7 @@ mod events; #[cfg(test)] mod test; -use bc_forge_admin::{self as admin, Role}; +use bc_forge_access::Role; use soroban_sdk::token::TokenInterface; use soroban_sdk::{ contract, contracterror, contractimpl, contracttype, Address, BytesN, Env, String, Vec, @@ -25,8 +25,6 @@ pub enum DataKey { PendingAdmin, /// Spending allowance: (owner, spender) → amount and expiration. Allowance(Address, Address), - /// Token balance for an address. - Allowance(Address, Address), AllowanceExp(Address, Address), Balance(Address), Name, @@ -94,7 +92,6 @@ impl BcForgeToken { fn set_admin(env: &Env, new_admin: &Address) { env.storage().instance().set(&DataKey::Admin, new_admin); - admin::set_admin(env, new_admin); } fn ensure_initialized(env: &Env) -> Result<(), TokenError> { @@ -134,34 +131,16 @@ impl BcForgeToken { } fn read_allowance(env: &Env, from: &Address, spender: &Address) -> i128 { - let allowance_info: AllowanceInfo = env.storage() - .persistent() - .get(&DataKey::Allowance(from.clone(), spender.clone())) - .unwrap_or(AllowanceInfo { amount: 0, exp_ledger: 0 }); + let allowance_info = Self::read_allowance_info(env, from, spender); - // Check if allowance has expired if allowance_info.exp_ledger > 0 { let current_ledger = env.ledger().sequence(); - if current_ledger > allowance_info.exp_ledger as u64 { - return 0; // Allowance expired + if current_ledger > allowance_info.exp_ledger { + return 0; } } allowance_info.amount - if let Some(exp_ledger) = env - .storage() - .persistent() - .get::<_, u32>(&DataKey::AllowanceExp(from.clone(), spender.clone())) - { - if exp_ledger > 0 && env.ledger().sequence() > exp_ledger { - return 0; - } - } - - env.storage() - .persistent() - .get(&DataKey::Allowance(from.clone(), spender.clone())) - .unwrap_or(0) } fn write_allowance(env: &Env, from: &Address, spender: &Address, amount: i128, exp: u32) { @@ -177,10 +156,6 @@ impl BcForgeToken { .persistent() .get(&DataKey::Allowance(from.clone(), spender.clone())) .unwrap_or(AllowanceInfo { amount: 0, exp_ledger: 0 }) - .set(&DataKey::Allowance(from.clone(), spender.clone()), &amount); - env.storage() - .persistent() - .set(&DataKey::AllowanceExp(from.clone(), spender.clone()), &exp); } fn move_balance( @@ -301,7 +276,7 @@ impl BcForgeToken { if amount <= 0 { soroban_sdk::panic_with_error!(&env, TokenError::InvalidAmount); } - total = match total.checked_add(amount) { +total = match total.checked_add(amount) { Some(total) => total, None => soroban_sdk::panic_with_error!(&env, TokenError::InvalidAmount), }; @@ -326,7 +301,8 @@ impl BcForgeToken { pub fn set_admin_pool(env: Env, pool: Vec
, threshold: u32) { let current_admin = Self::read_admin(&env).expect("contract not initialized"); current_admin.require_auth(); - admin::set_admin_pool(&env, pool, threshold); + // Assuming multi-sig management helper functions are exposed via bc_forge_access + bc_forge_access::set_admin_pool(&env, pool, threshold); } pub fn propose_action( @@ -335,7 +311,8 @@ impl BcForgeToken { action: TokenAction, description: String, ) -> u64 { - let id = admin::create_proposal(&env, signer, description); + signer.require_auth(); + let id = bc_forge_access::create_proposal(&env, signer, description); env.storage() .instance() .set(&DataKey::ProposalAction(id), &action); @@ -343,11 +320,12 @@ impl BcForgeToken { } pub fn approve_proposal(env: Env, signer: Address, proposal_id: u64) { - admin::approve_proposal(&env, signer, proposal_id); + signer.require_auth(); + bc_forge_access::approve_proposal(&env, signer, proposal_id); } pub fn execute_proposal(env: Env, proposal_id: u64) { - admin::mark_executed(&env, proposal_id); + bc_forge_access::mark_executed(&env, proposal_id); let action: TokenAction = env .storage() .instance() @@ -403,15 +381,19 @@ impl BcForgeToken { } pub fn grant_role(env: Env, role: Role, address: Address) { - admin::grant_role(&env, role, &address); + let current_admin = Self::read_admin(&env).expect("contract not initialized"); + current_admin.require_auth(); + bc_forge_access::grant_role(&env, role, &address); } pub fn revoke_role(env: Env, role: Role, address: Address) { - admin::revoke_role(&env, role, &address); + let current_admin = Self::read_admin(&env).expect("contract not initialized"); + current_admin.require_auth(); + bc_forge_access::revoke_role(&env, role, &address); } pub fn has_role(env: Env, role: Role, address: Address) -> bool { - admin::has_role(&env, role, &address) + bc_forge_access::has_role(&env, role, &address) } pub fn lock_tokens( @@ -615,12 +597,9 @@ impl TokenInterface for BcForgeToken { soroban_sdk::panic_with_error!(&env, TokenError::InsufficientAllowance); } - Self::move_balance(&env, &from, &to, amount); - // Preserve the original expiration let allowance_info = Self::read_allowance_info(&env, &from, &spender); Self::write_allowance(&env, &from, &spender, allowance - amount, allowance_info.exp_ledger); let _ = Self::panic_on_err(&env, Self::move_balance(&env, &from, &to, amount)); - Self::write_allowance(&env, &from, &spender, allowance - amount, 0); events::emit_transfer_from(&env, &spender, &from, &to, amount, allowance - amount); } @@ -664,10 +643,8 @@ impl TokenInterface for BcForgeToken { soroban_sdk::panic_with_error!(&env, TokenError::InsufficientBalance); } - // Preserve the original expiration let allowance_info = Self::read_allowance_info(&env, &from, &spender); Self::write_allowance(&env, &from, &spender, allowance - amount, allowance_info.exp_ledger); - Self::write_allowance(&env, &from, &spender, allowance - amount, 0); Self::write_balance(&env, &from, balance - amount); let supply = Self::read_supply(&env) - amount; Self::write_supply(&env, supply); @@ -697,4 +674,4 @@ impl TokenInterface for BcForgeToken { .get(&DataKey::Symbol) .unwrap_or_else(|| String::from_str(&env, "SFG")) } -} +} \ No newline at end of file