-
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
339 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,98 @@ | ||
#![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>; | ||
|
||
#[pallet::constant] | ||
type FaucetAmount: Get<BalanceOf<Self>>; | ||
|
||
#[pallet::constant] | ||
type FaucetDelay: Get<BlockNumberFor<Self>>; | ||
} | ||
|
||
#[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() | ||
} | ||
} | ||
|
||
#[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> { | ||
Dripped { | ||
who: T::AccountId, | ||
when: BlockNumberFor<T>, | ||
}, | ||
} | ||
|
||
#[pallet::error] | ||
pub enum Error<T> { | ||
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); | ||
log::info!("Drip successful"); | ||
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
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