Skip to content

Commit

Permalink
Fix for HAL-01 - amount of cspr attached to pick_bid is now verified.
Browse files Browse the repository at this point in the history
  • Loading branch information
kubaplas committed Apr 30, 2024
1 parent a31be43 commit d7a2279
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 10 deletions.
2 changes: 1 addition & 1 deletion dao/src/bid_escrow/bid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use odra::types::{Address, Balance, BlockTime};
use odra::OdraType;

/// Bid status representation
#[derive(OdraType, PartialEq)]
#[derive(OdraType, PartialEq, Debug)]
pub enum BidStatus {
/// Placed, awaiting to be picked.
Created,
Expand Down
4 changes: 3 additions & 1 deletion dao/src/bid_escrow/bid_engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::configuration::{Configuration, ConfigurationBuilder};
use crate::modules::refs::ContractRefs;
use crate::utils::withdraw;
use alloc::rc::Rc;
use odra::contract_env;
use odra::contract_env::{caller, get_block_time};
use odra::prelude::{vec, vec::Vec};
use odra::types::{event::OdraEvent, Address, Balance, BlockTime};
Expand Down Expand Up @@ -231,7 +232,8 @@ impl BidEngine {
block_time: get_block_time(),
timeframe: bid.proposed_timeframe,
payment: bid.proposed_payment,
transferred_cspr: cspr_amount,
transferred_cspr: contract_env::attached_value(),
cspr_amount,
stake: bid.reputation_stake,
external_worker_cspr_stake: bid.cspr_stake.unwrap_or_default(),
};
Expand Down
3 changes: 3 additions & 0 deletions dao/src/bid_escrow/job.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ pub struct PickBidRequest {
pub payment: Balance,
/// The amount transferred by `Job Poster`.
pub transferred_cspr: Balance,
/// The amount declared to be transferred by `Job Poster`.
pub cspr_amount: Balance,
/// Bid reputation stake.
pub stake: Balance,
/// Bid CSPR stake - for an [External Worker](crate::bid_escrow#definitions).
Expand Down Expand Up @@ -114,6 +116,7 @@ impl Job {
.add_validation(DoesProposedPaymentMatchTransferred::create(
request.payment,
request.transferred_cspr,
request.cspr_amount,
))
.build()
.validate_generic_validations();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ use odra::types::Balance;
pub struct DoesProposedPaymentMatchTransferred {
proposed_payment: Balance,
transferred: Balance,
declared: Balance,
}

impl Validation for DoesProposedPaymentMatchTransferred {
fn validate(&self) -> Result<(), Error> {
if self.proposed_payment != self.transferred {
if (self.proposed_payment != self.transferred)
|| (self.proposed_payment != self.declared)
|| self.transferred != self.declared
{
return Err(Error::PurseBalanceMismatch);
}

Expand Down
34 changes: 27 additions & 7 deletions dao/tests/common/contracts/bid_escrow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,6 @@ use odra::test_env;
use odra::types::{Balance, BlockTime};

impl DaoWorld {
pub fn get_bid(&self, offer_id: JobOfferId, poster: Account) -> Option<Bid> {
let poster = self.get_address(&poster);
let bid_id = self.bids.get(&(offer_id, poster))?;

self.bid_escrow.get_bid(*bid_id)
}

pub fn get_job_offer_id(&self, job_poster: &Account) -> Option<&JobOfferId> {
let job_poster = self.get_address(job_poster);
self.offers.get(&job_poster)
Expand Down Expand Up @@ -50,6 +43,13 @@ impl DaoWorld {
self.bids.insert((offer_id, bidder), bid_id);
}

pub fn get_bid(&self, offer_id: JobOfferId, poster: Account) -> Option<Bid> {
let poster = self.get_address(&poster);
let bid_id = self.bids.get(&(offer_id, poster))?;

self.bid_escrow.get_bid(*bid_id)
}

pub fn cancel_bid(&mut self, worker: Account, job_offer_id: JobOfferId, bid_id: BidId) {
let worker = self.get_address(&worker);
test_env::set_caller(worker);
Expand Down Expand Up @@ -97,6 +97,26 @@ impl DaoWorld {
);
}

pub fn pick_bid_without_enough_payment(&mut self, job_poster: Account, worker: Account) {
let job_poster = self.get_address(&job_poster);
let worker = self.get_address(&worker);
let job_offer_id = self.offers.get(&job_poster).expect("Job Offer not found.");
let bid_id = self
.bids
.get(&(*job_offer_id, worker))
.expect("Bid id not found.");
let bid = self.bid_escrow.get_bid(*bid_id).expect("Bid not found.");
let payment = bid.proposed_payment - Balance::one();
test_env::assert_exception(Error::PurseBalanceMismatch, || {
test_env::set_caller(job_poster);
self.bid_escrow.with_tokens(payment).pick_bid(
*job_offer_id,
*bid_id,
bid.proposed_payment,
);
});
}

// pub fn slash_all_active_job_offers(&mut self, bidder: Account) {
// let bidder = self.get_address(&bidder);
// self.bid_escrow.slash_all_active_job_offers(bidder);
Expand Down
41 changes: 41 additions & 0 deletions dao/tests/features/bid_escrow/picking_a_bid_without_paying.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
Feature: Picking a bid without paying
JobPoster posts a job, internal worker is bidding.
Job Poster picks a bid of an Internal Worker, without sending exact amount of CSPR.
Picking a bid is rejected.
This is a presentation of HAL-01 issue fix.
Background:
Given following balances
| account | CSPR balance | REP balance | REP stake | is_kyced | is_va |
| BidEscrow | 0 | 0 | 0 | false | false |
| MultisigWallet | 0 | 0 | 0 | false | false |
| JobPoster | 1000 | 0 | 0 | true | false |
| InternalWorker | 0 | 1000 | 0 | true | true |
| VA1 | 0 | 1000 | 0 | true | true |
| VA2 | 0 | 1000 | 0 | true | true |
And following configuration
| key | value |
| TimeBetweenInformalAndFormalVoting | 0 |
| VotingStartAfterJobSubmission | 0 |
When JobPoster posted a JobOffer with expected timeframe of 14 days, maximum budget of 1000 CSPR and 400 CSPR DOS Fee
And InternalWorker posted the Bid for JobOffer 0 with proposed timeframe of 7 days and 500 CSPR price and 100 REP stake
And 8 days passed
Then balances are
| account | CSPR balance | REP balance | REP stake |
# Initial 1000 + 400 dos fee. Notice lack of 500 CSPR payment from the bid.
| BidEscrow | 400 | 0 | 0 |
| JobPoster | 600 | 0 | 0 |
| InternalWorker | 0 | 1000 | 100 |
| VA1 | 0 | 1000 | 0 |
| VA2 | 0 | 1000 | 0 |
Scenario: JobPoster picked the Bid of InternalWorker without sending exact amount of CSPR
# Following step will fail, before fix it would pass
When JobPoster picked the Bid without paying for InternalWorker
Then balances are
| account | CSPR balance | REP balance | REP stake |
# Initial 1000 + 400 dos fee. Notice lack of 500 CSPR payment from the bid.
| BidEscrow | 400 | 0 | 0 |
| JobPoster | 600 | 0 | 0 |
| InternalWorker | 0 | 1000 | 100 |
| VA1 | 0 | 1000 | 0 |
| VA2 | 0 | 1000 | 0 |
And the Bid of InternalWorker is in state Created
13 changes: 13 additions & 0 deletions dao/tests/steps/bid_escrow.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use cucumber::{then, when};
use dao::bid_escrow::bid::BidStatus;
use dao::bid_escrow::contract::BidEscrowContractRef;
use dao::bid_escrow::job::JobStatus;
use dao::bid_escrow::job_offer::JobOfferStatus;
Expand Down Expand Up @@ -128,6 +129,11 @@ fn bid_picked(w: &mut DaoWorld, job_poster: Account, worker: Account) {
w.pick_bid(job_poster, worker);
}

#[when(expr = "{account} picked the Bid without paying for {account}")]
fn bid_picked_without_paying(w: &mut DaoWorld, job_poster: Account, worker: Account) {
w.pick_bid_without_enough_payment(job_poster, worker);
}

#[when(expr = "{account} submits the JobProof of Job {int}")]
fn submit_job_proof(w: &mut DaoWorld, worker: Account, job_id: JobId) {
let worker = w.get_address(&worker);
Expand Down Expand Up @@ -226,6 +232,13 @@ fn assert_job_offer_status(world: &mut DaoWorld, job_poster: Account, job_offer_
};
}

#[then(expr = "the Bid of InternalWorker is in state Created")]
fn assert_bid_status(world: &mut DaoWorld) {
let job_poster = Account::InternalWorker;
let bid = world.get_bid(0, job_poster).unwrap();
assert_eq!(bid.status, BidStatus::Created);
}

#[then(expr = "{account} cannot submit the JobProof of Job {int}")]
fn cannot_submit_job_proof(w: &mut DaoWorld, worker: Account, job_id: JobId) {
let worker = w.get_address(&worker);
Expand Down

0 comments on commit d7a2279

Please sign in to comment.