-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a faucet pallet with a drip function that can be used once a day per account and drips 10UNITs.
- Loading branch information
Showing
9 changed files
with
347 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
[package] | ||
authors.workspace = true | ||
description = "exposes a drip function for getting funds on testnet" | ||
edition.workspace = true | ||
homepage.workspace = true | ||
license-file.workspace = true | ||
name = "pallet-faucet" | ||
publish = false | ||
repository.workspace = true | ||
version = "0.0.0" | ||
|
||
[lints] | ||
workspace = true | ||
|
||
[package.metadata.docs.rs] | ||
targets = ["x86_64-unknown-linux-gnu"] | ||
|
||
[dependencies] | ||
codec = { workspace = true, default-features = false, features = ["derive"] } | ||
frame-support = { workspace = true, default-features = false } | ||
frame-system = { workspace = true, default-features = false } | ||
log = { workspace = true } | ||
pallet-balances = { workspace = true, default-features = false } | ||
scale-info = { workspace = true, default-features = false, features = ["derive"] } | ||
|
||
[dev-dependencies] | ||
env_logger = { workspace = true } | ||
sp-core = { workspace = true, default-features = false } | ||
sp-io = { workspace = true } | ||
sp-runtime = { workspace = true, default-features = false } | ||
|
||
[features] | ||
default = ["std"] | ||
runtime-benchmarks = ["frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks"] | ||
std = ["codec/std", "frame-support/std", "frame-system/std", "pallet-balances/std", "scale-info/std", "sp-core/std", "sp-io/std", "sp-runtime/std"] | ||
try-runtime = ["frame-support/try-runtime", "frame-system/try-runtime", "sp-runtime/try-runtime"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
#![cfg_attr(not(feature = "std"), no_std)] | ||
|
||
pub use pallet::*; | ||
|
||
#[cfg(test)] | ||
mod mock; | ||
#[cfg(test)] | ||
mod test; | ||
|
||
#[frame_support::pallet(dev_mode)] | ||
pub mod pallet { | ||
use frame_support::{ | ||
pallet_prelude::*, | ||
traits::{Currency, ReservableCurrency}, | ||
}; | ||
use frame_system::{ensure_none, pallet_prelude::*}; | ||
|
||
/// Allows to extract Balance of an account via the Config::Currency associated type. | ||
/// BalanceOf is a sophisticated way of getting an u128. | ||
pub type BalanceOf<T> = | ||
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance; | ||
|
||
#[pallet::pallet] | ||
#[pallet::without_storage_info] // Allows to define storage items without fixed size | ||
pub struct Pallet<T>(_); | ||
|
||
#[pallet::config] | ||
pub trait Config: frame_system::Config { | ||
/// Because this pallet emits events, it depends on the runtime's definition of an event. | ||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; | ||
|
||
/// The currency mechanism. | ||
type Currency: ReservableCurrency<Self::AccountId>; | ||
|
||
/// The amount that is dispensed in planck's | ||
#[pallet::constant] | ||
type FaucetAmount: Get<BalanceOf<Self>>; | ||
|
||
/// How often an account can use the drip function (1 day on testnet) | ||
#[pallet::constant] | ||
type FaucetDelay: Get<BlockNumberFor<Self>>; | ||
} | ||
|
||
/// By default pallet do no allow for unsigned transactions. | ||
/// Implementing this trait for the faucet Pallet allows unsigned extrinsics to be called. | ||
/// There is no complicated implementation needed (like checking the call type) | ||
/// because there is only one transaction in this pallet | ||
#[pallet::validate_unsigned] | ||
impl<T: Config> ValidateUnsigned for Pallet<T> { | ||
type Call = Call<T>; | ||
|
||
fn validate_unsigned( | ||
_source: TransactionSource, | ||
_call: &Self::Call, | ||
) -> TransactionValidity { | ||
let current_block = <frame_system::Pallet<T>>::block_number(); | ||
ValidTransaction::with_tag_prefix("pallet-faucet") | ||
.and_provides(current_block) | ||
.build() | ||
} | ||
} | ||
|
||
/// Keeps track of when accounts last used the drip function. | ||
#[pallet::storage] | ||
#[pallet::getter(fn drips)] | ||
pub type Drips<T: Config> = StorageMap<_, _, T::AccountId, BlockNumberFor<T>>; | ||
|
||
#[pallet::event] | ||
#[pallet::generate_deposit(fn deposit_event)] | ||
pub enum Event<T: Config> { | ||
/// Emitted when an account uses the drip function successfully. | ||
Dripped { | ||
who: T::AccountId, | ||
when: BlockNumberFor<T>, | ||
}, | ||
} | ||
|
||
#[pallet::error] | ||
pub enum Error<T> { | ||
/// Emitted when an account tries to call the drip function more than 1x in 24 hours. | ||
FaucetUsedRecently, | ||
} | ||
|
||
#[pallet::call] | ||
impl<T: Config> Pallet<T> { | ||
pub fn drip(origin: OriginFor<T>, account: T::AccountId) -> DispatchResult { | ||
let _ = ensure_none(origin)?; | ||
let current_block = <frame_system::Pallet<T>>::block_number(); | ||
if let Some(faucet_block) = Self::drips(&account) { | ||
ensure!(current_block >= (faucet_block + T::FaucetDelay::get()), { | ||
log::error!("{account:?} has recently used the faucet"); | ||
Error::<T>::FaucetUsedRecently | ||
}); | ||
} | ||
log::info!("Dripping {:?} to {account:?}", T::FaucetAmount::get()); | ||
let imbalance = T::Currency::deposit_creating(&account, T::FaucetAmount::get()); | ||
drop(imbalance); | ||
Drips::<T>::insert(account.clone(), current_block); | ||
Self::deposit_event(Event::<T>::Dripped { | ||
who: account, | ||
when: current_block, | ||
}); | ||
Ok(()) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
use frame_support::{derive_impl, parameter_types, traits::Hooks}; | ||
use frame_system::{self as system}; | ||
use sp_core::Pair; | ||
use sp_runtime::{ | ||
traits::{IdentifyAccount, IdentityLookup, Verify}, | ||
AccountId32, BuildStorage, MultiSignature, MultiSigner, | ||
}; | ||
|
||
use crate::{self as pallet_faucet, BalanceOf}; | ||
|
||
pub const ALICE: &'static str = "//Alice"; | ||
|
||
type Block = frame_system::mocking::MockBlock<Test>; | ||
type BlockNumber = u64; | ||
|
||
frame_support::construct_runtime!( | ||
pub enum Test { | ||
System: frame_system, | ||
Balances: pallet_balances, | ||
Faucet: pallet_faucet, | ||
} | ||
); | ||
|
||
pub type Signature = MultiSignature; | ||
pub type AccountPublic = <Signature as Verify>::Signer; | ||
pub type AccountId = <AccountPublic as IdentifyAccount>::AccountId; | ||
|
||
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] | ||
impl frame_system::Config for Test { | ||
type Block = Block; | ||
type AccountData = pallet_balances::AccountData<u64>; | ||
type AccountId = AccountId; | ||
type Lookup = IdentityLookup<Self::AccountId>; | ||
} | ||
|
||
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] | ||
impl pallet_balances::Config for Test { | ||
type AccountStore = System; | ||
} | ||
|
||
parameter_types! { | ||
pub const FaucetAmount: BalanceOf<Test> = 10_000_000_000_000; | ||
pub const FaucetDelay: BlockNumber = 1; | ||
} | ||
|
||
impl crate::Config for Test { | ||
type RuntimeEvent = RuntimeEvent; | ||
type Currency = Balances; | ||
type FaucetAmount = FaucetAmount; | ||
type FaucetDelay = FaucetDelay; | ||
} | ||
|
||
pub fn key_pair(name: &str) -> sp_core::sr25519::Pair { | ||
sp_core::sr25519::Pair::from_string(name, None).unwrap() | ||
} | ||
|
||
pub fn account<T: frame_system::Config>(name: &str) -> AccountId32 { | ||
let user_pair = key_pair(name); | ||
let signer = MultiSigner::Sr25519(user_pair.public()); | ||
signer.into_account() | ||
} | ||
|
||
pub fn events() -> Vec<RuntimeEvent> { | ||
let evt = System::events() | ||
.into_iter() | ||
.map(|evt| evt.event) | ||
.collect::<Vec<_>>(); | ||
System::reset_events(); | ||
evt | ||
} | ||
|
||
/// Run until a particular block. | ||
/// | ||
/// Stolen't from: <https://github.com/paritytech/polkadot-sdk/blob/7df94a469e02e1d553bd4050b0e91870d6a4c31b/substrate/frame/lottery/src/mock.rs#L87-L98> | ||
pub fn run_to_block(n: u64) { | ||
while System::block_number() < n { | ||
if System::block_number() > 1 { | ||
System::on_finalize(System::block_number()); | ||
} | ||
|
||
System::set_block_number(System::block_number() + 1); | ||
System::on_initialize(System::block_number()); | ||
} | ||
} | ||
|
||
/// Build genesis storage according to the mock runtime. | ||
pub fn new_test_ext() -> sp_io::TestExternalities { | ||
let _ = env_logger::try_init(); | ||
let t = system::GenesisConfig::<Test>::default() | ||
.build_storage() | ||
.unwrap() | ||
.into(); | ||
|
||
let mut ext = sp_io::TestExternalities::new(t); | ||
ext.execute_with(|| System::set_block_number(1)); | ||
ext | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
use frame_support::{assert_err, assert_ok}; | ||
use frame_system::Event as SystemEvent; | ||
use pallet_balances::Event as BalanceEvent; | ||
|
||
use crate::{mock::*, Error, Event}; | ||
|
||
#[test] | ||
fn drip() { | ||
new_test_ext().execute_with(|| { | ||
let account = account::<Test>(ALICE); | ||
assert_ok!(Faucet::drip(RuntimeOrigin::none(), account.clone())); | ||
|
||
// The initial drip should create the account | ||
assert_eq!( | ||
events(), | ||
[ | ||
RuntimeEvent::Balances(BalanceEvent::Deposit { | ||
who: account.clone(), | ||
amount: <Test as crate::Config>::FaucetAmount::get() | ||
}), | ||
RuntimeEvent::System(SystemEvent::NewAccount { | ||
account: account.clone() | ||
}), | ||
RuntimeEvent::Balances(BalanceEvent::Endowed { | ||
account: account.clone(), | ||
free_balance: <Test as crate::Config>::FaucetAmount::get() | ||
}), | ||
RuntimeEvent::Faucet(Event::Dripped { | ||
who: account.clone(), | ||
when: System::block_number() | ||
}) | ||
] | ||
); | ||
|
||
assert_eq!( | ||
Balances::free_balance(account.clone()), | ||
<Test as crate::Config>::FaucetAmount::get() | ||
); | ||
|
||
// Check that dripping at the same block is blocked | ||
assert_err!( | ||
Faucet::drip(RuntimeOrigin::none(), account.clone()), | ||
Error::<Test>::FaucetUsedRecently | ||
); | ||
|
||
// Run to block_number + faucet_delay | ||
run_to_block(System::block_number() + <Test as crate::Config>::FaucetDelay::get()); | ||
|
||
// Rerun drip, should be successful | ||
assert_ok!(Faucet::drip(RuntimeOrigin::none(), account.clone())); | ||
|
||
// Expecting less events because no new account is created | ||
assert_eq!( | ||
events(), | ||
[ | ||
RuntimeEvent::Balances(BalanceEvent::Deposit { | ||
who: account.clone(), | ||
amount: <Test as crate::Config>::FaucetAmount::get() | ||
}), | ||
RuntimeEvent::Faucet(Event::Dripped { | ||
who: account.clone(), | ||
when: System::block_number() | ||
}) | ||
] | ||
); | ||
|
||
assert_eq!( | ||
Balances::free_balance(account.clone()), | ||
<Test as crate::Config>::FaucetAmount::get() * 2 | ||
); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.