1use std::{
3 collections::BTreeMap,
4 convert::{TryFrom, TryInto},
5};
6
7use oasis_core_runtime::consensus::beacon::EpochTime;
8
9use crate::{
10 state::CurrentState,
11 storage::{self, Store},
12 types::address::Address,
13};
14
15use super::{types, Error, MODULE_NAME};
16
17pub const DELEGATIONS: &[u8] = &[0x01];
19pub const UNDELEGATIONS: &[u8] = &[0x02];
21pub const UNDELEGATION_QUEUE: &[u8] = &[0x03];
23pub const RECEIPTS: &[u8] = &[0x04];
25
26pub fn add_delegation(from: Address, to: Address, shares: u128) -> Result<(), Error> {
31 CurrentState::with_store(|store| {
32 let store = storage::PrefixStore::new(store, &MODULE_NAME);
33 let delegations = storage::PrefixStore::new(store, &DELEGATIONS);
34 let mut account = storage::TypedStore::new(storage::PrefixStore::new(delegations, &from));
35 let mut di: types::DelegationInfo = account.get(to).unwrap_or_default();
36
37 di.shares = di
38 .shares
39 .checked_add(shares)
40 .ok_or(Error::InvalidArgument)?;
41
42 account.insert(to, di);
43
44 Ok(())
45 })
46}
47
48pub fn sub_delegation(from: Address, to: Address, shares: u128) -> Result<(), Error> {
50 CurrentState::with_store(|store| {
51 let store = storage::PrefixStore::new(store, &MODULE_NAME);
52 let delegations = storage::PrefixStore::new(store, &DELEGATIONS);
53 let mut account = storage::TypedStore::new(storage::PrefixStore::new(delegations, &from));
54 let mut di: types::DelegationInfo = account.get(to).unwrap_or_default();
55
56 di.shares = di
57 .shares
58 .checked_sub(shares)
59 .ok_or(Error::InsufficientBalance)?;
60
61 if di.shares > 0 {
62 account.insert(to, di);
63 } else {
64 account.remove(to);
65 }
66
67 Ok(())
68 })
69}
70
71pub fn get_delegation(from: Address, to: Address) -> Result<types::DelegationInfo, Error> {
76 CurrentState::with_store(|store| {
77 let store = storage::PrefixStore::new(store, &MODULE_NAME);
78 let delegations = storage::PrefixStore::new(store, &DELEGATIONS);
79 let account = storage::TypedStore::new(storage::PrefixStore::new(delegations, &from));
80 Ok(account.get(to).unwrap_or_default())
81 })
82}
83
84pub fn get_delegations(from: Address) -> Result<Vec<types::ExtendedDelegationInfo>, Error> {
86 CurrentState::with_store(|store| {
87 let store = storage::PrefixStore::new(store, &MODULE_NAME);
88 let delegations = storage::PrefixStore::new(store, &DELEGATIONS);
89 let account = storage::TypedStore::new(storage::PrefixStore::new(delegations, &from));
90
91 Ok(account
92 .iter()
93 .map(
94 |(to, di): (Address, types::DelegationInfo)| -> types::ExtendedDelegationInfo {
95 types::ExtendedDelegationInfo {
96 to,
97 shares: di.shares,
98 }
99 },
100 )
101 .collect())
102 })
103}
104
105#[derive(Clone, PartialEq, PartialOrd, Eq, Ord)]
107struct AddressPair(Address, Address);
108
109#[derive(Error, Debug)]
110enum APError {
111 #[error("malformed address")]
112 MalformedAddress,
113}
114
115impl TryFrom<&[u8]> for AddressPair {
116 type Error = APError;
117
118 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
119 let a =
120 Address::try_from(&bytes[..Address::SIZE]).map_err(|_| APError::MalformedAddress)?;
121 let b =
122 Address::try_from(&bytes[Address::SIZE..]).map_err(|_| APError::MalformedAddress)?;
123 Ok(AddressPair(a, b))
124 }
125}
126
127pub fn get_delegations_by_destination() -> Result<BTreeMap<Address, u128>, Error> {
129 CurrentState::with_store(|store| {
130 let store = storage::PrefixStore::new(store, &MODULE_NAME);
131 let delegations = storage::TypedStore::new(storage::PrefixStore::new(store, &DELEGATIONS));
132
133 let mut by_destination: BTreeMap<Address, u128> = BTreeMap::new();
134 for (ap, di) in delegations.iter::<AddressPair, types::DelegationInfo>() {
135 let total = by_destination.entry(ap.1).or_default();
136 *total = total.checked_add(di.shares).ok_or(Error::InvalidArgument)?;
137 }
138
139 Ok(by_destination)
140 })
141}
142
143pub fn add_undelegation(
151 from: Address,
152 to: Address,
153 epoch: EpochTime,
154 shares: u128,
155 receipt: u64,
156) -> Result<u64, Error> {
157 CurrentState::with_store(|mut root_store| {
158 let store = storage::PrefixStore::new(&mut root_store, &MODULE_NAME);
159 let undelegations = storage::PrefixStore::new(store, &UNDELEGATIONS);
160 let account = storage::PrefixStore::new(undelegations, &to);
161 let mut entry = storage::TypedStore::new(storage::PrefixStore::new(account, &from));
162 let mut di: types::DelegationInfo = entry.get(epoch.to_storage_key()).unwrap_or_default();
163
164 if receipt > 0 && di.receipt == 0 {
165 di.receipt = receipt;
166 }
167 let done_receipt = di.receipt;
168
169 di.shares = di
170 .shares
171 .checked_add(shares)
172 .ok_or(Error::InvalidArgument)?;
173
174 entry.insert(epoch.to_storage_key(), di);
175
176 let store = storage::PrefixStore::new(root_store, &MODULE_NAME);
178 let mut queue = storage::PrefixStore::new(store, &UNDELEGATION_QUEUE);
179 queue.insert(
180 &queue_entry_key(from, to, epoch),
181 &[0xF6], );
183
184 Ok(done_receipt)
185 })
186}
187
188fn queue_entry_key(from: Address, to: Address, epoch: EpochTime) -> Vec<u8> {
189 [&epoch.to_storage_key(), to.as_ref(), from.as_ref()].concat()
190}
191
192pub fn take_undelegation(ud: &Undelegation) -> Result<types::DelegationInfo, Error> {
196 CurrentState::with_store(|mut root_store| {
197 let store = storage::PrefixStore::new(&mut root_store, &MODULE_NAME);
199 let undelegations = storage::PrefixStore::new(store, &UNDELEGATIONS);
200 let account = storage::PrefixStore::new(undelegations, &ud.to);
201 let mut entry = storage::TypedStore::new(storage::PrefixStore::new(account, &ud.from));
202 let di: types::DelegationInfo = entry.get(ud.epoch.to_storage_key()).unwrap_or_default();
203 entry.remove(ud.epoch.to_storage_key());
204
205 let store = storage::PrefixStore::new(root_store, &MODULE_NAME);
207 let mut queue = storage::PrefixStore::new(store, &UNDELEGATION_QUEUE);
208 queue.remove(&queue_entry_key(ud.from, ud.to, ud.epoch));
209
210 Ok(di)
211 })
212}
213
214struct AddressWithEpoch {
215 from: Address,
216 epoch: EpochTime,
217}
218
219impl TryFrom<&[u8]> for AddressWithEpoch {
220 type Error = anyhow::Error;
221
222 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
223 if value.len() != Address::SIZE + 8 {
224 anyhow::bail!("incorrect address with epoch key size");
225 }
226
227 Ok(Self {
228 from: Address::try_from(&value[..Address::SIZE])?,
229 epoch: EpochTime::from_be_bytes(value[Address::SIZE..].try_into()?),
230 })
231 }
232}
233
234pub fn get_undelegations(to: Address) -> Result<Vec<types::UndelegationInfo>, Error> {
236 CurrentState::with_store(|store| {
237 let store = storage::PrefixStore::new(store, &MODULE_NAME);
238 let undelegations = storage::PrefixStore::new(store, &UNDELEGATIONS);
239 let account = storage::TypedStore::new(storage::PrefixStore::new(undelegations, &to));
240
241 Ok(account
242 .iter()
243 .map(
244 |(ae, di): (AddressWithEpoch, types::DelegationInfo)| -> types::UndelegationInfo {
245 types::UndelegationInfo {
246 from: ae.from,
247 epoch: ae.epoch,
248 shares: di.shares,
249 }
250 },
251 )
252 .collect())
253 })
254}
255
256pub struct Undelegation {
258 pub from: Address,
259 pub to: Address,
260 pub epoch: EpochTime,
261}
262
263impl<'a> TryFrom<&'a [u8]> for Undelegation {
264 type Error = anyhow::Error;
265
266 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
267 if value.len() != 2 * Address::SIZE + 8 {
269 anyhow::bail!("incorrect undelegation key size");
270 }
271
272 Ok(Self {
273 epoch: EpochTime::from_be_bytes(value[..8].try_into()?),
274 to: Address::from_bytes(&value[8..8 + Address::SIZE])?,
275 from: Address::from_bytes(&value[8 + Address::SIZE..])?,
276 })
277 }
278}
279
280pub fn get_queued_undelegations(epoch: EpochTime) -> Result<Vec<Undelegation>, Error> {
282 CurrentState::with_store(|store| {
283 let store = storage::PrefixStore::new(store, &MODULE_NAME);
284 let queue = storage::TypedStore::new(storage::PrefixStore::new(store, &UNDELEGATION_QUEUE));
285
286 Ok(queue
287 .iter()
288 .map(|(k, _): (Undelegation, ())| k)
289 .take_while(|ud| ud.epoch <= epoch)
290 .collect())
291 })
292}
293
294pub fn set_receipt(owner: Address, kind: types::ReceiptKind, id: u64, receipt: types::Receipt) {
296 CurrentState::with_store(|store| {
297 let store = storage::PrefixStore::new(store, &MODULE_NAME);
298 let receipts = storage::PrefixStore::new(store, &RECEIPTS);
299 let of_owner = storage::PrefixStore::new(receipts, &owner);
300 let kind = [kind as u8];
301 let mut of_kind = storage::TypedStore::new(storage::PrefixStore::new(of_owner, &kind));
302
303 of_kind.insert(id.to_be_bytes(), receipt);
304 });
305}
306
307pub fn take_receipt(owner: Address, kind: types::ReceiptKind, id: u64) -> Option<types::Receipt> {
309 CurrentState::with_store(|store| {
310 let store = storage::PrefixStore::new(store, &MODULE_NAME);
311 let receipts = storage::PrefixStore::new(store, &RECEIPTS);
312 let of_owner = storage::PrefixStore::new(receipts, &owner);
313 let kind = [kind as u8];
314 let mut of_kind = storage::TypedStore::new(storage::PrefixStore::new(of_owner, &kind));
315
316 let receipt = of_kind.get(id.to_be_bytes());
317 of_kind.remove(id.to_be_bytes());
318
319 receipt
320 })
321}
322
323trait ToStorageKey {
325 fn to_storage_key(&self) -> [u8; 8];
326}
327
328impl ToStorageKey for EpochTime {
329 fn to_storage_key(&self) -> [u8; 8] {
330 self.to_be_bytes()
331 }
332}
333
334#[cfg(test)]
335mod test {
336 use super::*;
337 use crate::testing::{keys, mock};
338
339 #[test]
340 fn test_delegation() {
341 let _mock = mock::Mock::default();
342
343 add_delegation(keys::alice::address(), keys::bob::address(), 500).unwrap();
344 add_delegation(keys::alice::address(), keys::bob::address(), 500).unwrap();
345
346 let di = get_delegation(keys::bob::address(), keys::alice::address()).unwrap();
347 assert_eq!(di.shares, 0);
348 let di = get_delegation(keys::alice::address(), keys::bob::address()).unwrap();
349 assert_eq!(di.shares, 1000);
350
351 let dis = get_delegations(keys::bob::address()).unwrap();
352 assert!(dis.is_empty());
353 let dis = get_delegations(keys::alice::address()).unwrap();
354 assert_eq!(dis.len(), 1);
355 assert_eq!(dis[0].shares, 1000);
356
357 let totals = get_delegations_by_destination().unwrap();
358 assert_eq!(totals.len(), 1);
359 assert_eq!(totals[&keys::bob::address()], 1000);
360
361 sub_delegation(keys::alice::address(), keys::bob::address(), 100).unwrap();
362
363 let di = get_delegation(keys::alice::address(), keys::bob::address()).unwrap();
364 assert_eq!(di.shares, 900);
365
366 let totals = get_delegations_by_destination().unwrap();
367 assert_eq!(totals.len(), 1);
368 assert_eq!(totals[&keys::bob::address()], 900);
369
370 add_delegation(keys::bob::address(), keys::bob::address(), 200).unwrap();
371
372 let totals = get_delegations_by_destination().unwrap();
373 assert_eq!(totals.len(), 1);
374 assert_eq!(totals[&keys::bob::address()], 1100);
375
376 add_delegation(keys::bob::address(), keys::alice::address(), 100).unwrap();
377
378 let totals = get_delegations_by_destination().unwrap();
379 assert_eq!(totals.len(), 2);
380 assert_eq!(totals[&keys::alice::address()], 100);
381 assert_eq!(totals[&keys::bob::address()], 1100);
382 }
383
384 #[test]
385 fn test_undelegation() {
386 let _mock = mock::Mock::default();
387
388 add_undelegation(keys::alice::address(), keys::bob::address(), 42, 500, 12).unwrap();
389 add_undelegation(keys::alice::address(), keys::bob::address(), 42, 500, 24).unwrap();
390 add_undelegation(keys::alice::address(), keys::bob::address(), 84, 200, 36).unwrap();
391
392 let qd = get_queued_undelegations(10).unwrap();
393 assert!(qd.is_empty());
394 let qd = get_queued_undelegations(42).unwrap();
395 assert_eq!(qd.len(), 1);
396 assert_eq!(qd[0].from, keys::alice::address());
397 assert_eq!(qd[0].to, keys::bob::address());
398 assert_eq!(qd[0].epoch, 42);
399 let qd = get_queued_undelegations(43).unwrap();
400 assert_eq!(qd.len(), 1);
401 assert_eq!(qd[0].from, keys::alice::address());
402 assert_eq!(qd[0].to, keys::bob::address());
403 assert_eq!(qd[0].epoch, 42);
404
405 let udis = get_undelegations(keys::alice::address()).unwrap();
406 assert!(udis.is_empty());
407 let udis = get_undelegations(keys::bob::address()).unwrap();
408 assert_eq!(udis.len(), 2);
409 assert_eq!(udis[0].from, keys::alice::address());
410 assert_eq!(udis[0].shares, 1000);
411 assert_eq!(udis[0].epoch, 42);
412 assert_eq!(udis[1].from, keys::alice::address());
413 assert_eq!(udis[1].shares, 200);
414 assert_eq!(udis[1].epoch, 84);
415
416 let di = take_undelegation(&qd[0]).unwrap();
417 assert_eq!(di.shares, 1000);
418 assert_eq!(di.receipt, 12, "receipt id should not be overwritten");
419
420 let qd = get_queued_undelegations(42).unwrap();
421 assert!(qd.is_empty());
422
423 let udis = get_undelegations(keys::bob::address()).unwrap();
424 assert_eq!(udis.len(), 1);
425 }
426
427 #[test]
428 fn test_receipts() {
429 let _mock = mock::Mock::default();
430
431 let receipt = types::Receipt {
432 shares: 123,
433 ..Default::default()
434 };
435 set_receipt(
436 keys::alice::address(),
437 types::ReceiptKind::Delegate,
438 42,
439 receipt.clone(),
440 );
441
442 let dec_receipt = take_receipt(keys::alice::address(), types::ReceiptKind::Delegate, 10);
443 assert!(dec_receipt.is_none(), "missing receipt should return None");
444
445 let dec_receipt = take_receipt(
446 keys::alice::address(),
447 types::ReceiptKind::UndelegateStart,
448 42,
449 );
450 assert!(dec_receipt.is_none(), "missing receipt should return None");
451
452 let dec_receipt = take_receipt(
453 keys::alice::address(),
454 types::ReceiptKind::UndelegateDone,
455 42,
456 );
457 assert!(dec_receipt.is_none(), "missing receipt should return None");
458
459 let dec_receipt = take_receipt(keys::alice::address(), types::ReceiptKind::Delegate, 42);
460 assert_eq!(dec_receipt, Some(receipt), "receipt should be correct");
461
462 let dec_receipt = take_receipt(keys::alice::address(), types::ReceiptKind::Delegate, 42);
463 assert!(dec_receipt.is_none(), "receipt should have been removed");
464 }
465}