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
2 changes: 2 additions & 0 deletions contracts/split/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ pub enum ContractError {
MemoMismatch = 33,
/// Issue #439: Creator is in cooldown after cancelling an invoice.
CreatorCooldownActive = 31,
/// RBAC: Caller does not hold the required role for this entry point.
RoleNotHeld = 33,
/// Issue #482: Intermediate multiplication or division overflowed i128 bounds.
ArithmeticOverflow = 33,
/// Issue #483: A zero-value or negative amount was passed where a positive amount is required.
Expand Down
27 changes: 27 additions & 0 deletions contracts/split/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1090,6 +1090,33 @@ pub fn creator_cooldown_set(
);
}

/// RBAC: Emitted when an admin grants a role to an address.
/// Topics: (split, role_grt, grantee)
/// Data: (role_discriminant, admin)
pub fn role_granted(env: &Env, grantee: &Address, role_discriminant: u32, admin: &Address) {
env.events().publish(
(
symbol_short!("split"),
symbol_short!("role_grt"),
grantee.clone(),
),
(role_discriminant, admin.clone()),
);
}

/// RBAC: Emitted when an admin revokes a role from an address.
/// Topics: (split, role_rev, grantee)
/// Data: (role_discriminant, admin)
pub fn role_revoked(env: &Env, grantee: &Address, role_discriminant: u32, admin: &Address) {
env.events().publish(
(
symbol_short!("split"),
symbol_short!("role_rev"),
grantee.clone(),
),
(role_discriminant, admin.clone()),
);
}
/// Issue #474: Emitted when a creator cancels an open invoice and all contributors are refunded.
/// Topics: (split, inv_cncl, invoice_id)
/// Data: (creator, total_refunded, ledger)
Expand Down
50 changes: 50 additions & 0 deletions contracts/split/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ use types::{
Recipient, RebateTier, RepScore, ResolveAction, ResolveRule, SimulateReleaseResult, SplitRule,
SubscriptionParams, TimelockAction, Tranche, TreasuryRecord, UpgradeProposal,
LegacyInvoice, OverflowBehavior, Payment, PaymentCertificate, PaymentCommitment, PaymentProof,
ProtocolFeeConfig, QueuedAction, Recipient, RebateTier, RepScore, ResolveAction, ResolveRule,
Role, SimulateReleaseResult, SplitRule, SubscriptionParams, TimelockAction, Tranche,
TreasuryRecord, UpgradeProposal,
ProtocolFeeConfig, QueuedAction, Recipient, RecipientAddress, RebateTier, RepScore, ResolveAction, ResolveRule,
SimulateReleaseResult, SplitRule, SubscriptionParams, TimelockAction, Tranche, TreasuryRecord,
UpgradeProposal,
Expand Down Expand Up @@ -211,6 +214,20 @@ fn milestone_flags_key(id: u64) -> (Symbol, u64) {
(symbol_short!("ms_flgs"), id)
}

/// RBAC: per-(address, role) assignment flag — persistent storage.
/// Stored as `bool`; absent key means role is not held.
fn role_key(address: &Address, role_discriminant: u32) -> (Symbol, Address, u32) {
(symbol_short!("role_asn"), address.clone(), role_discriminant)
}

/// Convert a `Role` to its stable u32 discriminant used as the storage key component.
fn role_discriminant(role: &Role) -> u32 {
match role {
Role::Admin => 0,
Role::Creator => 1,
Role::Operator => 2,
Role::Auditor => 3,
}
/// Contract-level funding progress checkpoints in basis points.
fn funding_checkpoints_key() -> Symbol {
symbol_short!("fnd_chk")
Expand Down Expand Up @@ -2043,6 +2060,39 @@ fn require_not_frozen(env: &Env) {
assert!(!is_frozen, "contract is frozen for upgrade");
}

// ---------------------------------------------------------------------------
// RBAC helpers
// ---------------------------------------------------------------------------

/// Return `true` when `address` holds `role` **or** holds `Role::Admin`.
/// Admin is a super-role that implies all other roles.
fn has_role(env: &Env, address: &Address, role: &Role) -> bool {
// Admin implies every role
let admin_disc = role_discriminant(&Role::Admin);
let role_disc = role_discriminant(role);
env.storage()
.persistent()
.get::<_, bool>(&role_key(address, admin_disc))
.unwrap_or(false)
|| env.storage()
.persistent()
.get::<_, bool>(&role_key(address, role_disc))
.unwrap_or(false)
}

/// Require that `caller` holds at least one of the supplied roles.
/// Also requires `caller.require_auth()` so the call is signed.
/// Panics with "RoleNotHeld" when no role matches.
fn require_role(env: &Env, caller: &Address, roles: &[Role]) {
caller.require_auth();
for role in roles {
if has_role(env, caller, role) {
return;
}
}
panic!("RoleNotHeld");
}

// ---------------------------------------------------------------------------
// Issue #431: Duplicate payment detection
// ---------------------------------------------------------------------------
Expand Down
9 changes: 9 additions & 0 deletions contracts/split/src/storage_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,15 @@ pub fn required_memo_hash_key(invoice_id: u64) -> (Symbol, u64) { (symbol_short!
pub fn invoice_tags_key(invoice_id: u64) -> (Symbol, u64) { (symbol_short!("inv_tags"), invoice_id) }

// ---------------------------------------------------------------------------
// RBAC: Role assignment storage
// ---------------------------------------------------------------------------

/// Per-address per-role assignment flag — persistent storage.
/// Stored as a boolean `true`; absence means the role is not held.
/// Key: ("role_asn", address, role_u32) where role_u32 is the Role discriminant.
pub fn role_key(address: &Address, role_discriminant: u32) -> (Symbol, Address, u32) {
(symbol_short!("role_asn"), address.clone(), role_discriminant)
}
// Issue #474: Invoice cancellation
// ---------------------------------------------------------------------------

Expand Down
14 changes: 14 additions & 0 deletions contracts/split/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,20 @@ pub enum AdminRole {
Operator,
}

/// Issue RBAC: Fine-grained role assigned to an address.
/// - Admin : may perform any action (equivalent to SuperAdmin for RBAC gates).
/// - Creator : may call `create_invoice`.
/// - Operator : may call `release` / `release_invoice`.
/// - Auditor : read-only; may call `get_invoice` and other query entry points.
#[contracttype]
#[derive(Clone, Debug, PartialEq)]
pub enum Role {
Admin,
Creator,
Operator,
Auditor,
}

#[contracttype]
#[derive(Clone, Debug)]
pub struct Payment {
Expand Down
Loading