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

Girazoki change payment to tank model #392

Merged
merged 37 commits into from
Feb 5, 2024
Merged
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
056a8c5
start modifying
girazoki Jan 22, 2024
a38d9d6
adapt tests
girazoki Jan 23, 2024
cfe6a34
change traits to use onunbalanced
girazoki Jan 23, 2024
9c4ee4b
integration tests
girazoki Jan 23, 2024
705e89b
fix tests
girazoki Jan 23, 2024
307f56f
modify benches
girazoki Jan 23, 2024
eb8fa4f
fix clippy
girazoki Jan 23, 2024
eab766f
work clippy please
girazoki Jan 23, 2024
b569d88
typescript api
girazoki Jan 23, 2024
72f72e9
typescrip api flashbox
girazoki Jan 23, 2024
0f06e01
modify
girazoki Jan 23, 2024
3cc4536
adapt test
girazoki Jan 24, 2024
3f7938b
testing further
girazoki Jan 25, 2024
4a67b0f
more tests
girazoki Jan 25, 2024
8fd33bc
clippy
girazoki Jan 25, 2024
2edb516
unused variable
girazoki Jan 25, 2024
207f6b1
more unused
girazoki Jan 25, 2024
d68f8ca
Benchmark entire on_container_author_noted
girazoki Jan 25, 2024
212ed0f
fix error
girazoki Jan 25, 2024
6c77973
put payment file
girazoki Jan 26, 2024
248f553
modify purchase credits
girazoki Jan 26, 2024
c42a752
fmt
girazoki Jan 26, 2024
c54f7ca
dev tests
girazoki Jan 26, 2024
04af11e
implement burn and leave it prepared on deregister
girazoki Jan 29, 2024
742aebf
add test to see we remove money form tank on deregister
girazoki Jan 29, 2024
51556a5
move things to pallet-service-payment and rename function
girazoki Jan 29, 2024
a3049bf
make sure we are burning
girazoki Jan 29, 2024
6838e39
free credits rename
girazoki Jan 30, 2024
c9f66d2
clippy
girazoki Jan 30, 2024
3f75152
more clippy
girazoki Jan 30, 2024
00d90b2
burn free credit para
girazoki Feb 1, 2024
f5a4c4a
change to keep alive
girazoki Feb 1, 2024
0d65172
fix test
girazoki Feb 1, 2024
a5947df
free credits getter fixed
girazoki Feb 1, 2024
7fdc61b
Merge remote-tracking branch 'origin/master' into girazoki-change-pay…
girazoki Feb 5, 2024
1c1ca54
final review
girazoki Feb 5, 2024
1e0066c
remove lsat print
girazoki Feb 5, 2024
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
1 change: 1 addition & 0 deletions pallets/services-payment/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 }
50 changes: 35 additions & 15 deletions pallets/services-payment/src/benchmarks.rs
Original file line number Diff line number Diff line change
@@ -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.
@@ -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!(
@@ -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
@@ -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
@@ -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},
};

@@ -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>>;

@@ -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,
@@ -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>;

@@ -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())
@@ -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());

@@ -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)
@@ -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);
Agusrodri marked this conversation as resolved.
Show resolved Hide resolved
}
}

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
@@ -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::*,
@@ -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;
@@ -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