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 storage_usage_config: StorageUsageConfig {
302 storage_amount_per_byte: params.get(Parameter::StorageAmountPerByte)?,
303 num_bytes_account: params.get(Parameter::StorageNumBytesAccount)?,
304 num_extra_bytes_record: params.get(Parameter::StorageNumExtraBytesRecord)?,
305 },
306 }),
307 wasm_config: Arc::new(Config {
308 ext_costs: ExtCostsConfig {
309 costs: enum_map::enum_map! {
310 cost => params.get(cost.param())?
311 },
312 },
313 vm_kind: params.get(Parameter::VmKind)?,
314 grow_mem_cost: params.get(Parameter::WasmGrowMemCost)?,
315 regular_op_cost: params.get(Parameter::WasmRegularOpCost)?,
316 disable_9393_fix: params.get(Parameter::Disable9393Fix)?,
317 discard_custom_sections: params.get(Parameter::DiscardCustomSections)?,
318 limit_config: serde_yaml::from_value(params.yaml_map(Parameter::vm_limits()))
319 .map_err(InvalidConfigError::InvalidYaml)?,
320 fix_contract_loading_cost: params.get(Parameter::FixContractLoadingCost)?,
321 storage_get_mode: match params.get(Parameter::FlatStorageReads)? {
322 true => StorageGetMode::FlatStorage,
323 false => StorageGetMode::Trie,
324 },
325 implicit_account_creation: params.get(Parameter::ImplicitAccountCreation)?,
326 math_extension: params.get(Parameter::MathExtension)?,
327 ed25519_verify: params.get(Parameter::Ed25519Verify)?,
328 alt_bn128: params.get(Parameter::AltBn128)?,
329 function_call_weight: params.get(Parameter::FunctionCallWeight)?,
330 eth_implicit_accounts: params.get(Parameter::EthImplicitAccounts)?,
331 yield_resume_host_functions: params.get(Parameter::YieldResume)?,
332 }),
333 account_creation_config: AccountCreationConfig {
334 min_allowed_top_level_account_length: params
335 .get(Parameter::MinAllowedTopLevelAccountLength)?,
336 registrar_account_id: params.get(Parameter::RegistrarAccountId)?,
337 },
338 congestion_control_config: get_congestion_control_config(params)?,
339 witness_config: WitnessConfig {
340 main_storage_proof_size_soft_limit: params
341 .get(Parameter::MainStorageProofSizeSoftLimit)?,
342 combined_transactions_size_limit: params
343 .get(Parameter::CombinedTransactionsSizeLimit)?,
344 new_transactions_validation_state_size_soft_limit: params
345 .get(Parameter::NewTransactionsValidationStateSizeSoftLimit)?,
346 },
347 bandwidth_scheduler_config: BandwidthSchedulerConfig {
348 max_shard_bandwidth: params.get(Parameter::MaxShardBandwidth)?,
349 max_single_grant: params.get(Parameter::MaxSingleGrant)?,
350 max_allowance: params.get(Parameter::MaxAllowance)?,
351 max_base_bandwidth: params.get(Parameter::MaxBaseBandwidth)?,
352 },
353 use_state_stored_receipt: params.get(Parameter::UseStateStoredReceipt)?,
354 })
355 }
356}
357
358fn get_congestion_control_config(
359 params: &ParameterTable,
360) -> Result<CongestionControlConfig, <RuntimeConfig as TryFrom<&ParameterTable>>::Error> {
361 let congestion_control_config = CongestionControlConfig {
362 max_congestion_incoming_gas: params.get(Parameter::MaxCongestionIncomingGas)?,
363 max_congestion_outgoing_gas: params.get(Parameter::MaxCongestionOutgoingGas)?,
364 max_congestion_memory_consumption: params.get(Parameter::MaxCongestionMemoryConsumption)?,
365 max_congestion_missed_chunks: params.get(Parameter::MaxCongestionMissedChunks)?,
366 max_outgoing_gas: params.get(Parameter::MaxOutgoingGas)?,
367 min_outgoing_gas: params.get(Parameter::MinOutgoingGas)?,
368 allowed_shard_outgoing_gas: params.get(Parameter::AllowedShardOutgoingGas)?,
369 max_tx_gas: params.get(Parameter::MaxTxGas)?,
370 min_tx_gas: params.get(Parameter::MinTxGas)?,
371 reject_tx_congestion_threshold: {
372 let rational: Rational32 = params.get(Parameter::RejectTxCongestionThreshold)?;
373 *rational.numer() as f64 / *rational.denom() as f64
374 },
375 outgoing_receipts_usual_size_limit: params
376 .get(Parameter::OutgoingReceiptsUsualSizeLimit)?,
377 outgoing_receipts_big_size_limit: params.get(Parameter::OutgoingReceiptsBigSizeLimit)?,
378 };
379 Ok(congestion_control_config)
380}
381
382impl ParameterTable {
383 pub(crate) fn apply_diff(
384 &mut self,
385 diff: ParameterTableDiff,
386 ) -> Result<(), InvalidConfigError> {
387 for (key, (before, after)) in diff.parameters {
388 let old_value = self.parameters.get(&key);
389 if old_value != before.as_ref() {
390 if old_value.is_none() {
391 return Err(InvalidConfigError::NoOldValueExists(key, before.unwrap()));
392 }
393 if before.is_none() {
394 return Err(InvalidConfigError::OldValueExists(
395 key,
396 old_value.unwrap().clone(),
397 ));
398 }
399 return Err(InvalidConfigError::WrongOldValue(
400 key,
401 old_value.unwrap().clone(),
402 before.unwrap(),
403 ));
404 }
405
406 if let Some(new_value) = after {
407 self.parameters.insert(key, new_value);
408 } else {
409 self.parameters.remove(&key);
410 }
411 }
412 Ok(())
413 }
414
415 fn yaml_map(&self, params: impl Iterator<Item = &'static Parameter>) -> serde_yaml::Value {
416 serde_yaml::to_value(
418 params
419 .filter_map(|param| Some((param.to_string(), self.parameters.get(param)?)))
420 .collect::<BTreeMap<_, _>>(),
421 )
422 .expect("failed to convert parameter values to YAML")
423 }
424
425 fn get<'a, T>(&'a self, key: Parameter) -> Result<T, InvalidConfigError>
427 where
428 T: TryFrom<&'a ParameterValue, Error = ValueConversionError>,
429 {
430 let value = self.parameters.get(&key).ok_or(InvalidConfigError::MissingParameter(key))?;
431 value.try_into().map_err(|err| InvalidConfigError::ValueConversionError(err, key))
432 }
433
434 fn get_fee(&self, cost: ActionCosts) -> Result<Fee, InvalidConfigError> {
436 let key: Parameter = format!("{}", FeeParameter::from(cost)).parse().unwrap();
437 self.get(key)
438 }
439}
440
441#[derive(serde::Deserialize, Clone, Debug)]
443struct ParameterDiffConfigValue {
444 old: Option<serde_yaml::Value>,
445 new: Option<serde_yaml::Value>,
446}
447
448impl std::str::FromStr for ParameterTableDiff {
449 type Err = InvalidConfigError;
450 fn from_str(arg: &str) -> Result<ParameterTableDiff, InvalidConfigError> {
451 let yaml_map: BTreeMap<String, ParameterDiffConfigValue> =
452 serde_yaml::from_str(arg).map_err(|err| InvalidConfigError::InvalidYaml(err))?;
453
454 let parameters = yaml_map
455 .iter()
456 .map(|(key, value)| {
457 let typed_key: Parameter = key
458 .parse()
459 .map_err(|err| InvalidConfigError::UnknownParameter(err, key.to_owned()))?;
460
461 let old_value =
462 if let Some(s) = &value.old { Some(parse_parameter_value(s)?) } else { None };
463
464 let new_value =
465 if let Some(s) = &value.new { Some(parse_parameter_value(s)?) } else { None };
466
467 Ok((typed_key, (old_value, new_value)))
468 })
469 .collect::<Result<BTreeMap<_, _>, _>>()?;
470 Ok(ParameterTableDiff { parameters })
471 }
472}
473
474fn parse_parameter_value(value: &serde_yaml::Value) -> Result<ParameterValue, InvalidConfigError> {
476 Ok(serde_yaml::from_value(canonicalize_yaml_value(value)?)
477 .map_err(|err| InvalidConfigError::InvalidYaml(err))?)
478}
479
480fn canonicalize_yaml_value(
482 value: &serde_yaml::Value,
483) -> Result<serde_yaml::Value, InvalidConfigError> {
484 Ok(match value {
485 serde_yaml::Value::String(s) => canonicalize_yaml_string(s)?,
486 serde_yaml::Value::Mapping(m) => serde_yaml::Value::Mapping(
487 m.iter()
488 .map(|(key, value)| {
489 let canonical_value = canonicalize_yaml_value(value)?;
490 Ok((key.clone(), canonical_value))
491 })
492 .collect::<Result<_, _>>()?,
493 ),
494 _ => value.clone(),
495 })
496}
497
498fn canonicalize_yaml_string(value: &str) -> Result<serde_yaml::Value, InvalidConfigError> {
506 if value.is_empty() {
507 return Ok(serde_yaml::Value::Null);
508 }
509 if value.bytes().all(|c| c.is_ascii_digit() || c == '_' as u8) {
510 let mut raw_number = value.to_owned();
511 raw_number.retain(char::is_numeric);
512 if raw_number.len() < 20 {
516 serde_yaml::from_str(&raw_number)
517 .map_err(|err| InvalidConfigError::ValueParseError(err, value.to_owned()))
518 } else {
519 Ok(serde_yaml::Value::String(raw_number))
520 }
521 } else {
522 Ok(serde_yaml::Value::String(value.to_owned()))
523 }
524}
525
526#[cfg(test)]
527mod tests {
528 use super::{
529 parse_parameter_value, InvalidConfigError, ParameterTable, ParameterTableDiff,
530 ParameterValue,
531 };
532 use crate::Parameter;
533 use assert_matches::assert_matches;
534 use std::collections::BTreeMap;
535
536 #[track_caller]
537 fn check_parameter_table(
538 base_config: &str,
539 diffs: &[&str],
540 expected: impl IntoIterator<Item = (Parameter, &'static str)>,
541 ) {
542 let mut params: ParameterTable = base_config.parse().unwrap();
543 for diff in diffs {
544 let diff: ParameterTableDiff = diff.parse().unwrap();
545 params.apply_diff(diff).unwrap();
546 }
547
548 let expected_map = BTreeMap::from_iter(expected.into_iter().map(|(param, value)| {
549 (param, {
550 assert!(!value.is_empty(), "omit the parameter in the test instead");
551 parse_parameter_value(
552 &serde_yaml::from_str(value).expect("Test data has invalid YAML"),
553 )
554 .unwrap()
555 })
556 }));
557
558 assert_eq!(params.parameters, expected_map);
559 }
560
561 #[track_caller]
562 fn check_invalid_parameter_table(base_config: &str, diffs: &[&str]) -> InvalidConfigError {
563 let params = base_config.parse();
564
565 let result = params.and_then(|params: ParameterTable| {
566 diffs.iter().try_fold(params, |mut params, diff| {
567 params.apply_diff(diff.parse()?)?;
568 Ok(params)
569 })
570 });
571
572 match result {
573 Ok(_) => panic!("Input should have parser error"),
574 Err(err) => err,
575 }
576 }
577
578 static BASE_0: &str = include_str!("fixture_base_0.yml");
579 static BASE_1: &str = include_str!("fixture_base_1.yml");
580 static DIFF_0: &str = include_str!("fixture_diff_0.yml");
581 static DIFF_1: &str = include_str!("fixture_diff_1.yml");
582
583 #[test]
589 fn test_empty_parameter_table() {
590 check_parameter_table("", &[], []);
591 }
592
593 #[test]
595 fn test_basic_parameter_table() {
596 check_parameter_table(
597 BASE_0,
598 &[],
599 [
600 (Parameter::RegistrarAccountId, "\"registrar\""),
601 (Parameter::MinAllowedTopLevelAccountLength, "32"),
602 (Parameter::StorageAmountPerByte, "\"100000000000000000000\""),
603 (Parameter::StorageNumBytesAccount, "100"),
604 (Parameter::StorageNumExtraBytesRecord, "40"),
605 (Parameter::BurntGasReward, "{ numerator: 1_000_000, denominator: 300 }"),
606 (
607 Parameter::WasmStorageReadBase,
608 "{ gas: 50_000_000_000, compute: 100_000_000_000 }",
609 ),
610 ],
611 );
612 }
613
614 #[test]
616 fn test_basic_parameter_table_weird_syntax() {
617 check_parameter_table(
618 BASE_1,
619 &[],
620 [
621 (Parameter::RegistrarAccountId, "\"registrar\""),
622 (Parameter::MinAllowedTopLevelAccountLength, "32"),
623 (Parameter::StorageAmountPerByte, "\"100000000000000000000\""),
624 (Parameter::StorageNumBytesAccount, "100"),
625 (Parameter::StorageNumExtraBytesRecord, "40"),
626 ],
627 );
628 }
629
630 #[test]
632 fn test_parameter_table_with_diff() {
633 check_parameter_table(
634 BASE_0,
635 &[DIFF_0],
636 [
637 (Parameter::RegistrarAccountId, "\"near\""),
638 (Parameter::MinAllowedTopLevelAccountLength, "32000"),
639 (Parameter::StorageAmountPerByte, "\"100000000000000000000\""),
640 (Parameter::StorageNumBytesAccount, "100"),
641 (Parameter::StorageNumExtraBytesRecord, "40"),
642 (Parameter::WasmRegularOpCost, "3856371"),
643 (Parameter::BurntGasReward, "{ numerator: 2_000_000, denominator: 500 }"),
644 (
645 Parameter::WasmStorageReadBase,
646 "{ gas: 50_000_000_000, compute: 200_000_000_000 }",
647 ),
648 ],
649 );
650 }
651
652 #[test]
654 fn test_parameter_table_with_diffs() {
655 check_parameter_table(
656 BASE_0,
657 &[DIFF_0, DIFF_1],
658 [
659 (Parameter::RegistrarAccountId, "\"registrar\""),
660 (Parameter::MinAllowedTopLevelAccountLength, "32000"),
661 (Parameter::StorageAmountPerByte, "\"100000000000000000000\""),
662 (Parameter::StorageNumBytesAccount, "100"),
663 (Parameter::StorageNumExtraBytesRecord, "77"),
664 (Parameter::WasmRegularOpCost, "0"),
665 (Parameter::MaxMemoryPages, "512"),
666 (Parameter::BurntGasReward, "{ numerator: 3_000_000, denominator: 800 }"),
667 (
668 Parameter::WasmStorageReadBase,
669 "{ gas: 50_000_000_000, compute: 200_000_000_000 }",
670 ),
671 ],
672 );
673 }
674
675 #[test]
676 fn test_parameter_table_with_empty_value() {
677 let diff_with_empty_value = "min_allowed_top_level_account_length: { old: 32 }";
678 check_parameter_table(
679 BASE_0,
680 &[diff_with_empty_value],
681 [
682 (Parameter::RegistrarAccountId, "\"registrar\""),
683 (Parameter::StorageAmountPerByte, "\"100000000000000000000\""),
684 (Parameter::StorageNumBytesAccount, "100"),
685 (Parameter::StorageNumExtraBytesRecord, "40"),
686 (Parameter::BurntGasReward, "{ numerator: 1_000_000, denominator: 300 }"),
687 (
688 Parameter::WasmStorageReadBase,
689 "{ gas: 50_000_000_000, compute: 100_000_000_000 }",
690 ),
691 ],
692 );
693 }
694
695 #[test]
696 fn test_parameter_table_invalid_key() {
697 assert_matches!(
699 check_invalid_parameter_table("invalid_key: 100", &[]),
700 InvalidConfigError::UnknownParameter(_, _)
701 );
702 }
703
704 #[test]
705 fn test_parameter_table_invalid_key_in_diff() {
706 assert_matches!(
707 check_invalid_parameter_table(
708 "wasm_regular_op_cost: 100",
709 &["invalid_key: { new: 100 }"]
710 ),
711 InvalidConfigError::UnknownParameter(_, _)
712 );
713 }
714
715 #[test]
716 fn test_parameter_table_no_key() {
717 assert_matches!(
718 check_invalid_parameter_table(": 100", &[]),
719 InvalidConfigError::InvalidYaml(_)
720 );
721 }
722
723 #[test]
724 fn test_parameter_table_no_key_in_diff() {
725 assert_matches!(
726 check_invalid_parameter_table("wasm_regular_op_cost: 100", &[": 100"]),
727 InvalidConfigError::InvalidYaml(_)
728 );
729 }
730
731 #[test]
732 fn test_parameter_table_wrong_separator() {
733 assert_matches!(
734 check_invalid_parameter_table("wasm_regular_op_cost=100", &[]),
735 InvalidConfigError::InvalidYaml(_)
736 );
737 }
738
739 #[test]
740 fn test_parameter_table_wrong_separator_in_diff() {
741 assert_matches!(
742 check_invalid_parameter_table(
743 "wasm_regular_op_cost: 100",
744 &["wasm_regular_op_cost=100"]
745 ),
746 InvalidConfigError::InvalidYaml(_)
747 );
748 }
749
750 #[test]
751 fn test_parameter_table_wrong_old_value() {
752 assert_matches!(
753 check_invalid_parameter_table(
754 "min_allowed_top_level_account_length: 3_200_000_000",
755 &["min_allowed_top_level_account_length: { old: 3_200_000, new: 1_600_000 }"]
756 ),
757 InvalidConfigError::WrongOldValue(
758 Parameter::MinAllowedTopLevelAccountLength,
759 expected,
760 found
761 ) => {
762 assert_eq!(expected, ParameterValue::U64(3200000000));
763 assert_eq!(found, ParameterValue::U64(3200000));
764 }
765 );
766 }
767
768 #[test]
769 fn test_parameter_table_no_old_value() {
770 assert_matches!(
771 check_invalid_parameter_table(
772 "min_allowed_top_level_account_length: 3_200_000_000",
773 &["min_allowed_top_level_account_length: { new: 1_600_000 }"]
774 ),
775 InvalidConfigError::OldValueExists(Parameter::MinAllowedTopLevelAccountLength, expected) => {
776 assert_eq!(expected, ParameterValue::U64(3200000000));
777 }
778 );
779 }
780
781 #[test]
782 fn test_parameter_table_old_parameter_undefined() {
783 assert_matches!(
784 check_invalid_parameter_table(
785 "min_allowed_top_level_account_length: 3_200_000_000",
786 &["wasm_regular_op_cost: { old: 3_200_000, new: 1_600_000 }"]
787 ),
788 InvalidConfigError::NoOldValueExists(Parameter::WasmRegularOpCost, found) => {
789 assert_eq!(found, ParameterValue::U64(3200000));
790 }
791 );
792 }
793
794 #[test]
795 fn test_parameter_table_yaml_map() {
796 let params: ParameterTable = BASE_0.parse().unwrap();
797 let yaml = params.yaml_map(
798 [
799 Parameter::RegistrarAccountId,
800 Parameter::MinAllowedTopLevelAccountLength,
801 Parameter::StorageAmountPerByte,
802 Parameter::StorageNumBytesAccount,
803 Parameter::StorageNumExtraBytesRecord,
804 Parameter::BurntGasReward,
805 Parameter::WasmStorageReadBase,
806 ]
807 .iter(),
808 );
809 assert_eq!(
810 yaml,
811 serde_yaml::to_value(
812 params
813 .parameters
814 .iter()
815 .map(|(key, value)| (key.to_string(), value))
816 .collect::<BTreeMap<_, _>>()
817 )
818 .unwrap()
819 );
820 }
821}