lib.rs 14 KB

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