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
//! Repository module.
use crate::modules::repository::events::ValueUpdated;
use crate::utils::consts;
use crate::utils::Error::{ActivationTimeInPast, KeyValueStorageError};
use odra::contract_env::{get_block_time, revert};
use odra::prelude::{
    string::{String, ToString},
    vec,
    vec::Vec,
};
use odra::types::event::OdraEvent;
use odra::types::{Address, Balance, Bytes, OdraType as OdraTyped};
use odra::{List, Mapping, OdraType, UnwrapOrRevert};

/// A data struct stored in the repository.
///
/// The first value represents the current value.
///
/// The second value is an optional tuple consisting of the future value and its activation time.
#[derive(OdraType)]
pub struct Record {
    pub current_value: Bytes,
    pub next_value: Option<(Bytes, u64)>,
}

/// A module that stores the DAO configuration.
///
/// The modules stores key-value pairs and a set of keys.
/// The repository is initialized with the default values.
#[odra::module(events = [ValueUpdated])]
pub struct Repository {
    pub storage: Mapping<String, Record>,
    pub all_keys: List<String>,
}

#[odra::module]
impl Repository {
    #[odra(init)]
    pub fn init(
        &mut self,
        fiat_conversion: Address,
        bid_escrow_wallet: Address,
        voting_ids: Address,
    ) {
        let mut config = RepositoryDefaults::default();
        config.push(consts::FIAT_CONVERSION_RATE_ADDRESS, fiat_conversion);
        config.push(consts::BID_ESCROW_WALLET_ADDRESS, bid_escrow_wallet);
        config.push(consts::VOTING_IDS_ADDRESS, voting_ids);
        for (key, value) in config.items() {
            self.set(key, value);
        }
    }

    pub fn update_at(&mut self, key: String, value: Bytes, activation_time: Option<u64>) {
        let now = get_block_time();
        let value_for_event = value.clone();
        let new_value: Record = match activation_time {
            // If no activation_time provided update the record to the value from argument.
            None => Record {
                current_value: value,
                next_value: None,
            },

            // If activation_time is in the past, raise an error.
            Some(activation_time) if activation_time < now => revert(ActivationTimeInPast),

            // If activation time is in future.
            Some(activation_time) => {
                // Load the record.
                let record = self
                    .storage
                    .get(&key)
                    .unwrap_or_revert_with(KeyValueStorageError);
                let current_value = record.current_value;
                let current_next_value = record.next_value;
                match current_next_value {
                    // If current_next_value is not set, update it to the value from arguments.
                    None => Record {
                        current_value,
                        next_value: Some((value, activation_time)),
                    },

                    // If current_next_value is set, but it is in the past, make it a current
                    // value and set next_value to values from arguments.
                    Some((current_next_value, current_activation_time))
                        if current_activation_time < now =>
                    {
                        Record {
                            current_value: current_next_value,
                            next_value: Some((value, activation_time)),
                        }
                    }

                    // If current_next_value is set in future, update it.
                    Some(_) => Record {
                        current_value,
                        next_value: Some((value, activation_time)),
                    },
                }
            }
        };
        self.storage.set(&key, new_value);
        self.all_keys.push(key.clone());
        ValueUpdated {
            key,
            value: value_for_event,
            activation_time,
        }
        .emit();
    }

    pub fn get(&self, key: String) -> Option<Bytes> {
        let record = self.storage.get(&key)?;
        if let Some((value, activation_time)) = record.next_value {
            let now = get_block_time();
            if now > activation_time {
                return Some(value);
            }
        }
        Some(record.current_value)
    }

    pub fn get_full_value(&self, key: String) -> Option<Record> {
        self.storage.get(&key)
    }

    fn set(&mut self, key: String, value: Bytes) {
        self.update_at(key, value, None);
    }
}

struct RepositoryDefaults {
    pub items: Vec<(String, Bytes)>,
}

impl RepositoryDefaults {
    pub fn push<T: OdraTyped>(&mut self, key: &str, value: T) {
        self.items
            .push((key.to_string(), value.serialize().unwrap().into()));
    }

    pub fn items(self) -> Vec<(String, Bytes)> {
        self.items
    }
}

impl Default for RepositoryDefaults {
    fn default() -> Self {
        let mut items = RepositoryDefaults { items: vec![] };
        items.push(consts::POST_JOB_DOS_FEE, Balance::from(10000));
        items.push(consts::INTERNAL_AUCTION_TIME, 604800000u64);
        items.push(consts::PUBLIC_AUCTION_TIME, 864000000u64);
        items.push(consts::DEFAULT_POLICING_RATE, Balance::from(300));
        items.push(consts::REPUTATION_CONVERSION_RATE, Balance::from(100));
        items.push(consts::FORUM_KYC_REQUIRED, true);
        items.push(consts::BID_ESCROW_INFORMAL_QUORUM_RATIO, Balance::from(500));
        items.push(consts::BID_ESCROW_FORMAL_QUORUM_RATIO, Balance::from(500));
        items.push(consts::INFORMAL_QUORUM_RATIO, Balance::from(500));
        items.push(consts::FORMAL_QUORUM_RATIO, Balance::from(500));
        items.push(consts::BID_ESCROW_INFORMAL_VOTING_TIME, 432000000u64);
        items.push(consts::BID_ESCROW_FORMAL_VOTING_TIME, 432000000u64);
        items.push(consts::INFORMAL_VOTING_TIME, 432000000u64);
        items.push(consts::FORMAL_VOTING_TIME, 432000000u64);
        items.push(consts::INFORMAL_STAKE_REPUTATION, true);
        items.push(consts::TIME_BETWEEN_INFORMAL_AND_FORMAL_VOTING, 86400000u64);
        items.push(consts::VA_BID_ACCEPTANCE_TIMEOUT, 172800000u64);
        items.push(consts::VA_CAN_BID_ON_PUBLIC_AUCTION, false);
        items.push(consts::DISTRIBUTE_PAYMENT_TO_NON_VOTERS, true);
        items.push(consts::DEFAULT_REPUTATION_SLASH, Balance::from(100));
        items.push(consts::VOTING_CLEARNESS_DELTA, Balance::from(8));
        items.push(
            consts::VOTING_START_AFTER_JOB_WORKER_SUBMISSION,
            259200000u64,
        );
        items.push(consts::BID_ESCROW_PAYMENT_RATIO, Balance::from(100));
        items.push(consts::CANCEL_FINISHED_VOTING_TIMEOUT, 2592000000u64);
        items
    }
}

pub mod events {
    use odra::prelude::string::String;
    use odra::types::Bytes;
    use odra::Event;

    /// Event emitted when the repository value has been changed.
    #[derive(Event, PartialEq, Eq, Debug)]
    pub struct ValueUpdated {
        pub key: String,
        pub value: Bytes,
        pub activation_time: Option<u64>,
    }
}