Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Subscription execution hook #8

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,750 changes: 2,750 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

44 changes: 44 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
[package]
name = "pallet-template"
version = "4.0.0-dev"
description = "FRAME pallet template for defining custom runtime logic."
authors = ["Substrate DevHub <https://github.com/substrate-developer-hub>"]
homepage = "https://substrate.io/"
edition = "2021"
license = "Unlicense"
publish = false
repository = "https://github.com/substrate-developer-hub/substrate-node-template/"

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [
"derive",
] }
scale-info = { version = "2.0.1", default-features = false, features = ["derive"] }
frame-support = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.23"}
frame-system = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.23" }
frame-benchmarking = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.23", optional = true }
sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.23" }

[dev-dependencies]
sp-core = { default-features = false, version = "6.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.23" }
sp-io = { default-features = false, version = "6.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.23" }
sp-runtime = { default-features = false, version = "6.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.23" }
pallet-balances = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.23" }

[features]
default = ["std"]
std = [
"codec/std",
"pallet-balances/std",
"sp-std/std",
"scale-info/std",
"frame-support/std",
"frame-system/std",
"frame-benchmarking/std",
]

runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"]
try-runtime = ["frame-support/try-runtime"]
197 changes: 197 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::type_complexity)]

pub use pallet::*;

pub mod types;
pub use types::*;

pub mod weights;
pub use weights::*;

pub use frame_support::{
storage::IterableStorageMap,
traits::tokens::{
currency::{Currency, ReservableCurrency},
ExistenceRequirement,
},
ReversibleStorageHasher,
};

#[cfg(test)]
mod tests;

// #[cfg(feature = "runtime-benchmarks")]
// mod benchmarking;

#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;

#[pallet::config]
pub trait Config: frame_system::Config {
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
/// The trait to manage funds
type Currency: Currency<Self::AccountId>;

/// Weigths module
type WeightInfo: WeightInfo;
}

#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);

#[pallet::storage]
#[pallet::getter(fn plan_nonce)]
pub type PlanNonce<T: Config> = StorageValue<_, PlanId, ValueQuery>;

#[pallet::storage]
#[pallet::getter(fn subscription_plans)]
pub type Plans<T: Config> = StorageMap<
_,
Blake2_256,
PlanId,
Subscription<T::BlockNumber, BalanceOf<T>, T::AccountId>,
OptionQuery,
>;

#[pallet::storage]
#[pallet::getter(fn subscription_nonce)]
pub type SubscriptionNonce<T: Config> = StorageValue<_, SubscriptionId, ValueQuery>;

#[pallet::storage]
#[pallet::unbounded]
#[pallet::getter(fn subscriptions)]
pub type Subscriptions<T: Config> = StorageMap<
_,
Blake2_256,
T::BlockNumber,
Vec<(
Subscription<T::BlockNumber, BalanceOf<T>, T::AccountId>,
T::AccountId,
)>,
OptionQuery,
>;

#[pallet::event]
// #[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
SomethingStored(u32, T::AccountId),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add an event to signal execution of a subscription

}

#[pallet::error]
pub enum Error<T> {
NoneValue,
}

#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(n: T::BlockNumber) -> Weight {
gdnathan marked this conversation as resolved.
Show resolved Hide resolved
Self::do_execute_subscriptions(n)
}
}

#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(1)]
pub fn create_subsciption_plan(
_origin: OriginFor<T>,
_plan: Subscription<T::BlockNumber, BalanceOf<T>, T::AccountId>,
) -> DispatchResult {
// maybe make a new function for Subscription, so `plan` is already valid 100%
//
//
// todo:
// signed ?
// valid plan ?
//
// -> push the plan to the storage
Ok(())
}

#[pallet::weight(1)]
pub fn delete_subsciption_plan(_origin: OriginFor<T>, _plan_id: Nonce) -> DispatchResult {
// better ? 5 parameters but we can check, better option if no plan.new() function
//
//
// todo:
// signed ?
// plan exist ?
//
// -> remove the plan to the storage
Ok(())
}

#[pallet::weight(1)]
pub fn subscribe_to_plan(_origin: OriginFor<T>, _plan_id: Nonce) -> DispatchResult {
// todo:
// signed ?
// plan exist ?
//
// -> create entry in storage
Ok(())
}

#[pallet::weight(1)]
pub fn subscribe(
_origin: OriginFor<T>,
_plan: Subscription<T::BlockNumber, BalanceOf<T>, T::AccountId>,
) -> DispatchResult {
// todo:
// signed ?
// valid subscription ?
//
// -> create entry in storage
Ok(())
}

#[pallet::weight(1)]
pub fn unsubscribe(_origin: OriginFor<T>, _other: T::AccountId) -> DispatchResult {
// signed by the subscriber ?
// yes -> subscriber is subscribed to other ?
// delete from storage
// no -> signed by beneficiary ?
// other is subscribed to beneficiary ?
// delete from storage
Ok(())
}
}

impl<T: Config> Pallet<T> {
pub fn do_execute_subscriptions(n: T::BlockNumber) -> Weight {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

n is a terrible variable name.
use current_block_number

let mut itterations = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only one t in iterations


let subs = unwrap_or_return!(Subscriptions::<T>::get(n), 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You just did a read, so don't return 0.
Return weight of a read

for (mut sub, account) in subs {
if let Some(val) = sub.remaining_payments {
if val == 0 {
continue
}
}
itterations += 1;
if T::Currency::transfer(
&account,
&sub.beneficiary,
sub.amount,
ExistenceRequirement::AllowDeath,
)
.is_err()
{
continue
}
sub.remaining_payments = sub.remaining_payments.map(|amount| amount - 1);
let new_block = n + sub.frequency;
let new_subs = Subscriptions::<T>::take(new_block).map(|mut subs| {
subs.push((sub.clone(), account.clone()));
subs
}).unwrap_or_else(|| vec!((sub, account)));
Subscriptions::<T>::insert(new_block, new_subs);
Comment on lines +188 to +192
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't have to take it. You can just read it. You are going to replace the value with an insert just after anyhow.

Comment on lines +188 to +192
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing this you will do a read and a write for each subscription.
You should rather store the value you updated in a on-memory cache, update this cache as many time as you want and and only write them back to storage at the end of the whole loop

}
T::WeightInfo::do_execute_subscriptions(itterations)
Comment on lines +193 to +194
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should remove the key we used (current_block) from the storage

}
}
}
57 changes: 57 additions & 0 deletions src/tests/hook.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use super::mock::*;
use frame_support::traits::OnInitialize;

#[test]
fn working_subscription() {
ExtBuilder::default().balances(vec![]).build().execute_with(|| {
let _x = 10;
let _bob_balance = Balances::free_balance(BOB());
let _alice_balance = Balances::free_balance(ALICE());

//TODO: make a subscription of x amount, that recur every 1 block, from ALICE() to BOB()
//check subscription is stored in key BlockNumber 1

<Subscription as OnInitialize<u64>>::on_initialize(1);

//check subscription is not stored in key BlockNumber 1
//check subscription is stored in key BlockNumber 2

//assert_eq!(Balances::free_balance(BOB()), bob_balance + _x);
//assert_eq!(Balances::free_balance(ALICE()), alice_balance - _x);
//TODO: check if the subscription is still running (should be OK)
})
}

#[test]
fn sub_last_occurence() {
ExtBuilder::default().balances(vec![]).build().execute_with(|| {
let _x = 10;
let _bob_balance = Balances::free_balance(BOB());
let _alice_balance = Balances::free_balance(ALICE());

//TODO: make a subscription of x amount, that recur every 1 block, from ALICE() to BOB().
// with remaining_payments = 1, this should only be executed once.

//check subscription is not stored in key BlockNumber 1
//check subscription is stored in key BlockNumber 2
<Subscription as OnInitialize<u64>>::on_initialize(1);

//check subscription is not stored in key BlockNumber 1
//check subscription is stored in key BlockNumber 2

//assert_eq!(Balances::free_balance(BOB()), bob_balance + _x);
//assert_eq!(Balances::free_balance(ALICE()), alice_balance - _x);

//TODO: check if the subscription is still running (should be OK)

<Subscription as OnInitialize<u64>>::on_initialize(2);

//check subscription is not stored in key BlockNumber 2
//check subscription is not stored in key BlockNumber 3

//assert_eq!(Balances::free_balance(BOB()), bob_balance + _x);
//assert_eq!(Balances::free_balance(ALICE()), alice_balance - _x);

//TODO: check if the subscription is still running (should be KO)
})
}
Loading