Skip to content
Open
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
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
resolver = "2"

members = [
"contracts/*",
"contracts/access",
"contracts/token",
]


[profile.release]
opt-level = "z"
overflow-checks = true
Expand Down
2 changes: 1 addition & 1 deletion contracts/admin/Cargo.toml → contracts/access/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "bc-forge-admin"
name = "bc-forge-access"
version = "0.1.0"
edition = "2021"
publish = false
Expand Down
50 changes: 43 additions & 7 deletions contracts/admin/src/lib.rs → contracts/access/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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)]
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand All @@ -219,4 +255,4 @@ pub fn mark_executed(env: &Env, proposal_id: u64) {
env.storage()
.instance()
.set(&AdminKey::Proposal(proposal_id), &proposal);
}
}
2 changes: 1 addition & 1 deletion contracts/token/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
63 changes: 20 additions & 43 deletions contracts/token/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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) {
Expand All @@ -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(
Expand Down Expand Up @@ -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),
};
Expand All @@ -326,7 +301,8 @@ impl BcForgeToken {
pub fn set_admin_pool(env: Env, pool: Vec<Address>, 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(
Expand All @@ -335,19 +311,21 @@ 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);
id
}

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()
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -697,4 +674,4 @@ impl TokenInterface for BcForgeToken {
.get(&DataKey::Symbol)
.unwrap_or_else(|| String::from_str(&env, "SFG"))
}
}
}