|
@@ -1,406 +0,0 @@
|
|
|
-// Clippy linter warning. TODO: remove after the Constaninople release
|
|
|
-#![allow(clippy::type_complexity)]
|
|
|
-// disable it because of possible frontend API break
|
|
|
-
|
|
|
-// Clippy linter warning. TODO: refactor the Option<Option<>>
|
|
|
-#![allow(clippy::option_option)] // disable it because of possible API break
|
|
|
-
|
|
|
-// Ensure we're `no_std` when compiling for Wasm.
|
|
|
-#![cfg_attr(not(feature = "std"), no_std)]
|
|
|
-
|
|
|
-use codec::{Codec, Decode, Encode};
|
|
|
-use frame_support::storage::IterableStorageMap;
|
|
|
-use frame_support::{decl_module, decl_storage, ensure, Parameter};
|
|
|
-use sp_arithmetic::traits::{BaseArithmetic, One, Zero};
|
|
|
-use sp_runtime::traits::{MaybeSerialize, Member};
|
|
|
-
|
|
|
-use minting::BalanceOf;
|
|
|
-
|
|
|
-mod mock;
|
|
|
-mod tests;
|
|
|
-
|
|
|
-pub trait Trait: frame_system::Trait + minting::Trait {
|
|
|
- type PayoutStatusHandler: PayoutStatusHandler<Self>;
|
|
|
-
|
|
|
- /// Type of identifier for recipients.
|
|
|
- type RecipientId: Parameter
|
|
|
- + Member
|
|
|
- + BaseArithmetic
|
|
|
- + Codec
|
|
|
- + Default
|
|
|
- + Copy
|
|
|
- + MaybeSerialize
|
|
|
- + PartialEq;
|
|
|
-
|
|
|
- /// Type for identifier for relationship representing that a recipient recieves recurring reward from a token mint
|
|
|
- type RewardRelationshipId: Parameter
|
|
|
- + Member
|
|
|
- + BaseArithmetic
|
|
|
- + Codec
|
|
|
- + Default
|
|
|
- + Copy
|
|
|
- + MaybeSerialize
|
|
|
- + PartialEq;
|
|
|
-}
|
|
|
-
|
|
|
-/// Handler for aftermath of a payout attempt
|
|
|
-pub trait PayoutStatusHandler<T: Trait> {
|
|
|
- fn payout_succeeded(
|
|
|
- id: T::RewardRelationshipId,
|
|
|
- destination_account: &T::AccountId,
|
|
|
- amount: BalanceOf<T>,
|
|
|
- );
|
|
|
-
|
|
|
- fn payout_failed(
|
|
|
- id: T::RewardRelationshipId,
|
|
|
- destination_account: &T::AccountId,
|
|
|
- amount: BalanceOf<T>,
|
|
|
- );
|
|
|
-}
|
|
|
-
|
|
|
-/// Makes `()` empty tuple, a PayoutStatusHandler that does nothing.
|
|
|
-impl<T: Trait> PayoutStatusHandler<T> for () {
|
|
|
- fn payout_succeeded(
|
|
|
- _id: T::RewardRelationshipId,
|
|
|
- _destination_account: &T::AccountId,
|
|
|
- _amount: BalanceOf<T>,
|
|
|
- ) {
|
|
|
- }
|
|
|
-
|
|
|
- fn payout_failed(
|
|
|
- _id: T::RewardRelationshipId,
|
|
|
- _destination_account: &T::AccountId,
|
|
|
- _amount: BalanceOf<T>,
|
|
|
- ) {
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-/// A recipient of recurring rewards
|
|
|
-#[derive(Encode, Decode, Copy, Clone, Debug, Default)]
|
|
|
-pub struct Recipient<Balance> {
|
|
|
- // stats
|
|
|
- /// Total payout received by this recipient
|
|
|
- total_reward_received: Balance,
|
|
|
-
|
|
|
- /// Total payout missed for this recipient
|
|
|
- total_reward_missed: Balance,
|
|
|
-}
|
|
|
-
|
|
|
-#[derive(Encode, Decode, Copy, Clone, Debug, Default)]
|
|
|
-pub struct RewardRelationship<AccountId, Balance, BlockNumber, MintId, RecipientId> {
|
|
|
- /// Identifier for receiver
|
|
|
- recipient: RecipientId,
|
|
|
-
|
|
|
- /// Identifier for reward source
|
|
|
- mint_id: MintId,
|
|
|
-
|
|
|
- /// Destination account for reward
|
|
|
- pub account: AccountId,
|
|
|
-
|
|
|
- /// The payout amount at the next payout
|
|
|
- pub amount_per_payout: Balance,
|
|
|
-
|
|
|
- /// When set, identifies block when next payout should be processed,
|
|
|
- /// otherwise there is no pending payout
|
|
|
- next_payment_at_block: Option<BlockNumber>,
|
|
|
-
|
|
|
- /// When set, will be the basis for automatically setting next payment,
|
|
|
- /// otherwise any upcoming payout will be a one off.
|
|
|
- payout_interval: Option<BlockNumber>,
|
|
|
-
|
|
|
- // stats
|
|
|
- /// Total payout received in this relationship
|
|
|
- total_reward_received: Balance,
|
|
|
-
|
|
|
- /// Total payout failed in this relationship
|
|
|
- total_reward_missed: Balance,
|
|
|
-}
|
|
|
-
|
|
|
-impl<AccountId: Clone, Balance: Clone, BlockNumber: Clone, MintId: Clone, RecipientId: Clone>
|
|
|
- RewardRelationship<AccountId, Balance, BlockNumber, MintId, RecipientId>
|
|
|
-{
|
|
|
- /// Verifies whether relationship is active
|
|
|
- pub fn is_active(&self) -> bool {
|
|
|
- self.next_payment_at_block.is_some()
|
|
|
- }
|
|
|
-
|
|
|
- /// Make clone which is activated.
|
|
|
- pub fn clone_activated(&self, start_at: &BlockNumber) -> Self {
|
|
|
- Self {
|
|
|
- next_payment_at_block: Some((*start_at).clone()),
|
|
|
- ..((*self).clone())
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Make clone which is deactivated
|
|
|
- pub fn clone_deactivated(&self) -> Self {
|
|
|
- Self {
|
|
|
- next_payment_at_block: None,
|
|
|
- ..((*self).clone())
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-decl_storage! {
|
|
|
- trait Store for Module<T: Trait> as RecurringReward {
|
|
|
- Recipients get(fn recipients): map hasher(blake2_128_concat)
|
|
|
- T::RecipientId => Recipient<BalanceOf<T>>;
|
|
|
-
|
|
|
- RecipientsCreated get(fn recipients_created): T::RecipientId;
|
|
|
-
|
|
|
- pub RewardRelationships get(fn reward_relationships): map hasher(blake2_128_concat)
|
|
|
- T::RewardRelationshipId => RewardRelationship<T::AccountId, BalanceOf<T>, T::BlockNumber, T::MintId, T::RecipientId>;
|
|
|
-
|
|
|
- RewardRelationshipsCreated get(fn reward_relationships_created): T::RewardRelationshipId;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-decl_module! {
|
|
|
- pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
|
|
-
|
|
|
- fn on_finalize(now: T::BlockNumber) {
|
|
|
- Self::do_payouts(now);
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-#[derive(Eq, PartialEq, Debug)]
|
|
|
-pub enum RewardsError {
|
|
|
- RecipientNotFound,
|
|
|
- RewardSourceNotFound,
|
|
|
- NextPaymentNotInFuture,
|
|
|
- RewardRelationshipNotFound,
|
|
|
-}
|
|
|
-
|
|
|
-impl<T: Trait> Module<T> {
|
|
|
- /// Adds a new Recipient and returns new recipient identifier.
|
|
|
- pub fn add_recipient() -> T::RecipientId {
|
|
|
- let next_id = Self::recipients_created();
|
|
|
- <RecipientsCreated<T>>::put(next_id + One::one());
|
|
|
- <Recipients<T>>::insert(&next_id, Recipient::default());
|
|
|
- next_id
|
|
|
- }
|
|
|
-
|
|
|
- /// Adds a new RewardRelationship, for a given source mint, recipient, account.
|
|
|
- pub fn add_reward_relationship(
|
|
|
- mint_id: T::MintId,
|
|
|
- recipient: T::RecipientId,
|
|
|
- account: T::AccountId,
|
|
|
- amount_per_payout: BalanceOf<T>,
|
|
|
- next_payment_at_block: T::BlockNumber,
|
|
|
- payout_interval: Option<T::BlockNumber>,
|
|
|
- ) -> Result<T::RewardRelationshipId, RewardsError> {
|
|
|
- ensure!(
|
|
|
- <minting::Module<T>>::mint_exists(mint_id),
|
|
|
- RewardsError::RewardSourceNotFound
|
|
|
- );
|
|
|
- ensure!(
|
|
|
- <Recipients<T>>::contains_key(recipient),
|
|
|
- RewardsError::RecipientNotFound
|
|
|
- );
|
|
|
- ensure!(
|
|
|
- next_payment_at_block > <frame_system::Module<T>>::block_number(),
|
|
|
- RewardsError::NextPaymentNotInFuture
|
|
|
- );
|
|
|
-
|
|
|
- let relationship_id = Self::reward_relationships_created();
|
|
|
- <RewardRelationshipsCreated<T>>::put(relationship_id + One::one());
|
|
|
- <RewardRelationships<T>>::insert(
|
|
|
- relationship_id,
|
|
|
- RewardRelationship {
|
|
|
- mint_id,
|
|
|
- recipient,
|
|
|
- account,
|
|
|
- amount_per_payout,
|
|
|
- next_payment_at_block: Some(next_payment_at_block),
|
|
|
- payout_interval,
|
|
|
- total_reward_received: Zero::zero(),
|
|
|
- total_reward_missed: Zero::zero(),
|
|
|
- },
|
|
|
- );
|
|
|
- Ok(relationship_id)
|
|
|
- }
|
|
|
-
|
|
|
- /// Removes a relationship from RewardRelashionships and its recipient.
|
|
|
- pub fn remove_reward_relationship(id: T::RewardRelationshipId) {
|
|
|
- if <RewardRelationships<T>>::contains_key(&id) {
|
|
|
- <Recipients<T>>::remove(<RewardRelationships<T>>::take(&id).recipient);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Will attempt to activat a deactivated reward relationship.
|
|
|
- pub fn try_to_activate_relationship(
|
|
|
- id: T::RewardRelationshipId,
|
|
|
- next_payment_at_block: T::BlockNumber,
|
|
|
- ) -> Result<bool, ()> {
|
|
|
- // Ensure relationship exists
|
|
|
- let reward_relationship = Self::ensure_reward_relationship_exists(&id)?;
|
|
|
-
|
|
|
- let activated = if reward_relationship.is_active() {
|
|
|
- // Was not activated
|
|
|
- false
|
|
|
- } else {
|
|
|
- // Update as activated
|
|
|
- let activated_relationship =
|
|
|
- reward_relationship.clone_activated(&next_payment_at_block);
|
|
|
-
|
|
|
- RewardRelationships::<T>::insert(id, activated_relationship);
|
|
|
-
|
|
|
- // We activated
|
|
|
- true
|
|
|
- };
|
|
|
-
|
|
|
- Ok(activated)
|
|
|
- }
|
|
|
-
|
|
|
- /// Will attempt to deactivat a activated reward relationship.
|
|
|
- pub fn try_to_deactivate_relationship(id: T::RewardRelationshipId) -> Result<bool, ()> {
|
|
|
- // Ensure relationship exists
|
|
|
- let reward_relationship = Self::ensure_reward_relationship_exists(&id)?;
|
|
|
-
|
|
|
- let deactivated = if reward_relationship.is_active() {
|
|
|
- let deactivated_relationship = reward_relationship.clone_deactivated();
|
|
|
-
|
|
|
- RewardRelationships::<T>::insert(id, deactivated_relationship);
|
|
|
-
|
|
|
- // Was deactivated
|
|
|
- true
|
|
|
- } else {
|
|
|
- // Was not deactivated
|
|
|
- false
|
|
|
- };
|
|
|
-
|
|
|
- Ok(deactivated)
|
|
|
- }
|
|
|
-
|
|
|
- // For reward relationship found with given identifier, new values can be set for
|
|
|
- // account, payout, block number when next payout will be made and the new interval after
|
|
|
- // the next scheduled payout. All values are optional, but updating values are combined in this
|
|
|
- // single method to ensure atomic updates.
|
|
|
- pub fn set_reward_relationship(
|
|
|
- id: T::RewardRelationshipId,
|
|
|
- new_account: Option<T::AccountId>,
|
|
|
- new_payout: Option<BalanceOf<T>>,
|
|
|
- new_next_payment_at: Option<Option<T::BlockNumber>>,
|
|
|
- new_payout_interval: Option<Option<T::BlockNumber>>,
|
|
|
- ) -> Result<(), RewardsError> {
|
|
|
- ensure!(
|
|
|
- <RewardRelationships<T>>::contains_key(&id),
|
|
|
- RewardsError::RewardRelationshipNotFound
|
|
|
- );
|
|
|
-
|
|
|
- let mut relationship = Self::reward_relationships(&id);
|
|
|
-
|
|
|
- if let Some(account) = new_account {
|
|
|
- relationship.account = account;
|
|
|
- }
|
|
|
- if let Some(payout) = new_payout {
|
|
|
- relationship.amount_per_payout = payout;
|
|
|
- }
|
|
|
- if let Some(next_payout_at_block) = new_next_payment_at {
|
|
|
- if let Some(blocknumber) = next_payout_at_block {
|
|
|
- ensure!(
|
|
|
- blocknumber > <frame_system::Module<T>>::block_number(),
|
|
|
- RewardsError::NextPaymentNotInFuture
|
|
|
- );
|
|
|
- }
|
|
|
- relationship.next_payment_at_block = next_payout_at_block;
|
|
|
- }
|
|
|
- if let Some(payout_interval) = new_payout_interval {
|
|
|
- relationship.payout_interval = payout_interval;
|
|
|
- }
|
|
|
-
|
|
|
- <RewardRelationships<T>>::insert(&id, relationship);
|
|
|
- Ok(())
|
|
|
- }
|
|
|
-
|
|
|
- /*
|
|
|
- For all relationships where next_payment_at_block is set and matches current block height,
|
|
|
- a call to pay_reward is made for the suitable amount, recipient and source.
|
|
|
- The next_payment_in_block is updated based on payout_interval.
|
|
|
- If the call succeeds, total_reward_received is incremented on both
|
|
|
- recipient and dependency with amount_per_payout, and a call to T::PayoutStatusHandler is made.
|
|
|
- Otherwise, analogous steps for failure.
|
|
|
- */
|
|
|
- fn do_payouts(now: T::BlockNumber) {
|
|
|
- for (relationship_id, ref mut relationship) in <RewardRelationships<T>>::iter() {
|
|
|
- assert!(<Recipients<T>>::contains_key(&relationship.recipient));
|
|
|
-
|
|
|
- let mut recipient = Self::recipients(relationship.recipient);
|
|
|
-
|
|
|
- if let Some(next_payment_at_block) = relationship.next_payment_at_block {
|
|
|
- if next_payment_at_block != now {
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- // Add the missed payout and try to pay those in addition to scheduled payout?
|
|
|
- // let payout = relationship.total_reward_missed + relationship.amount_per_payout;
|
|
|
- let payout = relationship.amount_per_payout;
|
|
|
-
|
|
|
- // try to make payment
|
|
|
- if <minting::Module<T>>::transfer_tokens(
|
|
|
- relationship.mint_id,
|
|
|
- payout,
|
|
|
- &relationship.account,
|
|
|
- )
|
|
|
- .is_err()
|
|
|
- {
|
|
|
- // add only newly scheduled payout to total missed payout
|
|
|
- relationship.total_reward_missed += relationship.amount_per_payout;
|
|
|
-
|
|
|
- // update recipient stats
|
|
|
- recipient.total_reward_missed += relationship.amount_per_payout;
|
|
|
-
|
|
|
- T::PayoutStatusHandler::payout_failed(
|
|
|
- relationship_id,
|
|
|
- &relationship.account,
|
|
|
- payout,
|
|
|
- );
|
|
|
- } else {
|
|
|
- // update payout received stats
|
|
|
- relationship.total_reward_received += payout;
|
|
|
- recipient.total_reward_received += payout;
|
|
|
-
|
|
|
- // update missed payout stats
|
|
|
- // if relationship.total_reward_missed != Zero::zero() {
|
|
|
- // // update recipient stats
|
|
|
- // recipient.total_reward_missed -= relationship.total_reward_missed;
|
|
|
-
|
|
|
- // // clear missed reward on relationship
|
|
|
- // relationship.total_reward_missed = Zero::zero();
|
|
|
- // }
|
|
|
- T::PayoutStatusHandler::payout_succeeded(
|
|
|
- relationship_id,
|
|
|
- &relationship.account,
|
|
|
- payout,
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- // update next payout blocknumber at interval if set
|
|
|
- if let Some(payout_interval) = relationship.payout_interval {
|
|
|
- relationship.next_payment_at_block = Some(now + payout_interval);
|
|
|
- } else {
|
|
|
- relationship.next_payment_at_block = None;
|
|
|
- }
|
|
|
-
|
|
|
- <Recipients<T>>::insert(relationship.recipient, recipient);
|
|
|
- <RewardRelationships<T>>::insert(relationship_id, relationship);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-impl<T: Trait> Module<T> {
|
|
|
- fn ensure_reward_relationship_exists(
|
|
|
- id: &T::RewardRelationshipId,
|
|
|
- ) -> Result<
|
|
|
- RewardRelationship<T::AccountId, BalanceOf<T>, T::BlockNumber, T::MintId, T::RecipientId>,
|
|
|
- (),
|
|
|
- > {
|
|
|
- ensure!(RewardRelationships::<T>::contains_key(id), ());
|
|
|
-
|
|
|
- let relationship = RewardRelationships::<T>::get(id);
|
|
|
-
|
|
|
- Ok(relationship)
|
|
|
- }
|
|
|
-}
|