Skip to content

Commit

Permalink
Girazoki change payment to tank model (#392)
Browse files Browse the repository at this point in the history
* start modifying

* adapt tests

* change traits to use onunbalanced

* integration tests

* fix tests

* modify benches

* fix clippy

* work clippy please

* typescript api

* typescrip api flashbox

* modify

* adapt test

* testing further

* more tests

* clippy

* unused variable

* more unused

* Benchmark entire on_container_author_noted

* fix error

* put payment file

* modify purchase credits

* fmt

* dev tests

* implement burn and leave it prepared on deregister

* add test to see we remove money form tank on deregister

* move things to pallet-service-payment and rename function

* make sure we are burning

* free credits rename

* clippy

* more clippy

* burn free credit para

* change to keep alive

* fix test

* free credits getter fixed

* final review

* remove lsat print
  • Loading branch information
girazoki authored Feb 5, 2024
1 parent 768f9b3 commit c179e9d
Show file tree
Hide file tree
Showing 26 changed files with 1,361 additions and 756 deletions.
1 change: 1 addition & 0 deletions pallets/services-payment/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ log = { workspace = true }
parity-scale-codec = { workspace = true, features = [ "derive", "max-encoded-len" ] }
scale-info = { workspace = true }
serde = { workspace = true, optional = true, features = [ "derive" ] }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }
tp-traits = { workspace = true }
Expand Down
50 changes: 35 additions & 15 deletions pallets/services-payment/src/benchmarks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@

//! Benchmarking
use {
crate::{BalanceOf, BlockNumberFor, Call, Config, Pallet},
crate::{BalanceOf, BlockNumberFor, Call, Config, Pallet, ProvideBlockProductionCost},
frame_benchmarking::{account, v2::*},
frame_support::{
assert_ok,
traits::{Currency, Get},
},
frame_system::RawOrigin,
sp_runtime::Saturating,
sp_std::prelude::*,
tp_traits::AuthorNotingHook,
};

// Build genesis storage according to the mock runtime.
Expand Down Expand Up @@ -57,9 +59,11 @@ mod benchmarks {

#[benchmark]
fn purchase_credits() {
let caller = create_funded_user::<T>("caller", 1, 1000);
let para_id = 1001u32.into();
let credits = T::MaxCreditsStored::get();
let payment: BalanceOf<T> = T::ProvideBlockProductionCost::block_cost(&para_id)
.0
.saturating_mul(1000u32.into());
let caller = create_funded_user::<T>("caller", 1, 1_000_000_000u32);

// Before call: 0 credits
assert_eq!(
Expand All @@ -68,31 +72,24 @@ mod benchmarks {
);

#[extrinsic_call]
Pallet::<T>::purchase_credits(
RawOrigin::Signed(caller),
para_id,
credits,
Some(u32::MAX.into()),
);
Pallet::<T>::purchase_credits(RawOrigin::Signed(caller), para_id, payment);

// verification code
assert_eq!(
crate::BlockProductionCredits::<T>::get(&para_id).unwrap_or_default(),
credits
<T::Currency>::total_balance(&crate::Pallet::<T>::parachain_tank(para_id)),
payment
);
}

#[benchmark]
fn set_credits() {
let caller = create_funded_user::<T>("caller", 1, 1000);
let para_id = 1001u32.into();
let credits = T::MaxCreditsStored::get();

assert_ok!(Pallet::<T>::purchase_credits(
RawOrigin::Signed(caller).into(),
assert_ok!(Pallet::<T>::set_credits(
RawOrigin::Root.into(),
para_id,
credits,
Some(u32::MAX.into()),
));

// Before call: 1000 credits
Expand Down Expand Up @@ -125,5 +122,28 @@ mod benchmarks {
assert!(crate::GivenFreeCredits::<T>::get(&para_id).is_some());
}

#[benchmark]
fn on_container_author_noted() {
let para_id = 1001u32;
let block_cost = T::ProvideBlockProductionCost::block_cost(&para_id.into()).0;
let max_credit_stored = T::MaxCreditsStored::get();
let balance_to_purchase = block_cost.saturating_mul(max_credit_stored.into());
let caller = create_funded_user::<T>("caller", 1, 1_000_000_000u32);
let existential_deposit = <T::Currency>::minimum_balance();
assert_ok!(Pallet::<T>::purchase_credits(
RawOrigin::Signed(caller.clone()).into(),
para_id.into(),
balance_to_purchase + existential_deposit
));
#[block]
{
<Pallet<T> as AuthorNotingHook<T::AccountId>>::on_container_author_noted(
&caller,
0,
para_id.into(),
);
}
}

impl_benchmark_test_suite!(Pallet, crate::benchmarks::new_test_ext(), crate::mock::Test);
}
144 changes: 65 additions & 79 deletions pallets/services-payment/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@ use {
frame_support::{
pallet_prelude::*,
sp_runtime::{traits::Zero, Saturating},
traits::{tokens::ExistenceRequirement, Currency, WithdrawReasons},
traits::{tokens::ExistenceRequirement, Currency, OnUnbalanced, WithdrawReasons},
},
frame_system::pallet_prelude::*,
scale_info::prelude::vec::Vec,
sp_io::hashing::blake2_256,
sp_runtime::traits::TrailingZeroInput,
tp_traits::{AuthorNotingHook, BlockNumber},
};

Expand All @@ -68,11 +70,12 @@ pub mod pallet {
/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// Handler for fees
type OnChargeForBlockCredit: OnChargeForBlockCredit<Self>;
type OnChargeForBlock: OnUnbalanced<NegativeImbalanceOf<Self>>;
/// Currency type for fee payment
type Currency: Currency<Self::AccountId>;
/// Provider of a block cost which can adjust from block to block
type ProvideBlockProductionCost: ProvideBlockProductionCost<Self>;

/// The maximum number of credits that can be accumulated
type MaxCreditsStored: Get<BlockNumberFor<Self>>;

Expand All @@ -95,9 +98,7 @@ pub mod pallet {
CreditsPurchased {
para_id: ParaId,
payer: T::AccountId,
fee: BalanceOf<T>,
credits_purchased: BlockNumberFor<T>,
credits_remaining: BlockNumberFor<T>,
credit: BalanceOf<T>,
},
CreditBurned {
para_id: ParaId,
Expand All @@ -110,7 +111,7 @@ pub mod pallet {
}

#[pallet::storage]
#[pallet::getter(fn collator_commission)]
#[pallet::getter(fn free_block_production_credits)]
pub type BlockProductionCredits<T: Config> =
StorageMap<_, Blake2_128Concat, ParaId, BlockNumberFor<T>, OptionQuery>;

Expand All @@ -129,44 +130,21 @@ pub mod pallet {
pub fn purchase_credits(
origin: OriginFor<T>,
para_id: ParaId,
credits: BlockNumberFor<T>,
max_price_per_credit: Option<BalanceOf<T>>,
credit: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let account = ensure_signed(origin)?;

let existing_credits =
BlockProductionCredits::<T>::get(para_id).unwrap_or(BlockNumberFor::<T>::zero());
let credits_purchasable = T::MaxCreditsStored::get().saturating_sub(existing_credits);
let actual_credits_purchased = credits.min(credits_purchasable);

let updated_credits = existing_credits.saturating_add(actual_credits_purchased);

// get the current per-credit cost of a block
let (block_cost, _weight) = T::ProvideBlockProductionCost::block_cost(&para_id);
if let Some(max_price_per_credit) = max_price_per_credit {
ensure!(
block_cost <= max_price_per_credit,
Error::<T>::CreditPriceTooExpensive,
);
}

let total_fee = block_cost.saturating_mul(actual_credits_purchased.into());

T::OnChargeForBlockCredit::charge_credits(
let parachain_tank = Self::parachain_tank(para_id);
T::Currency::transfer(
&account,
&para_id,
actual_credits_purchased,
total_fee,
&parachain_tank,
credit,
ExistenceRequirement::KeepAlive,
)?;

BlockProductionCredits::<T>::insert(para_id, updated_credits);

Self::deposit_event(Event::<T>::CreditsPurchased {
para_id,
payer: account,
fee: total_fee,
credits_purchased: actual_credits_purchased,
credits_remaining: updated_credits,
credit: credit,
});

Ok(().into())
Expand Down Expand Up @@ -217,7 +195,7 @@ pub mod pallet {

impl<T: Config> Pallet<T> {
/// Burn a credit for the given para. Deducts one credit if possible, errors otherwise.
pub fn burn_credit_for_para(para_id: &ParaId) -> DispatchResultWithPostInfo {
pub fn burn_free_credit_for_para(para_id: &ParaId) -> DispatchResultWithPostInfo {
let existing_credits =
BlockProductionCredits::<T>::get(para_id).unwrap_or(BlockNumberFor::<T>::zero());

Expand Down Expand Up @@ -290,42 +268,12 @@ pub mod pallet {
pub type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;

pub type CurrencyOf<T> = <T as Config>::Currency;
/// Type alias to conveniently refer to the `Currency::NegativeImbalance` associated type.
pub type NegativeImbalanceOf<T> =
<CurrencyOf<T> as Currency<<T as frame_system::Config>::AccountId>>::NegativeImbalance;
/// Handler for fee charging. This will be invoked when fees need to be deducted from the fee
/// account for a given paraId.
pub trait OnChargeForBlockCredit<T: Config> {
fn charge_credits(
payer: &T::AccountId,
para_id: &ParaId,
credits: BlockNumberFor<T>,
fee: BalanceOf<T>,
) -> Result<(), Error<T>>;
}

pub struct ChargeForBlockCredit<Runtime>(PhantomData<Runtime>);
impl<T: Config> OnChargeForBlockCredit<T> for ChargeForBlockCredit<T> {
fn charge_credits(
payer: &T::AccountId,
_para_id: &ParaId,
_credits: BlockNumberFor<T>,
fee: BalanceOf<T>,
) -> Result<(), crate::Error<T>> {
use frame_support::traits::tokens::imbalance::Imbalance;

let result = T::Currency::withdraw(
payer,
fee,
WithdrawReasons::FEE,
ExistenceRequirement::AllowDeath,
);
let imbalance = result.map_err(|_| crate::Error::InsufficientFundsToPurchaseCredits)?;

if imbalance.peek() != fee {
panic!("withdrawn balance incorrect");
}

Ok(())
}
}
/// Returns the cost for a given block credit at the current time. This can be a complex operation,
/// so it also returns the weight it consumes. (TODO: or just rely on benchmarking)
Expand All @@ -342,16 +290,54 @@ impl<T: Config> AuthorNotingHook<T::AccountId> for Pallet<T> {
_block_number: BlockNumber,
para_id: ParaId,
) -> Weight {
let total_weight = T::DbWeight::get().reads_writes(1, 1);
if Pallet::<T>::burn_free_credit_for_para(&para_id).is_err() {
let (amount_to_charge, _weight) = T::ProvideBlockProductionCost::block_cost(&para_id);
match T::Currency::withdraw(
&Self::parachain_tank(para_id),
amount_to_charge,
WithdrawReasons::FEE,
ExistenceRequirement::KeepAlive,
) {
Err(e) => log::warn!(
"Failed to withdraw credits for container chain {}: {:?}",
u32::from(para_id),
e
),
Ok(imbalance) => {
T::OnChargeForBlock::on_unbalanced(imbalance);
}
}
}

if let Err(e) = Pallet::<T>::burn_credit_for_para(&para_id) {
log::warn!(
"Failed to burn credits for container chain {}: {:?}",
u32::from(para_id),
e
);
T::WeightInfo::on_container_author_noted()
}
}

impl<T: Config> Pallet<T> {
/// Derive a derivative account ID from the paraId.
pub fn parachain_tank(para_id: ParaId) -> T::AccountId {
let entropy = (b"modlpy/serpayment", para_id).using_encoded(blake2_256);
Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
.expect("infinite length input; no invalid inputs for type; qed")
}

/// Hook to perform things on deregister
pub fn para_deregistered(para_id: ParaId) {
// Drain the para-id account from tokens
let parachain_tank_balance = T::Currency::total_balance(&Self::parachain_tank(para_id));
if !parachain_tank_balance.is_zero() {
if let Ok(imbalance) = T::Currency::withdraw(
&Self::parachain_tank(para_id),
parachain_tank_balance,
WithdrawReasons::FEE,
ExistenceRequirement::AllowDeath,
) {
// Burn for now, we might be able to pass something to do with this
drop(imbalance);
}
}

total_weight
// Clean credits
BlockProductionCredits::<T>::remove(para_id);
}
}
14 changes: 2 additions & 12 deletions pallets/services-payment/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
//! to that containerChain, by simply assigning the slot position.
use {
crate::{self as pallet_services_payment, ChargeForBlockCredit, ProvideBlockProductionCost},
crate::{self as pallet_services_payment, ProvideBlockProductionCost},
cumulus_primitives_core::ParaId,
frame_support::{
pallet_prelude::*,
Expand Down Expand Up @@ -109,7 +109,7 @@ parameter_types! {

impl pallet_services_payment::Config for Test {
type RuntimeEvent = RuntimeEvent;
type OnChargeForBlockCredit = ChargeForBlockCredit<Test>;
type OnChargeForBlock = ();
type Currency = Balances;
type ProvideBlockProductionCost = BlockProductionCost<Test>;
type MaxCreditsStored = MaxCreditsStored;
Expand Down Expand Up @@ -164,13 +164,3 @@ pub(crate) fn events() -> Vec<pallet_services_payment::Event<Test>> {
})
.collect::<Vec<_>>()
}

// This function basically just builds a genesis storage key/value store according to
// our desired mockup.
#[cfg(feature = "runtime-benchmarks")]
pub fn new_test_ext() -> sp_io::TestExternalities {
frame_system::GenesisConfig::<Test>::default()
.build_storage()
.unwrap()
.into()
}
Loading

0 comments on commit c179e9d

Please sign in to comment.