Skip to content

Commit

Permalink
Merge pull request #1 from mnaamani/rewards
Browse files Browse the repository at this point in the history
Rewards
  • Loading branch information
bedeho authored Oct 22, 2019
2 parents a896b83 + 294c0e7 commit 5dcf286
Show file tree
Hide file tree
Showing 5 changed files with 686 additions and 9 deletions.
8 changes: 5 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = 'substrate-token-mint-module'
name = 'substrate-recurring-reward-module'
version = '1.0.0'
authors = ['Mokhtar Naamani <[email protected]>']
edition = '2018'
Expand Down Expand Up @@ -28,9 +28,10 @@ git = 'https://github.com/paritytech/substrate.git'
package = 'sr-io'
rev = 'a2a0eb5398d6223e531455b4c155ef053a4a3a2b'

[dependencues.minting]
[dependencies.minting]
default_features = false
git = 'https://github.com/mnaamani/substrate-token-minting'
git = 'https://github.com/mnaamani/substrate-token-minting-module'
package = 'substrate-token-mint-module'
branch = 'token-minting'

[dev-dependencies]
Expand All @@ -50,4 +51,5 @@ std = [
'system/std',
'balances/std',
'timestamp/std',
'minting/std',
]
298 changes: 292 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,313 @@

use rstd::prelude::*;

use codec::{Decode, Encode};
use codec::{Codec, Decode, Encode};
use runtime_primitives::traits::{MaybeSerializeDebug, Member, One, SimpleArithmetic, Zero};
use srml_support::{
decl_event, decl_module, decl_storage, dispatch, ensure, StorageMap, StorageValue,
decl_module, decl_storage, ensure, EnumerableStorageMap, Parameter, StorageMap, StorageValue,
};

use minting;
use minting::{self, BalanceOf};
use system;

pub trait Trait: system::Trait + minting::Trait + Sized {}
mod mock;
mod tests;

pub trait Trait: system::Trait + minting::Trait + minting::Trait {
type PayoutStatusHandler: PayoutStatusHandler<Self>;

/// Type of identifier for recipients.
type RecipientId: Parameter
+ Member
+ SimpleArithmetic
+ Codec
+ Default
+ Copy
+ MaybeSerializeDebug
+ PartialEq;

/// Type for identifier for relationship representing that a recipient recieves recurring reward from a token mint
type RewardRelationshipId: Parameter
+ Member
+ SimpleArithmetic
+ Codec
+ Default
+ Copy
+ MaybeSerializeDebug
+ 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
account: AccountId,

/// The payout amount at the next payout
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,
}

decl_storage! {
trait Store for Module<T: Trait> as RecurringReward {
Recipients get(recipients): linked_map T::RecipientId => Recipient<BalanceOf<T>>;

RecipientsCreated get(recipients_created): T::RecipientId;

RewardRelationships get(reward_relationships): linked_map T::RewardRelationshipId => RewardRelationship<T::AccountId, BalanceOf<T>, T::BlockNumber, T::MintId, T::RecipientId>;

RewardRelationshipsCreated get(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) {}
fn on_finalize(now: T::BlockNumber) {
Self::do_payouts(now);
}
}
}

impl<T: Trait> Module<T> {}
#[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>>::exists(recipient),
RewardsError::RecipientNotFound
);
ensure!(
next_payment_at_block > <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>>::exists(&id) {
<Recipients<T>>::remove(<RewardRelationships<T>>::take(&id).recipient);
}
}

// 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>>::exists(&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 > <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>>::enumerate() {
assert!(<Recipients<T>>::exists(&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);
}
}
}
}
Loading

0 comments on commit 5dcf286

Please sign in to comment.