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
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ testutils = ["soroban-sdk/testutils"]

[profile.release]
opt-level = "z"
overflow-checks = true
debug = 0
strip = "symbols"
debug-assertions = false
Expand Down
19 changes: 8 additions & 11 deletions src/access_control.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
use soroban_sdk::unwrap::UnwrapOptimized;
use soroban_sdk::{contracttype, Address, Env, Vec};

#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Eq, PartialEq)]
pub enum Role {
Admin,
Manager,
Moderator,
Verifier,
/// Emergency guardian: authorized to veto a pending contract WASM
/// upgrade during its timelock window (see `upgrade.rs`). Deliberately
/// separate from `Admin` so upgrade proposals can be checked by a party
/// other than the one proposing them.
Guardian,
}

#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Eq, PartialEq)]
pub struct RoleData {
pub role: Role,
pub granted_at: u64,
Expand All @@ -36,10 +33,10 @@ pub fn grant_role(env: Env, admin: Address, user: Address, role: Role) {
.storage()
.instance()
.get(&AccessControlKey::Admin)
.unwrap_or_else(|| panic!("not initialized"));
.unwrap_optimized();

if admin != stored_admin {
panic!("only admin can grant roles");
panic!();
}

let key = AccessControlKey::Role(user.clone());
Expand Down Expand Up @@ -72,10 +69,10 @@ pub fn revoke_role(env: Env, admin: Address, user: Address) {
.storage()
.instance()
.get(&AccessControlKey::Admin)
.unwrap_or_else(|| panic!("not initialized"));
.unwrap_optimized();

if admin != stored_admin {
panic!("only admin can revoke roles");
panic!();
}

let key = AccessControlKey::Role(user.clone());
Expand Down Expand Up @@ -117,6 +114,6 @@ pub fn get_role(env: Env, user: Address) -> Option<RoleData> {

pub fn require_role(env: Env, user: Address, role: Role) {
if !has_role(env.clone(), user, role) {
panic!("access denied: required role not found");
panic!();
}
}
35 changes: 18 additions & 17 deletions src/escrow.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use soroban_sdk::unwrap::UnwrapOptimized;
use soroban_sdk::{contracttype, Env, Vec};

#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Eq, PartialEq)]
pub enum MilestoneStatus {
Pending,
Submitted,
Expand All @@ -11,20 +12,20 @@ pub enum MilestoneStatus {
}

#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Eq, PartialEq)]
pub struct Milestone {
pub id: u32,
pub task_id: u32,
pub title: soroban_sdk::String,
pub title: soroban_sdk::Symbol,
pub amount: i128,
pub status: MilestoneStatus,
pub due_date: Option<u64>,
pub submission_url: Option<soroban_sdk::String>,
pub feedback: Option<soroban_sdk::String>,
pub submission_url: Option<soroban_sdk::Symbol>,
pub feedback: Option<soroban_sdk::Symbol>,
}

#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Eq, PartialEq)]
pub struct EscrowStats {
pub total_locked: i128,
pub total_released: i128,
Expand All @@ -44,7 +45,7 @@ pub enum EscrowKey {
pub fn create_milestone(
env: Env,
task_id: u32,
title: soroban_sdk::String,
title: soroban_sdk::Symbol,
amount: i128,
due_date: Option<u64>,
) -> u32 {
Expand Down Expand Up @@ -74,18 +75,18 @@ pub fn submit_milestone(
env: Env,
task_id: u32,
milestone_id: u32,
submission_url: soroban_sdk::String,
submission_url: soroban_sdk::Symbol,
) {
let key = EscrowKey::Milestone(task_id, milestone_id);
let mut milestone: Milestone = env
.storage()
.persistent()
.get(&key)
.unwrap_or_else(|| panic!("milestone not found"));
.unwrap_optimized();

if milestone.status != MilestoneStatus::Pending && milestone.status != MilestoneStatus::Rejected
{
panic!("milestone cannot be submitted");
panic!();
}

milestone.status = MilestoneStatus::Submitted;
Expand All @@ -98,17 +99,17 @@ pub fn approve_milestone(
env: Env,
task_id: u32,
milestone_id: u32,
feedback: Option<soroban_sdk::String>,
feedback: Option<soroban_sdk::Symbol>,
) -> i128 {
let key = EscrowKey::Milestone(task_id, milestone_id);
let mut milestone: Milestone = env
.storage()
.persistent()
.get(&key)
.unwrap_or_else(|| panic!("milestone not found"));
.unwrap_optimized();

if milestone.status != MilestoneStatus::Submitted {
panic!("milestone not submitted");
panic!();
}

milestone.status = MilestoneStatus::Approved;
Expand All @@ -124,16 +125,16 @@ pub fn approve_milestone(
amount
}

pub fn reject_milestone(env: Env, task_id: u32, milestone_id: u32, feedback: soroban_sdk::String) {
pub fn reject_milestone(env: Env, task_id: u32, milestone_id: u32, feedback: soroban_sdk::Symbol) {
let key = EscrowKey::Milestone(task_id, milestone_id);
let mut milestone: Milestone = env
.storage()
.persistent()
.get(&key)
.unwrap_or_else(|| panic!("milestone not found"));
.unwrap_optimized();

if milestone.status != MilestoneStatus::Submitted {
panic!("milestone not submitted");
panic!();
}

milestone.status = MilestoneStatus::Rejected;
Expand Down Expand Up @@ -212,7 +213,7 @@ pub fn release_escrow(env: Env, task_id: u32, amount: i128) {
let current: i128 = env.storage().persistent().get(&key).unwrap_or(0);

if current < amount {
panic!("insufficient escrow balance");
panic!();
}

let new_balance = current - amount;
Expand Down
47 changes: 15 additions & 32 deletions src/events.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
use soroban_sdk::{symbol_short, Address, Env, String};

/// Event module for the LatterFix TaskManager Soroban contract.
///
/// Every public state-changing action emits a structured event via
/// `env.events().publish()`. Events are indexed off-chain by:
/// - Stellar Expert contract event viewer
/// - Soroban RPC `getEvents` (filtered by contractId + topic)
/// - The LatterFix frontend via `fetchContractEvents()` in transactionHistory.ts
///
/// Topic layout: (symbol, primary_id)
/// Data layout: tuple of relevant fields
use soroban_sdk::{symbol_short, Address, Env, Symbol};

// ── Task Events ────────────────────────────────────────────────────────────
pub fn emit_task_created(env: &Env, task_id: u32, creator: Address, title: String, reward: i128) {
pub fn emit_task_created(env: &Env, task_id: u32, creator: Address, title: Symbol, reward: i128) {
let ledger_ts = env.ledger().timestamp();
env.events().publish(
(symbol_short!("task_cre"), task_id),
Expand All @@ -26,7 +16,7 @@ pub fn emit_task_assigned(env: &Env, task_id: u32, assignee: Address) {
);
}

pub fn emit_task_submitted(env: &Env, task_id: u32, assignee: Address, delivery_url: String) {
pub fn emit_task_submitted(env: &Env, task_id: u32, assignee: Address, delivery_url: Symbol) {
env.events().publish(
(symbol_short!("task_subm"), task_id),
(assignee, delivery_url, env.ledger().timestamp()),
Expand Down Expand Up @@ -70,14 +60,14 @@ pub fn emit_dispute_split_resolved(env: &Env, task_id: u32, platform_fee: i128,

// ── Profile Events ─────────────────────────────────────────────────────────

pub fn emit_profile_created(env: &Env, user: Address, username: String) {
pub fn emit_profile_created(env: &Env, user: Address, username: Symbol) {
env.events().publish(
(symbol_short!("prof_cre"), user),
(username, env.ledger().timestamp()),
);
}

pub fn emit_profile_updated(env: &Env, user: Address, field: String) {
pub fn emit_profile_updated(env: &Env, user: Address, field: Symbol) {
env.events().publish(
(symbol_short!("prof_upd"), user),
(field, env.ledger().timestamp()),
Expand Down Expand Up @@ -114,7 +104,7 @@ pub fn emit_milestone_approved(env: &Env, task_id: u32, milestone_id: u32, amoun
);
}

pub fn emit_milestone_rejected(env: &Env, task_id: u32, milestone_id: u32, feedback: String) {
pub fn emit_milestone_rejected(env: &Env, task_id: u32, milestone_id: u32, feedback: Symbol) {
env.events().publish(
(symbol_short!("mile_rej"), (task_id, milestone_id)),
(feedback, env.ledger().timestamp()),
Expand All @@ -123,14 +113,14 @@ pub fn emit_milestone_rejected(env: &Env, task_id: u32, milestone_id: u32, feedb

// ── Governance Events ──────────────────────────────────────────────────────

pub fn emit_proposal_created(env: &Env, proposal_id: u32, proposer: Address, title: String) {
pub fn emit_proposal_created(env: &Env, proposal_id: u32, proposer: Address, title: Symbol) {
env.events().publish(
(symbol_short!("prop_cre"), proposal_id),
(proposer, title, env.ledger().timestamp()),
);
}

pub fn emit_vote_cast(env: &Env, proposal_id: u32, voter: Address, vote_type: String, weight: u32) {
pub fn emit_vote_cast(env: &Env, proposal_id: u32, voter: Address, vote_type: Symbol, weight: u32) {
env.events().publish(
(symbol_short!("vote_cast"), (proposal_id, voter)),
(vote_type, weight, env.ledger().timestamp()),
Expand Down Expand Up @@ -161,7 +151,7 @@ pub fn emit_multisig_proposed(
env: &Env,
proposal_id: u32,
proposer: Address,
description: String,
description: Symbol,
threshold: u32,
) {
env.events().publish(
Expand Down Expand Up @@ -199,14 +189,14 @@ pub fn emit_multisig_cancelled(env: &Env, proposal_id: u32, caller: Address) {

// ── Access Control Events ──────────────────────────────────────────────────

pub fn emit_role_granted(env: &Env, user: Address, role: String, granted_by: Address) {
pub fn emit_role_granted(env: &Env, user: Address, role: Symbol, granted_by: Address) {
env.events().publish(
(symbol_short!("role_gr"), user),
(role, granted_by, env.ledger().timestamp()),
);
}

pub fn emit_role_revoked(env: &Env, user: Address, role: String, revoked_by: Address) {
pub fn emit_role_revoked(env: &Env, user: Address, role: Symbol, revoked_by: Address) {
env.events().publish(
(symbol_short!("role_rev"), user),
(role, revoked_by, env.ledger().timestamp()),
Expand All @@ -215,14 +205,14 @@ pub fn emit_role_revoked(env: &Env, user: Address, role: String, revoked_by: Add

// ── Pause Events ───────────────────────────────────────────────────────────

pub fn emit_paused(env: &Env, action: String, admin: Address) {
pub fn emit_paused(env: &Env, action: Symbol, admin: Address) {
env.events().publish(
(symbol_short!("paused"), action),
(admin, env.ledger().timestamp()),
);
}

pub fn emit_unpaused(env: &Env, action: String, admin: Address) {
pub fn emit_unpaused(env: &Env, action: Symbol, admin: Address) {
env.events().publish(
(symbol_short!("unpaused"), action),
(admin, env.ledger().timestamp()),
Expand All @@ -247,15 +237,13 @@ pub fn emit_tokens_released(env: &Env, task_id: u32, to: Address, amount: i128)

// ── Platform Events ────────────────────────────────────────────────────────

/// Emitted when platform fee basis points are updated by an admin.
pub fn emit_fee_updated(env: &Env, old_fee_bps: u32, new_fee_bps: u32, updated_by: Address) {
env.events().publish(
(symbol_short!("fee_upd"), updated_by),
(old_fee_bps, new_fee_bps, env.ledger().timestamp()),
);
}

/// Emitted when the contract is first initialized.
pub fn emit_contract_initialized(env: &Env, admin: Address, fee_bps: u32) {
env.events().publish(
(symbol_short!("init"), admin),
Expand All @@ -265,7 +253,6 @@ pub fn emit_contract_initialized(env: &Env, admin: Address, fee_bps: u32) {

// ── Swap Router Events ─────────────────────────────────────────────────────

/// Emitted when the multi-asset swap router config is set/updated.
pub fn emit_router_configured(
env: &Env,
admin: Address,
Expand Down Expand Up @@ -298,8 +285,6 @@ pub fn emit_stablecoin_removed(env: &Env, admin: Address, stablecoin: Address) {
);
}

/// Emitted when an incoming non-standard token is successfully routed and
/// converted into an approved vault stablecoin.
pub fn emit_swap_executed(
env: &Env,
sender: Address,
Expand All @@ -320,14 +305,12 @@ pub fn emit_swap_executed(
);
}

/// Emitted when a conversion is rejected before any funds are pulled from the
/// sender, e.g. because the route couldn't be resolved or has no oracle price.
pub fn emit_swap_refunded(
env: &Env,
sender: Address,
token_in: Address,
amount: i128,
reason: String,
reason: Symbol,
) {
env.events().publish(
(symbol_short!("swap_ref"), sender),
Expand Down
Loading
Loading