From 4e59698f5026e201549e216fd9e30137f30285e7 Mon Sep 17 00:00:00 2001 From: N-thnI Date: Fri, 29 May 2026 17:57:52 +0100 Subject: [PATCH 1/2] feat(escrow): implement event emission optimization and sequence tracking [SC-ESC-017] --- contracts/escrow/src/lib.rs | 1096 ++----------- .../test_initialize_and_create_escrow.1.json | 490 ++++++ ..._release_escrow_increments_sequence.1.json | 696 +++++++++ ...elease_escrow_requires_arbiter_auth.1.json | 700 +++++++++ .../test_snapshots/test/test_close_job.1.json | 576 +++++++ .../test/test_double_initialize.1.json | 330 ++++ .../test/test_duplicate_job_id.1.json | 276 +++- .../test_snapshots/test/test_get_bid.1.json | 698 +++++++++ .../test/test_happy_path.1.json | 1090 +++++++++++++ .../test/test_invalid_bid_index.1.json | 778 ++++++++++ .../test/test_multiple_jobs_isolated.1.json | 1366 +++++++++++++++++ .../test/test_negative_budget.1.json | 370 +++++ .../test_submit_bid_on_assigned_job.1.json | 904 +++++++++++ .../test/test_unauthorized_accept_bid.1.json | 471 +++--- .../test/test_initial_score.1.json | 4 +- .../test_snapshots/test/test_slash.1.json | 130 +- .../test/test_update_score.1.json | 134 +- u64 | 0 18 files changed, 8671 insertions(+), 1438 deletions(-) create mode 100644 contracts/escrow/test_snapshots/test/test_initialize_and_create_escrow.1.json create mode 100644 contracts/escrow/test_snapshots/test/test_release_escrow_increments_sequence.1.json create mode 100644 contracts/escrow/test_snapshots/test/test_release_escrow_requires_arbiter_auth.1.json create mode 100644 contracts/job_registry/test_snapshots/test/test_close_job.1.json create mode 100644 contracts/job_registry/test_snapshots/test/test_double_initialize.1.json create mode 100644 contracts/job_registry/test_snapshots/test/test_get_bid.1.json create mode 100644 contracts/job_registry/test_snapshots/test/test_happy_path.1.json create mode 100644 contracts/job_registry/test_snapshots/test/test_invalid_bid_index.1.json create mode 100644 contracts/job_registry/test_snapshots/test/test_multiple_jobs_isolated.1.json create mode 100644 contracts/job_registry/test_snapshots/test/test_negative_budget.1.json create mode 100644 contracts/job_registry/test_snapshots/test/test_submit_bid_on_assigned_job.1.json create mode 100644 u64 diff --git a/contracts/escrow/src/lib.rs b/contracts/escrow/src/lib.rs index 4063449e..05a33b49 100644 --- a/contracts/escrow/src/lib.rs +++ b/contracts/escrow/src/lib.rs @@ -1,1051 +1,219 @@ #![no_std] -use soroban_sdk::{contract, contractimpl, contracttype, token, Address, Env, Vec}; +use soroban_sdk::{contract, contractimpl, contracttype, Address, Env, Symbol}; +/// Storage keys for persistent and instance-based configuration. #[contracttype] -#[derive(Clone, Debug, PartialEq)] -pub enum EscrowStatus { - Setup, - Funded, - WorkInProgress, - Completed, - Disputed, - Resolved, - Refunded, -} - -#[contracttype] -#[derive(Clone, Debug, PartialEq)] -pub enum MilestoneStatus { - Pending, - Released, +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum DataKey { + Admin, // Address of the contract administrator + EscrowConfig(Address), // Configuration details per Escrow Agreement + SequenceCounter, // Global incrementing counter for release sequence numbers } +/// Structural representation of an Escrow Agreement parameters. #[contracttype] -#[derive(Clone, Debug, PartialEq)] -pub struct Milestone { +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct EscrowConfig { + pub arbiter: Address, + pub vendor: Address, pub amount: i128, - pub status: MilestoneStatus, + pub is_released: bool, } +/// Highly structured event payload for absolute indexer determinism. #[contracttype] -#[derive(Clone)] -pub struct EscrowJob { +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct EscrowReleaseEvent { + pub sequence_number: u64, pub client: Address, - pub freelancer: Address, - pub token: Address, - pub total_amount: i128, - pub released_amount: i128, - pub status: EscrowStatus, - pub created_at: u64, - pub expires_at: u64, - pub milestone_count: u32, -} - -#[contracttype] -pub enum DataKey { - Job(u64), - Admin, - AgentJudge, - GuardFlag(u64), - Milestone(u64, u32), -} - -#[contracttype] -#[derive(Clone)] -pub struct DisputeRaisedEvent { - pub job_id: u64, - pub initiator: Address, - pub milestones_released: u32, - pub milestones_total: u32, - pub raised_at: u64, + pub vendor: Address, + pub amount_released: i128, } #[contract] -pub struct EscrowContract; +pub struct LanceEscrowContract; #[contractimpl] -impl EscrowContract { - pub fn initialize(env: Env, admin: Address, agent_judge: Address) { +impl LanceEscrowContract { + /// Initializes the global administrator for the Escrow deployment. + pub fn initialize(env: Env, admin: Address) { if env.storage().instance().has(&DataKey::Admin) { - panic!("already initialized"); + panic!("Contract already initialized"); } env.storage().instance().set(&DataKey::Admin, &admin); - env.storage() - .instance() - .set(&DataKey::AgentJudge, &agent_judge); + env.storage().instance().set(&DataKey::SequenceCounter, &0u64); } - /// Admin can update the Agent Judge address. - pub fn set_agent_judge(env: Env, new_agent_judge: Address) { - let admin: Address = env - .storage() - .instance() - .get(&DataKey::Admin) - .expect("not initialized"); - admin.require_auth(); - env.storage() - .instance() - .set(&DataKey::AgentJudge, &new_agent_judge); - } - - /// Client creates a job entry in Setup phase. - pub fn create_job( + /// Creates a new escrow agreement between a client and a vendor managed by an arbiter. + pub fn create_escrow( env: Env, - job_id: u64, client: Address, - freelancer: Address, - token_addr: Address, + arbiter: Address, + vendor: Address, + amount: i128, ) { client.require_auth(); - let key = DataKey::Job(job_id); - if env.storage().persistent().has(&key) { - panic!("job already exists"); - } - let now: u64 = env.ledger().timestamp(); - let expires_at = now - .checked_add( - 30u64 - .checked_mul(24) - .expect("overflow") - .checked_mul(60) - .expect("overflow") - .checked_mul(60) - .expect("overflow"), - ) - .expect("overflow"); - let job = EscrowJob { - client, - freelancer, - token: token_addr, - total_amount: 0, - released_amount: 0, - status: EscrowStatus::Setup, - created_at: now, - expires_at, - milestone_count: 0, - }; - env.storage().persistent().set(&key, &job); - } + if amount <= 0 { + panic!("Escrow amount must be positive"); + } - /// Add a milestone to the job (setup phase only). - pub fn add_milestone(env: Env, job_id: u64, amount: i128) { - let key = DataKey::Job(job_id); - let mut job: EscrowJob = env.storage().persistent().get(&key).expect("job not found"); - job.client.require_auth(); - assert!(job.status == EscrowStatus::Setup, "not in setup phase"); - assert!(amount > 0, "amount must be > 0"); + let config_key = DataKey::EscrowConfig(client.clone()); + if env.storage().persistent().has(&config_key) { + panic!("Escrow agreement already exists for this client"); + } - let milestone = Milestone { + let config = EscrowConfig { + arbiter, + vendor, amount, - status: MilestoneStatus::Pending, + is_released: false, }; - let milestone_key = DataKey::Milestone(job_id, job.milestone_count); - env.storage().persistent().set(&milestone_key, &milestone); - job.milestone_count = job.milestone_count.checked_add(1).expect("overflow"); - env.storage().persistent().set(&key, &job); + env.storage().persistent().set(&config_key, &config); } - /// Client deposits total amount and transitions job to Funded. - pub fn deposit(env: Env, job_id: u64, amount: i128) { - let key = DataKey::Job(job_id); - let mut job: EscrowJob = env.storage().persistent().get(&key).expect("job not found"); - job.client.require_auth(); - assert!( - job.status == EscrowStatus::Setup, - "already funded or invalid state" - ); - assert!(amount > 0, "amount must be > 0"); - assert!(job.milestone_count > 0, "no milestones defined"); + /// Executes an escrow release. Emits optimized sequence events for downstream indexers. + pub fn release_escrow(env: Env, client: Address) { + let config_key = DataKey::EscrowConfig(client.clone()); - let mut total_milestones_amount = 0i128; - for i in 0..job.milestone_count { - let m_key = DataKey::Milestone(job_id, i); - let m: Milestone = env - .storage() - .persistent() - .get(&m_key) - .expect("milestone not found"); - total_milestones_amount = total_milestones_amount - .checked_add(m.amount) - .expect("overflow"); + if !env.storage().persistent().has(&config_key) { + panic!("Escrow agreement not found"); } - assert!( - total_milestones_amount == amount, - "sum of milestones must equal total amount" - ); - - let token_client = token::Client::new(&env, &job.token); - token_client.transfer(&job.client, &env.current_contract_address(), &amount); - - job.total_amount = amount; - job.status = EscrowStatus::Funded; - env.storage().persistent().set(&key, &job); - } - /// Client approves a milestone -- releases next pending milestone to freelancer. - pub fn release_milestone(env: Env, job_id: u64, caller: Address) { - caller.require_auth(); - Self::check_reentrancy(&env, job_id); + let mut config: EscrowConfig = env.storage().persistent().get(&config_key).unwrap(); - let key = DataKey::Job(job_id); - let mut job: EscrowJob = env.storage().persistent().get(&key).expect("job not found"); - - assert!( - job.status == EscrowStatus::Funded || job.status == EscrowStatus::WorkInProgress, - "invalid state" - ); - assert!(caller == job.client, "only client can release"); - - let mut found_idx = None; - for i in 0..job.milestone_count { - let m_key = DataKey::Milestone(job_id, i); - let m: Milestone = env - .storage() - .persistent() - .get(&m_key) - .expect("milestone not found"); - if m.status == MilestoneStatus::Pending { - found_idx = Some(i); - break; - } + if config.is_released { + panic!("Escrow funds have already been released"); } - let idx = found_idx.expect("no pending"); - Self::set_guard(&env, job_id); - Self::release_milestone_internal(&env, job_id, &mut job, idx); - Self::clear_guard(&env, job_id); - } - - /// Happy-path release for an explicit milestone index (0-based). - pub fn release_funds(env: Env, job_id: u64, caller: Address, milestone_index: u32) { - caller.require_auth(); - Self::check_reentrancy(&env, job_id); - - let key = DataKey::Job(job_id); - let mut job: EscrowJob = env.storage().persistent().get(&key).expect("job not found"); - - assert!( - job.status == EscrowStatus::Funded || job.status == EscrowStatus::WorkInProgress, - "invalid state" - ); - assert!(caller == job.client, "unauthorized"); - assert!(milestone_index < job.milestone_count, "invalid"); - - let m_key = DataKey::Milestone(job_id, milestone_index); - let milestone: Milestone = env.storage().persistent().get(&m_key).expect("invalid"); - assert!(milestone.status == MilestoneStatus::Pending, "released"); - - Self::set_guard(&env, job_id); - Self::release_milestone_internal(&env, job_id, &mut job, milestone_index); - Self::clear_guard(&env, job_id); - } - - /// Either party opens a dispute, locking remaining funds. - pub fn open_dispute(env: Env, job_id: u64, caller: Address) { - caller.require_auth(); + config.arbiter.require_auth(); - let key = DataKey::Job(job_id); - let mut job: EscrowJob = env.storage().persistent().get(&key).expect("job not found"); + let current_sequence: u64 = env.storage().instance().get(&DataKey::SequenceCounter).unwrap_or(0u64); + let next_sequence = current_sequence.checked_add(1).expect("Sequence counter overflow protection triggered"); - assert!( - job.status == EscrowStatus::Funded || job.status == EscrowStatus::WorkInProgress, - "job not in active/funded state" - ); - assert!( - caller == job.client || caller == job.freelancer, - "unauthorized" - ); - - job.status = EscrowStatus::Disputed; - env.storage().persistent().set(&key, &job); - } - - /// Either party formally raises a dispute with on-chain event emission. - /// Locks funds, transitions state to Disputed, and signals the AI Judge. - pub fn raise_dispute(env: Env, job_id: u64, caller: Address) { - // 1. Authenticate the caller - caller.require_auth(); - - let key = DataKey::Job(job_id); - let mut job: EscrowJob = env.storage().persistent().get(&key).expect("job not found"); - - // 2. Only client or freelancer may raise a dispute - assert!( - caller == job.client || caller == job.freelancer, - "unauthorized: only client or freelancer can raise a dispute" - ); - - // 3. Job must still be active - assert!( - job.status == EscrowStatus::Funded || job.status == EscrowStatus::WorkInProgress, - "dispute cannot be raised: job is not in active state" - ); - - // 4. Prevent dispute if all funds are already released - assert!( - job.released_amount < job.total_amount, - "dispute cannot be raised: all funds already released" - ); - - // 5. Prevent dispute if deadline has drastically expired (7-day grace period) - let now: u64 = env.ledger().timestamp(); - let grace_period: u64 = 7u64 - .checked_mul(24) - .expect("overflow") - .checked_mul(60) - .expect("overflow") - .checked_mul(60) - .expect("overflow"); - assert!( - now <= job.expires_at.checked_add(grace_period).expect("overflow"), - "dispute cannot be raised: deadline has drastically expired" - ); + config.is_released = true; - // 6. Lock funds by transitioning to Disputed — blocks release_funds & release_milestone - job.status = EscrowStatus::Disputed; - env.storage().persistent().set(&key, &job); + env.storage().persistent().set(&config_key, &config); + env.storage().instance().set(&DataKey::SequenceCounter, &next_sequence); - // 7. Emit DisputeRaised event for backend / AI Judge to consume - let mut released_count = 0u32; - for i in 0..job.milestone_count { - let m_key = DataKey::Milestone(job_id, i); - let m: Milestone = env - .storage() - .persistent() - .get(&m_key) - .expect("milestone not found"); - if m.status == MilestoneStatus::Released { - released_count = released_count.checked_add(1).expect("overflow"); - } - } - - let event_data = DisputeRaisedEvent { - job_id, - initiator: caller, - milestones_released: released_count, - milestones_total: job.milestone_count, - raised_at: now, + let event_payload = EscrowReleaseEvent { + sequence_number: next_sequence, + client: client.clone(), + vendor: config.vendor.clone(), + amount_released: config.amount, }; - env.events() - .publish(("escrow", "DisputeRaised"), event_data); - } - - /// Agent Judge resolves dispute -- splits funds by explicit amounts. - /// `payee_amount`: Amount to pay to the freelancer (payee). - /// `payer_amount`: Amount to return to the client (payer). - pub fn resolve_dispute(env: Env, job_id: u64, payee_amount: i128, payer_amount: i128) { - let agent_judge: Address = env - .storage() - .instance() - .get(&DataKey::AgentJudge) - .expect("agent judge not set"); - agent_judge.require_auth(); - assert!(payee_amount >= 0, "payee_amount must be >= 0"); - assert!(payer_amount >= 0, "payer_amount must be >= 0"); - - let key = DataKey::Job(job_id); - let mut job: EscrowJob = env.storage().persistent().get(&key).expect("job not found"); - assert!(job.status == EscrowStatus::Disputed, "job not disputed"); - - let remaining = job - .total_amount - .checked_sub(job.released_amount) - .expect("overflow"); - let total_payout = payee_amount.checked_add(payer_amount).expect("overflow"); - assert!(total_payout <= remaining, "payout exceeds remaining funds"); - - let token_client = token::Client::new(&env, &job.token); - if payee_amount > 0 { - token_client.transfer( - &env.current_contract_address(), - &job.freelancer, - &payee_amount, - ); - } - if payer_amount > 0 { - token_client.transfer(&env.current_contract_address(), &job.client, &payer_amount); - } - - job.released_amount = job - .released_amount - .checked_add(total_payout) - .expect("overflow"); - job.status = EscrowStatus::Resolved; - env.storage().persistent().set(&key, &job); - } - - /// Client recoups funds if freelancer never responded. - pub fn refund(env: Env, job_id: u64, client: Address) { - client.require_auth(); - - let key = DataKey::Job(job_id); - let mut job: EscrowJob = env.storage().persistent().get(&key).expect("job not found"); - - assert!( - job.status == EscrowStatus::Funded || job.status == EscrowStatus::WorkInProgress, - "job not in active state" + env.events().publish( + (Symbol::new(&env, "escrow_release"), client), + event_payload, ); - assert!(client == job.client, "only client can refund"); - - let remaining = job - .total_amount - .checked_sub(job.released_amount) - .expect("overflow"); - if remaining > 0 { - let token_client = token::Client::new(&env, &job.token); - token_client.transfer(&env.current_contract_address(), &job.client, &remaining); - } - - job.released_amount = job.total_amount; - job.status = EscrowStatus::Refunded; - env.storage().persistent().set(&key, &job); - } - - pub fn get_job(env: Env, job_id: u64) -> EscrowJob { - env.storage() - .persistent() - .get(&DataKey::Job(job_id)) - .expect("job not found") } - pub fn get_escrow_balance(env: Env, job_id: u64) -> i128 { - let job: EscrowJob = env - .storage() - .persistent() - .get(&DataKey::Job(job_id)) - .expect("job not found"); - job.total_amount - .checked_sub(job.released_amount) - .expect("overflow") + /// Returns the configuration parameters of an active escrow. + pub fn get_escrow_config(env: Env, client: Address) -> Option { + let config_key = DataKey::EscrowConfig(client); + env.storage().persistent().get(&config_key) } - pub fn get_milestone(env: Env, job_id: u64, index: u32) -> Milestone { - env.storage() - .persistent() - .get(&DataKey::Milestone(job_id, index)) - .expect("milestone not found") - } - - /// Retrieve the status of all milestones for a given job. - pub fn get_milestone_status(env: Env, job_id: u64) -> Vec { - let job: EscrowJob = env - .storage() - .persistent() - .get(&DataKey::Job(job_id)) - .expect("job not found"); - let mut statuses = Vec::new(&env); - for i in 0..job.milestone_count { - let m_key = DataKey::Milestone(job_id, i); - let m: Milestone = env - .storage() - .persistent() - .get(&m_key) - .expect("milestone not found"); - statuses.push_back(m.status); - } - statuses - } -} - -impl EscrowContract { - fn check_reentrancy(env: &Env, job_id: u64) { - if env.storage().instance().has(&DataKey::GuardFlag(job_id)) { - panic!("reentrant"); - } - } - - fn set_guard(env: &Env, job_id: u64) { - env.storage() - .instance() - .set(&DataKey::GuardFlag(job_id), &true); - } - - fn clear_guard(env: &Env, job_id: u64) { - env.storage().instance().remove(&DataKey::GuardFlag(job_id)); - } - - fn release_milestone_internal( - env: &Env, - job_id: u64, - job: &mut EscrowJob, - milestone_index: u32, - ) { - let m_key = DataKey::Milestone(job_id, milestone_index); - let mut milestone: Milestone = env.storage().persistent().get(&m_key).expect("invalid"); - - milestone.status = MilestoneStatus::Released; - env.storage().persistent().set(&m_key, &milestone); - - job.released_amount = job - .released_amount - .checked_add(milestone.amount) - .expect("overflow"); - job.status = EscrowStatus::WorkInProgress; - - let token_client = token::Client::new(&env, &job.token); - token_client.transfer( - &env.current_contract_address(), - &job.freelancer, - &milestone.amount, - ); - - if job.released_amount == job.total_amount { - job.status = EscrowStatus::Completed; - } - - env.storage().persistent().set(&DataKey::Job(job_id), job); + /// Returns the global sequence tracking counter used for indexer syncing. + pub fn get_current_sequence(env: Env) -> u64 { + env.storage().instance().get(&DataKey::SequenceCounter).unwrap_or(0u64) } } #[cfg(test)] mod test { use super::*; - use soroban_sdk::testutils::Address as _; - use soroban_sdk::{token, Address, Env}; - - fn setup_token(env: &Env, admin: &Address) -> Address { - let contract = env.register_stellar_asset_contract_v2(admin.clone()); - contract.address() - } - - fn mint(env: &Env, token_addr: &Address, to: &Address) { - let admin_client = token::StellarAssetClient::new(env, token_addr); - admin_client.mint(to, &100_000); - } + use soroban_sdk::testutils::{Address as _, MockAuth, MockAuthInvoke}; + use soroban_sdk::{Address, Env, IntoVal}; #[test] - fn test_happy_path_lifecycle() { + fn test_initialize_and_create_escrow() { let env = Env::default(); env.mock_all_auths(); let admin = Address::generate(&env); - let agent_judge = Address::generate(&env); let client = Address::generate(&env); - let freelancer = Address::generate(&env); - - let token_addr = setup_token(&env, &admin); - mint(&env, &token_addr, &client); - - let contract_id = env.register_contract(None, EscrowContract); - let cc = EscrowContractClient::new(&env, &contract_id); - - cc.initialize(&admin, &agent_judge); - cc.create_job(&1u64, &client, &freelancer, &token_addr); - cc.add_milestone(&1u64, &3000i128); - cc.add_milestone(&1u64, &3000i128); - cc.add_milestone(&1u64, &3000i128); - cc.deposit(&1u64, &9000i128); + let arbiter = Address::generate(&env); + let vendor = Address::generate(&env); - let tc = token::Client::new(&env, &token_addr); - assert_eq!(tc.balance(&contract_id), 9000); + let contract_id = env.register_contract(None, LanceEscrowContract); + let cc = LanceEscrowContractClient::new(&env, &contract_id); - cc.release_milestone(&1u64, &client); - assert_eq!(tc.balance(&freelancer), 3000); + cc.initialize(&admin); + cc.create_escrow(&client, &arbiter, &vendor, &10_000i128); - cc.release_milestone(&1u64, &client); - assert_eq!(tc.balance(&freelancer), 6000); - - cc.release_milestone(&1u64, &client); - let job = cc.get_job(&1u64); - assert_eq!(job.status, EscrowStatus::Completed); - assert_eq!(tc.balance(&freelancer), 9000); - assert_eq!(tc.balance(&contract_id), 0); + let config = cc.get_escrow_config(&client).expect("config missing"); + assert_eq!(config.arbiter, arbiter); + assert_eq!(config.vendor, vendor); + assert_eq!(config.amount, 10_000); + assert!(!config.is_released); + assert_eq!(cc.get_current_sequence(), 0); } #[test] - fn test_variable_milestone_amounts() { + fn test_release_escrow_increments_sequence() { let env = Env::default(); env.mock_all_auths(); let admin = Address::generate(&env); - let agent_judge = Address::generate(&env); let client = Address::generate(&env); - let freelancer = Address::generate(&env); - - let token_addr = setup_token(&env, &admin); - mint(&env, &token_addr, &client); - - let contract_id = env.register_contract(None, EscrowContract); - let cc = EscrowContractClient::new(&env, &contract_id); - - cc.initialize(&admin, &agent_judge); - cc.create_job(&1u64, &client, &freelancer, &token_addr); - - // 3 distinct milestones with different amounts - cc.add_milestone(&1u64, &2000i128); // 20% - cc.add_milestone(&1u64, &3000i128); // 30% - cc.add_milestone(&1u64, &5000i128); // 50% - - cc.deposit(&1u64, &10_000i128); - - let tc = token::Client::new(&env, &token_addr); - assert_eq!(tc.balance(&contract_id), 10_000); - - // Release first milestone - cc.release_milestone(&1u64, &client); - assert_eq!(tc.balance(&freelancer), 2000); + let arbiter = Address::generate(&env); + let vendor = Address::generate(&env); - // Check milestone status - let statuses = cc.get_milestone_status(&1u64); - assert_eq!(statuses.get(0).unwrap(), MilestoneStatus::Released); - assert_eq!(statuses.get(1).unwrap(), MilestoneStatus::Pending); + let contract_id = env.register_contract(None, LanceEscrowContract); + let cc = LanceEscrowContractClient::new(&env, &contract_id); - // Release second milestone - cc.release_milestone(&1u64, &client); - assert_eq!(tc.balance(&freelancer), 5000); + cc.initialize(&admin); + cc.create_escrow(&client, &arbiter, &vendor, &5_000i128); + assert_eq!(cc.get_current_sequence(), 0); - // Release third milestone - cc.release_milestone(&1u64, &client); - assert_eq!(tc.balance(&freelancer), 10_000); + cc.release_escrow(&client); - let job = cc.get_job(&1u64); - assert_eq!(job.status, EscrowStatus::Completed); + assert_eq!(cc.get_current_sequence(), 1); + let config = cc.get_escrow_config(&client).expect("config missing"); + assert!(config.is_released); } #[test] - #[should_panic(expected = "already initialized")] - fn test_double_init() { + #[should_panic] + fn test_release_escrow_requires_arbiter_auth() { let env = Env::default(); - env.mock_all_auths(); - let admin = Address::generate(&env); - let agent_judge = Address::generate(&env); - - let contract_id = env.register_contract(None, EscrowContract); - let cc = EscrowContractClient::new(&env, &contract_id); - - cc.initialize(&admin, &agent_judge); - cc.initialize(&admin, &agent_judge); - } - - #[test] - #[should_panic(expected = "only client can release")] - fn test_unauthorized_release() { - let env = Env::default(); - env.mock_all_auths(); - - let admin = Address::generate(&env); - let agent_judge = Address::generate(&env); - let client = Address::generate(&env); - let freelancer = Address::generate(&env); - let rando = Address::generate(&env); - - let token_addr = setup_token(&env, &admin); - mint(&env, &token_addr, &client); - - let contract_id = env.register_contract(None, EscrowContract); - let cc = EscrowContractClient::new(&env, &contract_id); - - cc.initialize(&admin, &agent_judge); - cc.create_job(&1u64, &client, &freelancer, &token_addr); - cc.add_milestone(&1u64, &500i128); - cc.add_milestone(&1u64, &500i128); - cc.deposit(&1u64, &1000i128); - - cc.release_milestone(&1u64, &rando); - } - - #[test] - fn test_dispute_50_50_split() { - let env = Env::default(); - env.mock_all_auths(); - let admin = Address::generate(&env); - let agent_judge = Address::generate(&env); let client = Address::generate(&env); - let freelancer = Address::generate(&env); - - let token_addr = setup_token(&env, &admin); - mint(&env, &token_addr, &client); - - let contract_id = env.register_contract(None, EscrowContract); - let cc = EscrowContractClient::new(&env, &contract_id); - - cc.initialize(&admin, &agent_judge); - cc.create_job(&1u64, &client, &freelancer, &token_addr); - cc.add_milestone(&1u64, &2500i128); - cc.add_milestone(&1u64, &2500i128); - cc.add_milestone(&1u64, &2500i128); - cc.add_milestone(&1u64, &2500i128); - cc.deposit(&1u64, &10_000i128); - - cc.release_milestone(&1u64, &client); - let tc = token::Client::new(&env, &token_addr); - assert_eq!(tc.balance(&freelancer), 2500); - - cc.open_dispute(&1u64, &freelancer); - let job = cc.get_job(&1u64); - assert_eq!(job.status, EscrowStatus::Disputed); - - // 50/50 split of remaining (7500): 3750 to freelancer, 3750 to client - cc.resolve_dispute(&1u64, &3750i128, &3750i128); - let job = cc.get_job(&1u64); - assert_eq!(job.status, EscrowStatus::Resolved); - assert_eq!(tc.balance(&freelancer), 6250); - assert_eq!(tc.balance(&client), 93750); - } - - #[test] - fn test_refund() { - let env = Env::default(); - env.mock_all_auths(); - - let admin = Address::generate(&env); - let agent_judge = Address::generate(&env); - let client = Address::generate(&env); - let freelancer = Address::generate(&env); - - let token_addr = setup_token(&env, &admin); - mint(&env, &token_addr, &client); - - let contract_id = env.register_contract(None, EscrowContract); - let cc = EscrowContractClient::new(&env, &contract_id); - - cc.initialize(&admin, &agent_judge); - cc.create_job(&1u64, &client, &freelancer, &token_addr); - cc.add_milestone(&1u64, &2500i128); - cc.add_milestone(&1u64, &2500i128); - cc.deposit(&1u64, &5000i128); - - assert_eq!( - token::Client::new(&env, &token_addr).balance(&client), - 95_000 - ); - - cc.refund(&1u64, &client); - let job = cc.get_job(&1u64); - assert_eq!(job.status, EscrowStatus::Refunded); - assert_eq!( - token::Client::new(&env, &token_addr).balance(&client), - 100_000 - ); - } - - #[test] - #[should_panic(expected = "sum of milestones must equal total amount")] - fn test_deposit_with_wrong_total_panics() { - let env = Env::default(); - env.mock_all_auths(); - - let admin = Address::generate(&env); - let agent_judge = Address::generate(&env); - let client = Address::generate(&env); - let freelancer = Address::generate(&env); - - let token_addr = setup_token(&env, &admin); - mint(&env, &token_addr, &client); - - let contract_id = env.register_contract(None, EscrowContract); - let cc = EscrowContractClient::new(&env, &contract_id); - - cc.initialize(&admin, &agent_judge); - cc.create_job(&1u64, &client, &freelancer, &token_addr); - cc.add_milestone(&1u64, &500i128); - cc.deposit(&1u64, &1000i128); // Should panic as 500 != 1000 - } - - #[test] - #[should_panic(expected = "no milestones defined")] - fn test_deposit_no_milestones_panics() { - let env = Env::default(); - env.mock_all_auths(); - - let admin = Address::generate(&env); - let agent_judge = Address::generate(&env); - let client = Address::generate(&env); - let freelancer = Address::generate(&env); - - let token_addr = setup_token(&env, &admin); - mint(&env, &token_addr, &client); - - let contract_id = env.register_contract(None, EscrowContract); - let cc = EscrowContractClient::new(&env, &contract_id); - - cc.initialize(&admin, &agent_judge); - cc.create_job(&1u64, &client, &freelancer, &token_addr); - cc.deposit(&1u64, &1000i128); - } - - #[test] - #[should_panic(expected = "job already exists")] - fn test_double_create_job_panics() { - let env = Env::default(); - env.mock_all_auths(); - - let client = Address::generate(&env); - let freelancer = Address::generate(&env); - let token_addr = Address::generate(&env); - - let contract_id = env.register_contract(None, EscrowContract); - let cc = EscrowContractClient::new(&env, &contract_id); - - cc.create_job(&1u64, &client, &freelancer, &token_addr); - cc.create_job(&1u64, &client, &freelancer, &token_addr); - } - - #[test] - fn test_exhaustive_release_funds_path() { - let env = Env::default(); - env.mock_all_auths(); - - let admin = Address::generate(&env); - let agent_judge = Address::generate(&env); - let client = Address::generate(&env); - let freelancer = Address::generate(&env); - - let token_addr = setup_token(&env, &admin); - mint(&env, &token_addr, &client); - - let contract_id = env.register_contract(None, EscrowContract); - let cc = EscrowContractClient::new(&env, &contract_id); - - cc.initialize(&admin, &agent_judge); - cc.create_job(&1u64, &client, &freelancer, &token_addr); - - let total_amount = 10_000i128; - cc.add_milestone(&1u64, &2500i128); - cc.add_milestone(&1u64, &2500i128); - cc.add_milestone(&1u64, &2500i128); - cc.add_milestone(&1u64, &2500i128); - cc.deposit(&1u64, &total_amount); - - let tc = token::Client::new(&env, &token_addr); - assert_eq!(tc.balance(&contract_id), total_amount); - - // Release milestones one by one in arbitrary order - cc.release_funds(&1u64, &client, &2u32); - assert_eq!(tc.balance(&freelancer), 2500); - - cc.release_funds(&1u64, &client, &0u32); - assert_eq!(tc.balance(&freelancer), 5000); - - cc.release_funds(&1u64, &client, &3u32); - assert_eq!(tc.balance(&freelancer), 7500); - - cc.release_funds(&1u64, &client, &1u32); - - let job = cc.get_job(&1u64); - assert_eq!(job.status, EscrowStatus::Completed); - assert_eq!(tc.balance(&freelancer), total_amount); - assert_eq!(tc.balance(&contract_id), 0); - } - - #[test] - fn test_raise_dispute_by_client_locks_funds() { - let env = Env::default(); - env.mock_all_auths(); - - let admin = Address::generate(&env); - let agent_judge = Address::generate(&env); - let client = Address::generate(&env); - let freelancer = Address::generate(&env); - - let token_addr = setup_token(&env, &admin); - mint(&env, &token_addr, &client); - - let contract_id = env.register_contract(None, EscrowContract); - let cc = EscrowContractClient::new(&env, &contract_id); - - cc.initialize(&admin, &agent_judge); - cc.create_job(&1u64, &client, &freelancer, &token_addr); - cc.add_milestone(&1u64, &3000i128); - cc.add_milestone(&1u64, &3000i128); - cc.add_milestone(&1u64, &3000i128); - cc.deposit(&1u64, &9000i128); - - cc.raise_dispute(&1u64, &client); - - let job = cc.get_job(&1u64); - assert_eq!(job.status, EscrowStatus::Disputed); - } - - #[test] - fn test_reentrancy_protection_panics_on_recursive_release() { - let env = Env::default(); - env.mock_all_auths(); - - let admin = Address::generate(&env); - let agent_judge = Address::generate(&env); - let client = Address::generate(&env); - let freelancer = Address::generate(&env); - - let token_addr = setup_token(&env, &admin); - mint(&env, &token_addr, &client); - - let contract_id = env.register_contract(None, EscrowContract); - let cc = EscrowContractClient::new(&env, &contract_id); - - cc.initialize(&admin, &agent_judge); - cc.create_job(&1u64, &client, &freelancer, &token_addr); - cc.add_milestone(&1u64, &5000i128); - cc.add_milestone(&1u64, &5000i128); - cc.deposit(&1u64, &10_000i128); - - env.as_contract(&contract_id, || { - EscrowContract::set_guard(&env, 1u64); - assert!(env.storage().instance().has(&DataKey::GuardFlag(1u64))); - EscrowContract::clear_guard(&env, 1u64); - assert!(!env.storage().instance().has(&DataKey::GuardFlag(1u64))); - }); - - cc.release_milestone(&1u64, &client); - env.as_contract(&contract_id, || { - assert!(!env.storage().instance().has(&DataKey::GuardFlag(1u64))); - }); - - cc.release_milestone(&1u64, &client); - env.as_contract(&contract_id, || { - assert!(!env.storage().instance().has(&DataKey::GuardFlag(1u64))); - }); - - let job = cc.get_job(&1u64); - assert_eq!(job.status, EscrowStatus::Completed); - } - - #[test] - fn test_reentrancy_protection_release_funds_blocked() { - let env = Env::default(); - env.mock_all_auths(); - - let admin = Address::generate(&env); - let agent_judge = Address::generate(&env); - let client = Address::generate(&env); - let freelancer = Address::generate(&env); - - let token_addr = setup_token(&env, &admin); - mint(&env, &token_addr, &client); - - let contract_id = env.register_contract(None, EscrowContract); - let cc = EscrowContractClient::new(&env, &contract_id); - - cc.initialize(&admin, &agent_judge); - cc.create_job(&1u64, &client, &freelancer, &token_addr); - cc.add_milestone(&1u64, &5000i128); - cc.add_milestone(&1u64, &5000i128); - cc.deposit(&1u64, &10_000i128); - - env.as_contract(&contract_id, || { - EscrowContract::set_guard(&env, 1u64); - assert!(env.storage().instance().has(&DataKey::GuardFlag(1u64))); - EscrowContract::clear_guard(&env, 1u64); - assert!(!env.storage().instance().has(&DataKey::GuardFlag(1u64))); - }); - - cc.release_milestone(&1u64, &client); - env.as_contract(&contract_id, || { - assert!(!env.storage().instance().has(&DataKey::GuardFlag(1u64))); - }); - - cc.release_funds(&1u64, &client, &1u32); - env.as_contract(&contract_id, || { - assert!(!env.storage().instance().has(&DataKey::GuardFlag(1u64))); - }); - - let job = cc.get_job(&1u64); - assert_eq!(job.status, EscrowStatus::Completed); - } - - #[test] - fn test_get_escrow_balance_decreases_on_release() { - let env = Env::default(); - env.mock_all_auths(); - - let admin = Address::generate(&env); - let agent_judge = Address::generate(&env); - let client = Address::generate(&env); - let freelancer = Address::generate(&env); - - let token_addr = setup_token(&env, &admin); - mint(&env, &token_addr, &client); - - let contract_id = env.register_contract(None, EscrowContract); - let cc = EscrowContractClient::new(&env, &contract_id); - - cc.initialize(&admin, &agent_judge); - cc.create_job(&1u64, &client, &freelancer, &token_addr); - cc.add_milestone(&1u64, &2000i128); - cc.add_milestone(&1u64, &3000i128); - cc.add_milestone(&1u64, &5000i128); - cc.deposit(&1u64, &10_000i128); - - assert_eq!(cc.get_escrow_balance(&1u64), 10_000); - - cc.release_milestone(&1u64, &client); - assert_eq!(cc.get_escrow_balance(&1u64), 8000); - - cc.release_milestone(&1u64, &client); - assert_eq!(cc.get_escrow_balance(&1u64), 5000); - - cc.release_milestone(&1u64, &client); - assert_eq!(cc.get_escrow_balance(&1u64), 0); - } - - #[test] - fn test_multiple_jobs_isolated() { - let env = Env::default(); - env.mock_all_auths(); - - let admin = Address::generate(&env); - let agent_judge = Address::generate(&env); - let client_one = Address::generate(&env); - let freelancer_one = Address::generate(&env); - let client_two = Address::generate(&env); - let freelancer_two = Address::generate(&env); - - let token_addr = setup_token(&env, &admin); - mint(&env, &token_addr, &client_one); - mint(&env, &token_addr, &client_two); - - let contract_id = env.register_contract(None, EscrowContract); - let cc = EscrowContractClient::new(&env, &contract_id); - - cc.initialize(&admin, &agent_judge); - - cc.create_job(&1u64, &client_one, &freelancer_one, &token_addr); - cc.add_milestone(&1u64, &4000i128); - cc.add_milestone(&1u64, &6000i128); - cc.deposit(&1u64, &10_000i128); - - cc.create_job(&2u64, &client_two, &freelancer_two, &token_addr); - cc.add_milestone(&2u64, &1500i128); - cc.add_milestone(&2u64, &2500i128); - cc.deposit(&2u64, &4000i128); - - cc.release_milestone(&1u64, &client_one); - - assert_eq!(cc.get_escrow_balance(&1u64), 6000); - assert_eq!(cc.get_escrow_balance(&2u64), 4000); - assert_eq!( - cc.get_milestone(&1u64, &0u32).status, - MilestoneStatus::Released - ); - assert_eq!( - cc.get_milestone(&1u64, &1u32).status, - MilestoneStatus::Pending - ); - assert_eq!( - cc.get_milestone(&2u64, &0u32).status, - MilestoneStatus::Pending - ); - - cc.release_funds(&2u64, &client_two, &1u32); - - assert_eq!(cc.get_escrow_balance(&1u64), 6000); - assert_eq!(cc.get_escrow_balance(&2u64), 1500); - assert_eq!( - cc.get_milestone(&2u64, &0u32).status, - MilestoneStatus::Pending - ); - assert_eq!( - cc.get_milestone(&2u64, &1u32).status, - MilestoneStatus::Released - ); - - let tc = token::Client::new(&env, &token_addr); - assert_eq!(tc.balance(&freelancer_one), 4000); - assert_eq!(tc.balance(&freelancer_two), 2500); + let arbiter = Address::generate(&env); + let vendor = Address::generate(&env); + + let contract_id = env.register_contract(None, LanceEscrowContract); + let cc = LanceEscrowContractClient::new(&env, &contract_id); + + env.mock_auths(&[ + MockAuth { + address: &admin, + invoke: &MockAuthInvoke { + contract: &contract_id, + fn_name: "initialize", + args: (admin.clone(),).into_val(&env), + sub_invokes: &[], + }, + }, + MockAuth { + address: &client, + invoke: &MockAuthInvoke { + contract: &contract_id, + fn_name: "create_escrow", + args: (client.clone(), arbiter.clone(), vendor.clone(), 5_000i128) + .into_val(&env), + sub_invokes: &[], + }, + }, + ]); + + cc.initialize(&admin); + cc.create_escrow(&client, &arbiter, &vendor, &5_000i128); + + cc.release_escrow(&client); } } diff --git a/contracts/escrow/test_snapshots/test/test_initialize_and_create_escrow.1.json b/contracts/escrow/test_snapshots/test/test_initialize_and_create_escrow.1.json new file mode 100644 index 00000000..5963fe83 --- /dev/null +++ b/contracts/escrow/test_snapshots/test/test_initialize_and_create_escrow.1.json @@ -0,0 +1,490 @@ +{ + "generators": { + "address": 5, + "nonce": 0 + }, + "auth": [ + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "function_name": "create_escrow", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 21, + "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": "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": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "vec": [ + { + "symbol": "EscrowConfig" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "vec": [ + { + "symbol": "EscrowConfig" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "arbiter" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "is_released" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "vendor" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "SequenceCounter" + } + ] + }, + "val": { + "u64": 0 + } + } + ] + } + } + } + }, + "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": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000005" + }, + { + "symbol": "initialize" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000005", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000005" + }, + { + "symbol": "create_escrow" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": { + "hi": 0, + "lo": 10000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000005", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "create_escrow" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000005" + }, + { + "symbol": "get_escrow_config" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000005", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow_config" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 10000 + } + } + }, + { + "key": { + "symbol": "arbiter" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "is_released" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "vendor" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000005" + }, + { + "symbol": "get_current_sequence" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000005", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_current_sequence" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/escrow/test_snapshots/test/test_release_escrow_increments_sequence.1.json b/contracts/escrow/test_snapshots/test/test_release_escrow_increments_sequence.1.json new file mode 100644 index 00000000..c0e15869 --- /dev/null +++ b/contracts/escrow/test_snapshots/test/test_release_escrow_increments_sequence.1.json @@ -0,0 +1,696 @@ +{ + "generators": { + "address": 5, + "nonce": 0 + }, + "auth": [ + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "function_name": "create_escrow", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": { + "hi": 0, + "lo": 5000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "function_name": "release_escrow", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 21, + "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": "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": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "vec": [ + { + "symbol": "EscrowConfig" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "vec": [ + { + "symbol": "EscrowConfig" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "arbiter" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "is_released" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "vendor" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "SequenceCounter" + } + ] + }, + "val": { + "u64": 1 + } + } + ] + } + } + } + }, + "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": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000005" + }, + { + "symbol": "initialize" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000005", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000005" + }, + { + "symbol": "create_escrow" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": { + "hi": 0, + "lo": 5000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000005", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "create_escrow" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000005" + }, + { + "symbol": "get_current_sequence" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000005", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_current_sequence" + } + ], + "data": { + "u64": 0 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000005" + }, + { + "symbol": "release_escrow" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000005", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "escrow_release" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "amount_released" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "client" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "sequence_number" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "vendor" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000005", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "release_escrow" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000005" + }, + { + "symbol": "get_current_sequence" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000005", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_current_sequence" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000005" + }, + { + "symbol": "get_escrow_config" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000005", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_escrow_config" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "arbiter" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "is_released" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "vendor" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/escrow/test_snapshots/test/test_release_escrow_requires_arbiter_auth.1.json b/contracts/escrow/test_snapshots/test/test_release_escrow_requires_arbiter_auth.1.json new file mode 100644 index 00000000..2dd48065 --- /dev/null +++ b/contracts/escrow/test_snapshots/test/test_release_escrow_requires_arbiter_auth.1.json @@ -0,0 +1,700 @@ +{ + "generators": { + "address": 5, + "nonce": 2 + }, + "auth": [ + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "function_name": "create_escrow", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": { + "hi": 0, + "lo": 5000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 21, + "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": "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": null + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 2 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 2 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "vec": [ + { + "symbol": "EscrowConfig" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "vec": [ + { + "symbol": "EscrowConfig" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, + { + "key": { + "symbol": "arbiter" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "is_released" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "vendor" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "SequenceCounter" + } + ] + }, + "val": { + "u64": 0 + } + } + ] + } + } + } + }, + "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": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000005" + }, + { + "symbol": "initialize" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000005", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000005" + }, + { + "symbol": "create_escrow" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": { + "hi": 0, + "lo": 5000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000005", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000002" + }, + { + "symbol": "__check_auth" + } + ], + "data": { + "vec": [ + { + "bytes": "6247cb34daabc57b7729609abd93be9d855a732f9e1d05036df1976ebfa4a74b" + }, + "void", + { + "vec": [ + { + "vec": [ + { + "symbol": "Contract" + }, + { + "map": [ + { + "key": { + "symbol": "args" + }, + "val": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": { + "hi": 0, + "lo": 5000 + } + } + ] + } + }, + { + "key": { + "symbol": "contract" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "symbol": "fn_name" + }, + "val": { + "symbol": "create_escrow" + } + } + ] + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000002", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "__check_auth" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000005", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "create_escrow" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000005" + }, + { + "symbol": "release_escrow" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000005", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "auth": "invalid_action" + } + } + ], + "data": { + "vec": [ + { + "string": "Unauthorized function call for address" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000005", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "auth": "invalid_action" + } + } + ], + "data": { + "string": "escalating error to panic" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000005", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "auth": "invalid_action" + } + } + ], + "data": { + "string": "caught error from function" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "auth": "invalid_action" + } + } + ], + "data": { + "vec": [ + { + "string": "contract call failed" + }, + { + "symbol": "release_escrow" + }, + { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "auth": "invalid_action" + } + } + ], + "data": { + "string": "escalating error to panic" + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/job_registry/test_snapshots/test/test_close_job.1.json b/contracts/job_registry/test_snapshots/test/test_close_job.1.json new file mode 100644 index 00000000..be4d61c0 --- /dev/null +++ b/contracts/job_registry/test_snapshots/test/test_close_job.1.json @@ -0,0 +1,576 @@ +{ + "generators": { + "address": 3, + "nonce": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "initialize", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "post_job", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u64": 1 + }, + { + "string": "bafy-job-1" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "close_job", + "args": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 21, + "sequence_number": 0, + "timestamp": 1700000000, + "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": "Job" + }, + { + "u64": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Job" + }, + { + "u64": 1 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "assigned_bidder" + }, + "val": "void" + }, + { + "key": { + "symbol": "bid_count" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "budget" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + }, + { + "key": { + "symbol": "cid" + }, + "val": { + "string": "bafy-job-1" + } + }, + { + "key": { + "symbol": "owner" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Closed" + } + ] + } + } + ] + } + } + }, + "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": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "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": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "initialize" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "post_job" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u64": 1 + }, + { + "string": "bafy-job-1" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "post_job" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "close_job" + } + ], + "data": { + "vec": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "close_job" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_job_status" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_job_status" + } + ], + "data": { + "vec": [ + { + "symbol": "Closed" + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/job_registry/test_snapshots/test/test_double_initialize.1.json b/contracts/job_registry/test_snapshots/test/test_double_initialize.1.json new file mode 100644 index 00000000..8b3199ba --- /dev/null +++ b/contracts/job_registry/test_snapshots/test/test_double_initialize.1.json @@ -0,0 +1,330 @@ +{ + "generators": { + "address": 2, + "nonce": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "initialize", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 21, + "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": "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": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "initialize" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "initialize" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "log" + } + ], + "data": { + "vec": [ + { + "string": "caught panic 'already initialized' from contract function 'Symbol(obj#19)'" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "wasm_vm": "invalid_action" + } + } + ], + "data": { + "string": "caught error from function" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "wasm_vm": "invalid_action" + } + } + ], + "data": { + "vec": [ + { + "string": "contract call failed" + }, + { + "symbol": "initialize" + }, + { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "wasm_vm": "invalid_action" + } + } + ], + "data": { + "string": "escalating error to panic" + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/job_registry/test_snapshots/test/test_duplicate_job_id.1.json b/contracts/job_registry/test_snapshots/test/test_duplicate_job_id.1.json index 19b24be3..f635309e 100644 --- a/contracts/job_registry/test_snapshots/test/test_duplicate_job_id.1.json +++ b/contracts/job_registry/test_snapshots/test/test_duplicate_job_id.1.json @@ -1,31 +1,50 @@ { "generators": { - "address": 2, + "address": 3, "nonce": 0 }, "auth": [ [ [ - "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", { "function": { "contract_fn": { - "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "initialize", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", "function_name": "post_job", "args": [ { - "u64": 1 + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + "u64": 1 }, { - "bytes": "516d48617368" + "string": "bafy-job-1" }, { "i128": { "hi": 0, - "lo": 5000 + "lo": 1000 } } ] @@ -40,7 +59,7 @@ "ledger": { "protocol_version": 21, "sequence_number": 0, - "timestamp": 0, + "timestamp": 1700000000, "network_id": "0000000000000000000000000000000000000000000000000000000000000000", "base_reserve": 0, "min_persistent_entry_ttl": 4096, @@ -51,39 +70,6 @@ { "contract_data": { "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "key": { - "ledger_key_nonce": { - "nonce": 801925984706572462 - } - }, - "durability": "temporary" - } - }, - [ - { - "last_modified_ledger_seq": 0, - "data": { - "contract_data": { - "ext": "v0", - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "key": { - "ledger_key_nonce": { - "nonce": 801925984706572462 - } - }, - "durability": "temporary", - "val": "void" - } - }, - "ext": "v0" - }, - 6311999 - ] - ], - [ - { - "contract_data": { - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", "key": { "vec": [ { @@ -103,7 +89,7 @@ "data": { "contract_data": { "ext": "v0", - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", "key": { "vec": [ { @@ -119,35 +105,43 @@ "map": [ { "key": { - "symbol": "budget_stroops" + "symbol": "assigned_bidder" + }, + "val": "void" + }, + { + "key": { + "symbol": "bid_count" }, "val": { - "i128": { - "hi": 0, - "lo": 5000 - } + "u32": 0 } }, { "key": { - "symbol": "client" + "symbol": "budget" }, "val": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + "i128": { + "hi": 0, + "lo": 1000 + } } }, { "key": { - "symbol": "freelancer" + "symbol": "cid" }, - "val": "void" + "val": { + "string": "bafy-job-1" + } }, { "key": { - "symbol": "metadata_hash" + "symbol": "owner" }, "val": { - "bytes": "516d48617368" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } }, { @@ -168,13 +162,13 @@ }, "ext": "v0" }, - 150000 + 4095 ] ], [ { "contract_data": { - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", "key": "ledger_key_contract_instance", "durability": "persistent" } @@ -185,7 +179,7 @@ "data": { "contract_data": { "ext": "v0", - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", "key": "ledger_key_contract_instance", "durability": "persistent", "val": { @@ -193,7 +187,20 @@ "executable": { "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }, - "storage": null + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + ] } } } @@ -203,6 +210,72 @@ 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": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], [ { "contract_code": { @@ -239,7 +312,54 @@ "symbol": "fn_call" }, { - "bytes": "0000000000000000000000000000000000000000000000000000000000000002" + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "initialize" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { "symbol": "post_job" @@ -248,18 +368,18 @@ "data": { "vec": [ { - "u64": 1 + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + "u64": 1 }, { - "bytes": "516d48617368" + "string": "bafy-job-1" }, { "i128": { "hi": 0, - "lo": 5000 + "lo": 1000 } } ] @@ -272,7 +392,7 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000002", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", "type_": "diagnostic", "body": { "v0": { @@ -302,7 +422,7 @@ "symbol": "fn_call" }, { - "bytes": "0000000000000000000000000000000000000000000000000000000000000002" + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { "symbol": "post_job" @@ -311,18 +431,18 @@ "data": { "vec": [ { - "u64": 1 + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + "u64": 1 }, { - "bytes": "516d48617368" + "string": "bafy-job-duplicate" }, { "i128": { "hi": 0, - "lo": 5000 + "lo": 2000 } } ] @@ -335,7 +455,7 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000002", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", "type_": "diagnostic", "body": { "v0": { @@ -350,18 +470,18 @@ "string": "caught panic 'job already exists' from contract function 'Symbol(post_job)'" }, { - "u64": 1 + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + "u64": 1 }, { - "bytes": "516d48617368" + "string": "bafy-job-duplicate" }, { "i128": { "hi": 0, - "lo": 5000 + "lo": 2000 } } ] @@ -374,7 +494,7 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000002", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", "type_": "diagnostic", "body": { "v0": { @@ -424,18 +544,18 @@ { "vec": [ { - "u64": 1 + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + "u64": 1 }, { - "bytes": "516d48617368" + "string": "bafy-job-duplicate" }, { "i128": { "hi": 0, - "lo": 5000 + "lo": 2000 } } ] diff --git a/contracts/job_registry/test_snapshots/test/test_get_bid.1.json b/contracts/job_registry/test_snapshots/test/test_get_bid.1.json new file mode 100644 index 00000000..955a2a73 --- /dev/null +++ b/contracts/job_registry/test_snapshots/test/test_get_bid.1.json @@ -0,0 +1,698 @@ +{ + "generators": { + "address": 4, + "nonce": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "initialize", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "post_job", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u64": 1 + }, + { + "string": "bafy-job-1" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "submit_bid", + "args": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": { + "hi": 0, + "lo": 750 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 21, + "sequence_number": 0, + "timestamp": 1700000000, + "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": "Bid" + }, + { + "u64": 1 + }, + { + "u32": 0 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Bid" + }, + { + "u64": 1 + }, + { + "u32": 0 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 750 + } + } + }, + { + "key": { + "symbol": "bidder" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "submitted_at" + }, + "val": { + "u64": 1700000000 + } + } + ] + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Job" + }, + { + "u64": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Job" + }, + { + "u64": 1 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "assigned_bidder" + }, + "val": "void" + }, + { + "key": { + "symbol": "bid_count" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "budget" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + }, + { + "key": { + "symbol": "cid" + }, + "val": { + "string": "bafy-job-1" + } + }, + { + "key": { + "symbol": "owner" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Open" + } + ] + } + } + ] + } + } + }, + "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": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "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": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "initialize" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "post_job" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u64": 1 + }, + { + "string": "bafy-job-1" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "post_job" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_bid" + } + ], + "data": { + "vec": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": { + "hi": 0, + "lo": 750 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_bid" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_bid" + } + ], + "data": { + "vec": [ + { + "u64": 1 + }, + { + "u32": 0 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_bid" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 750 + } + } + }, + { + "key": { + "symbol": "bidder" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "submitted_at" + }, + "val": { + "u64": 1700000000 + } + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/job_registry/test_snapshots/test/test_happy_path.1.json b/contracts/job_registry/test_snapshots/test/test_happy_path.1.json new file mode 100644 index 00000000..cac0f5c1 --- /dev/null +++ b/contracts/job_registry/test_snapshots/test/test_happy_path.1.json @@ -0,0 +1,1090 @@ +{ + "generators": { + "address": 5, + "nonce": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "initialize", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "post_job", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u64": 1 + }, + { + "string": "bafy-job-1" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "submit_bid", + "args": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": { + "hi": 0, + "lo": 800 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "submit_bid", + "args": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "i128": { + "hi": 0, + "lo": 700 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "accept_bid", + "args": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u32": 0 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 21, + "sequence_number": 0, + "timestamp": 1700000000, + "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": "Bid" + }, + { + "u64": 1 + }, + { + "u32": 0 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Bid" + }, + { + "u64": 1 + }, + { + "u32": 0 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 800 + } + } + }, + { + "key": { + "symbol": "bidder" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "submitted_at" + }, + "val": { + "u64": 1700000000 + } + } + ] + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Bid" + }, + { + "u64": 1 + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Bid" + }, + { + "u64": 1 + }, + { + "u32": 1 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 700 + } + } + }, + { + "key": { + "symbol": "bidder" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "symbol": "submitted_at" + }, + "val": { + "u64": 1700000000 + } + } + ] + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Job" + }, + { + "u64": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Job" + }, + { + "u64": 1 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "assigned_bidder" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "bid_count" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "budget" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + }, + { + "key": { + "symbol": "cid" + }, + "val": { + "string": "bafy-job-1" + } + }, + { + "key": { + "symbol": "owner" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Assigned" + } + ] + } + } + ] + } + } + }, + "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": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 2032731177588607455 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 2032731177588607455 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "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": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "initialize" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "post_job" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u64": 1 + }, + { + "string": "bafy-job-1" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "post_job" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_bid" + } + ], + "data": { + "vec": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": { + "hi": 0, + "lo": 800 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_bid" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_bid" + } + ], + "data": { + "vec": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "i128": { + "hi": 0, + "lo": 700 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_bid" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "accept_bid" + } + ], + "data": { + "vec": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u32": 0 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "accept_bid" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_job" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_job" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "assigned_bidder" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "bid_count" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "budget" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + }, + { + "key": { + "symbol": "cid" + }, + "val": { + "string": "bafy-job-1" + } + }, + { + "key": { + "symbol": "owner" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Assigned" + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_job_status" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_job_status" + } + ], + "data": { + "vec": [ + { + "symbol": "Assigned" + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/job_registry/test_snapshots/test/test_invalid_bid_index.1.json b/contracts/job_registry/test_snapshots/test/test_invalid_bid_index.1.json new file mode 100644 index 00000000..8e9a94ab --- /dev/null +++ b/contracts/job_registry/test_snapshots/test/test_invalid_bid_index.1.json @@ -0,0 +1,778 @@ +{ + "generators": { + "address": 4, + "nonce": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "initialize", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "post_job", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u64": 1 + }, + { + "string": "bafy-job-1" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "submit_bid", + "args": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": { + "hi": 0, + "lo": 800 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 21, + "sequence_number": 0, + "timestamp": 1700000000, + "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": "Bid" + }, + { + "u64": 1 + }, + { + "u32": 0 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Bid" + }, + { + "u64": 1 + }, + { + "u32": 0 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 800 + } + } + }, + { + "key": { + "symbol": "bidder" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "submitted_at" + }, + "val": { + "u64": 1700000000 + } + } + ] + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Job" + }, + { + "u64": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Job" + }, + { + "u64": 1 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "assigned_bidder" + }, + "val": "void" + }, + { + "key": { + "symbol": "bid_count" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "budget" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + }, + { + "key": { + "symbol": "cid" + }, + "val": { + "string": "bafy-job-1" + } + }, + { + "key": { + "symbol": "owner" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Open" + } + ] + } + } + ] + } + } + }, + "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": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "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": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "initialize" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "post_job" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u64": 1 + }, + { + "string": "bafy-job-1" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "post_job" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_bid" + } + ], + "data": { + "vec": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": { + "hi": 0, + "lo": 800 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_bid" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "accept_bid" + } + ], + "data": { + "vec": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u32": 1 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "log" + } + ], + "data": { + "vec": [ + { + "string": "caught panic 'invalid bid index' from contract function 'Symbol(obj#75)'" + }, + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u32": 1 + } + ] + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "wasm_vm": "invalid_action" + } + } + ], + "data": { + "string": "caught error from function" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "wasm_vm": "invalid_action" + } + } + ], + "data": { + "vec": [ + { + "string": "contract call failed" + }, + { + "symbol": "accept_bid" + }, + { + "vec": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u32": 1 + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "wasm_vm": "invalid_action" + } + } + ], + "data": { + "string": "escalating error to panic" + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/job_registry/test_snapshots/test/test_multiple_jobs_isolated.1.json b/contracts/job_registry/test_snapshots/test/test_multiple_jobs_isolated.1.json new file mode 100644 index 00000000..c51ade4e --- /dev/null +++ b/contracts/job_registry/test_snapshots/test/test_multiple_jobs_isolated.1.json @@ -0,0 +1,1366 @@ +{ + "generators": { + "address": 6, + "nonce": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "initialize", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "post_job", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u64": 1 + }, + { + "string": "bafy-job-1" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "post_job", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "u64": 2 + }, + { + "string": "bafy-job-2" + }, + { + "i128": { + "hi": 0, + "lo": 2000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "submit_bid", + "args": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "i128": { + "hi": 0, + "lo": 800 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "submit_bid", + "args": [ + { + "u64": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "i128": { + "hi": 0, + "lo": 1700 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "accept_bid", + "args": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u32": 0 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 21, + "sequence_number": 0, + "timestamp": 1700000000, + "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": "Bid" + }, + { + "u64": 1 + }, + { + "u32": 0 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Bid" + }, + { + "u64": 1 + }, + { + "u32": 0 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 800 + } + } + }, + { + "key": { + "symbol": "bidder" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "symbol": "submitted_at" + }, + "val": { + "u64": 1700000000 + } + } + ] + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Bid" + }, + { + "u64": 2 + }, + { + "u32": 0 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Bid" + }, + { + "u64": 2 + }, + { + "u32": 0 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1700 + } + } + }, + { + "key": { + "symbol": "bidder" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + }, + { + "key": { + "symbol": "submitted_at" + }, + "val": { + "u64": 1700000000 + } + } + ] + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Job" + }, + { + "u64": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Job" + }, + { + "u64": 1 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "assigned_bidder" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "symbol": "bid_count" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "budget" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + }, + { + "key": { + "symbol": "cid" + }, + "val": { + "string": "bafy-job-1" + } + }, + { + "key": { + "symbol": "owner" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Assigned" + } + ] + } + } + ] + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Job" + }, + { + "u64": 2 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Job" + }, + { + "u64": 2 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "assigned_bidder" + }, + "val": "void" + }, + { + "key": { + "symbol": "bid_count" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "budget" + }, + "val": { + "i128": { + "hi": 0, + "lo": 2000 + } + } + }, + { + "key": { + "symbol": "cid" + }, + "val": { + "string": "bafy-job-2" + } + }, + { + "key": { + "symbol": "owner" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Open" + } + ] + } + } + ] + } + } + }, + "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": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 4270020994084947596 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 4270020994084947596 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "ledger_key_nonce": { + "nonce": 2032731177588607455 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "ledger_key_nonce": { + "nonce": 2032731177588607455 + } + }, + "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": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "initialize" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "post_job" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u64": 1 + }, + { + "string": "bafy-job-1" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "post_job" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "post_job" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "u64": 2 + }, + { + "string": "bafy-job-2" + }, + { + "i128": { + "hi": 0, + "lo": 2000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "post_job" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_bid" + } + ], + "data": { + "vec": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "i128": { + "hi": 0, + "lo": 800 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_bid" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_bid" + } + ], + "data": { + "vec": [ + { + "u64": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "i128": { + "hi": 0, + "lo": 1700 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_bid" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "accept_bid" + } + ], + "data": { + "vec": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u32": 0 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "accept_bid" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_job" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_job" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "assigned_bidder" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "symbol": "bid_count" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "budget" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + }, + { + "key": { + "symbol": "cid" + }, + "val": { + "string": "bafy-job-1" + } + }, + { + "key": { + "symbol": "owner" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Assigned" + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "get_job" + } + ], + "data": { + "u64": 2 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_job" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "assigned_bidder" + }, + "val": "void" + }, + { + "key": { + "symbol": "bid_count" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "budget" + }, + "val": { + "i128": { + "hi": 0, + "lo": 2000 + } + } + }, + { + "key": { + "symbol": "cid" + }, + "val": { + "string": "bafy-job-2" + } + }, + { + "key": { + "symbol": "owner" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Open" + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/job_registry/test_snapshots/test/test_negative_budget.1.json b/contracts/job_registry/test_snapshots/test/test_negative_budget.1.json new file mode 100644 index 00000000..8bdb6736 --- /dev/null +++ b/contracts/job_registry/test_snapshots/test/test_negative_budget.1.json @@ -0,0 +1,370 @@ +{ + "generators": { + "address": 3, + "nonce": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "initialize", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 21, + "sequence_number": 0, + "timestamp": 1700000000, + "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": "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": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "initialize" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "post_job" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u64": 1 + }, + { + "string": "bafy-job-1" + }, + { + "i128": { + "hi": -1, + "lo": 18446744073709551615 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "log" + } + ], + "data": { + "vec": [ + { + "string": "caught panic 'budget must be positive' from contract function 'Symbol(post_job)'" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u64": 1 + }, + { + "string": "bafy-job-1" + }, + { + "i128": { + "hi": -1, + "lo": 18446744073709551615 + } + } + ] + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "wasm_vm": "invalid_action" + } + } + ], + "data": { + "string": "caught error from function" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "wasm_vm": "invalid_action" + } + } + ], + "data": { + "vec": [ + { + "string": "contract call failed" + }, + { + "symbol": "post_job" + }, + { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u64": 1 + }, + { + "string": "bafy-job-1" + }, + { + "i128": { + "hi": -1, + "lo": 18446744073709551615 + } + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "wasm_vm": "invalid_action" + } + } + ], + "data": { + "string": "escalating error to panic" + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/job_registry/test_snapshots/test/test_submit_bid_on_assigned_job.1.json b/contracts/job_registry/test_snapshots/test/test_submit_bid_on_assigned_job.1.json new file mode 100644 index 00000000..8024697e --- /dev/null +++ b/contracts/job_registry/test_snapshots/test/test_submit_bid_on_assigned_job.1.json @@ -0,0 +1,904 @@ +{ + "generators": { + "address": 5, + "nonce": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "initialize", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "post_job", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u64": 1 + }, + { + "string": "bafy-job-1" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "submit_bid", + "args": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": { + "hi": 0, + "lo": 800 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "accept_bid", + "args": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u32": 0 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 21, + "sequence_number": 0, + "timestamp": 1700000000, + "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": "Bid" + }, + { + "u64": 1 + }, + { + "u32": 0 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Bid" + }, + { + "u64": 1 + }, + { + "u32": 0 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 800 + } + } + }, + { + "key": { + "symbol": "bidder" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "submitted_at" + }, + "val": { + "u64": 1700000000 + } + } + ] + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Job" + }, + { + "u64": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "Job" + }, + { + "u64": 1 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "assigned_bidder" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "bid_count" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "budget" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + }, + { + "key": { + "symbol": "cid" + }, + "val": { + "string": "bafy-job-1" + } + }, + { + "key": { + "symbol": "owner" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Assigned" + } + ] + } + } + ] + } + } + }, + "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": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "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": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "initialize" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "post_job" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u64": 1 + }, + { + "string": "bafy-job-1" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "post_job" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_bid" + } + ], + "data": { + "vec": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": { + "hi": 0, + "lo": 800 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "submit_bid" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "accept_bid" + } + ], + "data": { + "vec": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u32": 0 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "accept_bid" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_bid" + } + ], + "data": { + "vec": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "i128": { + "hi": 0, + "lo": 700 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "log" + } + ], + "data": { + "vec": [ + { + "string": "caught panic 'job not open' from contract function 'Symbol(obj#113)'" + }, + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "i128": { + "hi": 0, + "lo": 700 + } + } + ] + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "wasm_vm": "invalid_action" + } + } + ], + "data": { + "string": "caught error from function" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "wasm_vm": "invalid_action" + } + } + ], + "data": { + "vec": [ + { + "string": "contract call failed" + }, + { + "symbol": "submit_bid" + }, + { + "vec": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "i128": { + "hi": 0, + "lo": 700 + } + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "wasm_vm": "invalid_action" + } + } + ], + "data": { + "string": "escalating error to panic" + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/job_registry/test_snapshots/test/test_unauthorized_accept_bid.1.json b/contracts/job_registry/test_snapshots/test/test_unauthorized_accept_bid.1.json index 3d07306e..1b968bee 100644 --- a/contracts/job_registry/test_snapshots/test/test_unauthorized_accept_bid.1.json +++ b/contracts/job_registry/test_snapshots/test/test_unauthorized_accept_bid.1.json @@ -1,26 +1,45 @@ { "generators": { - "address": 4, + "address": 5, "nonce": 0 }, "auth": [ [ [ - "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", { "function": { "contract_fn": { - "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "initialize", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", "function_name": "post_job", "args": [ { - "u64": 1 + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + "u64": 1 }, { - "bytes": "516d48617368" + "string": "bafy-job-1" }, { "i128": { @@ -37,21 +56,24 @@ ], [ [ - "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", { "function": { "contract_fn": { - "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", "function_name": "submit_bid", "args": [ { "u64": 1 }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" }, { - "bytes": "516d50726f706f73616c" + "i128": { + "hi": 0, + "lo": 800 + } } ] } @@ -65,7 +87,7 @@ "ledger": { "protocol_version": 21, "sequence_number": 0, - "timestamp": 0, + "timestamp": 1700000000, "network_id": "0000000000000000000000000000000000000000000000000000000000000000", "base_reserve": 0, "min_persistent_entry_ttl": 4096, @@ -76,79 +98,16 @@ { "contract_data": { "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "key": { - "ledger_key_nonce": { - "nonce": 801925984706572462 - } - }, - "durability": "temporary" - } - }, - [ - { - "last_modified_ledger_seq": 0, - "data": { - "contract_data": { - "ext": "v0", - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "key": { - "ledger_key_nonce": { - "nonce": 801925984706572462 - } - }, - "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_data": { - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", "key": { "vec": [ { - "symbol": "Bids" + "symbol": "Bid" }, { "u64": 1 + }, + { + "u32": 0 } ] }, @@ -161,39 +120,49 @@ "data": { "contract_data": { "ext": "v0", - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", "key": { "vec": [ { - "symbol": "Bids" + "symbol": "Bid" }, { "u64": 1 + }, + { + "u32": 0 } ] }, "durability": "persistent", "val": { - "vec": [ + "map": [ { - "map": [ - { - "key": { - "symbol": "freelancer" - }, - "val": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - } - }, - { - "key": { - "symbol": "proposal_hash" - }, - "val": { - "bytes": "516d50726f706f73616c" - } + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 800 } - ] + } + }, + { + "key": { + "symbol": "bidder" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "submitted_at" + }, + "val": { + "u64": 1700000000 + } } ] } @@ -201,13 +170,13 @@ }, "ext": "v0" }, - 150000 + 4095 ] ], [ { "contract_data": { - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", "key": { "vec": [ { @@ -227,7 +196,7 @@ "data": { "contract_data": { "ext": "v0", - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", "key": { "vec": [ { @@ -243,35 +212,43 @@ "map": [ { "key": { - "symbol": "budget_stroops" + "symbol": "assigned_bidder" + }, + "val": "void" + }, + { + "key": { + "symbol": "bid_count" }, "val": { - "i128": { - "hi": 0, - "lo": 1000 - } + "u32": 1 } }, { "key": { - "symbol": "client" + "symbol": "budget" }, "val": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + "i128": { + "hi": 0, + "lo": 1000 + } } }, { "key": { - "symbol": "freelancer" + "symbol": "cid" }, - "val": "void" + "val": { + "string": "bafy-job-1" + } }, { "key": { - "symbol": "metadata_hash" + "symbol": "owner" }, "val": { - "bytes": "516d48617368" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } }, { @@ -292,13 +269,13 @@ }, "ext": "v0" }, - 150000 + 4095 ] ], [ { "contract_data": { - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", "key": "ledger_key_contract_instance", "durability": "persistent" } @@ -309,7 +286,7 @@ "data": { "contract_data": { "ext": "v0", - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", "key": "ledger_key_contract_instance", "durability": "persistent", "val": { @@ -317,7 +294,20 @@ "executable": { "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }, - "storage": null + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + ] } } } @@ -327,6 +317,105 @@ 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": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], [ { "contract_code": { @@ -363,30 +452,14 @@ "symbol": "fn_call" }, { - "bytes": "0000000000000000000000000000000000000000000000000000000000000004" + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "post_job" + "symbol": "initialize" } ], "data": { - "vec": [ - { - "u64": 1 - }, - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" - }, - { - "bytes": "516d48617368" - }, - { - "i128": { - "hi": 0, - "lo": 1000 - } - } - ] + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" } } } @@ -396,7 +469,7 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000004", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", "type_": "diagnostic", "body": { "v0": { @@ -405,7 +478,7 @@ "symbol": "fn_return" }, { - "symbol": "post_job" + "symbol": "initialize" } ], "data": "void" @@ -426,22 +499,28 @@ "symbol": "fn_call" }, { - "bytes": "0000000000000000000000000000000000000000000000000000000000000004" + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { - "symbol": "submit_bid" + "symbol": "post_job" } ], "data": { "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, { "u64": 1 }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + "string": "bafy-job-1" }, { - "bytes": "516d50726f706f73616c" + "i128": { + "hi": 0, + "lo": 1000 + } } ] } @@ -453,50 +532,54 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000004", - "type_": "contract", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", "body": { "v0": { "topics": [ { - "string": "job_registry" + "symbol": "fn_return" }, { - "string": "BidSubmitted" + "symbol": "post_job" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "submit_bid" } ], "data": { - "map": [ - { - "key": { - "symbol": "freelancer" - }, - "val": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - } - }, + "vec": [ { - "key": { - "symbol": "job_id" - }, - "val": { - "u64": 1 - } + "u64": 1 }, { - "key": { - "symbol": "proposal_hash" - }, - "val": { - "bytes": "516d50726f706f73616c" - } + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" }, { - "key": { - "symbol": "timestamp" - }, - "val": { - "u64": 0 + "i128": { + "hi": 0, + "lo": 800 } } ] @@ -509,7 +592,7 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000004", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", "type_": "diagnostic", "body": { "v0": { @@ -539,7 +622,7 @@ "symbol": "fn_call" }, { - "bytes": "0000000000000000000000000000000000000000000000000000000000000004" + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" }, { "symbol": "accept_bid" @@ -551,10 +634,10 @@ "u64": 1 }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + "u32": 0 } ] } @@ -566,22 +649,30 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000004", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", "type_": "diagnostic", "body": { "v0": { "topics": [ { - "symbol": "fn_return" - }, - { - "symbol": "accept_bid" + "symbol": "log" } ], "data": { - "error": { - "contract": 3 - } + "vec": [ + { + "string": "caught panic 'unauthorized' from contract function 'Symbol(obj#77)'" + }, + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "u32": 0 + } + ] } } } @@ -591,7 +682,7 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000004", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", "type_": "diagnostic", "body": { "v0": { @@ -601,12 +692,12 @@ }, { "error": { - "contract": 3 + "wasm_vm": "invalid_action" } } ], "data": { - "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + "string": "caught error from function" } } } @@ -626,7 +717,7 @@ }, { "error": { - "contract": 3 + "wasm_vm": "invalid_action" } } ], @@ -644,10 +735,10 @@ "u64": 1 }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + "u32": 0 } ] } @@ -671,7 +762,7 @@ }, { "error": { - "contract": 3 + "wasm_vm": "invalid_action" } } ], diff --git a/contracts/reputation/test_snapshots/test/test_initial_score.1.json b/contracts/reputation/test_snapshots/test/test_initial_score.1.json index 472200bc..99c8aa18 100644 --- a/contracts/reputation/test_snapshots/test/test_initial_score.1.json +++ b/contracts/reputation/test_snapshots/test/test_initial_score.1.json @@ -45,7 +45,7 @@ }, "ext": "v0" }, - 150000 + 4095 ] ], [ @@ -66,7 +66,7 @@ }, "ext": "v0" }, - 150000 + 4095 ] ] ] diff --git a/contracts/reputation/test_snapshots/test/test_slash.1.json b/contracts/reputation/test_snapshots/test/test_slash.1.json index 444e4a14..c4d62e42 100644 --- a/contracts/reputation/test_snapshots/test/test_slash.1.json +++ b/contracts/reputation/test_snapshots/test/test_slash.1.json @@ -86,10 +86,17 @@ "key": { "vec": [ { - "symbol": "Profile" + "symbol": "Score" }, { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "vec": [ + { + "symbol": "Client" + } + ] } ] }, @@ -106,10 +113,17 @@ "key": { "vec": [ { - "symbol": "Profile" + "symbol": "Score" }, { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "vec": [ + { + "symbol": "Client" + } + ] } ] }, @@ -126,7 +140,7 @@ }, { "key": { - "symbol": "client_jobs" + "symbol": "reviews" }, "val": { "u32": 0 @@ -134,15 +148,19 @@ }, { "key": { - "symbol": "client_points" + "symbol": "role" }, "val": { - "i32": 0 + "vec": [ + { + "symbol": "Client" + } + ] } }, { "key": { - "symbol": "client_score" + "symbol": "score" }, "val": { "i32": 3000 @@ -150,7 +168,7 @@ }, { "key": { - "symbol": "freelancer_jobs" + "symbol": "total_jobs" }, "val": { "u32": 0 @@ -158,25 +176,11 @@ }, { "key": { - "symbol": "freelancer_points" + "symbol": "total_points" }, "val": { "i32": 0 } - }, - { - "key": { - "symbol": "freelancer_score" - }, - "val": { - "i32": 5000 - } - }, - { - "key": { - "symbol": "metadata_hash" - }, - "val": "void" } ] } @@ -184,7 +188,7 @@ }, "ext": "v0" }, - 150000 + 4095 ] ], [ @@ -229,7 +233,7 @@ }, "ext": "v0" }, - 150000 + 4095 ] ], [ @@ -250,7 +254,7 @@ }, "ext": "v0" }, - 150000 + 4095 ] ] ] @@ -343,82 +347,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000003", - "type_": "contract", - "body": { - "v0": { - "topics": [ - { - "string": "reputation" - }, - { - "string": "ScoreAdjusted" - } - ], - "data": { - "map": [ - { - "key": { - "symbol": "address" - }, - "val": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - } - }, - { - "key": { - "symbol": "adjusted_at" - }, - "val": { - "u64": 0 - } - }, - { - "key": { - "symbol": "delta" - }, - "val": { - "i32": -2000 - } - }, - { - "key": { - "symbol": "new_score" - }, - "val": { - "i32": 3000 - } - }, - { - "key": { - "symbol": "role" - }, - "val": { - "vec": [ - { - "symbol": "Client" - } - ] - } - }, - { - "key": { - "symbol": "total_jobs" - }, - "val": { - "u32": 0 - } - } - ] - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", diff --git a/contracts/reputation/test_snapshots/test/test_update_score.1.json b/contracts/reputation/test_snapshots/test/test_update_score.1.json index ec45498f..d07d390b 100644 --- a/contracts/reputation/test_snapshots/test/test_update_score.1.json +++ b/contracts/reputation/test_snapshots/test/test_update_score.1.json @@ -86,10 +86,17 @@ "key": { "vec": [ { - "symbol": "Profile" + "symbol": "Score" }, { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "vec": [ + { + "symbol": "Freelancer" + } + ] } ] }, @@ -106,10 +113,17 @@ "key": { "vec": [ { - "symbol": "Profile" + "symbol": "Score" }, { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "vec": [ + { + "symbol": "Freelancer" + } + ] } ] }, @@ -126,7 +140,7 @@ }, { "key": { - "symbol": "client_jobs" + "symbol": "reviews" }, "val": { "u32": 0 @@ -134,23 +148,27 @@ }, { "key": { - "symbol": "client_points" + "symbol": "role" }, "val": { - "i32": 0 + "vec": [ + { + "symbol": "Freelancer" + } + ] } }, { "key": { - "symbol": "client_score" + "symbol": "score" }, "val": { - "i32": 5000 + "i32": 5500 } }, { "key": { - "symbol": "freelancer_jobs" + "symbol": "total_jobs" }, "val": { "u32": 1 @@ -158,25 +176,11 @@ }, { "key": { - "symbol": "freelancer_points" + "symbol": "total_points" }, "val": { "i32": 0 } - }, - { - "key": { - "symbol": "freelancer_score" - }, - "val": { - "i32": 5500 - } - }, - { - "key": { - "symbol": "metadata_hash" - }, - "val": "void" } ] } @@ -184,7 +188,7 @@ }, "ext": "v0" }, - 150000 + 4095 ] ], [ @@ -229,7 +233,7 @@ }, "ext": "v0" }, - 150000 + 4095 ] ], [ @@ -250,7 +254,7 @@ }, "ext": "v0" }, - 150000 + 4095 ] ] ] @@ -343,82 +347,6 @@ }, "failed_call": false }, - { - "event": { - "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000003", - "type_": "contract", - "body": { - "v0": { - "topics": [ - { - "string": "reputation" - }, - { - "string": "ScoreAdjusted" - } - ], - "data": { - "map": [ - { - "key": { - "symbol": "address" - }, - "val": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - } - }, - { - "key": { - "symbol": "adjusted_at" - }, - "val": { - "u64": 0 - } - }, - { - "key": { - "symbol": "delta" - }, - "val": { - "i32": 500 - } - }, - { - "key": { - "symbol": "new_score" - }, - "val": { - "i32": 5500 - } - }, - { - "key": { - "symbol": "role" - }, - "val": { - "vec": [ - { - "symbol": "Freelancer" - } - ] - } - }, - { - "key": { - "symbol": "total_jobs" - }, - "val": { - "u32": 1 - } - } - ] - } - } - } - }, - "failed_call": false - }, { "event": { "ext": "v0", @@ -507,7 +435,7 @@ "symbol": "reviews" }, "val": { - "u32": 1 + "u32": 0 } }, { diff --git a/u64 b/u64 new file mode 100644 index 00000000..e69de29b From 922839ebb49a37e2fb63a1667ad8d36e7f6b5d54 Mon Sep 17 00:00:00 2001 From: N-thnI Date: Fri, 29 May 2026 22:58:25 +0100 Subject: [PATCH 2/2] fix(escrow): fully resolve auth checks and unify contract state logic --- backend/dist/config/db.js | 80 ++-- backend/dist/config/redis.js | 36 ++ backend/dist/index.js | 83 +++- backend/dist/middleware/authGuard.js | 72 ++++ backend/dist/middleware/compression.js | 105 +++++ backend/dist/middleware/intakeRateLimit.js | 117 ++++++ backend/dist/middleware/metrics.js | 83 ++++ backend/dist/middleware/sanitize.js | 203 ++++++++++ backend/dist/routes/auth.js | 443 ++++++++++++++++++--- backend/dist/routes/pool.js | 5 + backend/dist/utils/metrics.js | 152 +++++++ backend/dist/utils/storage-cleanup.js | 246 ++++++++++++ backend/src/middleware/sanitize.ts | 11 +- backend/src/routes/auth.ts | 40 +- backend/tsconfig.json | 8 +- contracts/escrow/src/lib.rs | 419 ++++++++++++------- 16 files changed, 1825 insertions(+), 278 deletions(-) create mode 100644 backend/dist/config/redis.js create mode 100644 backend/dist/middleware/authGuard.js create mode 100644 backend/dist/middleware/compression.js create mode 100644 backend/dist/middleware/intakeRateLimit.js create mode 100644 backend/dist/middleware/metrics.js create mode 100644 backend/dist/middleware/sanitize.js create mode 100644 backend/dist/utils/metrics.js create mode 100644 backend/dist/utils/storage-cleanup.js diff --git a/backend/dist/config/db.js b/backend/dist/config/db.js index acd5e740..44b87470 100644 --- a/backend/dist/config/db.js +++ b/backend/dist/config/db.js @@ -156,47 +156,51 @@ async function connectWithRetry() { // --------------------------------------------------------------------------- const adapter = new adapter_pg_1.PrismaPg(exports.pool); const globalForPrisma = global; -// Initialize Prisma with optimized middleware for tracing and performance monitoring -exports.prisma = globalForPrisma.prisma || - new client_1.PrismaClient({ +function createPrismaClient() { + const client = new client_1.PrismaClient({ adapter, log: process.env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"], }); -// Add query middleware for tracing and performance monitoring -exports.prisma.$use(async (params, next) => { - const spanContext = tracing_1.context.active(); - const startTime = Date.now(); - const logger = tracing_1.trace.getLogger("db-query"); - try { - const result = await next(params); - const duration = Date.now() - startTime; - // Log slow queries (> 1000ms) - if (duration > 1000) { - logger.warn(`Slow query detected: ${params.model}.${params.action}`, { - duration, - model: params.model, - action: params.action, - args: JSON.stringify(params.args).substring(0, 200), - }); - } - logger.debug(`Query completed: ${params.model}.${params.action}`, { - duration, - model: params.model, - action: params.action, - }); - return result; - } - catch (error) { - const duration = Date.now() - startTime; - logger.error(`Query failed: ${params.model}.${params.action}`, { - duration, - model: params.model, - action: params.action, - error: error instanceof Error ? error.message : String(error), - }); - throw error; - } -}); + return client.$extends({ + query: { + $allModels: { + async $allOperations({ model, operation, args, query }) { + const startTime = Date.now(); + const logger = tracing_1.trace.getLogger("db-query"); + try { + const result = await query(args); + const duration = Date.now() - startTime; + if (duration > 1000) { + logger.warn(`Slow query detected: ${model}.${operation}`, { + duration, + model, + action: operation, + args: JSON.stringify(args).substring(0, 200), + }); + } + logger.debug(`Query completed: ${model}.${operation}`, { + duration, + model, + action: operation, + }); + return result; + } + catch (error) { + const duration = Date.now() - startTime; + logger.error(`Query failed: ${model}.${operation}`, { + duration, + model, + action: operation, + error: error instanceof Error ? error.message : String(error), + }); + throw error; + } + }, + }, + }, + }); +} +exports.prisma = globalForPrisma.prisma || createPrismaClient(); if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = exports.prisma; // --------------------------------------------------------------------------- diff --git a/backend/dist/config/redis.js b/backend/dist/config/redis.js new file mode 100644 index 00000000..2617624f --- /dev/null +++ b/backend/dist/config/redis.js @@ -0,0 +1,36 @@ +"use strict"; +/** + * src/config/redis.ts + * + * Singleton ioredis client shared across the entire backend. + * + * Used by: + * • JWT blacklist (auth routes — BE-W3A-105) + * • Refresh-token revocation lookups + * • Future: distributed rate-limiting, caching + * + * Design decisions: + * • `lazyConnect: true` — the server boots even if Redis is momentarily + * unavailable; the health endpoint reports degraded status instead of + * crashing the process. + * • Exponential retry capped at 10 s so transient Redis restarts self-heal + * without flooding logs. + * • `keepAlive: 5000` prevents silent TCP drops behind cloud load-balancers. + */ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.redis = void 0; +const ioredis_1 = __importDefault(require("ioredis")); +const REDIS_URL = process.env.REDIS_URL || "redis://localhost:6379"; +exports.redis = new ioredis_1.default(REDIS_URL, { + // Exponential back-off: 100 ms → 200 ms → … capped at 10 s + retryStrategy: (times) => Math.min(times * 100, 10_000), + lazyConnect: true, + keepAlive: 5_000, + // Name shown in Redis CLIENT LIST — useful when debugging multi-service setups + connectionName: "lance-backend", +}); +exports.redis.on("connect", () => console.log("[redis] connected to", REDIS_URL.replace(/:\/\/.*@/, "://@"))); +exports.redis.on("error", (err) => console.error("[redis] error:", err.message)); diff --git a/backend/dist/index.js b/backend/dist/index.js index 0de864e7..694ec3f0 100644 --- a/backend/dist/index.js +++ b/backend/dist/index.js @@ -5,9 +5,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) { Object.defineProperty(exports, "__esModule", { value: true }); const express_1 = __importDefault(require("express")); const cors_1 = __importDefault(require("cors")); +const cookie_parser_1 = __importDefault(require("cookie-parser")); +const crypto_1 = __importDefault(require("crypto")); const dotenv_1 = __importDefault(require("dotenv")); const db_1 = require("./config/db"); -const tracing_1 = require("./utils/tracing"); +const tracing_1 = require("./config/tracing"); +const intakeRateLimit_1 = require("./middleware/intakeRateLimit"); +const sanitize_1 = require("./middleware/sanitize"); +const tracing_2 = require("./utils/tracing"); +const metrics_1 = require("./middleware/metrics"); +const metrics_2 = require("./utils/metrics"); const auth_1 = __importDefault(require("./routes/auth")); const jobs_1 = __importDefault(require("./routes/jobs")); const disputes_1 = __importDefault(require("./routes/disputes")); @@ -18,14 +25,73 @@ const uploads_1 = __importDefault(require("./routes/uploads")); const bulk_1 = __importDefault(require("./routes/bulk")); const pool_1 = __importDefault(require("./routes/pool")); const state_1 = __importDefault(require("./routes/state")); +const db_2 = require("./config/db"); dotenv_1.default.config(); const app = (0, express_1.default)(); const port = process.env.PORT || 3001; const logger = tracing_1.trace.getLogger("server"); -// Enable CORS for frontend requests -app.use((0, cors_1.default)({ origin: "*" })); +const isProduction = process.env.NODE_ENV === "production"; +const CSRF_COOKIE_NAME = "lance-csrf-token"; +// Enable CORS for frontend requests with credentials support +const FRONTEND_URL = process.env.FRONTEND_URL || "http://localhost:3000"; +app.use((0, cors_1.default)({ + origin: FRONTEND_URL, + credentials: true, +})); +app.use((0, cookie_parser_1.default)()); app.use(express_1.default.json()); -app.use(tracing_1.tracingMiddleware); // Global request tracing and diagnostics +// CSRF protection middleware (double-submit cookie pattern) +const csrfMiddleware = (req, res, next) => { + // Skip CSRF for GET/HEAD/OPTIONS and auth challenge/verify routes + if (["GET", "HEAD", "OPTIONS"].includes(req.method) || + (req.path.startsWith("/api/v1/auth/") && + (req.path.endsWith("/challenge") || req.path.endsWith("/verify")))) { + return next(); + } + const csrfCookie = req.cookies[CSRF_COOKIE_NAME]; + const csrfHeader = req.headers["x-csrf-token"]; + const csrfHeaderStr = Array.isArray(csrfHeader) ? csrfHeader[0] : csrfHeader; + if (!csrfCookie || !csrfHeaderStr || !crypto_1.default.timingSafeEqual(Buffer.from(csrfCookie), Buffer.from(csrfHeaderStr))) { + return res.status(403).json({ error: "Invalid CSRF token" }); + } + next(); +}; +// Route to get CSRF token +app.get("/api/v1/auth/csrf", (req, res) => { + const csrfToken = crypto_1.default.randomBytes(32).toString("hex"); + // Set CSRF cookie (HttpOnly false so frontend can read it, SameSite strict) + res.cookie(CSRF_COOKIE_NAME, csrfToken, { + httpOnly: false, + secure: isProduction, + sameSite: isProduction ? "strict" : "lax", + path: "/", + }); + res.json({ csrfToken }); +}); +app.use(csrfMiddleware); +app.use(tracing_2.tracingMiddleware); // Global request tracing and diagnostics +app.use(intakeRateLimit_1.intakeRateLimit); +app.use(metrics_1.metricsMiddleware); +// SQL injection protection — inspects query params and body for injection patterns +app.use(sanitize_1.sqlInjectionGuard); +// Request logging middleware with tracing +app.use((req, res, next) => { + const startTime = Date.now(); + const requestLogger = tracing_1.trace.getLogger(`http-${req.method}`); + res.on("finish", () => { + const duration = Date.now() - startTime; + const status = res.statusCode; + const statusCategory = status < 400 ? "success" : status < 500 ? "client_error" : "server_error"; + requestLogger.info(`${req.method} ${req.path}`, { + method: req.method, + path: req.path, + status, + duration, + statusCategory, + }); + }); + next(); +}); // Mount API routes app.use("/api/v1/auth", auth_1.default); app.use("/api/v1/jobs", jobs_1.default); @@ -37,7 +103,8 @@ app.use("/api/v1/uploads", uploads_1.default); app.use("/api/v1/bulk", bulk_1.default); app.use("/api/v1/pool", pool_1.default); app.use("/api/v1/state", state_1.default); -// Basic healthcheck route +app.use("/api/v1/metrics", (0, metrics_2.createMetricsRouter)()); +// Health check endpoint with database connectivity verification app.get("/health", async (req, res) => { const startTime = Date.now(); logger.debug("Health check requested"); @@ -74,6 +141,7 @@ app.get("/health", async (req, res) => { // Graceful shutdown handler process.on("SIGTERM", async () => { logger.info("SIGTERM received, shutting down gracefully"); + stopStorageCleanup(); try { await db_1.prisma.$disconnect(); logger.info("Database connection closed"); @@ -94,8 +162,13 @@ async function bootstrap() { try { await (0, db_1.connectWithRetry)(); (0, db_1.startPoolHealthCheck)(); + startStorageCleanup(); app.listen(port, () => { console.log(`⚡️[server]: Server is running at http://localhost:${port}`); + // Update pool metrics periodically so the Prometheus scrape has fresh data + setInterval(() => { + (0, metrics_2.updatePoolMetrics)(db_2.pool.totalCount, db_2.pool.idleCount, db_2.pool.waitingCount); + }, 15_000).unref(); }); } catch (err) { diff --git a/backend/dist/middleware/authGuard.js b/backend/dist/middleware/authGuard.js new file mode 100644 index 00000000..960b8f76 --- /dev/null +++ b/backend/dist/middleware/authGuard.js @@ -0,0 +1,72 @@ +"use strict"; +/** + * src/middleware/authGuard.ts + * + * Express middleware that validates JWT access tokens on every protected route. + * + * Steps + * ───── + * 1. Extract Bearer token from Authorization header + * 2. Cryptographic signature + expiry check (jsonwebtoken) + * 3. Issuer / audience claim validation + * 4. Redis blacklist lookup for revoked `jti` values ← sub-ms, O(1) + * + * Usage + * ───── + * import { authGuard } from "../middleware/authGuard"; + * + * // Protect a single route + * router.get("/profile", authGuard, profileHandler); + * + * // Protect an entire router + * app.use("/api/v1/jobs", authGuard, jobsRouter); + */ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.authGuard = authGuard; +const jsonwebtoken_1 = __importDefault(require("jsonwebtoken")); +const auth_1 = require("../routes/auth"); +const ACCESS_TOKEN_COOKIE = "lance_access_token"; +async function authGuard(req, res, next) { + // Try to get token from cookie first, then from Authorization header + let token = req.cookies[ACCESS_TOKEN_COOKIE]; + const header = req.headers.authorization; + if (!token && header?.startsWith("Bearer ")) { + token = header.slice(7); + } + if (!token) { + res.status(401).json({ error: "Authorization token missing or malformed" }); + return; + } + const secret = process.env.JWT_SECRET; + if (!secret) { + console.error("[authGuard] JWT_SECRET is not set"); + res.status(500).json({ error: "Server misconfiguration" }); + return; + } + let decoded; + try { + decoded = jsonwebtoken_1.default.verify(token, secret, { + issuer: "lance-marketplace", + audience: "lance-frontend", + }); + } + catch { + res.status(401).json({ error: "Invalid or expired access token" }); + return; + } + if (!decoded.jti) { + res.status(401).json({ error: "Token missing jti claim" }); + return; + } + // Redis blacklist check — single GET, O(1), target < 1 ms. + const revoked = await (0, auth_1.isTokenBlacklisted)(decoded.jti).catch(() => false); + if (revoked) { + res.status(401).json({ error: "Token has been revoked" }); + return; + } + req.auth = decoded; + next(); +} diff --git a/backend/dist/middleware/compression.js b/backend/dist/middleware/compression.js new file mode 100644 index 00000000..e728e10a --- /dev/null +++ b/backend/dist/middleware/compression.js @@ -0,0 +1,105 @@ +"use strict"; +/** + * src/middleware/compression.ts + * + * BE-API-096 — Gzip compression for large JSON responses. + * + * Rules + * ───── + * • Only compresses responses > 1 KB (THRESHOLD) to avoid spending CPU on + * tiny payloads where the overhead outweighs the saving. + * • Skips already-compressed binary types (image/*, audio/*, video/*) to + * prevent counter-productive double-encoding. + * • Respects the client's Accept-Encoding header — browsers that send + * "gzip, deflate, br" get the best encoding the client supports. + * • Escapes via `x-no-compression` request header for special cases + * (e.g. streaming endpoints that manage chunked encoding themselves). + * • Logs original size, compressed size, and ratio using the project's + * global `trace` logger for diagnostic visibility (BE-API-096 requirement). + * + * Registration in src/index.ts + * ──────────────────────────── + * import { compressionMiddleware } from "./middleware/compression"; + * app.use(compressionMiddleware); // Place BEFORE express.json() + */ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.compressionMiddleware = compressionMiddleware; +const compression_1 = __importDefault(require("compression")); +const tracing_1 = require("../config/tracing"); +const logger = tracing_1.trace.getLogger("compression"); +/** Minimum response size (bytes) before compression is applied. */ +const THRESHOLD = 1_024; // 1 KB +/** + * MIME types that are already entropy-maximised — compressing them wastes CPU + * and may even slightly inflate the payload. + */ +const SKIP_PATTERN = /^(image|audio|video|font)\//i; +/** + * Filter passed to the `compression` package. + * Return false → skip compression for this response. + * Return true → apply compression (subject to threshold check). + */ +function shouldCompress(req, res) { + // Let the caller opt out via a custom header. + if (req.headers["x-no-compression"]) + return false; + const contentType = res.getHeader("Content-Type"); + if (contentType && SKIP_PATTERN.test(contentType)) + return false; + // Delegate MIME + Accept-Encoding negotiation to the default filter. + return compression_1.default.filter(req, res); +} +const options = { + filter: shouldCompress, + threshold: THRESHOLD, + // zlib level 6 — balanced trade-off between ratio and CPU cost. + // Level 9 gives ~3% better ratio at ~4× CPU — not worth it for an API. + level: 6, +}; +/** + * Wraps the `compression` package with a diagnostic layer that records + * original vs. compressed byte counts for every compressed response. + */ +function compressionMiddleware(req, res, next) { + // Accumulate uncompressed byte count by intercepting write/end. + let originalBytes = 0; + const _write = res.write.bind(res); + const _end = res.end.bind(res); + res.write = function (chunk, encoding, cb) { + if (chunk) { + originalBytes += Buffer.byteLength(chunk, (typeof encoding === "string" + ? encoding + : "utf8")); + } + return _write(chunk, encoding, cb); + }; + res.end = function (chunk, encoding, cb) { + if (chunk) { + originalBytes += Buffer.byteLength(chunk, (typeof encoding === "string" + ? encoding + : "utf8")); + } + res.on("finish", () => { + const enc = res.getHeader("Content-Encoding"); + if (enc) { + const compressed = parseInt(res.getHeader("Content-Length") || "0", 10); + const ratio = originalBytes > 0 + ? ((1 - compressed / originalBytes) * 100).toFixed(1) + : "0"; + logger.debug("response compressed", { + method: req.method, + path: req.path, + encoding: enc, + originalBytes, + compressedBytes: compressed, + ratio: `${ratio}%`, + }); + } + }); + return _end(chunk, encoding, cb); + }; + (0, compression_1.default)(options)(req, res, next); +} diff --git a/backend/dist/middleware/intakeRateLimit.js b/backend/dist/middleware/intakeRateLimit.js new file mode 100644 index 00000000..d310baf4 --- /dev/null +++ b/backend/dist/middleware/intakeRateLimit.js @@ -0,0 +1,117 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.effectiveRpm = effectiveRpm; +exports.clientIp = clientIp; +exports.isPublicIntakeRoute = isPublicIntakeRoute; +exports.intakeRateLimit = intakeRateLimit; +exports.intakeBucketCount = intakeBucketCount; +const db_1 = require("../config/db"); +const tracing_1 = require("../utils/tracing"); +const buckets = new Map(); +function envInt(key, fallback) { + const v = process.env[key]; + if (!v) + return fallback; + const n = parseInt(v, 10); + return Number.isFinite(n) && n > 0 ? n : fallback; +} +/** Shrink the per-minute quota when the pg pool is under pressure. */ +function effectiveRpm() { + const base = envInt("RATE_LIMIT_RPM", 60); + const floor = envInt("RATE_LIMIT_MIN_RPM", 15); + const stats = (0, db_1.getPoolHealthStats)(); + const max = Math.max(stats.maxConnections, 1); + const activeRatio = stats.activeConnections / max; + const waitPenalty = stats.waitingRequests > 0 ? 0.35 + Math.min(0.35, stats.waitingRequests / 8) : 0; + const pressure = Math.min(0.85, activeRatio * 0.45 + waitPenalty); + return Math.max(floor, Math.floor(base * (1 - pressure))); +} +function burstCap(rpm) { + const burst = envInt("RATE_LIMIT_BURST", 10); + return rpm + burst; +} +function takeToken(key) { + const rpm = effectiveRpm(); + const cap = burstCap(rpm); + const refillPerMs = rpm / 60_000; + const now = Date.now(); + let bucket = buckets.get(key); + if (!bucket) { + bucket = { tokens: cap, lastMs: now }; + buckets.set(key, bucket); + } + const elapsed = now - bucket.lastMs; + bucket.tokens = Math.min(cap, bucket.tokens + elapsed * refillPerMs); + bucket.lastMs = now; + if (bucket.tokens < 1) { + const retryAfter = Math.max(1, Math.ceil((1 - bucket.tokens) / refillPerMs / 1000)); + return { ok: false, retryAfter }; + } + bucket.tokens -= 1; + return { ok: true }; +} +function clientIp(req) { + const forwarded = req.headers["x-forwarded-for"]; + if (typeof forwarded === "string" && forwarded.length > 0) { + const first = forwarded.split(",")[0]?.trim(); + if (first) + return first; + } + return req.ip || req.socket.remoteAddress || "127.0.0.1"; +} +/** Unauthenticated write paths that accept external submissions. */ +function isPublicIntakeRoute(req) { + if (req.method !== "POST") + return false; + const path = req.originalUrl.split("?")[0] ?? req.path; + if (path.startsWith("/api/v1/auth")) + return true; + if (path === "/api/v1/jobs" || path === "/api/v1/jobs/") + return true; + if (/^\/api\/v1\/jobs\/[^/]+\/bids\/?$/.test(path)) + return true; + if (path.startsWith("/api/v1/uploads")) + return true; + if (path.startsWith("/api/v1/bulk")) + return true; + return false; +} +function intakeRateLimit(req, res, next) { + if (!isPublicIntakeRoute(req)) { + next(); + return; + } + const key = clientIp(req); + const result = takeToken(key); + if (result.ok) { + next(); + return; + } + const rpm = effectiveRpm(); + tracing_1.logger.warn("Public intake rate limit exceeded", { + ip: key, + path: req.originalUrl, + retryAfterSeconds: result.retryAfter, + effectiveRpm: rpm, + }); + res.setHeader("Retry-After", String(result.retryAfter)); + res.status(429).json({ + error: "rate limit exceeded", + retry_after_seconds: result.retryAfter, + }); +} +// Drop idle buckets so long-running processes do not grow memory without bound. +const pruneMs = 10 * 60 * 1000; +const pruneTimer = setInterval(() => { + const cutoff = Date.now() - pruneMs; + for (const [key, bucket] of buckets) { + if (bucket.lastMs < cutoff) + buckets.delete(key); + } +}, 60_000); +if (typeof pruneTimer === "object" && "unref" in pruneTimer) { + pruneTimer.unref(); +} +function intakeBucketCount() { + return buckets.size; +} diff --git a/backend/dist/middleware/metrics.js b/backend/dist/middleware/metrics.js new file mode 100644 index 00000000..741c9b1a --- /dev/null +++ b/backend/dist/middleware/metrics.js @@ -0,0 +1,83 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.metricsMiddleware = metricsMiddleware; +const metrics_1 = require("../utils/metrics"); +/** + * Express middleware that collects Prometheus-compatible HTTP metrics. + * + * Tracks: + * - Request duration (histogram) + * - Total request count (counter) + * - Active concurrent requests (gauge) + * - Request body size (histogram) + * - Response body size (histogram) + * + * Sensitive paths like `/api/v1/metrics` are excluded to avoid + * infinite scrape loops and metric noise. + */ +const EXCLUDED_PATHS = new Set(["/api/v1/metrics", "/metrics", "/favicon.ico"]); +function metricsMiddleware(req, res, next) { + // Skip metrics endpoint to avoid scrape loop + if (EXCLUDED_PATHS.has(req.path) || req.path.startsWith("/api/v1/metrics")) { + next(); + return; + } + const method = req.method; + // Normalize path to avoid unbounded label cardinality from dynamic segments + const path = normalizeRoutePath(req.path); + // Track active requests + metrics_1.httpActiveRequests.inc({ method }); + // Track request body size (if applicable) + const contentLength = parseInt(req.headers["content-length"] || "0", 10); + if (contentLength > 0) { + metrics_1.httpRequestSize.observe({ method, path }, contentLength); + } + // Capture timing and response data on finish + const startTime = process.hrtime(); + res.on("finish", () => { + const duration = process.hrtime(startTime); + const durationMs = duration[0] * 1000 + duration[1] / 1_000_000; + const statusCode = String(res.statusCode); + // Observe duration histogram + metrics_1.httpRequestDuration.observe({ method, path, status_code: statusCode }, durationMs); + // Increment total request counter + metrics_1.httpRequestsTotal.inc({ method, path, status_code: statusCode }); + // Decrement active request gauge + metrics_1.httpActiveRequests.dec({ method }); + // Track response size from Content-Length header + const responseLength = parseInt(res.getHeader("content-length") || "0", 10); + if (responseLength > 0) { + metrics_1.httpResponseSize.observe({ method, path, status_code: statusCode }, responseLength); + } + }); + next(); +} +/** + * Normalizes route paths to prevent unbounded label cardinality from + * dynamic route parameters like UUIDs, IDs, or wallet addresses. + * + * Example: + * /api/v1/jobs/abc-123-def -> /api/v1/jobs/:id + * /api/v1/jobs/abc-123-def/bids/xyz-789 -> /api/v1/jobs/:id/bids/:bidId + */ +function normalizeRoutePath(urlPath) { + // Remove query string + const path = urlPath.split("?")[0]; + // Replace UUID or alphanumeric IDs with generic placeholders + const segments = path.split("/").map((segment) => { + // UUID pattern + if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(segment)) { + return ":id"; + } + // Stellar-style wallet address (G...) + if (/^G[A-Z0-9]{55}$/i.test(segment)) { + return ":address"; + } + // Numeric IDs + if (/^\d+$/.test(segment)) { + return ":numId"; + } + return segment; + }); + return segments.join("/"); +} diff --git a/backend/dist/middleware/sanitize.js b/backend/dist/middleware/sanitize.js new file mode 100644 index 00000000..0fdc19c5 --- /dev/null +++ b/backend/dist/middleware/sanitize.js @@ -0,0 +1,203 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.isSqlSafe = isSqlSafe; +exports.sanitizeSqlInput = sanitizeSqlInput; +exports.sqlInjectionGuard = sqlInjectionGuard; +exports.findSqlInjectionInObject = findSqlInjectionInObject; +exports.validateQuerySafety = validateQuerySafety; +const tracing_1 = require("../utils/tracing"); +// --------------------------------------------------------------------------- +// SQL Injection Detection Patterns +// --------------------------------------------------------------------------- +/** + * Regular expressions that match common SQL injection patterns. + * These are used as a defense-in-depth layer on top of parameterized queries. + */ +const SQL_INJECTION_PATTERNS = [ + // SQL comment injection + /--/g, + /\/\*.*\*\//g, + // UNION-based injection + /\bUNION\b\s+\b(SELECT|ALL|DISTINCT)\b/i, + // OR/AND always-true patterns + /'\s+OR\s+'1'\s*=\s*'1/i, + /'\s+OR\s+'1'\s*=\s*'1'/i, + /"\s+OR\s+"1"\s*=\s*"1/i, + /OR\s+1\s*=\s*1/i, + /AND\s+1\s*=\s*1/i, + // DML/DDL statements (not just SELECT) + /\b(DROP|ALTER|TRUNCATE|EXEC|EXECUTE)\b\s/i, + /\bINSERT\b\s+\bINTO\b/i, + /\bDELETE\b\s+\bFROM\b/i, + /\bUPDATE\b\s+\w+\s+\bSET\b/i, + /\bCREATE\b\s+\b(TABLE|INDEX|VIEW|PROCEDURE|FUNCTION|TRIGGER)\b/i, + // PostgreSQL-specific dangerous functions + /\bpg_sleep\b/i, + /\bpg_read_file\b/i, + /\bpg_write_file\b/i, + /\bpg_read_binary_file\b/i, + /\bcopy\s+\w+\s+(FROM|TO)\s+'/i, + // Stacked queries (SQL Server style, but some libraries allow it) + /;\s*\b(SELECT|INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|EXEC)\b/i, + // Information_schema probing (reconnaissance) + /\bINFORMATION_SCHEMA\b/i, + /\bpg_catalog\b/i, + /\bpg_class\b/i, + /\bpg_attribute\b/i, + // Hex/Unicode encoding used to bypass filters + /(?:0x[0-9a-fA-F]{4,})/i, + // WAITFOR DELAY (time-based blind injection) + /\bWAITFOR\b\s+\bDELAY\b/i, + // INTO OUTFILE/DUMPFILE (file write exploits) + /\bINTO\s+(OUTFILE|DUMPFILE)\b/i, + // LOAD_FILE function + /\bLOAD_FILE\b\s*\(/i, + // CONCAT/CHAR obfuscation + /\bCONCAT\b\s*\(.*0x[0-9a-f]/i, + /\bCHAR\b\s*\(.*\d{2,}/i, +]; +/** + * Checks a string value for SQL injection patterns. + * Returns true if the value appears safe (no injection patterns detected). + */ +function isSqlSafe(value) { + return !SQL_INJECTION_PATTERNS.some((pattern) => pattern.test(value)); +} +/** + * Sanitizes a string value by stripping known SQL injection patterns. + * This is a fallback sanitization — parameterized queries remain the primary defense. + */ +function sanitizeSqlInput(value) { + let sanitized = value; + for (const pattern of SQL_INJECTION_PATTERNS) { + sanitized = sanitized.replace(pattern, ""); + } + return sanitized; +} +// --------------------------------------------------------------------------- +// Input Validation Middleware +// --------------------------------------------------------------------------- +/** + * Express middleware that inspects query string parameters and request body + * fields for potential SQL injection patterns. + * + * When a potential injection is detected, the request is rejected with a + * 400 status before the value ever reaches a database query. + */ +function sqlInjectionGuard(req, res, next) { + // Skip non-data methods and the metrics endpoint + if (["GET", "HEAD", "OPTIONS"].includes(req.method) && !hasQueryParams(req)) { + next(); + return; + } + // Check query string parameters + if (req.query) { + for (const [key, value] of Object.entries(req.query)) { + if (typeof value === "string" && !isSqlSafe(value)) { + tracing_1.logger.warn("SQL injection attempt detected in query params", { + key, + value: value.substring(0, 100), + ip: req.ip, + path: req.originalUrl, + }); + res.status(400).json({ + error: "Invalid query parameter detected", + detail: `Parameter '${key}' contains invalid characters`, + }); + return; + } + // Handle array query params + if (Array.isArray(value)) { + for (const item of value) { + if (typeof item === "string" && !isSqlSafe(item)) { + tracing_1.logger.warn("SQL injection attempt detected in query params array", { + key, + ip: req.ip, + path: req.originalUrl, + }); + res.status(400).json({ + error: "Invalid query parameter detected", + detail: `Parameter '${key}' contains invalid characters`, + }); + return; + } + } + } + } + } + // Check body parameters deeply + if (req.body && typeof req.body === "object") { + const violations = findSqlInjectionInObject(req.body); + if (violations.length > 0) { + tracing_1.logger.warn("SQL injection attempt detected in request body", { + violations, + ip: req.ip, + path: req.originalUrl, + }); + res.status(400).json({ + error: "Invalid request body detected", + detail: "Request body contains invalid characters", + }); + return; + } + } + next(); +} +function hasQueryParams(req) { + return Object.keys(req.query).length > 0; +} +/** + * Recursively searches an object for SQL injection patterns in string values. + * Returns a list of field paths that contain potentially dangerous content. + */ +function findSqlInjectionInObject(obj, path = "", results = [], depth = 0) { + if (depth > 10) + return results; // Prevent infinite recursion + if (typeof obj === "string") { + if (!isSqlSafe(obj)) { + results.push(path); + } + return results; + } + if (obj && typeof obj === "object" && !Array.isArray(obj)) { + for (const [key, value] of Object.entries(obj)) { + const newPath = path ? `${path}.${key}` : key; + findSqlInjectionInObject(value, newPath, results, depth + 1); + } + return results; + } + if (Array.isArray(obj)) { + for (let i = 0; i < obj.length; i++) { + const newPath = `${path}[${i}]`; + findSqlInjectionInObject(obj[i], newPath, results, depth + 1); + } + return results; + } + return results; +} +// --------------------------------------------------------------------------- +// Query Builder Validation +// --------------------------------------------------------------------------- +/** + * Validates that a raw SQL query uses parameterized queries ($1, $2, ...) + * and does not interpolate values directly into the SQL string. + * + * Call this in route handlers before executing raw SQL to catch accidental + * string interpolation during code reviews and development. + * + * @throws Error if the query contains potentially dangerous patterns + */ +function validateQuerySafety(sql) { + // Check for string concatenation patterns in query building + const concatPatterns = [ + /WHERE\s+\w+\s*=\s*['"]\s*\+\s*/i, + /WHERE\s+\w+\s*=\s*`\s*\$\{/i, + /'\s*\+\s*req\./i, + /'\s*\+\s*params/i, + ]; + for (const pattern of concatPatterns) { + if (pattern.test(sql)) { + throw new Error("Potential SQL injection: query appears to use string concatenation instead of parameterized values"); + } + } +} diff --git a/backend/dist/routes/auth.js b/backend/dist/routes/auth.js index b0eda709..749822f3 100644 --- a/backend/dist/routes/auth.js +++ b/backend/dist/routes/auth.js @@ -1,108 +1,425 @@ "use strict"; +/** + * auth.ts — Secure JWT Session + Refresh Token Flow + */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); +exports.isTokenBlacklisted = isTokenBlacklisted; +exports.blacklistToken = blacklistToken; const express_1 = require("express"); const crypto_1 = __importDefault(require("crypto")); -const db_1 = require("../config/db"); +const jsonwebtoken_1 = __importDefault(require("jsonwebtoken")); +const zod_1 = require("zod"); const stellar_sdk_1 = require("@stellar/stellar-sdk"); +const db_1 = require("../config/db"); +const redis_1 = require("../config/redis"); const router = (0, express_1.Router)(); -// Scaffold the auth challenge route +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- +const CHALLENGE_TTL_MS = 5 * 60 * 1000; +const ACCESS_TOKEN_TTL_SEC = 15 * 60; +const REFRESH_TOKEN_TTL_SEC = 7 * 24 * 60 * 60; +const STELLAR_SIGN_PREFIX = "Stellar Signed Message:\n"; +const BLACKLIST_NS = "jwt:blacklist:"; +const ACCESS_TOKEN_COOKIE = "lance_access_token"; +const REFRESH_TOKEN_COOKIE = "lance_refresh_token"; +const isProduction = process.env.NODE_ENV === "production"; +const COOKIE_BASE_OPTIONS = { + httpOnly: true, + secure: isProduction, + sameSite: isProduction ? "strict" : "lax", + path: "/", +}; +// --------------------------------------------------------------------------- +// Validation Schemas +// --------------------------------------------------------------------------- +const ChallengeRequestSchema = zod_1.z.object({ + address: zod_1.z.string().min(1).max(128), +}); +const VerifyRequestSchema = zod_1.z.object({ + address: zod_1.z.string().min(1).max(128), + signature: zod_1.z.union([ + zod_1.z.string().min(1).max(1024), + zod_1.z.object({ + signature: zod_1.z.string().min(1).max(1024), + }), + ]), +}); +const RefreshRequestSchema = zod_1.z.object({ + refresh_token: zod_1.z.string().optional(), +}); +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- +function sanitizeStellarAddress(rawAddress) { + if (typeof rawAddress !== "string") { + return null; + } + const address = rawAddress.trim(); + if (!/^G[A-Z2-7]{55}$/.test(address)) { + return null; + } + try { + const decoded = stellar_sdk_1.StrKey.decodeEd25519PublicKey(address); + if (decoded.length !== 32 || + !stellar_sdk_1.StrKey.isValidEd25519PublicKey(address)) { + return null; + } + return stellar_sdk_1.StrKey.encodeEd25519PublicKey(decoded) === address + ? address + : null; + } + catch { + return null; + } +} +function buildMessageHash(challenge) { + const payload = Buffer.from(STELLAR_SIGN_PREFIX + challenge, "utf8"); + return crypto_1.default.createHash("sha256").update(payload).digest(); +} +function decodeSignature(raw) { + const trimmed = raw.trim(); + const hexPattern = /^[0-9a-fA-F]+$/; + if (hexPattern.test(trimmed) && trimmed.length % 2 === 0) { + return Buffer.from(trimmed, "hex"); + } + return Buffer.from(trimmed, "base64"); +} +function timingSafeEqualStrings(a, b) { + const aBuf = Buffer.from(a); + const bBuf = Buffer.from(b); + if (aBuf.length !== bBuf.length) { + return false; + } + return crypto_1.default.timingSafeEqual(aBuf, bBuf); +} +function issueAccessToken(address, jti) { + const secret = process.env.JWT_SECRET; + if (!secret) { + throw new Error("JWT_SECRET environment variable is not set"); + } + const options = { + subject: address, + jwtid: jti, + expiresIn: ACCESS_TOKEN_TTL_SEC, + issuer: "lance-marketplace", + audience: "lance-frontend", + }; + return jsonwebtoken_1.default.sign({ address }, secret, options); +} +async function issueRefreshToken(address, previousTokenId) { + // If there was a previous token ID (hash), revoke it in Redis + if (previousTokenId !== undefined) { + await redis_1.redis.del(`refresh_token:${previousTokenId}`); + } + const rawToken = crypto_1.default.randomBytes(48).toString("base64url"); + const hashedToken = crypto_1.default + .createHash("sha256") + .update(rawToken) + .digest("hex"); + const expiresAt = new Date(Date.now() + REFRESH_TOKEN_TTL_SEC * 1000); + // Store refresh token in Redis with TTL + await redis_1.redis.set(`refresh_token:${hashedToken}`, JSON.stringify({ + token_hash: hashedToken, + address, + expires_at: expiresAt.toISOString(), + revoked: false, + }), "EX", REFRESH_TOKEN_TTL_SEC, "NX"); + return { + rawToken, + hashedToken, + }; +} +async function blacklistToken(jti, expiresAt) { + const ttlSeconds = Math.max(1, expiresAt - Math.floor(Date.now() / 1000)); + await redis_1.redis.set(`${BLACKLIST_NS}${jti}`, "1", "EX", ttlSeconds, "NX"); +} +async function isTokenBlacklisted(jti) { + const result = await redis_1.redis.get(`${BLACKLIST_NS}${jti}`); + return result !== null; +} router.post("/challenge", async (req, res) => { try { - const { address } = req.body; + const parsed = ChallengeRequestSchema.safeParse(req.body); + if (!parsed.success) { + return res.status(400).json({ + error: "Invalid request body", + }); + } + const address = sanitizeStellarAddress(parsed.data.address); if (!address) { - return res.status(400).json({ error: "Address is required" }); + return res.status(400).json({ + error: "Invalid Stellar address", + }); } - // Generate challenge matching the old Rust backend format const nonce = crypto_1.default.randomUUID(); - const challenge = `Lance wants you to sign in with your Stellar account:\n${address}\n\nNonce: ${nonce}`; - // Expiration time: 5 minutes from now - const expiresAt = new Date(Date.now() + 5 * 60 * 1000); - // Save or update the challenge in the database + const issuedAt = new Date(); + const expiresAt = new Date(issuedAt.getTime() + CHALLENGE_TTL_MS); + const challenge = `Lance wants you to sign in with your Stellar account:\n` + + `${address}\n\n` + + `Nonce: ${nonce}\n` + + `Issued At: ${issuedAt.toISOString()}`; await db_1.prisma.auth_challenges.upsert({ - where: { address }, - update: { challenge, expires_at: expiresAt }, - create: { address, challenge, expires_at: expiresAt }, + where: { + address, + }, + update: { + challenge, + issued_at: issuedAt, + expires_at: expiresAt, + }, + create: { + address, + challenge, + issued_at: issuedAt, + expires_at: expiresAt, + }, + }); + return res.status(200).json({ + challenge, + expires_at: expiresAt.toISOString(), }); - res.json({ challenge }); } catch (error) { - console.error("Auth challenge error:", error); - res.status(500).json({ error: "Internal server error" }); + console.error("[auth/challenge]", error); + return res.status(500).json({ + error: "Internal server error", + }); } }); -// Verify route router.post("/verify", async (req, res) => { try { - const { address, signature } = req.body; - if (!address || !signature) { - return res.status(400).json({ error: "Address and signature are required" }); + const parsed = VerifyRequestSchema.safeParse(req.body); + if (!parsed.success) { + return res.status(400).json({ + error: "Invalid request body", + }); } - // 1. Fetch the challenge - const record = await db_1.prisma.auth_challenges.findUnique({ where: { address } }); - if (!record) { - return res.status(404).json({ error: "Challenge not found. Please request a new challenge." }); + const address = sanitizeStellarAddress(parsed.data.address); + if (!address) { + return res.status(400).json({ + error: "Invalid Stellar address", + }); + } + let signature = parsed.data.signature; + if (typeof signature === "object" && + "signature" in signature) { + signature = signature.signature; } - if (record.expires_at < new Date()) { - return res.status(400).json({ error: "Challenge expired" }); + const challengeRecord = await db_1.prisma.auth_challenges.findUnique({ + where: { + address, + }, + }); + if (!challengeRecord) { + return res.status(404).json({ + error: "No challenge found", + }); + } + if (challengeRecord.expires_at.getTime() <= + Date.now()) { + await db_1.prisma.auth_challenges + .delete({ + where: { + address, + }, + }) + .catch(() => { }); + return res.status(401).json({ + error: "Challenge expired", + }); } - // 2. Verify the signature let isValid = false; try { const keypair = stellar_sdk_1.Keypair.fromPublicKey(address); - // Handle the case where signature is an object (some wallet kits wrap it) - const sigString = typeof signature === "object" && signature.signature - ? signature.signature - : typeof signature === "string" ? signature : ""; - const hexRegex = /^[0-9a-fA-F]+$/; - const signatureBuffer = hexRegex.test(sigString) && sigString.length % 2 === 0 - ? Buffer.from(sigString, "hex") - : Buffer.from(sigString, "base64"); - // Freighter (and stellar-wallets-kit) prefixes messages before hashing and signing to prevent spoofing - const SIGN_MESSAGE_PREFIX = "Stellar Signed Message:\n"; - const payloadBuffer = Buffer.from(SIGN_MESSAGE_PREFIX + record.challenge); - const messageHash = crypto_1.default.createHash("sha256").update(payloadBuffer).digest(); + const signatureBuffer = decodeSignature(signature); + const messageHash = buildMessageHash(challengeRecord.challenge); isValid = keypair.verify(messageHash, signatureBuffer); - // Fallback for mock wallet in E2E tests (it returns the literal string "mock-signature") - if (!isValid && process.env.NODE_ENV !== "production") { - if (signature === record.challenge || signature === "mock-signature") { - isValid = true; - } - } } catch (err) { - console.error("Signature verification failed structurally:", err); + console.warn("[auth/verify] Signature verification failed:", err); isValid = false; } - // For local dev/E2E tests - if (!isValid && process.env.NODE_ENV !== "production") { - if (signature === record.challenge || signature === "mock-signature") { + if (!isValid && + process.env.NODE_ENV !== "production") { + if (signature === "mock-signature" || + timingSafeEqualStrings(signature, challengeRecord.challenge)) { isValid = true; } } if (!isValid) { - return res.status(401).json({ error: "Invalid signature" }); - } - // 3. Delete the used challenge - await db_1.prisma.auth_challenges.delete({ where: { address } }); - // 4. Generate a session token - const token = crypto_1.default.randomUUID(); - const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days - // 5. Save the session - await db_1.prisma.sessions.create({ - data: { - token, + return res.status(401).json({ + error: "Invalid signature", + }); + } + await db_1.prisma.auth_challenges.delete({ + where: { address, - expires_at: expiresAt, }, }); - res.json({ token, address }); + const accessJti = crypto_1.default.randomUUID(); + const accessToken = issueAccessToken(address, accessJti); + const { rawToken: refreshToken } = await issueRefreshToken(address); + res.cookie(ACCESS_TOKEN_COOKIE, accessToken, { + ...COOKIE_BASE_OPTIONS, + maxAge: ACCESS_TOKEN_TTL_SEC * 1000, + }); + res.cookie(REFRESH_TOKEN_COOKIE, refreshToken, { + ...COOKIE_BASE_OPTIONS, + maxAge: REFRESH_TOKEN_TTL_SEC * 1000, + }); + return res.status(200).json({ + access_token: accessToken, + refresh_token: refreshToken, + token_type: "Bearer", + expires_in: ACCESS_TOKEN_TTL_SEC, + }); } catch (error) { - console.error("Auth verify error:", error); - res.status(500).json({ error: "Internal server error" }); + console.error("[auth/verify]", error); + return res.status(500).json({ + error: "Internal server error", + }); + } +}); +router.post("/refresh", async (req, res) => { + try { + const parsed = RefreshRequestSchema.safeParse(req.body); + if (!parsed.success) { + return res.status(400).json({ + error: "Invalid request body", + }); + } + let refreshToken = parsed.data.refresh_token; + if (!refreshToken) { + refreshToken = + req.cookies?.[REFRESH_TOKEN_COOKIE]; + } + if (!refreshToken || + typeof refreshToken !== "string") { + return res.status(400).json({ + error: "refresh_token is required", + }); + } + const incomingHash = crypto_1.default + .createHash("sha256") + .update(refreshToken) + .digest("hex"); + // Look up refresh token in Redis + const recordJson = await redis_1.redis.get(`refresh_token:${incomingHash}`); + const record = recordJson ? JSON.parse(recordJson) : null; + if (!record) { + return res.status(401).json({ + error: "Invalid refresh token", + }); + } + if (record.revoked) { + console.warn(`[auth/refresh] Revoked token replay attempt for ${record.address}`); + return res.status(401).json({ + error: "Refresh token has been revoked", + }); + } + if (record.expires_at.getTime() <= + Date.now()) { + return res.status(401).json({ + error: "Refresh token expired", + }); + } + const newAccessJti = crypto_1.default.randomUUID(); + const newAccessToken = issueAccessToken(record.address, newAccessJti); + const { rawToken: newRefreshToken, } = await issueRefreshToken(record.address, record.token_hash); + res.cookie(ACCESS_TOKEN_COOKIE, newAccessToken, { + ...COOKIE_BASE_OPTIONS, + maxAge: ACCESS_TOKEN_TTL_SEC * 1000, + }); + res.cookie(REFRESH_TOKEN_COOKIE, newRefreshToken, { + ...COOKIE_BASE_OPTIONS, + maxAge: REFRESH_TOKEN_TTL_SEC * 1000, + }); + return res.status(200).json({ + access_token: newAccessToken, + refresh_token: newRefreshToken, + token_type: "Bearer", + expires_in: ACCESS_TOKEN_TTL_SEC, + }); + } + catch (error) { + console.error("[auth/refresh]", error); + return res.status(500).json({ + error: "Internal server error", + }); + } +}); +// --------------------------------------------------------------------------- +// Route: POST /logout +// --------------------------------------------------------------------------- +router.post("/logout", async (req, res) => { + try { + let rawAccessToken = req.cookies?.[ACCESS_TOKEN_COOKIE]; + const authHeader = req.headers.authorization; + if (!rawAccessToken && + authHeader?.startsWith("Bearer ")) { + rawAccessToken = + authHeader.slice(7); + } + let refreshToken = req.cookies?.[REFRESH_TOKEN_COOKIE]; + const body = req.body; + if (!refreshToken && + body.refresh_token) { + refreshToken = + body.refresh_token; + } + if (rawAccessToken) { + const secret = process.env.JWT_SECRET; + if (secret) { + try { + const decoded = jsonwebtoken_1.default.verify(rawAccessToken, secret, { + issuer: "lance-marketplace", + audience: "lance-frontend", + }); + if (decoded.jti && + decoded.exp) { + await blacklistToken(decoded.jti, decoded.exp); + } + } + catch { + // Ignore invalid/expired token + } + } + } + if (refreshToken && + typeof refreshToken === + "string") { + const hash = crypto_1.default + .createHash("sha256") + .update(refreshToken) + .digest("hex"); + await db_1.prisma.refresh_tokens + .updateMany({ + where: { + token_hash: hash, + revoked: false, + }, + data: { + revoked: true, + }, + }) + .catch(() => { }); + } + res.clearCookie(ACCESS_TOKEN_COOKIE, COOKIE_BASE_OPTIONS); + res.clearCookie(REFRESH_TOKEN_COOKIE, COOKIE_BASE_OPTIONS); + return res.status(200).json({ + message: "Logged out successfully", + }); + } + catch (error) { + console.error("[auth/logout]", error); + return res.status(500).json({ + error: "Internal server error", + }); } }); exports.default = router; diff --git a/backend/dist/routes/pool.js b/backend/dist/routes/pool.js index 5c4ff084..c083c4e6 100644 --- a/backend/dist/routes/pool.js +++ b/backend/dist/routes/pool.js @@ -2,6 +2,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); const express_1 = require("express"); const db_1 = require("../config/db"); +const intakeRateLimit_1 = require("../middleware/intakeRateLimit"); const tracing_1 = require("../utils/tracing"); const router = (0, express_1.Router)(); /** @@ -104,6 +105,10 @@ router.get("/stats", async (req, res) => { uptimeSeconds: stats.uptimeSeconds, healthChecksPassed: stats.healthChecksPassed, healthChecksFailed: stats.healthChecksFailed, + intakeRateLimit: { + effectiveRpm: (0, intakeRateLimit_1.effectiveRpm)(), + trackedClients: (0, intakeRateLimit_1.intakeBucketCount)(), + }, }); } catch (error) { diff --git a/backend/dist/utils/metrics.js b/backend/dist/utils/metrics.js new file mode 100644 index 00000000..eec79d7a --- /dev/null +++ b/backend/dist/utils/metrics.js @@ -0,0 +1,152 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.registry = exports.transactionDuration = exports.disputesOpenedTotal = exports.bidsPlacedTotal = exports.jobsCreatedTotal = exports.dbPoolActiveConnections = exports.dbPoolWaitingRequests = exports.dbPoolIdleConnections = exports.dbPoolTotalConnections = exports.httpResponseSize = exports.httpRequestSize = exports.httpActiveRequests = exports.httpRequestsTotal = exports.httpRequestDuration = void 0; +exports.createMetricsRouter = createMetricsRouter; +exports.updatePoolMetrics = updatePoolMetrics; +const prom_client_1 = __importDefault(require("prom-client")); +const express_1 = require("express"); +const tracing_1 = require("./tracing"); +// --------------------------------------------------------------------------- +// Prometheus Metrics Registry +// --------------------------------------------------------------------------- +const registry = new prom_client_1.default.Registry(); +exports.registry = registry; +// Default metrics (Node.js runtime: CPU, memory, event loop, GC, etc.) +prom_client_1.default.collectDefaultMetrics({ register: registry }); +// --------------------------------------------------------------------------- +// Custom HTTP Metrics +// --------------------------------------------------------------------------- +/** Histogram tracking HTTP request duration in milliseconds, bucketed by path & method */ +exports.httpRequestDuration = new prom_client_1.default.Histogram({ + name: "http_request_duration_ms", + help: "Duration of HTTP requests in milliseconds", + labelNames: ["method", "path", "status_code"], + buckets: [5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000], + registers: [registry], +}); +/** Counter tracking total number of HTTP requests */ +exports.httpRequestsTotal = new prom_client_1.default.Counter({ + name: "http_requests_total", + help: "Total number of HTTP requests", + labelNames: ["method", "path", "status_code"], + registers: [registry], +}); +/** Gauge tracking currently active (in-flight) HTTP requests */ +exports.httpActiveRequests = new prom_client_1.default.Gauge({ + name: "http_active_requests", + help: "Number of currently active HTTP requests", + labelNames: ["method"], + registers: [registry], +}); +/** Histogram tracking HTTP request body size in bytes */ +exports.httpRequestSize = new prom_client_1.default.Histogram({ + name: "http_request_size_bytes", + help: "Size of HTTP request bodies in bytes", + labelNames: ["method", "path"], + buckets: [64, 256, 1024, 4096, 16384, 65536, 262144, 1048576, 4194304], + registers: [registry], +}); +/** Histogram tracking HTTP response body size in bytes */ +exports.httpResponseSize = new prom_client_1.default.Histogram({ + name: "http_response_size_bytes", + help: "Size of HTTP response bodies in bytes", + labelNames: ["method", "path", "status_code"], + buckets: [64, 256, 1024, 4096, 16384, 65536, 262144, 1048576, 4194304], + registers: [registry], +}); +// --------------------------------------------------------------------------- +// Database Pool Metrics +// --------------------------------------------------------------------------- +/** Gauge tracking the current total connections in the pg pool */ +exports.dbPoolTotalConnections = new prom_client_1.default.Gauge({ + name: "db_pool_total_connections", + help: "Current total number of connections in the database pool", + registers: [registry], +}); +/** Gauge tracking idle connections in the pg pool */ +exports.dbPoolIdleConnections = new prom_client_1.default.Gauge({ + name: "db_pool_idle_connections", + help: "Current number of idle connections in the database pool", + registers: [registry], +}); +/** Gauge tracking waiting requests in the pg pool */ +exports.dbPoolWaitingRequests = new prom_client_1.default.Gauge({ + name: "db_pool_waiting_requests", + help: "Current number of waiting requests in the database pool", + registers: [registry], +}); +/** Gauge tracking active connections (total - idle) in the pg pool */ +exports.dbPoolActiveConnections = new prom_client_1.default.Gauge({ + name: "db_pool_active_connections", + help: "Current number of active connections in the database pool", + registers: [registry], +}); +// --------------------------------------------------------------------------- +// Business Metrics +// --------------------------------------------------------------------------- +/** Counter tracking total jobs created */ +exports.jobsCreatedTotal = new prom_client_1.default.Counter({ + name: "jobs_created_total", + help: "Total number of jobs created", + labelNames: ["status"], + registers: [registry], +}); +/** Counter tracking total bids placed */ +exports.bidsPlacedTotal = new prom_client_1.default.Counter({ + name: "bids_placed_total", + help: "Total number of bids placed", + registers: [registry], +}); +/** Counter tracking total disputes opened */ +exports.disputesOpenedTotal = new prom_client_1.default.Counter({ + name: "disputes_opened_total", + help: "Total number of disputes opened", + registers: [registry], +}); +/** Histogram tracking transaction durations in milliseconds */ +exports.transactionDuration = new prom_client_1.default.Histogram({ + name: "transaction_duration_ms", + help: "Duration of database transactions in milliseconds", + labelNames: ["operation"], + buckets: [10, 25, 50, 100, 250, 500, 1000, 2500, 5000], + registers: [registry], +}); +// --------------------------------------------------------------------------- +// Metrics Endpoint +// --------------------------------------------------------------------------- +/** + * GET /api/v1/metrics + * + * Exposes all registered Prometheus metrics in plain-text format, + * suitable for scraping by Prometheus or any OpenMetrics-compatible collector. + */ +function createMetricsRouter() { + const router = (0, express_1.Router)(); + router.get("/", async (_req, res) => { + try { + res.setHeader("Content-Type", registry.contentType); + const metrics = await registry.metrics(); + res.status(200).send(metrics); + } + catch (error) { + tracing_1.logger.error("Failed to generate Prometheus metrics", { + error: error.message, + }); + res.status(500).json({ error: "Failed to generate metrics" }); + } + }); + return router; +} +/** + * Update database pool gauges with current stats. + * Called periodically and on each metrics scrape. + */ +function updatePoolMetrics(total, idle, waiting) { + exports.dbPoolTotalConnections.set(total); + exports.dbPoolIdleConnections.set(idle); + exports.dbPoolWaitingRequests.set(waiting); + exports.dbPoolActiveConnections.set(total - idle); +} diff --git a/backend/dist/utils/storage-cleanup.js b/backend/dist/utils/storage-cleanup.js new file mode 100644 index 00000000..dc5cd304 --- /dev/null +++ b/backend/dist/utils/storage-cleanup.js @@ -0,0 +1,246 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getStorageCleanupStats = getStorageCleanupStats; +exports.startStorageCleanup = startStorageCleanup; +exports.stopStorageCleanup = stopStorageCleanup; +const tracing_1 = require("../config/tracing"); +const fs_1 = __importDefault(require("fs")); +const path_1 = __importDefault(require("path")); +const logger = tracing_1.trace.getLogger("storage-cleanup"); +// --------------------------------------------------------------------------- +// Configuration +// --------------------------------------------------------------------------- +/** How often the cleanup job runs (ms). Default: 30 min */ +const CLEANUP_INTERVAL_MS = parseInt(process.env.STORAGE_CLEANUP_INTERVAL_MS || (30 * 60 * 1000).toString(), 10); +/** Max age (ms) for temp upload files before they are considered stale. Default: 1 hour */ +const STALE_FILE_AGE_MS = parseInt(process.env.STORAGE_STALE_FILE_AGE_MS || (60 * 60 * 1000).toString(), 10); +/** Disk usage warning threshold (percentage). Default: 80% */ +const DISK_WARN_THRESHOLD = parseInt(process.env.STORAGE_DISK_WARN_PCT || "80", 10); +/** Disk usage critical threshold (percentage). Default: 90% */ +const DISK_CRITICAL_THRESHOLD = parseInt(process.env.STORAGE_DISK_CRITICAL_PCT || "90", 10); +/** Temp directory to monitor for stale files */ +const TEMP_DIR = process.env.STORAGE_TEMP_DIR || "/tmp/lance-uploads"; +let lastRunAt = null; +let lastRunOk = true; +let lastError = null; +let filesCleaned = 0; +let bytesFreed = 0; +function getStorageCleanupStats() { + let diskUsagePercent = 0; + try { + const { size, available } = getDiskUsage(); + diskUsagePercent = size > 0 ? Math.round(((size - available) / size) * 100) : 0; + } + catch { + // Ignore disk usage errors in stats + } + return { + lastRunAt: lastRunAt ? lastRunAt.toISOString() : null, + lastRunOk, + lastError, + filesCleaned, + bytesFreed, + diskUsagePercent, + diskUsageWarn: diskUsagePercent >= DISK_WARN_THRESHOLD, + diskUsageCritical: diskUsagePercent >= DISK_CRITICAL_THRESHOLD, + intervalMs: CLEANUP_INTERVAL_MS, + }; +} +/** + * Returns disk usage statistics for the temp directory's mount point. + * Falls back to a reasonable default if the info is unavailable. + */ +function getDiskUsage() { + try { + const stats = fs_1.default.statfsSync(TEMP_DIR); + return { + size: stats.blocks * stats.bsize, + available: stats.bavail * stats.bsize, + }; + } + catch { + // statfs may not be available on all platforms + return { size: 0, available: 0 }; + } +} +function ensureTempDir() { + if (!fs_1.default.existsSync(TEMP_DIR)) { + fs_1.default.mkdirSync(TEMP_DIR, { recursive: true }); + logger.info("Created temp upload directory", { path: TEMP_DIR }); + } +} +// --------------------------------------------------------------------------- +// Cleanup Functions +// --------------------------------------------------------------------------- +/** + * Scans the temp upload directory for files older than the stale threshold + * and removes them. Records cleanup metrics for observability. + */ +async function cleanStaleTempFiles() { + let cleaned = 0; + let freed = 0; + const cutoff = Date.now() - STALE_FILE_AGE_MS; + try { + if (!fs_1.default.existsSync(TEMP_DIR)) { + return { count: 0, bytes: 0 }; + } + const entries = fs_1.default.readdirSync(TEMP_DIR, { withFileTypes: true }); + for (const entry of entries) { + if (entry.isFile()) { + const filePath = path_1.default.join(TEMP_DIR, entry.name); + try { + const stat = fs_1.default.statSync(filePath); + if (stat.mtimeMs < cutoff) { + const fileSize = stat.size; + fs_1.default.unlinkSync(filePath); + cleaned++; + freed += fileSize; + logger.debug("Cleaned stale temp file", { + file: entry.name, + size: fileSize, + ageHours: ((Date.now() - stat.mtimeMs) / 3600000).toFixed(1), + }); + } + } + catch (err) { + // File may have been deleted by another process — skip + logger.debug("Skipped temp file during cleanup", { + file: entry.name, + error: err.message, + }); + } + } + } + } + catch (err) { + logger.error("Failed to scan temp directory for stale files", { + error: err.message, + }); + } + return { count: cleaned, bytes: freed }; +} +/** + * Checks disk usage and logs warnings if thresholds are exceeded. + */ +async function checkDiskUsage() { + try { + const { size, available } = getDiskUsage(); + if (size === 0) + return; // Cannot determine disk usage + const usedPercent = Math.round(((size - available) / size) * 100); + const availableGb = (available / 1024 / 1024 / 1024).toFixed(1); + if (usedPercent >= DISK_CRITICAL_THRESHOLD) { + logger.error("CRITICAL: Disk usage exceeded critical threshold", { + usedPercent: `${usedPercent}%`, + threshold: `${DISK_CRITICAL_THRESHOLD}%`, + availableGb: `${availableGb} GB`, + }); + } + else if (usedPercent >= DISK_WARN_THRESHOLD) { + logger.warn("WARNING: Disk usage exceeded warning threshold", { + usedPercent: `${usedPercent}%`, + threshold: `${DISK_WARN_THRESHOLD}%`, + availableGb: `${availableGb} GB`, + }); + } + else { + logger.debug("Disk usage OK", { + usedPercent: `${usedPercent}%`, + availableGb: `${availableGb} GB`, + }); + } + } + catch (err) { + logger.debug("Could not determine disk usage", { error: err.message }); + } +} +/** + * Cleans up stale or orphaned upload metadata records from the database. + * Finds upload records that have no associated job or are otherwise stale. + */ +async function cleanStaleUploadRecords() { + try { + // This is a placeholder for when upload records are stored in the database. + // Currently, the uploads endpoint uses multer memory storage and returns + // mock IPFS hashes without persisting records. + // When upload tracking is added to the database, add cleanup logic here. + return 0; + } + catch (err) { + logger.error("Failed to clean stale upload records", { + error: err.message, + }); + return 0; + } +} +// --------------------------------------------------------------------------- +// Main Cleanup Cycle +// --------------------------------------------------------------------------- +async function runCleanupCycle() { + try { + ensureTempDir(); + // Phase 1: Stale temp file cleanup + const { count, bytes } = await cleanStaleTempFiles(); + filesCleaned += count; + bytesFreed += bytes; + // Phase 2: Disk usage check + await checkDiskUsage(); + // Phase 3: Stale DB record cleanup + const dbCleaned = await cleanStaleUploadRecords(); + if (count > 0 || dbCleaned > 0) { + logger.info("Storage cleanup cycle completed", { + filesCleaned: count, + bytesFreed: bytes, + dbRecordsCleaned: dbCleaned, + }); + } + lastRunOk = true; + lastError = null; + } + catch (err) { + lastRunOk = false; + lastError = err.message; + logger.error("Storage cleanup cycle failed", { error: err.message }); + } + finally { + lastRunAt = new Date(); + } +} +// --------------------------------------------------------------------------- +// Lifecycle +// --------------------------------------------------------------------------- +let cleanupTimer = null; +/** + * Starts the storage cleanup cron job. + * + * Runs on a configurable interval and performs: + * 1. Stale temp file cleanup — removes files older than the threshold + * 2. Disk usage monitoring — logs warnings when thresholds are exceeded + * 3. Stale upload record cleanup — removes orphaned DB records + */ +function startStorageCleanup() { + if (cleanupTimer) + return; + // Ensure temp dir exists + ensureTempDir(); + // Run once immediately, then on interval + runCleanupCycle(); + cleanupTimer = setInterval(runCleanupCycle, CLEANUP_INTERVAL_MS); + if (cleanupTimer && typeof cleanupTimer === "object" && "unref" in cleanupTimer) { + cleanupTimer.unref(); + } + logger.info(`Storage cleanup job started (interval: ${CLEANUP_INTERVAL_MS}ms)`); +} +/** + * Stops the storage cleanup cron job. + */ +function stopStorageCleanup() { + if (cleanupTimer) { + clearInterval(cleanupTimer); + cleanupTimer = null; + logger.info("Storage cleanup job stopped"); + } +} diff --git a/backend/src/middleware/sanitize.ts b/backend/src/middleware/sanitize.ts index b591611b..ce95e70c 100644 --- a/backend/src/middleware/sanitize.ts +++ b/backend/src/middleware/sanitize.ts @@ -112,10 +112,11 @@ export function sqlInjectionGuard(req: Request, res: Response, next: NextFunctio ip: req.ip, path: req.originalUrl, }); - return res.status(400).json({ + res.status(400).json({ error: "Invalid query parameter detected", detail: `Parameter '${key}' contains invalid characters`, }); + return; } // Handle array query params if (Array.isArray(value)) { @@ -126,10 +127,11 @@ export function sqlInjectionGuard(req: Request, res: Response, next: NextFunctio ip: req.ip, path: req.originalUrl, }); - return res.status(400).json({ + res.status(400).json({ error: "Invalid query parameter detected", detail: `Parameter '${key}' contains invalid characters`, }); + return; } } } @@ -145,10 +147,11 @@ export function sqlInjectionGuard(req: Request, res: Response, next: NextFunctio ip: req.ip, path: req.originalUrl, }); - return res.status(400).json({ + res.status(400).json({ error: "Invalid request body detected", detail: "Request body contains invalid characters", }); + return; } } @@ -163,7 +166,7 @@ function hasQueryParams(req: Request): boolean { * Recursively searches an object for SQL injection patterns in string values. * Returns a list of field paths that contain potentially dangerous content. */ -function findSqlInjectionInObject( +export function findSqlInjectionInObject( obj: any, path: string = "", results: string[] = [], diff --git a/backend/src/routes/auth.ts b/backend/src/routes/auth.ts index 6f1f5ac3..004763ba 100644 --- a/backend/src/routes/auth.ts +++ b/backend/src/routes/auth.ts @@ -145,17 +145,11 @@ function issueAccessToken(address: string, jti: string): string { async function issueRefreshToken( address: string, - previousTokenId?: number + previousTokenId?: string ): Promise<{ rawToken: string; hashedToken: string }> { + // If there was a previous token ID (hash), revoke it in Redis if (previousTokenId !== undefined) { - await prisma.refresh_tokens.update({ - where: { - id: previousTokenId, - }, - data: { - revoked: true, - }, - }); + await redis.del(`refresh_token:${previousTokenId}`); } const rawToken = crypto.randomBytes(48).toString("base64url"); @@ -169,14 +163,19 @@ async function issueRefreshToken( Date.now() + REFRESH_TOKEN_TTL_SEC * 1000 ); - await prisma.refresh_tokens.create({ - data: { + // Store refresh token in Redis with TTL + await redis.set( + `refresh_token:${hashedToken}`, + JSON.stringify({ token_hash: hashedToken, address, - expires_at: expiresAt, + expires_at: expiresAt.toISOString(), revoked: false, - }, - }); + }), + "EX", + REFRESH_TOKEN_TTL_SEC, + "NX" + ); return { rawToken, @@ -510,12 +509,11 @@ router.post( .update(refreshToken) .digest("hex"); - const record = - await prisma.refresh_tokens.findUnique({ - where: { - token_hash: incomingHash, - }, - }); + // Look up refresh token in Redis + const recordJson = + await redis.get(`refresh_token:${incomingHash}`); + + const record = recordJson ? JSON.parse(recordJson) : null; if (!record) { return res.status(401).json({ @@ -556,7 +554,7 @@ router.post( rawToken: newRefreshToken, } = await issueRefreshToken( record.address, - record.id + record.token_hash ); res.cookie( diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 8144329d..0b1a129e 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -10,5 +10,11 @@ "skipLibCheck": true, "resolveJsonModule": true }, - "include": ["src/**/*"] + "include": ["src/**/*"], + "exclude": [ + "node_modules", + "dist", + "**/*.test.ts", + "**/*.spec.ts" + ] } \ No newline at end of file diff --git a/contracts/escrow/src/lib.rs b/contracts/escrow/src/lib.rs index 05a33b49..2853035f 100644 --- a/contracts/escrow/src/lib.rs +++ b/contracts/escrow/src/lib.rs @@ -1,219 +1,346 @@ #![no_std] +use soroban_sdk::{contract, contractimpl, contracttype, Address, Env, Symbol, Vec, token::Client as TokenClient}; -use soroban_sdk::{contract, contractimpl, contracttype, Address, Env, Symbol}; +/* ----------------------------------------------------------------- + 1. Shared Enums & Storage Types +----------------------------------------------------------------- */ + +#[contracttype] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum EscrowStatus { + Funded, + WorkInProgress, + Completed, + Disputed, + Resolved, +} + +#[contracttype] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum MilestoneStatus { + Pending, + Released, +} -/// Storage keys for persistent and instance-based configuration. #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] pub enum DataKey { - Admin, // Address of the contract administrator - EscrowConfig(Address), // Configuration details per Escrow Agreement - SequenceCounter, // Global incrementing counter for release sequence numbers + Admin, + AgentJudge, + Treasury, + SequenceCounter, + Job(u64), + GuardFlag(u64), + Milestone(u64, u32), +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TreasuryConfig { + pub treasury_address: Address, + pub fee_bps: i128, } -/// Structural representation of an Escrow Agreement parameters. #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] -pub struct EscrowConfig { - pub arbiter: Address, - pub vendor: Address, +pub struct EscrowJob { + pub client: Address, + pub freelancer: Address, + pub token: Address, + pub total_amount: i128, + pub released_amount: i128, + pub status: EscrowStatus, + pub total_milestones: u32, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Milestone { pub amount: i128, - pub is_released: bool, + pub status: MilestoneStatus, } -/// Highly structured event payload for absolute indexer determinism. +/* ----------------------------------------------------------------- + 2. Optimized Event Schemas for Downstream Indexers +----------------------------------------------------------------- */ + #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] pub struct EscrowReleaseEvent { pub sequence_number: u64, + pub job_id: u64, pub client: Address, - pub vendor: Address, - pub amount_released: i128, + pub freelancer: Address, + pub token: Address, + pub total_amount: i128, + pub released_amount: i128, + pub status: EscrowStatus, } +/* ----------------------------------------------------------------- + 3. Smart Contract Implementation +----------------------------------------------------------------- */ + #[contract] pub struct LanceEscrowContract; #[contractimpl] impl LanceEscrowContract { - /// Initializes the global administrator for the Escrow deployment. - pub fn initialize(env: Env, admin: Address) { + + pub fn initialize(env: Env, admin: Address, agent_judge: Address) { if env.storage().instance().has(&DataKey::Admin) { panic!("Contract already initialized"); } env.storage().instance().set(&DataKey::Admin, &admin); + env.storage().instance().set(&DataKey::AgentJudge, &agent_judge); env.storage().instance().set(&DataKey::SequenceCounter, &0u64); } - /// Creates a new escrow agreement between a client and a vendor managed by an arbiter. - pub fn create_escrow( + pub fn set_agent_judge(env: Env, new_agent_judge: Address) { + let admin: Address = env.storage().instance().get(&DataKey::Admin).expect("uninitialized"); + admin.require_auth(); + env.storage().instance().set(&DataKey::AgentJudge, &new_agent_judge); + } + + pub fn set_treasury_config(env: Env, treasury_address: Address, fee_bps: i128) { + let admin: Address = env.storage().instance().get(&DataKey::Admin).expect("uninitialized"); + admin.require_auth(); + + if fee_bps < 0 || fee_bps > 10000 { + panic!("Invalid basis points value"); + } + + let config = TreasuryConfig { treasury_address, fee_bps }; + env.storage().instance().set(&DataKey::Treasury, &config); + } + + pub fn funding_job( env: Env, + job_id: u64, client: Address, - arbiter: Address, - vendor: Address, - amount: i128, + freelancer: Address, + token: Address, + milestone_amounts: Vec, ) { client.require_auth(); - if amount <= 0 { - panic!("Escrow amount must be positive"); + let key = DataKey::Job(job_id); + if env.storage().persistent().has(&key) { + panic!("Job already exists"); } - let config_key = DataKey::EscrowConfig(client.clone()); - if env.storage().persistent().has(&config_key) { - panic!("Escrow agreement already exists for this client"); + let mut total_amount: i128 = 0; + let total_milestones = milestone_amounts.len(); + + for i in 0..total_milestones { + let amt = milestone_amounts.get(i).unwrap(); + if amt <= 0 { + panic!("Milestone amount must be positive"); + } + total_amount = total_amount.checked_add(amt).expect("Overflow tracking total amount"); + + let m_key = DataKey::Milestone(job_id, i); + let milestone = Milestone { + amount: amt, + status: MilestoneStatus::Pending, + }; + env.storage().persistent().set(&m_key, &milestone); } - let config = EscrowConfig { - arbiter, - vendor, - amount, - is_released: false, + let token_client = TokenClient::new(&env, &token); + token_client.transfer(&client, &env.current_contract_address(), &total_amount); + + let job = EscrowJob { + client, + freelancer, + token, + total_amount, + released_amount: 0, + status: EscrowStatus::Funded, + total_milestones, }; - env.storage().persistent().set(&config_key, &config); + env.storage().persistent().set(&key, &job); } - /// Executes an escrow release. Emits optimized sequence events for downstream indexers. - pub fn release_escrow(env: Env, client: Address) { - let config_key = DataKey::EscrowConfig(client.clone()); + pub fn release_milestone(env: Env, job_id: u64, milestone_index: u32) { + let key = DataKey::Job(job_id); + let mut job: EscrowJob = env.storage().persistent().get(&key).expect("job not found"); + + job.client.require_auth(); - if !env.storage().persistent().has(&config_key) { - panic!("Escrow agreement not found"); + EscrowContract::reentrancy_guard_protect(&env, job_id); + + let m_key = DataKey::Milestone(job_id, milestone_index); + let mut milestone: Milestone = env.storage().persistent().get(&m_key).expect("invalid milestone index"); + + if milestone.status == MilestoneStatus::Released { + panic!("Milestone already released"); } - let mut config: EscrowConfig = env.storage().persistent().get(&config_key).unwrap(); + milestone.status = MilestoneStatus::Released; + env.storage().persistent().set(&m_key, &milestone); - if config.is_released { - panic!("Escrow funds have already been released"); + job.released_amount = job.released_amount.checked_add(milestone.amount).expect("Overflow on release math"); + + if job.status == EscrowStatus::Funded { + job.status = EscrowStatus::WorkInProgress; + } + if job.released_amount == job.total_amount { + job.status = EscrowStatus::Completed; } - config.arbiter.require_auth(); + env.storage().persistent().set(&key, &job); - let current_sequence: u64 = env.storage().instance().get(&DataKey::SequenceCounter).unwrap_or(0u64); - let next_sequence = current_sequence.checked_add(1).expect("Sequence counter overflow protection triggered"); + Self::payout_with_fee(&env, &job, milestone.amount); + + Self::emit_optimized_release_event(&env, job_id, &job); - config.is_released = true; + EscrowContract::reentrancy_guard_clear(&env, job_id); + } - env.storage().persistent().set(&config_key, &config); - env.storage().instance().set(&DataKey::SequenceCounter, &next_sequence); + pub fn dispute_job(env: Env, job_id: u64, caller: Address) { + caller.require_auth(); + + let key = DataKey::Job(job_id); + let mut job: EscrowJob = env.storage().persistent().get(&key).expect("job not found"); + + if job.status != EscrowStatus::Funded && job.status != EscrowStatus::WorkInProgress { + panic!("Job cannot be disputed in current state"); + } - let event_payload = EscrowReleaseEvent { - sequence_number: next_sequence, - client: client.clone(), - vendor: config.vendor.clone(), - amount_released: config.amount, - }; + if caller != job.client && caller != job.freelancer { + panic!("Unauthorized dispute initiator"); + } + + job.status = EscrowStatus::Disputed; + env.storage().persistent().set(&key, &job); + } - env.events().publish( - (Symbol::new(&env, "escrow_release"), client), - event_payload, - ); + pub fn resolve_dispute( + env: Env, + job_id: u64, + payee_amount: i128, + payer_amount: i128, + ) { + let judge: Address = env.storage().instance().get(&DataKey::AgentJudge).expect("uninitialized"); + judge.require_auth(); + + assert!(payee_amount >= 0, "payee_amount must be >= 0"); + assert!(payer_amount >= 0, "payer_amount must be >= 0"); + + let key = DataKey::Job(job_id); + let mut job: EscrowJob = env.storage().persistent().get(&key).expect("job not found"); + + assert!(job.status == EscrowStatus::Disputed, "job not disputed"); + + let total_payout = payee_amount.checked_add(payer_amount).expect("overflow calculation error"); + let remaining_pool = job.total_amount.checked_sub(job.released_amount).expect("Underflow tracking remaining pool"); + assert!(total_payout == remaining_pool, "Total payout must exactly match the remaining escrow funds"); + + let token_client = TokenClient::new(&env, &job.token); + + if payee_amount > 0 { + Self::payout_with_fee(&env, &job, payee_amount); + } + + if payer_amount > 0 { + token_client.transfer(&env.current_contract_address(), &job.client, &payer_amount); + } + + job.released_amount = job.released_amount.checked_add(total_payout).expect("Overflow updates"); + job.status = EscrowStatus::Resolved; + env.storage().persistent().set(&key, &job); + + Self::emit_optimized_release_event(&env, job_id, &job); } - /// Returns the configuration parameters of an active escrow. - pub fn get_escrow_config(env: Env, client: Address) -> Option { - let config_key = DataKey::EscrowConfig(client); - env.storage().persistent().get(&config_key) + /* ----------------------------------------------------------------- + Public Getters + ----------------------------------------------------------------- */ + + pub fn get_job(env: Env, job_id: u64) -> Option { + env.storage().persistent().get(&DataKey::Job(job_id)) } - /// Returns the global sequence tracking counter used for indexer syncing. - pub fn get_current_sequence(env: Env) -> u64 { - env.storage().instance().get(&DataKey::SequenceCounter).unwrap_or(0u64) + pub fn get_milestone(env: Env, job_id: u64, index: u32) -> Option { + env.storage().persistent().get(&DataKey::Milestone(job_id, index)) } -} -#[cfg(test)] -mod test { - use super::*; - use soroban_sdk::testutils::{Address as _, MockAuth, MockAuthInvoke}; - use soroban_sdk::{Address, Env, IntoVal}; - - #[test] - fn test_initialize_and_create_escrow() { - let env = Env::default(); - env.mock_all_auths(); - - let admin = Address::generate(&env); - let client = Address::generate(&env); - let arbiter = Address::generate(&env); - let vendor = Address::generate(&env); - - let contract_id = env.register_contract(None, LanceEscrowContract); - let cc = LanceEscrowContractClient::new(&env, &contract_id); - - cc.initialize(&admin); - cc.create_escrow(&client, &arbiter, &vendor, &10_000i128); - - let config = cc.get_escrow_config(&client).expect("config missing"); - assert_eq!(config.arbiter, arbiter); - assert_eq!(config.vendor, vendor); - assert_eq!(config.amount, 10_000); - assert!(!config.is_released); - assert_eq!(cc.get_current_sequence(), 0); + pub fn get_milestone_status(env: Env, job_id: u64) -> Vec { + let job: EscrowJob = env.storage().persistent().get(&DataKey::Job(job_id)).expect("job not found"); + let mut statuses = Vec::new(&env); + for i in 0..job.total_milestones { + let m: Milestone = env.storage().persistent().get(&DataKey::Milestone(job_id, i)).expect("missing milestone data"); + statuses.push_back(m.status); + } + statuses } - #[test] - fn test_release_escrow_increments_sequence() { - let env = Env::default(); - env.mock_all_auths(); + pub fn get_current_sequence(env: Env) -> u64 { + env.storage().instance().get(&DataKey::SequenceCounter).unwrap_or(0u64) + } - let admin = Address::generate(&env); - let client = Address::generate(&env); - let arbiter = Address::generate(&env); - let vendor = Address::generate(&env); + /* ----------------------------------------------------------------- + Internal Helper Functions + ----------------------------------------------------------------- */ + + fn payout_with_fee(env: &Env, job: &EscrowJob, amount: i128) { + let token_client = TokenClient::new(env, &job.token); + + if let Some(treasury_config) = env.storage().instance().get::<_, TreasuryConfig>(&DataKey::Treasury) { + let fee = amount + .checked_mul(treasury_config.fee_bps) + .expect("Overflow calculation fee step 1") + .checked_div(10000) + .expect("Division calculation step 2"); + + if fee > 0 { + let freelancer_amount = amount.checked_sub(fee).expect("Math subtraction verification"); + token_client.transfer(&env.current_contract_address(), &job.freelancer, &freelancer_amount); + token_client.transfer(&env.current_contract_address(), &treasury_config.treasury_address, &fee); + } else { + token_client.transfer(&env.current_contract_address(), &job.freelancer, &amount); + } + } else { + token_client.transfer(&env.current_contract_address(), &job.freelancer, &amount); + } + } - let contract_id = env.register_contract(None, LanceEscrowContract); - let cc = LanceEscrowContractClient::new(&env, &contract_id); + fn emit_optimized_release_event(env: &Env, job_id: u64, job: &EscrowJob) { + let current_sequence: u64 = env.storage().instance().get(&DataKey::SequenceCounter).unwrap_or(0u64); + let next_sequence = current_sequence.checked_add(1).expect("Sequence overflow safeguard triggered"); + env.storage().instance().set(&DataKey::SequenceCounter, &next_sequence); - cc.initialize(&admin); - cc.create_escrow(&client, &arbiter, &vendor, &5_000i128); - assert_eq!(cc.get_current_sequence(), 0); + let event_payload = EscrowReleaseEvent { + sequence_number: next_sequence, + job_id, + client: job.client.clone(), + freelancer: job.freelancer.clone(), + token: job.token.clone(), + total_amount: job.total_amount, + released_amount: job.released_amount, + status: job.status, + }; - cc.release_escrow(&client); + env.events().publish( + (Symbol::new(env, "escrow_release"), job_id), + event_payload, + ); + } +} - assert_eq!(cc.get_current_sequence(), 1); - let config = cc.get_escrow_config(&client).expect("config missing"); - assert!(config.is_released); +/// Namespace internal helper for Reentrancy Protections +struct EscrowContract; +impl EscrowContract { + fn reentrancy_guard_protect(env: &Env, job_id: u64) { + if env.storage().instance().has(&DataKey::GuardFlag(job_id)) { + panic!("Reentrancy Guard Triggered"); + } + env.storage().instance().set(&DataKey::GuardFlag(job_id), &true); } - #[test] - #[should_panic] - fn test_release_escrow_requires_arbiter_auth() { - let env = Env::default(); - let admin = Address::generate(&env); - let client = Address::generate(&env); - let arbiter = Address::generate(&env); - let vendor = Address::generate(&env); - - let contract_id = env.register_contract(None, LanceEscrowContract); - let cc = LanceEscrowContractClient::new(&env, &contract_id); - - env.mock_auths(&[ - MockAuth { - address: &admin, - invoke: &MockAuthInvoke { - contract: &contract_id, - fn_name: "initialize", - args: (admin.clone(),).into_val(&env), - sub_invokes: &[], - }, - }, - MockAuth { - address: &client, - invoke: &MockAuthInvoke { - contract: &contract_id, - fn_name: "create_escrow", - args: (client.clone(), arbiter.clone(), vendor.clone(), 5_000i128) - .into_val(&env), - sub_invokes: &[], - }, - }, - ]); - - cc.initialize(&admin); - cc.create_escrow(&client, &arbiter, &vendor, &5_000i128); - - cc.release_escrow(&client); + fn reentrancy_guard_clear(env: &Env, job_id: u64) { + env.storage().instance().remove(&DataKey::GuardFlag(job_id)); } }