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, Balance, Gas};
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: Gas, send_not_sir: Gas, execution: Gas },
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::from_gas(*gas), compute: *compute })
125 }
126 &ParameterValue::U64(v) => Ok(ParameterCost { gas: Gas::from_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 Gas {
137 type Error = ValueConversionError;
138
139 fn try_from(value: &ParameterValue) -> Result<Self, Self::Error> {
140 match value {
141 ParameterValue::U64(v) => Ok(Gas::from_gas(u64::from(*v))),
142 _ => Err(ValueConversionError::ParseType(std::any::type_name::<Gas>(), value.clone())),
143 }
144 }
145}
146
147impl TryFrom<&ParameterValue> for Balance {
148 type Error = ValueConversionError;
149
150 fn try_from(value: &ParameterValue) -> Result<Self, Self::Error> {
151 match value {
152 ParameterValue::U64(v) => Ok(Balance::from_yoctonear(u128::from(*v))),
153 ParameterValue::String(s) => s.parse::<Balance>().map_err(|_err| {
154 ValueConversionError::ParseType(std::any::type_name::<Balance>(), value.clone())
155 }),
156 _ => Err(ValueConversionError::ParseType(
157 std::any::type_name::<Balance>(),
158 value.clone(),
159 )),
160 }
161 }
162}
163
164impl TryFrom<&ParameterValue> for Fee {
165 type Error = ValueConversionError;
166
167 fn try_from(value: &ParameterValue) -> Result<Self, Self::Error> {
168 match value {
169 &ParameterValue::Fee { send_sir, send_not_sir, execution } => {
170 Ok(Fee { send_sir: send_sir, send_not_sir: send_not_sir, execution: execution })
171 }
172 _ => Err(ValueConversionError::ParseType(std::any::type_name::<Fee>(), value.clone())),
173 }
174 }
175}
176
177impl<'a> TryFrom<&'a ParameterValue> for &'a str {
178 type Error = ValueConversionError;
179
180 fn try_from(value: &'a ParameterValue) -> Result<Self, Self::Error> {
181 match value {
182 ParameterValue::String(v) => Ok(v),
183 _ => {
184 Err(ValueConversionError::ParseType(std::any::type_name::<String>(), value.clone()))
185 }
186 }
187 }
188}
189
190impl TryFrom<&ParameterValue> for AccountId {
191 type Error = ValueConversionError;
192
193 fn try_from(value: &ParameterValue) -> Result<Self, Self::Error> {
194 let value: &str = value.try_into()?;
195 value.parse().map_err(|err| ValueConversionError::ParseAccountId(err, value.to_string()))
196 }
197}
198
199impl TryFrom<&ParameterValue> for VMKind {
200 type Error = ValueConversionError;
201
202 fn try_from(value: &ParameterValue) -> Result<Self, Self::Error> {
203 match value {
204 ParameterValue::String(v) => v
205 .parse()
206 .map(|v: VMKind| v.replace_with_wasmtime_if_unsupported())
207 .map_err(|e| ValueConversionError::ParseVmKind(e, value.to_string())),
208 _ => {
209 Err(ValueConversionError::ParseType(std::any::type_name::<VMKind>(), value.clone()))
210 }
211 }
212 }
213}
214
215fn format_number(mut n: u64) -> String {
216 let mut parts = Vec::new();
217 while n >= 1000 {
218 parts.push(format!("{:03?}", n % 1000));
219 n /= 1000;
220 }
221 parts.push(n.to_string());
222 parts.reverse();
223 parts.join("_")
224}
225
226impl core::fmt::Display for ParameterValue {
227 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
228 match self {
229 ParameterValue::U64(v) => write!(f, "{:>20}", format_number(*v)),
230 ParameterValue::Rational { numerator, denominator } => {
231 write!(f, "{numerator} / {denominator}")
232 }
233 ParameterValue::ParameterCost { gas, compute } => {
234 write!(f, "{:>20}, compute: {:>20}", format_number(*gas), format_number(*compute))
235 }
236 ParameterValue::Fee { send_sir, send_not_sir, execution } => {
237 write!(
238 f,
239 r#"
240- send_sir: {:>20}
241- send_not_sir: {:>20}
242- execution: {:>20}"#,
243 format_number((*send_sir).as_gas()),
244 format_number((*send_not_sir).as_gas()),
245 format_number((*execution).as_gas())
246 )
247 }
248 ParameterValue::String(v) => write!(f, "{v}"),
249 ParameterValue::Flag(b) => write!(f, "{b:?}"),
250 }
251 }
252}
253
254pub(crate) struct ParameterTable {
255 parameters: BTreeMap<Parameter, ParameterValue>,
256}
257
258impl core::fmt::Display for ParameterTable {
261 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
262 for (key, value) in &self.parameters {
263 write!(f, "{key:40}{value}\n")?
264 }
265 Ok(())
266 }
267}
268
269pub(crate) struct ParameterTableDiff {
271 parameters: BTreeMap<Parameter, (Option<ParameterValue>, Option<ParameterValue>)>,
272}
273
274#[derive(thiserror::Error, Debug)]
276pub(crate) enum InvalidConfigError {
277 #[error("could not parse `{1}` as a parameter")]
278 UnknownParameter(#[source] strum::ParseError, String),
279 #[error("could not parse `{1}` as a value")]
280 ValueParseError(#[source] serde_yaml::Error, String),
281 #[error("could not parse YAML that defines the structure of the config")]
282 InvalidYaml(#[source] serde_yaml::Error),
283 #[error("config diff expected to contain old value `{1:?}` for parameter `{0}`")]
284 OldValueExists(Parameter, ParameterValue),
285 #[error(
286 "unexpected old value `{1:?}` for parameter `{0}` in config diff, previous version does not have such a value"
287 )]
288 NoOldValueExists(Parameter, ParameterValue),
289 #[error("expected old value `{1:?}` but found `{2:?}` for parameter `{0}` in config diff")]
290 WrongOldValue(Parameter, ParameterValue, ParameterValue),
291 #[error("expected a value for `{0}` but found none")]
292 MissingParameter(Parameter),
293 #[error("failed to convert a value for `{1}`")]
294 ValueConversionError(#[source] ValueConversionError, Parameter),
295}
296
297impl std::str::FromStr for ParameterTable {
298 type Err = InvalidConfigError;
299 fn from_str(arg: &str) -> Result<ParameterTable, InvalidConfigError> {
300 let yaml_map: BTreeMap<String, serde_yaml::Value> =
301 serde_yaml::from_str(arg).map_err(|err| InvalidConfigError::InvalidYaml(err))?;
302
303 let parameters = yaml_map
304 .iter()
305 .map(|(key, value)| {
306 let typed_key: Parameter = key
307 .parse()
308 .map_err(|err| InvalidConfigError::UnknownParameter(err, key.to_owned()))?;
309 Ok((typed_key, parse_parameter_value(value)?))
310 })
311 .collect::<Result<BTreeMap<_, _>, _>>()?;
312
313 Ok(ParameterTable { parameters })
314 }
315}
316
317impl TryFrom<&ParameterTable> for RuntimeConfig {
318 type Error = InvalidConfigError;
319
320 fn try_from(params: &ParameterTable) -> Result<Self, Self::Error> {
321 Ok(RuntimeConfig {
322 fees: Arc::new(RuntimeFeesConfig {
323 action_fees: enum_map::enum_map! {
324 action_cost => params.get_fee(action_cost)?
325 },
326 burnt_gas_reward: params.get(Parameter::BurntGasReward)?,
327 pessimistic_gas_price_inflation_ratio: params
328 .get(Parameter::PessimisticGasPriceInflation)?,
329 gas_refund_penalty: params.get(Parameter::GasRefundPenalty)?,
330 min_gas_refund_penalty: params.get(Parameter::MinGasRefundPenalty)?,
331 storage_usage_config: StorageUsageConfig {
332 storage_amount_per_byte: params.get(Parameter::StorageAmountPerByte)?,
333 num_bytes_account: params.get(Parameter::StorageNumBytesAccount)?,
334 num_extra_bytes_record: params.get(Parameter::StorageNumExtraBytesRecord)?,
335 global_contract_storage_amount_per_byte: params
336 .get(Parameter::GlobalContractStorageAmountPerByte)?,
337 },
338 }),
339 wasm_config: Arc::new(Config {
340 ext_costs: ExtCostsConfig {
341 costs: enum_map::enum_map! {
342 cost => params.get(cost.param())?
343 },
344 },
345 vm_kind: params.get(Parameter::VmKind)?,
346 grow_mem_cost: params.get(Parameter::WasmGrowMemCost)?,
347 regular_op_cost: params.get(Parameter::WasmRegularOpCost)?,
348 linear_op_base_cost: params.get(Parameter::WasmLinearOpBaseCost)?,
349 linear_op_unit_cost: params.get(Parameter::WasmLinearOpUnitCost)?,
350 discard_custom_sections: params.get(Parameter::DiscardCustomSections)?,
351 saturating_float_to_int: params.get(Parameter::SaturatingFloatToInt)?,
352 reftypes_bulk_memory: params.get(Parameter::ReftypesBulkMemory)?,
353 limit_config: serde_yaml::from_value(params.yaml_map(Parameter::vm_limits()))
354 .map_err(InvalidConfigError::InvalidYaml)?,
355 fix_contract_loading_cost: params.get(Parameter::FixContractLoadingCost)?,
356 storage_get_mode: match params.get(Parameter::FlatStorageReads)? {
357 true => StorageGetMode::FlatStorage,
358 false => StorageGetMode::Trie,
359 },
360 implicit_account_creation: params.get(Parameter::ImplicitAccountCreation)?,
361 eth_implicit_accounts: params.get(Parameter::EthImplicitAccounts)?,
362 global_contract_host_fns: params.get(Parameter::GlobalContractHostFns)?,
363 deterministic_account_ids: params.get(Parameter::DeterministicAccountIds)?,
364 }),
365 account_creation_config: AccountCreationConfig {
366 min_allowed_top_level_account_length: params
367 .get(Parameter::MinAllowedTopLevelAccountLength)?,
368 registrar_account_id: params.get(Parameter::RegistrarAccountId)?,
369 },
370 congestion_control_config: get_congestion_control_config(params)?,
371 witness_config: WitnessConfig {
372 main_storage_proof_size_soft_limit: params
373 .get(Parameter::MainStorageProofSizeSoftLimit)?,
374 combined_transactions_size_limit: params
375 .get(Parameter::CombinedTransactionsSizeLimit)?,
376 new_transactions_validation_state_size_soft_limit: params
377 .get(Parameter::NewTransactionsValidationStateSizeSoftLimit)?,
378 },
379 bandwidth_scheduler_config: BandwidthSchedulerConfig {
380 max_shard_bandwidth: params.get(Parameter::MaxShardBandwidth)?,
381 max_single_grant: params.get(Parameter::MaxSingleGrant)?,
382 max_allowance: params.get(Parameter::MaxAllowance)?,
383 max_base_bandwidth: params.get(Parameter::MaxBaseBandwidth)?,
384 },
385 use_state_stored_receipt: params.get(Parameter::UseStateStoredReceipt)?,
386 })
387 }
388}
389
390fn get_congestion_control_config(
391 params: &ParameterTable,
392) -> Result<CongestionControlConfig, <RuntimeConfig as TryFrom<&ParameterTable>>::Error> {
393 let congestion_control_config = CongestionControlConfig {
394 max_congestion_incoming_gas: params.get(Parameter::MaxCongestionIncomingGas)?,
395 max_congestion_outgoing_gas: params.get(Parameter::MaxCongestionOutgoingGas)?,
396 max_congestion_memory_consumption: params.get(Parameter::MaxCongestionMemoryConsumption)?,
397 max_congestion_missed_chunks: params.get(Parameter::MaxCongestionMissedChunks)?,
398 max_outgoing_gas: params.get(Parameter::MaxOutgoingGas)?,
399 min_outgoing_gas: params.get(Parameter::MinOutgoingGas)?,
400 allowed_shard_outgoing_gas: params.get(Parameter::AllowedShardOutgoingGas)?,
401 max_tx_gas: params.get(Parameter::MaxTxGas)?,
402 min_tx_gas: params.get(Parameter::MinTxGas)?,
403 reject_tx_congestion_threshold: {
404 let rational: Rational32 = params.get(Parameter::RejectTxCongestionThreshold)?;
405 *rational.numer() as f64 / *rational.denom() as f64
406 },
407 outgoing_receipts_usual_size_limit: params
408 .get(Parameter::OutgoingReceiptsUsualSizeLimit)?,
409 outgoing_receipts_big_size_limit: params.get(Parameter::OutgoingReceiptsBigSizeLimit)?,
410 };
411 Ok(congestion_control_config)
412}
413
414impl ParameterTable {
415 pub(crate) fn apply_diff(
416 &mut self,
417 diff: ParameterTableDiff,
418 ) -> Result<(), InvalidConfigError> {
419 for (key, (before, after)) in diff.parameters {
420 let old_value = self.parameters.get(&key);
421 if old_value != before.as_ref() {
422 if old_value.is_none() {
423 return Err(InvalidConfigError::NoOldValueExists(key, before.unwrap()));
424 }
425 if before.is_none() {
426 return Err(InvalidConfigError::OldValueExists(
427 key,
428 old_value.unwrap().clone(),
429 ));
430 }
431 return Err(InvalidConfigError::WrongOldValue(
432 key,
433 old_value.unwrap().clone(),
434 before.unwrap(),
435 ));
436 }
437
438 if let Some(new_value) = after {
439 self.parameters.insert(key, new_value);
440 } else {
441 self.parameters.remove(&key);
442 }
443 }
444 Ok(())
445 }
446
447 fn yaml_map(&self, params: impl Iterator<Item = &'static Parameter>) -> serde_yaml::Value {
448 serde_yaml::to_value(
450 params
451 .filter_map(|param| Some((param.to_string(), self.parameters.get(param)?)))
452 .collect::<BTreeMap<_, _>>(),
453 )
454 .expect("failed to convert parameter values to YAML")
455 }
456
457 fn get<'a, T>(&'a self, key: Parameter) -> Result<T, InvalidConfigError>
459 where
460 T: TryFrom<&'a ParameterValue, Error = ValueConversionError>,
461 {
462 let value = self.parameters.get(&key).ok_or(InvalidConfigError::MissingParameter(key))?;
463 value.try_into().map_err(|err| InvalidConfigError::ValueConversionError(err, key))
464 }
465
466 fn get_fee(&self, cost: ActionCosts) -> Result<Fee, InvalidConfigError> {
468 let key: Parameter = format!("{}", FeeParameter::from(cost)).parse().unwrap();
469 self.get(key)
470 }
471}
472
473#[derive(serde::Deserialize, Clone, Debug)]
475struct ParameterDiffConfigValue {
476 old: Option<serde_yaml::Value>,
477 new: Option<serde_yaml::Value>,
478}
479
480impl std::str::FromStr for ParameterTableDiff {
481 type Err = InvalidConfigError;
482 fn from_str(arg: &str) -> Result<ParameterTableDiff, InvalidConfigError> {
483 let yaml_map: BTreeMap<String, ParameterDiffConfigValue> =
484 serde_yaml::from_str(arg).map_err(|err| InvalidConfigError::InvalidYaml(err))?;
485
486 let parameters = yaml_map
487 .iter()
488 .map(|(key, value)| {
489 let typed_key: Parameter = key
490 .parse()
491 .map_err(|err| InvalidConfigError::UnknownParameter(err, key.to_owned()))?;
492
493 let old_value =
494 if let Some(s) = &value.old { Some(parse_parameter_value(s)?) } else { None };
495
496 let new_value =
497 if let Some(s) = &value.new { Some(parse_parameter_value(s)?) } else { None };
498
499 Ok((typed_key, (old_value, new_value)))
500 })
501 .collect::<Result<BTreeMap<_, _>, _>>()?;
502 Ok(ParameterTableDiff { parameters })
503 }
504}
505
506fn parse_parameter_value(value: &serde_yaml::Value) -> Result<ParameterValue, InvalidConfigError> {
508 Ok(serde_yaml::from_value(canonicalize_yaml_value(value)?)
509 .map_err(|err| InvalidConfigError::InvalidYaml(err))?)
510}
511
512fn canonicalize_yaml_value(
514 value: &serde_yaml::Value,
515) -> Result<serde_yaml::Value, InvalidConfigError> {
516 Ok(match value {
517 serde_yaml::Value::String(s) => canonicalize_yaml_string(s)?,
518 serde_yaml::Value::Mapping(m) => serde_yaml::Value::Mapping(
519 m.iter()
520 .map(|(key, value)| {
521 let canonical_value = canonicalize_yaml_value(value)?;
522 Ok((key.clone(), canonical_value))
523 })
524 .collect::<Result<_, _>>()?,
525 ),
526 _ => value.clone(),
527 })
528}
529
530fn canonicalize_yaml_string(value: &str) -> Result<serde_yaml::Value, InvalidConfigError> {
538 if value.is_empty() {
539 return Ok(serde_yaml::Value::Null);
540 }
541 if value.bytes().all(|c| c.is_ascii_digit() || c == '_' as u8) {
542 let mut raw_number = value.to_owned();
543 raw_number.retain(char::is_numeric);
544 if raw_number.len() < 20 {
548 serde_yaml::from_str(&raw_number)
549 .map_err(|err| InvalidConfigError::ValueParseError(err, value.to_owned()))
550 } else {
551 Ok(serde_yaml::Value::String(raw_number))
552 }
553 } else {
554 Ok(serde_yaml::Value::String(value.to_owned()))
555 }
556}
557
558#[cfg(test)]
559mod tests {
560 use super::{
561 InvalidConfigError, ParameterTable, ParameterTableDiff, ParameterValue,
562 parse_parameter_value,
563 };
564 use crate::Parameter;
565 use assert_matches::assert_matches;
566 use std::collections::BTreeMap;
567
568 #[track_caller]
569 fn check_parameter_table(
570 base_config: &str,
571 diffs: &[&str],
572 expected: impl IntoIterator<Item = (Parameter, &'static str)>,
573 ) {
574 let mut params: ParameterTable = base_config.parse().unwrap();
575 for diff in diffs {
576 let diff: ParameterTableDiff = diff.parse().unwrap();
577 params.apply_diff(diff).unwrap();
578 }
579
580 let expected_map = BTreeMap::from_iter(expected.into_iter().map(|(param, value)| {
581 (param, {
582 assert!(!value.is_empty(), "omit the parameter in the test instead");
583 parse_parameter_value(
584 &serde_yaml::from_str(value).expect("Test data has invalid YAML"),
585 )
586 .unwrap()
587 })
588 }));
589
590 assert_eq!(params.parameters, expected_map);
591 }
592
593 #[track_caller]
594 fn check_invalid_parameter_table(base_config: &str, diffs: &[&str]) -> InvalidConfigError {
595 let params = base_config.parse();
596
597 let result = params.and_then(|params: ParameterTable| {
598 diffs.iter().try_fold(params, |mut params, diff| {
599 params.apply_diff(diff.parse()?)?;
600 Ok(params)
601 })
602 });
603
604 match result {
605 Ok(_) => panic!("Input should have parser error"),
606 Err(err) => err,
607 }
608 }
609
610 static BASE_0: &str = include_str!("fixture_base_0.yml");
611 static BASE_1: &str = include_str!("fixture_base_1.yml");
612 static DIFF_0: &str = include_str!("fixture_diff_0.yml");
613 static DIFF_1: &str = include_str!("fixture_diff_1.yml");
614
615 #[test]
621 fn test_empty_parameter_table() {
622 check_parameter_table("", &[], []);
623 }
624
625 #[test]
627 fn test_basic_parameter_table() {
628 check_parameter_table(
629 BASE_0,
630 &[],
631 [
632 (Parameter::RegistrarAccountId, "\"registrar\""),
633 (Parameter::MinAllowedTopLevelAccountLength, "32"),
634 (Parameter::StorageAmountPerByte, "\"0.0001 N\""),
635 (Parameter::StorageNumBytesAccount, "100"),
636 (Parameter::StorageNumExtraBytesRecord, "40"),
637 (Parameter::BurntGasReward, "{ numerator: 1_000_000, denominator: 300 }"),
638 (
639 Parameter::WasmStorageReadBase,
640 "{ gas: 50_000_000_000, compute: 100_000_000_000 }",
641 ),
642 ],
643 );
644 }
645
646 #[test]
648 fn test_basic_parameter_table_weird_syntax() {
649 check_parameter_table(
650 BASE_1,
651 &[],
652 [
653 (Parameter::RegistrarAccountId, "\"registrar\""),
654 (Parameter::MinAllowedTopLevelAccountLength, "32"),
655 (Parameter::StorageAmountPerByte, "\"0.0001 N\""),
656 (Parameter::StorageNumBytesAccount, "100"),
657 (Parameter::StorageNumExtraBytesRecord, "40"),
658 ],
659 );
660 }
661
662 #[test]
664 fn test_parameter_table_with_diff() {
665 check_parameter_table(
666 BASE_0,
667 &[DIFF_0],
668 [
669 (Parameter::RegistrarAccountId, "\"near\""),
670 (Parameter::MinAllowedTopLevelAccountLength, "32000"),
671 (Parameter::StorageAmountPerByte, "\"0.0001 N\""),
672 (Parameter::StorageNumBytesAccount, "100"),
673 (Parameter::StorageNumExtraBytesRecord, "40"),
674 (Parameter::WasmRegularOpCost, "3856371"),
675 (Parameter::BurntGasReward, "{ numerator: 2_000_000, denominator: 500 }"),
676 (
677 Parameter::WasmStorageReadBase,
678 "{ gas: 50_000_000_000, compute: 200_000_000_000 }",
679 ),
680 ],
681 );
682 }
683
684 #[test]
686 fn test_parameter_table_with_diffs() {
687 check_parameter_table(
688 BASE_0,
689 &[DIFF_0, DIFF_1],
690 [
691 (Parameter::RegistrarAccountId, "\"registrar\""),
692 (Parameter::MinAllowedTopLevelAccountLength, "32000"),
693 (Parameter::StorageAmountPerByte, "\"0.0001 N\""),
694 (Parameter::StorageNumBytesAccount, "100"),
695 (Parameter::StorageNumExtraBytesRecord, "77"),
696 (Parameter::WasmRegularOpCost, "0"),
697 (Parameter::MaxMemoryPages, "512"),
698 (Parameter::BurntGasReward, "{ numerator: 3_000_000, denominator: 800 }"),
699 (
700 Parameter::WasmStorageReadBase,
701 "{ gas: 50_000_000_000, compute: 200_000_000_000 }",
702 ),
703 ],
704 );
705 }
706
707 #[test]
708 fn test_parameter_table_with_empty_value() {
709 let diff_with_empty_value = "min_allowed_top_level_account_length: { old: 32 }";
710 check_parameter_table(
711 BASE_0,
712 &[diff_with_empty_value],
713 [
714 (Parameter::RegistrarAccountId, "\"registrar\""),
715 (Parameter::StorageAmountPerByte, "\"0.0001 N\""),
716 (Parameter::StorageNumBytesAccount, "100"),
717 (Parameter::StorageNumExtraBytesRecord, "40"),
718 (Parameter::BurntGasReward, "{ numerator: 1_000_000, denominator: 300 }"),
719 (
720 Parameter::WasmStorageReadBase,
721 "{ gas: 50_000_000_000, compute: 100_000_000_000 }",
722 ),
723 ],
724 );
725 }
726
727 #[test]
728 fn test_parameter_table_invalid_key() {
729 assert_matches!(
731 check_invalid_parameter_table("invalid_key: 100", &[]),
732 InvalidConfigError::UnknownParameter(_, _)
733 );
734 }
735
736 #[test]
737 fn test_parameter_table_invalid_key_in_diff() {
738 assert_matches!(
739 check_invalid_parameter_table(
740 "wasm_regular_op_cost: 100",
741 &["invalid_key: { new: 100 }"]
742 ),
743 InvalidConfigError::UnknownParameter(_, _)
744 );
745 }
746
747 #[test]
748 fn test_parameter_table_no_key() {
749 assert_matches!(
750 check_invalid_parameter_table(": 100", &[]),
751 InvalidConfigError::InvalidYaml(_)
752 );
753 }
754
755 #[test]
756 fn test_parameter_table_no_key_in_diff() {
757 assert_matches!(
758 check_invalid_parameter_table("wasm_regular_op_cost: 100", &[": 100"]),
759 InvalidConfigError::InvalidYaml(_)
760 );
761 }
762
763 #[test]
764 fn test_parameter_table_wrong_separator() {
765 assert_matches!(
766 check_invalid_parameter_table("wasm_regular_op_cost=100", &[]),
767 InvalidConfigError::InvalidYaml(_)
768 );
769 }
770
771 #[test]
772 fn test_parameter_table_wrong_separator_in_diff() {
773 assert_matches!(
774 check_invalid_parameter_table(
775 "wasm_regular_op_cost: 100",
776 &["wasm_regular_op_cost=100"]
777 ),
778 InvalidConfigError::InvalidYaml(_)
779 );
780 }
781
782 #[test]
783 fn test_parameter_table_wrong_old_value() {
784 assert_matches!(
785 check_invalid_parameter_table(
786 "min_allowed_top_level_account_length: 3_200_000_000",
787 &["min_allowed_top_level_account_length: { old: 3_200_000, new: 1_600_000 }"]
788 ),
789 InvalidConfigError::WrongOldValue(
790 Parameter::MinAllowedTopLevelAccountLength,
791 expected,
792 found
793 ) => {
794 assert_eq!(expected, ParameterValue::U64(3200000000));
795 assert_eq!(found, ParameterValue::U64(3200000));
796 }
797 );
798 }
799
800 #[test]
801 fn test_parameter_table_no_old_value() {
802 assert_matches!(
803 check_invalid_parameter_table(
804 "min_allowed_top_level_account_length: 3_200_000_000",
805 &["min_allowed_top_level_account_length: { new: 1_600_000 }"]
806 ),
807 InvalidConfigError::OldValueExists(Parameter::MinAllowedTopLevelAccountLength, expected) => {
808 assert_eq!(expected, ParameterValue::U64(3200000000));
809 }
810 );
811 }
812
813 #[test]
814 fn test_parameter_table_old_parameter_undefined() {
815 assert_matches!(
816 check_invalid_parameter_table(
817 "min_allowed_top_level_account_length: 3_200_000_000",
818 &["wasm_regular_op_cost: { old: 3_200_000, new: 1_600_000 }"]
819 ),
820 InvalidConfigError::NoOldValueExists(Parameter::WasmRegularOpCost, found) => {
821 assert_eq!(found, ParameterValue::U64(3200000));
822 }
823 );
824 }
825
826 #[test]
827 fn test_parameter_table_yaml_map() {
828 let params: ParameterTable = BASE_0.parse().unwrap();
829 let yaml = params.yaml_map(
830 [
831 Parameter::RegistrarAccountId,
832 Parameter::MinAllowedTopLevelAccountLength,
833 Parameter::StorageAmountPerByte,
834 Parameter::StorageNumBytesAccount,
835 Parameter::StorageNumExtraBytesRecord,
836 Parameter::BurntGasReward,
837 Parameter::WasmStorageReadBase,
838 ]
839 .iter(),
840 );
841 assert_eq!(
842 yaml,
843 serde_yaml::to_value(
844 params
845 .parameters
846 .iter()
847 .map(|(key, value)| (key.to_string(), value))
848 .collect::<BTreeMap<_, _>>()
849 )
850 .unwrap()
851 );
852 }
853}