From ef47f5efa9783b2369b14e77e32f338b69d788e1 Mon Sep 17 00:00:00 2001 From: devfoma Date: Wed, 22 Apr 2026 14:41:16 -0700 Subject: [PATCH 1/5] Feat: Implements Job registry and get bids view function --- contracts/job_registry/src/lib.rs | 83 ++++++++++++++++++++++++------- contracts/reputation/src/lib.rs | 5 +- docs/contracts/job_registry.md | 33 +++++++++++- 3 files changed, 101 insertions(+), 20 deletions(-) diff --git a/contracts/job_registry/src/lib.rs b/contracts/job_registry/src/lib.rs index 47c89477..d67c1ffd 100644 --- a/contracts/job_registry/src/lib.rs +++ b/contracts/job_registry/src/lib.rs @@ -323,18 +323,47 @@ impl JobRegistryContract { Ok(()) } - pub fn get_job(env: Env, job_id: u64) -> JobRecord { + /// Retrieves a job record by its ID. + /// + /// This is a view function that provides the full state of a job, + /// including its status, client, and assigned freelancer. + /// + /// # Arguments + /// * `env` - The Soroban environment + /// * `job_id` - The unique identifier of the job + /// + /// # Returns + /// * `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() .persistent() .get(&DataKey::Job(job_id)) - .expect("job not found") + .ok_or(JobRegistryError::JobNotFound) } - pub fn get_bids(env: Env, job_id: u64) -> Vec { - env.storage() + /// Retrieves all bids for a specific job. + /// + /// This is a view function that returns the history of all bids + /// submitted for a given job. If a job exists but has no bids, + /// an empty vector is returned. + /// + /// # Arguments + /// * `env` - The Soroban environment + /// * `job_id` - The unique identifier of the job + /// + /// # Returns + /// * `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)) { + return Err(JobRegistryError::JobNotFound); + } + + Ok(env.storage() .persistent() .get(&DataKey::Bids(job_id)) - .unwrap_or_else(|| Vec::new(&env)) + .unwrap_or_else(|| Vec::new(&env))) } pub fn get_deliverable(env: Env, job_id: u64) -> Bytes { @@ -365,25 +394,25 @@ mod test { let hash = Bytes::from_slice(&env, b"QmSomeIPFSHash"); cc.post_job(&1u64, &client, &hash, &5000i128); - let job = cc.get_job(&1u64); + let job = cc.get_job(&1u64).unwrap(); assert_eq!(job.status, JobStatus::Open); assert_eq!(job.freelancer, None); let proposal = Bytes::from_slice(&env, b"QmProposalHash"); cc.submit_bid(&1u64, &freelancer, &proposal); - let bids = cc.get_bids(&1u64); + let bids = cc.get_bids(&1u64).unwrap(); assert_eq!(bids.len(), 1); cc.accept_bid(&1u64, &client, &freelancer); - let job = cc.get_job(&1u64); + let job = cc.get_job(&1u64).unwrap(); assert_eq!(job.status, JobStatus::InProgress); assert_eq!(job.freelancer, Some(freelancer.clone())); let deliverable = Bytes::from_slice(&env, b"QmDeliverableHash"); cc.submit_deliverable(&1u64, &freelancer, &deliverable); - let job = cc.get_job(&1u64); + let job = cc.get_job(&1u64).unwrap(); assert_eq!(job.status, JobStatus::DeliverableSubmitted); let d = cc.get_deliverable(&1u64); @@ -411,7 +440,7 @@ mod test { let proposal = Bytes::from_slice(&env, b"QmProposal"); cc.submit_bid(&1u64, &freelancer, &proposal); - let bids = cc.get_bids(&1u64); + let bids = cc.get_bids(&1u64).unwrap(); assert_eq!(bids.len(), 1); assert_eq!(bids.get(0).unwrap().freelancer, freelancer); assert_eq!(bids.get(0).unwrap().proposal_hash, proposal); @@ -494,7 +523,7 @@ mod test { cc.accept_bid(&1u64, &client, &freelancer); - let job = cc.get_job(&1u64); + let job = cc.get_job(&1u64).unwrap(); assert_eq!(job.status, JobStatus::InProgress); assert_eq!(job.freelancer, Some(freelancer)); } @@ -544,7 +573,7 @@ mod test { cc.submit_bid(&1u64, &freelancer, &proposal); } - let bids = cc.get_bids(&1u64); + let bids = cc.get_bids(&1u64).unwrap(); assert_eq!(bids.len(), 5); } @@ -569,7 +598,7 @@ mod test { let proposal2 = Bytes::from_slice(&env, b"QmProposal2"); cc.submit_bid(&1u64, &freelancer, &proposal2); - let bids = cc.get_bids(&1u64); + let bids = cc.get_bids(&1u64).unwrap(); assert_eq!(bids.len(), 2); } @@ -620,7 +649,7 @@ mod test { cc.accept_bid(&1u64, &client, &freelancer); cc.mark_disputed(&1u64); - let job = cc.get_job(&1u64); + let job = cc.get_job(&1u64).unwrap(); assert_eq!(job.status, JobStatus::Disputed); } @@ -646,7 +675,7 @@ mod test { cc.submit_deliverable(&1u64, &freelancer, &deliverable); cc.mark_disputed(&1u64); - let job = cc.get_job(&1u64); + let job = cc.get_job(&1u64).unwrap(); assert_eq!(job.status, JobStatus::Disputed); } @@ -714,8 +743,8 @@ mod test { cc.submit_bid(&2u64, &f, &prop2); } - assert_eq!(cc.get_bids(&1u64).len(), 3); - assert_eq!(cc.get_bids(&2u64).len(), 3); + assert_eq!(cc.get_bids(&1u64).unwrap().len(), 3); + assert_eq!(cc.get_bids(&2u64).unwrap().len(), 3); } #[test] @@ -810,4 +839,24 @@ mod test { let empty_deliverable = Bytes::from_slice(&env, b""); cc.submit_deliverable(&1u64, &freelancer, &empty_deliverable); } + + #[test] + #[should_panic(expected = "Error(Contract, #1)")] + fn test_get_job_not_found() { + let env = Env::default(); + let contract_id = env.register_contract(None, JobRegistryContract); + let cc = JobRegistryContractClient::new(&env, &contract_id); + + cc.get_job(&999u64); + } + + #[test] + #[should_panic(expected = "Error(Contract, #1)")] + fn test_get_bids_job_not_found() { + let env = Env::default(); + let contract_id = env.register_contract(None, JobRegistryContract); + let cc = JobRegistryContractClient::new(&env, &contract_id); + + cc.get_bids(&999u64); + } } diff --git a/contracts/reputation/src/lib.rs b/contracts/reputation/src/lib.rs index 7daead64..656d3611 100644 --- a/contracts/reputation/src/lib.rs +++ b/contracts/reputation/src/lib.rs @@ -93,7 +93,10 @@ impl ReputationContract { // call JobRegistry.get_job(job_id) and decode into local JobRecord let get_sym = Symbol::new(&env, "get_job"); let args = soroban_sdk::vec![&env, job_id.into_val(&env)]; - let job: JobRecord = env.invoke_contract::(®istry_addr, &get_sym, args); + let job: JobRecord = env + .invoke_contract::>(®istry_addr, &get_sym, args) + .unwrap() + .unwrap(); // verify job is completed (ratings only allowed after completion) assert!(job.status == JobStatus::Completed, "job not completed"); diff --git a/docs/contracts/job_registry.md b/docs/contracts/job_registry.md index 7979fd1d..eba1a1f2 100644 --- a/docs/contracts/job_registry.md +++ b/docs/contracts/job_registry.md @@ -28,6 +28,35 @@ The `JobRegistry` contract manages job postings, bid submissions, bid acceptance - `Unauthorized` (3): caller is not the job's client. - `BidNotFound` (6): selected freelancer did not submit a bid. -### Notes - This implementation strengthens trustlessness by ensuring bid acceptance can only succeed for bidders who actually participated in the auction. + +## `get_job` + +### Purpose + +`get_job` is a view function that retrieves the full record of a specific job. + +### Behavior + +- Retrieves the `JobRecord` from persistent storage. +- Returns the job details if it exists. + +### Errors + +- `JobNotFound` (1): The specified job ID does not exist. + +## `get_bids` + +### Purpose + +`get_bids` is a view function that retrieves all bids submitted for a specific job. + +### Behavior + +- Verifies the job exists. +- Retrieves the list of `BidRecord`s associated with the job. +- Returns an empty list if the job exists but has no bids. + +### Errors + +- `JobNotFound` (1): The specified job ID does not exist. From e40b7e49b7a6f48f3ec786e6f9e7f8464c7c1782 Mon Sep 17 00:00:00 2001 From: devfoma Date: Wed, 22 Apr 2026 14:54:47 -0700 Subject: [PATCH 2/5] Feat: Implements Job registry and get bids view function --- contracts/job_registry/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/job_registry/src/lib.rs b/contracts/job_registry/src/lib.rs index d67c1ffd..6e9ad583 100644 --- a/contracts/job_registry/src/lib.rs +++ b/contracts/job_registry/src/lib.rs @@ -360,7 +360,8 @@ impl JobRegistryContract { return Err(JobRegistryError::JobNotFound); } - Ok(env.storage() + Ok(env + .storage() .persistent() .get(&DataKey::Bids(job_id)) .unwrap_or_else(|| Vec::new(&env))) From 625dea5eb778ce4d255bb8b4abcbe40413fc939c Mon Sep 17 00:00:00 2001 From: devfoma Date: Wed, 22 Apr 2026 15:07:11 -0700 Subject: [PATCH 3/5] Feat: Implements Job registry and get bids view function --- contracts/reputation/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/reputation/src/lib.rs b/contracts/reputation/src/lib.rs index 656d3611..7883eb22 100644 --- a/contracts/reputation/src/lib.rs +++ b/contracts/reputation/src/lib.rs @@ -94,8 +94,7 @@ impl ReputationContract { let get_sym = Symbol::new(&env, "get_job"); let args = soroban_sdk::vec![&env, job_id.into_val(&env)]; let job: JobRecord = env - .invoke_contract::>(®istry_addr, &get_sym, args) - .unwrap() + .invoke_contract::>(®istry_addr, &get_sym, args) .unwrap(); // verify job is completed (ratings only allowed after completion) From 01d983ddebd45b1d4854424d4d196ed7f31eccb4 Mon Sep 17 00:00:00 2001 From: devfoma Date: Wed, 22 Apr 2026 15:19:24 -0700 Subject: [PATCH 4/5] Feat: Implements Job registry and get bids view function --- contracts/reputation/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/contracts/reputation/src/lib.rs b/contracts/reputation/src/lib.rs index 7883eb22..d9a31cd1 100644 --- a/contracts/reputation/src/lib.rs +++ b/contracts/reputation/src/lib.rs @@ -94,7 +94,11 @@ impl ReputationContract { let get_sym = Symbol::new(&env, "get_job"); let args = soroban_sdk::vec![&env, job_id.into_val(&env)]; let job: JobRecord = env - .invoke_contract::>(®istry_addr, &get_sym, args) + .invoke_contract::>( + ®istry_addr, + &get_sym, + args, + ) .unwrap(); // verify job is completed (ratings only allowed after completion) From 594dc98801382522d2f75159fe8cd942a9c26dde Mon Sep 17 00:00:00 2001 From: devfoma Date: Wed, 22 Apr 2026 15:28:20 -0700 Subject: [PATCH 5/5] Feat: Implements Job registry and get bids view function --- contracts/job_registry/src/lib.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/contracts/job_registry/src/lib.rs b/contracts/job_registry/src/lib.rs index 6e9ad583..b633a1c5 100644 --- a/contracts/job_registry/src/lib.rs +++ b/contracts/job_registry/src/lib.rs @@ -395,25 +395,25 @@ mod test { let hash = Bytes::from_slice(&env, b"QmSomeIPFSHash"); cc.post_job(&1u64, &client, &hash, &5000i128); - let job = cc.get_job(&1u64).unwrap(); + let job = cc.get_job(&1u64); assert_eq!(job.status, JobStatus::Open); assert_eq!(job.freelancer, None); let proposal = Bytes::from_slice(&env, b"QmProposalHash"); cc.submit_bid(&1u64, &freelancer, &proposal); - let bids = cc.get_bids(&1u64).unwrap(); + let bids = cc.get_bids(&1u64); assert_eq!(bids.len(), 1); cc.accept_bid(&1u64, &client, &freelancer); - let job = cc.get_job(&1u64).unwrap(); + let job = cc.get_job(&1u64); assert_eq!(job.status, JobStatus::InProgress); assert_eq!(job.freelancer, Some(freelancer.clone())); let deliverable = Bytes::from_slice(&env, b"QmDeliverableHash"); cc.submit_deliverable(&1u64, &freelancer, &deliverable); - let job = cc.get_job(&1u64).unwrap(); + let job = cc.get_job(&1u64); assert_eq!(job.status, JobStatus::DeliverableSubmitted); let d = cc.get_deliverable(&1u64); @@ -441,7 +441,7 @@ mod test { let proposal = Bytes::from_slice(&env, b"QmProposal"); cc.submit_bid(&1u64, &freelancer, &proposal); - let bids = cc.get_bids(&1u64).unwrap(); + let bids = cc.get_bids(&1u64); assert_eq!(bids.len(), 1); assert_eq!(bids.get(0).unwrap().freelancer, freelancer); assert_eq!(bids.get(0).unwrap().proposal_hash, proposal); @@ -524,7 +524,7 @@ mod test { cc.accept_bid(&1u64, &client, &freelancer); - let job = cc.get_job(&1u64).unwrap(); + let job = cc.get_job(&1u64); assert_eq!(job.status, JobStatus::InProgress); assert_eq!(job.freelancer, Some(freelancer)); } @@ -574,7 +574,7 @@ mod test { cc.submit_bid(&1u64, &freelancer, &proposal); } - let bids = cc.get_bids(&1u64).unwrap(); + let bids = cc.get_bids(&1u64); assert_eq!(bids.len(), 5); } @@ -599,7 +599,7 @@ mod test { let proposal2 = Bytes::from_slice(&env, b"QmProposal2"); cc.submit_bid(&1u64, &freelancer, &proposal2); - let bids = cc.get_bids(&1u64).unwrap(); + let bids = cc.get_bids(&1u64); assert_eq!(bids.len(), 2); } @@ -650,7 +650,7 @@ mod test { cc.accept_bid(&1u64, &client, &freelancer); cc.mark_disputed(&1u64); - let job = cc.get_job(&1u64).unwrap(); + let job = cc.get_job(&1u64); assert_eq!(job.status, JobStatus::Disputed); } @@ -676,7 +676,7 @@ mod test { cc.submit_deliverable(&1u64, &freelancer, &deliverable); cc.mark_disputed(&1u64); - let job = cc.get_job(&1u64).unwrap(); + let job = cc.get_job(&1u64); assert_eq!(job.status, JobStatus::Disputed); } @@ -744,8 +744,8 @@ mod test { cc.submit_bid(&2u64, &f, &prop2); } - assert_eq!(cc.get_bids(&1u64).unwrap().len(), 3); - assert_eq!(cc.get_bids(&2u64).unwrap().len(), 3); + assert_eq!(cc.get_bids(&1u64).len(), 3); + assert_eq!(cc.get_bids(&2u64).len(), 3); } #[test]