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