diff --git a/Cargo.lock b/Cargo.lock index f0475b37..1c8cdd6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -804,6 +804,7 @@ checksum = "2bfcf67fea2815c2fc3b90873fae90957be12ff417335dfadc7f52927feb03b2" name = "escrow" version = "0.1.0" dependencies = [ + "job_registry", "soroban-sdk", ] diff --git a/contracts/escrow/Cargo.toml b/contracts/escrow/Cargo.toml index f7b3c711..d2d6996c 100644 --- a/contracts/escrow/Cargo.toml +++ b/contracts/escrow/Cargo.toml @@ -11,3 +11,4 @@ soroban-sdk = { workspace = true } [dev-dependencies] soroban-sdk = { workspace = true, features = ["testutils"] } +job_registry = { path = "../job_registry" } diff --git a/contracts/escrow/src/lib.rs b/contracts/escrow/src/lib.rs index 9413b0de..6097d180 100644 --- a/contracts/escrow/src/lib.rs +++ b/contracts/escrow/src/lib.rs @@ -1,6 +1,25 @@ #![no_std] -use soroban_sdk::{contract, contracterror, contractimpl, contracttype, token, Address, Env, Vec}; +use soroban_sdk::BytesN; +use soroban_sdk::{ + contract, contractclient, contracterror, contractimpl, contracttype, token, Address, Env, Vec, +}; + +#[contracterror] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum JobRegistryErrorCode { + JobNotFound = 1, + JobNotOpen = 2, + Unauthorized = 3, + InvalidInput = 4, + InvalidState = 5, + BidNotFound = 6, +} + +#[contractclient(name = "JobRegistryClient")] +pub trait JobRegistryContract { + fn mark_disputed(env: Env, job_id: u64) -> Result<(), JobRegistryErrorCode>; +} #[contracttype] #[derive(Clone, Debug, PartialEq)] @@ -47,6 +66,7 @@ pub enum DataKey { Job(u64), Admin, AgentJudge, + JobRegistry, } #[contracttype] @@ -76,6 +96,8 @@ pub enum EscrowError { InvalidState = 6, AmountMismatch = 7, NoPendingMilestones = 8, + JobRegistrySyncFailed = 9, + UpgradeUnauthorized = 10, } #[contracttype] @@ -111,11 +133,84 @@ pub struct OpenDisputeEvent { pub opened_at: u64, } +#[contracttype] +#[derive(Clone)] +pub struct JobRegistryConfiguredEvent { + pub configured_by: Address, + pub registry_contract: Address, + pub configured_at: u64, +} + +#[contracttype] +#[derive(Clone)] +pub struct RegistryDisputeSyncedEvent { + pub job_id: u64, + pub registry_contract: Address, + pub synced_at: u64, +} + +#[contracttype] +#[derive(Clone)] +pub struct ContractUpgradedEvent { + pub by_admin: Address, + pub new_wasm_hash: BytesN<32>, + pub upgraded_at: u64, +} + #[contract] pub struct EscrowContract; #[contractimpl] impl EscrowContract { + const INSTANCE_TTL_THRESHOLD: u32 = 50_000; + const INSTANCE_TTL_EXTEND_TO: u32 = 150_000; + const PERSISTENT_TTL_THRESHOLD: u32 = 50_000; + const PERSISTENT_TTL_EXTEND_TO: u32 = 150_000; + + fn bump_instance_ttl(env: &Env) { + env.storage() + .instance() + .extend_ttl(Self::INSTANCE_TTL_THRESHOLD, Self::INSTANCE_TTL_EXTEND_TO); + } + + fn bump_job_ttl(env: &Env, key: &DataKey) { + if env.storage().persistent().has(key) { + env.storage().persistent().extend_ttl( + key, + Self::PERSISTENT_TTL_THRESHOLD, + Self::PERSISTENT_TTL_EXTEND_TO, + ); + } + } + + fn sync_dispute_to_job_registry(env: &Env, job_id: u64) -> Result<(), EscrowError> { + Self::bump_instance_ttl(env); + let Some(registry_contract) = env + .storage() + .instance() + .get::<_, Address>(&DataKey::JobRegistry) + else { + return Ok(()); + }; + + let client = JobRegistryClient::new(env, ®istry_contract); + client + .try_mark_disputed(&job_id) + .map_err(|_| EscrowError::JobRegistrySyncFailed)? + .map_err(|_| EscrowError::JobRegistrySyncFailed)?; + + env.events().publish( + ("escrow", "RegistryDisputeSynced"), + RegistryDisputeSyncedEvent { + job_id, + registry_contract, + synced_at: env.ledger().timestamp(), + }, + ); + + Ok(()) + } + pub fn initialize(env: Env, admin: Address, agent_judge: Address) -> Result<(), EscrowError> { // Prevent double initialization if env.storage().instance().has(&DataKey::Admin) { @@ -138,6 +233,8 @@ impl EscrowContract { (admin.clone(), agent_judge.clone(), env.ledger().timestamp()), ); + Self::bump_instance_ttl(&env); + Ok(()) } /// Admin can update the Agent Judge address. @@ -169,6 +266,68 @@ impl EscrowContract { ), ); + Self::bump_instance_ttl(&env); + + Ok(()) + } + + /// Admin configures the JobRegistry contract address used for cross-contract sync. + pub fn set_job_registry(env: Env, job_registry: Address) -> Result<(), EscrowError> { + let admin: Address = env + .storage() + .instance() + .get(&DataKey::Admin) + .ok_or(EscrowError::NotInitialized)?; + admin.require_auth(); + + env.storage() + .instance() + .set(&DataKey::JobRegistry, &job_registry); + + env.events().publish( + ("escrow", "JobRegistryConfigured"), + JobRegistryConfiguredEvent { + configured_by: admin, + registry_contract: job_registry, + configured_at: env.ledger().timestamp(), + }, + ); + + Self::bump_instance_ttl(&env); + + Ok(()) + } + + /// Upgrades the current contract WASM. Only callable by admin. + pub fn upgrade( + env: Env, + caller: Address, + new_wasm_hash: BytesN<32>, + ) -> Result<(), EscrowError> { + Self::bump_instance_ttl(&env); + caller.require_auth(); + + let admin: Address = env + .storage() + .instance() + .get(&DataKey::Admin) + .ok_or(EscrowError::NotInitialized)?; + + if caller != admin { + return Err(EscrowError::UpgradeUnauthorized); + } + + env.deployer() + .update_current_contract_wasm(new_wasm_hash.clone()); + env.events().publish( + ("escrow", "ContractUpgraded"), + ContractUpgradedEvent { + by_admin: caller, + new_wasm_hash, + upgraded_at: env.ledger().timestamp(), + }, + ); + Ok(()) } @@ -200,12 +359,14 @@ impl EscrowContract { milestones: Vec::new(&env), }; env.storage().persistent().set(&key, &job); + Self::bump_job_ttl(&env, &key); } /// 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"); + Self::bump_job_ttl(&env, &key); job.client.require_auth(); assert!(job.status == EscrowStatus::Setup, "not in setup phase"); assert!(amount > 0, "amount must be > 0"); @@ -215,6 +376,7 @@ impl EscrowContract { status: MilestoneStatus::Pending, }); env.storage().persistent().set(&key, &job); + Self::bump_job_ttl(&env, &key); } /// Client deposits total amount and transitions job to Funded. @@ -225,6 +387,7 @@ impl EscrowContract { .persistent() .get(&key) .ok_or(EscrowError::JobNotFound)?; + Self::bump_job_ttl(&env, &key); // Caller must be client job.client.require_auth(); @@ -258,6 +421,7 @@ impl EscrowContract { job.total_amount = amount; job.status = EscrowStatus::Funded; env.storage().persistent().set(&key, &job); + Self::bump_job_ttl(&env, &key); // Emit deposit event for off-chain logging let evt = DepositEvent { @@ -280,6 +444,7 @@ impl EscrowContract { .persistent() .get(&key) .ok_or(EscrowError::JobNotFound)?; + Self::bump_job_ttl(&env, &key); if !(job.status == EscrowStatus::Funded || job.status == EscrowStatus::WorkInProgress) { return Err(EscrowError::InvalidState); @@ -322,6 +487,7 @@ impl EscrowContract { } env.storage().persistent().set(&key, &job); + Self::bump_job_ttl(&env, &key); // Emit event env.events().publish( @@ -339,6 +505,7 @@ impl EscrowContract { let key = DataKey::Job(job_id); let mut job: EscrowJob = env.storage().persistent().get(&key).expect("job not found"); + Self::bump_job_ttl(&env, &key); assert!( job.status == EscrowStatus::Funded || job.status == EscrowStatus::WorkInProgress, @@ -377,6 +544,7 @@ impl EscrowContract { } env.storage().persistent().set(&key, &job); + Self::bump_job_ttl(&env, &key); } /// Either party opens a dispute, locking remaining funds. @@ -389,6 +557,7 @@ impl EscrowContract { .persistent() .get(&key) .ok_or(EscrowError::JobNotFound)?; + Self::bump_job_ttl(&env, &key); if !(job.status == EscrowStatus::Funded || job.status == EscrowStatus::WorkInProgress) { return Err(EscrowError::InvalidState); @@ -400,6 +569,9 @@ impl EscrowContract { job.status = EscrowStatus::Disputed; env.storage().persistent().set(&key, &job); + Self::bump_job_ttl(&env, &key); + + Self::sync_dispute_to_job_registry(&env, job_id)?; env.events().publish( ("escrow", "OpenDispute"), @@ -411,12 +583,13 @@ impl EscrowContract { /// 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) { + pub fn raise_dispute(env: Env, job_id: u64, caller: Address) -> Result<(), EscrowError> { // 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"); + Self::bump_job_ttl(&env, &key); // 2. Only client or freelancer may raise a dispute assert!( @@ -447,6 +620,9 @@ impl EscrowContract { // 6. Lock funds by transitioning to Disputed — blocks release_funds & release_milestone job.status = EscrowStatus::Disputed; env.storage().persistent().set(&key, &job); + Self::bump_job_ttl(&env, &key); + + Self::sync_dispute_to_job_registry(&env, job_id)?; // 7. Emit DisputeRaised event for backend / AI Judge to consume let mut released_count = 0u32; @@ -466,12 +642,15 @@ impl EscrowContract { now, ), ); + + Ok(()) } /// 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) { + Self::bump_instance_ttl(&env); let agent_judge: Address = env .storage() .instance() @@ -484,6 +663,7 @@ impl EscrowContract { let key = DataKey::Job(job_id); let mut job: EscrowJob = env.storage().persistent().get(&key).expect("job not found"); + Self::bump_job_ttl(&env, &key); assert!(job.status == EscrowStatus::Disputed, "job not disputed"); let remaining = job.total_amount - job.released_amount; @@ -505,6 +685,7 @@ impl EscrowContract { job.released_amount += total_payout; job.status = EscrowStatus::Resolved; env.storage().persistent().set(&key, &job); + Self::bump_job_ttl(&env, &key); } /// Client recoups funds if freelancer never responded. @@ -513,6 +694,7 @@ impl EscrowContract { let key = DataKey::Job(job_id); let mut job: EscrowJob = env.storage().persistent().get(&key).expect("job not found"); + Self::bump_job_ttl(&env, &key); assert!( job.status == EscrowStatus::Funded || job.status == EscrowStatus::WorkInProgress, @@ -529,22 +711,21 @@ impl EscrowContract { job.released_amount = job.total_amount; job.status = EscrowStatus::Refunded; env.storage().persistent().set(&key, &job); + Self::bump_job_ttl(&env, &key); } pub fn get_job(env: Env, job_id: u64) -> EscrowJob { - env.storage() - .persistent() - .get(&DataKey::Job(job_id)) - .expect("job not found") + let key = DataKey::Job(job_id); + let job = env.storage().persistent().get(&key).expect("job not found"); + Self::bump_job_ttl(&env, &key); + job } /// 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 key = DataKey::Job(job_id); + let job: EscrowJob = env.storage().persistent().get(&key).expect("job not found"); + Self::bump_job_ttl(&env, &key); let mut statuses = Vec::new(&env); for m in job.milestones.iter() { statuses.push_back(m.status); @@ -556,8 +737,9 @@ impl EscrowContract { #[cfg(test)] mod test { use super::*; + use job_registry::{JobRegistryContract, JobRegistryContractClient, JobStatus}; use soroban_sdk::testutils::Address as _; - use soroban_sdk::{token, Address, Env}; + use soroban_sdk::{token, Address, Bytes, BytesN, Env}; fn setup_token(env: &Env, admin: &Address) -> Address { let contract = env.register_stellar_asset_contract_v2(admin.clone()); @@ -922,4 +1104,65 @@ mod test { let job = cc.get_job(&1u64); assert_eq!(job.status, EscrowStatus::Disputed); } + + #[test] + fn test_open_dispute_syncs_job_registry_status() { + 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 escrow_id = env.register_contract(None, EscrowContract); + let escrow = EscrowContractClient::new(&env, &escrow_id); + + let registry_id = env.register_contract(None, JobRegistryContract); + let registry = JobRegistryContractClient::new(&env, ®istry_id); + + let metadata = Bytes::from_slice(&env, b"QmJobMetadataHash"); + let proposal = Bytes::from_slice(&env, b"QmBidProposalHash"); + + registry.post_job(&1u64, &client, &metadata, &9000i128); + registry.submit_bid(&1u64, &freelancer, &proposal); + registry.accept_bid(&1u64, &client, &freelancer); + assert_eq!(registry.get_job(&1u64).status, JobStatus::InProgress); + + escrow.initialize(&admin, &agent_judge); + escrow.set_job_registry(®istry_id); + escrow.create_job(&1u64, &client, &freelancer, &token_addr); + escrow.add_milestone(&1u64, &4500i128); + escrow.add_milestone(&1u64, &4500i128); + escrow.deposit(&1u64, &9000i128); + + escrow.open_dispute(&1u64, &client); + + let escrow_job = escrow.get_job(&1u64); + let registry_job = registry.get_job(&1u64); + + assert_eq!(escrow_job.status, EscrowStatus::Disputed); + assert_eq!(registry_job.status, JobStatus::Disputed); + } + + #[test] + #[should_panic(expected = "Error(Contract, #10)")] + fn test_upgrade_requires_admin() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let agent_judge = Address::generate(&env); + let attacker = Address::generate(&env); + + let contract_id = env.register_contract(None, EscrowContract); + let cc = EscrowContractClient::new(&env, &contract_id); + + cc.initialize(&admin, &agent_judge); + let wasm_hash = BytesN::from_array(&env, &[0; 32]); + cc.upgrade(&attacker, &wasm_hash); + } } diff --git a/contracts/escrow/test_snapshots/test/test_deposit_no_milestones_panics.1.json b/contracts/escrow/test_snapshots/test/test_deposit_no_milestones_panics.1.json index fbb1b799..571be6c3 100644 --- a/contracts/escrow/test_snapshots/test/test_deposit_no_milestones_panics.1.json +++ b/contracts/escrow/test_snapshots/test/test_deposit_no_milestones_panics.1.json @@ -341,7 +341,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -398,7 +398,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -604,7 +604,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ] ] diff --git a/contracts/escrow/test_snapshots/test/test_deposit_with_wrong_total_panics.1.json b/contracts/escrow/test_snapshots/test/test_deposit_with_wrong_total_panics.1.json index 4ce3ab7a..5609aa7d 100644 --- a/contracts/escrow/test_snapshots/test/test_deposit_with_wrong_total_panics.1.json +++ b/contracts/escrow/test_snapshots/test/test_deposit_with_wrong_total_panics.1.json @@ -427,7 +427,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -484,7 +484,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -690,7 +690,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ] ] diff --git a/contracts/escrow/test_snapshots/test/test_dispute_50_50_split.1.json b/contracts/escrow/test_snapshots/test/test_dispute_50_50_split.1.json index c8e2ff3b..f186f83c 100644 --- a/contracts/escrow/test_snapshots/test/test_dispute_50_50_split.1.json +++ b/contracts/escrow/test_snapshots/test/test_dispute_50_50_split.1.json @@ -942,7 +942,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -999,7 +999,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -1351,7 +1351,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ] ] diff --git a/contracts/escrow/test_snapshots/test/test_double_create_job_panics.1.json b/contracts/escrow/test_snapshots/test/test_double_create_job_panics.1.json index 59ae0a22..79de78af 100644 --- a/contracts/escrow/test_snapshots/test/test_double_create_job_panics.1.json +++ b/contracts/escrow/test_snapshots/test/test_double_create_job_panics.1.json @@ -202,7 +202,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -375,7 +375,7 @@ "data": { "vec": [ { - "string": "caught panic 'job already exists' from contract function 'Symbol(obj#41)'" + "string": "caught panic 'job already exists' from contract function 'Symbol(obj#45)'" }, { "u64": 1 diff --git a/contracts/escrow/test_snapshots/test/test_double_init.1.json b/contracts/escrow/test_snapshots/test/test_double_init.1.json index 37d50e99..311a3e8a 100644 --- a/contracts/escrow/test_snapshots/test/test_double_init.1.json +++ b/contracts/escrow/test_snapshots/test/test_double_init.1.json @@ -71,7 +71,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -92,7 +92,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ] ] diff --git a/contracts/escrow/test_snapshots/test/test_exhaustive_release_funds_path.1.json b/contracts/escrow/test_snapshots/test/test_exhaustive_release_funds_path.1.json index 7ed59536..0ee5672d 100644 --- a/contracts/escrow/test_snapshots/test/test_exhaustive_release_funds_path.1.json +++ b/contracts/escrow/test_snapshots/test/test_exhaustive_release_funds_path.1.json @@ -1002,7 +1002,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -1059,7 +1059,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -1411,7 +1411,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ] ] diff --git a/contracts/escrow/test_snapshots/test/test_happy_path_lifecycle.1.json b/contracts/escrow/test_snapshots/test/test_happy_path_lifecycle.1.json index ecde6f4b..61ca1945 100644 --- a/contracts/escrow/test_snapshots/test/test_happy_path_lifecycle.1.json +++ b/contracts/escrow/test_snapshots/test/test_happy_path_lifecycle.1.json @@ -849,7 +849,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -906,7 +906,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -1258,7 +1258,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ] ] diff --git a/contracts/escrow/test_snapshots/test/test_open_dispute_syncs_job_registry_status.1.json b/contracts/escrow/test_snapshots/test/test_open_dispute_syncs_job_registry_status.1.json new file mode 100644 index 00000000..b78dfbaf --- /dev/null +++ b/contracts/escrow/test_snapshots/test/test_open_dispute_syncs_job_registry_status.1.json @@ -0,0 +1,3079 @@ +{ + "generators": { + "address": 7, + "nonce": 0 + }, + "auth": [ + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + { + "function": { + "contract_fn": { + "contract_address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "i128": { + "hi": 0, + "lo": 100000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM", + "function_name": "post_job", + "args": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "bytes": "516d4a6f624d6574616461746148617368" + }, + { + "i128": { + "hi": 0, + "lo": 9000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM", + "function_name": "submit_bid", + "args": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "bytes": "516d42696450726f706f73616c48617368" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM", + "function_name": "accept_bid", + "args": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "function_name": "set_job_registry", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "function_name": "create_job", + "args": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "function_name": "add_milestone", + "args": [ + { + "u64": 1 + }, + { + "i128": { + "hi": 0, + "lo": 4500 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "function_name": "add_milestone", + "args": [ + { + "u64": 1 + }, + { + "i128": { + "hi": 0, + "lo": 4500 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "function_name": "deposit", + "args": [ + { + "u64": 1 + }, + { + "i128": { + "hi": 0, + "lo": 9000 + } + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "i128": { + "hi": 0, + "lo": 9000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "function_name": "open_dispute", + "args": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + }, + "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": [ + [ + { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + "balance": 0, + "seq_num": 0, + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + null + ] + ], + [ + { + "contract_data": { + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 4270020994084947596 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 4270020994084947596 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 115220454072064130 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 115220454072064130 + } + }, + "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": 1194852393571756375 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 1194852393571756375 + } + }, + "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": 5806905060045992000 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 5806905060045992000 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 6277191135259896685 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 6277191135259896685 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 8370022561469687789 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 8370022561469687789 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "vec": [ + { + "symbol": "Job" + }, + { + "u64": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "vec": [ + { + "symbol": "Job" + }, + { + "u64": 1 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "client" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": 0 + } + }, + { + "key": { + "symbol": "expires_at" + }, + "val": { + "u64": 2592000 + } + }, + { + "key": { + "symbol": "freelancer" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "milestones" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 4500 + } + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Pending" + } + ] + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 4500 + } + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Pending" + } + ] + } + } + ] + } + ] + } + }, + { + "key": { + "symbol": "released_amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Disputed" + } + ] + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "total_amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 9000 + } + } + } + ] + } + } + }, + "ext": "v0" + }, + 150000 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AgentJudge" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "JobRegistry" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 150000 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM", + "key": { + "vec": [ + { + "symbol": "Bids" + }, + { + "u64": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM", + "key": { + "vec": [ + { + "symbol": "Bids" + }, + { + "u64": 1 + } + ] + }, + "durability": "persistent", + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "freelancer" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "proposal_hash" + }, + "val": { + "bytes": "516d42696450726f706f73616c48617368" + } + } + ] + } + ] + } + } + }, + "ext": "v0" + }, + 150000 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM", + "key": { + "vec": [ + { + "symbol": "Job" + }, + { + "u64": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM", + "key": { + "vec": [ + { + "symbol": "Job" + }, + { + "u64": 1 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "budget_stroops" + }, + "val": { + "i128": { + "hi": 0, + "lo": 9000 + } + } + }, + { + "key": { + "symbol": "client" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "freelancer" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "metadata_hash" + }, + "val": { + "bytes": "516d4a6f624d6574616461746148617368" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Disputed" + } + ] + } + } + ] + } + } + }, + "ext": "v0" + }, + 150000 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 91000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 9000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000005" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 120960 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 150000 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" + }, + { + "symbol": "init_asset" + } + ], + "data": { + "bytes": "0000000161616100000000000000000000000000000000000000000000000000000000000000000000000005" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_asset" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" + }, + { + "symbol": "set_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "set_admin" + }, + { + "address": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "set_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" + }, + { + "symbol": "mint" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "i128": { + "hi": 0, + "lo": 100000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "mint" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 100000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "mint" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000007" + }, + { + "symbol": "post_job" + } + ], + "data": { + "vec": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "bytes": "516d4a6f624d6574616461746148617368" + }, + { + "i128": { + "hi": 0, + "lo": 9000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000007", + "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": "0000000000000000000000000000000000000000000000000000000000000007" + }, + { + "symbol": "submit_bid" + } + ], + "data": { + "vec": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "bytes": "516d42696450726f706f73616c48617368" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000007", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "string": "job_registry" + }, + { + "string": "BidSubmitted" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "freelancer" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "job_id" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "proposal_hash" + }, + "val": { + "bytes": "516d42696450726f706f73616c48617368" + } + }, + { + "key": { + "symbol": "timestamp" + }, + "val": { + "u64": 0 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000007", + "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": "0000000000000000000000000000000000000000000000000000000000000007" + }, + { + "symbol": "accept_bid" + } + ], + "data": { + "vec": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000007", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "string": "job_registry" + }, + { + "string": "BidAccepted" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "client" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "freelancer" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "job_id" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "timestamp" + }, + "val": { + "u64": 0 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000007", + "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": "0000000000000000000000000000000000000000000000000000000000000007" + }, + { + "symbol": "get_job" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000007", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_job" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "budget_stroops" + }, + "val": { + "i128": { + "hi": 0, + "lo": 9000 + } + } + }, + { + "key": { + "symbol": "client" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "freelancer" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "metadata_hash" + }, + "val": { + "bytes": "516d4a6f624d6574616461746148617368" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "InProgress" + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000006" + }, + { + "symbol": "initialize" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000006", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "string": "escrow" + }, + { + "string": "Initialized" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "u64": 0 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000006", + "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": "0000000000000000000000000000000000000000000000000000000000000006" + }, + { + "symbol": "set_job_registry" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000006", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "string": "escrow" + }, + { + "string": "JobRegistryConfigured" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "configured_at" + }, + "val": { + "u64": 0 + } + }, + { + "key": { + "symbol": "configured_by" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "registry_contract" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000006", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "set_job_registry" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000006" + }, + { + "symbol": "create_job" + } + ], + "data": { + "vec": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000006", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "create_job" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000006" + }, + { + "symbol": "add_milestone" + } + ], + "data": { + "vec": [ + { + "u64": 1 + }, + { + "i128": { + "hi": 0, + "lo": 4500 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000006", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "add_milestone" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000006" + }, + { + "symbol": "add_milestone" + } + ], + "data": { + "vec": [ + { + "u64": 1 + }, + { + "i128": { + "hi": 0, + "lo": 4500 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000006", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "add_milestone" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000006" + }, + { + "symbol": "deposit" + } + ], + "data": { + "vec": [ + { + "u64": 1 + }, + { + "i128": { + "hi": 0, + "lo": 9000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000006", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73" + }, + { + "symbol": "transfer" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "i128": { + "hi": 0, + "lo": 9000 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "transfer" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 9000 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "d63a954726751a876d37290072af1ee723d7d761eec3bf4191849d2116acdc73", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "transfer" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000006", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "string": "escrow" + }, + { + "string": "Deposit" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 9000 + } + } + }, + { + "key": { + "symbol": "deposited_at" + }, + "val": { + "u64": 0 + } + }, + { + "key": { + "symbol": "job_id" + }, + "val": { + "u64": 1 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000006", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "deposit" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000006" + }, + { + "symbol": "open_dispute" + } + ], + "data": { + "vec": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000006", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000007" + }, + { + "symbol": "mark_disputed" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000007", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "string": "job_registry" + }, + { + "string": "Disputed" + } + ], + "data": { + "vec": [ + { + "u64": 1 + }, + { + "u64": 0 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000007", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "mark_disputed" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000006", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "string": "escrow" + }, + { + "string": "RegistryDisputeSynced" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "job_id" + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "symbol": "registry_contract" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + }, + { + "key": { + "symbol": "synced_at" + }, + "val": { + "u64": 0 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000006", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "string": "escrow" + }, + { + "string": "OpenDispute" + } + ], + "data": { + "vec": [ + { + "u64": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u64": 0 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000006", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "open_dispute" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000006" + }, + { + "symbol": "get_job" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000006", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_job" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "client" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": 0 + } + }, + { + "key": { + "symbol": "expires_at" + }, + "val": { + "u64": 2592000 + } + }, + { + "key": { + "symbol": "freelancer" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "milestones" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 4500 + } + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Pending" + } + ] + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 4500 + } + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Pending" + } + ] + } + } + ] + } + ] + } + }, + { + "key": { + "symbol": "released_amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Disputed" + } + ] + } + }, + { + "key": { + "symbol": "token" + }, + "val": { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + } + }, + { + "key": { + "symbol": "total_amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 9000 + } + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000007" + }, + { + "symbol": "get_job" + } + ], + "data": { + "u64": 1 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000007", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_job" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "budget_stroops" + }, + "val": { + "i128": { + "hi": 0, + "lo": 9000 + } + } + }, + { + "key": { + "symbol": "client" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "freelancer" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "metadata_hash" + }, + "val": { + "bytes": "516d4a6f624d6574616461746148617368" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Disputed" + } + ] + } + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/escrow/test_snapshots/test/test_raise_dispute_by_client_locks_funds.1.json b/contracts/escrow/test_snapshots/test/test_raise_dispute_by_client_locks_funds.1.json index e37dca8e..45e36687 100644 --- a/contracts/escrow/test_snapshots/test/test_raise_dispute_by_client_locks_funds.1.json +++ b/contracts/escrow/test_snapshots/test/test_raise_dispute_by_client_locks_funds.1.json @@ -734,7 +734,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -791,7 +791,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -1070,7 +1070,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ] ] diff --git a/contracts/escrow/test_snapshots/test/test_refund.1.json b/contracts/escrow/test_snapshots/test/test_refund.1.json index 81390bdd..8af8d894 100644 --- a/contracts/escrow/test_snapshots/test/test_refund.1.json +++ b/contracts/escrow/test_snapshots/test/test_refund.1.json @@ -651,7 +651,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -708,7 +708,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -987,7 +987,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ] ] diff --git a/contracts/escrow/test_snapshots/test/test_unauthorized_release.1.json b/contracts/escrow/test_snapshots/test/test_unauthorized_release.1.json index b3f6f1ee..ccc555e2 100644 --- a/contracts/escrow/test_snapshots/test/test_unauthorized_release.1.json +++ b/contracts/escrow/test_snapshots/test/test_unauthorized_release.1.json @@ -594,7 +594,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -651,7 +651,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -930,7 +930,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ] ] diff --git a/contracts/escrow/test_snapshots/test/test_upgrade_requires_admin.1.json b/contracts/escrow/test_snapshots/test/test_upgrade_requires_admin.1.json new file mode 100644 index 00000000..da0ab13f --- /dev/null +++ b/contracts/escrow/test_snapshots/test/test_upgrade_requires_admin.1.json @@ -0,0 +1,339 @@ +{ + "generators": { + "address": 4, + "nonce": 0 + }, + "auth": [ + [], + [] + ], + "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": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AgentJudge" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 150000 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 150000 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000004" + }, + { + "symbol": "initialize" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000004", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "string": "escrow" + }, + { + "string": "Initialized" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "u64": 0 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000004", + "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": "0000000000000000000000000000000000000000000000000000000000000004" + }, + { + "symbol": "upgrade" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000000" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000004", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "upgrade" + } + ], + "data": { + "error": { + "contract": 10 + } + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000004", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 10 + } + } + ], + "data": { + "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 10 + } + } + ], + "data": { + "vec": [ + { + "string": "contract call failed" + }, + { + "symbol": "upgrade" + }, + { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000000" + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 10 + } + } + ], + "data": { + "string": "escalating error to panic" + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/escrow/test_snapshots/test/test_variable_milestone_amounts.1.json b/contracts/escrow/test_snapshots/test/test_variable_milestone_amounts.1.json index 3d7dd03c..8b92ccfc 100644 --- a/contracts/escrow/test_snapshots/test/test_variable_milestone_amounts.1.json +++ b/contracts/escrow/test_snapshots/test/test_variable_milestone_amounts.1.json @@ -849,7 +849,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -906,7 +906,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -1258,7 +1258,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ] ] diff --git a/contracts/job_registry/src/lib.rs b/contracts/job_registry/src/lib.rs index b9084b77..47c6d6fe 100644 --- a/contracts/job_registry/src/lib.rs +++ b/contracts/job_registry/src/lib.rs @@ -1,5 +1,6 @@ #![no_std] +use soroban_sdk::BytesN; use soroban_sdk::{contract, contracterror, contractimpl, contracttype, Address, Bytes, Env, Vec}; #[contracttype] @@ -34,6 +35,7 @@ pub enum DataKey { Job(u64), Bids(u64), Deliverable(u64), + UpgradeAdmin, } /// Error codes for JobRegistry contract operations. @@ -56,6 +58,10 @@ pub enum JobRegistryError { InvalidState = 5, /// Indicates the selected freelancer did not submit a bid for the job (error code: 6). BidNotFound = 6, + /// Indicates upgrade admin has already been initialized (error code: 7). + UpgradeAdminAlreadySet = 7, + /// Indicates upgrade admin is not configured (error code: 8). + UpgradeAdminNotSet = 8, } /// Event emitted when a bid is successfully submitted. @@ -94,11 +100,137 @@ pub struct DeliverableSubmittedEvent { pub timestamp: u64, } +/// Event emitted when upgrade admin is configured or changed. +#[contracttype] +#[derive(Clone)] +pub struct UpgradeAdminSetEvent { + pub previous_admin: Option
, + pub new_admin: Address, + pub timestamp: u64, +} + +/// Event emitted when the contract is upgraded to a new WASM hash. +#[contracttype] +#[derive(Clone)] +pub struct ContractUpgradedEvent { + pub by_admin: Address, + pub new_wasm_hash: BytesN<32>, + pub timestamp: u64, +} + #[contract] pub struct JobRegistryContract; #[contractimpl] impl JobRegistryContract { + const PERSISTENT_TTL_THRESHOLD: u32 = 50_000; + const PERSISTENT_TTL_EXTEND_TO: u32 = 150_000; + + fn bump_persistent_ttl(env: &Env, key: &DataKey) { + if env.storage().persistent().has(key) { + env.storage().persistent().extend_ttl( + key, + Self::PERSISTENT_TTL_THRESHOLD, + Self::PERSISTENT_TTL_EXTEND_TO, + ); + } + } + + fn require_upgrade_admin(env: &Env, caller: &Address) -> Result<(), JobRegistryError> { + caller.require_auth(); + let admin: Address = env + .storage() + .instance() + .get(&DataKey::UpgradeAdmin) + .ok_or(JobRegistryError::UpgradeAdminNotSet)?; + + if *caller != admin { + return Err(JobRegistryError::Unauthorized); + } + + Ok(()) + } + + /// One-time initialization for upgrade admin. + pub fn init_upgrade_admin(env: Env, admin: Address) -> Result<(), JobRegistryError> { + admin.require_auth(); + + if env.storage().instance().has(&DataKey::UpgradeAdmin) { + return Err(JobRegistryError::UpgradeAdminAlreadySet); + } + + env.storage().instance().set(&DataKey::UpgradeAdmin, &admin); + env.events().publish( + ("job_registry", "UpgradeAdminSet"), + UpgradeAdminSetEvent { + previous_admin: None, + new_admin: admin, + timestamp: env.ledger().timestamp(), + }, + ); + + Ok(()) + } + + /// Rotate upgrade admin authority to a new address. + pub fn set_upgrade_admin( + env: Env, + caller: Address, + new_admin: Address, + ) -> Result<(), JobRegistryError> { + Self::require_upgrade_admin(&env, &caller)?; + + let previous_admin: Address = env + .storage() + .instance() + .get(&DataKey::UpgradeAdmin) + .ok_or(JobRegistryError::UpgradeAdminNotSet)?; + + env.storage() + .instance() + .set(&DataKey::UpgradeAdmin, &new_admin); + env.events().publish( + ("job_registry", "UpgradeAdminSet"), + UpgradeAdminSetEvent { + previous_admin: Some(previous_admin), + new_admin, + timestamp: env.ledger().timestamp(), + }, + ); + + Ok(()) + } + + /// Returns the currently configured upgrade admin. + pub fn get_upgrade_admin(env: Env) -> Result { + env.storage() + .instance() + .get(&DataKey::UpgradeAdmin) + .ok_or(JobRegistryError::UpgradeAdminNotSet) + } + + /// Upgrade contract WASM hash, callable only by upgrade admin. + pub fn upgrade( + env: Env, + caller: Address, + new_wasm_hash: BytesN<32>, + ) -> Result<(), JobRegistryError> { + Self::require_upgrade_admin(&env, &caller)?; + + env.deployer() + .update_current_contract_wasm(new_wasm_hash.clone()); + env.events().publish( + ("job_registry", "ContractUpgraded"), + ContractUpgradedEvent { + by_admin: caller, + new_wasm_hash, + timestamp: env.ledger().timestamp(), + }, + ); + + Ok(()) + } + /// Client posts a job. `metadata_hash` = IPFS CID bytes. pub fn post_job(env: Env, job_id: u64, client: Address, hash: Bytes, budget: i128) { client.require_auth(); @@ -116,11 +248,7 @@ impl JobRegistryContract { status: JobStatus::Open, }; env.storage().persistent().set(&key, &job); - - let bids: Vec = Vec::new(&env); - env.storage() - .persistent() - .set(&DataKey::Bids(job_id), &bids); + Self::bump_persistent_ttl(&env, &key); } /// Freelancer submits a bid on an open job. @@ -170,6 +298,7 @@ impl JobRegistryContract { .persistent() .get(&key) .ok_or(JobRegistryError::JobNotFound)?; + Self::bump_persistent_ttl(&env, &key); // Ensure job is in Open status - cannot bid on jobs that are not accepting bids if job.status != JobStatus::Open { @@ -192,6 +321,7 @@ impl JobRegistryContract { // Persist updated bids vector to storage env.storage().persistent().set(&bids_key, &bids); + Self::bump_persistent_ttl(&env, &bids_key); // Emit auditable event for off-chain indexing and monitoring // Timestamp ensures audit trail for all submissions @@ -223,6 +353,7 @@ impl JobRegistryContract { .persistent() .get(&key) .ok_or(JobRegistryError::JobNotFound)?; + Self::bump_persistent_ttl(&env, &key); if job.status != JobStatus::Open { return Err(JobRegistryError::InvalidState); @@ -237,6 +368,7 @@ impl JobRegistryContract { .persistent() .get(&bids_key) .unwrap_or_else(|| Vec::new(&env)); + Self::bump_persistent_ttl(&env, &bids_key); let mut bid_found = false; let mut idx = 0u32; @@ -257,6 +389,7 @@ impl JobRegistryContract { job.freelancer = Some(freelancer.clone()); job.status = JobStatus::InProgress; env.storage().persistent().set(&key, &job); + Self::bump_persistent_ttl(&env, &key); env.events().publish( ("job_registry", "BidAccepted"), @@ -320,6 +453,7 @@ impl JobRegistryContract { .persistent() .get(&key) .ok_or(JobRegistryError::JobNotFound)?; + Self::bump_persistent_ttl(&env, &key); if job.status != JobStatus::InProgress { return Err(JobRegistryError::InvalidState); @@ -330,9 +464,11 @@ impl JobRegistryContract { job.status = JobStatus::DeliverableSubmitted; env.storage().persistent().set(&key, &job); - env.storage() - .persistent() - .set(&DataKey::Deliverable(job_id), &hash); + Self::bump_persistent_ttl(&env, &key); + + let deliverable_key = DataKey::Deliverable(job_id); + env.storage().persistent().set(&deliverable_key, &hash); + Self::bump_persistent_ttl(&env, &deliverable_key); env.events().publish( ("job_registry", "DeliverableSubmitted"), @@ -355,6 +491,7 @@ impl JobRegistryContract { .persistent() .get(&key) .ok_or(JobRegistryError::JobNotFound)?; + Self::bump_persistent_ttl(&env, &key); if job.status != JobStatus::InProgress && job.status != JobStatus::DeliverableSubmitted { return Err(JobRegistryError::InvalidState); @@ -362,6 +499,7 @@ impl JobRegistryContract { job.status = JobStatus::Disputed; env.storage().persistent().set(&key, &job); + Self::bump_persistent_ttl(&env, &key); env.events().publish( ("job_registry", "Disputed"), @@ -384,10 +522,14 @@ impl JobRegistryContract { /// * `Ok(JobRecord)` - The job record if found /// * `Err(JobRegistryError::JobNotFound)` - If the job ID does not exist pub fn get_job(env: Env, job_id: u64) -> Result { - env.storage() + let key = DataKey::Job(job_id); + let job = env + .storage() .persistent() - .get(&DataKey::Job(job_id)) - .ok_or(JobRegistryError::JobNotFound) + .get(&key) + .ok_or(JobRegistryError::JobNotFound)?; + Self::bump_persistent_ttl(&env, &key); + Ok(job) } /// Retrieves all bids for a specific job. @@ -404,22 +546,31 @@ impl JobRegistryContract { /// * `Ok(Vec)` - A vector of all bids submitted for the job /// * `Err(JobRegistryError::JobNotFound)` - If the job ID does not exist pub fn get_bids(env: Env, job_id: u64) -> Result, JobRegistryError> { - if !env.storage().persistent().has(&DataKey::Job(job_id)) { + let job_key = DataKey::Job(job_id); + if !env.storage().persistent().has(&job_key) { return Err(JobRegistryError::JobNotFound); } + Self::bump_persistent_ttl(&env, &job_key); - Ok(env + let bids_key = DataKey::Bids(job_id); + let bids = env .storage() .persistent() - .get(&DataKey::Bids(job_id)) - .unwrap_or_else(|| Vec::new(&env))) + .get(&bids_key) + .unwrap_or_else(|| Vec::new(&env)); + Self::bump_persistent_ttl(&env, &bids_key); + Ok(bids) } pub fn get_deliverable(env: Env, job_id: u64) -> Bytes { - env.storage() + let key = DataKey::Deliverable(job_id); + let deliverable = env + .storage() .persistent() - .get(&DataKey::Deliverable(job_id)) - .expect("no deliverable") + .get(&key) + .expect("no deliverable"); + Self::bump_persistent_ttl(&env, &key); + deliverable } } @@ -908,4 +1059,48 @@ mod test { cc.get_bids(&999u64); } + + #[test] + fn test_upgrade_admin_initialize_and_read() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let contract_id = env.register_contract(None, JobRegistryContract); + let cc = JobRegistryContractClient::new(&env, &contract_id); + + cc.init_upgrade_admin(&admin); + let configured = cc.get_upgrade_admin(); + assert_eq!(configured, admin); + } + + #[test] + #[should_panic(expected = "Error(Contract, #7)")] + fn test_upgrade_admin_cannot_initialize_twice() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let contract_id = env.register_contract(None, JobRegistryContract); + let cc = JobRegistryContractClient::new(&env, &contract_id); + + cc.init_upgrade_admin(&admin); + cc.init_upgrade_admin(&admin); + } + + #[test] + #[should_panic(expected = "Error(Contract, #3)")] + fn test_set_upgrade_admin_requires_current_admin() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let attacker = Address::generate(&env); + let new_admin = Address::generate(&env); + let contract_id = env.register_contract(None, JobRegistryContract); + let cc = JobRegistryContractClient::new(&env, &contract_id); + + cc.init_upgrade_admin(&admin); + cc.set_upgrade_admin(&attacker, &new_admin); + } } diff --git a/contracts/job_registry/test_snapshots/test/test_accept_bid_requires_existing_bid.1.json b/contracts/job_registry/test_snapshots/test/test_accept_bid_requires_existing_bid.1.json index d9269dde..d31d906c 100644 --- a/contracts/job_registry/test_snapshots/test/test_accept_bid_requires_existing_bid.1.json +++ b/contracts/job_registry/test_snapshots/test/test_accept_bid_requires_existing_bid.1.json @@ -80,51 +80,6 @@ 6311999 ] ], - [ - { - "contract_data": { - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", - "key": { - "vec": [ - { - "symbol": "Bids" - }, - { - "u64": 1 - } - ] - }, - "durability": "persistent" - } - }, - [ - { - "last_modified_ledger_seq": 0, - "data": { - "contract_data": { - "ext": "v0", - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", - "key": { - "vec": [ - { - "symbol": "Bids" - }, - { - "u64": 1 - } - ] - }, - "durability": "persistent", - "val": { - "vec": [] - } - } - }, - "ext": "v0" - }, - 4095 - ] - ], [ { "contract_data": { @@ -213,7 +168,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ diff --git a/contracts/job_registry/test_snapshots/test/test_accept_bid_success.1.json b/contracts/job_registry/test_snapshots/test/test_accept_bid_success.1.json index fc0a0ff0..5d6e068e 100644 --- a/contracts/job_registry/test_snapshots/test/test_accept_bid_success.1.json +++ b/contracts/job_registry/test_snapshots/test/test_accept_bid_success.1.json @@ -259,7 +259,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -352,7 +352,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ diff --git a/contracts/job_registry/test_snapshots/test/test_bid_on_non_open_panics.1.json b/contracts/job_registry/test_snapshots/test/test_bid_on_non_open_panics.1.json index 5aad1f92..1a3c7ae7 100644 --- a/contracts/job_registry/test_snapshots/test/test_bid_on_non_open_panics.1.json +++ b/contracts/job_registry/test_snapshots/test/test_bid_on_non_open_panics.1.json @@ -259,7 +259,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -352,7 +352,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ diff --git a/contracts/job_registry/test_snapshots/test/test_bid_same_freelancer_multiple_times.1.json b/contracts/job_registry/test_snapshots/test/test_bid_same_freelancer_multiple_times.1.json index 3cc5aa3e..2ac91591 100644 --- a/contracts/job_registry/test_snapshots/test/test_bid_same_freelancer_multiple_times.1.json +++ b/contracts/job_registry/test_snapshots/test/test_bid_same_freelancer_multiple_times.1.json @@ -279,7 +279,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -370,7 +370,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ diff --git a/contracts/job_registry/test_snapshots/test/test_cannot_accept_bid_twice.1.json b/contracts/job_registry/test_snapshots/test/test_cannot_accept_bid_twice.1.json index e45920d0..436a7177 100644 --- a/contracts/job_registry/test_snapshots/test/test_cannot_accept_bid_twice.1.json +++ b/contracts/job_registry/test_snapshots/test/test_cannot_accept_bid_twice.1.json @@ -337,7 +337,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -430,7 +430,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ 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 beee66ba..19b24be3 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 @@ -80,51 +80,6 @@ 6311999 ] ], - [ - { - "contract_data": { - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", - "key": { - "vec": [ - { - "symbol": "Bids" - }, - { - "u64": 1 - } - ] - }, - "durability": "persistent" - } - }, - [ - { - "last_modified_ledger_seq": 0, - "data": { - "contract_data": { - "ext": "v0", - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", - "key": { - "vec": [ - { - "symbol": "Bids" - }, - { - "u64": 1 - } - ] - }, - "durability": "persistent", - "val": { - "vec": [] - } - } - }, - "ext": "v0" - }, - 4095 - ] - ], [ { "contract_data": { @@ -213,7 +168,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ diff --git a/contracts/job_registry/test_snapshots/test/test_full_lifecycle.1.json b/contracts/job_registry/test_snapshots/test/test_full_lifecycle.1.json index a79714f1..ba52da62 100644 --- a/contracts/job_registry/test_snapshots/test/test_full_lifecycle.1.json +++ b/contracts/job_registry/test_snapshots/test/test_full_lifecycle.1.json @@ -321,7 +321,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -366,7 +366,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -459,7 +459,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ diff --git a/contracts/job_registry/test_snapshots/test/test_get_bids_job_not_found.1.json b/contracts/job_registry/test_snapshots/test/test_get_bids_job_not_found.1.json new file mode 100644 index 00000000..48ff924a --- /dev/null +++ b/contracts/job_registry/test_snapshots/test/test_get_bids_job_not_found.1.json @@ -0,0 +1,216 @@ +{ + "generators": { + "address": 1, + "nonce": 0 + }, + "auth": [ + [] + ], + "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_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": "get_bids" + } + ], + "data": { + "u64": 999 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_bids" + } + ], + "data": { + "error": { + "contract": 1 + } + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 1 + } + } + ], + "data": { + "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 1 + } + } + ], + "data": { + "vec": [ + { + "string": "contract call failed" + }, + { + "symbol": "get_bids" + }, + { + "vec": [ + { + "u64": 999 + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 1 + } + } + ], + "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_get_job_not_found.1.json b/contracts/job_registry/test_snapshots/test/test_get_job_not_found.1.json new file mode 100644 index 00000000..d49e9f74 --- /dev/null +++ b/contracts/job_registry/test_snapshots/test/test_get_job_not_found.1.json @@ -0,0 +1,216 @@ +{ + "generators": { + "address": 1, + "nonce": 0 + }, + "auth": [ + [] + ], + "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_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": "get_job" + } + ], + "data": { + "u64": 999 + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_job" + } + ], + "data": { + "error": { + "contract": 1 + } + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 1 + } + } + ], + "data": { + "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 1 + } + } + ], + "data": { + "vec": [ + { + "string": "contract call failed" + }, + { + "symbol": "get_job" + }, + { + "vec": [ + { + "u64": 999 + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 1 + } + } + ], + "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_mark_disputed_from_deliverable_submitted.1.json b/contracts/job_registry/test_snapshots/test/test_mark_disputed_from_deliverable_submitted.1.json index dc3ca747..ea8270c6 100644 --- a/contracts/job_registry/test_snapshots/test/test_mark_disputed_from_deliverable_submitted.1.json +++ b/contracts/job_registry/test_snapshots/test/test_mark_disputed_from_deliverable_submitted.1.json @@ -318,7 +318,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -363,7 +363,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -456,7 +456,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ diff --git a/contracts/job_registry/test_snapshots/test/test_mark_disputed_from_in_progress.1.json b/contracts/job_registry/test_snapshots/test/test_mark_disputed_from_in_progress.1.json index b347744b..9eca4f69 100644 --- a/contracts/job_registry/test_snapshots/test/test_mark_disputed_from_in_progress.1.json +++ b/contracts/job_registry/test_snapshots/test/test_mark_disputed_from_in_progress.1.json @@ -260,7 +260,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -353,7 +353,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ diff --git a/contracts/job_registry/test_snapshots/test/test_mark_disputed_from_open_fails.1.json b/contracts/job_registry/test_snapshots/test/test_mark_disputed_from_open_fails.1.json index 3e0ff3e9..88c19429 100644 --- a/contracts/job_registry/test_snapshots/test/test_mark_disputed_from_open_fails.1.json +++ b/contracts/job_registry/test_snapshots/test/test_mark_disputed_from_open_fails.1.json @@ -80,51 +80,6 @@ 6311999 ] ], - [ - { - "contract_data": { - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", - "key": { - "vec": [ - { - "symbol": "Bids" - }, - { - "u64": 1 - } - ] - }, - "durability": "persistent" - } - }, - [ - { - "last_modified_ledger_seq": 0, - "data": { - "contract_data": { - "ext": "v0", - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", - "key": { - "vec": [ - { - "symbol": "Bids" - }, - { - "u64": 1 - } - ] - }, - "durability": "persistent", - "val": { - "vec": [] - } - } - }, - "ext": "v0" - }, - 4095 - ] - ], [ { "contract_data": { @@ -213,7 +168,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ diff --git a/contracts/job_registry/test_snapshots/test/test_multiple_bids_on_same_job.1.json b/contracts/job_registry/test_snapshots/test/test_multiple_bids_on_same_job.1.json index 18bf8fd9..53236257 100644 --- a/contracts/job_registry/test_snapshots/test/test_multiple_bids_on_same_job.1.json +++ b/contracts/job_registry/test_snapshots/test/test_multiple_bids_on_same_job.1.json @@ -348,7 +348,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -439,7 +439,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ diff --git a/contracts/job_registry/test_snapshots/test/test_multiple_jobs_and_bids.1.json b/contracts/job_registry/test_snapshots/test/test_multiple_jobs_and_bids.1.json index ca715fa4..747bc6e0 100644 --- a/contracts/job_registry/test_snapshots/test/test_multiple_jobs_and_bids.1.json +++ b/contracts/job_registry/test_snapshots/test/test_multiple_jobs_and_bids.1.json @@ -596,7 +596,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -702,7 +702,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -793,7 +793,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -884,7 +884,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ diff --git a/contracts/job_registry/test_snapshots/test/test_set_upgrade_admin_requires_current_admin.1.json b/contracts/job_registry/test_snapshots/test/test_set_upgrade_admin_requires_current_admin.1.json new file mode 100644 index 00000000..e772c798 --- /dev/null +++ b/contracts/job_registry/test_snapshots/test/test_set_upgrade_admin_requires_current_admin.1.json @@ -0,0 +1,384 @@ +{ + "generators": { + "address": 4, + "nonce": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "init_upgrade_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "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_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": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "UpgradeAdmin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + ] + } + } + } + }, + "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": "0000000000000000000000000000000000000000000000000000000000000004" + }, + { + "symbol": "init_upgrade_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000004", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "string": "job_registry" + }, + { + "string": "UpgradeAdminSet" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "new_admin" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "previous_admin" + }, + "val": "void" + }, + { + "key": { + "symbol": "timestamp" + }, + "val": { + "u64": 0 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000004", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_upgrade_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000004" + }, + { + "symbol": "set_upgrade_admin" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000004", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "set_upgrade_admin" + } + ], + "data": { + "error": { + "contract": 3 + } + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000004", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 3 + } + } + ], + "data": { + "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 3 + } + } + ], + "data": { + "vec": [ + { + "string": "contract call failed" + }, + { + "symbol": "set_upgrade_admin" + }, + { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 3 + } + } + ], + "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_empty_proposal_hash.1.json b/contracts/job_registry/test_snapshots/test/test_submit_bid_empty_proposal_hash.1.json index 3e0e9e7e..aa543661 100644 --- a/contracts/job_registry/test_snapshots/test/test_submit_bid_empty_proposal_hash.1.json +++ b/contracts/job_registry/test_snapshots/test/test_submit_bid_empty_proposal_hash.1.json @@ -80,51 +80,6 @@ 6311999 ] ], - [ - { - "contract_data": { - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", - "key": { - "vec": [ - { - "symbol": "Bids" - }, - { - "u64": 1 - } - ] - }, - "durability": "persistent" - } - }, - [ - { - "last_modified_ledger_seq": 0, - "data": { - "contract_data": { - "ext": "v0", - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", - "key": { - "vec": [ - { - "symbol": "Bids" - }, - { - "u64": 1 - } - ] - }, - "durability": "persistent", - "val": { - "vec": [] - } - } - }, - "ext": "v0" - }, - 4095 - ] - ], [ { "contract_data": { @@ -213,7 +168,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ diff --git a/contracts/job_registry/test_snapshots/test/test_submit_bid_on_non_open_job.1.json b/contracts/job_registry/test_snapshots/test/test_submit_bid_on_non_open_job.1.json index f879535e..be7693ce 100644 --- a/contracts/job_registry/test_snapshots/test/test_submit_bid_on_non_open_job.1.json +++ b/contracts/job_registry/test_snapshots/test/test_submit_bid_on_non_open_job.1.json @@ -259,7 +259,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -352,7 +352,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ diff --git a/contracts/job_registry/test_snapshots/test/test_submit_bid_success.1.json b/contracts/job_registry/test_snapshots/test/test_submit_bid_success.1.json index ebf69340..71a638a0 100644 --- a/contracts/job_registry/test_snapshots/test/test_submit_bid_success.1.json +++ b/contracts/job_registry/test_snapshots/test/test_submit_bid_success.1.json @@ -201,7 +201,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -292,7 +292,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ diff --git a/contracts/job_registry/test_snapshots/test/test_submit_deliverable_empty_hash.1.json b/contracts/job_registry/test_snapshots/test/test_submit_deliverable_empty_hash.1.json index 357b664c..5cbbe11f 100644 --- a/contracts/job_registry/test_snapshots/test/test_submit_deliverable_empty_hash.1.json +++ b/contracts/job_registry/test_snapshots/test/test_submit_deliverable_empty_hash.1.json @@ -259,7 +259,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -352,7 +352,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ diff --git a/contracts/job_registry/test_snapshots/test/test_submit_deliverable_unauthorized.1.json b/contracts/job_registry/test_snapshots/test/test_submit_deliverable_unauthorized.1.json index cc739ee1..3b7358a8 100644 --- a/contracts/job_registry/test_snapshots/test/test_submit_deliverable_unauthorized.1.json +++ b/contracts/job_registry/test_snapshots/test/test_submit_deliverable_unauthorized.1.json @@ -259,7 +259,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -352,7 +352,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ 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 063a98e8..3d07306e 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 @@ -201,7 +201,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -292,7 +292,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ diff --git a/contracts/job_registry/test_snapshots/test/test_upgrade_admin_cannot_initialize_twice.1.json b/contracts/job_registry/test_snapshots/test/test_upgrade_admin_cannot_initialize_twice.1.json new file mode 100644 index 00000000..9585b3ad --- /dev/null +++ b/contracts/job_registry/test_snapshots/test/test_upgrade_admin_cannot_initialize_twice.1.json @@ -0,0 +1,374 @@ +{ + "generators": { + "address": 2, + "nonce": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "init_upgrade_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "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_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_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": [ + { + "key": { + "vec": [ + { + "symbol": "UpgradeAdmin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + ] + } + } + } + }, + "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": "0000000000000000000000000000000000000000000000000000000000000002" + }, + { + "symbol": "init_upgrade_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000002", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "string": "job_registry" + }, + { + "string": "UpgradeAdminSet" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "new_admin" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "previous_admin" + }, + "val": "void" + }, + { + "key": { + "symbol": "timestamp" + }, + "val": { + "u64": 0 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000002", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_upgrade_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000002" + }, + { + "symbol": "init_upgrade_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000002", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_upgrade_admin" + } + ], + "data": { + "error": { + "contract": 7 + } + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000002", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 7 + } + } + ], + "data": { + "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 7 + } + } + ], + "data": { + "vec": [ + { + "string": "contract call failed" + }, + { + "symbol": "init_upgrade_admin" + }, + { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 7 + } + } + ], + "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_upgrade_admin_initialize_and_read.1.json b/contracts/job_registry/test_snapshots/test/test_upgrade_admin_initialize_and_read.1.json new file mode 100644 index 00000000..c53ee506 --- /dev/null +++ b/contracts/job_registry/test_snapshots/test/test_upgrade_admin_initialize_and_read.1.json @@ -0,0 +1,281 @@ +{ + "generators": { + "address": 2, + "nonce": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "init_upgrade_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "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_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_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": [ + { + "key": { + "vec": [ + { + "symbol": "UpgradeAdmin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + ] + } + } + } + }, + "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": "0000000000000000000000000000000000000000000000000000000000000002" + }, + { + "symbol": "init_upgrade_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000002", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "string": "job_registry" + }, + { + "string": "UpgradeAdminSet" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "new_admin" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "previous_admin" + }, + "val": "void" + }, + { + "key": { + "symbol": "timestamp" + }, + "val": { + "u64": 0 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000002", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "init_upgrade_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000002" + }, + { + "symbol": "get_upgrade_admin" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000002", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_upgrade_admin" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/reputation/src/lib.rs b/contracts/reputation/src/lib.rs index 899fec3e..38a5899c 100644 --- a/contracts/reputation/src/lib.rs +++ b/contracts/reputation/src/lib.rs @@ -1,7 +1,8 @@ #![no_std] use soroban_sdk::{ - contract, contractimpl, contracttype, Address, Bytes, Env, IntoVal, Symbol, Vec, + contract, contracterror, contractimpl, contracttype, Address, Bytes, BytesN, Env, IntoVal, + Symbol, Vec, }; mod profile; @@ -56,24 +57,93 @@ pub enum DataKey { Reviewed(u64, Address), } +#[contracterror] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ReputationError { + NotInitialized = 1, + Unauthorized = 2, +} + +#[contracttype] +#[derive(Clone)] +pub struct ContractUpgradedEvent { + pub by_admin: Address, + pub new_wasm_hash: BytesN<32>, + pub upgraded_at: u64, +} + #[contract] pub struct ReputationContract; #[contractimpl] impl ReputationContract { + const INSTANCE_TTL_THRESHOLD: u32 = 50_000; + const INSTANCE_TTL_EXTEND_TO: u32 = 150_000; + const PERSISTENT_TTL_THRESHOLD: u32 = 50_000; + const PERSISTENT_TTL_EXTEND_TO: u32 = 150_000; + + fn bump_instance_ttl(env: &Env) { + env.storage() + .instance() + .extend_ttl(Self::INSTANCE_TTL_THRESHOLD, Self::INSTANCE_TTL_EXTEND_TO); + } + + /// Upgrades the current contract WASM. Only callable by admin. + pub fn upgrade( + env: Env, + caller: Address, + new_wasm_hash: BytesN<32>, + ) -> Result<(), ReputationError> { + Self::bump_instance_ttl(&env); + caller.require_auth(); + + let admin: Address = env + .storage() + .instance() + .get(&DataKey::Admin) + .ok_or(ReputationError::NotInitialized)?; + + if caller != admin { + return Err(ReputationError::Unauthorized); + } + + env.deployer() + .update_current_contract_wasm(new_wasm_hash.clone()); + env.events().publish( + ("reputation", "ContractUpgraded"), + ContractUpgradedEvent { + by_admin: caller, + new_wasm_hash, + upgraded_at: env.ledger().timestamp(), + }, + ); + + Ok(()) + } + pub fn initialize(env: Env, admin: Address) { if env.storage().instance().has(&DataKey::Admin) { panic!("already initialized"); } env.storage().instance().set(&DataKey::Admin, &admin); + Self::bump_instance_ttl(&env); } /// Set the JobRegistry contract address (admin only) pub fn set_job_registry(env: Env, admin: Address, registry: Address) { + let configured_admin: Address = env + .storage() + .instance() + .get(&DataKey::Admin) + .expect("not initialized"); + admin.require_auth(); + assert!(admin == configured_admin, "only admin can set job registry"); + env.storage() .instance() .set(&DataKey::JobRegistry, ®istry); + Self::bump_instance_ttl(&env); } /// Submit a rating for a target address tied to a Job ID. Caller must be the client or freelancer @@ -137,6 +207,12 @@ impl ReputationContract { storage::write_profile(&env, &target, &profile); env.storage().persistent().set(&reviewed_key, &true); + env.storage().persistent().extend_ttl( + &reviewed_key, + Self::PERSISTENT_TTL_THRESHOLD, + Self::PERSISTENT_TTL_EXTEND_TO, + ); + Self::bump_instance_ttl(&env); } /// Update reputation after a completed job. `delta` in basis points. @@ -164,6 +240,7 @@ impl ReputationContract { } storage::write_profile(&env, &address, &profile); + Self::bump_instance_ttl(&env); } /// Slash address for fraud / abandonment — reduces score by 20%. @@ -187,9 +264,11 @@ impl ReputationContract { } storage::write_profile(&env, &address, &profile); + Self::bump_instance_ttl(&env); } pub fn get_score(env: Env, address: Address, role: Role) -> ReputationScore { + Self::bump_instance_ttl(&env); let profile = storage::read_profile_or_default(&env, &address); match role { Role::Client => ReputationScore { @@ -217,16 +296,19 @@ impl ReputationContract { let mut profile = storage::read_profile_or_default(&env, &address); profile.metadata_hash = Some(metadata_hash); storage::write_profile(&env, &address, &profile); + Self::bump_instance_ttl(&env); } /// Get profile metadata hash pub fn get_profile_metadata(env: Env, address: Address) -> Option { + Self::bump_instance_ttl(&env); storage::read_profile(&env, &address).and_then(|p| p.metadata_hash) } /// Frontend-friendly aggregate metrics for public profile pages. /// Returns: [score_bps, total_jobs, total_points, reviews] pub fn get_public_metrics(env: Env, address: Address, role_name: Symbol) -> Vec { + Self::bump_instance_ttl(&env); let role = if role_name == Symbol::new(&env, "client") { Role::Client } else { @@ -253,7 +335,7 @@ impl ReputationContract { mod test { use super::*; use soroban_sdk::testutils::Address as _; - use soroban_sdk::{Address, Env}; + use soroban_sdk::{Address, BytesN, Env}; #[test] fn test_initial_score() { @@ -345,4 +427,20 @@ mod test { assert_eq!(f_score.score, 6000); assert_eq!(c_score.score, 5500); } + + #[test] + #[should_panic(expected = "Error(Contract, #2)")] + fn test_upgrade_requires_admin() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let attacker = Address::generate(&env); + let contract_id = env.register_contract(None, ReputationContract); + let client = ReputationContractClient::new(&env, &contract_id); + + client.initialize(&admin); + let wasm_hash = BytesN::from_array(&env, &[0; 32]); + client.upgrade(&attacker, &wasm_hash); + } } diff --git a/contracts/reputation/src/storage.rs b/contracts/reputation/src/storage.rs index 1b55d46f..1c1242dc 100644 --- a/contracts/reputation/src/storage.rs +++ b/contracts/reputation/src/storage.rs @@ -1,15 +1,25 @@ use crate::profile::Profile; use soroban_sdk::{Address, Env}; +const PERSISTENT_TTL_THRESHOLD: u32 = 50_000; +const PERSISTENT_TTL_EXTEND_TO: u32 = 150_000; + #[soroban_sdk::contracttype] pub enum StorageKey { Profile(Address), } pub fn read_profile(env: &Env, address: &Address) -> Option { - env.storage() - .persistent() - .get(&StorageKey::Profile(address.clone())) + let key = StorageKey::Profile(address.clone()); + if env.storage().persistent().has(&key) { + env.storage().persistent().extend_ttl( + &key, + PERSISTENT_TTL_THRESHOLD, + PERSISTENT_TTL_EXTEND_TO, + ); + } + + env.storage().persistent().get(&key) } pub fn read_profile_or_default(env: &Env, address: &Address) -> Profile { @@ -17,7 +27,9 @@ pub fn read_profile_or_default(env: &Env, address: &Address) -> Profile { } pub fn write_profile(env: &Env, address: &Address, profile: &Profile) { + let key = StorageKey::Profile(address.clone()); + env.storage().persistent().set(&key, profile); env.storage() .persistent() - .set(&StorageKey::Profile(address.clone()), profile); + .extend_ttl(&key, PERSISTENT_TTL_THRESHOLD, PERSISTENT_TTL_EXTEND_TO); } 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 d9fa2969..472200bc 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" }, - 4095 + 150000 ] ], [ @@ -66,7 +66,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ] ] @@ -134,6 +134,14 @@ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" } }, + { + "key": { + "symbol": "reviews" + }, + "val": { + "u32": 0 + } + }, { "key": { "symbol": "role" @@ -161,6 +169,14 @@ "val": { "u32": 0 } + }, + { + "key": { + "symbol": "total_points" + }, + "val": { + "i32": 0 + } } ] } diff --git a/contracts/reputation/test_snapshots/test/test_profile_metadata.1.json b/contracts/reputation/test_snapshots/test/test_profile_metadata.1.json new file mode 100644 index 00000000..edd47fae --- /dev/null +++ b/contracts/reputation/test_snapshots/test/test_profile_metadata.1.json @@ -0,0 +1,344 @@ +{ + "generators": { + "address": 2, + "nonce": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "update_profile_metadata", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "bytes": "516d50726f66696c6548617368" + } + ] + } + }, + "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_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": [ + { + "symbol": "Profile" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "vec": [ + { + "symbol": "Profile" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "address" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "client_jobs" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "client_points" + }, + "val": { + "i32": 0 + } + }, + { + "key": { + "symbol": "client_score" + }, + "val": { + "i32": 5000 + } + }, + { + "key": { + "symbol": "freelancer_jobs" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "freelancer_points" + }, + "val": { + "i32": 0 + } + }, + { + "key": { + "symbol": "freelancer_score" + }, + "val": { + "i32": 5000 + } + }, + { + "key": { + "symbol": "metadata_hash" + }, + "val": { + "bytes": "516d50726f66696c6548617368" + } + } + ] + } + } + }, + "ext": "v0" + }, + 150000 + ] + ], + [ + { + "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" + }, + 150000 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 150000 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000002" + }, + { + "symbol": "update_profile_metadata" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "bytes": "516d50726f66696c6548617368" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000002", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "update_profile_metadata" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000002" + }, + { + "symbol": "get_profile_metadata" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000002", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_profile_metadata" + } + ], + "data": { + "bytes": "516d50726f66696c6548617368" + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/reputation/test_snapshots/test/test_slash.1.json b/contracts/reputation/test_snapshots/test/test_slash.1.json index fe632428..1f21ea7b 100644 --- a/contracts/reputation/test_snapshots/test/test_slash.1.json +++ b/contracts/reputation/test_snapshots/test/test_slash.1.json @@ -86,17 +86,10 @@ "key": { "vec": [ { - "symbol": "Score" + "symbol": "Profile" }, { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - }, - { - "vec": [ - { - "symbol": "Client" - } - ] } ] }, @@ -113,17 +106,10 @@ "key": { "vec": [ { - "symbol": "Score" + "symbol": "Profile" }, { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - }, - { - "vec": [ - { - "symbol": "Client" - } - ] } ] }, @@ -140,19 +126,23 @@ }, { "key": { - "symbol": "role" + "symbol": "client_jobs" }, "val": { - "vec": [ - { - "symbol": "Client" - } - ] + "u32": 0 } }, { "key": { - "symbol": "score" + "symbol": "client_points" + }, + "val": { + "i32": 0 + } + }, + { + "key": { + "symbol": "client_score" }, "val": { "i32": 3000 @@ -160,11 +150,33 @@ }, { "key": { - "symbol": "total_jobs" + "symbol": "freelancer_jobs" }, "val": { "u32": 0 } + }, + { + "key": { + "symbol": "freelancer_points" + }, + "val": { + "i32": 0 + } + }, + { + "key": { + "symbol": "freelancer_score" + }, + "val": { + "i32": 5000 + } + }, + { + "key": { + "symbol": "metadata_hash" + }, + "val": "void" } ] } @@ -172,7 +184,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -217,7 +229,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -238,7 +250,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ] ] @@ -414,6 +426,14 @@ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" } }, + { + "key": { + "symbol": "reviews" + }, + "val": { + "u32": 0 + } + }, { "key": { "symbol": "role" @@ -441,6 +461,14 @@ "val": { "u32": 0 } + }, + { + "key": { + "symbol": "total_points" + }, + "val": { + "i32": 0 + } } ] } diff --git a/contracts/reputation/test_snapshots/test/test_unified_storage.1.json b/contracts/reputation/test_snapshots/test/test_unified_storage.1.json new file mode 100644 index 00000000..b00cad6b --- /dev/null +++ b/contracts/reputation/test_snapshots/test/test_unified_storage.1.json @@ -0,0 +1,718 @@ +{ + "generators": { + "address": 3, + "nonce": 0 + }, + "auth": [ + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "update_score", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "vec": [ + { + "symbol": "Freelancer" + } + ] + }, + { + "i32": 1000 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "update_score", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "vec": [ + { + "symbol": "Client" + } + ] + }, + { + "i32": 500 + } + ] + } + }, + "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_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": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Profile" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Profile" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "address" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "client_jobs" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "client_points" + }, + "val": { + "i32": 0 + } + }, + { + "key": { + "symbol": "client_score" + }, + "val": { + "i32": 5500 + } + }, + { + "key": { + "symbol": "freelancer_jobs" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "freelancer_points" + }, + "val": { + "i32": 0 + } + }, + { + "key": { + "symbol": "freelancer_score" + }, + "val": { + "i32": 6000 + } + }, + { + "key": { + "symbol": "metadata_hash" + }, + "val": "void" + } + ] + } + } + }, + "ext": "v0" + }, + 150000 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 150000 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 150000 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000003" + }, + { + "symbol": "initialize" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000003", + "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": "0000000000000000000000000000000000000000000000000000000000000003" + }, + { + "symbol": "update_score" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "vec": [ + { + "symbol": "Freelancer" + } + ] + }, + { + "i32": 1000 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000003", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "update_score" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000003" + }, + { + "symbol": "update_score" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "vec": [ + { + "symbol": "Client" + } + ] + }, + { + "i32": 500 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000003", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "update_score" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000003" + }, + { + "symbol": "get_score" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "vec": [ + { + "symbol": "Freelancer" + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000003", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_score" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "address" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "reviews" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "role" + }, + "val": { + "vec": [ + { + "symbol": "Freelancer" + } + ] + } + }, + { + "key": { + "symbol": "score" + }, + "val": { + "i32": 6000 + } + }, + { + "key": { + "symbol": "total_jobs" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "total_points" + }, + "val": { + "i32": 0 + } + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000003" + }, + { + "symbol": "get_score" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "vec": [ + { + "symbol": "Client" + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000003", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "get_score" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "address" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "reviews" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "role" + }, + "val": { + "vec": [ + { + "symbol": "Client" + } + ] + } + }, + { + "key": { + "symbol": "score" + }, + "val": { + "i32": 5500 + } + }, + { + "key": { + "symbol": "total_jobs" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "total_points" + }, + "val": { + "i32": 0 + } + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file 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 930434db..3bca725f 100644 --- a/contracts/reputation/test_snapshots/test/test_update_score.1.json +++ b/contracts/reputation/test_snapshots/test/test_update_score.1.json @@ -86,17 +86,10 @@ "key": { "vec": [ { - "symbol": "Score" + "symbol": "Profile" }, { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - }, - { - "vec": [ - { - "symbol": "Freelancer" - } - ] } ] }, @@ -113,17 +106,10 @@ "key": { "vec": [ { - "symbol": "Score" + "symbol": "Profile" }, { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - }, - { - "vec": [ - { - "symbol": "Freelancer" - } - ] } ] }, @@ -140,31 +126,57 @@ }, { "key": { - "symbol": "role" + "symbol": "client_jobs" }, "val": { - "vec": [ - { - "symbol": "Freelancer" - } - ] + "u32": 0 } }, { "key": { - "symbol": "score" + "symbol": "client_points" }, "val": { - "i32": 5500 + "i32": 0 + } + }, + { + "key": { + "symbol": "client_score" + }, + "val": { + "i32": 5000 } }, { "key": { - "symbol": "total_jobs" + "symbol": "freelancer_jobs" }, "val": { "u32": 1 } + }, + { + "key": { + "symbol": "freelancer_points" + }, + "val": { + "i32": 0 + } + }, + { + "key": { + "symbol": "freelancer_score" + }, + "val": { + "i32": 5500 + } + }, + { + "key": { + "symbol": "metadata_hash" + }, + "val": "void" } ] } @@ -172,7 +184,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -217,7 +229,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ], [ @@ -238,7 +250,7 @@ }, "ext": "v0" }, - 4095 + 150000 ] ] ] @@ -414,6 +426,14 @@ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" } }, + { + "key": { + "symbol": "reviews" + }, + "val": { + "u32": 1 + } + }, { "key": { "symbol": "role" @@ -441,6 +461,14 @@ "val": { "u32": 1 } + }, + { + "key": { + "symbol": "total_points" + }, + "val": { + "i32": 0 + } } ] } diff --git a/contracts/reputation/test_snapshots/test/test_upgrade_requires_admin.1.json b/contracts/reputation/test_snapshots/test/test_upgrade_requires_admin.1.json new file mode 100644 index 00000000..354e5d8c --- /dev/null +++ b/contracts/reputation/test_snapshots/test/test_upgrade_requires_admin.1.json @@ -0,0 +1,287 @@ +{ + "generators": { + "address": 3, + "nonce": 0 + }, + "auth": [ + [], + [] + ], + "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": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 150000 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 150000 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000003" + }, + { + "symbol": "initialize" + } + ], + "data": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000003", + "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": "0000000000000000000000000000000000000000000000000000000000000003" + }, + { + "symbol": "upgrade" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000000" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000003", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "upgrade" + } + ], + "data": { + "error": { + "contract": 2 + } + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000003", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 2 + } + } + ], + "data": { + "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + } + } + } + }, + "failed_call": true + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 2 + } + } + ], + "data": { + "vec": [ + { + "string": "contract call failed" + }, + { + "symbol": "upgrade" + }, + { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000000" + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "contract": 2 + } + } + ], + "data": { + "string": "escalating error to panic" + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/docs/contracts/escrow_job_registry_integration.md b/docs/contracts/escrow_job_registry_integration.md new file mode 100644 index 00000000..eba553d7 --- /dev/null +++ b/docs/contracts/escrow_job_registry_integration.md @@ -0,0 +1,67 @@ +# Escrow -> JobRegistry Cross-Contract Integration + +## Overview + +This document describes the Escrow-to-JobRegistry cross-contract synchronization flow. + +When a dispute is opened in the Escrow contract, Escrow now performs a cross-contract call to JobRegistry and marks the corresponding job as disputed. This keeps both contracts aligned for dispute workflows and backend indexing. + +## New Escrow Public Function + +### `set_job_registry(env, job_registry)` + +Configures the JobRegistry contract address used for dispute status synchronization. + +Behavior: + +- Requires Escrow admin authentication. +- Stores the JobRegistry address in Escrow instance storage. +- Emits `JobRegistryConfigured` event for observability. + +Errors: + +- `NotInitialized` (2): Escrow was not initialized. + +## Updated Escrow Behavior + +### `open_dispute(env, job_id, caller)` + +After validating caller and state, Escrow now: + +1. Sets Escrow job status to `Disputed`. +2. Calls `JobRegistry.mark_disputed(job_id)`. +3. Emits `RegistryDisputeSynced` and `OpenDispute` events. + +### `raise_dispute(env, job_id, caller)` + +After existing validation checks, Escrow now: + +1. Sets Escrow job status to `Disputed`. +2. Calls `JobRegistry.mark_disputed(job_id)`. +3. Emits `RegistryDisputeSynced` and `DisputeRaised` events. + +## Error Handling + +Escrow introduces an explicit Soroban error code for cross-contract failures: + +- `JobRegistrySyncFailed` (9): Cross-contract dispute sync failed. + +These provide clear, actionable failures without silent state drift. + +## Security and Validation Notes + +- Cross-contract call is only executed after existing caller and state validation passes. +- Sync is optional: if JobRegistry is not configured via `set_job_registry`, dispute flow continues in Escrow only. +- No arithmetic changes were introduced in this integration path. +- Cross-contract sync runs in the same transaction context, preserving atomicity. + +## Test Coverage + +Escrow now includes an integration test: + +- `test_open_dispute_syncs_job_registry_status` + +This test verifies: + +- Job is `InProgress` in JobRegistry before dispute. +- Opening dispute in Escrow transitions both Escrow and JobRegistry to `Disputed`. diff --git a/docs/contracts/storage_fee_handling.md b/docs/contracts/storage_fee_handling.md new file mode 100644 index 00000000..9d6c53ee --- /dev/null +++ b/docs/contracts/storage_fee_handling.md @@ -0,0 +1,72 @@ +# Soroban Storage Fee Handling (Footprints) + +## Overview + +This document describes how Lance contracts proactively handle Soroban storage rent by extending TTL for active state entries. The goal is to reduce footprint-related failures for long-running jobs and profiles while keeping on-chain behavior deterministic. + +## Strategy + +Each contract now applies a consistent TTL extension policy: + +- `threshold`: `50_000` +- `extend_to`: `150_000` + +When a key is read or written in a hot path, the contract calls `extend_ttl` to keep the entry alive. + +## Contract Coverage + +### Escrow + +File: `contracts/escrow/src/lib.rs` + +- Adds TTL helpers for instance and persistent storage. +- Extends instance TTL for admin/config-driven flows. +- Extends persistent TTL for job records in all major state transitions: + - `create_job` + - `add_milestone` + - `deposit` + - `release_milestone` + - `release_funds` + - `open_dispute` + - `raise_dispute` + - `resolve_dispute` + - `refund` + - `get_job` + - `get_milestone_status` + +### Job Registry + +File: `contracts/job_registry/src/lib.rs` + +- Adds persistent TTL helper. +- Extends TTL for job, bids, and deliverable keys during: + - `post_job` + - `submit_bid` + - `accept_bid` + - `submit_deliverable` + - `mark_disputed` + - `get_job` + - `get_bids` + - `get_deliverable` + +### Reputation + +Files: + +- `contracts/reputation/src/lib.rs` +- `contracts/reputation/src/storage.rs` + +- Extends instance TTL after admin/config and key update paths. +- Extends persistent TTL for profile and reviewed-marker entries. +- Applies TTL refresh in profile read/write helpers and in rating submission. + +## Security Notes + +- No external-call ordering was changed in a way that introduces reentrancy risk. +- Arithmetic and state validation checks remain intact. +- TTL extensions are conditional on key existence where required. + +## Operational Notes + +- This change improves resilience against storage-expiry related transaction failures. +- Threshold values are conservative defaults and can be tuned in future governance upgrades. diff --git a/docs/contracts/storage_layout_optimization.md b/docs/contracts/storage_layout_optimization.md new file mode 100644 index 00000000..ed72d763 --- /dev/null +++ b/docs/contracts/storage_layout_optimization.md @@ -0,0 +1,55 @@ +# Storage Layout Optimization (ContractData vs ContractInstance) + +## Overview + +This change optimizes Soroban storage layout by reducing unnecessary `ContractData` writes and tightening `ContractInstance`-based config control. + +The objective is to lower rent footprint and execution overhead without changing external behavior. + +## What Changed + +### 1) JobRegistry: lazy `Bids(job_id)` ContractData allocation + +File: `contracts/job_registry/src/lib.rs` + +Before: + +- `post_job` always created two persistent entries: + - `Job(job_id)` + - `Bids(job_id)` initialized as an empty vector + +After: + +- `post_job` creates only `Job(job_id)`. +- `Bids(job_id)` is created on first `submit_bid` write. +- Read paths (`get_bids`, `accept_bid`) already safely handle missing bids entry via `unwrap_or_else(Vec::new)`. + +Impact: + +- One less persistent `ContractData` entry per newly posted job that never receives bids. +- Lower storage rent pressure and smaller ledger footprint. + +### 2) Reputation: strict admin verification for instance config updates + +File: `contracts/reputation/src/lib.rs` + +`set_job_registry` now verifies: + +- caller auth (`require_auth`) and +- equality with admin stored in `ContractInstance` (`DataKey::Admin`). + +Impact: + +- Preserves intended instance-based config authority model. +- Prevents unauthorized instance config writes by any authenticated address. + +## Why this aligns with ContractData vs ContractInstance + +- `ContractInstance`: used for compact, singleton contract config (admin, registry pointers). +- `ContractData`: used for per-job/per-user dynamic state. +- Dynamic keys are now allocated lazily where possible (`Bids(job_id)`), minimizing persistent data creation. + +## Compatibility + +- No public function signatures were changed. +- Existing tests and behavior remain compatible. diff --git a/docs/contracts/upgradeability_pattern.md b/docs/contracts/upgradeability_pattern.md new file mode 100644 index 00000000..91169bdf --- /dev/null +++ b/docs/contracts/upgradeability_pattern.md @@ -0,0 +1,66 @@ +# Upgradeability Pattern + +## Overview + +This document describes the admin-gated contract upgrade pattern used in Lance Soroban contracts. + +The pattern enables controlled WASM upgrades while preserving on-chain state and minimizing attack surface. + +## Contracts Covered + +- `contracts/escrow/src/lib.rs` +- `contracts/job_registry/src/lib.rs` +- `contracts/reputation/src/lib.rs` + +## Pattern + +1. Authorized upgrade caller is checked on-chain. +2. Caller must pass `require_auth()`. +3. Contract verifies caller matches stored upgrade/admin authority. +4. Contract executes `env.deployer().update_current_contract_wasm(new_wasm_hash)`. +5. Contract emits a `ContractUpgraded` event for auditability. + +## Escrow + +- Uses existing `DataKey::Admin` as upgrade authority. +- New method: `upgrade(env, caller, new_wasm_hash) -> Result<(), EscrowError>`. +- New error code: `UpgradeUnauthorized`. +- New event: `ContractUpgraded`. + +## JobRegistry + +- Adds explicit upgrade authority management: + - `DataKey::UpgradeAdmin` + - `init_upgrade_admin(env, admin)` (one-time) + - `set_upgrade_admin(env, caller, new_admin)` + - `get_upgrade_admin(env)` + - `upgrade(env, caller, new_wasm_hash)` +- New errors: + - `UpgradeAdminAlreadySet` + - `UpgradeAdminNotSet` + - reuses `Unauthorized` for non-admin calls. +- New events: + - `UpgradeAdminSet` + - `ContractUpgraded` + +## Reputation + +- Uses existing `DataKey::Admin` as upgrade authority. +- New method: `upgrade(env, caller, new_wasm_hash) -> Result<(), ReputationError>`. +- New errors: + - `NotInitialized` + - `Unauthorized` +- New event: `ContractUpgraded`. + +## Security Considerations + +- Upgrade authority checks are explicit and enforced before WASM update call. +- State-changing upgrade operations emit events for off-chain monitoring. +- No reentrancy-sensitive external call sequence was introduced. +- Existing business logic and storage schemas remain backward compatible. + +## Operational Guidance + +- Use multisig/governance-controlled admin addresses in production. +- Rotate `JobRegistry` upgrade admin with `set_upgrade_admin` when operational roles change. +- Validate new WASM hash in staging/testnet before main deployment. diff --git a/package-lock.json b/package-lock.json index cd216233..ddd44451 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,7 +40,6 @@ "lucide-react": "^1.7.0", "next": "16.1.6", "next-themes": "^0.4.6", - "preact": "^10.29.0", "react": "19.2.3", "react-dom": "19.2.3", "sonner": "^2.0.7",