Skip to content

Commit

Permalink
feat: Add faucet pallet
Browse files Browse the repository at this point in the history
Add a faucet pallet with a drip function that can be used once a day per
account and drips 10UNITs.
  • Loading branch information
aidan46 committed Nov 18, 2024
1 parent 5468462 commit 8b6ec0a
Show file tree
Hide file tree
Showing 9 changed files with 347 additions and 0 deletions.
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ members = [
"lib/polka-storage-proofs",
"maat",
"node",
"pallets/faucet",
"pallets/market",
"pallets/proofs",
"pallets/randomness",
Expand Down Expand Up @@ -127,6 +128,7 @@ zombienet-support = "=0.2.14"
# Local
cli-primitives = { path = "primitives/cli" }
mater = { path = "storage/mater" }
pallet-faucet = { path = "pallets/faucet", default-features = false }
pallet-market = { path = "pallets/market", default-features = false }
pallet-proofs = { path = "pallets/proofs", default-features = false }
pallet-randomness = { path = "pallets/randomness", default-features = false }
Expand Down
36 changes: 36 additions & 0 deletions pallets/faucet/Cargo.toml
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"]
106 changes: 106 additions & 0 deletions pallets/faucet/src/lib.rs
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(())
}
}
}
97 changes: 97 additions & 0 deletions pallets/faucet/src/mock.rs
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
}
72 changes: 72 additions & 0 deletions pallets/faucet/src/test.rs
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
);
});
}
1 change: 1 addition & 0 deletions runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ substrate-wasm-builder = { workspace = true, optional = true }

[dependencies]
# Local Pallets
pallet-faucet = { workspace = true, default-features = false }
pallet-market = { workspace = true, default-features = false }
pallet-proofs = { workspace = true, default-features = false }
pallet-randomness = { workspace = true, default-features = false }
Expand Down
12 changes: 12 additions & 0 deletions runtime/src/configs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,10 @@ parameter_types! {
// Randomness pallet
pub const CleanupInterval: BlockNumber = DAYS;
pub const SeedAgeLimit: BlockNumber = 30 * DAYS;

// Faucet pallet
pub const FaucetAmount: Balance = 10_000_000_000_000;
pub const FaucetDelay: BlockNumber = DAYS;
}

impl pallet_storage_provider::Config for Runtime {
Expand Down Expand Up @@ -418,6 +422,14 @@ impl pallet_proofs::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
}

#[cfg(feature = "testnet")]
impl pallet_faucet::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type FaucetAmount = FaucetAmount;
type FaucetDelay = FaucetDelay;
}

/// Config for insecure randomness
impl pallet_insecure_randomness_collective_flip::Config for Runtime {}

Expand Down
Loading

0 comments on commit 8b6ec0a

Please sign in to comment.