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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
//! System configuration.
//!
//! A configuration is a mix of [`Governance Variables`] and voting configuration.
//! DAO supports a few types of voting. Each type may have a slightly different configuration.
//! Once voting is created, until the end, voting relies on the system's state at the moment of voting creation.
//! It mitigates unexpected behavior during voting if the internal DAO state changes.
//!
//! [`Governance Variables`]: crate::core_contracts::VariableRepositoryContract
mod builder;
mod dao_configuration;
mod voting_configuration;

pub use builder::ConfigurationBuilder;
pub use dao_configuration::DaoConfiguration;
pub use voting_configuration::VotingConfiguration;

use crate::utils::{per_mil_of, per_mil_of_as_u32, to_per_mils, ContractCall, Error};
use odra::contract_env::revert;
use odra::prelude::{collections::BTreeMap, string::String, vec::Vec};
use odra::types::{Address, Balance, BlockTime, Bytes, OdraType};
use odra::{OdraType, UnwrapOrRevert};

/// Represents the current system configuration.
#[derive(OdraType)]
pub struct Configuration {
    dao_configuration: DaoConfiguration,
    voting_configuration: VotingConfiguration,
    total_onboarded: Balance,
    fiat_rate: Option<Balance>,
}

impl Configuration {
    pub fn set_bind_ballot_for_successful_voting(
        &mut self,
        bind_ballot_for_successful_voting: bool,
    ) {
        self.voting_configuration
            .set_bind_ballot_for_successful_voting(bind_ballot_for_successful_voting);
    }

    pub fn set_unbound_ballot_address(&mut self, unbound_ballot_address: Option<Address>) {
        self.voting_configuration
            .set_unbound_ballot_address(unbound_ballot_address);
    }

    pub fn set_is_bid_escrow(&mut self, is_bid_escrow: bool) {
        self.voting_configuration.set_is_bid_escrow(is_bid_escrow);
    }

    pub fn set_only_va_can_create(&mut self, only_va_can_create: bool) {
        self.voting_configuration
            .set_only_va_can_create(only_va_can_create);
    }

    pub fn set_contract_calls(&mut self, contract_calls: Vec<ContractCall>) {
        self.voting_configuration.set_contract_calls(contract_calls);
    }

    /// Indicates if the creator ballot should be bounded at the voting ends.
    pub fn should_bind_ballot_for_successful_voting(&self) -> bool {
        self.voting_configuration
            .should_bind_ballot_for_successful_voting()
    }

    /// Gets the address of the user who cast an unbound ballot.
    pub fn get_unbound_ballot_address(&self) -> Option<Address> {
        self.voting_configuration.get_unbound_ballot_address()
    }

    pub fn new(
        dao_configuration: DaoConfiguration,
        voting_configuration: VotingConfiguration,
        total_onboarded: Balance,
    ) -> Configuration {
        Configuration {
            dao_configuration,
            voting_configuration,
            total_onboarded,
            fiat_rate: None,
        }
    }

    pub fn set_fiat_rate(&mut self, fiat_rate: Option<Balance>) {
        self.fiat_rate = fiat_rate;
    }

    /// Sets the flag `bind_ballot_for_successful_voting` and the address of the voter.
    pub fn bind_ballot_for_successful_voting(&mut self, address: Address) {
        self.voting_configuration.bind_ballot_for_successful_voting = true;
        self.voting_configuration.unbound_ballot_address = Some(address);
    }

    /// Sets the flag `double_time_between_votings`. See [Self::should_double_time_between_votings()].
    pub fn double_time_between_votings(&mut self) {
        self.voting_configuration.double_time_between_votings = true
    }

    /// Indicates if the time between informal and formal voting should be doubled.
    ///
    /// See [Variable Repository](crate::core_contracts::VariableRepositoryContract) TimeBetweenInformalAndFormalVoting
    /// ([available keys](crate::core_contracts::VariableRepositoryContract#available-keys)).
    pub fn should_double_time_between_votings(&self) -> bool {
        self.voting_configuration.double_time_between_votings
    }

    /// Gets the address of the contract holding the current fiat conversion rate.
    ///
    /// See [Variable Repository](crate::core_contracts::VariableRepositoryContract) FiatConversionRateAddress
    /// ([available keys](crate::core_contracts::VariableRepositoryContract#available-keys)).
    pub fn fiat_conversion_rate_address(&self) -> Address {
        self.dao_configuration.fiat_conversion_rate_address
    }

    /// Gets formal voting quorum.
    ///
    /// See [Variable Repository](crate::core_contracts::VariableRepositoryContract) BidEscrowFormalQuorumRatio/FormalQuorumRatio
    /// ([available keys](crate::core_contracts::VariableRepositoryContract#available-keys)).
    pub fn formal_voting_quorum(&self) -> u32 {
        let ratio = match self.voting_configuration.is_bid_escrow {
            true => self.dao_configuration.bid_escrow_formal_quorum_ratio,
            false => self.dao_configuration.formal_quorum_ratio,
        };

        per_mil_of_as_u32(ratio, self.total_onboarded())
            .unwrap_or_revert_with(Error::ArithmeticOverflow)
    }

    /// Gets informal voting quorum.
    ///
    /// See [Variable Repository](crate::core_contracts::VariableRepositoryContract) BidEscrowInformalQuorumRatio/InformalQuorumRatio
    /// ([available keys](crate::core_contracts::VariableRepositoryContract#available-keys)).
    pub fn informal_voting_quorum(&self) -> u32 {
        let ratio = match self.voting_configuration.is_bid_escrow {
            true => self.dao_configuration.bid_escrow_informal_quorum_ratio,
            false => self.dao_configuration.informal_quorum_ratio,
        };

        per_mil_of_as_u32(ratio, self.total_onboarded())
            .unwrap_or_revert_with(Error::ArithmeticOverflow)
    }

    /// Gets informal voting time.
    ///
    /// See [Variable Repository](crate::core_contracts::VariableRepositoryContract) BidEscrowInformalVotingTime/InformalVotingTime
    /// ([available keys](crate::core_contracts::VariableRepositoryContract#available-keys)).
    pub fn informal_voting_time(&self) -> BlockTime {
        match self.voting_configuration.is_bid_escrow {
            true => self.dao_configuration.bid_escrow_informal_voting_time,
            false => self.dao_configuration.informal_voting_time,
        }
    }

    /// Gets formal voting time.
    ///
    /// See [Variable Repository](crate::core_contracts::VariableRepositoryContract) BidEscrowInformalVotingTime/InformalVotingTime
    /// ([available keys](crate::core_contracts::VariableRepositoryContract#available-keys)).
    pub fn formal_voting_time(&self) -> BlockTime {
        match self.voting_configuration.is_bid_escrow {
            true => self.dao_configuration.bid_escrow_formal_voting_time,
            false => self.dao_configuration.formal_voting_time,
        }
    }

    /// Gets formal voting time.
    ///
    /// See [Variable Repository](crate::core_contracts::VariableRepositoryContract) InformalStakeReputation
    /// ([available keys](crate::core_contracts::VariableRepositoryContract#available-keys)).
    pub fn informal_stake_reputation(&self) -> bool {
        self.dao_configuration.informal_stake_reputation
    }

    /// Gets the time between informal and formal voting.
    ///
    /// See [Variable Repository](crate::core_contracts::VariableRepositoryContract) TimeBetweenInformalAndFormalVoting
    /// ([available keys](crate::core_contracts::VariableRepositoryContract#available-keys)).
    pub fn time_between_informal_and_formal_voting(&self) -> BlockTime {
        if self.voting_configuration.double_time_between_votings {
            self.dao_configuration
                .time_between_informal_and_formal_voting
                * 2
        } else {
            self.dao_configuration
                .time_between_informal_and_formal_voting
        }
    }

    /// Gets the address of a multisig wallet of the DAO.
    ///
    /// See [Variable Repository](crate::core_contracts::VariableRepositoryContract) BidEscrowWalletAddress
    /// ([available keys](crate::core_contracts::VariableRepositoryContract#available-keys)).
    pub fn bid_escrow_wallet_address(&self) -> Address {
        self.dao_configuration.bid_escrow_wallet_address
    }

    /// Gets the default reputation slash ratio.
    ///
    /// See [Variable Repository](crate::core_contracts::VariableRepositoryContract) DefaultReputationSlash
    /// ([available keys](crate::core_contracts::VariableRepositoryContract#available-keys)).
    pub fn default_reputation_slash(&self) -> Balance {
        self.dao_configuration.default_reputation_slash
    }

    /// Gets the voting clearness delta.
    ///
    /// See [Variable Repository](crate::core_contracts::VariableRepositoryContract) VotingClearnessDelta
    /// ([available keys](crate::core_contracts::VariableRepositoryContract#available-keys)).
    pub fn voting_clearness_delta(&self) -> Balance {
        self.dao_configuration.voting_clearness_delta
    }

    /// Gets the time between voting creation and the actual voting start.
    ///
    /// Non-BidEscrow voting always starts instantly.
    ///
    /// See [Variable Repository](crate::core_contracts::VariableRepositoryContract) VotingClearnessDelta
    /// ([available keys](crate::core_contracts::VariableRepositoryContract#available-keys)).
    pub fn voting_delay(&self) -> BlockTime {
        if self.voting_configuration.is_bid_escrow {
            self.dao_configuration
                .voting_start_after_job_worker_submission
        } else {
            0
        }
    }

    /// Indicates if the attached DOS Fee is too low.
    ///
    /// See [Variable Repository](crate::core_contracts::VariableRepositoryContract) PostJobDOSFee
    /// ([available keys](crate::core_contracts::VariableRepositoryContract#available-keys)).
    pub fn is_post_job_dos_fee_too_low(&self, fiat_value: Balance) -> bool {
        to_per_mils(self.dao_configuration.post_job_dos_fee) > fiat_value
    }

    /// Gets the time of an internal auction.
    pub fn internal_auction_time(&self) -> BlockTime {
        self.dao_configuration.internal_auction_time
    }

    /// Gets the time of a public auction.
    pub fn public_auction_time(&self) -> BlockTime {
        self.dao_configuration.public_auction_time
    }

    /// Gets the bid acceptance timeout.
    ///
    /// See [Variable Repository](crate::core_contracts::VariableRepositoryContract) VABidAcceptanceTimeout
    /// ([available keys](crate::core_contracts::VariableRepositoryContract#available-keys)).
    pub fn va_bid_acceptance_timeout(&self) -> BlockTime {
        self.dao_configuration.va_bid_acceptance_timeout
    }

    /// Indicates if a VA can bid on a public auction.
    ///
    /// See [Variable Repository](crate::core_contracts::VariableRepositoryContract) VACanBidOnPublicAuction
    /// ([available keys](crate::core_contracts::VariableRepositoryContract#available-keys)).
    pub fn va_can_bid_on_public_auction(&self) -> bool {
        self.dao_configuration.va_can_bid_on_public_auction
    }

    /// Indicates if the payment for the job should be distributed between all VA’s or only to those who voted
    ///
    /// See [Variable Repository](crate::core_contracts::VariableRepositoryContract) DistributePaymentToNonVoters.
    /// ([available keys](crate::core_contracts::VariableRepositoryContract#available-keys)).
    pub fn distribute_payment_to_non_voters(&self) -> bool {
        self.dao_configuration.distribute_payment_to_non_voters
    }

    /// Returns the number of onboarded users (VA's).
    pub fn total_onboarded(&self) -> Balance {
        self.total_onboarded
    }

    /// Returns a vec of calls to be performed once voting is finished.
    pub fn contract_calls(&self) -> &Vec<ContractCall> {
        &self.voting_configuration.contract_calls
    }

    /// Indicates only a VA can create voting.
    pub fn only_va_can_create(&self) -> bool {
        self.voting_configuration.only_va_can_create
    }

    /// Indicates if the voting is an instance of BidEscrow.
    pub fn is_bid_escrow(&self) -> bool {
        self.voting_configuration.is_bid_escrow
    }

    /// Returns the address of the voting id generator contract.
    pub fn voting_ids_address(&self) -> Address {
        self.dao_configuration.voting_ids_address
    }

    /// Indicates if the stake of the voting creator should be converted to a ballot.
    pub fn should_cast_first_vote(&self) -> bool {
        !self.is_bid_escrow()
    }

    /// Applies the value of `DefaultPolicingRate` variable to a given amount.
    pub fn apply_default_policing_rate_to(&self, amount: Balance) -> Balance {
        per_mil_of(amount, self.dao_configuration.default_policing_rate)
            .unwrap_or_revert_with(Error::ArithmeticOverflow)
    }

    /// Applies the value of `BidEscrowPaymentRatio` variable to a given amount.
    pub fn apply_bid_escrow_payment_ratio_to(&self, amount: Balance) -> Balance {
        per_mil_of(amount, self.dao_configuration.bid_escrow_payment_ratio)
            .unwrap_or_revert_with(Error::ArithmeticOverflow)
    }

    /// Applies the value of `ReputationConversionRate` variable to a given amount.
    pub fn apply_reputation_conversion_rate_to(&self, amount: Balance) -> Balance {
        per_mil_of(amount, self.dao_configuration.reputation_conversion_rate)
            .unwrap_or_revert_with(Error::ArithmeticOverflow)
    }

    /// Applies the value of `DefaultReputationSlash` variable to a given amount.
    pub fn apply_default_reputation_slash_to(&self, amount: Balance) -> Balance {
        per_mil_of(amount, self.dao_configuration.default_reputation_slash)
            .unwrap_or_revert_with(Error::ArithmeticOverflow)
    }

    /// Gets the current CSPR:Fiat rate.
    pub fn fiat_rate(&self) -> Option<Balance> {
        self.fiat_rate
    }

    /// Calculates the value CSPRs in Fiat currency.
    pub fn convert_to_fiat(&self, cspr_amount: Balance) -> Result<Balance, Error> {
        if let Some(fiat_rate) = self.fiat_rate {
            if let Some(fiat_amount) = cspr_amount.checked_div(fiat_rate) {
                Ok(fiat_amount)
            } else {
                Err(Error::ArithmeticOverflow)
            }
        } else {
            Err(Error::FiatRateNotSet)
        }
    }

    pub fn cancel_finished_voting_timeout(&self) -> BlockTime {
        self.dao_configuration.cancel_finished_voting_timeout
    }
}

pub fn get_variable<T: OdraType>(key: &str, variables: &BTreeMap<String, Bytes>) -> T {
    let variable = variables.get(key);
    let bytes = match variable {
        None => revert(Error::ValueNotAvailable),
        Some(bytes) => bytes,
    };

    let result = <T>::deserialize(bytes.as_slice()).unwrap_or_else(|| {
        revert(Error::BytesDeserializationError);
    });

    result
}