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

feat: Add faucet pallet #584

Merged
merged 6 commits into from
Nov 19, 2024
Merged
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
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
Binary file modified cli/artifacts/metadata.scale
Binary file not shown.
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"]
110 changes: 110 additions & 0 deletions pallets/faucet/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#![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]
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 FaucetDripAmount: Get<BalanceOf<Self>>;

/// How often an account can use the drip function (1 day on testnet)
#[pallet::constant]
type FaucetDripDelay: 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> {
#[pallet::weight((Weight::zero(), Pays::No))]
pub fn drip(origin: OriginFor<T>, account: T::AccountId) -> DispatchResult {
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::FaucetDripDelay::get()),
{
log::error!("{account:?} has recently used the faucet");
Error::<T>::FaucetUsedRecently
}
);
}
log::info!("Dripping {:?} to {account:?}", T::FaucetDripAmount::get());
// Infallible https://docs.rs/frame-support/latest/frame_support/traits/tokens/currency/trait.Currency.html#tymethod.issue
let imbalance = T::Currency::issue(T::FaucetDripAmount::get());
T::Currency::resolve_creating(&account, imbalance);
Drips::<T>::insert(account.clone(), current_block);
jmg-duarte marked this conversation as resolved.
Show resolved Hide resolved
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 FaucetDripAmount: BalanceOf<Test> = 10_000_000_000_000;
pub const FaucetDripDelay: BlockNumber = 1;
}

impl crate::Config for Test {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type FaucetDripAmount = FaucetDripAmount;
type FaucetDripDelay = FaucetDripDelay;
}

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>::FaucetDripAmount::get()
}),
RuntimeEvent::System(SystemEvent::NewAccount {
account: account.clone()
}),
RuntimeEvent::Balances(BalanceEvent::Endowed {
account: account.clone(),
free_balance: <Test as crate::Config>::FaucetDripAmount::get()
}),
RuntimeEvent::Faucet(Event::Dripped {
who: account.clone(),
when: System::block_number()
})
]
);

assert_eq!(
Balances::free_balance(account.clone()),
<Test as crate::Config>::FaucetDripAmount::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>::FaucetDripDelay::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>::FaucetDripAmount::get()
}),
RuntimeEvent::Faucet(Event::Dripped {
who: account.clone(),
when: System::block_number()
})
]
);

assert_eq!(
Balances::free_balance(account),
<Test as crate::Config>::FaucetDripAmount::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 FaucetDripAmount: Balance = 10_000_000_000_000;
pub const FaucetDripDelay: 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 FaucetDripAmount = FaucetDripAmount;
type FaucetDripDelay = FaucetDripDelay;
}

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

Expand Down
Loading