1use super::config::{AccountCreationConfig, RuntimeConfig};
2use crate::config::{BandwidthSchedulerConfig, CongestionControlConfig, WitnessConfig};
3use crate::cost::{
4 ActionCosts, ExtCostsConfig, Fee, ParameterCost, RuntimeFeesConfig, StorageUsageConfig,
5};
6use crate::parameter::{FeeParameter, Parameter};
7use crate::vm::VMKind;
8use crate::vm::{Config, StorageGetMode};
9use near_primitives_core::account::id::ParseAccountError;
10use near_primitives_core::types::AccountId;
11use num_rational::Rational32;
12use std::collections::BTreeMap;
13use std::sync::Arc;
14
15#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq)]
17#[serde(untagged)]
18pub(crate) enum ParameterValue {
19 U64(u64),
20 Rational { numerator: i32, denominator: i32 },
21 ParameterCost { gas: u64, compute: u64 },
22 Fee { send_sir: u64, send_not_sir: u64, execution: u64 },
23 String(String),
27 Flag(bool),
28}
29
30#[derive(thiserror::Error, Debug)]
31pub(crate) enum ValueConversionError {
32 #[error("expected a value of type `{0}`, but could not parse it from `{1:?}`")]
33 ParseType(&'static str, ParameterValue),
34
35 #[error("expected an integer of type `{1}` but could not parse it from `{2:?}`")]
36 ParseInt(#[source] std::num::ParseIntError, &'static str, ParameterValue),
37
38 #[error("expected an integer of type `{1}` but could not parse it from `{2:?}`")]
39 TryFromInt(#[source] std::num::TryFromIntError, &'static str, ParameterValue),
40
41 #[error("expected an account id, but could not parse it from `{1}`")]
42 ParseAccountId(#[source] ParseAccountError, String),
43
44 #[error("expected a VM kind, but could not parse it from `{1}`")]
45 ParseVmKind(#[source] strum::ParseError, String),
46}
47
48macro_rules! implement_conversion_to {
49 ($($ty: ty),*) => {
50 $(impl TryFrom<&ParameterValue> for $ty {
51 type Error = ValueConversionError;
52 fn try_from(value: &ParameterValue) -> Result<Self, Self::Error> {
53 match value {
54 ParameterValue::U64(v) => <$ty>::try_from(*v).map_err(|err| {
55 ValueConversionError::TryFromInt(
56 err.into(),
57 std::any::type_name::<$ty>(),
58 value.clone(),
59 )
60 }),
61 _ => Err(ValueConversionError::ParseType(
62 std::any::type_name::<$ty>(), value.clone()
63 )),
64 }
65 }
66 })*
67 }
68}
69
70implement_conversion_to!(u64, u32, u16, u8, i64, i32, i16, i8, usize, isize);
71
72impl TryFrom<&ParameterValue> for u128 {
73 type Error = ValueConversionError;
74
75 fn try_from(value: &ParameterValue) -> Result<Self, Self::Error> {
76 match value {
77 ParameterValue::U64(v) => Ok(u128::from(*v)),
78 ParameterValue::String(s) => s.parse().map_err(|err| {
79 ValueConversionError::ParseInt(err, std::any::type_name::<u128>(), value.clone())
80 }),
81 _ => Err(ValueConversionError::ParseType(std::any::type_name::<u128>(), value.clone())),
82 }
83 }
84}
85
86impl TryFrom<&ParameterValue> for bool {
87 type Error = ValueConversionError;
88
89 fn try_from(value: &ParameterValue) -> Result<Self, Self::Error> {
90 match value {
91 ParameterValue::Flag(b) => Ok(*b),
92 ParameterValue::String(s) => match &**s {
93 "true" => Ok(true),
94 "false" => Ok(false),
95 _ => Err(ValueConversionError::ParseType("bool", value.clone())),
96 },
97 _ => Err(ValueConversionError::ParseType("bool", value.clone())),
98 }
99 }
100}
101
102impl TryFrom<&ParameterValue> for Rational32 {
103 type Error = ValueConversionError;
104
105 fn try_from(value: &ParameterValue) -> Result<Self, Self::Error> {
106 match value {
107 &ParameterValue::Rational { numerator, denominator } => {
108 Ok(Rational32::new(numerator, denominator))
109 }
110 _ => Err(ValueConversionError::ParseType(
111 std::any::type_name::<Rational32>(),
112 value.clone(),
113 )),
114 }
115 }
116}
117
118impl TryFrom<&ParameterValue> for ParameterCost {
119 type Error = ValueConversionError;
120
121 fn try_from(value: &ParameterValue) -> Result<Self, Self::Error> {
122 match value {
123 ParameterValue::ParameterCost { gas, compute } => {
124 Ok(ParameterCost { gas: *gas, compute: *compute })
125 }
126 &ParameterValue::U64(v) => Ok(ParameterCost { gas: v, compute: v }),
128 _ => Err(ValueConversionError::ParseType(
129 std::any::type_name::<ParameterCost>(),
130 value.clone(),
131 )),
132 }
133 }
134}
135
136impl TryFrom<&ParameterValue> for Fee {
137 type Error = ValueConversionError;
138
139 fn try_from(value: &ParameterValue) -> Result<Self, Self::Error> {
140 match value {
141 &ParameterValue::Fee { send_sir, send_not_sir, execution } => {
142 Ok(Fee { send_sir, send_not_sir, execution })
143 }
144 _ => Err(ValueConversionError::ParseType(std::any::type_name::<Fee>(), value.clone())),
145 }
146 }
147}
148
149impl<'a> TryFrom<&'a ParameterValue> for &'a str {
150 type Error = ValueConversionError;
151
152 fn try_from(value: &'a ParameterValue) -> Result<Self, Self::Error> {
153 match value {
154 ParameterValue::String(v) => Ok(v),
155 _ => {
156 Err(ValueConversionError::ParseType(std::any::type_name::<String>(), value.clone()))
157 }
158 }
159 }
160}
161
162impl TryFrom<&ParameterValue> for AccountId {
163 type Error = ValueConversionError;
164
165 fn try_from(value: &ParameterValue) -> Result<Self, Self::Error> {
166 let value: &str = value.try_into()?;
167 value.parse().map_err(|err| ValueConversionError::ParseAccountId(err, value.to_string()))
168 }
169}
170
171impl TryFrom<&ParameterValue> for VMKind {
172 type Error = ValueConversionError;
173
174 fn try_from(value: &ParameterValue) -> Result<Self, Self::Error> {
175 match value {
176 ParameterValue::String(v) => v
177 .parse()
178 .map(|v: VMKind| v.replace_with_wasmtime_if_unsupported())
179 .map_err(|e| ValueConversionError::ParseVmKind(e, value.to_string())),
180 _ => {
181 Err(ValueConversionError::ParseType(std::any::type_name::<VMKind>(), value.clone()))
182 }
183 }
184 }
185}
186
187fn format_number(mut n: u64) -> String {
188 let mut parts = Vec::new();
189 while n >= 1000 {
190 parts.push(format!("{:03?}", n % 1000));
191 n /= 1000;
192 }
193 parts.push(n.to_string());
194 parts.reverse();
195 parts.join("_")
196}
197
198impl core::fmt::Display for ParameterValue {
199 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
200 match self {
201 ParameterValue::U64(v) => write!(f, "{:>20}", format_number(*v)),
202 ParameterValue::Rational { numerator, denominator } => {
203 write!(f, "{numerator} / {denominator}")
204 }
205 ParameterValue::ParameterCost { gas, compute } => {
206 write!(f, "{:>20}, compute: {:>20}", format_number(*gas), format_number(*compute))
207 }
208 ParameterValue::Fee { send_sir, send_not_sir, execution } => {
209 write!(
210 f,
211 r#"
212- send_sir: {:>20}
213- send_not_sir: {:>20}
214- execution: {:>20}"#,
215 format_number(*send_sir),
216 format_number(*send_not_sir),
217 format_number(*execution)
218 )
219 }
220 ParameterValue::String(v) => write!(f, "{v}"),
221 ParameterValue::Flag(b) => write!(f, "{b:?}"),
222 }
223 }
224}
225
226pub(crate) struct ParameterTable {
227 parameters: BTreeMap<Parameter, ParameterValue>,
228}
229
230impl core::fmt::Display for ParameterTable {
233 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
234 for (key, value) in &self.parameters {
235 write!(f, "{key:40}{value}\n")?
236 }
237 Ok(())
238 }
239}
240
241pub(crate) struct ParameterTableDiff {
243 parameters: BTreeMap<Parameter, (Option<ParameterValue>, Option<ParameterValue>)>,
244}
245
246#[derive(thiserror::Error, Debug)]
248pub(crate) enum InvalidConfigError {
249 #[error("could not parse `{1}` as a parameter")]
250 UnknownParameter(#[source] strum::ParseError, String),
251 #[error("could not parse `{1}` as a value")]
252 ValueParseError(#[source] serde_yaml::Error, String),
253 #[error("could not parse YAML that defines the structure of the config")]
254 InvalidYaml(#[source] serde_yaml::Error),
255 #[error("config diff expected to contain old value `{1:?}` for parameter `{0}`")]
256 OldValueExists(Parameter, ParameterValue),
257 #[error(
258 "unexpected old value `{1:?}` for parameter `{0}` in config diff, previous version does not have such a value"
259 )]
260 NoOldValueExists(Parameter, ParameterValue),
261 #[error("expected old value `{1:?}` but found `{2:?}` for parameter `{0}` in config diff")]
262 WrongOldValue(Parameter, ParameterValue, ParameterValue),
263 #[error("expected a value for `{0}` but found none")]
264 MissingParameter(Parameter),
265 #[error("failed to convert a value for `{1}`")]
266 ValueConversionError(#[source] ValueConversionError, Parameter),
267}
268
269impl std::str::FromStr for ParameterTable {
270 type Err = InvalidConfigError;
271 fn from_str(arg: &str) -> Result<ParameterTable, InvalidConfigError> {
272 let yaml_map: BTreeMap<String, serde_yaml::Value> =
273 serde_yaml::from_str(arg).map_err(|err| InvalidConfigError::InvalidYaml(err))?;
274
275 let parameters = yaml_map
276 .iter()
277 .map(|(key, value)| {
278 let typed_key: Parameter = key
279 .parse()
280 .map_err(|err| InvalidConfigError::UnknownParameter(err, key.to_owned()))?;
281 Ok((typed_key, parse_parameter_value(value)?))
282 })
283 .collect::<Result<BTreeMap<_, _>, _>>()?;
284
285 Ok(ParameterTable { parameters })
286 }
287}
288
289impl TryFrom<&ParameterTable> for RuntimeConfig {
290 type Error = InvalidConfigError;
291
292 fn try_from(params: &ParameterTable) -> Result<Self, Self::Error> {
293 Ok(RuntimeConfig {
294 fees: Arc::new(RuntimeFeesConfig {
295 action_fees: enum_map::enum_map! {
296 action_cost => params.get_fee(action_cost)?
297 },
298 burnt_gas_reward: params.get(Parameter::BurntGasReward)?,
299 pessimistic_gas_price_inflation_ratio: params
300 .get(Parameter::PessimisticGasPriceInflation)?,
301 refund_gas_price_changes: params.get(Parameter::RefundGasPriceChanges)?,
302 gas_refund_penalty: params.get(Parameter::GasRefundPenalty)?,
303 min_gas_refund_penalty: params.get(Parameter::MinGasRefundPenalty)?,
304 storage_usage_config: StorageUsageConfig {
305 storage_amount_per_byte: params.get(Parameter::StorageAmountPerByte)?,
306 num_bytes_account: params.get(Parameter::StorageNumBytesAccount)?,
307 num_extra_bytes_record: params.get(Parameter::StorageNumExtraBytesRecord)?,
308 global_contract_storage_amount_per_byte: params
309 .get(Parameter::GlobalContractStorageAmountPerByte)?,
310 },
311 }),
312 wasm_config: Arc::new(Config {
313 ext_costs: ExtCostsConfig {
314 costs: enum_map::enum_map! {
315 cost => params.get(cost.param())?
316 },
317 },
318 vm_kind: params.get(Parameter::VmKind)?,
319 grow_mem_cost: params.get(Parameter::WasmGrowMemCost)?,
320 regular_op_cost: params.get(Parameter::WasmRegularOpCost)?,
321 discard_custom_sections: params.get(Parameter::DiscardCustomSections)?,
322 saturating_float_to_int: params.get(Parameter::SaturatingFloatToInt)?,
323 limit_config: serde_yaml::from_value(params.yaml_map(Parameter::vm_limits()))
324 .map_err(InvalidConfigError::InvalidYaml)?,
325 fix_contract_loading_cost: params.get(Parameter::FixContractLoadingCost)?,
326 storage_get_mode: match params.get(Parameter::FlatStorageReads)? {
327 true => StorageGetMode::FlatStorage,
328 false => StorageGetMode::Trie,
329 },
330 implicit_account_creation: params.get(Parameter::ImplicitAccountCreation)?,
331 eth_implicit_accounts: params.get(Parameter::EthImplicitAccounts)?,
332 global_contract_host_fns: params.get(Parameter::GlobalContractHostFns)?,
333 }),
334 account_creation_config: AccountCreationConfig {
335 min_allowed_top_level_account_length: params
336 .get(Parameter::MinAllowedTopLevelAccountLength)?,
337 registrar_account_id: params.get(Parameter::RegistrarAccountId)?,
338 },
339 congestion_control_config: get_congestion_control_config(params)?,
340 witness_config: WitnessConfig {
341 main_storage_proof_size_soft_limit: params
342 .get(Parameter::MainStorageProofSizeSoftLimit)?,
343 combined_transactions_size_limit: params
344 .get(Parameter::CombinedTransactionsSizeLimit)?,
345 new_transactions_validation_state_size_soft_limit: params
346 .get(Parameter::NewTransactionsValidationStateSizeSoftLimit)?,
347 },
348 bandwidth_scheduler_config: BandwidthSchedulerConfig {
349 max_shard_bandwidth: params.get(Parameter::MaxShardBandwidth)?,
350 max_single_grant: params.get(Parameter::MaxSingleGrant)?,
351 max_allowance: params.get(Parameter::MaxAllowance)?,
352 max_base_bandwidth: params.get(Parameter::MaxBaseBandwidth)?,
353 },
354 use_state_stored_receipt: params.get(Parameter::UseStateStoredReceipt)?,
355 })
356 }
357}
358
359fn get_congestion_control_config(
360 params: &ParameterTable,
361) -> Result<CongestionControlConfig, <RuntimeConfig as TryFrom<&ParameterTable>>::Error> {
362 let congestion_control_config = CongestionControlConfig {
363 max_congestion_incoming_gas: params.get(Parameter::MaxCongestionIncomingGas)?,
364 max_congestion_outgoing_gas: params.get(Parameter::MaxCongestionOutgoingGas)?,
365 max_congestion_memory_consumption: params.get(Parameter::MaxCongestionMemoryConsumption)?,
366 max_congestion_missed_chunks: params.get(Parameter::MaxCongestionMissedChunks)?,
367 max_outgoing_gas: params.get(Parameter::MaxOutgoingGas)?,
368 min_outgoing_gas: params.get(Parameter::MinOutgoingGas)?,
369 allowed_shard_outgoing_gas: params.get(Parameter::AllowedShardOutgoingGas)?,
370 max_tx_gas: params.get(Parameter::MaxTxGas)?,
371 min_tx_gas: params.get(Parameter::MinTxGas)?,
372 reject_tx_congestion_threshold: {
373 let rational: Rational32 = params.get(Parameter::RejectTxCongestionThreshold)?;
374 *rational.numer() as f64 / *rational.denom() as f64
375 },
376 outgoing_receipts_usual_size_limit: params
377 .get(Parameter::OutgoingReceiptsUsualSizeLimit)?,
378 outgoing_receipts_big_size_limit: params.get(Parameter::OutgoingReceiptsBigSizeLimit)?,
379 };
380 Ok(congestion_control_config)
381}
382
383impl ParameterTable {
384 pub(crate) fn apply_diff(
385 &mut self,
386 diff: ParameterTableDiff,
387 ) -> Result<(), InvalidConfigError> {
388 for (key, (before, after)) in diff.parameters {
389 let old_value = self.parameters.get(&key);
390 if old_value != before.as_ref() {
391 if old_value.is_none() {
392 return Err(InvalidConfigError::NoOldValueExists(key, before.unwrap()));
393 }
394 if before.is_none() {
395 return Err(InvalidConfigError::OldValueExists(
396 key,
397 old_value.unwrap().clone(),
398 ));
399 }
400 return Err(InvalidConfigError::WrongOldValue(
401 key,
402 old_value.unwrap().clone(),
403 before.unwrap(),
404 ));
405 }
406
407 if let Some(new_value) = after {
408 self.parameters.insert(key, new_value);
409 } else {
410 self.parameters.remove(&key);
411 }
412 }
413 Ok(())
414 }
415
416 fn yaml_map(&self, params: impl Iterator<Item = &'static Parameter>) -> serde_yaml::Value {
417 serde_yaml::to_value(
419 params
420 .filter_map(|param| Some((param.to_string(), self.parameters.get(param)?)))
421 .collect::<BTreeMap<_, _>>(),
422 )
423 .expect("failed to convert parameter values to YAML")
424 }
425
426 fn get<'a, T>(&'a self, key: Parameter) -> Result<T, InvalidConfigError>
428 where
429 T: TryFrom<&'a ParameterValue, Error = ValueConversionError>,
430 {
431 let value = self.parameters.get(&key).ok_or(InvalidConfigError::MissingParameter(key))?;
432 value.try_into().map_err(|err| InvalidConfigError::ValueConversionError(err, key))
433 }
434
435 fn get_fee(&self, cost: ActionCosts) -> Result<Fee, InvalidConfigError> {
437 let key: Parameter = format!("{}", FeeParameter::from(cost)).parse().unwrap();
438 self.get(key)
439 }
440}
441
442#[derive(serde::Deserialize, Clone, Debug)]
444struct ParameterDiffConfigValue {
445 old: Option<serde_yaml::Value>,
446 new: Option<serde_yaml::Value>,
447}
448
449impl std::str::FromStr for ParameterTableDiff {
450 type Err = InvalidConfigError;
451 fn from_str(arg: &str) -> Result<ParameterTableDiff, InvalidConfigError> {
452 let yaml_map: BTreeMap<String, ParameterDiffConfigValue> =
453 serde_yaml::from_str(arg).map_err(|err| InvalidConfigError::InvalidYaml(err))?;
454
455 let parameters = yaml_map
456 .iter()
457 .map(|(key, value)| {
458 let typed_key: Parameter = key
459 .parse()
460 .map_err(|err| InvalidConfigError::UnknownParameter(err, key.to_owned()))?;
461
462 let old_value =
463 if let Some(s) = &value.old { Some(parse_parameter_value(s)?) } else { None };
464
465 let new_value =
466 if let Some(s) = &value.new { Some(parse_parameter_value(s)?) } else { None };
467
468 Ok((typed_key, (old_value, new_value)))
469 })
470 .collect::<Result<BTreeMap<_, _>, _>>()?;
471 Ok(ParameterTableDiff { parameters })
472 }
473}
474
475fn parse_parameter_value(value: &serde_yaml::Value) -> Result<ParameterValue, InvalidConfigError> {
477 Ok(serde_yaml::from_value(canonicalize_yaml_value(value)?)
478 .map_err(|err| InvalidConfigError::InvalidYaml(err))?)
479}
480
481fn canonicalize_yaml_value(
483 value: &serde_yaml::Value,
484) -> Result<serde_yaml::Value, InvalidConfigError> {
485 Ok(match value {
486 serde_yaml::Value::String(s) => canonicalize_yaml_string(s)?,
487 serde_yaml::Value::Mapping(m) => serde_yaml::Value::Mapping(
488 m.iter()
489 .map(|(key, value)| {
490 let canonical_value = canonicalize_yaml_value(value)?;
491 Ok((key.clone(), canonical_value))
492 })
493 .collect::<Result<_, _>>()?,
494 ),
495 _ => value.clone(),
496 })
497}
498
499fn canonicalize_yaml_string(value: &str) -> Result<serde_yaml::Value, InvalidConfigError> {
507 if value.is_empty() {
508 return Ok(serde_yaml::Value::Null);
509 }
510 if value.bytes().all(|c| c.is_ascii_digit() || c == '_' as u8) {
511 let mut raw_number = value.to_owned();
512 raw_number.retain(char::is_numeric);
513 if raw_number.len() < 20 {
517 serde_yaml::from_str(&raw_number)
518 .map_err(|err| InvalidConfigError::ValueParseError(err, value.to_owned()))
519 } else {
520 Ok(serde_yaml::Value::String(raw_number))
521 }
522 } else {
523 Ok(serde_yaml::Value::String(value.to_owned()))
524 }
525}
526
527#[cfg(test)]
528mod tests {
529 use super::{
530 InvalidConfigError, ParameterTable, ParameterTableDiff, ParameterValue,
531 parse_parameter_value,
532 };
533 use crate::Parameter;
534 use assert_matches::assert_matches;
535 use std::collections::BTreeMap;
536
537 #[track_caller]
538 fn check_parameter_table(
539 base_config: &str,
540 diffs: &[&str],
541 expected: impl IntoIterator<Item = (Parameter, &'static str)>,
542 ) {
543 let mut params: ParameterTable = base_config.parse().unwrap();
544 for diff in diffs {
545 let diff: ParameterTableDiff = diff.parse().unwrap();
546 params.apply_diff(diff).unwrap();
547 }
548
549 let expected_map = BTreeMap::from_iter(expected.into_iter().map(|(param, value)| {
550 (param, {
551 assert!(!value.is_empty(), "omit the parameter in the test instead");
552 parse_parameter_value(
553 &serde_yaml::from_str(value).expect("Test data has invalid YAML"),
554 )
555 .unwrap()
556 })
557 }));
558
559 assert_eq!(params.parameters, expected_map);
560 }
561
562 #[track_caller]
563 fn check_invalid_parameter_table(base_config: &str, diffs: &[&str]) -> InvalidConfigError {
564 let params = base_config.parse();
565
566 let result = params.and_then(|params: ParameterTable| {
567 diffs.iter().try_fold(params, |mut params, diff| {
568 params.apply_diff(diff.parse()?)?;
569 Ok(params)
570 })
571 });
572
573 match result {
574 Ok(_) => panic!("Input should have parser error"),
575 Err(err) => err,
576 }
577 }
578
579 static BASE_0: &str = include_str!("fixture_base_0.yml");
580 static BASE_1: &str = include_str!("fixture_base_1.yml");
581 static DIFF_0: &str = include_str!("fixture_diff_0.yml");
582 static DIFF_1: &str = include_str!("fixture_diff_1.yml");
583
584 #[test]
590 fn test_empty_parameter_table() {
591 check_parameter_table("", &[], []);
592 }
593
594 #[test]
596 fn test_basic_parameter_table() {
597 check_parameter_table(
598 BASE_0,
599 &[],
600 [
601 (Parameter::RegistrarAccountId, "\"registrar\""),
602 (Parameter::MinAllowedTopLevelAccountLength, "32"),
603 (Parameter::StorageAmountPerByte, "\"100000000000000000000\""),
604 (Parameter::StorageNumBytesAccount, "100"),
605 (Parameter::StorageNumExtraBytesRecord, "40"),
606 (Parameter::BurntGasReward, "{ numerator: 1_000_000, denominator: 300 }"),
607 (
608 Parameter::WasmStorageReadBase,
609 "{ gas: 50_000_000_000, compute: 100_000_000_000 }",
610 ),
611 ],
612 );
613 }
614
615 #[test]
617 fn test_basic_parameter_table_weird_syntax() {
618 check_parameter_table(
619 BASE_1,
620 &[],
621 [
622 (Parameter::RegistrarAccountId, "\"registrar\""),
623 (Parameter::MinAllowedTopLevelAccountLength, "32"),
624 (Parameter::StorageAmountPerByte, "\"100000000000000000000\""),
625 (Parameter::StorageNumBytesAccount, "100"),
626 (Parameter::StorageNumExtraBytesRecord, "40"),
627 ],
628 );
629 }
630
631 #[test]
633 fn test_parameter_table_with_diff() {
634 check_parameter_table(
635 BASE_0,
636 &[DIFF_0],
637 [
638 (Parameter::RegistrarAccountId, "\"near\""),
639 (Parameter::MinAllowedTopLevelAccountLength, "32000"),
640 (Parameter::StorageAmountPerByte, "\"100000000000000000000\""),
641 (Parameter::StorageNumBytesAccount, "100"),
642 (Parameter::StorageNumExtraBytesRecord, "40"),
643 (Parameter::WasmRegularOpCost, "3856371"),
644 (Parameter::BurntGasReward, "{ numerator: 2_000_000, denominator: 500 }"),
645 (
646 Parameter::WasmStorageReadBase,
647 "{ gas: 50_000_000_000, compute: 200_000_000_000 }",
648 ),
649 ],
650 );
651 }
652
653 #[test]
655 fn test_parameter_table_with_diffs() {
656 check_parameter_table(
657 BASE_0,
658 &[DIFF_0, DIFF_1],
659 [
660 (Parameter::RegistrarAccountId, "\"registrar\""),
661 (Parameter::MinAllowedTopLevelAccountLength, "32000"),
662 (Parameter::StorageAmountPerByte, "\"100000000000000000000\""),
663 (Parameter::StorageNumBytesAccount, "100"),
664 (Parameter::StorageNumExtraBytesRecord, "77"),
665 (Parameter::WasmRegularOpCost, "0"),
666 (Parameter::MaxMemoryPages, "512"),
667 (Parameter::BurntGasReward, "{ numerator: 3_000_000, denominator: 800 }"),
668 (
669 Parameter::WasmStorageReadBase,
670 "{ gas: 50_000_000_000, compute: 200_000_000_000 }",
671 ),
672 ],
673 );
674 }
675
676 #[test]
677 fn test_parameter_table_with_empty_value() {
678 let diff_with_empty_value = "min_allowed_top_level_account_length: { old: 32 }";
679 check_parameter_table(
680 BASE_0,
681 &[diff_with_empty_value],
682 [
683 (Parameter::RegistrarAccountId, "\"registrar\""),
684 (Parameter::StorageAmountPerByte, "\"100000000000000000000\""),
685 (Parameter::StorageNumBytesAccount, "100"),
686 (Parameter::StorageNumExtraBytesRecord, "40"),
687 (Parameter::BurntGasReward, "{ numerator: 1_000_000, denominator: 300 }"),
688 (
689 Parameter::WasmStorageReadBase,
690 "{ gas: 50_000_000_000, compute: 100_000_000_000 }",
691 ),
692 ],
693 );
694 }
695
696 #[test]
697 fn test_parameter_table_invalid_key() {
698 assert_matches!(
700 check_invalid_parameter_table("invalid_key: 100", &[]),
701 InvalidConfigError::UnknownParameter(_, _)
702 );
703 }
704
705 #[test]
706 fn test_parameter_table_invalid_key_in_diff() {
707 assert_matches!(
708 check_invalid_parameter_table(
709 "wasm_regular_op_cost: 100",
710 &["invalid_key: { new: 100 }"]
711 ),
712 InvalidConfigError::UnknownParameter(_, _)
713 );
714 }
715
716 #[test]
717 fn test_parameter_table_no_key() {
718 assert_matches!(
719 check_invalid_parameter_table(": 100", &[]),
720 InvalidConfigError::InvalidYaml(_)
721 );
722 }
723
724 #[test]
725 fn test_parameter_table_no_key_in_diff() {
726 assert_matches!(
727 check_invalid_parameter_table("wasm_regular_op_cost: 100", &[": 100"]),
728 InvalidConfigError::InvalidYaml(_)
729 );
730 }
731
732 #[test]
733 fn test_parameter_table_wrong_separator() {
734 assert_matches!(
735 check_invalid_parameter_table("wasm_regular_op_cost=100", &[]),
736 InvalidConfigError::InvalidYaml(_)
737 );
738 }
739
740 #[test]
741 fn test_parameter_table_wrong_separator_in_diff() {
742 assert_matches!(
743 check_invalid_parameter_table(
744 "wasm_regular_op_cost: 100",
745 &["wasm_regular_op_cost=100"]
746 ),
747 InvalidConfigError::InvalidYaml(_)
748 );
749 }
750
751 #[test]
752 fn test_parameter_table_wrong_old_value() {
753 assert_matches!(
754 check_invalid_parameter_table(
755 "min_allowed_top_level_account_length: 3_200_000_000",
756 &["min_allowed_top_level_account_length: { old: 3_200_000, new: 1_600_000 }"]
757 ),
758 InvalidConfigError::WrongOldValue(
759 Parameter::MinAllowedTopLevelAccountLength,
760 expected,
761 found
762 ) => {
763 assert_eq!(expected, ParameterValue::U64(3200000000));
764 assert_eq!(found, ParameterValue::U64(3200000));
765 }
766 );
767 }
768
769 #[test]
770 fn test_parameter_table_no_old_value() {
771 assert_matches!(
772 check_invalid_parameter_table(
773 "min_allowed_top_level_account_length: 3_200_000_000",
774 &["min_allowed_top_level_account_length: { new: 1_600_000 }"]
775 ),
776 InvalidConfigError::OldValueExists(Parameter::MinAllowedTopLevelAccountLength, expected) => {
777 assert_eq!(expected, ParameterValue::U64(3200000000));
778 }
779 );
780 }
781
782 #[test]
783 fn test_parameter_table_old_parameter_undefined() {
784 assert_matches!(
785 check_invalid_parameter_table(
786 "min_allowed_top_level_account_length: 3_200_000_000",
787 &["wasm_regular_op_cost: { old: 3_200_000, new: 1_600_000 }"]
788 ),
789 InvalidConfigError::NoOldValueExists(Parameter::WasmRegularOpCost, found) => {
790 assert_eq!(found, ParameterValue::U64(3200000));
791 }
792 );
793 }
794
795 #[test]
796 fn test_parameter_table_yaml_map() {
797 let params: ParameterTable = BASE_0.parse().unwrap();
798 let yaml = params.yaml_map(
799 [
800 Parameter::RegistrarAccountId,
801 Parameter::MinAllowedTopLevelAccountLength,
802 Parameter::StorageAmountPerByte,
803 Parameter::StorageNumBytesAccount,
804 Parameter::StorageNumExtraBytesRecord,
805 Parameter::BurntGasReward,
806 Parameter::WasmStorageReadBase,
807 ]
808 .iter(),
809 );
810 assert_eq!(
811 yaml,
812 serde_yaml::to_value(
813 params
814 .parameters
815 .iter()
816 .map(|(key, value)| (key.to_string(), value))
817 .collect::<BTreeMap<_, _>>()
818 )
819 .unwrap()
820 );
821 }
822}