1use crate::config::RuntimeConfig;
2use crate::parameter_table::{ParameterTable, ParameterTableDiff};
3use std::collections::BTreeMap;
4use std::ops::Bound;
5use std::sync::Arc;
6use unc_primitives_core::types::ProtocolVersion;
7
8macro_rules! include_config {
9 ($file:expr) => {
10 include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/res/runtime_configs/", $file))
11 };
12}
13
14static BASE_CONFIG: &str = include_config!("parameters.yaml");
17
18static CONFIG_DIFFS: &[(ProtocolVersion, &str)] = &[
21 (35, include_config!("35.yaml")),
22 (42, include_config!("42.yaml")),
23 (46, include_config!("46.yaml")),
24 (48, include_config!("48.yaml")),
25 (49, include_config!("49.yaml")),
26 (50, include_config!("50.yaml")),
27 (52, include_config!("52.yaml")),
29 (53, include_config!("53.yaml")),
32 (55, include_config!("55.yaml")),
33 (57, include_config!("57.yaml")),
34 (59, include_config!("59.yaml")),
36 (61, include_config!("61.yaml")),
37 (62, include_config!("62.yaml")),
38 (63, include_config!("63.yaml")),
39 (64, include_config!("64.yaml")),
40 (129, include_config!("129.yaml")),
41 (138, include_config!("138.yaml")),
43];
44
45pub static INITIAL_TESTNET_CONFIG: &str = include_config!("parameters_testnet.yaml");
47
48#[derive(Clone, Debug)]
50pub struct RuntimeConfigStore {
51 store: BTreeMap<ProtocolVersion, Arc<RuntimeConfig>>,
53}
54
55impl RuntimeConfigStore {
56 pub fn new(genesis_runtime_config: Option<&RuntimeConfig>) -> Self {
67 let mut params: ParameterTable =
68 BASE_CONFIG.parse().expect("Failed parsing base parameter file.");
69
70 let mut store = BTreeMap::new();
71 #[cfg(not(feature = "calimero_zero_storage"))]
72 {
73 let initial_config = RuntimeConfig::new(¶ms).unwrap_or_else(|err| panic!("Failed generating `RuntimeConfig` from parameters for base parameter file. Error: {err}"));
74 store.insert(0, Arc::new(initial_config));
75 }
76 #[cfg(feature = "calimero_zero_storage")]
77 {
78 let mut initial_config = RuntimeConfig::new(¶ms).unwrap_or_else(|err| panic!("Failed generating `RuntimeConfig` from parameters for base parameter file. Error: {err}"));
79 initial_config.fees.storage_usage_config.storage_amount_per_byte = 0;
80 store.insert(0, Arc::new(initial_config));
81 }
82
83 for (protocol_version, diff_bytes) in CONFIG_DIFFS {
84 let diff :ParameterTableDiff= diff_bytes.parse().unwrap_or_else(|err| panic!("Failed parsing runtime parameters diff for version {protocol_version}. Error: {err}"));
85 params.apply_diff(diff).unwrap_or_else(|err| panic!("Failed applying diff to `RuntimeConfig` for version {protocol_version}. Error: {err}"));
86 #[cfg(not(feature = "calimero_zero_storage"))]
87 store.insert(
88 *protocol_version,
89 Arc::new(RuntimeConfig::new(¶ms).unwrap_or_else(|err| panic!("Failed generating `RuntimeConfig` from parameters for version {protocol_version}. Error: {err}"))),
90 );
91 #[cfg(feature = "calimero_zero_storage")]
92 {
93 let mut runtime_config = RuntimeConfig::new(¶ms).unwrap_or_else(|err| panic!("Failed generating `RuntimeConfig` from parameters for version {protocol_version}. Error: {err}"));
94 runtime_config.fees.storage_usage_config.storage_amount_per_byte = 0;
95 store.insert(*protocol_version, Arc::new(runtime_config));
96 }
97 }
98
99 if let Some(runtime_config) = genesis_runtime_config {
100 let mut config = runtime_config.clone();
101 store.insert(0, Arc::new(config.clone()));
102
103 config.fees.storage_usage_config.storage_amount_per_byte = 10u128.pow(19);
104 store.insert(42, Arc::new(config));
105 }
106
107 Self { store }
108 }
109
110 pub fn for_chain_id(chain_id: &str) -> Self {
117 match chain_id {
118 unc_primitives_core::chains::TESTNET => {
119 let genesis_runtime_config = RuntimeConfig::initial_testnet_config();
120 Self::new(Some(&genesis_runtime_config))
121 }
122 _ => Self::new(None),
123 }
124 }
125
126 pub fn with_one_config(runtime_config: RuntimeConfig) -> Self {
128 Self { store: BTreeMap::from_iter([(0, Arc::new(runtime_config))].iter().cloned()) }
129 }
130
131 pub fn test() -> Self {
133 Self::with_one_config(RuntimeConfig::test())
134 }
135
136 pub fn free() -> Self {
138 Self::with_one_config(RuntimeConfig::free())
139 }
140
141 pub fn get_config(&self, protocol_version: ProtocolVersion) -> &Arc<RuntimeConfig> {
143 self.store
144 .range((Bound::Unbounded, Bound::Included(protocol_version)))
145 .next_back()
146 .unwrap_or_else(|| {
147 panic!("Not found RuntimeConfig for protocol version {}", protocol_version)
148 })
149 .1
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156 use crate::cost::{ActionCosts, ExtCosts};
157 use std::collections::HashSet;
158 use unc_primitives_core::version::ProtocolFeature::{
159 LowerDataReceiptAndEcrecoverBaseCost, LowerStorageCost, LowerStorageKeyLimit,
160 };
161
162 const GENESIS_PROTOCOL_VERSION: ProtocolVersion = 29;
163 const RECEIPTS_DEPTH: u64 = 63;
164
165 #[test]
166 fn all_configs_are_specified() {
167 let file_versions =
168 std::fs::read_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/res/runtime_configs/"))
169 .expect("can open config directory");
170 let mut files = file_versions
171 .into_iter()
172 .map(|de| {
173 de.expect("direntry should read successfully")
174 .path()
175 .file_name()
176 .expect("direntry should have a filename")
177 .to_string_lossy()
178 .into_owned()
179 })
180 .collect::<HashSet<_>>();
181
182 for (ver, _) in super::CONFIG_DIFFS {
183 assert!(files.remove(&format!("{ver}.yaml")), "{ver}.yaml file is missing?");
184 }
185
186 for file in files {
187 let Some((name, "yaml")) = file.rsplit_once(".") else { continue };
188 let Ok(version_num) = name.parse::<u32>() else { continue };
189 panic!("CONFIG_DIFFS does not contain reference to the {version_num}.yaml file!");
190 }
191 }
192
193 #[test]
194 fn test_max_prepaid_gas() {
195 let store = RuntimeConfigStore::new(None);
196 for (protocol_version, config) in store.store.iter() {
197 assert!(
198 config.wasm_config.limit_config.max_total_prepaid_gas
199 / config.fees.min_receipt_with_function_call_gas()
200 <= 63,
201 "The maximum desired depth of receipts for protocol version {} should be at most {}",
202 protocol_version,
203 RECEIPTS_DEPTH
204 );
205 }
206 }
207
208 #[test]
209 #[cfg(not(feature = "calimero_zero_storage"))]
210 fn test_lower_storage_cost() {
211 let store = RuntimeConfigStore::new(None);
212 let base_cfg = store.get_config(GENESIS_PROTOCOL_VERSION);
213 let new_cfg = store.get_config(LowerStorageCost.protocol_version());
214 assert!(base_cfg.storage_amount_per_byte() > new_cfg.storage_amount_per_byte());
215 }
216
217 #[test]
218 fn test_override_account_length() {
219 let base_store = RuntimeConfigStore::new(None);
221 let base_cfg = base_store.get_config(GENESIS_PROTOCOL_VERSION);
222 assert_eq!(base_cfg.account_creation_config.min_allowed_top_level_account_length, 32);
223
224 let mut cfg = base_cfg.as_ref().clone();
225 cfg.account_creation_config.min_allowed_top_level_account_length = 0;
226
227 let new_store = RuntimeConfigStore::new(Some(&cfg));
229 let new_cfg = new_store.get_config(GENESIS_PROTOCOL_VERSION);
230 assert_eq!(new_cfg.account_creation_config.min_allowed_top_level_account_length, 0);
231 }
232
233 #[test]
234 fn test_lower_data_receipt_cost() {
235 let store = RuntimeConfigStore::new(None);
236 let base_cfg = store.get_config(LowerStorageCost.protocol_version());
237 let new_cfg = store.get_config(LowerDataReceiptAndEcrecoverBaseCost.protocol_version());
238 assert!(
239 base_cfg.fees.fee(ActionCosts::new_data_receipt_base).send_sir
240 > new_cfg.fees.fee(ActionCosts::new_data_receipt_base).send_sir
241 );
242 assert!(
243 base_cfg.fees.fee(ActionCosts::new_data_receipt_byte).send_sir
244 > new_cfg.fees.fee(ActionCosts::new_data_receipt_byte).send_sir
245 );
246 }
247
248 #[test]
251 #[cfg(not(feature = "calimero_zero_storage"))]
252 fn test_override_runtime_config() {
253 let store = RuntimeConfigStore::new(None);
254 let config = store.get_config(0);
255
256 let mut base_params = BASE_CONFIG.parse().unwrap();
257 let base_config = RuntimeConfig::new(&base_params).unwrap();
258 assert_eq!(config.as_ref(), &base_config);
259
260 let config = store.get_config(LowerStorageCost.protocol_version());
261 assert_eq!(base_config.storage_amount_per_byte(), 100_000_000_000_000_000_000u128);
262 assert_eq!(config.storage_amount_per_byte(), 10_000_000_000_000_000_000u128);
263 assert_eq!(config.fees.fee(ActionCosts::new_data_receipt_base).send_sir, 4_697_339_419_375);
264 assert_ne!(config.as_ref(), &base_config);
265 assert_ne!(
266 config.as_ref(),
267 store.get_config(LowerStorageCost.protocol_version() - 1).as_ref()
268 );
269
270 for (ver, diff) in &CONFIG_DIFFS[..] {
271 if *ver <= LowerStorageCost.protocol_version() {
272 base_params.apply_diff(diff.parse().unwrap()).unwrap();
273 }
274 }
275 let expected_config = RuntimeConfig::new(&base_params).unwrap();
276 assert_eq!(**config, expected_config);
277
278 let config = store.get_config(LowerDataReceiptAndEcrecoverBaseCost.protocol_version());
279 assert_eq!(config.fees.fee(ActionCosts::new_data_receipt_base).send_sir, 36_486_732_312);
280 for (ver, diff) in &CONFIG_DIFFS[..] {
281 if *ver <= LowerStorageCost.protocol_version() {
282 continue;
283 } else if *ver <= LowerDataReceiptAndEcrecoverBaseCost.protocol_version() {
284 base_params.apply_diff(diff.parse().unwrap()).unwrap();
285 }
286 }
287 let expected_config = RuntimeConfig::new(&base_params).unwrap();
288 assert_eq!(config.as_ref(), &expected_config);
289 }
290
291 #[test]
292 fn test_lower_ecrecover_base_cost() {
293 let store = RuntimeConfigStore::new(None);
294 let base_cfg = store.get_config(LowerStorageCost.protocol_version());
295 let new_cfg = store.get_config(LowerDataReceiptAndEcrecoverBaseCost.protocol_version());
296 assert!(
297 base_cfg.wasm_config.ext_costs.gas_cost(ExtCosts::ecrecover_base)
298 > new_cfg.wasm_config.ext_costs.gas_cost(ExtCosts::ecrecover_base)
299 );
300 }
301
302 #[test]
303 fn test_lower_max_length_storage_key() {
304 let store = RuntimeConfigStore::new(None);
305 let base_cfg = store.get_config(LowerStorageKeyLimit.protocol_version() - 1);
306 let new_cfg = store.get_config(LowerStorageKeyLimit.protocol_version());
307 assert!(
308 base_cfg.wasm_config.limit_config.max_length_storage_key
309 > new_cfg.wasm_config.limit_config.max_length_storage_key
310 );
311 }
312
313 #[test]
318 #[cfg(not(feature = "nightly"))]
319 #[cfg(not(feature = "calimero_zero_storage"))]
320 fn test_json_unchanged() {
321 use crate::view::RuntimeConfigView;
322 use unc_primitives_core::version::PROTOCOL_VERSION;
323
324 let store = RuntimeConfigStore::new(None);
325 let mut any_failure = false;
326
327 for version in store.store.keys() {
328 let snapshot_name = format!("{version}.json");
329 let config_view = RuntimeConfigView::from(store.get_config(*version).as_ref().clone());
330 any_failure |= std::panic::catch_unwind(|| {
331 insta::assert_json_snapshot!(snapshot_name, config_view, { ".wasm_config.vm_kind" => "<REDACTED>"});
332 })
333 .is_err();
334 }
335
336 {
338 let mut params: ParameterTable = BASE_CONFIG.parse().unwrap();
339 for (_, diff_bytes) in
340 CONFIG_DIFFS.iter().filter(|(version, _)| *version <= PROTOCOL_VERSION)
341 {
342 params.apply_diff(diff_bytes.parse().unwrap()).unwrap();
343 }
344 insta::with_settings!({
345 snapshot_path => "../res/runtime_configs",
346 prepend_module_to_snapshot => false,
347 description => "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
348 omit_expression => true,
349 }, {
350 any_failure |= std::panic::catch_unwind(|| {
351 insta::assert_display_snapshot!("parameters", params);
352 }).is_err();
353 });
354 }
355
356 let params = INITIAL_TESTNET_CONFIG.parse().unwrap();
358 let new_genesis_runtime_config = RuntimeConfig::new(¶ms).unwrap();
359 let testnet_store = RuntimeConfigStore::new(Some(&new_genesis_runtime_config));
360
361 for version in testnet_store.store.keys() {
362 let snapshot_name = format!("testnet_{version}.json");
363 let config_view = RuntimeConfigView::from(store.get_config(*version).as_ref().clone());
364 any_failure |= std::panic::catch_unwind(|| {
365 insta::assert_json_snapshot!(snapshot_name, config_view, { ".wasm_config.vm_kind" => "<REDACTED>"});
366 })
367 .is_err();
368 }
369 if any_failure {
370 panic!("some snapshot assertions failed");
371 }
372 }
373
374 #[test]
375 #[cfg(feature = "calimero_zero_storage")]
376 fn test_calimero_storage_costs_zero() {
377 let store = RuntimeConfigStore::new(None);
378 for (_, config) in store.store.iter() {
379 assert_eq!(config.storage_amount_per_byte(), 0u128);
380 }
381 }
382}