lib.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. // Clippy linter warning. TODO: remove after the Constaninople release
  2. #![allow(clippy::type_complexity)]
  3. // disable it because of possible frontend API break
  4. // Clippy linter warning. TODO: refactor the Option<Option<>>
  5. #![allow(clippy::option_option)] // disable it because of possible API break
  6. // Ensure we're `no_std` when compiling for Wasm.
  7. #![cfg_attr(not(feature = "std"), no_std)]
  8. use rstd::prelude::*;
  9. use codec::{Codec, Decode, Encode};
  10. use runtime_primitives::traits::{MaybeSerialize, Member, One, SimpleArithmetic, Zero};
  11. use srml_support::{decl_module, decl_storage, ensure, Parameter};
  12. use minting::{self, BalanceOf};
  13. mod mock;
  14. mod tests;
  15. pub trait Trait: system::Trait + minting::Trait {
  16. type PayoutStatusHandler: PayoutStatusHandler<Self>;
  17. /// Type of identifier for recipients.
  18. type RecipientId: Parameter
  19. + Member
  20. + SimpleArithmetic
  21. + Codec
  22. + Default
  23. + Copy
  24. + MaybeSerialize
  25. + PartialEq;
  26. /// Type for identifier for relationship representing that a recipient recieves recurring reward from a token mint
  27. type RewardRelationshipId: Parameter
  28. + Member
  29. + SimpleArithmetic
  30. + Codec
  31. + Default
  32. + Copy
  33. + MaybeSerialize
  34. + PartialEq;
  35. }
  36. /// Handler for aftermath of a payout attempt
  37. pub trait PayoutStatusHandler<T: Trait> {
  38. fn payout_succeeded(
  39. id: T::RewardRelationshipId,
  40. destination_account: &T::AccountId,
  41. amount: BalanceOf<T>,
  42. );
  43. fn payout_failed(
  44. id: T::RewardRelationshipId,
  45. destination_account: &T::AccountId,
  46. amount: BalanceOf<T>,
  47. );
  48. }
  49. /// Makes `()` empty tuple, a PayoutStatusHandler that does nothing.
  50. impl<T: Trait> PayoutStatusHandler<T> for () {
  51. fn payout_succeeded(
  52. _id: T::RewardRelationshipId,
  53. _destination_account: &T::AccountId,
  54. _amount: BalanceOf<T>,
  55. ) {
  56. }
  57. fn payout_failed(
  58. _id: T::RewardRelationshipId,
  59. _destination_account: &T::AccountId,
  60. _amount: BalanceOf<T>,
  61. ) {
  62. }
  63. }
  64. /// A recipient of recurring rewards
  65. #[derive(Encode, Decode, Copy, Clone, Debug, Default)]
  66. pub struct Recipient<Balance> {
  67. // stats
  68. /// Total payout received by this recipient
  69. total_reward_received: Balance,
  70. /// Total payout missed for this recipient
  71. total_reward_missed: Balance,
  72. }
  73. #[derive(Encode, Decode, Copy, Clone, Debug, Default)]
  74. pub struct RewardRelationship<AccountId, Balance, BlockNumber, MintId, RecipientId> {
  75. /// Identifier for receiver
  76. recipient: RecipientId,
  77. /// Identifier for reward source
  78. mint_id: MintId,
  79. /// Destination account for reward
  80. pub account: AccountId,
  81. /// The payout amount at the next payout
  82. pub amount_per_payout: Balance,
  83. /// When set, identifies block when next payout should be processed,
  84. /// otherwise there is no pending payout
  85. next_payment_at_block: Option<BlockNumber>,
  86. /// When set, will be the basis for automatically setting next payment,
  87. /// otherwise any upcoming payout will be a one off.
  88. payout_interval: Option<BlockNumber>,
  89. // stats
  90. /// Total payout received in this relationship
  91. total_reward_received: Balance,
  92. /// Total payout failed in this relationship
  93. total_reward_missed: Balance,
  94. }
  95. impl<AccountId: Clone, Balance: Clone, BlockNumber: Clone, MintId: Clone, RecipientId: Clone>
  96. RewardRelationship<AccountId, Balance, BlockNumber, MintId, RecipientId>
  97. {
  98. /// Verifies whether relationship is active
  99. pub fn is_active(&self) -> bool {
  100. self.next_payment_at_block.is_some()
  101. }
  102. /// Make clone which is activated.
  103. pub fn clone_activated(&self, start_at: &BlockNumber) -> Self {
  104. Self {
  105. next_payment_at_block: Some((*start_at).clone()),
  106. ..((*self).clone())
  107. }
  108. }
  109. /// Make clone which is deactivated
  110. pub fn clone_deactivated(&self) -> Self {
  111. Self {
  112. next_payment_at_block: None,
  113. ..((*self).clone())
  114. }
  115. }
  116. }
  117. decl_storage! {
  118. trait Store for Module<T: Trait> as RecurringReward {
  119. Recipients get(recipients): linked_map T::RecipientId => Recipient<BalanceOf<T>>;
  120. RecipientsCreated get(recipients_created): T::RecipientId;
  121. pub RewardRelationships get(reward_relationships): linked_map T::RewardRelationshipId => RewardRelationship<T::AccountId, BalanceOf<T>, T::BlockNumber, T::MintId, T::RecipientId>;
  122. RewardRelationshipsCreated get(reward_relationships_created): T::RewardRelationshipId;
  123. }
  124. }
  125. decl_module! {
  126. pub struct Module<T: Trait> for enum Call where origin: T::Origin {
  127. fn on_finalize(now: T::BlockNumber) {
  128. Self::do_payouts(now);
  129. }
  130. }
  131. }
  132. #[derive(Eq, PartialEq, Debug)]
  133. pub enum RewardsError {
  134. RecipientNotFound,
  135. RewardSourceNotFound,
  136. NextPaymentNotInFuture,
  137. RewardRelationshipNotFound,
  138. }
  139. impl<T: Trait> Module<T> {
  140. /// Adds a new Recipient and returns new recipient identifier.
  141. pub fn add_recipient() -> T::RecipientId {
  142. let next_id = Self::recipients_created();
  143. <RecipientsCreated<T>>::put(next_id + One::one());
  144. <Recipients<T>>::insert(&next_id, Recipient::default());
  145. next_id
  146. }
  147. /// Adds a new RewardRelationship, for a given source mint, recipient, account.
  148. pub fn add_reward_relationship(
  149. mint_id: T::MintId,
  150. recipient: T::RecipientId,
  151. account: T::AccountId,
  152. amount_per_payout: BalanceOf<T>,
  153. next_payment_at_block: T::BlockNumber,
  154. payout_interval: Option<T::BlockNumber>,
  155. ) -> Result<T::RewardRelationshipId, RewardsError> {
  156. ensure!(
  157. <minting::Module<T>>::mint_exists(mint_id),
  158. RewardsError::RewardSourceNotFound
  159. );
  160. ensure!(
  161. <Recipients<T>>::exists(recipient),
  162. RewardsError::RecipientNotFound
  163. );
  164. ensure!(
  165. next_payment_at_block > <system::Module<T>>::block_number(),
  166. RewardsError::NextPaymentNotInFuture
  167. );
  168. let relationship_id = Self::reward_relationships_created();
  169. <RewardRelationshipsCreated<T>>::put(relationship_id + One::one());
  170. <RewardRelationships<T>>::insert(
  171. relationship_id,
  172. RewardRelationship {
  173. mint_id,
  174. recipient,
  175. account,
  176. amount_per_payout,
  177. next_payment_at_block: Some(next_payment_at_block),
  178. payout_interval,
  179. total_reward_received: Zero::zero(),
  180. total_reward_missed: Zero::zero(),
  181. },
  182. );
  183. Ok(relationship_id)
  184. }
  185. /// Removes a relationship from RewardRelashionships and its recipient.
  186. pub fn remove_reward_relationship(id: T::RewardRelationshipId) {
  187. if <RewardRelationships<T>>::exists(&id) {
  188. <Recipients<T>>::remove(<RewardRelationships<T>>::take(&id).recipient);
  189. }
  190. }
  191. /// Will attempt to activat a deactivated reward relationship.
  192. pub fn try_to_activate_relationship(
  193. id: T::RewardRelationshipId,
  194. next_payment_at_block: T::BlockNumber,
  195. ) -> Result<bool, ()> {
  196. // Ensure relationship exists
  197. let reward_relationship = Self::ensure_reward_relationship_exists(&id)?;
  198. let activated = if reward_relationship.is_active() {
  199. // Was not activated
  200. false
  201. } else {
  202. // Update as activated
  203. let activated_relationship =
  204. reward_relationship.clone_activated(&next_payment_at_block);
  205. RewardRelationships::<T>::insert(id, activated_relationship);
  206. // We activated
  207. true
  208. };
  209. Ok(activated)
  210. }
  211. /// Will attempt to deactivat a activated reward relationship.
  212. pub fn try_to_deactivate_relationship(id: T::RewardRelationshipId) -> Result<bool, ()> {
  213. // Ensure relationship exists
  214. let reward_relationship = Self::ensure_reward_relationship_exists(&id)?;
  215. let deactivated = if reward_relationship.is_active() {
  216. let deactivated_relationship = reward_relationship.clone_deactivated();
  217. RewardRelationships::<T>::insert(id, deactivated_relationship);
  218. // Was deactivated
  219. true
  220. } else {
  221. // Was not deactivated
  222. false
  223. };
  224. Ok(deactivated)
  225. }
  226. // For reward relationship found with given identifier, new values can be set for
  227. // account, payout, block number when next payout will be made and the new interval after
  228. // the next scheduled payout. All values are optional, but updating values are combined in this
  229. // single method to ensure atomic updates.
  230. pub fn set_reward_relationship(
  231. id: T::RewardRelationshipId,
  232. new_account: Option<T::AccountId>,
  233. new_payout: Option<BalanceOf<T>>,
  234. new_next_payment_at: Option<Option<T::BlockNumber>>,
  235. new_payout_interval: Option<Option<T::BlockNumber>>,
  236. ) -> Result<(), RewardsError> {
  237. ensure!(
  238. <RewardRelationships<T>>::exists(&id),
  239. RewardsError::RewardRelationshipNotFound
  240. );
  241. let mut relationship = Self::reward_relationships(&id);
  242. if let Some(account) = new_account {
  243. relationship.account = account;
  244. }
  245. if let Some(payout) = new_payout {
  246. relationship.amount_per_payout = payout;
  247. }
  248. if let Some(next_payout_at_block) = new_next_payment_at {
  249. if let Some(blocknumber) = next_payout_at_block {
  250. ensure!(
  251. blocknumber > <system::Module<T>>::block_number(),
  252. RewardsError::NextPaymentNotInFuture
  253. );
  254. }
  255. relationship.next_payment_at_block = next_payout_at_block;
  256. }
  257. if let Some(payout_interval) = new_payout_interval {
  258. relationship.payout_interval = payout_interval;
  259. }
  260. <RewardRelationships<T>>::insert(&id, relationship);
  261. Ok(())
  262. }
  263. /*
  264. For all relationships where next_payment_at_block is set and matches current block height,
  265. a call to pay_reward is made for the suitable amount, recipient and source.
  266. The next_payment_in_block is updated based on payout_interval.
  267. If the call succeeds, total_reward_received is incremented on both
  268. recipient and dependency with amount_per_payout, and a call to T::PayoutStatusHandler is made.
  269. Otherwise, analogous steps for failure.
  270. */
  271. fn do_payouts(now: T::BlockNumber) {
  272. for (relationship_id, ref mut relationship) in <RewardRelationships<T>>::enumerate() {
  273. assert!(<Recipients<T>>::exists(&relationship.recipient));
  274. let mut recipient = Self::recipients(relationship.recipient);
  275. if let Some(next_payment_at_block) = relationship.next_payment_at_block {
  276. if next_payment_at_block != now {
  277. continue;
  278. }
  279. // Add the missed payout and try to pay those in addition to scheduled payout?
  280. // let payout = relationship.total_reward_missed + relationship.amount_per_payout;
  281. let payout = relationship.amount_per_payout;
  282. // try to make payment
  283. if <minting::Module<T>>::transfer_tokens(
  284. relationship.mint_id,
  285. payout,
  286. &relationship.account,
  287. )
  288. .is_err()
  289. {
  290. // add only newly scheduled payout to total missed payout
  291. relationship.total_reward_missed += relationship.amount_per_payout;
  292. // update recipient stats
  293. recipient.total_reward_missed += relationship.amount_per_payout;
  294. T::PayoutStatusHandler::payout_failed(
  295. relationship_id,
  296. &relationship.account,
  297. payout,
  298. );
  299. } else {
  300. // update payout received stats
  301. relationship.total_reward_received += payout;
  302. recipient.total_reward_received += payout;
  303. // update missed payout stats
  304. // if relationship.total_reward_missed != Zero::zero() {
  305. // // update recipient stats
  306. // recipient.total_reward_missed -= relationship.total_reward_missed;
  307. // // clear missed reward on relationship
  308. // relationship.total_reward_missed = Zero::zero();
  309. // }
  310. T::PayoutStatusHandler::payout_succeeded(
  311. relationship_id,
  312. &relationship.account,
  313. payout,
  314. );
  315. }
  316. // update next payout blocknumber at interval if set
  317. if let Some(payout_interval) = relationship.payout_interval {
  318. relationship.next_payment_at_block = Some(now + payout_interval);
  319. } else {
  320. relationship.next_payment_at_block = None;
  321. }
  322. <Recipients<T>>::insert(relationship.recipient, recipient);
  323. <RewardRelationships<T>>::insert(relationship_id, relationship);
  324. }
  325. }
  326. }
  327. }
  328. impl<T: Trait> Module<T> {
  329. fn ensure_reward_relationship_exists(
  330. id: &T::RewardRelationshipId,
  331. ) -> Result<
  332. RewardRelationship<T::AccountId, BalanceOf<T>, T::BlockNumber, T::MintId, T::RecipientId>,
  333. (),
  334. > {
  335. ensure!(RewardRelationships::<T>::exists(id), ());
  336. let relationship = RewardRelationships::<T>::get(id);
  337. Ok(relationship)
  338. }
  339. }