1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
//! JobOffer-related structs.

extern crate alloc;

use crate::bid_escrow::job::PickBidRequest;
use crate::bid_escrow::types::JobOfferId;
use crate::configuration::Configuration;
use crate::rules::validation::bid_escrow::{
    CanJobOfferBeCancelled, CanProgressJobOffer, HasPermissionsToCancelJobOffer, IsDosFeeEnough,
};
use crate::rules::validation::IsUserKyced;
use crate::rules::RulesBuilder;
use alloc::rc::Rc;
use odra::types::{Address, Balance, BlockTime};
use odra::OdraType;

/// Serializable JobOffer status representation.
#[derive(OdraType, PartialEq)]
pub enum JobOfferStatus {
    /// Created, Bidders can place bids.
    Created,
    /// Bid selected, a Worker works on it.
    InProgress,
    /// Offer canceled, is no longer valid.
    Cancelled,
}

/// Auction state representation.
#[derive(PartialEq)]
pub enum AuctionState {
    /// Unknown state.
    None,
    /// Internal Auction - only VAs' can bid.
    Internal,
    /// Public Auction - nonVAs' can bid.
    Public,
}

/// Data required to post a job offer.
pub struct PostJobOfferRequest {
    /// New offer id.
    pub job_offer_id: JobOfferId,
    /// The offer creator.
    pub job_poster: Address,
    /// Is the creator passed the KYC process.
    pub job_poster_kyced: bool,
    /// Max amount the Job Poster can pay for the Job.
    pub max_budget: Balance,
    /// The time the Job should be completed.
    pub expected_timeframe: BlockTime,
    /// CSPR amount attached to Post Job query.
    pub dos_fee: Balance,
    /// The time since the offer is available for Bidders.
    pub start_time: BlockTime,
    /// Job configuration.
    pub configuration: Rc<Configuration>,
}

/// Data required to cancel a job offer.
pub struct CancelJobOfferRequest {
    /// The request caller.
    pub caller: Address,
    /// The request creation time.
    pub block_time: BlockTime,
}

/// Writeable/readable representation of a `Job Offer`.
#[derive(OdraType)]
pub struct JobOffer {
    /// Offer id.
    pub job_offer_id: JobOfferId,
    /// The offer creator.
    pub job_poster: Address,
    /// Max amount the Job Poster can pay for the Job.
    pub max_budget: Balance,
    /// The time the Job should be completed.
    pub expected_timeframe: BlockTime,
    /// CSPR amount attached to the offer.
    pub dos_fee: Balance,
    /// The current job offer status.
    pub status: JobOfferStatus,
    /// The time since the offer is available for Bidders.
    pub start_time: BlockTime,
    /// Job configuration.
    pub configuration: Configuration,
}

impl JobOffer {
    /// Conditionally creates a new instance of JobOffer.
    ///
    /// Runs validation:
    /// * [`IsUserKyced`]
    /// * [`IsDosFeeEnough`]
    /// Stops contract execution if any validation fails.
    pub fn new(request: PostJobOfferRequest) -> JobOffer {
        RulesBuilder::new()
            .add_validation(IsUserKyced::create(request.job_poster_kyced))
            .add_validation(IsDosFeeEnough::create(
                request.configuration.clone(),
                request.dos_fee,
            ))
            .build()
            .validate_generic_validations();

        JobOffer {
            job_offer_id: request.job_offer_id,
            job_poster: request.job_poster,
            max_budget: request.max_budget,
            expected_timeframe: request.expected_timeframe,
            dos_fee: request.dos_fee,
            status: JobOfferStatus::Created,
            start_time: request.start_time,
            configuration: (*request.configuration).clone(),
        }
    }

    /// Conditionally changes the status to [InProgress](JobOfferStatus::InProgress).
    ///
    /// Runs validation:
    /// * [`CanProgressJobOffer`]
    ///
    /// Stops contract execution if the validation fails.
    pub fn in_progress(&mut self, request: &PickBidRequest) {
        RulesBuilder::new()
            .add_validation(CanProgressJobOffer::create(request.caller, self.job_poster))
            .build()
            .validate_generic_validations();

        self.status = JobOfferStatus::InProgress;
    }

    /// Conditionally changes the status to [Cancelled](JobOfferStatus::Cancelled).
    ///
    /// Runs validation:
    /// * [`HasPermissionsToCancelJobOffer`]
    /// * [`CanJobOfferBeCancelled`]
    ///
    /// Stops contract execution if any validation fails.
    pub fn cancel(&mut self, request: &CancelJobOfferRequest) {
        RulesBuilder::new()
            .add_validation(HasPermissionsToCancelJobOffer::create(
                request.caller,
                self.job_poster,
            ))
            .add_validation(CanJobOfferBeCancelled::create(
                self.auction_state(request.block_time),
            ))
            .build()
            .validate_generic_validations();

        self.status = JobOfferStatus::Cancelled;
    }

    /// Gets the auction state in a given time.
    pub fn auction_state(&self, block_time: BlockTime) -> AuctionState {
        let public_auction_start_time =
            self.start_time + self.configuration.internal_auction_time();
        let public_auction_end_time =
            public_auction_start_time + self.configuration.public_auction_time();
        if block_time >= self.start_time && block_time < public_auction_start_time {
            AuctionState::Internal
        } else if block_time >= public_auction_start_time && block_time < public_auction_end_time {
            AuctionState::Public
        } else {
            AuctionState::None
        }
    }

    /// Gets a reference to the job configuration.
    pub fn configuration(&self) -> &Configuration {
        &self.configuration
    }

    pub fn slash(&mut self) {
        self.status = JobOfferStatus::Cancelled;
    }
}