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 (AddressPair(_, to), di) in delegations.iter::<AddressPair, types::DelegationInfo>() {
135 let total = by_destination.entry(to).or_default();
136 *total = total.checked_add(di.shares).ok_or(Error::InvalidArgument)?;
137 }
138
139 Ok(by_destination)
140 })
141}
142
143pub fn get_all_delegations() -> Result<Vec<types::CompleteDelegationInfo>, Error> {
145 CurrentState::with_store(|store| {
146 let store = storage::PrefixStore::new(store, &MODULE_NAME);
147 let delegations = storage::TypedStore::new(storage::PrefixStore::new(store, &DELEGATIONS));
148
149 Ok(delegations
150 .iter::<AddressPair, types::DelegationInfo>()
151 .map(
152 |(AddressPair(from, to), di)| types::CompleteDelegationInfo {
153 from,
154 to,
155 shares: di.shares,
156 },
157 )
158 .collect())
159 })
160}
161
162pub fn add_undelegation(
170 from: Address,
171 to: Address,
172 epoch: EpochTime,
173 shares: u128,
174 receipt: u64,
175) -> Result<u64, Error> {
176 CurrentState::with_store(|mut root_store| {
177 let store = storage::PrefixStore::new(&mut root_store, &MODULE_NAME);
178 let undelegations = storage::PrefixStore::new(store, &UNDELEGATIONS);
179 let account = storage::PrefixStore::new(undelegations, &to);
180 let mut entry = storage::TypedStore::new(storage::PrefixStore::new(account, &from));
181 let mut di: types::DelegationInfo = entry.get(epoch.to_storage_key()).unwrap_or_default();
182
183 if receipt > 0 && di.receipt == 0 {
184 di.receipt = receipt;
185 }
186 let done_receipt = di.receipt;
187
188 di.shares = di
189 .shares
190 .checked_add(shares)
191 .ok_or(Error::InvalidArgument)?;
192
193 entry.insert(epoch.to_storage_key(), di);
194
195 let store = storage::PrefixStore::new(root_store, &MODULE_NAME);
197 let mut queue = storage::PrefixStore::new(store, &UNDELEGATION_QUEUE);
198 queue.insert(
199 &queue_entry_key(from, to, epoch),
200 &[0xF6], );
202
203 Ok(done_receipt)
204 })
205}
206
207fn queue_entry_key(from: Address, to: Address, epoch: EpochTime) -> Vec<u8> {
208 [&epoch.to_storage_key(), to.as_ref(), from.as_ref()].concat()
209}
210
211pub fn take_undelegation(ud: &Undelegation) -> Result<types::DelegationInfo, Error> {
215 CurrentState::with_store(|mut root_store| {
216 let store = storage::PrefixStore::new(&mut root_store, &MODULE_NAME);
218 let undelegations = storage::PrefixStore::new(store, &UNDELEGATIONS);
219 let account = storage::PrefixStore::new(undelegations, &ud.to);
220 let mut entry = storage::TypedStore::new(storage::PrefixStore::new(account, &ud.from));
221 let di: types::DelegationInfo = entry.get(ud.epoch.to_storage_key()).unwrap_or_default();
222 entry.remove(ud.epoch.to_storage_key());
223
224 let store = storage::PrefixStore::new(root_store, &MODULE_NAME);
226 let mut queue = storage::PrefixStore::new(store, &UNDELEGATION_QUEUE);
227 queue.remove(&queue_entry_key(ud.from, ud.to, ud.epoch));
228
229 Ok(di)
230 })
231}
232
233struct AddressWithEpoch {
234 from: Address,
235 epoch: EpochTime,
236}
237
238impl TryFrom<&[u8]> for AddressWithEpoch {
239 type Error = anyhow::Error;
240
241 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
242 if value.len() != Address::SIZE + 8 {
243 anyhow::bail!("incorrect address with epoch key size");
244 }
245
246 Ok(Self {
247 from: Address::try_from(&value[..Address::SIZE])?,
248 epoch: EpochTime::from_be_bytes(value[Address::SIZE..].try_into()?),
249 })
250 }
251}
252
253pub fn get_undelegations(to: Address) -> Result<Vec<types::UndelegationInfo>, Error> {
255 CurrentState::with_store(|store| {
256 let store = storage::PrefixStore::new(store, &MODULE_NAME);
257 let undelegations = storage::PrefixStore::new(store, &UNDELEGATIONS);
258 let account = storage::TypedStore::new(storage::PrefixStore::new(undelegations, &to));
259
260 Ok(account
261 .iter()
262 .map(
263 |(ae, di): (AddressWithEpoch, types::DelegationInfo)| -> types::UndelegationInfo {
264 types::UndelegationInfo {
265 from: ae.from,
266 epoch: ae.epoch,
267 shares: di.shares,
268 }
269 },
270 )
271 .collect())
272 })
273}
274
275#[derive(Clone, PartialEq, PartialOrd, Eq, Ord)]
277struct AddressPairWithEpoch(AddressPair, EpochTime);
278
279impl TryFrom<&[u8]> for AddressPairWithEpoch {
280 type Error = anyhow::Error;
281
282 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
283 if bytes.len() != (2 * Address::SIZE) + 8 {
284 anyhow::bail!("incorrect address pair with epoch key size");
285 }
286 let a = Address::try_from(&bytes[..Address::SIZE])
287 .map_err(|_| anyhow::anyhow!("malformed address"))?;
288 let b = Address::try_from(&bytes[Address::SIZE..bytes.len() - 8])
289 .map_err(|_| anyhow::anyhow!("malformed address"))?;
290 let epoch = EpochTime::from_be_bytes(bytes[bytes.len() - 8..].try_into()?);
291 Ok(AddressPairWithEpoch(AddressPair(a, b), epoch))
292 }
293}
294
295pub fn get_all_undelegations() -> Result<Vec<types::CompleteUndelegationInfo>, Error> {
297 CurrentState::with_store(|store| {
298 let store = storage::PrefixStore::new(store, &MODULE_NAME);
299 let undelegations =
300 storage::TypedStore::new(storage::PrefixStore::new(store, &UNDELEGATIONS));
301
302 Ok(undelegations
303 .iter::<AddressPairWithEpoch, types::DelegationInfo>()
304 .map(|(AddressPairWithEpoch(AddressPair(to, from), epoch), di)| {
305 types::CompleteUndelegationInfo {
306 from,
307 to,
308 epoch,
309 shares: di.shares,
310 }
311 })
312 .collect())
313 })
314}
315
316pub struct Undelegation {
318 pub from: Address,
319 pub to: Address,
320 pub epoch: EpochTime,
321}
322
323impl<'a> TryFrom<&'a [u8]> for Undelegation {
324 type Error = anyhow::Error;
325
326 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
327 if value.len() != 2 * Address::SIZE + 8 {
329 anyhow::bail!("incorrect undelegation key size");
330 }
331
332 Ok(Self {
333 epoch: EpochTime::from_be_bytes(value[..8].try_into()?),
334 to: Address::from_bytes(&value[8..8 + Address::SIZE])?,
335 from: Address::from_bytes(&value[8 + Address::SIZE..])?,
336 })
337 }
338}
339
340pub fn get_queued_undelegations(epoch: EpochTime) -> Result<Vec<Undelegation>, Error> {
342 CurrentState::with_store(|store| {
343 let store = storage::PrefixStore::new(store, &MODULE_NAME);
344 let queue = storage::TypedStore::new(storage::PrefixStore::new(store, &UNDELEGATION_QUEUE));
345
346 Ok(queue
347 .iter()
348 .map(|(k, _): (Undelegation, ())| k)
349 .take_while(|ud| ud.epoch <= epoch)
350 .collect())
351 })
352}
353
354pub fn set_receipt(owner: Address, kind: types::ReceiptKind, id: u64, receipt: types::Receipt) {
356 CurrentState::with_store(|store| {
357 let store = storage::PrefixStore::new(store, &MODULE_NAME);
358 let receipts = storage::PrefixStore::new(store, &RECEIPTS);
359 let of_owner = storage::PrefixStore::new(receipts, &owner);
360 let kind = [kind as u8];
361 let mut of_kind = storage::TypedStore::new(storage::PrefixStore::new(of_owner, &kind));
362
363 of_kind.insert(id.to_be_bytes(), receipt);
364 });
365}
366
367pub fn take_receipt(owner: Address, kind: types::ReceiptKind, id: u64) -> Option<types::Receipt> {
369 CurrentState::with_store(|store| {
370 let store = storage::PrefixStore::new(store, &MODULE_NAME);
371 let receipts = storage::PrefixStore::new(store, &RECEIPTS);
372 let of_owner = storage::PrefixStore::new(receipts, &owner);
373 let kind = [kind as u8];
374 let mut of_kind = storage::TypedStore::new(storage::PrefixStore::new(of_owner, &kind));
375
376 let receipt = of_kind.get(id.to_be_bytes());
377 of_kind.remove(id.to_be_bytes());
378
379 receipt
380 })
381}
382
383trait ToStorageKey {
385 fn to_storage_key(&self) -> [u8; 8];
386}
387
388impl ToStorageKey for EpochTime {
389 fn to_storage_key(&self) -> [u8; 8] {
390 self.to_be_bytes()
391 }
392}
393
394#[cfg(test)]
395mod test {
396 use super::*;
397 use crate::testing::{keys, mock};
398
399 #[test]
400 fn test_delegation() {
401 let _mock = mock::Mock::default();
402
403 add_delegation(keys::alice::address(), keys::bob::address(), 500).unwrap();
404 add_delegation(keys::alice::address(), keys::bob::address(), 500).unwrap();
405
406 let di = get_delegation(keys::bob::address(), keys::alice::address()).unwrap();
407 assert_eq!(di.shares, 0);
408 let di = get_delegation(keys::alice::address(), keys::bob::address()).unwrap();
409 assert_eq!(di.shares, 1000);
410
411 let dis = get_delegations(keys::bob::address()).unwrap();
412 assert!(dis.is_empty());
413 let dis = get_delegations(keys::alice::address()).unwrap();
414 assert_eq!(dis.len(), 1);
415 assert_eq!(dis[0].shares, 1000);
416
417 let totals = get_delegations_by_destination().unwrap();
418 assert_eq!(totals.len(), 1);
419 assert_eq!(totals[&keys::bob::address()], 1000);
420
421 sub_delegation(keys::alice::address(), keys::bob::address(), 100).unwrap();
422
423 let di = get_delegation(keys::alice::address(), keys::bob::address()).unwrap();
424 assert_eq!(di.shares, 900);
425
426 let totals = get_delegations_by_destination().unwrap();
427 assert_eq!(totals.len(), 1);
428 assert_eq!(totals[&keys::bob::address()], 900);
429
430 add_delegation(keys::bob::address(), keys::bob::address(), 200).unwrap();
431
432 let totals = get_delegations_by_destination().unwrap();
433 assert_eq!(totals.len(), 1);
434 assert_eq!(totals[&keys::bob::address()], 1100);
435
436 add_delegation(keys::bob::address(), keys::alice::address(), 100).unwrap();
437
438 let totals = get_delegations_by_destination().unwrap();
439 assert_eq!(totals.len(), 2);
440 assert_eq!(totals[&keys::alice::address()], 100);
441 assert_eq!(totals[&keys::bob::address()], 1100);
442 }
443
444 #[test]
445 fn test_undelegation() {
446 let _mock = mock::Mock::default();
447
448 add_undelegation(keys::alice::address(), keys::bob::address(), 42, 500, 12).unwrap();
449 add_undelegation(keys::alice::address(), keys::bob::address(), 42, 500, 24).unwrap();
450 add_undelegation(keys::alice::address(), keys::bob::address(), 84, 200, 36).unwrap();
451
452 let qd = get_queued_undelegations(10).unwrap();
453 assert!(qd.is_empty());
454 let qd = get_queued_undelegations(42).unwrap();
455 assert_eq!(qd.len(), 1);
456 assert_eq!(qd[0].from, keys::alice::address());
457 assert_eq!(qd[0].to, keys::bob::address());
458 assert_eq!(qd[0].epoch, 42);
459 let qd = get_queued_undelegations(43).unwrap();
460 assert_eq!(qd.len(), 1);
461 assert_eq!(qd[0].from, keys::alice::address());
462 assert_eq!(qd[0].to, keys::bob::address());
463 assert_eq!(qd[0].epoch, 42);
464
465 let udis = get_undelegations(keys::alice::address()).unwrap();
466 assert!(udis.is_empty());
467 let udis = get_undelegations(keys::bob::address()).unwrap();
468 assert_eq!(udis.len(), 2);
469 assert_eq!(udis[0].from, keys::alice::address());
470 assert_eq!(udis[0].shares, 1000);
471 assert_eq!(udis[0].epoch, 42);
472 assert_eq!(udis[1].from, keys::alice::address());
473 assert_eq!(udis[1].shares, 200);
474 assert_eq!(udis[1].epoch, 84);
475
476 let di = take_undelegation(&qd[0]).unwrap();
477 assert_eq!(di.shares, 1000);
478 assert_eq!(di.receipt, 12, "receipt id should not be overwritten");
479
480 let qd = get_queued_undelegations(42).unwrap();
481 assert!(qd.is_empty());
482
483 let udis = get_undelegations(keys::bob::address()).unwrap();
484 assert_eq!(udis.len(), 1);
485 }
486
487 #[test]
488 fn test_receipts() {
489 let _mock = mock::Mock::default();
490
491 let receipt = types::Receipt {
492 shares: 123,
493 ..Default::default()
494 };
495 set_receipt(
496 keys::alice::address(),
497 types::ReceiptKind::Delegate,
498 42,
499 receipt.clone(),
500 );
501
502 let dec_receipt = take_receipt(keys::alice::address(), types::ReceiptKind::Delegate, 10);
503 assert!(dec_receipt.is_none(), "missing receipt should return None");
504
505 let dec_receipt = take_receipt(
506 keys::alice::address(),
507 types::ReceiptKind::UndelegateStart,
508 42,
509 );
510 assert!(dec_receipt.is_none(), "missing receipt should return None");
511
512 let dec_receipt = take_receipt(
513 keys::alice::address(),
514 types::ReceiptKind::UndelegateDone,
515 42,
516 );
517 assert!(dec_receipt.is_none(), "missing receipt should return None");
518
519 let dec_receipt = take_receipt(keys::alice::address(), types::ReceiptKind::Delegate, 42);
520 assert_eq!(dec_receipt, Some(receipt), "receipt should be correct");
521
522 let dec_receipt = take_receipt(keys::alice::address(), types::ReceiptKind::Delegate, 42);
523 assert!(dec_receipt.is_none(), "receipt should have been removed");
524 }
525}