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

refactor: Billing refactor #992

Merged
merged 83 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
b5538c3
refactor: remove-is-next-author
sameh-farouk Jul 18, 2024
5c2acbd
initial balance lock fix
sameh-farouk Jul 19, 2024
4754d5a
temp changes to decide later
sameh-farouk Jul 24, 2024
d8737af
wip
sameh-farouk Jul 28, 2024
36b7984
refactor-billing-logic
sameh-farouk Jul 30, 2024
150f006
handle grace for node contracts under rent-contract
sameh-farouk Jul 30, 2024
dc2a341
fix tests
sameh-farouk Jul 30, 2024
bd2331d
Adjust other tests
sameh-farouk Jul 31, 2024
2094436
fix benchmarks
sameh-farouk Jul 31, 2024
2274df4
claen up the code a bit
sameh-farouk Aug 1, 2024
ded7466
Fix cost of certified capacity
sameh-farouk Aug 2, 2024
47f1b72
Adjust benchmarking
sameh-farouk Aug 2, 2024
14cd8ed
clean comments
sameh-farouk Aug 2, 2024
3dcc930
define a custom SignedExtension for bill_contract_for_block
sameh-farouk Aug 13, 2024
77b3b4a
Adjusting some logs
sameh-farouk Aug 13, 2024
6564009
Adjust logs
sameh-farouk Aug 13, 2024
f5acea5
Add new billing events to go client
sameh-farouk Aug 13, 2024
7d03ead
Add comments
sameh-farouk Aug 14, 2024
6d60e78
fmt: format code
sameh-farouk Aug 14, 2024
a94a7da
Update comment
sameh-farouk Aug 14, 2024
38842d6
chore: update benchmark `weights.rs` file for all pallets
github-actions[bot] Aug 14, 2024
b237b6b
Fix typo
sameh-farouk Aug 15, 2024
4422f08
Extract is_validator into a function
sameh-farouk Aug 15, 2024
7de554d
Merge branch 'development-contracts-billing-refactor' of https://gith…
sameh-farouk Aug 15, 2024
a64f5b7
Change partial_billed_amount to partially_billed_amount
sameh-farouk Aug 15, 2024
0c3f136
fix govet linter
sameh-farouk Aug 15, 2024
cb10db5
fix typo
sameh-farouk Aug 18, 2024
348cfb9
fix typos
sameh-farouk Aug 18, 2024
72d2202
Correct naming convention for ContractPaymentState API
sameh-farouk Aug 18, 2024
44279ff
change ContractPaymentOverdrawn event's field name
sameh-farouk Aug 18, 2024
f8a2073
fix json in contract_events
sameh-farouk Aug 18, 2024
2180827
make ContractPaymentOverdrawn.overdraft represnt new overdrawn amount…
sameh-farouk Aug 18, 2024
4b2d2b0
change transfer_reserved to fail if cant repatriate reserved
sameh-farouk Aug 18, 2024
09cbdd7
Add new ADR
sameh-farouk Aug 18, 2024
e07e8cd
adding unit tests for ContractPaymentState methods
sameh-farouk Aug 18, 2024
f082a8c
fix integration tests
sameh-farouk Aug 18, 2024
927a599
fix ADR formating
sameh-farouk Aug 18, 2024
56a970e
rename has_reserved_amount() to has_reserve()
sameh-farouk Aug 18, 2024
a1af14a
Hardening transfer_reserved
sameh-farouk Aug 18, 2024
ebd6471
Adjsut tests for transfer_reserved changes (slash and deposit events)
sameh-farouk Aug 18, 2024
e200715
Fix cycles count
sameh-farouk Aug 18, 2024
544cbf1
rename some tests
sameh-farouk Aug 18, 2024
d824e59
Add more cases to test_settle_partial_overdraft untit test
sameh-farouk Aug 19, 2024
ba1d0aa
Use EXISTENTIAL_DEPOSIT const inplace of the value
sameh-farouk Aug 19, 2024
6b58587
rename get_reserved to get_reserve
sameh-farouk Aug 19, 2024
8032c96
Use EXISTENTIAL_DEPOSIT const inplace of the value
sameh-farouk Aug 19, 2024
af76619
Optimize transfer_reserved to reduce overhead and improve efficiency
sameh-farouk Aug 20, 2024
7cb0e3e
Add a test to verify that unbilled NU usage is reset when billing a c…
sameh-farouk Aug 20, 2024
0276f86
fix typo
sameh-farouk Aug 20, 2024
74c42bb
update validate_distribution_rewards and resduce duplicated code in t…
sameh-farouk Aug 20, 2024
2d9d644
Remove duplicated code
sameh-farouk Aug 20, 2024
09867df
write payment_state to storage early during migration
sameh-farouk Aug 21, 2024
2af204e
Add new tests and remove obsolete ones
sameh-farouk Aug 21, 2024
154b10f
Write contract payment state to storage during migration
sameh-farouk Aug 21, 2024
3ac6381
replace the lazy migration with a regular storage migration
sameh-farouk Aug 21, 2024
04fd0e7
Update pallet smart contract migration v12
sameh-farouk Aug 21, 2024
37d41db
make sure reservation works if a lock exists on user balance
sameh-farouk Aug 22, 2024
145c577
Avoid unnecessary recalculation of reservable balance
sameh-farouk Aug 22, 2024
9abeb0c
add more logs in billing and defensive check
sameh-farouk Aug 22, 2024
eea2376
fmt: just formating
sameh-farouk Aug 22, 2024
9d51049
fix typos
sameh-farouk Aug 22, 2024
247bc45
rename few functions to make code easier to understand
sameh-farouk Aug 22, 2024
b00b9dd
Ensure advancement of billing cycles in tests is more consistent
sameh-farouk Aug 23, 2024
71e2663
chore: update benchmark `weights.rs` file for all pallets
github-actions[bot] Aug 23, 2024
7361e66
Adjust the distribution of utilization revenue
sameh-farouk Aug 25, 2024
83ccaa4
Adjust imports for polkadot-sdk v1.1.0
sameh-farouk Aug 26, 2024
4251895
Removing solution provider integration test
sameh-farouk Aug 26, 2024
2df78b4
chore: update benchmark `weights.rs` file for all pallets
github-actions[bot] Aug 26, 2024
bfbe519
Merge branch 'development' into development-contracts-billing-refactor
sameh-farouk Aug 26, 2024
b443114
chore: update benchmark `weights.rs` file for all pallets
github-actions[bot] Aug 26, 2024
db42dce
Fixing a typo and adding a comment for clarity
sameh-farouk Aug 26, 2024
c62a30f
Leaving the solution provider test for now and just removing the bill…
sameh-farouk Aug 26, 2024
24e3ac9
Merge branch 'development-contracts-billing-refactor' of https://gith…
sameh-farouk Aug 26, 2024
8585901
chore: update benchmark `weights.rs` file for all pallets
github-actions[bot] Aug 26, 2024
efd76af
Switch storageKind from ValueQuery to OptionQuery for ContractPayment…
sameh-farouk Aug 26, 2024
e19934a
Minor refactor for the smart contract pallet storage migration v12
sameh-farouk Aug 26, 2024
dea3dc5
Improve the migration performance
sameh-farouk Aug 27, 2024
0a52b08
Merge branch 'development-contracts-billing-refactor' of https://gith…
sameh-farouk Aug 27, 2024
deee291
Fix benchmarks
sameh-farouk Aug 27, 2024
4a51902
Avoiding unwrap
sameh-farouk Aug 27, 2024
d48619b
Adding ContractPaymentStateNotExists error to tfchain client
sameh-farouk Aug 27, 2024
24b712f
Weights correction in smart-contract-pallet v12 migration
sameh-farouk Aug 27, 2024
b70501f
chore: update benchmark `weights.rs` file for all pallets
github-actions[bot] Aug 27, 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
4 changes: 3 additions & 1 deletion substrate-node/node/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use sp_core::{Encode, Pair};
use sp_inherents::{InherentData, InherentDataProvider};
use sp_keyring::Sr25519Keyring;
use sp_runtime::{OpaqueExtrinsic, SaturatedConversion};
use tfchain_runtime as runtime;
use tfchain_runtime::{self as runtime, pallet_smart_contract};

use std::{sync::Arc, time::Duration};

Expand Down Expand Up @@ -132,6 +132,7 @@ pub fn create_benchmark_extrinsic(
frame_system::CheckNonce::<runtime::Runtime>::from(nonce),
frame_system::CheckWeight::<runtime::Runtime>::new(),
pallet_transaction_payment::ChargeTransactionPayment::<runtime::Runtime>::from(0),
pallet_smart_contract::types::ContractIdProvides::<runtime::Runtime>::new(),
);

let raw_payload = runtime::SignedPayload::from_raw(
Expand All @@ -146,6 +147,7 @@ pub fn create_benchmark_extrinsic(
(),
(),
(),
(),
),
);
let signature = raw_payload.using_encoded(|e| sender.sign(e));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,11 +305,11 @@ benchmarks! {

let contract = SmartContractModule::<T>::contracts(contract_id).unwrap();
// Get contract cost before billing to take into account nu
let (cost, discount_level) = contract.calculate_contract_cost_tft(balance_init_amount, elapsed_seconds).unwrap();
let (cost, discount_level) = contract.calculate_contract_cost_tft(balance_init_amount, elapsed_seconds, None).unwrap();
}: _(RawOrigin::Signed(farmer), contract_id)
verify {
let lock = SmartContractModule::<T>::contract_number_of_cylces_billed(contract_id);
assert_eq!(lock.amount_locked, cost);
let contract_payment_state = SmartContractModule::<T>::contract_payment_state(contract_id);
assert_eq!(contract_payment_state.standard_reserved, cost);
let contract_bill = types::ContractBill {
contract_id,
timestamp: SmartContractModule::<T>::get_current_timestamp_in_secs(),
Expand Down
1,070 changes: 568 additions & 502 deletions substrate-node/pallets/pallet-smart-contract/src/billing.rs

Large diffs are not rendered by default.

36 changes: 16 additions & 20 deletions substrate-node/pallets/pallet-smart-contract/src/cost.rs
sameh-farouk marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use tfchain_support::{
types::NodeCertification,
};

pub const CERTIFIED_INCREASE_FACTOR: f64 = 1.25;

impl<T: Config> types::Contract<T> {
pub fn get_billing_info(&self) -> types::ContractBillingInformation {
pallet::ContractBillingInformationByID::<T>::get(self.contract_id)
Expand All @@ -18,10 +20,10 @@ impl<T: Config> types::Contract<T> {
&self,
balance: BalanceOf<T>,
seconds_elapsed: u64,
certification_type: Option<NodeCertification>,
) -> Result<(BalanceOf<T>, types::DiscountLevel), DispatchErrorWithPostInfo> {
// Fetch the default pricing policy and certification type
let pricing_policy = pallet_tfgrid::PricingPolicies::<T>::get(1).unwrap();
let certification_type = NodeCertification::Diy;

// Calculate the cost for a contract, can be any of:
// - NodeContract
Expand All @@ -30,12 +32,11 @@ impl<T: Config> types::Contract<T> {
let total_cost =
self.calculate_contract_cost_units_usd(&pricing_policy, seconds_elapsed)?;

// If cost is 0, reinsert to be billed at next interval
if total_cost == 0 {
return Ok((BalanceOf::<T>::zero(), types::DiscountLevel::None));
}

let total_cost_tft_64 = calculate_cost_in_tft_from_units_usd::<T>(total_cost)?;
let total_cost_tft_64 = calculate_cost_in_tft_from_units_usd::<T>(total_cost);

// Calculate the amount due and discount received based on the total_cost amount due
let (amount_due, discount_received) = calculate_discount_tft::<T>(
Expand Down Expand Up @@ -114,18 +115,14 @@ impl<T: Config> types::Contract<T> {
}

// Calculates the cost of extra fee for a dedicated node in TFT.
pub fn calculate_extra_fee_cost_tft(
&self,
node_id: u32,
seconds_elapsed: u64,
) -> Result<BalanceOf<T>, DispatchErrorWithPostInfo> {
pub fn calculate_extra_fee_cost_tft(&self, node_id: u32, seconds_elapsed: u64) -> BalanceOf<T> {
let cost = calculate_extra_fee_cost_units_usd::<T>(node_id, seconds_elapsed);
if cost == 0 {
return Ok(BalanceOf::<T>::zero());
return BalanceOf::<T>::zero();
}
let cost_tft = calculate_cost_in_tft_from_units_usd::<T>(cost)?;
let cost_tft = calculate_cost_in_tft_from_units_usd::<T>(cost);

Ok(BalanceOf::<T>::saturated_from(cost_tft))
BalanceOf::<T>::saturated_from(cost_tft)
}
}

Expand Down Expand Up @@ -178,7 +175,7 @@ impl types::ServiceContract {
}

// Calculate the cost in TFT for service contract
let total_cost_tft_64 = calculate_cost_in_tft_from_units_usd::<T>(total_cost)?;
let total_cost_tft_64 = calculate_cost_in_tft_from_units_usd::<T>(total_cost);

// convert to balance object
let amount_due: BalanceOf<T> = BalanceOf::<T>::saturated_from(total_cost_tft_64);
Expand Down Expand Up @@ -297,7 +294,7 @@ pub fn calculate_discount_tft<T: Config>(
amount_due: u64,
seconds_elapsed: u64,
balance: BalanceOf<T>,
certification_type: NodeCertification,
certification_type: Option<NodeCertification>,
) -> (BalanceOf<T>, types::DiscountLevel) {
if amount_due == 0 {
return (BalanceOf::<T>::zero(), types::DiscountLevel::None);
Expand Down Expand Up @@ -329,8 +326,9 @@ pub fn calculate_discount_tft<T: Config>(
let mut amount_due = U64F64::from_num(amount_due) * discount_received.price_multiplier();

// Certified capacity costs 25% more
if certification_type == NodeCertification::Certified {
amount_due = amount_due * U64F64::from_num(1.25);
if let Some(NodeCertification::Certified) = certification_type {
log::debug!("Certified node detected, increasing amount due by 25%");
amount_due *= U64F64::from_num(CERTIFIED_INCREASE_FACTOR);
}

// convert to balance object
Expand All @@ -340,9 +338,7 @@ pub fn calculate_discount_tft<T: Config>(
(amount_due, discount_received)
}

pub fn calculate_cost_in_tft_from_units_usd<T: Config>(
cost_units_usd: u64,
) -> Result<u64, DispatchErrorWithPostInfo> {
pub fn calculate_cost_in_tft_from_units_usd<T: Config>(cost_units_usd: u64) -> u64 {
let avg_tft_price = pallet_tft_price::AverageTftPrice::<T>::get();

// Guarantee tft price will never be lower than min tft price
Expand All @@ -360,7 +356,7 @@ pub fn calculate_cost_in_tft_from_units_usd<T: Config>(
let cost_tft = U64F64::from_num(cost_units_usd) / U64F64::from_num(tft_price_units_usd);

// Multiply by the chain precision (7 decimals)
Ok((cost_tft * U64F64::from_num(10u64.pow(7)))
(cost_tft * U64F64::from_num(10u64.pow(7)))
.round()
.to_num::<u64>())
.to_num::<u64>()
}
37 changes: 24 additions & 13 deletions substrate-node/pallets/pallet-smart-contract/src/grid_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,9 @@ impl<T: Config> Pallet<T> {
ContractID::<T>::put(id);

let now = Self::get_current_timestamp_in_secs();
let mut contract_lock = types::ContractLock::default();
contract_lock.lock_updated = now;
ContractLock::<T>::insert(id, contract_lock);
let mut contract_payment_state = types::ContractPaymentState::default();
contract_payment_state.last_updated_seconds = now;
ContractPaymentState::<T>::insert(id, contract_payment_state);

Ok(contract)
}
Expand Down Expand Up @@ -321,14 +321,16 @@ impl<T: Config> Pallet<T> {
}

Self::update_contract_state(contract, &types::ContractState::Deleted(cause))?;
log::debug!("Billing for contract {} kicked in due to cancel request", contract.contract_id);
Self::bill_contract(contract.contract_id)?;

Ok(().into())
}

pub fn remove_contract(contract_id: u64) -> DispatchResultWithPostInfo {
let contract = Contracts::<T>::get(contract_id).ok_or(Error::<T>::ContractNotExists)?;


log::debug!("removing contract {}", contract_id);
match contract.contract_type.clone() {
types::ContractData::NodeContract(mut node_contract) => {
if node_contract.public_ips > 0 {
Expand Down Expand Up @@ -370,13 +372,12 @@ impl<T: Config> Pallet<T> {
}
};

log::debug!("removing contract");
Contracts::<T>::remove(contract_id);
ContractLock::<T>::remove(contract_id);

ContractPaymentState::<T>::remove(contract_id);
// Clean up contract from billing loop
// This is the only place it should be done
log::debug!("cleaning up deleted contract from billing loop");
log::debug!("cleaning up deleted contract {} from billing loop", contract_id);
Self::remove_contract_from_billing_loop(contract_id)?;

Ok(().into())
Expand Down Expand Up @@ -404,7 +405,7 @@ impl<T: Config> Pallet<T> {
contract.state = state.clone();
Contracts::<T>::insert(&contract.contract_id, contract.clone());

// if the contract is a name contract, nothing to do left here
// if the contract is a name or rent contract, nothing to do left here
match contract.contract_type {
types::ContractData::NameContract(_) => return Ok(().into()),
types::ContractData::RentContract(_) => return Ok(().into()),
Expand Down Expand Up @@ -702,7 +703,12 @@ impl<T: Config> ChangeNode<LocationOf<T>, InterfaceOf<T>, SerialNumberOf<T>> for
&mut contract,
&types::ContractState::Deleted(types::Cause::CanceledByUser),
);
let _ = Self::bill_contract(node_contract_id);
log::debug!("Billing for node contract {} kicked in due to node deletion", contract.contract_id);
let res = Self::bill_contract(node_contract_id);
if let Err(e) = res {
log::error!("error in node_deleted hook while billing contract {:?}: {:?}. Contract could be in dirty state", node_contract_id, e);
}

}
}

Expand All @@ -714,7 +720,11 @@ impl<T: Config> ChangeNode<LocationOf<T>, InterfaceOf<T>, SerialNumberOf<T>> for
&mut contract,
&types::ContractState::Deleted(types::Cause::CanceledByUser),
);
let _ = Self::bill_contract(contract.contract_id);
log::debug!("Billing for rent contract {} kicked in due to node deletion", contract.contract_id);
let res = Self::bill_contract(contract.contract_id);
if let Err(e) = res {
log::error!("error in node_deleted hook while billing contract id {:?}: {:?}. Contract could be in dirty state", rc_id, e);
}
}
}
}
Expand All @@ -725,10 +735,11 @@ impl<T: Config> ChangeNode<LocationOf<T>, InterfaceOf<T>, SerialNumberOf<T>> for
let node_power = pallet_tfgrid::NodePower::<T>::get(node.id);
if !node_power.is_standby() {
if let Some(rc_id) = ActiveRentContractForNode::<T>::get(node.id) {
let mut contract_lock = ContractLock::<T>::get(rc_id);
let mut contract_payment_state = ContractPaymentState::<T>::get(rc_id);
let now = Self::get_current_timestamp_in_secs();
contract_lock.lock_updated = now;
ContractLock::<T>::insert(rc_id, &contract_lock);
contract_payment_state.last_updated_seconds = now;
ContractPaymentState::<T>::insert(rc_id, &contract_payment_state);
log::debug!("Rented node {} is back up, updated contract contract_payment_state for contract {}", node.id, rc_id);
}
}
}
Expand Down
65 changes: 60 additions & 5 deletions substrate-node/pallets/pallet-smart-contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ pub mod pallet {
use super::*;
use frame_support::{
pallet_prelude::*,
traits::{Currency, Get, Hooks, LockIdentifier, LockableCurrency, OnUnbalanced},
traits::{Currency, Get, Hooks, LockIdentifier, LockableCurrency, OnUnbalanced, ReservableCurrency, tokens::fungible::*},
};
use frame_system::{
self as system, ensure_signed,
Expand All @@ -72,6 +72,7 @@ pub mod pallet {
};
use parity_scale_codec::FullCodec;
use sp_core::H256;
use sp_runtime::traits::Convert;
use sp_std::{
convert::{TryFrom, TryInto},
fmt::Debug,
Expand Down Expand Up @@ -195,6 +196,11 @@ pub mod pallet {
#[pallet::getter(fn dedicated_nodes_extra_fee)]
pub type DedicatedNodesExtraFee<T> = StorageMap<_, Blake2_128Concat, u32, u64, ValueQuery>;

#[pallet::storage]
#[pallet::getter(fn contract_payment_state)]
pub type ContractPaymentState<T: Config> =
StorageMap<_, Blake2_128Concat, u64, types::ContractPaymentState<BalanceOf<T>>, ValueQuery>;

#[pallet::config]
pub trait Config:
CreateSignedTransaction<Call<Self>>
Expand All @@ -207,7 +213,7 @@ pub mod pallet {
+ pallet_session::Config
{
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type Currency: LockableCurrency<Self::AccountId>;
type Currency: LockableCurrency<Self::AccountId> + ReservableCurrency<Self::AccountId> + InspectHold<Self::AccountId> + Inspect<Self::AccountId>;
/// Handler for the unbalanced decrement when slashing (burning collateral)
type Burn: OnUnbalanced<NegativeImbalanceOf<Self>>;
type StakingPoolAccount: Get<Self::AccountId>;
Expand Down Expand Up @@ -289,14 +295,14 @@ pub mod pallet {
RentContractCanceled {
contract_id: u64,
},
/// A Contract grace period is triggered
/// A Contract grace period is triggered due to overdarfted
sameh-farouk marked this conversation as resolved.
Show resolved Hide resolved
ContractGracePeriodStarted {
contract_id: u64,
node_id: u32,
twin_id: u32,
block_number: u64,
},
/// A Contract grace period was ended
/// A Contract grace period was ended due to overdarfted being settled
ContractGracePeriodEnded {
contract_id: u64,
node_id: u32,
Expand Down Expand Up @@ -328,6 +334,28 @@ pub mod pallet {
node_id: u32,
extra_fee: u64,
},
// A rent contract is waived due to node being in standby
RentWaived {
contract_id: u64,
},
// A Contract grace Period is elapsed
ContractGracePeriodElapsed {
contract_id: u64,
grace_period: u64,
},
// Overdafted incurred
ContractPaymentOverdrafted {
contract_id: u64,
timestamp: u64,
partial_billed_amount: BalanceOf<T>, // amount billed
overdrafted_amount: BalanceOf<T>, // amount overdrafted
},
// RewardDistributed
RewardDistributed {
contract_id: u64,
standard_rewards: BalanceOf<T>,
additional_rewards: BalanceOf<T>,
},
}

#[pallet::error]
Expand Down Expand Up @@ -516,7 +544,34 @@ pub mod pallet {
contract_id: u64,
) -> DispatchResultWithPostInfo {
let _account_id = ensure_signed(origin)?;
Self::bill_contract(contract_id)
let validators = pallet_session::Pallet::<T>::validators();
let is_validator =
<T as pallet_session::Config>::ValidatorIdOf::convert(_account_id.clone())
.map_or(false, |validator_id| validators.contains(&validator_id));

let res = Self::bill_contract(contract_id);

let pays: Pays = if is_validator {
log::debug!("validator is exempt from fees");
// Exempt fees for validators
Pays::No.into()
} else {
log::debug!("caller is not exempt from fees");
Pays::Yes.into()
};

match res {
Ok(mut info) => {
log::info!("successfully billed contract with id {:?}", contract_id,);
info.pays_fee = pays;
Ok(info)
}
Err(mut info) => {
log::info!("failed to bill contract with id {:?}", contract_id);
info.post_info.pays_fee = pays;
Err(info)
}
}
}

#[pallet::call_index(11)]
Expand Down
Loading
Loading