Skip to content

Commit b33137a

Browse files
feat: allow setting bounds for avg tft price (#427)
1 parent 6b46466 commit b33137a

File tree

6 files changed

+417
-217
lines changed

6 files changed

+417
-217
lines changed

substrate-node/node/src/chain_spec.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ pub fn development_config() -> Result<ChainSpec, String> {
117117
get_account_id_from_seed::<sr25519::Public>("Ferdie"),
118118
// TFT price pallet allow account
119119
get_account_id_from_seed::<sr25519::Public>("Alice"),
120+
// TFT price pallet min price
121+
10,
122+
// TFT price pallet max price
123+
1000,
120124
)
121125
},
122126
// Bootnodes
@@ -201,6 +205,10 @@ pub fn local_testnet_config() -> Result<ChainSpec, String> {
201205
get_account_id_from_seed::<sr25519::Public>("Ferdie"),
202206
// TFT price pallet allow account
203207
get_account_id_from_seed::<sr25519::Public>("Alice"),
208+
// TFT price pallet min price
209+
10,
210+
// TFT price pallet max price
211+
1000,
204212
)
205213
},
206214
// Bootnodes
@@ -229,6 +237,8 @@ fn testnet_genesis(
229237
bridge_validator_accounts: Vec<AccountId>,
230238
bridge_fee_account: AccountId,
231239
tft_price_allowed_account: AccountId,
240+
min_tft_price: u32,
241+
max_tft_price: u32,
232242
) -> GenesisConfig {
233243
GenesisConfig {
234244
system: SystemConfig {
@@ -316,6 +326,8 @@ fn testnet_genesis(
316326
// just some default for development
317327
tft_price_module: TFTPriceModuleConfig {
318328
allowed_origin: Some(tft_price_allowed_account),
329+
min_tft_price,
330+
max_tft_price,
319331
},
320332
}
321333
}

substrate-node/pallets/pallet-smart-contract/src/cost.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::pallet::Error;
44
use crate::types;
55
use crate::types::{Contract, ContractBillingInformation};
66
use crate::Config;
7-
use frame_support::{dispatch::DispatchErrorWithPostInfo, ensure};
7+
use frame_support::dispatch::DispatchErrorWithPostInfo;
88
use pallet_tfgrid::types as pallet_tfgrid_types;
99
use sp_runtime::Percent;
1010
use sp_runtime::SaturatedConversion;
@@ -244,10 +244,17 @@ pub fn calculate_cost_in_tft_from_musd<T: Config>(
244244
total_cost: u64,
245245
) -> Result<u64, DispatchErrorWithPostInfo> {
246246
let avg_tft_price = pallet_tft_price::AverageTftPrice::<T>::get();
247-
ensure!(avg_tft_price > 0, Error::<T>::TFTPriceValueError);
247+
248+
// Guaranty tft price will never be lower than min tft price
249+
let min_tft_price = pallet_tft_price::MinTftPrice::<T>::get();
250+
let mut tft_price = avg_tft_price.max(min_tft_price);
251+
252+
// Guaranty tft price will never be higher than max tft price
253+
let max_tft_price = pallet_tft_price::MaxTftPrice::<T>::get();
254+
tft_price = tft_price.min(max_tft_price);
248255

249256
// TFT Price is in musd
250-
let tft_price_musd = U64F64::from_num(avg_tft_price);
257+
let tft_price_musd = U64F64::from_num(tft_price);
251258

252259
// Cost is expressed in units USD, divide by 10000 to get the price in musd
253260
let total_cost_musd = U64F64::from_num(total_cost) / 10000;

substrate-node/pallets/pallet-smart-contract/src/mock.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,8 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
302302

303303
let genesis = pallet_tft_price::GenesisConfig::<TestRuntime> {
304304
allowed_origin: Some(bob()),
305+
min_tft_price: 10,
306+
max_tft_price: 1000,
305307
};
306308
genesis.assimilate_storage(&mut t).unwrap();
307309

substrate-node/pallets/pallet-tft-price/src/lib.rs

Lines changed: 58 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
/// https://substrate.dev/docs/en/knowledgebase/runtime/frame
66
use frame_support::{dispatch::DispatchResultWithPostInfo, weights::Pays};
77
use frame_system::offchain::{SendSignedTransaction, Signer};
8-
use lite_json::json::JsonValue;
98
use log;
109
use sp_runtime::offchain::{http, Duration};
1110
use sp_runtime::traits::SaturatedConversion;
@@ -26,6 +25,9 @@ const DST_ISSUER: &str = "GBOVQKJYHXRR3DX6NOX2RRYFRCUMSADGDESTDNBDS6CDVLGVESRTAC
2625
const DST_CODE: &str = "TFT";
2726
const DST_AMOUNT: u32 = 100;
2827

28+
#[cfg(test)]
29+
mod mock;
30+
2931
#[cfg(test)]
3032
mod tests;
3133

@@ -91,6 +93,8 @@ pub mod pallet {
9193
PriceStored(u32),
9294
OffchainWorkerExecuted(T::AccountId),
9395
AveragePriceStored(u32),
96+
AveragePriceIsAboveMaxPrice(u32, u32),
97+
AveragePriceIsBelowMinPrice(u32, u32),
9498
}
9599

96100
#[pallet::error]
@@ -99,6 +103,8 @@ pub mod pallet {
99103
OffchainSignedTxError,
100104
NoLocalAcctForSigning,
101105
AccountUnauthorizedToSetPrice,
106+
MaxPriceBelowMinPriceError,
107+
MinPriceAboveMaxPriceError,
102108
}
103109

104110
#[pallet::pallet]
@@ -130,6 +136,14 @@ pub mod pallet {
130136
#[pallet::getter(fn allowed_origin)]
131137
pub type AllowedOrigin<T: Config> = StorageValue<_, T::AccountId, OptionQuery>;
132138

139+
#[pallet::storage]
140+
#[pallet::getter(fn min_tft_price)]
141+
pub type MinTftPrice<T> = StorageValue<_, u32, ValueQuery>;
142+
143+
#[pallet::storage]
144+
#[pallet::getter(fn max_tft_price)]
145+
pub type MaxTftPrice<T> = StorageValue<_, u32, ValueQuery>;
146+
133147
#[pallet::call]
134148
impl<T: Config> Pallet<T> {
135149
#[pallet::weight(100_000_000 + T::DbWeight::get().writes(1) + T::DbWeight::get().reads(1))]
@@ -158,6 +172,28 @@ pub mod pallet {
158172
AllowedOrigin::<T>::set(Some(target));
159173
Ok(().into())
160174
}
175+
176+
#[pallet::weight(100_000_000 + T::DbWeight::get().writes(1) + T::DbWeight::get().reads(1))]
177+
pub fn set_min_tft_price(origin: OriginFor<T>, price: u32) -> DispatchResultWithPostInfo {
178+
T::RestrictedOrigin::ensure_origin(origin)?;
179+
ensure!(
180+
price < MaxTftPrice::<T>::get(),
181+
Error::<T>::MinPriceAboveMaxPriceError
182+
);
183+
MinTftPrice::<T>::put(price);
184+
Ok(().into())
185+
}
186+
187+
#[pallet::weight(100_000_000 + T::DbWeight::get().writes(1) + T::DbWeight::get().reads(1))]
188+
pub fn set_max_tft_price(origin: OriginFor<T>, price: u32) -> DispatchResultWithPostInfo {
189+
T::RestrictedOrigin::ensure_origin(origin)?;
190+
ensure!(
191+
price > MinTftPrice::<T>::get(),
192+
Error::<T>::MaxPriceBelowMinPriceError
193+
);
194+
MaxTftPrice::<T>::put(price);
195+
Ok(().into())
196+
}
161197
}
162198

163199
#[pallet::hooks]
@@ -173,13 +209,17 @@ pub mod pallet {
173209
#[pallet::genesis_config]
174210
pub struct GenesisConfig<T: Config> {
175211
pub allowed_origin: Option<T::AccountId>,
212+
pub min_tft_price: u32,
213+
pub max_tft_price: u32,
176214
}
177215

178216
#[cfg(feature = "std")]
179217
impl<T: Config> Default for GenesisConfig<T> {
180218
fn default() -> Self {
181219
Self {
182220
allowed_origin: None,
221+
min_tft_price: 10,
222+
max_tft_price: 1000,
183223
}
184224
}
185225
}
@@ -188,6 +228,8 @@ pub mod pallet {
188228
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
189229
fn build(&self) {
190230
AllowedOrigin::<T>::set(self.allowed_origin.clone());
231+
MinTftPrice::<T>::put(self.min_tft_price);
232+
MaxTftPrice::<T>::put(self.max_tft_price);
191233
}
192234
}
193235
}
@@ -212,6 +254,18 @@ impl<T: Config> Pallet<T> {
212254
AverageTftPrice::<T>::put(average);
213255
Self::deposit_event(Event::AveragePriceStored(average));
214256

257+
let min = Self::min_tft_price();
258+
if average < min {
259+
log::info!("average price {:?} is below min price {:?} !", average, min);
260+
Self::deposit_event(Event::AveragePriceIsBelowMinPrice(average, min));
261+
}
262+
263+
let max = Self::max_tft_price();
264+
if average > max {
265+
log::info!("average price {:?} is above max price {:?} !", average, max);
266+
Self::deposit_event(Event::AveragePriceIsAboveMaxPrice(average, max));
267+
}
268+
215269
Ok(Pays::No.into())
216270
}
217271

@@ -305,28 +359,6 @@ impl<T: Config> Pallet<T> {
305359
return Err(<Error<T>>::OffchainSignedTxError);
306360
}
307361

308-
/// Parse the price from the given JSON string using `lite-json`.
309-
///
310-
/// Returns `None` when parsing failed or `Some(price in mUSD)` when parsing is successful.
311-
pub fn parse_price(price_str: &str) -> Option<u32> {
312-
let val = lite_json::parse_json(price_str);
313-
let price = match val.ok()? {
314-
JsonValue::Object(obj) => {
315-
let (_, v) = obj
316-
.into_iter()
317-
.find(|(k, _)| k.iter().copied().eq("USD".chars()))?;
318-
match v {
319-
JsonValue::Number(number) => number,
320-
_ => return None,
321-
}
322-
}
323-
_ => return None,
324-
};
325-
326-
let exp = price.fraction_length.saturating_sub(3);
327-
Some(price.integer as u32 * 1000 + (price.fraction / 10_u64.pow(exp)) as u32)
328-
}
329-
330362
/// Parse the lowest price from the given JSON string using `serde_json`.
331363
///
332364
/// Returns `None` when parsing failed or `Some(price in mUSD)` when parsing is successful.
@@ -366,8 +398,9 @@ impl<T: Config> Pallet<T> {
366398
}
367399

368400
fn calc_avg() -> u32 {
369-
let queue = Self::queue_transient();
370-
let items = queue.get_all_values();
401+
let items: Vec<u32> = TftPriceHistory::<T>::iter_values()
402+
.filter(|val| val > &0_u32)
403+
.collect();
371404
items.iter().fold(0_u32, |a, b| a.saturating_add(*b)) / items.len() as u32
372405
}
373406
}

0 commit comments

Comments
 (0)