diff --git a/Cargo.lock b/Cargo.lock index 818378ba28..52de724fa6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4095,6 +4095,7 @@ dependencies = [ "tycho-crypto", "tycho-network", "tycho-rpc", + "tycho-slasher", "tycho-storage", "tycho-types", "tycho-util", @@ -4146,6 +4147,7 @@ dependencies = [ "tycho-crypto", "tycho-executor", "tycho-network", + "tycho-slasher-traits", "tycho-storage", "tycho-types", "tycho-util", @@ -4191,6 +4193,7 @@ dependencies = [ "tracing-subscriber", "tycho-crypto", "tycho-network", + "tycho-slasher-traits", "tycho-storage", "tycho-types", "tycho-util", @@ -4427,6 +4430,41 @@ dependencies = [ "tycho-util", ] +[[package]] +name = "tycho-slasher" +version = "0.3.9" +dependencies = [ + "anyhow", + "arc-swap", + "dashmap", + "futures-util", + "metrics", + "parking_lot", + "scopeguard", + "serde", + "tl-proto", + "tokio", + "tokio-util", + "tracing", + "tycho-block-util", + "tycho-core", + "tycho-crypto", + "tycho-slasher-traits", + "tycho-storage", + "tycho-types", + "tycho-util", + "weedb", +] + +[[package]] +name = "tycho-slasher-traits" +version = "0.3.9" +dependencies = [ + "indexmap", + "tycho-types", + "tycho-util", +] + [[package]] name = "tycho-storage" version = "0.3.9" @@ -4461,8 +4499,7 @@ dependencies = [ [[package]] name = "tycho-types" version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85ebf3e9cb2b0e515adc25e4b30f46bd70cbd4d67edfaca7e3440c0ab7405086" +source = "git+https://github.com/broxus/tycho-types.git?rev=aeea4e8d007e8a64439d3d923a3752fc823b2256#aeea4e8d007e8a64439d3d923a3752fc823b2256" dependencies = [ "ahash", "anyhow", @@ -4495,8 +4532,7 @@ dependencies = [ [[package]] name = "tycho-types-abi-proc" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c813c08a03554252747f9e5e88485d9af4c30077394a1c3bb6d774ddca56b07" +source = "git+https://github.com/broxus/tycho-types.git?rev=aeea4e8d007e8a64439d3d923a3752fc823b2256#aeea4e8d007e8a64439d3d923a3752fc823b2256" dependencies = [ "anyhow", "proc-macro2", @@ -4507,8 +4543,7 @@ dependencies = [ [[package]] name = "tycho-types-proc" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad05cf4ab89631f8c11d85c3aa80f781502440f75361d251f866e0d76ae9d31" +source = "git+https://github.com/broxus/tycho-types.git?rev=aeea4e8d007e8a64439d3d923a3752fc823b2256#aeea4e8d007e8a64439d3d923a3752fc823b2256" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index bcd1fac120..db7fa0d5dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,14 +19,16 @@ members = [ "gen-protos", "network", "rpc", + "rpc-subscriptions", "simulator", + "slasher", + "slasher-traits", "storage", "storage-traits", "tycho-build-info", "util", "util-proc", "wu-tuner", - "rpc-subscriptions", ] [workspace.dependencies] @@ -61,7 +63,10 @@ exponential-backoff = "1.2.1" fdlimit = "0.3.0" futures-executor = "0.3" futures-util = "0.3" -governor = { version = "0.9", default-features = false, features = ["std", "quanta"] } +governor = { version = "0.9", default-features = false, features = [ + "std", + "quanta", +] } getip = "0.2.1" hdrhistogram = "7.5.4" hex = "0.4" @@ -86,7 +91,10 @@ proc-macro2 = "1.0" prost = "0.14.3" prost-build = "0.14.3" quick_cache = "0.6.21" -quinn = { version = "0.11.9", default-features = false, features = ["runtime-tokio", "rustls"] } +quinn = { version = "0.11.9", default-features = false, features = [ + "runtime-tokio", + "rustls", +] } quote = "1.0" object_store = "0.13" rand = "0.9" @@ -129,7 +137,9 @@ tikv-jemalloc-ctl = { version = "0.6.1", features = ["stats"] } tl-proto = "0.5.4" tokio = { version = "1", default-features = false } tokio-stream = "0.1.18" -tokio-util = { version = "0.7.18", default-features = false, features = ["codec"] } +tokio-util = { version = "0.7.18", default-features = false, features = [ + "codec", +] } tower = "0.5" tower-http = "0.6" tracing = "0.1" @@ -158,6 +168,8 @@ tycho-core = { path = "./core", version = "0.3.9" } tycho-network = { path = "./network", version = "0.3.9" } tycho-rpc-subscriptions = { path = "./rpc-subscriptions", version = "0.3.9" } tycho-rpc = { path = "./rpc", version = "0.3.9" } +tycho-slasher = { path = "./slasher", version = "0.3.9" } +tycho-slasher-traits = { path = "./slasher-traits", version = "0.3.9" } tycho-storage = { path = "./storage", version = "0.3.9" } tycho-storage-traits = { path = "./storage-traits", version = "0.3.9" } tycho-util = { path = "./util", version = "0.3.9" } @@ -166,6 +178,7 @@ tycho-wu-tuner = { path = "./wu-tuner", version = "0.3.9" } [patch.crates-io] # patches here +tycho-types = { git = "https://github.com/broxus/tycho-types.git", rev = "aeea4e8d007e8a64439d3d923a3752fc823b2256" } [workspace.lints.rust] future_incompatible = "warn" diff --git a/block-util/src/block/block_proof_stuff.rs b/block-util/src/block/block_proof_stuff.rs index c59e8ce743..4f407637f0 100644 --- a/block-util/src/block/block_proof_stuff.rs +++ b/block-util/src/block/block_proof_stuff.rs @@ -284,7 +284,7 @@ impl BlockProofStuff { let weight = match signatures .signatures - .check_signatures(&subset.validators, &checked_data) + .check_signatures(subset.validators.iter().map(AsRef::as_ref), &checked_data) { Ok(weight) => weight, Err(e) => anyhow::bail!("proof contains invalid signatures: {e:?}"), @@ -388,15 +388,16 @@ impl BlockProofStuff { validator_set: &ValidatorSet, shuffle_validators: bool, ) -> Result { - let cc_seqno = self + let Some(vset_switch_round) = self .inner .proof .signatures .as_ref() - .map(|s| s.validator_info.catchain_seqno) - .unwrap_or_default(); - - ValidatorSubsetInfo::compute_standard(validator_set, cc_seqno, shuffle_validators) + .map(|s| s.consensus_info.vset_switch_round) + else { + anyhow::bail!("no `consensus_info` to compute subset from"); + }; + ValidatorSubsetInfo::compute_standard(validator_set, vset_switch_round, shuffle_validators) } } @@ -520,18 +521,18 @@ fn pre_check_key_block_proof(virt_block: &Block) -> Result<()> { #[derive(Clone, Debug)] pub struct ValidatorSubsetInfo { - pub validators: Vec, + pub validators: Vec, pub short_hash: u32, } impl ValidatorSubsetInfo { pub fn compute_standard( validator_set: &ValidatorSet, - cc_seqno: u32, + vset_switch_round: u32, shuffle_validators: bool, ) -> Result { let Some((validators, short_hash)) = - validator_set.compute_mc_subset(cc_seqno, shuffle_validators) + validator_set.compute_mc_subset_indexed(vset_switch_round, shuffle_validators) else { anyhow::bail!("failed to compute a validator subset"); }; diff --git a/cli/Cargo.toml b/cli/Cargo.toml index dfaf84f4e6..3c17bb4274 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,7 +1,13 @@ [package] name = "tycho-cli" description = "Node CLI." -include = ["src/**/*.rs", "res/**/*.boc", "./LICENSE-*", "./README.md", "build.rs"] +include = [ + "src/**/*.rs", + "res/**/*.boc", + "./LICENSE-*", + "./README.md", + "build.rs", +] version.workspace = true authors.workspace = true edition.workspace = true @@ -55,14 +61,15 @@ weedb = { workspace = true } # local deps tycho-block-util = { workspace = true } tycho-collator = { workspace = true } +tycho-consensus = { workspace = true } tycho-control = { workspace = true, features = ["full"] } tycho-core = { workspace = true, features = ["cli"] } tycho-network = { workspace = true } tycho-rpc = { workspace = true, features = ["http2"] } +tycho-slasher = { workspace = true } tycho-storage = { workspace = true } tycho-util = { workspace = true, features = ["cli"] } tycho-wu-tuner = { workspace = true } -tycho-consensus = { workspace = true } [dev-dependencies] tycho-collator = { workspace = true, features = ["test"] } diff --git a/cli/res/slasher_code.boc b/cli/res/slasher_code.boc new file mode 100644 index 0000000000..87ef7baa68 Binary files /dev/null and b/cli/res/slasher_code.boc differ diff --git a/cli/src/cmd/tools/gen_zerostate.rs b/cli/src/cmd/tools/gen_zerostate.rs index 779622679b..906e0b7e26 100644 --- a/cli/src/cmd/tools/gen_zerostate.rs +++ b/cli/src/cmd/tools/gen_zerostate.rs @@ -211,6 +211,10 @@ impl ZerostateConfig { fundamental_addresses.set(minter_address, ())?; } + if let Some(slasher_params) = self.params.get::()? { + fundamental_addresses.set(slasher_params.address, ())?; + } + self.params.set::(&fundamental_addresses)?; } @@ -500,9 +504,9 @@ impl Default for ZerostateConfig { global_id: 0, config_public_key: *zero_public_key(), minter_public_key: None, - config_balance: Tokens::new(500_000_000_000), // 500 + config_balance: Tokens::new(500_000_000_000), config_code: None, - elector_balance: Tokens::new(500_000_000_000), // 500 + elector_balance: Tokens::new(500_000_000_000), elector_code: None, accounts: Default::default(), validators: Default::default(), @@ -808,7 +812,6 @@ fn make_default_params() -> Result { // Param 31 params.set_fundamental_addresses(&[HashBytes([0x00; 32]), HashBytes([0x33; 32])])?; - // Param 43 params.set_size_limits(&SizeLimitsConfig { max_msg_bits: 1 << 21, diff --git a/cli/src/node/config.rs b/cli/src/node/config.rs index 31da0f5566..aebde661c4 100644 --- a/cli/src/node/config.rs +++ b/cli/src/node/config.rs @@ -10,6 +10,7 @@ use tycho_control::ControlServerConfig; use tycho_core::node::NodeBaseConfig; use tycho_crypto::ed25519; use tycho_rpc::RpcConfig; +use tycho_slasher::SlasherConfig; use tycho_types::cell::HashBytes; use tycho_types::models::StdAddr; use tycho_util::cli::config::ThreadPoolConfig; @@ -165,6 +166,9 @@ pub struct NodeConfig { pub validator: ValidatorStdImplConfig, + #[partial] + pub slasher: SlasherConfig, + #[partial] pub rpc: Option, @@ -191,6 +195,7 @@ impl Default for NodeConfig { mempool: Default::default(), internal_queue: Default::default(), validator: Default::default(), + slasher: Default::default(), rpc: Some(Default::default()), control: Default::default(), metrics: Some(Default::default()), diff --git a/cli/src/node/mod.rs b/cli/src/node/mod.rs index 3934c6febb..7c4b71f278 100644 --- a/cli/src/node/mod.rs +++ b/cli/src/node/mod.rs @@ -32,6 +32,7 @@ use tycho_core::node::{NodeBase, NodeKeys}; use tycho_core::storage::NodeSyncState; use tycho_network::InboundRequestMeta; use tycho_rpc::{NodeBaseInitRpc, RpcConfig}; +use tycho_slasher::SlasherConfig; use tycho_types::models::*; use tycho_util::futures::JoinTask; use tycho_wu_tuner::service::WuTunerServiceBuilder; @@ -57,6 +58,7 @@ pub struct Node { collator_config: CollatorConfig, validator_config: ValidatorStdImplConfig, internal_queue_config: QueueConfig, + slasher_config: SlasherConfig, mempool_config_override: Option, /// Path to the work units tuner config. @@ -131,6 +133,7 @@ impl Node { collator_config: node_config.collator, validator_config: node_config.validator, internal_queue_config: node_config.internal_queue, + slasher_config: node_config.slasher, mempool_config_override: global_config.mempool, wu_tuner_config_path, }) @@ -222,6 +225,16 @@ impl Node { let top_shards = mc_state.get_top_shards()?; message_queue_adapter.clear_uncommitted_state(&top_shards)?; + // NOTE: Stub + let slasher = tycho_slasher::Slasher::new( + base.keypair.clone(), + tycho_slasher::StubSlasherContract, + base.blockchain_rpc_client.clone(), + &base.storage_context, + self.slasher_config, + ) + .context("failed to create slasher")?; + let validator = ValidatorStdImpl::new( ValidatorNetworkContext { network: base.network.clone(), @@ -231,6 +244,7 @@ impl Node { }, base.keypair.clone(), self.validator_config, + slasher.validator_events_listener(), ); // Explicitly handle the initial state @@ -257,6 +271,7 @@ impl Node { CollatorStdImplFactory { wu_tuner_event_sender: Some(wu_tuner.event_sender.clone()), }, + slasher.validator_events_listener(), self.mempool_config_override.clone(), ); let collator_subcriber = CollatorStateSubscriber { @@ -349,7 +364,12 @@ impl Node { ( ShardStateApplier::new( base.core_storage.clone(), - (collator_subcriber, rpc_state_subscriber, control_server), + ( + collator_subcriber, + rpc_state_subscriber, + control_server, + slasher, + ), ), rpc_block_subscriber, base.validator_resolver().clone(), diff --git a/collator/Cargo.toml b/collator/Cargo.toml index 1f9f6b901a..a09876787f 100644 --- a/collator/Cargo.toml +++ b/collator/Cargo.toml @@ -73,6 +73,7 @@ tycho-consensus = { workspace = true } tycho-core = { workspace = true } tycho-executor = { workspace = true } tycho-network = { workspace = true } +tycho-slasher-traits = { workspace = true } tycho-storage = { workspace = true } tycho-util = { workspace = true } tycho-vm = { workspace = true } diff --git a/collator/src/collator/anchors_cache.rs b/collator/src/collator/anchors_cache.rs index 38eadfd9ea..992dec74b8 100644 --- a/collator/src/collator/anchors_cache.rs +++ b/collator/src/collator/anchors_cache.rs @@ -343,6 +343,7 @@ mod tests { chain_time, author: PeerId([0; 32]), externals: Default::default(), + stats: None, }) } diff --git a/collator/src/collator/do_collate/finalize.rs b/collator/src/collator/do_collate/finalize.rs index 48f723a60d..e72c6d381e 100644 --- a/collator/src/collator/do_collate/finalize.rs +++ b/collator/src/collator/do_collate/finalize.rs @@ -887,7 +887,7 @@ impl Phase { validator_info = session_update.apply( &mut consensus_info, next_session_start_round, - session_start.is_curr_switch_after_pause, + &session_start, )?; } let validator_info = validator_info.unwrap_or(ValidatorInfo { @@ -1483,10 +1483,11 @@ mod vset_update_start { is_consensus_info_overridden: bool, pub is_consensus_config_changed: bool, - pub is_curr_switch_after_pause: bool, + is_curr_switch_after_pause: bool, gen_chain_time_millis: u64, after_pause_round: u32, + catchain_seqno: u32, } impl KbNextSessionStart { @@ -1503,6 +1504,10 @@ mod vset_update_start { let after_pause_round = Self::after_pause_round(prev_processed_to_anchor, &prev_consensus_config); + let catchain_seqno = (prev_state_extra.validator_info.catchain_seqno) + .checked_add(1) + .context("catchain seqno overflow")?; + Ok(Self { is_consensus_info_overridden: { consensus_info != &prev_state_extra.consensus_info @@ -1514,6 +1519,7 @@ mod vset_update_start { gen_chain_time_millis: collation_data.get_gen_chain_time(), after_pause_round, + catchain_seqno, prev_consensus_config, }) @@ -1603,7 +1609,7 @@ mod vset_update_start { &self, consensus_info: &mut ConsensusInfo, next_session_start_round: u32, - is_curr_switch_after_pause: bool, + session_start: &KbNextSessionStart, ) -> Result> { let is_vset_same = *self.current_vset.repr_hash() == self.prev_vset_hash; let is_shuffle_same = self.shuffle_mc_validators == self.prev_shuffle_mc_validators; @@ -1618,7 +1624,7 @@ mod vset_update_start { // take prev_* attributes for mempool to calculate a subset from v_set (if used); // also mempool may skip a short-lived session that ended sooner than schedule // was applied in mempool (but subset rotations should not be that short) - if !is_curr_switch_after_pause { + if !session_start.is_curr_switch_after_pause { consensus_info.prev_shuffle_mc_validators = self.prev_shuffle_mc_validators; consensus_info.prev_vset_switch_round = consensus_info.vset_switch_round; } @@ -1633,7 +1639,7 @@ mod vset_update_start { else { anyhow::bail!( "Error calculating subset of validators for next session \ - (shard_id = {}, session_seqno = {next_session_start_round})", + (shard_id = {}, start_round = {next_session_start_round})", ShardIdent::MASTERCHAIN, ); }; @@ -1641,7 +1647,7 @@ mod vset_update_start { Ok(Some(ValidatorInfo { validator_list_hash_short, // TODO: rename field in types - catchain_seqno: next_session_start_round, + catchain_seqno: session_start.catchain_seqno, nx_cc_updated: true, })) } @@ -1683,18 +1689,26 @@ mod vset_update_start { } } + fn random_session_start() -> KbNextSessionStart { + KbNextSessionStart { + prev_consensus_config: default_test_config().conf.consensus, + catchain_seqno: random(), + is_consensus_info_overridden: random(), + is_consensus_config_changed: random(), + is_curr_switch_after_pause: random(), + gen_chain_time_millis: random(), + after_pause_round: random(), + } + } + #[test] fn genesis_override_overcomes_config_change() { let mut cons_info = random_consensus_info(); let before = cons_info; let start = KbNextSessionStart { - prev_consensus_config: default_test_config().conf.consensus, is_consensus_info_overridden: true, - is_consensus_config_changed: random(), - is_curr_switch_after_pause: random(), - gen_chain_time_millis: random(), - after_pause_round: random(), + ..random_session_start() }; let next_session_start = start.round(&mut cons_info, random()); @@ -1708,12 +1722,10 @@ mod vset_update_start { let mut cons_info = random_consensus_info(); let start = KbNextSessionStart { - prev_consensus_config: default_test_config().conf.consensus, is_consensus_info_overridden: false, // no guard here: may overwrite ANY genesis is_consensus_config_changed: true, - is_curr_switch_after_pause: random(), gen_chain_time_millis: 50_000, - after_pause_round: random(), + ..random_session_start() }; let next_session_start = start.round(&mut cons_info, 600); @@ -1767,6 +1779,7 @@ mod vset_update_start { KbNextSessionStart::after_pause_round(processed_up_to, &cons_conf); let start_1 = KbNextSessionStart { + catchain_seqno: 10, prev_consensus_config: cons_conf.clone(), is_consensus_info_overridden: false, is_consensus_config_changed: false, @@ -1780,49 +1793,45 @@ mod vset_update_start { assert_eq!(next_1, after_pause_round); let validator_info = stub_update - .apply(&mut cons_info, next_1, start_1.is_curr_switch_after_pause) - .unwrap() - .unwrap(); + .apply(&mut cons_info, next_1, &start_1) + .expect("must be Ok") + .expect("must be Some"); assert_eq!(cons_info.prev_vset_switch_round, 0); assert_eq!(cons_info.vset_switch_round, after_pause_round); - assert_eq!(validator_info.catchain_seqno, next_1); + assert_eq!(validator_info.catchain_seqno, start_1.catchain_seqno); // Second vset change while switch is still "applied/too close": push by full history. processed_up_to += 1; after_pause_round = KbNextSessionStart::after_pause_round(processed_up_to, &cons_conf); - let start_2 = { - let mut temp = start_1; - temp.is_curr_switch_after_pause = cons_info.vset_switch_round > after_pause_round; - temp.after_pause_round = after_pause_round; - assert!(!temp.is_curr_switch_after_pause); - temp + let start_2 = KbNextSessionStart { + is_curr_switch_after_pause: cons_info.vset_switch_round > after_pause_round, + after_pause_round, + ..start_1 }; + assert!(!start_2.is_curr_switch_after_pause); let next_2 = start_2.round(&mut cons_info, processed_up_to); assert!(next_2 > processed_up_to); assert_eq!(next_2, (next_1 + cons_conf.max_total_rounds() + 1)); - let validator_info = stub_update - .apply(&mut cons_info, next_2, start_2.is_curr_switch_after_pause) - .unwrap() - .unwrap(); + stub_update + .apply(&mut cons_info, next_2, &start_2) + .expect("must be Ok") + .expect("must be Some"); assert_eq!(cons_info.prev_vset_switch_round, next_1); assert_eq!(cons_info.vset_switch_round, next_2); - assert_eq!(validator_info.catchain_seqno, next_2); // Third vset change while switch is far in the future: keep the same switch round. processed_up_to += 1; after_pause_round = KbNextSessionStart::after_pause_round(processed_up_to, &cons_conf); - let start_3 = { - let mut temp = start_2; - temp.is_curr_switch_after_pause = cons_info.vset_switch_round > after_pause_round; - temp.after_pause_round = after_pause_round; - assert!(temp.is_curr_switch_after_pause); - temp + let start_3 = KbNextSessionStart { + is_curr_switch_after_pause: cons_info.vset_switch_round > after_pause_round, + after_pause_round, + ..start_2 }; assert!(start_3.is_curr_switch_after_pause); @@ -1830,17 +1839,21 @@ mod vset_update_start { assert!(next_3 > processed_up_to); assert_eq!(next_3, next_2); - let validator_info = stub_update - .apply(&mut cons_info, next_3, start_3.is_curr_switch_after_pause) - .unwrap() - .unwrap(); + stub_update + .apply(&mut cons_info, next_3, &start_3) + .expect("must be Ok") + .expect("must be Some"); assert_eq!(cons_info.prev_vset_switch_round, next_1); assert_eq!(cons_info.vset_switch_round, next_2); - assert_eq!(validator_info.catchain_seqno, next_2); } #[test] fn noop_if_v_set_unchanged() { + let start = KbNextSessionStart { + is_curr_switch_after_pause: true, + ..random_session_start() + }; + let is_shuffle = random(); let update = KbNextSessionUpdate { prev_shuffle_mc_validators: is_shuffle, @@ -1852,7 +1865,7 @@ mod vset_update_start { let mut cons_info = random_consensus_info(); let before = cons_info; - let validator_info = update.apply(&mut cons_info, random(), true).unwrap(); + let validator_info = update.apply(&mut cons_info, random(), &start).unwrap(); assert!(validator_info.is_none(), "{update:?} {cons_info:?}"); assert_eq!(cons_info, before, "{update:?} {cons_info:?}"); diff --git a/collator/src/collator/mod.rs b/collator/src/collator/mod.rs index 97fed8fc1c..8e55821c7a 100644 --- a/collator/src/collator/mod.rs +++ b/collator/src/collator/mod.rs @@ -72,6 +72,7 @@ pub(super) mod tests; #[cfg(test)] pub(crate) use messages_reader::tests::{TestInternalMessage, TestMessageFactory}; +use tycho_slasher_traits::ValidatorEventsListener; // FACTORY @@ -82,6 +83,7 @@ pub struct CollatorContext { pub config: Arc, pub collation_session: Arc, pub zerostate_id: ZerostateId, + pub stats_recorder: Arc, pub shard_id: ShardIdent, pub prev_blocks_ids: Vec, @@ -163,6 +165,7 @@ pub struct CollatorStdImpl { collation_session: Arc, zerostate_id: ZerostateId, + stats_recorder: Arc, mq_adapter: Arc>, mpool_adapter: Arc, state_node_adapter: Arc, @@ -257,6 +260,7 @@ impl CollatorStdImpl { config, collation_session, zerostate_id, + stats_recorder, shard_id, prev_blocks_ids, mempool_config_override, @@ -279,6 +283,7 @@ impl CollatorStdImpl { config, collation_session, zerostate_id, + stats_recorder, mq_adapter, mpool_adapter, state_node_adapter, @@ -1514,6 +1519,21 @@ impl CollatorStdImpl { ) .record(elapsed_from_prev_anchor); + if let Some(stats) = &next_anchor.stats { + let (catchain_seqno, vset_switch_round) = + self.collation_session.get_validation_session_id(); + + self.stats_recorder.on_anchor_import( + tycho_slasher_traits::ValidationSessionId { + catchain_seqno, + vset_switch_round, + }, + &self.next_block_info, + next_anchor.id, + stats.clone(), + ); + } + working_state.wu_used_from_last_anchor = 0; // time between anchors @@ -1721,6 +1741,21 @@ impl CollatorStdImpl { "imported next anchor, will notify collation manager", ); + if let Some(stats) = &next_anchor.stats { + let (catchain_seqno, vset_switch_round) = + self.collation_session.get_validation_session_id(); + + self.stats_recorder.on_anchor_import( + tycho_slasher_traits::ValidationSessionId { + catchain_seqno, + vset_switch_round, + }, + &self.next_block_info, + next_anchor.id, + stats.clone(), + ); + } + // this may start master block collation or cause next anchor import let res = CollatorResult::skipped( working_state.mc_data.block_id, diff --git a/collator/src/collator/tests/messages_reader_tests.rs b/collator/src/collator/tests/messages_reader_tests.rs index e601ea5fc3..a9ec5fa052 100644 --- a/collator/src/collator/tests/messages_reader_tests.rs +++ b/collator/src/collator/tests/messages_reader_tests.rs @@ -1816,6 +1816,7 @@ where author: PeerId(Default::default()), chain_time: anchor_ct, externals, + stats: None, }); self.mempool.insert(anchor_id, anchor.clone()); diff --git a/collator/src/manager/mod.rs b/collator/src/manager/mod.rs index 0e88035169..47de71f46e 100644 --- a/collator/src/manager/mod.rs +++ b/collator/src/manager/mod.rs @@ -14,9 +14,10 @@ use tycho_block_util::state::ShardStateStuff; use tycho_core::global_config::MempoolGlobalConfig; use tycho_core::storage::{LoadStateHint, StateNotFound}; use tycho_crypto::ed25519::KeyPair; +use tycho_slasher_traits::ValidatorEventsListener; use tycho_types::models::{ - BlockId, BlockIdShort, CollationConfig, GlobalCapabilities, ProcessedUptoInfo, ShardIdent, - ValidatorDescription, + BlockId, BlockIdShort, CollationConfig, GlobalCapabilities, IndexedValidatorDescription, + ProcessedUptoInfo, ShardIdent, }; use tycho_util::futures::{AwaitBlocking, JoinTask}; use tycho_util::metrics::HistogramGuard; @@ -85,6 +86,7 @@ where validator: Arc, cancel_validation_runner: Mutex, + stats_recorder: Arc, active_collation_sessions: RwLock>>, active_collators: FastDashMap>>, @@ -144,6 +146,7 @@ where mpool_adapter_factory: MPF, validator: V, collator_factory: CF, + stats_recorder: Arc, mempool_config_override: Option, ) -> Arc> where @@ -178,6 +181,7 @@ where validator, cancel_validation_runner: Default::default(), + stats_recorder, active_collation_sessions: Default::default(), active_collators: Default::default(), @@ -2328,7 +2332,9 @@ where self.apply_split_merge_actions(&new_shards_info)?; // find out the actual collation session start round from master state - let current_session_seqno = mc_data.validator_info.catchain_seqno; + let catchain_seqno = mc_data.validator_info.catchain_seqno; + let vset_switch_round = mc_data.consensus_info.vset_switch_round; + let validation_session_id = (catchain_seqno, vset_switch_round); // we need full validators set to define the subset for each session and to check if current node should collate let full_validators_set = mc_data.config.get_current_validator_set()?; @@ -2342,22 +2348,24 @@ where let mut subset_cache = FastHashMap::new(); let mut get_validator_subset = |shard_id| match subset_cache.entry(shard_id) { hash_map::Entry::Occupied(entry) => { - let (subset, hash_short): &(Arc>, u32) = - entry.get(); + let (subset, hash_short): &( + Arc>, + u32, + ) = entry.get(); Result::<_>::Ok((subset.clone(), *hash_short)) } hash_map::Entry::Vacant(entry) => { let (subset, hash_short) = full_validators_set - .compute_mc_subset(current_session_seqno, collation_config.shuffle_mc_validators) + .compute_mc_subset_indexed(vset_switch_round, collation_config.shuffle_mc_validators) .ok_or_else(|| anyhow!( - "Error calculating subset of validators for session (shard_id = {}, seqno = {})", + "Error calculating subset of validators for catchain session (shard_id = {}, seqno = {})", ShardIdent::MASTERCHAIN, - current_session_seqno, + vset_switch_round, ))?; let subset: FastHashMap<_, _> = subset .into_iter() - .map(|vldr| (vldr.public_key.into(), vldr)) + .map(|vldr| (vldr.desc.public_key.into(), vldr)) .collect(); let subset = Arc::new(subset); @@ -2386,7 +2394,6 @@ where tracing::debug!( target: tracing_targets::COLLATION_MANAGER, public_key = %self.keypair.public_key, - current_session_seqno, hash_short, "Current node was not authorized to collate shard {}. Use TRACE to see subset", shard_id, @@ -2407,7 +2414,8 @@ where if local_pubkey.is_some() { // start new session when seqno changed or subset changed for the same seqno if existing_session_info.collators().short_hash == hash_short - && existing_session_info.seqno() == current_session_seqno + && existing_session_info.get_validation_session_id() + == validation_session_id { sessions_to_keep.push((shard_id, existing_session_info, block_ids)); } else { @@ -2447,7 +2455,7 @@ where tracing::info!( target: tracing_targets::COLLATION_MANAGER, "Will start new collation sessions: {:?}", - DebugIter(sessions_to_start.iter().map(|(s, _)| (s, current_session_seqno))), + DebugIter(sessions_to_start.iter().map(|(s, _)| (s, validation_session_id))), ); } @@ -2458,7 +2466,8 @@ where let new_session_info = Arc::new(CollationSessionInfo::new( shard_id, - current_session_seqno, + validation_session_id.0, + validation_session_id.1, ValidatorSubsetInfo { validators: subset.values().cloned().collect(), short_hash: hash_short, @@ -2499,6 +2508,7 @@ where state_node_adapter: self.state_node_adapter.clone(), config: self.config.clone(), collation_session: new_session_info.clone(), + stats_recorder: self.stats_recorder.clone(), zerostate_id: *self.state_node_adapter.zerostate_id(), shard_id, prev_blocks_ids: prev_blocks_ids.clone(), diff --git a/collator/src/manager/utils.rs b/collator/src/manager/utils.rs index 35ed7e282c..fa20847242 100644 --- a/collator/src/manager/utils.rs +++ b/collator/src/manager/utils.rs @@ -1,11 +1,10 @@ use tycho_crypto::ed25519::{KeyPair, PublicKey}; -use tycho_types::models::ValidatorDescription; use tycho_util::FastHashMap; #[cfg(not(any(feature = "test", test)))] -pub fn find_us_in_collators_set( +pub fn find_us_in_collators_set( keypair: &KeyPair, - set: &FastHashMap<[u8; 32], ValidatorDescription>, + set: &FastHashMap<[u8; 32], T>, ) -> Option { let local_pubkey = keypair.public_key; if set.contains_key(local_pubkey.as_bytes()) { @@ -16,9 +15,9 @@ pub fn find_us_in_collators_set( } #[cfg(any(test, feature = "test"))] -pub fn find_us_in_collators_set( +pub fn find_us_in_collators_set( keypair: &KeyPair, - _set: &FastHashMap<[u8; 32], ValidatorDescription>, + _set: &FastHashMap<[u8; 32], T>, ) -> Option { Some(keypair.public_key) } diff --git a/collator/src/mempool/impls/common/cache.rs b/collator/src/mempool/impls/common/cache.rs index 15fa543d7e..9cc7ca653f 100644 --- a/collator/src/mempool/impls/common/cache.rs +++ b/collator/src/mempool/impls/common/cache.rs @@ -356,6 +356,7 @@ mod tests { author: PeerId(Default::default()), chain_time: id as u64, externals: vec![], + stats: None, }) } diff --git a/collator/src/mempool/impls/common/shuttle.rs b/collator/src/mempool/impls/common/shuttle.rs index 91faa60e28..fae776a7af 100644 --- a/collator/src/mempool/impls/common/shuttle.rs +++ b/collator/src/mempool/impls/common/shuttle.rs @@ -56,6 +56,7 @@ impl Shuttle { chain_time, author: *committed.anchor.author(), externals: unique_messages, + stats: committed.stats.clone(), }); metrics::counter!("tycho_mempool_msgs_unique_count") diff --git a/collator/src/mempool/impls/dump_anchors.rs b/collator/src/mempool/impls/dump_anchors.rs index efaf2f5184..0b2cb45a07 100644 --- a/collator/src/mempool/impls/dump_anchors.rs +++ b/collator/src/mempool/impls/dump_anchors.rs @@ -45,6 +45,7 @@ impl TryFrom for MempoolAnchor { author: value.author, chain_time: value.chain_time, externals, + stats: None, }) } } diff --git a/collator/src/mempool/impls/single_node_impl/anchor_handler.rs b/collator/src/mempool/impls/single_node_impl/anchor_handler.rs index 20af83b60a..cc1ec41e8b 100644 --- a/collator/src/mempool/impls/single_node_impl/anchor_handler.rs +++ b/collator/src/mempool/impls/single_node_impl/anchor_handler.rs @@ -60,6 +60,7 @@ impl SingleNodeAnchorHandler { chain_time, author: self.peer_id, externals: unique_messages, + stats: None, })) .expect("push new anchor"); diff --git a/collator/src/mempool/impls/stub_impl.rs b/collator/src/mempool/impls/stub_impl.rs index 46dea9df3a..797968bee3 100644 --- a/collator/src/mempool/impls/stub_impl.rs +++ b/collator/src/mempool/impls/stub_impl.rs @@ -443,6 +443,7 @@ pub(crate) fn make_empty_anchor( author: PeerId(Default::default()), chain_time, externals: vec![], + stats: None, }) } @@ -467,6 +468,7 @@ pub(crate) fn make_stub_anchor(id: MempoolAnchorId, prev_id: MempoolAnchorId) -> author: PeerId(Default::default()), chain_time, externals, + stats: None, } } @@ -530,6 +532,7 @@ pub(crate) fn make_anchor_from_file( author: PeerId(Default::default()), chain_time, externals, + stats: None, })) } diff --git a/collator/src/mempool/mod.rs b/collator/src/mempool/mod.rs index ae8fa230f4..4375f0578b 100644 --- a/collator/src/mempool/mod.rs +++ b/collator/src/mempool/mod.rs @@ -6,6 +6,7 @@ use anyhow::Result; use async_trait::async_trait; use bytes::Bytes; use tycho_network::PeerId; +use tycho_slasher_traits::AnchorStats; use tycho_types::models::*; use tycho_types::prelude::*; @@ -126,6 +127,7 @@ pub struct MempoolAnchor { pub author: PeerId, pub chain_time: u64, pub externals: Vec>, + pub stats: Option, } impl MempoolAnchor { diff --git a/collator/src/types.rs b/collator/src/types.rs index 7e6bbe6669..a27a888bb0 100644 --- a/collator/src/types.rs +++ b/collator/src/types.rs @@ -354,39 +354,48 @@ pub(crate) type CollationSessionId = (ShardIdent, u32, u32); #[derive(Clone)] pub struct CollationSessionInfo { shard: ShardIdent, - /// Sequence number of the collation session - seqno: u32, + vset_switch_round: u32, + catchain_seqno: u32, collators: ValidatorSubsetInfo, current_collator_keypair: Option>, } + +// #[derive(Clone, PartialEq, Eq)] +// pub struct SessionId { +// pub vset_switch_round: u32, +// pub catchain_seqno: u32, +// } + impl CollationSessionInfo { pub fn new( shard: ShardIdent, - seqno: u32, + catchain_seqno: u32, + vset_switch_round: u32, collators: ValidatorSubsetInfo, current_collator_keypair: Option>, ) -> Self { Self { shard, - seqno, + vset_switch_round, + catchain_seqno, collators, current_collator_keypair, } } pub fn id(&self) -> CollationSessionId { - (self.shard, self.seqno, self.collators.short_hash) + (self.shard, self.catchain_seqno, self.collators.short_hash) } pub fn get_validation_session_id(&self) -> ValidationSessionId { - (self.seqno, self.collators.short_hash) + (self.catchain_seqno, self.vset_switch_round) } pub fn shard(&self) -> ShardIdent { self.shard } pub fn seqno(&self) -> u32 { - self.seqno + self.catchain_seqno } pub fn collators(&self) -> &ValidatorSubsetInfo { @@ -401,7 +410,7 @@ impl fmt::Debug for CollationSessionInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("CollationSessionInfo") .field("shard", &self.shard) - .field("seqno", &self.seqno) + .field("catchain_seqno", &self.catchain_seqno) .field("collators", &self.collators) .field( "current_collator_pubkey", diff --git a/collator/src/validator/impls/std_impl/mod.rs b/collator/src/validator/impls/std_impl/mod.rs index fd00f72358..98be36aa76 100644 --- a/collator/src/validator/impls/std_impl/mod.rs +++ b/collator/src/validator/impls/std_impl/mod.rs @@ -7,6 +7,7 @@ use indexmap::{self, IndexMap}; use serde::{Deserialize, Serialize}; use session::DebugLogValidatorSesssion; use tycho_crypto::ed25519::KeyPair; +use tycho_slasher_traits::{ValidatorEvents, ValidatorEventsListener}; use tycho_types::models::*; use tycho_util::{FastHashMap, serde_helpers}; @@ -76,6 +77,7 @@ impl ValidatorStdImpl { net_context: ValidatorNetworkContext, keypair: Arc, config: ValidatorStdImplConfig, + stats_recorder: Arc, ) -> Self { Self { inner: Arc::new(Inner { @@ -83,6 +85,7 @@ impl ValidatorStdImpl { keypair, sessions: Default::default(), config, + events: ValidatorEvents::new(stats_recorder), }), } } @@ -91,18 +94,19 @@ impl ValidatorStdImpl { #[async_trait] impl Validator for ValidatorStdImpl { fn add_session(&self, info: AddSession<'_>) -> Result<()> { - let session = ValidatorSession::new( - &self.inner.net_context, - self.inner.keypair.clone(), - &self.inner.config, - info, - )?; - let mut sessions = self.inner.sessions.lock(); let shard_sessions = sessions.entry(info.shard_ident).or_default(); match shard_sessions.entry(info.session_id) { indexmap::map::Entry::Vacant(entry) => { + let session = ValidatorSession::new( + &self.inner.net_context, + self.inner.keypair.clone(), + &self.inner.config, + info, + &self.inner.events, + )?; + tracing::debug!( target: tracing_targets::VALIDATOR, session = ?DebugLogValidatorSesssion(&session), @@ -217,8 +221,9 @@ struct Inner { keypair: Arc, sessions: parking_lot::Mutex, config: ValidatorStdImplConfig, + events: ValidatorEvents, } type Sessions = FastHashMap; -/// We use `IndexMap` because "subset short hash" component of session id is not sequential +/// We use `IndexMap` to keep deterministic insertion order for latest-session scans. type ShardSessions = IndexMap; diff --git a/collator/src/validator/impls/std_impl/session.rs b/collator/src/validator/impls/std_impl/session.rs index e27f66dec4..7ac2d9c09d 100644 --- a/collator/src/validator/impls/std_impl/session.rs +++ b/collator/src/validator/impls/std_impl/session.rs @@ -1,8 +1,8 @@ use std::fmt; use std::future::IntoFuture; use std::pin::{Pin, pin}; -use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}; +use std::sync::{Arc, Weak}; use std::task::{Context, Poll, Waker}; use anyhow::Result; @@ -16,6 +16,7 @@ use tokio_util::sync::CancellationToken; use tracing::Instrument; use tycho_crypto::ed25519::KeyPair; use tycho_network::{OverlayId, PeerId, PrivateOverlay}; +use tycho_slasher_traits::{BlockValidationScope, ValidatorEvents, ValidatorSessionScope}; use tycho_types::models::*; use tycho_util::FastHashMap; use tycho_util::futures::JoinTask; @@ -60,12 +61,13 @@ impl ValidatorSession { key_pair: Arc, config: &ValidatorStdImplConfig, info: AddSession<'_>, + events: &ValidatorEvents, ) -> Result { // Prepare a map with other validators let mut validators = FastHashMap::default(); - for descr in info.validators { + for item in info.validators { // TODO: Skip invalid entries? But what should we do with the total weight? - let validator_info = BriefValidatorDescr::try_from(descr)?; + let validator_info = BriefValidatorDescr::from_descr(item)?; validators.insert(validator_info.peer_id, validator_info); } @@ -73,8 +75,8 @@ impl ValidatorSession { let weight_threshold = max_weight.saturating_mul(2) / 3 + 1; let peer_id = net_context.network.peer_id(); - let own_weight = match validators.remove(peer_id) { - Some(info) => info.weight, + let (own_weight, own_validator_idx) = match validators.remove(peer_id) { + Some(info) => (info.weight, info.validator_idx), None => anyhow::bail!("node is not in the validator set"), }; @@ -82,6 +84,14 @@ impl ValidatorSession { let peer_ids = validators.values().map(|v| v.peer_id).collect::>(); + // Create events scope + let events_scope = events.begin_session( + info.session_id.into(), + info.start_block_seqno, + own_validator_idx, + info.validators, + ); + // Create the session state let state = Arc::new(SessionState { shard_ident: info.shard_ident, @@ -91,6 +101,7 @@ impl ValidatorSession { cached_signatures: TreeIndex::new(), cancelled: AtomicBool::new(false), cancelled_signal: Notify::new(), + events_scope, }); // Create the private overlay @@ -124,6 +135,7 @@ impl ValidatorSession { key_pair, peer_id: *peer_id, own_weight, + own_validator_idx, state, min_seqno: AtomicU32::new(info.start_block_seqno), }), @@ -147,6 +159,7 @@ impl ValidatorSession { } pub fn cancel(&self) { + self.inner.state.events_scope.finish(); self.inner.state.cancelled.store(true, Ordering::Release); self.inner.state.cancelled_signal.notify_waiters(); } @@ -180,6 +193,15 @@ impl ValidatorSession { debug_assert_eq!(self.inner.state.shard_ident, block_id.shard); + let events_scope = scopeguard::guard( + Arc::new(self.inner.state.events_scope.begin_block(block_id)), + |scope| { + // Discard block if parent (this) future was cancelled. + // Due to spawned tasks we need to explicitly call this method. + scope.discard(); + }, + ); + self.inner .min_seqno .fetch_max(block_id.seqno, Ordering::Release); @@ -196,8 +218,8 @@ impl ValidatorSession { // Prepare block signatures let block_signatures = match &cached { - Some(cached) => self.reuse_signatures(block_id, cached.clone()).await, - None => self.prepare_new_signatures(block_id), + Some(cached) => self.reuse_signatures(block_id, &events_scope, cached).await, + None => self.prepare_new_signatures(block_id, &events_scope), } .build(block_id, state.weight_threshold); @@ -231,6 +253,10 @@ impl ValidatorSession { *self.inner.client.peer_id(), block_signatures.own_signature.clone(), ); + + // Notify listeners about the own signature + events_scope.receive_signature(self.inner.own_validator_idx, true); + let mut total_weight = self.inner.own_weight; let semaphore = Arc::new(Semaphore::new(self.inner.config.max_parallel_requests)); @@ -306,6 +332,8 @@ impl ValidatorSession { total_weight += validator_info.weight; } + scopeguard::ScopeGuard::into_inner(events_scope).commit(); + tracing::info!(target: tracing_targets::VALIDATOR, "finished"); Ok(ValidationStatus::Complete(ValidationComplete { signatures: result, @@ -313,7 +341,11 @@ impl ValidatorSession { })) } - fn prepare_new_signatures(&self, block_id: &BlockId) -> BlockSignaturesBuilder { + fn prepare_new_signatures( + &self, + block_id: &BlockId, + events_scope: &Arc, + ) -> BlockSignaturesBuilder { let data = Block::build_data_for_sign(block_id); // Prepare our own signature @@ -341,13 +373,15 @@ impl ValidatorSession { own_signature, other_signatures, total_weight: self.inner.own_weight, + events_scope: Arc::downgrade(events_scope), } } async fn reuse_signatures( &self, block_id: &BlockId, - cached: Arc, + events_scope: &Arc, + cached: &Arc, ) -> BlockSignaturesBuilder { let data = Block::build_data_for_sign(block_id); let block_id = *block_id; @@ -356,8 +390,9 @@ impl ValidatorSession { let my_peer_id = self.inner.peer_id; let validators = self.inner.state.validators.clone(); let mut total_weight = self.inner.own_weight; - let span = tracing::Span::current(); + let events_scope = Arc::downgrade(events_scope); + let cached = cached.clone(); tycho_util::sync::rayon_run(move || { let _span = span.enter(); @@ -386,6 +421,8 @@ impl ValidatorSession { }; let validator_info = validators.get(peer_id).expect("peer info out of sync"); + let validator_idx = validator_info.validator_idx; + if !validator_info.public_key.verify_raw(&data, &signature) { tracing::warn!( target: tracing_targets::VALIDATOR, @@ -401,10 +438,17 @@ impl ValidatorSession { metrics::counter!(METRIC_INVALID_SIGNATURES_CACHED_TOTAL).increment(1); - // TODO: Somehow mark that this validator sent an invalid signature? + if let Some(scope) = events_scope.upgrade() { + scope.receive_signature(validator_idx, false); + } + break 'stored Default::default(); } + if let Some(scope) = events_scope.upgrade() { + scope.receive_signature(validator_idx, true); + } + total_weight += validator_info.weight; Some(signature) }; @@ -416,6 +460,7 @@ impl ValidatorSession { own_signature, other_signatures, total_weight, + events_scope, } }) .await @@ -446,6 +491,7 @@ impl fmt::Debug for DebugLogValidatorSesssion<'_> { .field("session_id", &self.0.inner.session_id) .field("public_key", &self.0.inner.key_pair.public_key) .field("peer_id", &self.0.inner.peer_id) + .field("own_validator_idx", &self.0.inner.own_validator_idx) .field("own_weight", &self.0.inner.own_weight) .field("weight_threshold", &self.0.inner.state.weight_threshold) .field("start_block_seqno", &self.0.inner.start_block_seqno) @@ -462,6 +508,7 @@ struct Inner { client: ValidatorClient, key_pair: Arc, peer_id: PeerId, + own_validator_idx: u16, own_weight: u64, state: Arc, min_seqno: AtomicU32, @@ -613,6 +660,7 @@ struct SessionState { cached_signatures: TreeIndex>, cancelled: AtomicBool, cancelled_signal: Notify, + events_scope: ValidatorSessionScope, } impl SessionState { @@ -649,10 +697,16 @@ impl SessionState { // TODO: Store that the signature is invalid to avoid further checks on retries // TODO: Collect statistics on invalid signatures to slash the malicious validator metrics::counter!(METRIC_INVALID_SIGNATURES_IN_TOTAL).increment(1); + + if let Some(scope) = block.events_scope.upgrade() { + scope.receive_signature(validator_info.validator_idx, false); + } + return Err(ValidationError::InvalidSignature); } let mut can_notify = false; + let mut record_event = false; match &*slot.compare_and_swap(&None::>, Some(signature.clone())) { None => { slot.notify(); @@ -664,6 +718,7 @@ impl SessionState { + validator_info.weight; can_notify = total_weight >= self.weight_threshold; + record_event = true; } Some(saved) => { if saved.as_ref() != signature.as_ref() { @@ -674,8 +729,18 @@ impl SessionState { } } - if can_notify { - block.validated.store(true, Ordering::Release); + // NOTE: We can only record event if the block was not marked as validated. + let was_sealed = if can_notify { + block.validated.swap(true, Ordering::Release) + } else { + block.validated.load(Ordering::Relaxed) + }; + + if record_event + && !was_sealed + && let Some(scope) = block.events_scope.upgrade() + { + scope.receive_signature(validator_info.validator_idx, true); } Ok(()) @@ -691,6 +756,7 @@ struct BlockSignaturesBuilder { own_signature: Arc<[u8; 64]>, other_signatures: SignatureSlotsMap, total_weight: u64, + events_scope: Weak, } impl BlockSignaturesBuilder { @@ -704,6 +770,7 @@ impl BlockSignaturesBuilder { total_weight: AtomicU64::new(self.total_weight), validated: AtomicBool::new(self.total_weight >= weight_threshold), cancelled: CancellationToken::new(), + events_scope: self.events_scope, }) } } @@ -715,6 +782,7 @@ struct BlockSignatures { total_weight: AtomicU64, validated: AtomicBool, cancelled: CancellationToken, + events_scope: Weak, } impl Drop for BlockSignatures { @@ -897,7 +965,7 @@ fn compute_session_overlay_id( zerostate_root_hash: zerostate_id.root_hash.0, zerostate_file_hash: zerostate_id.file_hash.0, shard_ident: *shard_ident, - session_seqno: session_id.seqno(), + session_seqno: session_id.catchain_seqno(), })) } diff --git a/collator/src/validator/mod.rs b/collator/src/validator/mod.rs index 652750edf7..d2003ee251 100644 --- a/collator/src/validator/mod.rs +++ b/collator/src/validator/mod.rs @@ -4,7 +4,7 @@ use anyhow::Result; use async_trait::async_trait; use tycho_crypto::ed25519::PublicKey; use tycho_network::{Network, OverlayService, PeerId, PeerResolver}; -use tycho_types::models::{BlockId, BlockIdShort, ShardIdent, ValidatorDescription}; +use tycho_types::models::{BlockId, BlockIdShort, IndexedValidatorDescription, ShardIdent}; use tycho_util::FastHashMap; pub use self::impls::*; @@ -52,17 +52,22 @@ pub struct ValidatorNetworkContext { pub zerostate_id: BlockId, } -/// (seqno, subset `short_hash`) +/// (`catchain_seqno`, `vset_switch_round`) pub type ValidationSessionId = (u32, u32); pub trait CompositeValidationSessionId { - fn seqno(&self) -> u32; + fn catchain_seqno(&self) -> u32; + fn vset_switch_round(&self) -> u32; } impl CompositeValidationSessionId for ValidationSessionId { - fn seqno(&self) -> u32 { + fn catchain_seqno(&self) -> u32 { self.0 } + + fn vset_switch_round(&self) -> u32 { + self.1 + } } #[derive(Debug, Clone, Copy)] @@ -70,7 +75,7 @@ pub struct AddSession<'a> { pub shard_ident: ShardIdent, pub start_block_seqno: u32, pub session_id: ValidationSessionId, - pub validators: &'a [ValidatorDescription], + pub validators: &'a [IndexedValidatorDescription], } #[derive(Debug, Clone)] @@ -92,12 +97,11 @@ pub struct BriefValidatorDescr { pub peer_id: PeerId, pub public_key: PublicKey, pub weight: u64, + pub validator_idx: u16, } -impl TryFrom<&ValidatorDescription> for BriefValidatorDescr { - type Error = anyhow::Error; - - fn try_from(descr: &ValidatorDescription) -> Result { +impl BriefValidatorDescr { + pub fn from_descr(descr: &IndexedValidatorDescription) -> Result { let Some(public_key) = PublicKey::from_bytes(descr.public_key.0) else { anyhow::bail!("invalid validator public key"); }; @@ -106,6 +110,7 @@ impl TryFrom<&ValidatorDescription> for BriefValidatorDescr { peer_id: PeerId(descr.public_key.0), public_key, weight: descr.weight, + validator_idx: descr.validator_idx, }) } } diff --git a/collator/tests/collation_tests.rs b/collator/tests/collation_tests.rs index 7151ef8a28..c901c8a84c 100644 --- a/collator/tests/collation_tests.rs +++ b/collator/tests/collation_tests.rs @@ -26,6 +26,7 @@ use tycho_core::global_config::ZerostateId; use tycho_core::node::NodeKeys; use tycho_core::storage::CoreStorage; use tycho_crypto::ed25519; +use tycho_slasher_traits::NoopValidatorEventsRecorder; use tycho_storage::StorageContext; use tycho_types::models::{BlockId, BlockIdShort, ShardIdent}; @@ -216,6 +217,8 @@ fn start_collation_manager( ctx: &DumpCollationContext, dumped_anchors: Vec, ) -> RunningCollationManager { + let recorder = Arc::new(NoopValidatorEventsRecorder); + CollationManager::create( ctx.keypair.clone(), ctx.config.clone(), @@ -237,6 +240,7 @@ fn start_collation_manager( CollatorStdImplFactory { wu_tuner_event_sender: None, }, + recorder, None, ) } diff --git a/collator/tests/validator_tests.rs b/collator/tests/validator_tests.rs index 390b4603e0..1cb278a03e 100644 --- a/collator/tests/validator_tests.rs +++ b/collator/tests/validator_tests.rs @@ -9,8 +9,9 @@ use tycho_collator::validator::{ }; use tycho_crypto::ed25519; use tycho_network::{DhtClient, PeerInfo}; +use tycho_slasher_traits::NoopValidatorEventsRecorder; use tycho_types::cell::HashBytes; -use tycho_types::models::{BlockId, ShardIdent, ValidatorDescription}; +use tycho_types::models::{BlockId, IndexedValidatorDescription, ShardIdent, ValidatorDescription}; use tycho_util::futures::JoinTask; mod common; @@ -23,7 +24,7 @@ struct ValidatorNode { } impl ValidatorNode { - fn generate(zerostate_id: &BlockId, rng: &mut impl rand::Rng) -> Self { + fn generate(zerostate_id: &BlockId, rng: &mut impl rand::Rng, idx: u16) -> Self { let secret_key = rng.random::(); let keypair = Arc::new(ed25519::KeyPair::from(&secret_key)); @@ -32,6 +33,7 @@ impl ValidatorNode { peer_id: *validator_network.network.peer_id(), public_key: keypair.public_key, weight: 1, + validator_idx: idx, }; let network = &validator_network.network; @@ -45,6 +47,7 @@ impl ValidatorNode { validator_network, keypair.clone(), ValidatorStdImplConfig::default(), + Arc::new(NoopValidatorEventsRecorder), ); Self { @@ -62,7 +65,7 @@ fn generate_network( rng: &mut impl rand::Rng, ) -> Vec { let nodes = (0..node_count) - .map(|_| ValidatorNode::generate(zerostate_id, rng)) + .map(|i| ValidatorNode::generate(zerostate_id, rng, i as u16)) .collect::>(); for i in 0..nodes.len() { @@ -81,16 +84,19 @@ fn generate_network( nodes } -fn make_description(seqno: u32, nodes: &[ValidatorNode]) -> Vec { +fn make_description(seqno: u32, nodes: &[ValidatorNode]) -> Vec { let mut validators = Vec::with_capacity(nodes.len()); let mut prev_total_weight = 0; - for node in nodes { - validators.push(ValidatorDescription { - public_key: HashBytes(*node.descr.public_key.as_bytes()), - weight: 1, - adnl_addr: Some(HashBytes(*node.descr.peer_id.as_bytes())), - mc_seqno_since: seqno, - prev_total_weight, + for (i, node) in nodes.iter().enumerate() { + validators.push(IndexedValidatorDescription { + desc: ValidatorDescription { + public_key: HashBytes(*node.descr.public_key.as_bytes()), + weight: 1, + adnl_addr: Some(HashBytes(*node.descr.peer_id.as_bytes())), + mc_seqno_since: seqno, + prev_total_weight, + }, + validator_idx: i as u16, }); prev_total_weight += node.descr.weight; } @@ -107,6 +113,7 @@ async fn validator_signatures_match() -> Result<()> { const NODE_COUNT: usize = 13; const SESSION_COUNT: usize = 5; + const REQUIRED_SIGS: usize = (NODE_COUNT * 2) / 3 + 1; let zerostate_id = BlockId { shard: ShardIdent::MASTERCHAIN, @@ -121,7 +128,9 @@ async fn validator_signatures_match() -> Result<()> { ..zerostate_id }; for session_seqno in (0..).step_by(1000).take(SESSION_COUNT) { - let session_id = (session_seqno, 0); + let vset_switch_round = session_seqno + 100; + let catchain_seqno = session_seqno + 200; + let session_id = (catchain_seqno, vset_switch_round); tracing::info!(?session_id, %block_id, "adding session"); @@ -156,11 +165,47 @@ async fn validator_signatures_match() -> Result<()> { let BriefStatus::Complete(signature_count) = status else { panic!("must not be skipped"); }; - assert!(signature_count > (NODE_COUNT * 2) / 3); + assert!( + signature_count >= REQUIRED_SIGS, + "expected at least {REQUIRED_SIGS} signatures, got {signature_count}" + ); tracing::info!(%peer_id, ?status, "validation completed"); } + // TODO: Build test around some test-only events collector + + // let short = block_id.as_short_id(); + // let range = short..=short; + + // for node in &nodes { + // let events = node.event_collector.stats_for_blocks(range.clone()); + + // // check current node signature + // let self_stat = events + // .get(&node.descr.peer_id) + // .expect("current node should have stats"); + + // assert_eq!(self_stat.invalid, 0); + // assert_eq!(self_stat.valid, 1); + + // // check total valid signatures + // let total_valid: usize = events.values().filter(|s| s.valid > 0).count(); + + // assert!( + // total_valid >= REQUIRED_SIGS, + // "total_valid ({total_valid}) < REQUIRED_SIGS ({REQUIRED_SIGS})" + // ); + + // // check that no invalid signatures were given + // for (peer, stat) in &events { + // assert_eq!( + // stat.invalid, 0, + // "peer {peer:?} has invalid signatures: {stat:?}" + // ); + // } + // } + for node in &nodes { node.validator .cancel_validation(&block_id.as_short_id(), Some(session_id))?; @@ -184,6 +229,7 @@ async fn malicious_validators_are_ignored() -> Result<()> { const MALICIOUS_NODE_COUNT: usize = 3; const SESSION_COUNT: usize = 5; + const REQUIRED_SIGS: usize = (NODE_COUNT * 2) / 3 + 1; // 9 let zerostate_id = BlockId { shard: ShardIdent::MASTERCHAIN, @@ -198,7 +244,9 @@ async fn malicious_validators_are_ignored() -> Result<()> { ..zerostate_id }; for session_seqno in (0..).step_by(1000).take(SESSION_COUNT) { - let session_id = (session_seqno, 0); + let vset_switch_round = session_seqno + 100; + let catchain_seqno = session_seqno + 200; + let session_id = (catchain_seqno, vset_switch_round); tracing::info!(?session_id, %block_id, "adding session"); @@ -246,7 +294,12 @@ async fn malicious_validators_are_ignored() -> Result<()> { match &status { ValidationStatus::Complete(res) => { - assert!(res.signatures.len() > (NODE_COUNT * 2) / 3); + let sigs = res.signatures.len(); + assert!(sigs >= REQUIRED_SIGS, "need {REQUIRED_SIGS}, got {sigs}"); + assert!( + sigs <= NODE_COUNT - MALICIOUS_NODE_COUNT, + "malicious sigs leaked, got {sigs}" + ); } ValidationStatus::Skipped => panic!("good validator skipped block"), } @@ -270,6 +323,38 @@ async fn malicious_validators_are_ignored() -> Result<()> { } } + // TODO: Build test around some test-only events collector + + // let short = block_id.as_short_id(); + // let range = short..=short; + // for (i, node) in nodes.iter().enumerate() { + // let stats = node.event_collector.stats_for_blocks(range.clone()); + // let s = stats.get(&node.descr.peer_id); + + // if i < MALICIOUS_NODE_COUNT { + // // malicious node must not have valid stats + // assert!( + // s.is_none_or(|st| st.valid == 0), + // "malicious node {:?} has valid sigs in stats: {:?}", + // node.descr.peer_id, + // s + // ); + // } else { + // // good node must have valid stats + // let st = s.expect("good node must have stats"); + // assert_eq!( + // st.valid, 1, + // "good node {:?} valid !=1 {:?}", + // node.descr.peer_id, st + // ); + // assert_eq!( + // st.invalid, 0, + // "good node {:?} invalid !=0 {:?}", + // node.descr.peer_id, st + // ); + // } + // } + block_id.seqno += 1; } } @@ -299,7 +384,7 @@ async fn network_gets_stuck_without_signatures() -> Result<()> { seqno: 1, ..zerostate_id }; - let session_id = (0, 0); + let session_id = (100, 200); let validators = make_description(block_id.seqno, &nodes); for node in &nodes { @@ -316,27 +401,33 @@ async fn network_gets_stuck_without_signatures() -> Result<()> { let mut good_validators = futures_util::stream::FuturesOrdered::new(); let mut bad_validators = futures_util::stream::FuturesOrdered::new(); + let mut nodes_blocks = Vec::with_capacity(NODE_COUNT); for (i, node) in nodes.iter().enumerate() { + let is_malicious = i < malicious_node_count; + + let mut blk = block_id; + if is_malicious { + blk.root_hash = rand::random(); + } + + nodes_blocks.push(blk); + let peer_id = node.descr.peer_id; let validator = node.validator.clone(); - let is_malicious = i < malicious_node_count; - if is_malicious { - bad_validators.push_back(JoinTask::new(async move { - let mut block_id = block_id; - block_id.root_hash = rand::random(); + let fut = async move { + let res = validator.validate(session_id, &blk).await; + (peer_id, res) + }; - let res = validator.validate(session_id, &block_id).await; - (peer_id, res) - })); + if is_malicious { + bad_validators.push_back(JoinTask::new(fut)); } else { - good_validators.push_back(JoinTask::new(async move { - let res = validator.validate(session_id, &block_id).await; - (peer_id, res) - })); + good_validators.push_back(JoinTask::new(fut)); } } + // let range = block_id.as_short_id()..=block_id.as_short_id(); tokio::select! { _ = good_validators.next() => { panic!("good validator completed block"); @@ -346,6 +437,49 @@ async fn network_gets_stuck_without_signatures() -> Result<()> { } _ = tokio::time::sleep(STUCK_DURATION) => { tracing::info!("network got stuck as expected"); + + // TODO: Build test around some test-only events collector + + // // 1) check event collector in each node + // for node in &nodes { + // let events = node.event_collector.stats_for_blocks(range.clone()); + // // each node should have no events + // assert_eq!(events.len(), 0); + // } + + // // 2) notify all nodes about validation completion + // for (i, node) in nodes.iter().enumerate() { + // let block_id = nodes_blocks.get(i) + // .expect("should have block id for each node"); + // node.event_collector.on_validation_complete( + // &SessionCtx { session_id }, + // block_id, + // )?; + // } + + // // 3) calc total valid and invalid signatures + // for (i, node) in nodes.iter().enumerate() { + // let is_malicious = i < malicious_node_count; + // let events = node.event_collector.stats_for_blocks(range.clone()); + // let total_invalid = events.values().map(|s| s.invalid).sum::() as usize; + // let total_valid = events.values().map(|s| s.valid).sum::() as usize; + // if is_malicious { + // // valid only self-own signature because block has a random root hash + // assert_eq!(total_valid, 1, + // "malicious node {:?} has valid signatures", node.descr.peer_id); + // // malicious nodes should have no valid signatures except their own + // assert_eq!(total_invalid, NODE_COUNT - 1, + // "malicious node {:?} has valid signatures", node.descr.peer_id); + // } else { + // // good nodes should have valid signatures from all other good nodes + // assert_eq!(total_valid, NODE_COUNT - malicious_node_count, + // "good node {:?} has no valid signatures", node.descr.peer_id); + // // good nodes should have invalid signatures from all malicious nodes + // assert_eq!(total_invalid, malicious_node_count, + // "good node {:?} has invalid signatures", node.descr.peer_id); + + // } + // } } } diff --git a/consensus/Cargo.toml b/consensus/Cargo.toml index e33b512826..7d9593df99 100644 --- a/consensus/Cargo.toml +++ b/consensus/Cargo.toml @@ -49,8 +49,9 @@ weedb = { workspace = true } # local deps tycho-network = { workspace = true } -tycho-util = { workspace = true } +tycho-slasher-traits = { workspace = true } tycho-storage = { workspace = true } +tycho-util = { workspace = true } [dev-dependencies] humantime = { workspace = true } diff --git a/consensus/src/dag/commit/mod.rs b/consensus/src/dag/commit/mod.rs index 4339773c28..1946a861c0 100644 --- a/consensus/src/dag/commit/mod.rs +++ b/consensus/src/dag/commit/mod.rs @@ -322,6 +322,7 @@ impl Committer { proof_key: next.proof.key(), history: committed, is_executable, + stats: None, // may be set later }) } } diff --git a/consensus/src/engine/committer_task.rs b/consensus/src/engine/committer_task.rs index 8b8d8dda18..56dc437a6e 100644 --- a/consensus/src/engine/committer_task.rs +++ b/consensus/src/engine/committer_task.rs @@ -1,8 +1,10 @@ use std::mem; +use std::sync::Arc; use itertools::Itertools; use tokio::sync::mpsc; use tycho_network::PeerId; +use tycho_slasher_traits::{AnchorPeerStats, AnchorStats}; use tycho_util::FastHashMap; use tycho_util::metrics::HistogramGuard; @@ -165,7 +167,7 @@ impl State { let stats_ranges = inner.peer_schedule.atomic().stats_ranges(); - for adata in committed { + for mut adata in committed { let anchor_round = adata.anchor.round(); let (stat_map, events) = (inner.committer).remove_committed( @@ -173,6 +175,9 @@ impl State { &stats_ranges, round_ctx.conf(), )?; + if let Some(stats_slot_map) = stats_ranges.stats_slot_map(anchor_round) { + adata.stats = Some(build_dense_anchor_stats(&stat_map, stats_slot_map)); + } all_stats.push(stat_map); round_ctx.commit_metrics(&adata.anchor); @@ -196,6 +201,35 @@ impl State { } } +fn build_dense_anchor_stats( + stat_map: &FastHashMap, + stats_slot_map: &FastHashMap, +) -> AnchorStats { + let mut dense = (0..stats_slot_map.len()) + .map(|_| AnchorPeerStats { points_proven: 0 }) + .collect::>(); + + for (peer_id, stats) in stat_map { + let Some(counters) = stats.counters() else { + continue; + }; + if counters.points_proved.inner() == 0 { + continue; + } + let Some(slot_id) = stats_slot_map.get(peer_id) else { + continue; + }; + let Some(slot) = dense.get_mut(*slot_id) else { + continue; + }; + slot.points_proven = slot + .points_proven + .saturating_add(counters.points_proved.inner()); + } + + AnchorStats(Arc::<[AnchorPeerStats]>::from(dense)) +} + impl RoundCtx { fn commit_metrics(&self, anchor: &PointInfo) { metrics::counter!("tycho_mempool_commit_anchors").increment(1); diff --git a/consensus/src/models/output.rs b/consensus/src/models/output.rs index ebcc40143d..6e37047bfc 100644 --- a/consensus/src/models/output.rs +++ b/consensus/src/models/output.rs @@ -1,3 +1,5 @@ +use tycho_slasher_traits::AnchorStats; + use crate::models::{PointInfo, PointKey, Round}; pub struct AnchorData { @@ -7,6 +9,7 @@ pub struct AnchorData { pub prev_anchor: Option, pub history: Vec, pub is_executable: bool, + pub stats: Option, } pub enum MempoolOutput { diff --git a/consensus/src/storage/adapter_store.rs b/consensus/src/storage/adapter_store.rs index 479f547f28..d9ab8ec924 100644 --- a/consensus/src/storage/adapter_store.rs +++ b/consensus/src/storage/adapter_store.rs @@ -324,6 +324,7 @@ impl MempoolAdapterStore { .map(|r| r.prev()), history: keyed_vec.into_iter().map(|(_, info)| info).collect(), is_executable: false, // define later + stats: None, // not needed }); } Ok(result) diff --git a/contracts/package.json b/contracts/package.json index 0283c35401..2227863ccf 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -9,18 +9,18 @@ "release": "blueprint pack && npm publish --access public" }, "dependencies": { - "@ton/core": "~0" + "@ton/core": ">=0.62.0" }, "devDependencies": { "@tact-lang/compiler": "1.6.13", "@ton-community/func-js": ">=0.10.0", "@ton/blueprint": ">=0.40.0", "@ton/crypto": "^3.3.0", - "@ton/sandbox": ">=0.37.0", + "@ton/sandbox": ">=0.39.0", "@ton/test-utils": ">=0.11.0", "@ton/tolk-js": ">=1.0.0", "@ton/ton": ">=15.2.1 <16.0.0", - "@tychosdk/emulator": "^0.2.0", + "@tychosdk/emulator": "^0.2.6", "@types/jest": "^30.0.0", "@types/node": "^22.15.32", "arg": "^5.0.2", diff --git a/contracts/scripts/genSlasherStub.ts b/contracts/scripts/genSlasherStub.ts new file mode 100644 index 0000000000..c54001f5e3 --- /dev/null +++ b/contracts/scripts/genSlasherStub.ts @@ -0,0 +1,57 @@ +import arg from "arg"; +import { address, beginCell, storeAccount, toNano } from "@ton/core"; +import { storeSlasherStubData } from "../wrappers/SlasherStub"; +import { compile } from "@ton/blueprint"; + +async function main() { + const args = arg({ + "--balance": String, + }); + const balance = args["--balance"]; + if (balance == null) { + throw new Error("`--balance` option is missing"); + } + + const code = await compile("SlasherStub"); + + const account = beginCell() + .storeBit(true) + .store( + storeAccount({ + addr: address( + "-1:0000000000000000000000000000000000000000000000000000000000000000" + ), + storage: { + balance: { + coins: toNano(balance), + }, + lastTransLt: 0n, + state: { + type: "active", + state: { + code, + data: beginCell() + .store( + storeSlasherStubData({ + updatedAtMs: 0n, + }) + ) + .endCell(), + }, + }, + }, + storageStats: { + used: { + bits: 0n, + cells: 0n, + }, + lastPaid: 0, + storageExtra: null, + }, + }) + ) + .endCell(); + console.log(account.toBoc().toString("base64")); +} + +main().catch(console.error); diff --git a/contracts/scripts/printElectorData.ts b/contracts/scripts/printElectorData.ts index 0931025282..eb5d49456a 100644 --- a/contracts/scripts/printElectorData.ts +++ b/contracts/scripts/printElectorData.ts @@ -6,10 +6,10 @@ import { } from "@tychosdk/emulator"; import { Blockchain } from "@ton/sandbox"; import { Address, Cell, Dictionary } from "@ton/core"; -import { Elector, loadElectorData } from "../wrappers/Elector"; +import { loadElectorData } from "../wrappers/Elector"; const ELECTOR_ADDRESS = Address.parse( - "-1:3333333333333333333333333333333333333333333333333333333333333333" + "-1:3333333333333333333333333333333333333333333333333333333333333333", ); async function main() { @@ -44,7 +44,7 @@ async function main() { cs.remainingBits != 0 ? cs.loadDict( Dictionary.Keys.BigUint(256), - Dictionary.Values.BitString(0) + Dictionary.Values.BitString(0), ) : null; diff --git a/contracts/src/slasher-stub.tolk b/contracts/src/slasher-stub.tolk new file mode 100644 index 0000000000..f0a965ac58 --- /dev/null +++ b/contracts/src/slasher-stub.tolk @@ -0,0 +1,171 @@ +import "@stdlib/gas-payments" +import "@stdlib/tvm-dicts" +import "lib/config-params" + +const ERROR_INVALID_SIGNATURE = 40 +const ERROR_VALIDATOR_NOT_FOUND = 50 +const ERROR_REPLAY_PROTECTION = 52 +const ERROR_MESSAGE_EXPIRED = 57 +const ERROR_INVALID_BLOCKS_BATCH = 100 +const ERROR_NO_SLASHER_CONFIG = 101 +const ERROR_NO_PREV_BLOCK_ID = 102 + +const REPLAY_OFFSET_MS = 5000 +const FUTURE_OFFSET_SEC = 60 + +struct Storage { + updatedAtMs: uint64 +} + +fun Storage.load(): Storage { + return Storage.fromCell(contract.getData()); +} + +fun Storage.save(self) { + contract.setData(self.toCell()); +} + +// +// === Slasher Config param === +// +const PARAM_IDX_SLASHER_PARAMS = 666 + +struct (0x01) SlasherParams { + address: bits256 + blocksBatchSize: uint8 +} + +// +// === Getters === +// +get fun is_blocks_batch_valid(batch: cell, mcSeqno: int): bool { + val params = loadSlasherParams(); + val vset = lazy ValidatorSet.fromCell(blockchain.configParam(PARAM_IDX_CURRENT_VSET)!); + val validatorCount = min(vset.total, vset.main); + return validateBlocksBatch( + batch.beginParse(), + { + batchSize: params.blocksBatchSize, + mcSeqno, + validatorCount, + } + ); +} + +// +// === Logic === +// +fun onInternalMessage(_in: InMessage) { +} + +fun onExternalMessage(inMsg: slice) { + val signature = inMsg.loadBits(512); + val signedBody = inMsg; + val createdAtMs = inMsg.loadUint(64); + val expireAtSec = inMsg.loadUint(32); + val _catchainSeqno = inMsg.loadUint(32); + val _vsetSwitchRound = inMsg.loadUint(32); + val validatorIdx = inMsg.loadUint(16); + val batch = inMsg.loadRef(); + inMsg.assertEnd(); + assert(blockchain.now() <= expireAtSec, ERROR_MESSAGE_EXPIRED); + + val toSign = beginCell().storeSlice(signedBody).endCell(); + val vset = lazy ValidatorSet.fromCell(blockchain.configParam(PARAM_IDX_CURRENT_VSET)!); + var (validatorCs, validatorFound) = vset.list.uDictGet(16, validatorIdx); + assert(validatorFound, ERROR_VALIDATOR_NOT_FOUND); + val validatorPubkey = ValidatorDescr.readPubkeyOnly(validatorCs!); + assert(isSignatureValid(toSign.hash(), signature, validatorPubkey), ERROR_INVALID_SIGNATURE); + + val validatorCount = min(vset.total, vset.main); + + val batchSize = loadSlasherParams().blocksBatchSize; + val mcSeqno = blockchain.prevMcSeqno() + 1; + assert(validateBlocksBatch( + batch.beginParse(), + { + batchSize, + validatorCount, + mcSeqno, + } + ), ERROR_INVALID_BLOCKS_BATCH); + + var data = Storage.load(); + assert(createdAtMs > (data.updatedAtMs - REPLAY_OFFSET_MS) && + createdAtMs <= (blockchain.now() + FUTURE_OFFSET_SEC) * 1000, ERROR_REPLAY_PROTECTION); + + data.updatedAtMs = max(createdAtMs, data.updatedAtMs); + data.save(); + + acceptExternalMessage(); +} + +struct ValidateBlocksBatchParams { + batchSize: int + mcSeqno: int + validatorCount: int +} + +fun validateBlocksBatch(batch: slice, params: ValidateBlocksBatchParams): bool { + val startSeqno = batch.loadUint(32); + if (startSeqno + params.batchSize >= params.mcSeqno) { + // Batch contains blocks that were not produced yet. + return false; + } + if (startSeqno + params.batchSize * 2 < params.mcSeqno) { + // Batch contains too old blocks. + return false; + } + + batch.skipBits(params.batchSize); + val history = batch.loadRef() as dict; + if (!batch.isEmpty()) { + return false; + } + + var iterNext = -1; + do { + val (validatorIdx, cs, found) = history.uDictGetNext(16, iterNext); + if (found) { + iterNext = validatorIdx!; + + if (validatorIdx! >= params.validatorCount) { + return false; + } + + val (csBits, csRefs) = cs!.remainingBitsAndRefsCount(); + if (csBits != params.batchSize * 2 || csRefs != 0) { + return false; + } + } + } while (found); + + return true; +} + +fun loadSlasherParams(): SlasherParams { + val param = blockchain.configParam(PARAM_IDX_SLASHER_PARAMS); + assert(param != null, ERROR_NO_SLASHER_CONFIG); + return SlasherParams.fromCell(param); +} + +fun blockchain.prevMcSeqno(): int { + val prevBlocks = blockchain.prevMcBlocks(); + assert(prevBlocks != null, ERROR_NO_PREV_BLOCK_ID); + // Item at index 0 is the latest mc seqno. + // Inner item at index 2 is a seqno of the block id. + return prevBlocks.0.2; +} + +fun ValidatorDescr.readPubkeyOnly(s: slice): int { + val tag = s.loadUint(8); + assert((tag & ~0x20) == VALIDATOR_DESCR_TAG_SIMPLE, ERROR_INVALID_VALIDATOR_DESCR); + assert(s.loadUint(32) == PUBKEY_TAG_ED25519, ERROR_INVALID_VALIDATOR_DESCR); + return s.loadUint(256); +} + +@pure +fun blockchain.prevMcBlocks(): [BlockId] | null + asm "PREVMCBLOCKS" + +type BlockId = [int, int, int, int, int] diff --git a/contracts/tests/Slasher.spec.ts b/contracts/tests/Slasher.spec.ts new file mode 100644 index 0000000000..4ed660a7d8 --- /dev/null +++ b/contracts/tests/Slasher.spec.ts @@ -0,0 +1,194 @@ +import assert from "assert"; +import { compile } from "@ton/blueprint"; +import { + address, + beginCell, + BitString, + Cell, + Dictionary, + toNano, +} from "@ton/core"; +import { Blockchain, createShardAccount, SmartContract } from "@ton/sandbox"; +import { + getSecureRandomBytes, + KeyPair, + keyPairFromSeed, + sign, +} from "@ton/crypto"; +import { TychoExecutor } from "@tychosdk/emulator"; +import { + PARAM_IDX_SLASHER_PARAMS, + SlasherStub, + storeSlasherParams, + storeSlasherStubData, +} from "../wrappers/SlasherStub"; +import { + bufferToBigInt, + ConfigParams, + makeStubValidatorSet, + ValidatorDescrValue, +} from "../wrappers/util"; + +const SLASHER_ADDR = address( + "-1:6666666666666666666666666666666666666666666666666666666666666666", +); +const BLOCKS_BATCH_SIZE = 10; + +const SAMPLE_BLOCKS_BATCH = Cell.fromBase64( + "te6ccgEBCAEAMAABCwAAAObYYAECAswFAgIBIAQDAAfRCgDAAAdpRQBgAgEgBwYAB2UFAGAAB/SKAMA=", +); + +describe("Slasher", () => { + let config: Cell; + let code: Cell; + let executor: TychoExecutor; + let blockchain: Blockchain; + let slasher: SmartContract; + let keypair: KeyPair; + + beforeAll(async () => { + keypair = await getSecureRandomBytes(32).then(keyPairFromSeed); + + const params = new ConfigParams(TychoExecutor.defaultConfig); + params.setSignatureModifiers({ + signatureWithId: false, + signatureDomain: false, + }); + params.setRaw( + PARAM_IDX_SLASHER_PARAMS, + beginCell() + .store( + storeSlasherParams({ + address: SLASHER_ADDR.hash, + blocksBatchSize: BLOCKS_BATCH_SIZE, + }), + ) + .endCell(), + ); + + const vset = await makeStubValidatorSet({ + utimeSince: 0, + utimeUntil: 1 << 30, + validatorCount: 13, + }); + vset.validators.set(0, { + pubkey: bufferToBigInt(keypair.publicKey), + weight: 1n, + adnlAddr: null, + }); + params.setCurrentVset(vset); + + const fundamentalAddresses = Dictionary.load( + Dictionary.Keys.Buffer(32), + Dictionary.Values.BitString(0), + params.getRaw(31)!, + ); + fundamentalAddresses.set(SLASHER_ADDR.hash, BitString.EMPTY); + params.setRaw(31, beginCell().storeDict(fundamentalAddresses).endCell()); + + config = params.toCell(); + + code = await compile("SlasherStub", { debugInfo: true }); + executor = await TychoExecutor.create(); + }); + + beforeEach(async () => { + blockchain = await Blockchain.create({ + config, + executor, + }); + + await blockchain.setShardAccount( + SLASHER_ADDR, + createShardAccount({ + address: SLASHER_ADDR, + balance: toNano(500), + code, + data: beginCell() + .store( + storeSlasherStubData({ + updatedAtMs: 0n, + }), + ) + .endCell(), + workchain: -1, + }), + ); + + slasher = await blockchain.getContract(SLASHER_ADDR); + }); + + it("should accept valid blocks batch", async () => { + const { isValid } = await getters(blockchain, slasher).isBlocksBatchValid({ + blocksBatch: SAMPLE_BLOCKS_BATCH, + mcSeqno: 241, + }); + expect(isValid).toBe(true); + }); + + it("should accept valid messages", async () => { + const now = 10000000; + blockchain.now = now; + + const nowMs = now * 1000 + 500; + const expireAt = ~~(nowMs / 1000) + 60; + const catchainSeqno = 0; + const vsetSwitchRound = 0; + const validatorIdx = 0; + + const bodyToSign = beginCell() + .storeUint(nowMs, 64) + .storeUint(expireAt, 32) + .storeUint(catchainSeqno, 32) + .storeUint(vsetSwitchRound, 32) + .storeUint(validatorIdx, 16) + .storeRef(SAMPLE_BLOCKS_BATCH) + .endCell(); + const signature = sign(bodyToSign.hash(), keypair.secretKey); + const body = beginCell() + .storeBuffer(signature, 64) + .storeSlice(bodyToSign.asSlice()) + .endCell(); + + // slasher.setVerbosity({ + // blockchainLogs: true, + // debugLogs: true, + // vmLogs: "vm_logs_full", + // }); + + blockchain.prevBlocks = { + lastMcBlocks: [ + { + workchain: -1, + shard: 1n << 63n, + seqno: 241, + rootHash: Buffer.alloc(32), + fileHash: Buffer.alloc(32), + }, + ], + prevKeyBlock: { + workchain: -1, + shard: 1n << 63n, + seqno: 0, + rootHash: Buffer.alloc(32), + fileHash: Buffer.alloc(32), + }, + }; + const tx = await slasher.receiveMessage({ + info: { + type: "external-in", + dest: SLASHER_ADDR, + importFee: 0n, + }, + body, + }); + assert(tx.description.type === "generic"); + expect(tx.description.aborted).toBe(false); + }); +}); + +function getters(blockchain: Blockchain, slasher: SmartContract) { + return blockchain.openContract( + SlasherStub.createFromAddress(slasher.address), + ); +} diff --git a/contracts/wrappers/SlasherStub.compile.ts b/contracts/wrappers/SlasherStub.compile.ts new file mode 100644 index 0000000000..44aa7aeb51 --- /dev/null +++ b/contracts/wrappers/SlasherStub.compile.ts @@ -0,0 +1,9 @@ +import { CompilerConfig } from "@ton/blueprint"; + +export const compile: CompilerConfig = { + lang: "tolk", + entrypoint: "src/slasher-stub.tolk", + withStackComments: true, + withSrcLineComments: true, + experimentalOptions: "", +}; diff --git a/contracts/wrappers/SlasherStub.ts b/contracts/wrappers/SlasherStub.ts new file mode 100644 index 0000000000..1d7e54c16f --- /dev/null +++ b/contracts/wrappers/SlasherStub.ts @@ -0,0 +1,87 @@ +import { + Address, + Builder, + Cell, + Contract, + ContractProvider, + Slice, +} from "@ton/core"; +import { UnknownTagError } from "./util"; + +export const PARAM_IDX_SLASHER_PARAMS = 666; + +const SLASHER_PARAMS_TAG = 0x01; +export type SlasherParams = { + /// Slasher address in the masterchain. + address: Buffer; + blocksBatchSize: number; +}; + +export function loadSlasherParams(cs: Slice): SlasherParams { + const tag = cs.loadUint(8); + if (tag != SLASHER_PARAMS_TAG) { + throw new UnknownTagError({ tag, bits: 8 }); + } + return { + address: cs.loadBuffer(32), + blocksBatchSize: cs.loadUint(8), + }; +} + +export function storeSlasherParams( + s: SlasherParams, +): (builder: Builder) => void { + return (builder) => { + builder.storeUint(SLASHER_PARAMS_TAG, 8); + builder.storeBuffer(s.address, 32); + builder.storeUint(s.blocksBatchSize, 8); + }; +} + +export type SlasherStubData = { + updatedAtMs: bigint; +}; + +export function loadSlasherStubData(cs: Slice): SlasherStubData { + return { + updatedAtMs: cs.loadUintBig(64), + }; +} + +export function storeSlasherStubData( + s: SlasherStubData, +): (builder: Builder) => void { + return (builder) => { + builder.storeUint(s.updatedAtMs, 64); + }; +} + +export class SlasherStub implements Contract { + constructor( + readonly address: Address, + readonly init?: { code: Cell; data: Cell }, + ) {} + + static createFromAddress(address: Address) { + return new SlasherStub(address); + } + + async isBlocksBatchValid( + provider: ContractProvider, + args: { blocksBatch: Cell; mcSeqno: number }, + ) { + const { stack } = await provider.get("is_blocks_batch_valid", [ + { + type: "cell", + cell: args.blocksBatch, + }, + { + type: "int", + value: BigInt(args.mcSeqno), + }, + ]); + return { + isValid: stack.readBoolean(), + }; + } +} diff --git a/contracts/wrappers/util.ts b/contracts/wrappers/util.ts index a2199bd382..490cc3e340 100644 --- a/contracts/wrappers/util.ts +++ b/contracts/wrappers/util.ts @@ -5,6 +5,7 @@ import { Builder, Cell, Dictionary, + DictionaryValue, Message, Slice, toNano, @@ -32,6 +33,60 @@ export class ConfigParams { }; } + setRaw(idx: number, value: Cell) { + this.dict.set(idx, value); + } + + getRaw(idx: number): Cell | undefined { + return this.dict.get(idx); + } + + setSignatureModifiers(args: { + signatureWithId: boolean; + signatureDomain: boolean; + }) { + const GLOBAL_VERSION_TAG = 0xc4; + const SIGNATURE_WITH_ID_FLAG = 0x4000000n; + const SIGNATURE_DOMAIN_FLAG = 0x800000000n; + + const setFlag = (value: bigint, flag: bigint, set: boolean) => { + if (set) { + return value | flag; + } else { + return value & ~flag; + } + }; + + const global = this.dict.get(8)?.asSlice(); + if (global == null) { + return; + } + + const tag = global.loadUint(8); + if (tag != GLOBAL_VERSION_TAG) { + throw new UnknownTagError({ tag, bits: 8 }); + } + const version = global.loadUint(32); + let capabilities = global.loadUintBig(64); + capabilities = setFlag( + capabilities, + SIGNATURE_WITH_ID_FLAG, + args.signatureWithId, + ); + capabilities = setFlag( + capabilities, + SIGNATURE_DOMAIN_FLAG, + args.signatureDomain, + ); + + const newGlobal = beginCell() + .storeUint(tag, 8) + .storeUint(version, 32) + .storeUint(capabilities, 64) + .endCell(); + this.dict.set(8, newGlobal); + } + getVsetTimings(): VsetTimings { const cell = this.dict.get(15)!; const cs = cell.beginParse(); @@ -139,10 +194,10 @@ export async function makeStubValidatorSet(args: { utimeUntil: number; validatorCount: number; }): Promise { - let validators = Dictionary.empty(Dictionary.Keys.Uint(16), { - parse: loadValidatorDescr, - serialize: storeValidatorDescr, - }); + let validators = Dictionary.empty( + Dictionary.Keys.Uint(16), + ValidatorDescrValue, + ); for (let i = 0; i < args.validatorCount; i++) { validators.set(i, { @@ -167,20 +222,29 @@ export function loadValidatorSet(cs: Slice): ValidatorSet { throw new UnknownTagError({ tag, bits: 8 }); } + const utimeSince = cs.loadUint(32); + const utimeUntil = cs.loadUint(32); + const total = cs.loadUint(16); + const main = cs.loadUint(16); + const totalWeight = cs.loadUintBig(64); + const validators = cs.loadDict(Dictionary.Keys.Uint(16), ValidatorDescrValue); + if (validators.size != total) { + throw new Error( + `validator count mismatch: expected=${total}, got=${validators.size}`, + ); + } + return { - utimeSince: cs.loadUint(32), - utimeUntil: cs.loadUint(32), - main: cs.loadUint(16), - totalWeight: cs.loadUintBig(64), - validators: cs.loadDict(Dictionary.Keys.Uint(16), { - parse: loadValidatorDescr, - serialize: storeValidatorDescr, - }), + utimeSince, + utimeUntil, + main, + totalWeight, + validators, }; } export function storeValidatorSet( - vset: ValidatorSet + vset: ValidatorSet, ): (builder: Builder) => void { return (builder: Builder) => { builder.storeUint(VALIDATOR_SET_TAG, 8); @@ -194,9 +258,18 @@ export function storeValidatorSet( } const VALIDATOR_DESCR_TAG_SIMPLE = 0x53; -const VALIDATOR_DESCR_TAG_WITH_ADDR = 0x53; +const VALIDATOR_DESCR_TAG_WITH_ADDR = 0x73; const PUBKEY_TAG_ED25519 = 0x8e81278a; +export const ValidatorDescrValue: DictionaryValue = { + serialize: (src, builder) => builder.store(storeValidatorDescr(src)), + parse: (cs) => { + const res = loadValidatorDescr(cs); + cs.endParse(); + return res; + }, +}; + export type ValidatorDescr = { pubkey: bigint; weight: bigint; @@ -212,8 +285,13 @@ export function loadValidatorDescr(cs: Slice): ValidatorDescr { default: throw new UnknownTagError({ tag, bits: 8 }); } - const withAddr = tag === VALIDATOR_DESCR_TAG_WITH_ADDR; + + const pubkeyTag = cs.loadUint(32); + if (pubkeyTag != PUBKEY_TAG_ED25519) { + throw new UnknownTagError({ tag: pubkeyTag, bits: 32 }); + } + return { pubkey: cs.loadUintBig(256), weight: cs.loadUintBig(64), @@ -222,14 +300,14 @@ export function loadValidatorDescr(cs: Slice): ValidatorDescr { } export function storeValidatorDescr( - d: ValidatorDescr + d: ValidatorDescr, ): (builder: Builder) => void { return (builder: Builder) => { builder.storeUint( d.adnlAddr != null ? VALIDATOR_DESCR_TAG_WITH_ADDR : VALIDATOR_DESCR_TAG_SIMPLE, - 8 + 8, ); builder.storeUint(PUBKEY_TAG_ED25519, 32); builder.storeUint(d.pubkey, 256); @@ -283,7 +361,7 @@ export class ValidatorAccount { public createStakeMessage( electorAddress: Address, - args: ParticipateInElections + args: ParticipateInElections, ): Message { return simpleInternal({ src: this.address, @@ -370,7 +448,7 @@ export class UnknownTagError extends Error { super( `unknown tag 0x${args.tag .toString(16) - .padStart(Math.ceil(args.bits / 8), "0")}` + .padStart(Math.ceil(args.bits / 8), "0")}`, ); } } diff --git a/contracts/yarn.lock b/contracts/yarn.lock index ef19805c59..9ab77505ce 100644 --- a/contracts/yarn.lock +++ b/contracts/yarn.lock @@ -4,7 +4,7 @@ "@ampproject/remapping@^2.2.0": version "2.3.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz" integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== dependencies: "@jridgewell/gen-mapping" "^0.3.5" @@ -12,12 +12,12 @@ "@assemblyscript/loader@^0.9.4": version "0.9.4" - resolved "https://registry.yarnpkg.com/@assemblyscript/loader/-/loader-0.9.4.tgz#a483c54c1253656bb33babd464e3154a173e1577" + resolved "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.9.4.tgz" integrity sha512-HazVq9zwTVwGmqdwYzu7WyQ6FQVZ7SwET0KKQuKm55jD0IfUpZgN0OPIiZG3zV1iSrVYcN0bdwLRXI/VNCYsUA== -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.27.1": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1": version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz" integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== dependencies: "@babel/helper-validator-identifier" "^7.27.1" @@ -26,12 +26,12 @@ "@babel/compat-data@^7.27.2": version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.0.tgz#9fc6fd58c2a6a15243cd13983224968392070790" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz" integrity sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw== -"@babel/core@^7.23.9", "@babel/core@^7.27.4": +"@babel/core@^7.0.0", "@babel/core@^7.0.0 || ^8.0.0-0", "@babel/core@^7.0.0-0", "@babel/core@^7.11.0", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9", "@babel/core@^7.27.4", "@babel/core@^7.8.0", "@babel/core@>=7.0.0-beta.0 <8": version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.3.tgz#aceddde69c5d1def69b839d09efa3e3ff59c97cb" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz" integrity sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ== dependencies: "@ampproject/remapping" "^2.2.0" @@ -50,9 +50,9 @@ json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.27.5", "@babel/generator@^7.28.3": +"@babel/generator@^7.27.5", "@babel/generator@^7.28.3", "@babel/generator@^7.7.2": version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.3.tgz#9626c1741c650cbac39121694a0f2d7451b8ef3e" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz" integrity sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw== dependencies: "@babel/parser" "^7.28.3" @@ -63,7 +63,7 @@ "@babel/helper-compilation-targets@^7.27.2": version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz" integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== dependencies: "@babel/compat-data" "^7.27.2" @@ -74,12 +74,12 @@ "@babel/helper-globals@^7.28.0": version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + resolved "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz" integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== "@babel/helper-module-imports@^7.27.1": version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz" integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== dependencies: "@babel/traverse" "^7.27.1" @@ -87,7 +87,7 @@ "@babel/helper-module-transforms@^7.28.3": version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz#a2b37d3da3b2344fe085dab234426f2b9a2fa5f6" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz" integrity sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw== dependencies: "@babel/helper-module-imports" "^7.27.1" @@ -96,161 +96,161 @@ "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.8.0": version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz" integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== "@babel/helper-string-parser@^7.27.1": version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz" integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== "@babel/helper-validator-identifier@^7.27.1": version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz" integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== "@babel/helper-validator-option@^7.27.1": version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz" integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== "@babel/helpers@^7.28.3": version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.3.tgz#b83156c0a2232c133d1b535dd5d3452119c7e441" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz" integrity sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw== dependencies: "@babel/template" "^7.27.2" "@babel/types" "^7.28.2" -"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.27.2", "@babel/parser@^7.28.3": +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.27.2", "@babel/parser@^7.28.3": version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.3.tgz#d2d25b814621bca5fe9d172bc93792547e7a2a71" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz" integrity sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA== dependencies: "@babel/types" "^7.28.2" "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz" integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-bigint@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz" integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-class-properties@^7.12.13": version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz" integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== dependencies: "@babel/helper-plugin-utils" "^7.12.13" "@babel/plugin-syntax-class-static-block@^7.14.5": version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz" integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-import-attributes@^7.24.7": version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz#34c017d54496f9b11b61474e7ea3dfd5563ffe07" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz" integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== dependencies: "@babel/helper-plugin-utils" "^7.27.1" "@babel/plugin-syntax-import-meta@^7.10.4": version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz" integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz" integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@^7.27.1": +"@babel/plugin-syntax-jsx@^7.27.1", "@babel/plugin-syntax-jsx@^7.7.2": version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz" integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== dependencies: "@babel/helper-plugin-utils" "^7.27.1" "@babel/plugin-syntax-logical-assignment-operators@^7.10.4": version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz" integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz" integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-numeric-separator@^7.10.4": version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz" integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-object-rest-spread@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz" integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-optional-catch-binding@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz" integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-optional-chaining@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz" integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-private-property-in-object@^7.14.5": version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz" integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-top-level-await@^7.14.5": version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz" integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.27.1": +"@babel/plugin-syntax-typescript@^7.27.1", "@babel/plugin-syntax-typescript@^7.7.2": version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz#5147d29066a793450f220c63fa3a9431b7e6dd18" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz" integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ== dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/template@^7.27.2": +"@babel/template@^7.27.2", "@babel/template@^7.3.3": version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz" integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== dependencies: "@babel/code-frame" "^7.27.1" @@ -259,7 +259,7 @@ "@babel/traverse@^7.27.1", "@babel/traverse@^7.28.3": version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.3.tgz#6911a10795d2cce43ec6a28cffc440cca2593434" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz" integrity sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ== dependencies: "@babel/code-frame" "^7.27.1" @@ -270,9 +270,9 @@ "@babel/types" "^7.28.2" debug "^4.3.1" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2": +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.3.3": version "7.28.2" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.2.tgz#da9db0856a9a88e0a13b019881d7513588cf712b" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz" integrity sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ== dependencies: "@babel/helper-string-parser" "^7.27.1" @@ -280,41 +280,19 @@ "@bcoe/v8-coverage@^0.2.3": version "0.2.3" - resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== "@cspotcode/source-map-support@^0.8.0": version "0.8.1" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz" integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@emnapi/core@^1.4.3": - version "1.4.5" - resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.4.5.tgz#bfbb0cbbbb9f96ec4e2c4fd917b7bbe5495ceccb" - integrity sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q== - dependencies: - "@emnapi/wasi-threads" "1.0.4" - tslib "^2.4.0" - -"@emnapi/runtime@^1.4.3": - version "1.4.5" - resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.4.5.tgz#c67710d0661070f38418b6474584f159de38aba9" - integrity sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg== - dependencies: - tslib "^2.4.0" - -"@emnapi/wasi-threads@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.0.4.tgz#703fc094d969e273b1b71c292523b2f792862bf4" - integrity sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g== - dependencies: - tslib "^2.4.0" - "@inquirer/external-editor@^1.0.0": version "1.0.1" - resolved "https://registry.yarnpkg.com/@inquirer/external-editor/-/external-editor-1.0.1.tgz#ab0a82c5719a963fb469021cde5cd2b74fea30f8" + resolved "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.1.tgz" integrity sha512-Oau4yL24d2B5IL4ma4UpbQigkVhzPDXLoqy1ggK4gnHg/stmkffJE4oOXHXF3uz0UEpywG68KcyXsyYpA1Re/Q== dependencies: chardet "^2.1.0" @@ -322,14 +300,14 @@ "@ipld/dag-pb@^2.0.2": version "2.1.18" - resolved "https://registry.yarnpkg.com/@ipld/dag-pb/-/dag-pb-2.1.18.tgz#12d63e21580e87c75fd1a2c62e375a78e355c16f" + resolved "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-2.1.18.tgz" integrity sha512-ZBnf2fuX9y3KccADURG5vb9FaOeMjFkCrNysB0PtftME/4iCTjxfaLoNq/IAh5fTqUOMXvryN6Jyka4ZGuMLIg== dependencies: multiformats "^9.5.4" "@isaacs/cliui@^8.0.2": version "8.0.2" - resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + resolved "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz" integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== dependencies: string-width "^5.1.2" @@ -341,7 +319,7 @@ "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + resolved "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz" integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== dependencies: camelcase "^5.3.1" @@ -352,12 +330,24 @@ "@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + "@jest/console@30.0.5": version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-30.0.5.tgz#d7d027c2db5c64c20a973b7f3e57b49956d6c335" + resolved "https://registry.npmjs.org/@jest/console/-/console-30.0.5.tgz" integrity sha512-xY6b0XiL0Nav3ReresUarwl2oIz1gTnxGbGpho9/rbUWsLH0f1OD/VT84xs8c7VmH7MChnLb0pag6PhZhAdDiA== dependencies: "@jest/types" "30.0.5" @@ -367,9 +357,43 @@ jest-util "30.0.5" slash "^3.0.0" +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + "@jest/core@30.0.5": version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-30.0.5.tgz#b5778922d2928f676636e3ec199829554e61e452" + resolved "https://registry.npmjs.org/@jest/core/-/core-30.0.5.tgz" integrity sha512-fKD0OulvRsXF1hmaFgHhVJzczWzA1RXMMo9LTPuFXo9q/alDbME3JIyWYqovWsUBWSoBcsHaGPSLF9rz4l9Qeg== dependencies: "@jest/console" "30.0.5" @@ -403,12 +427,22 @@ "@jest/diff-sequences@30.0.1": version "30.0.1" - resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz#0ededeae4d071f5c8ffe3678d15f3a1be09156be" + resolved "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz" integrity sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw== +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + "@jest/environment@30.0.5": version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-30.0.5.tgz#eaaae0403c7d3f8414053c2224acc3011e1c3a1b" + resolved "https://registry.npmjs.org/@jest/environment/-/environment-30.0.5.tgz" integrity sha512-aRX7WoaWx1oaOkDQvCWImVQ8XNtdv5sEWgk4gxR6NXb7WBUnL5sRak4WRzIQRZ1VTWPvV4VI4mgGjNL9TeKMYA== dependencies: "@jest/fake-timers" "30.0.5" @@ -416,24 +450,51 @@ "@types/node" "*" jest-mock "30.0.5" +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + "@jest/expect-utils@30.0.5": version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.0.5.tgz#9d42e4b8bc80367db30abc6c42b2cb14073f66fc" + resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.5.tgz" integrity sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew== dependencies: "@jest/get-type" "30.0.1" +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + "@jest/expect@30.0.5": version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-30.0.5.tgz#2bbd101df4869f5d171c3cfee881f810f1525005" + resolved "https://registry.npmjs.org/@jest/expect/-/expect-30.0.5.tgz" integrity sha512-6udac8KKrtTtC+AXZ2iUN/R7dp7Ydry+Fo6FPFnDG54wjVMnb6vW/XNlf7Xj8UDjAE3aAVAsR4KFyKk3TCXmTA== dependencies: expect "30.0.5" jest-snapshot "30.0.5" +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + "@jest/fake-timers@30.0.5": version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-30.0.5.tgz#c028a9465a44b7744cb2368196bed89ce13c7054" + resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.0.5.tgz" integrity sha512-ZO5DHfNV+kgEAeP3gK3XlpJLL4U3Sz6ebl/n68Uwt64qFFs5bv4bfEEjyRGK5uM0C90ewooNgFuKMdkbEoMEXw== dependencies: "@jest/types" "30.0.5" @@ -445,12 +506,12 @@ "@jest/get-type@30.0.1": version "30.0.1" - resolved "https://registry.yarnpkg.com/@jest/get-type/-/get-type-30.0.1.tgz#0d32f1bbfba511948ad247ab01b9007724fc9f52" + resolved "https://registry.npmjs.org/@jest/get-type/-/get-type-30.0.1.tgz" integrity sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw== -"@jest/globals@30.0.5": +"@jest/globals@*", "@jest/globals@30.0.5": version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-30.0.5.tgz#ca70e0ac08ab40417cf8cd92bcb76116c2ccca63" + resolved "https://registry.npmjs.org/@jest/globals/-/globals-30.0.5.tgz" integrity sha512-7oEJT19WW4oe6HR7oLRvHxwlJk2gev0U9px3ufs8sX9PoD1Eza68KF0/tlN7X0dq/WVsBScXQGgCldA1V9Y/jA== dependencies: "@jest/environment" "30.0.5" @@ -458,17 +519,57 @@ "@jest/types" "30.0.5" jest-mock "30.0.5" +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + "@jest/pattern@30.0.1": version "30.0.1" - resolved "https://registry.yarnpkg.com/@jest/pattern/-/pattern-30.0.1.tgz#d5304147f49a052900b4b853dedb111d080e199f" + resolved "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz" integrity sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA== dependencies: "@types/node" "*" jest-regex-util "30.0.1" +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + "@jest/reporters@30.0.5": version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-30.0.5.tgz#b83585e6448d390a8d92a641c567f1655976d5c6" + resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-30.0.5.tgz" integrity sha512-mafft7VBX4jzED1FwGC1o/9QUM2xebzavImZMeqnsklgcyxBto8mV4HzNSzUrryJ+8R9MFOM3HgYuDradWR+4g== dependencies: "@bcoe/v8-coverage" "^0.2.3" @@ -495,16 +596,23 @@ string-length "^4.0.2" v8-to-istanbul "^9.0.1" +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + "@jest/schemas@30.0.5": version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-30.0.5.tgz#7bdf69fc5a368a5abdb49fd91036c55225846473" + resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz" integrity sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA== dependencies: "@sinclair/typebox" "^0.34.0" "@jest/snapshot-utils@30.0.5": version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/snapshot-utils/-/snapshot-utils-30.0.5.tgz#e23a0e786f174e8cff7f150c1cfbdc9cb7cc81a4" + resolved "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.0.5.tgz" integrity sha512-XcCQ5qWHLvi29UUrowgDFvV4t7ETxX91CbDczMnoqXPOIcZOxyNdSjm6kV5XMc8+HkxfRegU/MUmnTbJRzGrUQ== dependencies: "@jest/types" "30.0.5" @@ -512,18 +620,37 @@ graceful-fs "^4.2.11" natural-compare "^1.4.0" +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + "@jest/source-map@30.0.1": version "30.0.1" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-30.0.1.tgz#305ebec50468f13e658b3d5c26f85107a5620aaa" + resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz" integrity sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg== dependencies: "@jridgewell/trace-mapping" "^0.3.25" callsites "^3.1.0" graceful-fs "^4.2.11" +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + "@jest/test-result@30.0.5": version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-30.0.5.tgz#064c5210c24d5ea192fb02ceddad3be1cfa557c8" + resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-30.0.5.tgz" integrity sha512-wPyztnK0gbDMQAJZ43tdMro+qblDHH1Ru/ylzUo21TBKqt88ZqnKKK2m30LKmLLoKtR2lxdpCC/P3g1vfKcawQ== dependencies: "@jest/console" "30.0.5" @@ -531,9 +658,19 @@ "@types/istanbul-lib-coverage" "^2.0.6" collect-v8-coverage "^1.0.2" +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + "@jest/test-sequencer@30.0.5": version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-30.0.5.tgz#c6dba8fc3c386dd793c087626e8508ff1ead19f4" + resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.0.5.tgz" integrity sha512-Aea/G1egWoIIozmDD7PBXUOxkekXl7ueGzrsGGi1SbeKgQqCYCIf+wfbflEbf2LiPxL8j2JZGLyrzZagjvW4YQ== dependencies: "@jest/test-result" "30.0.5" @@ -541,9 +678,9 @@ jest-haste-map "30.0.5" slash "^3.0.0" -"@jest/transform@30.0.5": +"@jest/transform@^29.0.0 || ^30.0.0", "@jest/transform@30.0.5": version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-30.0.5.tgz#f8ca2e9f7466b77b406807d3bef1f6790dd384e4" + resolved "https://registry.npmjs.org/@jest/transform/-/transform-30.0.5.tgz" integrity sha512-Vk8amLQCmuZyy6GbBht1Jfo9RSdBtg7Lks+B0PecnjI8J+PCLQPGh7uI8Q/2wwpW2gLdiAfiHNsmekKlywULqg== dependencies: "@babel/core" "^7.27.4" @@ -562,9 +699,30 @@ slash "^3.0.0" write-file-atomic "^5.0.1" -"@jest/types@30.0.5": +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^29.0.0 || ^30.0.0", "@jest/types@30.0.5": version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.0.5.tgz#29a33a4c036e3904f1cfd94f6fe77f89d2e1cc05" + resolved "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz" integrity sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ== dependencies: "@jest/pattern" "30.0.1" @@ -575,9 +733,21 @@ "@types/yargs" "^17.0.33" chalk "^4.1.2" +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + "@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": version "0.3.13" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz" integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== dependencies: "@jridgewell/sourcemap-codec" "^1.5.0" @@ -585,90 +755,81 @@ "@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": version "1.5.5" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz" integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.23", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.30" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz" + integrity sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@jridgewell/trace-mapping@0.3.9": version "0.3.9" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== dependencies: "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.23", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.28": - version "0.3.30" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz#4a76c4daeee5df09f5d3940e087442fb36ce2b99" - integrity sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - "@multiformats/murmur3@^1.0.3": version "1.1.3" - resolved "https://registry.yarnpkg.com/@multiformats/murmur3/-/murmur3-1.1.3.tgz#70349166992e5f981f1ddff0200fa775b2bf6606" + resolved "https://registry.npmjs.org/@multiformats/murmur3/-/murmur3-1.1.3.tgz" integrity sha512-wAPLUErGR8g6Lt+bAZn6218k9YQPym+sjszsXL6o4zfxbA22P+gxWZuuD9wDbwL55xrKO5idpcuQUX7/E3oHcw== dependencies: multiformats "^9.5.4" murmurhash3js-revisited "^3.0.0" -"@napi-rs/wasm-runtime@^0.2.11": - version "0.2.12" - resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz#3e78a8b96e6c33a6c517e1894efbd5385a7cb6f2" - integrity sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ== - dependencies: - "@emnapi/core" "^1.4.3" - "@emnapi/runtime" "^1.4.3" - "@tybys/wasm-util" "^0.10.0" - "@noble/ed25519@^1.6.1": version "1.7.5" - resolved "https://registry.yarnpkg.com/@noble/ed25519/-/ed25519-1.7.5.tgz#94df8bdb9fec9c4644a56007eecb57b0e9fbd0d7" + resolved "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.5.tgz" integrity sha512-xuS0nwRMQBvSxDa7UxMb61xTiH3MxTgUfhyPUALVIe0FlOAz4sjELwyDRyUvqeEYfRSG9qNjFIycqLZppg4RSA== "@noble/hashes@^1.2.0": version "1.8.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" + resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz" integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== "@pkgjs/parseargs@^0.11.0": version "0.11.0" - resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== "@pkgr/core@^0.2.9": version "0.2.9" - resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.9.tgz#d229a7b7f9dac167a156992ef23c7f023653f53b" + resolved "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz" integrity sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA== "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + resolved "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz" integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== "@protobufjs/base64@^1.1.2": version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + resolved "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz" integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== "@protobufjs/codegen@^2.0.4": version "2.0.4" - resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + resolved "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz" integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== "@protobufjs/eventemitter@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + resolved "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz" integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== "@protobufjs/fetch@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + resolved "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz" integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== dependencies: "@protobufjs/aspromise" "^1.1.1" @@ -676,51 +837,63 @@ "@protobufjs/float@^1.0.2": version "1.0.2" - resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + resolved "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz" integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== "@protobufjs/inquire@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + resolved "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz" integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== "@protobufjs/path@^1.1.2": version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + resolved "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz" integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== "@protobufjs/pool@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + resolved "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz" integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== "@protobufjs/utf8@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + resolved "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz" integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + "@sinclair/typebox@^0.34.0": version "0.34.40" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.40.tgz#740056ea8d8aaada2ac1ce414c2f074798283b92" + resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.40.tgz" integrity sha512-gwBNIP8ZAYev/ORDWW0QvxdwPXwxBtLsdsJgSc7eDIRt8ubP+rxUBzPsrwnu16fgEF8Bx4lh/+mvQvJzcTM6Kw== -"@sinonjs/commons@^3.0.1": +"@sinonjs/commons@^3.0.0", "@sinonjs/commons@^3.0.1": version "3.0.1" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz" integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== dependencies: type-detect "4.0.8" +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + "@sinonjs/fake-timers@^13.0.0": version "13.0.5" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz#36b9dbc21ad5546486ea9173d6bea063eb1717d5" + resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz" integrity sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw== dependencies: "@sinonjs/commons" "^3.0.1" -"@tact-lang/compiler@1.6.13": +"@tact-lang/compiler@>=1.6.5", "@tact-lang/compiler@1.6.13": version "1.6.13" - resolved "https://registry.yarnpkg.com/@tact-lang/compiler/-/compiler-1.6.13.tgz#b2f7e6eb75b52fea052369d8ac34c061f63d5c99" + resolved "https://registry.npmjs.org/@tact-lang/compiler/-/compiler-1.6.13.tgz" integrity sha512-lrgT/kCgC+nuppB4zPSDCAcLQ6EauTJ3NEBX4prEBBRmJ8aexYAfFUfVayskZw96JrDjReNIhnD8dG/yU0Fk+w== dependencies: "@tact-lang/opcode" "^0.3.0" @@ -736,7 +909,7 @@ "@tact-lang/opcode@^0.3.0": version "0.3.2" - resolved "https://registry.yarnpkg.com/@tact-lang/opcode/-/opcode-0.3.2.tgz#0d103c5c5d3360348bf72b1b20fc507066fc4e01" + resolved "https://registry.npmjs.org/@tact-lang/opcode/-/opcode-0.3.2.tgz" integrity sha512-ZFsgOBTCxsKkYYOKomdaHMc8VSOFQKTbjLR1mYq6NFYyTdaz69gHEqgsIEZ0URSNWGg2er5H+LXlv8+8Tlt7sA== dependencies: "@ton/core" "^0.61.0" @@ -745,24 +918,24 @@ "@ton-api/client@^0.2.0": version "0.2.0" - resolved "https://registry.yarnpkg.com/@ton-api/client/-/client-0.2.0.tgz#82ca5cfba84919fd5010260d5695496af5b4e785" + resolved "https://registry.npmjs.org/@ton-api/client/-/client-0.2.0.tgz" integrity sha512-m/T8Nroq4rghUcg72Bbt2He9x5g9RlrP4F9rs7J4DVYLMHnKQTlKDo9JMD/feLXtHxOMh/YGJNcb+BGBkNbNug== dependencies: core-js-pure "^3.38.0" "@ton-api/ton-adapter@^0.2.0": version "0.2.0" - resolved "https://registry.yarnpkg.com/@ton-api/ton-adapter/-/ton-adapter-0.2.0.tgz#d118ed8fe683c670907b0d94d52ee3847b8050f4" + resolved "https://registry.npmjs.org/@ton-api/ton-adapter/-/ton-adapter-0.2.0.tgz" integrity sha512-0l1Y7pgi6/N6HOqRAdgOemDssYB4sXKtHWSKgm+cDL754ZMP3gsj/6pEmgOo/H7+itsKf4t0UqOU0q2JuqG/zw== "@ton-community/func-js-bin@0.4.6-wasmfix.debugger.0": version "0.4.6-wasmfix.debugger.0" - resolved "https://registry.yarnpkg.com/@ton-community/func-js-bin/-/func-js-bin-0.4.6-wasmfix.debugger.0.tgz#26af588cf4c0a10ba4d2cdfdcdc50b330edea19e" + resolved "https://registry.npmjs.org/@ton-community/func-js-bin/-/func-js-bin-0.4.6-wasmfix.debugger.0.tgz" integrity sha512-g23zEoaTn5Rja6TBTZxX3E4zh+PlWnt7iRJJT5mPuUvQXhWB9wkx9VNZN8KpTdICJIXTW5b5wEE/801W14xQvg== "@ton-community/func-js@>=0.10.0": version "0.10.0" - resolved "https://registry.yarnpkg.com/@ton-community/func-js/-/func-js-0.10.0.tgz#6f35f990255c5d1ea730d7ca9f5b4b2e2933f17a" + resolved "https://registry.npmjs.org/@ton-community/func-js/-/func-js-0.10.0.tgz" integrity sha512-YvkRTwkwc7e54Ig7oRKGercE91Fi+EuEDLO1kp/RnwslcUcgKQ+w2P7OFMNh/FWCJnU3ADhOBtfTU+dta+XHpw== dependencies: "@ton-community/func-js-bin" "0.4.6-wasmfix.debugger.0" @@ -771,7 +944,7 @@ "@ton/blueprint@>=0.40.0": version "0.40.0" - resolved "https://registry.yarnpkg.com/@ton/blueprint/-/blueprint-0.40.0.tgz#22a569a623eed8368fc4f758f4ef29b7599f13fb" + resolved "https://registry.npmjs.org/@ton/blueprint/-/blueprint-0.40.0.tgz" integrity sha512-DL0PDSgsZm7qmQTlCq+KbWjvzFTSQnRy4YQOHPfMpe3/Hla3BeblYiBCwFjk6ev7LXxcEPMlmliRnyXBO8DZuQ== dependencies: "@ton-api/client" "^0.2.0" @@ -786,72 +959,77 @@ ton-lite-client "^3.1.1" ts-node "^10.9.1" -"@ton/core@0.60.1", "@ton/core@^0.60.1": - version "0.60.1" - resolved "https://registry.yarnpkg.com/@ton/core/-/core-0.60.1.tgz#cc9a62fb308d7597b1217dc8e44c7e2dcc0aceaa" - integrity sha512-8FwybYbfkk57C3l9gvnlRhRBHbLYmeu0LbB1z9N+dhDz0Z+FJW8w0TJlks8CgHrAFxsT3FlR2LsqFnsauMp38w== +"@ton/core@^0.61.0": + version "0.61.0" + resolved "https://registry.npmjs.org/@ton/core/-/core-0.61.0.tgz" + integrity sha512-0qyVfP2dDue2bq80ydXggo2MlufcmzuFk6G94qRrZxvyQ3NSe4UeBTeRf1gQmN7tywgTsX2gS61e4yvJrlUu4Q== dependencies: symbol.inspect "1.0.1" -"@ton/core@^0.61.0", "@ton/core@~0": - version "0.61.0" - resolved "https://registry.yarnpkg.com/@ton/core/-/core-0.61.0.tgz#09b37801cb2f5a942020fcc992be1e99f4b16689" - integrity sha512-0qyVfP2dDue2bq80ydXggo2MlufcmzuFk6G94qRrZxvyQ3NSe4UeBTeRf1gQmN7tywgTsX2gS61e4yvJrlUu4Q== +"@ton/core@>=0.49.2", "@ton/core@>=0.56.0", "@ton/core@>=0.59.0", "@ton/core@>=0.60.0", "@ton/core@>=0.61.0", "@ton/core@>=0.62.0": + version "0.63.1" + resolved "https://registry.npmjs.org/@ton/core/-/core-0.63.1.tgz" + integrity sha512-hDWMjlKzc18W2E4OeV3hUP8ohRJNHPD4Wd1+AQJj8zshZyCRT0usrvnExgbNUTo/vntDqCGMzgYWbXxyaA+L4g== + +"@ton/core@0.60.1": + version "0.60.1" + resolved "https://registry.npmjs.org/@ton/core/-/core-0.60.1.tgz" + integrity sha512-8FwybYbfkk57C3l9gvnlRhRBHbLYmeu0LbB1z9N+dhDz0Z+FJW8w0TJlks8CgHrAFxsT3FlR2LsqFnsauMp38w== dependencies: symbol.inspect "1.0.1" "@ton/crypto-primitives@2.1.0": version "2.1.0" - resolved "https://registry.yarnpkg.com/@ton/crypto-primitives/-/crypto-primitives-2.1.0.tgz#8c9277c250b59aae3c819e0d6bd61e44d998e9ca" + resolved "https://registry.npmjs.org/@ton/crypto-primitives/-/crypto-primitives-2.1.0.tgz" integrity sha512-PQesoyPgqyI6vzYtCXw4/ZzevePc4VGcJtFwf08v10OevVJHVfW238KBdpj1kEDQkxWLeuNHEpTECNFKnP6tow== dependencies: jssha "3.2.0" -"@ton/crypto@^3.2.0", "@ton/crypto@^3.3.0": +"@ton/crypto@^3.2.0", "@ton/crypto@^3.3.0", "@ton/crypto@>=3.2.0", "@ton/crypto@>=3.3.0": version "3.3.0" - resolved "https://registry.yarnpkg.com/@ton/crypto/-/crypto-3.3.0.tgz#019103df6540fbc1d8102979b4587bc85ff9779e" + resolved "https://registry.npmjs.org/@ton/crypto/-/crypto-3.3.0.tgz" integrity sha512-/A6CYGgA/H36OZ9BbTaGerKtzWp50rg67ZCH2oIjV1NcrBaCK9Z343M+CxedvM7Haf3f/Ee9EhxyeTp0GKMUpA== dependencies: "@ton/crypto-primitives" "2.1.0" jssha "3.2.0" tweetnacl "1.0.3" -"@ton/sandbox@>=0.37.0": - version "0.37.0" - resolved "https://registry.yarnpkg.com/@ton/sandbox/-/sandbox-0.37.0.tgz#8315977379fabeaee8398dd89a72e1a0fb2ffd9f" - integrity sha512-1WK79g2cksOJPLsGtF/U8eZwSjw92jw7Jzb6R0wzUfWwZ8S9hEyQZcevkU8FOVDMWXJO84i+8is8GW8TsYZpFg== +"@ton/sandbox@^0.32.2": + version "0.32.2" + resolved "https://registry.npmjs.org/@ton/sandbox/-/sandbox-0.32.2.tgz" + integrity sha512-D+Yuyka3pMuoD1KPufRGzE3iFZ0QLyba/xC5mfrXoLtV111ubKxc7RscndOsggeru0bdDYm0i/iaWO5YQWqUfw== dependencies: - "@vscode/debugadapter" "^1.68.0" chalk "^4.1.2" - fflate "^0.8.2" table "^6.9.0" - ton-assembly "0.1.0" -"@ton/sandbox@^0.32.2": - version "0.32.2" - resolved "https://registry.yarnpkg.com/@ton/sandbox/-/sandbox-0.32.2.tgz#e949d1df2ae98c1552901597200209163dd8cc42" - integrity sha512-D+Yuyka3pMuoD1KPufRGzE3iFZ0QLyba/xC5mfrXoLtV111ubKxc7RscndOsggeru0bdDYm0i/iaWO5YQWqUfw== +"@ton/sandbox@>=0.34.0", "@ton/sandbox@>=0.39.0": + version "0.41.0" + resolved "https://registry.npmjs.org/@ton/sandbox/-/sandbox-0.41.0.tgz" + integrity sha512-+WRWiHfm62xQebVt6BvLb2UhVphpBHCwSby8R5vP9llzdVck+XEs+p4csIkZBh6gRQsy1Xomzh1PpgZS5XVE3A== dependencies: + "@vscode/debugadapter" "^1.68.0" chalk "^4.1.2" + fflate "^0.8.2" table "^6.9.0" + ton-assembly "0.6.1" -"@ton/test-utils@>=0.11.0": +"@ton/test-utils@>=0.11.0", "@ton/test-utils@>=0.7.0": version "0.11.0" - resolved "https://registry.yarnpkg.com/@ton/test-utils/-/test-utils-0.11.0.tgz#002ec16ce585f930ebdca3c93d20cde8aea6bc30" + resolved "https://registry.npmjs.org/@ton/test-utils/-/test-utils-0.11.0.tgz" integrity sha512-GFYUGsNdT+0xNU62aG+RG605sGYoLqLTEpfmR5TR2RjDZm+noDA50Dp0ImWGXBhD74/RrMKPaJ6KvzFgLC4vNg== dependencies: node-inspect-extracted "^2.0.0" -"@ton/tolk-js@>=1.0.0": +"@ton/tolk-js@>=0.13.0", "@ton/tolk-js@>=1.0.0": version "1.0.0" - resolved "https://registry.yarnpkg.com/@ton/tolk-js/-/tolk-js-1.0.0.tgz#f9863d8ed7016ea50890e5d33d9972042ef61c0f" + resolved "https://registry.npmjs.org/@ton/tolk-js/-/tolk-js-1.0.0.tgz" integrity sha512-OWFybjqo7MYZBB2XSuALEaH3uMJQeQHKbpbTmCWXJYnZskhPT4jwBshZ0gDvTskag++rwyPkcYTNJsfi6ngMXw== dependencies: arg "^5.0.2" -"@ton/ton@>=15.2.1 <16.0.0": +"@ton/ton@>=15.2.1", "@ton/ton@>=15.2.1 <16.0.0": version "15.3.1" - resolved "https://registry.yarnpkg.com/@ton/ton/-/ton-15.3.1.tgz#c20688b27eb8ce8474610843804a7599679c38a2" + resolved "https://registry.npmjs.org/@ton/ton/-/ton-15.3.1.tgz" integrity sha512-+UuvbE0o0VIU/0W90STO+emRIDr3Vs39LdbX5ySm/Ra+RQJSiH0KX6TDOFqWDmD2Wzk4/zw21KwSiZ6Xjk8zlw== dependencies: axios "^1.6.7" @@ -862,21 +1040,21 @@ "@tonconnect/isomorphic-eventsource@^0.0.1": version "0.0.1" - resolved "https://registry.yarnpkg.com/@tonconnect/isomorphic-eventsource/-/isomorphic-eventsource-0.0.1.tgz#199e5a86c31dad706b79826f65879e0d77d3dd51" + resolved "https://registry.npmjs.org/@tonconnect/isomorphic-eventsource/-/isomorphic-eventsource-0.0.1.tgz" integrity sha512-ODk48pMlqLSOvu3fM0R1sdlz/Cv2y4hSfwtXmLq9ky9+H7ZQfw/16ElpIJ69B4lUvHycxrueNgrRtF9PJHoGMw== dependencies: eventsource "^2.0.2" "@tonconnect/isomorphic-fetch@^0.0.2": version "0.0.2" - resolved "https://registry.yarnpkg.com/@tonconnect/isomorphic-fetch/-/isomorphic-fetch-0.0.2.tgz#c09ff05a409ec89262c369d4bf27305820cdaa33" + resolved "https://registry.npmjs.org/@tonconnect/isomorphic-fetch/-/isomorphic-fetch-0.0.2.tgz" integrity sha512-DAyA4oL7MqbBo9k8+8E+YiWsGCYi6UMhDTcsZjhgzhESkBNG6b+NBkpb1KH4oi0xDZQoknFtY9XogJLuQtSMQQ== dependencies: node-fetch "^2.6.9" "@tonconnect/protocol@^2.2.5": version "2.3.0" - resolved "https://registry.yarnpkg.com/@tonconnect/protocol/-/protocol-2.3.0.tgz#3b1ab9a56185ad676f52889a5fb44a611cf7a059" + resolved "https://registry.npmjs.org/@tonconnect/protocol/-/protocol-2.3.0.tgz" integrity sha512-OxrmcXF/EsSdGeASP9VpTRojuMtTV87DKYFLRq4ZJvF/Hirfm2cgcxzzj2uksEGm5IIR010UWo6b38RuokNwFQ== dependencies: tweetnacl "^1.0.3" @@ -884,56 +1062,49 @@ "@tonconnect/sdk@^2.2.0": version "2.2.0" - resolved "https://registry.yarnpkg.com/@tonconnect/sdk/-/sdk-2.2.0.tgz#8b0432102a4634ed3a1d2de1f44e1e03e4059591" + resolved "https://registry.npmjs.org/@tonconnect/sdk/-/sdk-2.2.0.tgz" integrity sha512-8plnAXzaLhapUnt47ZqAOQSIQ8NHSvgTSR74QVJdPWqg8128smgGM4cDYewKdBfTD6Lup0odT1WMMrJu+rE4NQ== dependencies: "@tonconnect/isomorphic-eventsource" "^0.0.1" "@tonconnect/isomorphic-fetch" "^0.0.2" "@tonconnect/protocol" "^2.2.5" -"@tonstudio/parser-runtime@0.0.1", "@tonstudio/parser-runtime@^0.0.1": +"@tonstudio/parser-runtime@^0.0.1", "@tonstudio/parser-runtime@0.0.1": version "0.0.1" - resolved "https://registry.yarnpkg.com/@tonstudio/parser-runtime/-/parser-runtime-0.0.1.tgz#469955fb7ea354d4fadaa5964359b11fd17f926b" + resolved "https://registry.npmjs.org/@tonstudio/parser-runtime/-/parser-runtime-0.0.1.tgz" integrity sha512-5s4fLkXWxa4SAd7QGGvJXe13GakEo0J3VF5dUI/i3A//bGZxMwCp1FcnbErpNs3y0LcAZoXE5FCUnDowDQptqw== "@tsconfig/node10@^1.0.7": version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz" integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== "@tsconfig/node12@^1.0.7": version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz" integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== "@tsconfig/node14@^1.0.0": version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz" integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== "@tsconfig/node16@^1.0.2": version "1.0.4" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz" integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== -"@tybys/wasm-util@^0.10.0": - version "0.10.0" - resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.0.tgz#2fd3cd754b94b378734ce17058d0507c45c88369" - integrity sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ== - dependencies: - tslib "^2.4.0" - -"@tychosdk/emulator@^0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@tychosdk/emulator/-/emulator-0.2.0.tgz#d1d28542d28b13b087e278924acdaa96002928f1" - integrity sha512-hyvd/iCqjUfBArgR/BjY3znjVSPB4xeDnzQi9rnf4SaMfKqzuIBk0jK02Y3g/n5egy02Hh+PfWeUoKr029z6zw== +"@tychosdk/emulator@^0.2.6": + version "0.2.6" + resolved "https://registry.npmjs.org/@tychosdk/emulator/-/emulator-0.2.6.tgz" + integrity sha512-03pJ9RroOpyVQ7006Ib7YSYeQBXS3ZeLHriU53jliKR0hZQmU+JqxFyKpa2DAvzM+G8VYTvBKWTwsH1XVdkUlw== dependencies: axios "^1.8.4" zod "^3.24.2" -"@types/babel__core@^7.20.5": +"@types/babel__core@^7.1.14", "@types/babel__core@^7.20.5": version "7.20.5" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz" integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== dependencies: "@babel/parser" "^7.20.7" @@ -944,55 +1115,62 @@ "@types/babel__generator@*": version "7.27.0" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" + resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz" integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== dependencies: "@babel/types" "^7.0.0" "@types/babel__template@*": version "7.4.4" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + resolved "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz" integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" -"@types/babel__traverse@*": +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": version "7.28.0" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74" + resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz" integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== dependencies: "@babel/types" "^7.28.2" "@types/bn.js@^5.1.0": version "5.2.0" - resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.2.0.tgz#4349b9710e98f9ab3cdc50f1c5e4dcbd8ef29c80" + resolved "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.2.0.tgz" integrity sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q== dependencies: "@types/node" "*" -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.1", "@types/istanbul-lib-coverage@^2.0.6": +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1", "@types/istanbul-lib-coverage@^2.0.6": version "2.0.6" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz" integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== "@types/istanbul-lib-report@*": version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz" integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== dependencies: "@types/istanbul-lib-coverage" "*" -"@types/istanbul-reports@^3.0.4": +"@types/istanbul-reports@^3.0.0", "@types/istanbul-reports@^3.0.4": version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz" integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== dependencies: "@types/istanbul-lib-report" "*" "@types/jest@^30.0.0": version "30.0.0" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-30.0.0.tgz#5e85ae568006712e4ad66f25433e9bdac8801f1d" + resolved "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz" integrity sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA== dependencies: expect "^30.0.0" @@ -1000,174 +1178,80 @@ "@types/long@^4.0.1": version "4.0.2" - resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" + resolved "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz" integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== -"@types/node@*", "@types/node@>=13.7.0": - version "24.3.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-24.3.0.tgz#89b09f45cb9a8ee69466f18ee5864e4c3eb84dec" - integrity sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow== - dependencies: - undici-types "~7.10.0" - -"@types/node@^22.15.32": +"@types/node@*", "@types/node@^22.15.32", "@types/node@>=13.7.0", "@types/node@>=18": version "22.17.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.17.2.tgz#47a93d6f4b79327da63af727e7c54e8cab8c4d33" + resolved "https://registry.npmjs.org/@types/node/-/node-22.17.2.tgz" integrity sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w== dependencies: undici-types "~6.21.0" "@types/pegjs@^0.10.3": version "0.10.6" - resolved "https://registry.yarnpkg.com/@types/pegjs/-/pegjs-0.10.6.tgz#bc20fc4809fed4cddab8d0dbee0e568803741a82" + resolved "https://registry.npmjs.org/@types/pegjs/-/pegjs-0.10.6.tgz" integrity sha512-eLYXDbZWXh2uxf+w8sXS8d6KSoXTswfps6fvCUuVAGN8eRpfe7h9eSRydxiSJvo9Bf+GzifsDOr9TMQlmJdmkw== -"@types/stack-utils@^2.0.3": +"@types/stack-utils@^2.0.0", "@types/stack-utils@^2.0.3": version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== "@types/yargs-parser@*": version "21.0.3" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz" integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== -"@types/yargs@^17.0.33": +"@types/yargs@^17.0.33", "@types/yargs@^17.0.8": version "17.0.33" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz" integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== dependencies: "@types/yargs-parser" "*" "@ungap/structured-clone@^1.3.0": version "1.3.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz" integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== -"@unrs/resolver-binding-android-arm-eabi@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz#9f5b04503088e6a354295e8ea8fe3cb99e43af81" - integrity sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw== - -"@unrs/resolver-binding-android-arm64@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz#7414885431bd7178b989aedc4d25cccb3865bc9f" - integrity sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g== - -"@unrs/resolver-binding-darwin-arm64@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz#b4a8556f42171fb9c9f7bac8235045e82aa0cbdf" - integrity sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g== - -"@unrs/resolver-binding-darwin-x64@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz#fd4d81257b13f4d1a083890a6a17c00de571f0dc" - integrity sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ== - -"@unrs/resolver-binding-freebsd-x64@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz#d2513084d0f37c407757e22f32bd924a78cfd99b" - integrity sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw== - -"@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz#844d2605d057488d77fab09705f2866b86164e0a" - integrity sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw== - -"@unrs/resolver-binding-linux-arm-musleabihf@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz#204892995cefb6bd1d017d52d097193bc61ddad3" - integrity sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw== - -"@unrs/resolver-binding-linux-arm64-gnu@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz#023eb0c3aac46066a10be7a3f362e7b34f3bdf9d" - integrity sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ== - -"@unrs/resolver-binding-linux-arm64-musl@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz#9e6f9abb06424e3140a60ac996139786f5d99be0" - integrity sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w== - -"@unrs/resolver-binding-linux-ppc64-gnu@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz#b111417f17c9d1b02efbec8e08398f0c5527bb44" - integrity sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA== - -"@unrs/resolver-binding-linux-riscv64-gnu@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz#92ffbf02748af3e99873945c9a8a5ead01d508a9" - integrity sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ== - -"@unrs/resolver-binding-linux-riscv64-musl@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz#0bec6f1258fc390e6b305e9ff44256cb207de165" - integrity sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew== - -"@unrs/resolver-binding-linux-s390x-gnu@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz#577843a084c5952f5906770633ccfb89dac9bc94" - integrity sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg== - "@unrs/resolver-binding-linux-x64-gnu@1.11.1": version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz#36fb318eebdd690f6da32ac5e0499a76fa881935" + resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz" integrity sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w== "@unrs/resolver-binding-linux-x64-musl@1.11.1": version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz#bfb9af75f783f98f6a22c4244214efe4df1853d6" + resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz" integrity sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA== -"@unrs/resolver-binding-wasm32-wasi@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz#752c359dd875684b27429500d88226d7cc72f71d" - integrity sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ== - dependencies: - "@napi-rs/wasm-runtime" "^0.2.11" - -"@unrs/resolver-binding-win32-arm64-msvc@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz#ce5735e600e4c2fbb409cd051b3b7da4a399af35" - integrity sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw== - -"@unrs/resolver-binding-win32-ia32-msvc@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz#72fc57bc7c64ec5c3de0d64ee0d1810317bc60a6" - integrity sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ== - -"@unrs/resolver-binding-win32-x64-msvc@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz#538b1e103bf8d9864e7b85cc96fa8d6fb6c40777" - integrity sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g== - "@vscode/debugadapter@^1.68.0": version "1.68.0" - resolved "https://registry.yarnpkg.com/@vscode/debugadapter/-/debugadapter-1.68.0.tgz#abb23463cb750ca4a6f0834c5d4db659258dc159" + resolved "https://registry.npmjs.org/@vscode/debugadapter/-/debugadapter-1.68.0.tgz" integrity sha512-D6gk5Fw2y4FV8oYmltoXpj+VAZexxJFopN/mcZ6YcgzQE9dgq2L45Aj3GLxScJOD6GeLILcxJIaA8l3v11esGg== dependencies: "@vscode/debugprotocol" "1.68.0" "@vscode/debugprotocol@1.68.0": version "1.68.0" - resolved "https://registry.yarnpkg.com/@vscode/debugprotocol/-/debugprotocol-1.68.0.tgz#e558ba6affe1be7aff4ec824599f316b61d9a69d" + resolved "https://registry.npmjs.org/@vscode/debugprotocol/-/debugprotocol-1.68.0.tgz" integrity sha512-2J27dysaXmvnfuhFGhfeuxfHRXunqNPxtBoR3koiTOA9rdxWNDTa1zIFLCFMSHJ9MPTPKFcBeblsyaCJCIlQxg== acorn-walk@^8.1.1: version "8.3.4" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz" integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== dependencies: acorn "^8.11.0" acorn@^8.11.0, acorn@^8.4.1: version "8.15.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz" integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== adnl@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/adnl/-/adnl-1.0.3.tgz#1fb328b70c2fdce2617667efa9ef750bc5561d6a" + resolved "https://registry.npmjs.org/adnl/-/adnl-1.0.3.tgz" integrity sha512-P+jGWhWTp0f4EskKie5rUA+EnQINzq0qvu1N3UkNSjynIOWCzl0wOPGUo3IAJ2r9/MI9DnebRYO0e4wyX7qbBw== dependencies: "@noble/ed25519" "^1.6.1" @@ -1180,12 +1264,12 @@ adnl@^1.0.3: aes-js@^3.1.2: version "3.1.2" - resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.2.tgz#db9aabde85d5caabbfc0d4f2a4446960f627146a" + resolved "https://registry.npmjs.org/aes-js/-/aes-js-3.1.2.tgz" integrity sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ== ajv@^8.0.1: version "8.17.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + resolved "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz" integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== dependencies: fast-deep-equal "^3.1.3" @@ -1195,41 +1279,46 @@ ajv@^8.0.1: ansi-escapes@^4.2.1, ansi-escapes@^4.3.2: version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== dependencies: type-fest "^0.21.3" ansi-regex@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== ansi-regex@^6.0.1: version "6.2.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.2.0.tgz#2f302e7550431b1b7762705fffb52cf1ffa20447" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz" integrity sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg== ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: color-convert "^2.0.1" +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + ansi-styles@^5.2.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== ansi-styles@^6.1.0: version "6.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== -anymatch@^3.1.3: +anymatch@^3.0.3, anymatch@^3.1.3: version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz" integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== dependencies: normalize-path "^3.0.0" @@ -1237,43 +1326,43 @@ anymatch@^3.1.3: arg@^4.1.0: version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== arg@^5.0.2: version "5.0.2" - resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" + resolved "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz" integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== argparse@^1.0.7: version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== dependencies: sprintf-js "~1.0.2" astral-regex@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + resolved "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== asynckit@^0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== axios@^1.6.7, axios@^1.7.7, axios@^1.8.4: version "1.11.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.11.0.tgz#c2ec219e35e414c025b2095e8b8280278478fdb6" + resolved "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz" integrity sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA== dependencies: follow-redirects "^1.15.6" form-data "^4.0.4" proxy-from-env "^1.1.0" -babel-jest@30.0.5: +"babel-jest@^29.0.0 || ^30.0.0", babel-jest@30.0.5: version "30.0.5" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-30.0.5.tgz#7cc7dd03d0d613125d458521f635b8c2361e89cc" + resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-30.0.5.tgz" integrity sha512-mRijnKimhGDMsizTvBTWotwNpzrkHr+VvZUQBof2AufXKB8NXrL1W69TG20EvOz7aevx6FTJIaBuBkYxS8zolg== dependencies: "@jest/transform" "30.0.5" @@ -1284,9 +1373,33 @@ babel-jest@30.0.5: graceful-fs "^4.2.11" slash "^3.0.0" +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + babel-plugin-istanbul@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.0.tgz#629a178f63b83dc9ecee46fd20266283b1f11280" + resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.0.tgz" integrity sha512-C5OzENSx/A+gt7t4VH1I2XsflxyPUmXRFPKBxt33xncdOmq7oROVM3bZv9Ysjjkv8OJYDMa+tKuKMvqU/H3xdw== dependencies: "@babel/helper-plugin-utils" "^7.0.0" @@ -1295,18 +1408,28 @@ babel-plugin-istanbul@^7.0.0: istanbul-lib-instrument "^6.0.2" test-exclude "^6.0.0" +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + babel-plugin-jest-hoist@30.0.1: version "30.0.1" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.0.1.tgz#f271b2066d2c1fb26a863adb8e13f85b06247125" + resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.0.1.tgz" integrity sha512-zTPME3pI50NsFW8ZBaVIOeAxzEY7XHlmWeXXu9srI+9kNfzCUTy8MFan46xOGZY8NZThMqq+e3qZUKsvXbasnQ== dependencies: "@babel/template" "^7.27.2" "@babel/types" "^7.27.3" "@types/babel__core" "^7.20.5" -babel-preset-current-node-syntax@^1.1.0: +babel-preset-current-node-syntax@^1.0.0, babel-preset-current-node-syntax@^1.1.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz#20730d6cdc7dda5d89401cab10ac6a32067acde6" + resolved "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz" integrity sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg== dependencies: "@babel/plugin-syntax-async-generators" "^7.8.4" @@ -1325,9 +1448,17 @@ babel-preset-current-node-syntax@^1.1.0: "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-syntax-top-level-await" "^7.14.5" +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + babel-preset-jest@30.0.1: version "30.0.1" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-30.0.1.tgz#7d28db9531bce264e846c8483d54236244b8ae88" + resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.0.1.tgz" integrity sha512-+YHejD5iTWI46cZmcc/YtX4gaKBtdqCHCVfuVinizVpbmyjO3zYmeuyFdfA8duRqQZfgCAMlsfmkVbJ+e2MAJw== dependencies: babel-plugin-jest-hoist "30.0.1" @@ -1335,17 +1466,17 @@ babel-preset-jest@30.0.1: balanced-match@^1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== base64-js@^1.3.1: version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== bl@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== dependencies: buffer "^5.5.0" @@ -1354,7 +1485,7 @@ bl@^4.1.0: bl@^5.0.0: version "5.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-5.1.0.tgz#183715f678c7188ecef9fe475d90209400624273" + resolved "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz" integrity sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ== dependencies: buffer "^6.0.3" @@ -1363,7 +1494,7 @@ bl@^5.0.0: blockstore-core@1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/blockstore-core/-/blockstore-core-1.0.5.tgz#2e34b6a7faae0d4b6c98dc8573c6f998eb457f36" + resolved "https://registry.npmjs.org/blockstore-core/-/blockstore-core-1.0.5.tgz" integrity sha512-i/9CUMMvBALVbtSqUIuiWB3tk//a4Q2I2CEWiBuYNnhJvk/DWplXjLt8Sqc5VGkRVXVPSsEuH8fUtqJt5UFYcA== dependencies: err-code "^3.0.1" @@ -1377,12 +1508,12 @@ blockstore-core@1.0.5: bn.js@^5.2.0: version "5.2.2" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.2.tgz#82c09f9ebbb17107cd72cb7fd39bd1f9d0aaa566" + resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz" integrity sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw== brace-expansion@^1.1.7: version "1.1.12" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz" integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== dependencies: balanced-match "^1.0.0" @@ -1390,21 +1521,21 @@ brace-expansion@^1.1.7: brace-expansion@^2.0.1: version "2.0.2" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz" integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== dependencies: balanced-match "^1.0.0" braces@^3.0.3: version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: fill-range "^7.1.1" -browserslist@^4.24.0: +browserslist@^4.24.0, "browserslist@>= 4.21.0": version "4.25.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.25.3.tgz#9167c9cbb40473f15f75f85189290678b99b16c5" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.25.3.tgz" integrity sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ== dependencies: caniuse-lite "^1.0.30001735" @@ -1414,26 +1545,26 @@ browserslist@^4.24.0: bs-logger@^0.2.6: version "0.2.6" - resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + resolved "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz" integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== dependencies: fast-json-stable-stringify "2.x" bser@2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz" integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== dependencies: node-int64 "^0.4.0" buffer-from@^1.0.0: version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== buffer@^5.5.0: version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== dependencies: base64-js "^1.3.1" @@ -1441,7 +1572,7 @@ buffer@^5.5.0: buffer@^6.0.3: version "6.0.3" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== dependencies: base64-js "^1.3.1" @@ -1449,45 +1580,50 @@ buffer@^6.0.3: cac@^6.7.14: version "6.7.14" - resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + resolved "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz" integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== dependencies: es-errors "^1.3.0" function-bind "^1.1.2" -callsites@^3.1.0: +callsites@^3.0.0, callsites@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== camelcase@^5.3.1: version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + camelcase@^6.3.0: version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001735: version "1.0.30001736" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001736.tgz#3710a99cf154b653590fb6a57f81ee34173c3b47" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001736.tgz" integrity sha512-ImpN5gLEY8gWeqfLUyEF4b7mYWcYoR2Si1VhnrbM4JizRFmfGaAQ12PhNykq6nvI4XvKLrsp8Xde74D5phJOSw== case-shift@^2.5.3: version "2.5.3" - resolved "https://registry.yarnpkg.com/case-shift/-/case-shift-2.5.3.tgz#b86ca38a75ade4efabd65dd1b064eab42288d844" + resolved "https://registry.npmjs.org/case-shift/-/case-shift-2.5.3.tgz" integrity sha512-6SdS9W5xu82Kj1Z6f14h0zsbWTdXGtD0RftPNnqbAFFqqlzX1SMFi1E1NDIBg5LC2m9EYWWPUV80nTb3iu2e6Q== -chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" @@ -1495,44 +1631,54 @@ chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: char-regex@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== chardet@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-2.1.0.tgz#1007f441a1ae9f9199a4a67f6e978fb0aa9aa3fe" + resolved "https://registry.npmjs.org/chardet/-/chardet-2.1.0.tgz" integrity sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA== +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + ci-info@^4.2.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.3.0.tgz#c39b1013f8fdbd28cd78e62318357d02da160cd7" + resolved "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz" integrity sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ== +cjs-module-lexer@^1.0.0: + version "1.4.3" + resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz" + integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== + cjs-module-lexer@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-2.1.0.tgz#586e87d4341cb2661850ece5190232ccdebcff8b" + resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.0.tgz" integrity sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA== cli-cursor@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz" integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== dependencies: restore-cursor "^3.1.0" cli-spinners@^2.5.0: version "2.9.2" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" + resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz" integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== cli-width@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + resolved "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz" integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== cliui@^8.0.1: version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz" integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== dependencies: string-width "^4.2.0" @@ -1541,66 +1687,79 @@ cliui@^8.0.1: clone@^1.0.2: version "1.0.4" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== co@^4.6.0: version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz" integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== -collect-v8-coverage@^1.0.2: +collect-v8-coverage@^1.0.0, collect-v8-coverage@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + resolved "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz" integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== color-convert@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== dependencies: color-name "~1.1.4" color-name@~1.1.4: version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== combined-stream@^1.0.8: version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: delayed-stream "~1.0.0" concat-map@0.0.1: version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== convert-source-map@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== core-js-pure@^3.38.0: version "3.45.1" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.45.1.tgz#b129d86a5f7f8380378577c7eaee83608570a05a" + resolved "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.45.1.tgz" integrity sha512-OHnWFKgTUshEU8MK+lOs1H8kC8GkTi9Z1tvNkxrCcw9wl3MJIO7q2ld77wjWn4/xuGrVu2X+nME1iIIPBSdyEQ== crc-32@^1.2.2: version "1.2.2" - resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" + resolved "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz" integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + create-require@^1.1.0: version "1.1.1" - resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== cross-spawn@^7.0.3, cross-spawn@^7.0.6: version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" @@ -1609,56 +1768,61 @@ cross-spawn@^7.0.3, cross-spawn@^7.0.6: dataloader@^2.0.0, dataloader@^2.1.0: version "2.2.3" - resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-2.2.3.tgz#42d10b4913515f5b37c6acedcb4960d6ae1b1517" + resolved "https://registry.npmjs.org/dataloader/-/dataloader-2.2.3.tgz" integrity sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA== debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: version "4.4.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz" integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== dependencies: ms "^2.1.3" -dedent@^1.6.0: +dedent@^1.0.0, dedent@^1.6.0: version "1.6.0" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.6.0.tgz#79d52d6389b1ffa67d2bcef59ba51847a9d503b2" + resolved "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz" integrity sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA== -deepmerge@^4.3.1: +deepmerge@^4.2.2, deepmerge@^4.3.1: version "4.3.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== defaults@^1.0.3: version "1.0.4" - resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" + resolved "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz" integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== dependencies: clone "^1.0.2" delayed-stream@~1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== -detect-newline@^3.1.0: +detect-newline@^3.0.0, detect-newline@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + diff@^4.0.1: version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== dotenv@^16.1.4: version "16.6.1" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz" integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow== dunder-proto@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz" integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== dependencies: call-bind-apply-helpers "^1.0.1" @@ -1667,61 +1831,61 @@ dunder-proto@^1.0.1: eastasianwidth@^0.2.0: version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== electron-to-chromium@^1.5.204: version "1.5.207" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.207.tgz#0fedde3eec615065ee95531c09a10578644c5552" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.207.tgz" integrity sha512-mryFrrL/GXDTmAtIVMVf+eIXM09BBPlO5IQ7lUyKmK8d+A4VpRGG+M3ofoVef6qyF8s60rJei8ymlJxjUA8Faw== emittery@^0.13.1: version "0.13.1" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + resolved "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz" integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== emoji-regex@^8.0.0: version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== emoji-regex@^9.2.2: version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== err-code@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/err-code/-/err-code-3.0.1.tgz#a444c7b992705f2b120ee320b09972eef331c920" + resolved "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz" integrity sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA== error-ex@^1.3.1: version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== dependencies: is-arrayish "^0.2.1" es-define-property@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== es-errors@^1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz" integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== dependencies: es-errors "^1.3.0" es-set-tostringtag@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz" integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== dependencies: es-errors "^1.3.0" @@ -1731,37 +1895,37 @@ es-set-tostringtag@^2.1.0: escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== escape-string-regexp@^1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== escape-string-regexp@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== esprima@^4.0.0: version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== events@^3.3.0: version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== eventsource@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-2.0.2.tgz#76dfcc02930fb2ff339520b6d290da573a9e8508" + resolved "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz" integrity sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA== -execa@^5.1.1: +execa@^5.0.0, execa@^5.1.1: version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz" integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== dependencies: cross-spawn "^7.0.3" @@ -1776,12 +1940,28 @@ execa@^5.1.1: exit-x@^0.2.2: version "0.2.2" - resolved "https://registry.yarnpkg.com/exit-x/-/exit-x-0.2.2.tgz#1f9052de3b8d99a696b10dad5bced9bdd5c3aa64" + resolved "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz" integrity sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ== -expect@30.0.5, expect@^30.0.0: +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + +expect@^30.0.0, expect@30.0.5: version "30.0.5" - resolved "https://registry.yarnpkg.com/expect/-/expect-30.0.5.tgz#c23bf193c5e422a742bfd2990ad990811de41a5a" + resolved "https://registry.npmjs.org/expect/-/expect-30.0.5.tgz" integrity sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ== dependencies: "@jest/expect-utils" "30.0.5" @@ -1793,48 +1973,48 @@ expect@30.0.5, expect@^30.0.0: fast-deep-equal@^3.1.3: version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.1.0: +fast-json-stable-stringify@^2.1.0, fast-json-stable-stringify@2.x: version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== fast-uri@^3.0.1: version "3.0.6" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.6.tgz#88f130b77cfaea2378d56bf970dea21257a68748" + resolved "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz" integrity sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw== -fb-watchman@^2.0.2: +fb-watchman@^2.0.0, fb-watchman@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz" integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== dependencies: bser "2.1.1" fflate@^0.8.2: version "0.8.2" - resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" + resolved "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz" integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A== figures@^3.0.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + resolved "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz" integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== dependencies: escape-string-regexp "^1.0.5" fill-range@^7.1.1: version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz" integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== dependencies: locate-path "^5.0.0" @@ -1842,12 +2022,12 @@ find-up@^4.0.0, find-up@^4.1.0: follow-redirects@^1.15.6: version "1.15.11" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz" integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== foreground-child@^3.1.0: version "3.3.1" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" + resolved "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz" integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== dependencies: cross-spawn "^7.0.6" @@ -1855,7 +2035,7 @@ foreground-child@^3.1.0: form-data@^4.0.4: version "4.0.4" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.4.tgz#784cdcce0669a9d68e94d11ac4eea98088edd2c4" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz" integrity sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow== dependencies: asynckit "^0.4.0" @@ -1866,32 +2046,27 @@ form-data@^4.0.4: fs.realpath@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - function-bind@^1.1.2: version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== gensync@^1.0.0-beta.2: version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== get-caller-file@^2.0.5: version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== get-intrinsic@^1.2.6: version "1.3.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== dependencies: call-bind-apply-helpers "^1.0.2" @@ -1907,12 +2082,12 @@ get-intrinsic@^1.2.6: get-package-type@^0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== get-proto@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz" integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== dependencies: dunder-proto "^1.0.1" @@ -1920,12 +2095,12 @@ get-proto@^1.0.1: get-stream@^6.0.0: version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== glob@^10.3.10: version "10.4.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + resolved "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz" integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== dependencies: foreground-child "^3.1.0" @@ -1935,9 +2110,21 @@ glob@^10.3.10: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" +glob@^7.1.3: + version "7.2.3" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^7.1.4: version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" @@ -1949,7 +2136,7 @@ glob@^7.1.4: glob@^8.1.0: version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + resolved "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz" integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== dependencies: fs.realpath "^1.0.0" @@ -1960,17 +2147,17 @@ glob@^8.1.0: gopd@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graceful-fs@^4.2.11: +graceful-fs@^4.2.11, graceful-fs@^4.2.9: version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== hamt-sharding@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/hamt-sharding/-/hamt-sharding-2.0.1.tgz#f45686d0339e74b03b233bee1bde9587727129b6" + resolved "https://registry.npmjs.org/hamt-sharding/-/hamt-sharding-2.0.1.tgz" integrity sha512-vnjrmdXG9dDs1m/H4iJ6z0JFI2NtgsW5keRkTcM85NGak69Mkf5PHUqBz+Xs0T4sg0ppvj9O5EGAJo40FTxmmA== dependencies: sparse-array "^1.3.1" @@ -1978,7 +2165,7 @@ hamt-sharding@^2.0.0: handlebars@^4.7.8: version "4.7.8" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" + resolved "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz" integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== dependencies: minimist "^1.2.5" @@ -1990,53 +2177,53 @@ handlebars@^4.7.8: has-flag@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== has-symbols@^1.0.3, has-symbols@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== has-tostringtag@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz" integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== dependencies: has-symbols "^1.0.3" hasown@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== dependencies: function-bind "^1.1.2" html-escaper@^2.0.0: version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== human-signals@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== iconv-lite@^0.6.3: version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== dependencies: safer-buffer ">= 2.1.2 < 3.0.0" ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -import-local@^3.2.0: +import-local@^3.0.2, import-local@^3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + resolved "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz" integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== dependencies: pkg-dir "^4.2.0" @@ -2044,25 +2231,25 @@ import-local@^3.2.0: imurmurhash@^0.1.4: version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== inflight@^1.0.4: version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.3, inherits@^2.0.4: +inherits@^2.0.3, inherits@^2.0.4, inherits@2: version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== inquirer@^8.2.5: version "8.2.7" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.7.tgz#62f6b931a9b7f8735dc42db927316d8fb6f71de8" + resolved "https://registry.npmjs.org/inquirer/-/inquirer-8.2.7.tgz" integrity sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA== dependencies: "@inquirer/external-editor" "^1.0.0" @@ -2083,7 +2270,7 @@ inquirer@^8.2.5: interface-blockstore@^2.0.2, interface-blockstore@^2.0.3: version "2.0.3" - resolved "https://registry.yarnpkg.com/interface-blockstore/-/interface-blockstore-2.0.3.tgz#b85270eb5180e65e46c9f66980a0fa4d98f5d73e" + resolved "https://registry.npmjs.org/interface-blockstore/-/interface-blockstore-2.0.3.tgz" integrity sha512-OwVUnlNcx7H5HloK0Myv6c/C1q9cNG11HX6afdeU6q6kbuNj8jKCwVnmJHhC94LZaJ+9hvVOk4IUstb3Esg81w== dependencies: interface-store "^2.0.2" @@ -2091,12 +2278,12 @@ interface-blockstore@^2.0.2, interface-blockstore@^2.0.3: interface-store@^2.0.1, interface-store@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/interface-store/-/interface-store-2.0.2.tgz#83175fd2b0c501585ed96db54bb8ba9d55fce34c" + resolved "https://registry.npmjs.org/interface-store/-/interface-store-2.0.2.tgz" integrity sha512-rScRlhDcz6k199EkHqT8NpM87ebN89ICOzILoBHgaG36/WX50N32BnU/kpZgCGPLhARRAWUUX5/cyaIjt7Kipg== ipfs-unixfs-importer@9.0.10: version "9.0.10" - resolved "https://registry.yarnpkg.com/ipfs-unixfs-importer/-/ipfs-unixfs-importer-9.0.10.tgz#2527ea0b4e018a9e80fa981101485babcd05c494" + resolved "https://registry.npmjs.org/ipfs-unixfs-importer/-/ipfs-unixfs-importer-9.0.10.tgz" integrity sha512-W+tQTVcSmXtFh7FWYWwPBGXJ1xDgREbIyI1E5JzDcimZLIyT5gGMfxR3oKPxxWj+GKMpP5ilvMQrbsPzWcm3Fw== dependencies: "@ipld/dag-pb" "^2.0.2" @@ -2117,7 +2304,7 @@ ipfs-unixfs-importer@9.0.10: ipfs-unixfs@^6.0.0: version "6.0.9" - resolved "https://registry.yarnpkg.com/ipfs-unixfs/-/ipfs-unixfs-6.0.9.tgz#f6613b8e081d83faa43ed96e016a694c615a9374" + resolved "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-6.0.9.tgz" integrity sha512-0DQ7p0/9dRB6XCb0mVCTli33GzIzSVx5udpJuVM47tGcD+W+Bl4LsnoLswd3ggNnNEakMv1FdoFITiEnchXDqQ== dependencies: err-code "^3.0.1" @@ -2125,62 +2312,80 @@ ipfs-unixfs@^6.0.0: is-arrayish@^0.2.1: version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + is-fullwidth-code-point@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-generator-fn@^2.1.0: +is-generator-fn@^2.0.0, is-generator-fn@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== is-interactive@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + resolved "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz" integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== is-number@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== is-plain-obj@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== is-stream@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== is-unicode-supported@^0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== isexe@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== isomorphic-ws@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" + resolved "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz" integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz" integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + istanbul-lib-instrument@^6.0.0, istanbul-lib-instrument@^6.0.2: version "6.0.3" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz" integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== dependencies: "@babel/core" "^7.23.9" @@ -2191,16 +2396,25 @@ istanbul-lib-instrument@^6.0.0, istanbul-lib-instrument@^6.0.2: istanbul-lib-report@^3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + resolved "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz" integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== dependencies: istanbul-lib-coverage "^3.0.0" make-dir "^4.0.0" supports-color "^7.1.0" +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + istanbul-lib-source-maps@^5.0.0: version "5.0.6" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz#acaef948df7747c8eb5fbf1265cb980f6353a441" + resolved "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz" integrity sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A== dependencies: "@jridgewell/trace-mapping" "^0.3.23" @@ -2209,7 +2423,7 @@ istanbul-lib-source-maps@^5.0.0: istanbul-reports@^3.1.3: version "3.2.0" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz#cb4535162b5784aa623cee21a7252cf2c807ac93" + resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz" integrity sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA== dependencies: html-escaper "^2.0.0" @@ -2217,62 +2431,97 @@ istanbul-reports@^3.1.3: it-all@^1.0.4, it-all@^1.0.5: version "1.0.6" - resolved "https://registry.yarnpkg.com/it-all/-/it-all-1.0.6.tgz#852557355367606295c4c3b7eff0136f07749335" + resolved "https://registry.npmjs.org/it-all/-/it-all-1.0.6.tgz" integrity sha512-3cmCc6Heqe3uWi3CVM/k51fa/XbMFpQVzFoDsV0IZNHSQDyAXl3c4MjHkFX5kF3922OGj7Myv1nSEUgRtcuM1A== it-batch@^1.0.8, it-batch@^1.0.9: version "1.0.9" - resolved "https://registry.yarnpkg.com/it-batch/-/it-batch-1.0.9.tgz#7e95aaacb3f9b1b8ca6c8b8367892171d6a5b37f" + resolved "https://registry.npmjs.org/it-batch/-/it-batch-1.0.9.tgz" integrity sha512-7Q7HXewMhNFltTsAMdSz6luNhyhkhEtGGbYek/8Xb/GiqYMtwUmopE1ocPSiJKKp3rM4Dt045sNFoUu+KZGNyA== it-drain@^1.0.4: version "1.0.5" - resolved "https://registry.yarnpkg.com/it-drain/-/it-drain-1.0.5.tgz#0466d4e286b37bcd32599d4e99b37a87cb8cfdf6" + resolved "https://registry.npmjs.org/it-drain/-/it-drain-1.0.5.tgz" integrity sha512-r/GjkiW1bZswC04TNmUnLxa6uovme7KKwPhc+cb1hHU65E3AByypHH6Pm91WHuvqfFsm+9ws0kPtDBV3/8vmIg== it-filter@^1.0.2: version "1.0.3" - resolved "https://registry.yarnpkg.com/it-filter/-/it-filter-1.0.3.tgz#66ea0cc4bf84af71bebd353c05a9c5735fcba751" + resolved "https://registry.npmjs.org/it-filter/-/it-filter-1.0.3.tgz" integrity sha512-EI3HpzUrKjTH01miLHWmhNWy3Xpbx4OXMXltgrNprL5lDpF3giVpHIouFpr5l+evXw6aOfxhnt01BIB+4VQA+w== it-first@^1.0.6: version "1.0.7" - resolved "https://registry.yarnpkg.com/it-first/-/it-first-1.0.7.tgz#a4bef40da8be21667f7d23e44dae652f5ccd7ab1" + resolved "https://registry.npmjs.org/it-first/-/it-first-1.0.7.tgz" integrity sha512-nvJKZoBpZD/6Rtde6FXqwDqDZGF1sCADmr2Zoc0hZsIvnE449gRFnGctxDf09Bzc/FWnHXAdaHVIetY6lrE0/g== it-parallel-batch@^1.0.9: version "1.0.11" - resolved "https://registry.yarnpkg.com/it-parallel-batch/-/it-parallel-batch-1.0.11.tgz#f889b4e1c7a62ef24111dbafbaaa010b33d00f69" + resolved "https://registry.npmjs.org/it-parallel-batch/-/it-parallel-batch-1.0.11.tgz" integrity sha512-UWsWHv/kqBpMRmyZJzlmZeoAMA0F3SZr08FBdbhtbe+MtoEBgr/ZUAKrnenhXCBrsopy76QjRH2K/V8kNdupbQ== dependencies: it-batch "^1.0.9" it-take@^1.0.1: version "1.0.2" - resolved "https://registry.yarnpkg.com/it-take/-/it-take-1.0.2.tgz#b5f1570014db7c3454897898b69bb7ac9c3bffc1" + resolved "https://registry.npmjs.org/it-take/-/it-take-1.0.2.tgz" integrity sha512-u7I6qhhxH7pSevcYNaMECtkvZW365ARqAIt9K+xjdK1B2WUDEjQSfETkOCT8bxFq/59LqrN3cMLUtTgmDBaygw== jackspeak@^3.1.2: version "3.4.3" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + resolved "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz" integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== dependencies: "@isaacs/cliui" "^8.0.2" optionalDependencies: "@pkgjs/parseargs" "^0.11.0" +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + jest-changed-files@30.0.5: version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-30.0.5.tgz#ec448f83bd9caa894dd7da8707f207c356a19924" + resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.0.5.tgz" integrity sha512-bGl2Ntdx0eAwXuGpdLdVYVr5YQHnSZlQ0y9HVDu565lCUAe9sj6JOtBbMmBBikGIegne9piDDIOeiLVoqTkz4A== dependencies: execa "^5.1.1" jest-util "30.0.5" p-limit "^3.1.0" +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + jest-circus@30.0.5: version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-30.0.5.tgz#9b4d44feb56c7ffe14411ad7fc08af188c5d4da7" + resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-30.0.5.tgz" integrity sha512-h/sjXEs4GS+NFFfqBDYT7y5Msfxh04EwWLhQi0F8kuWpe+J/7tICSlswU8qvBqumR3kFgHbfu7vU6qruWWBPug== dependencies: "@jest/environment" "30.0.5" @@ -2296,9 +2545,26 @@ jest-circus@30.0.5: slash "^3.0.0" stack-utils "^2.0.6" +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + jest-cli@30.0.5: version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-30.0.5.tgz#c3fbfdabd1a5c428429476f915a1ba6d0774cc50" + resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-30.0.5.tgz" integrity sha512-Sa45PGMkBZzF94HMrlX4kUyPOwUpdZasaliKN3mifvDmkhLYqLLg8HQTzn6gq7vJGahFYMQjXgyJWfYImKZzOw== dependencies: "@jest/core" "30.0.5" @@ -2312,9 +2578,37 @@ jest-cli@30.0.5: jest-validate "30.0.5" yargs "^17.7.2" +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + jest-config@30.0.5: version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-30.0.5.tgz#567cf39b595229b786506a496c22e222d5e8d480" + resolved "https://registry.npmjs.org/jest-config/-/jest-config-30.0.5.tgz" integrity sha512-aIVh+JNOOpzUgzUnPn5FLtyVnqc3TQHVMupYtyeURSb//iLColiMIR8TxCIDKyx9ZgjKnXGucuW68hCxgbrwmA== dependencies: "@babel/core" "^7.27.4" @@ -2342,9 +2636,19 @@ jest-config@30.0.5: slash "^3.0.0" strip-json-comments "^3.1.1" +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + jest-diff@30.0.5: version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.0.5.tgz#b40f81e0c0d13e5b81c4d62b0d0dfa6a524ee0fd" + resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.5.tgz" integrity sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A== dependencies: "@jest/diff-sequences" "30.0.1" @@ -2352,16 +2656,34 @@ jest-diff@30.0.5: chalk "^4.1.2" pretty-format "30.0.5" +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + jest-docblock@30.0.1: version "30.0.1" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-30.0.1.tgz#545ff59f2fa88996bd470dba7d3798a8421180b1" + resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.0.1.tgz" integrity sha512-/vF78qn3DYphAaIc3jy4gA7XSAz167n9Bm/wn/1XhTLW7tTBIzXtCJpb/vcmc73NIIeeohCbdL94JasyXUZsGA== dependencies: detect-newline "^3.1.0" +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + jest-each@30.0.5: version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-30.0.5.tgz#5962264ff246cd757ba44db096c1bc5b4835173e" + resolved "https://registry.npmjs.org/jest-each/-/jest-each-30.0.5.tgz" integrity sha512-dKjRsx1uZ96TVyejD3/aAWcNKy6ajMaN531CwWIsrazIqIoXI9TnnpPlkrEYku/8rkS3dh2rbH+kMOyiEIv0xQ== dependencies: "@jest/get-type" "30.0.1" @@ -2370,9 +2692,21 @@ jest-each@30.0.5: jest-util "30.0.5" pretty-format "30.0.5" +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + jest-environment-node@30.0.5: version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-30.0.5.tgz#6a98dd80e0384ead67ed05643381395f6cda93c9" + resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.0.5.tgz" integrity sha512-ppYizXdLMSvciGsRsMEnv/5EFpvOdXBaXRBzFUDPWrsfmog4kYrOGWXarLllz6AXan6ZAA/kYokgDWuos1IKDA== dependencies: "@jest/environment" "30.0.5" @@ -2383,9 +2717,33 @@ jest-environment-node@30.0.5: jest-util "30.0.5" jest-validate "30.0.5" +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + jest-haste-map@30.0.5: version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-30.0.5.tgz#fdd0daa322b02eb34267854cff2859fae21e92a6" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.0.5.tgz" integrity sha512-dkmlWNlsTSR0nH3nRfW5BKbqHefLZv0/6LCccG0xFCTWcJu8TuEwG+5Cm75iBfjVoockmO6J35o5gxtFSn5xeg== dependencies: "@jest/types" "30.0.5" @@ -2401,17 +2759,35 @@ jest-haste-map@30.0.5: optionalDependencies: fsevents "^2.3.3" +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + jest-leak-detector@30.0.5: version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-30.0.5.tgz#00cfd2b323f48d8f4416b0a3e05fcf4c51f18864" + resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.0.5.tgz" integrity sha512-3Uxr5uP8jmHMcsOtYMRB/zf1gXN3yUIc+iPorhNETG54gErFIiUhLvyY/OggYpSMOEYqsmRxmuU4ZOoX5jpRFg== dependencies: "@jest/get-type" "30.0.1" pretty-format "30.0.5" +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + jest-matcher-utils@30.0.5: version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz#dff3334be58faea4a5e1becc228656fbbfc2467d" + resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz" integrity sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ== dependencies: "@jest/get-type" "30.0.1" @@ -2419,9 +2795,24 @@ jest-matcher-utils@30.0.5: jest-diff "30.0.5" pretty-format "30.0.5" +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + jest-message-util@30.0.5: version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.0.5.tgz#dd12ffec91dd3fa6a59cbd538a513d8e239e070c" + resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.5.tgz" integrity sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA== dependencies: "@babel/code-frame" "^7.27.1" @@ -2434,36 +2825,58 @@ jest-message-util@30.0.5: slash "^3.0.0" stack-utils "^2.0.6" +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + jest-mock@30.0.5: version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.0.5.tgz#ef437e89212560dd395198115550085038570bdd" + resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.5.tgz" integrity sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ== dependencies: "@jest/types" "30.0.5" "@types/node" "*" jest-util "30.0.5" -jest-pnp-resolver@^1.2.3: +jest-pnp-resolver@^1.2.2, jest-pnp-resolver@^1.2.3: version "1.2.3" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz" integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + jest-regex-util@30.0.1: version "30.0.1" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-30.0.1.tgz#f17c1de3958b67dfe485354f5a10093298f2a49b" + resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz" integrity sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA== +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + jest-resolve-dependencies@30.0.5: version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-30.0.5.tgz#53be4c51d296c84a0e75608e7b77b6fe92dbac29" + resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.0.5.tgz" integrity sha512-/xMvBR4MpwkrHW4ikZIWRttBBRZgWK4d6xt3xW1iRDSKt4tXzYkMkyPfBnSCgv96cpkrctfXs6gexeqMYqdEpw== dependencies: jest-regex-util "30.0.1" jest-snapshot "30.0.5" -jest-resolve@30.0.5: +jest-resolve@*, jest-resolve@30.0.5: version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-30.0.5.tgz#f52f91600070b7073db465dc553eee5471ea8e06" + resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.0.5.tgz" integrity sha512-d+DjBQ1tIhdz91B79mywH5yYu76bZuE96sSbxj8MkjWVx5WNdt1deEFRONVL4UkKLSrAbMkdhb24XN691yDRHg== dependencies: chalk "^4.1.2" @@ -2475,9 +2888,51 @@ jest-resolve@30.0.5: slash "^3.0.0" unrs-resolver "^1.7.11" +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + jest-runner@30.0.5: version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-30.0.5.tgz#5cbaaf85964246da4f65d697f186846f23cd9b5a" + resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-30.0.5.tgz" integrity sha512-JcCOucZmgp+YuGgLAXHNy7ualBx4wYSgJVWrYMRBnb79j9PD0Jxh0EHvR5Cx/r0Ce+ZBC4hCdz2AzFFLl9hCiw== dependencies: "@jest/console" "30.0.5" @@ -2503,9 +2958,37 @@ jest-runner@30.0.5: p-limit "^3.1.0" source-map-support "0.5.13" +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + jest-runtime@30.0.5: version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-30.0.5.tgz#d6a7e22687264240d1786d6f7682ac6a2872e552" + resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.0.5.tgz" integrity sha512-7oySNDkqpe4xpX5PPiJTe5vEa+Ak/NnNz2bGYZrA1ftG3RL3EFlHaUkA1Cjx+R8IhK0Vg43RML5mJedGTPNz3A== dependencies: "@jest/environment" "30.0.5" @@ -2531,9 +3014,35 @@ jest-runtime@30.0.5: slash "^3.0.0" strip-bom "^4.0.0" +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + natural-compare "^1.4.0" + pretty-format "^29.7.0" + semver "^7.5.3" + jest-snapshot@30.0.5: version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-30.0.5.tgz#6600716eef2e6d8ea1dd788ae4385f3a2791b11f" + resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.0.5.tgz" integrity sha512-T00dWU/Ek3LqTp4+DcW6PraVxjk28WY5Ua/s+3zUKSERZSNyxTqhDXCWKG5p2HAJ+crVQ3WJ2P9YVHpj1tkW+g== dependencies: "@babel/core" "^7.27.4" @@ -2558,9 +3067,9 @@ jest-snapshot@30.0.5: semver "^7.7.2" synckit "^0.11.8" -jest-util@30.0.5: +"jest-util@^29.0.0 || ^30.0.0", jest-util@30.0.5: version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.0.5.tgz#035d380c660ad5f1748dff71c4105338e05f8669" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz" integrity sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g== dependencies: "@jest/types" "30.0.5" @@ -2570,9 +3079,33 @@ jest-util@30.0.5: graceful-fs "^4.2.11" picomatch "^4.0.2" +jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + jest-validate@30.0.5: version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-30.0.5.tgz#d26fd218b8d566bff48fd98880b8ea94fd0d8456" + resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-30.0.5.tgz" integrity sha512-ouTm6VFHaS2boyl+k4u+Qip4TSH7Uld5tyD8psQ8abGgt2uYYB8VwVfAHWHjHc0NWmGGbwO5h0sCPOGHHevefw== dependencies: "@jest/get-type" "30.0.1" @@ -2582,9 +3115,23 @@ jest-validate@30.0.5: leven "^3.1.0" pretty-format "30.0.5" +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + jest-watcher@30.0.5: version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-30.0.5.tgz#90db6e3f582b88085bde58f7555cbdd3a1beb10d" + resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.0.5.tgz" integrity sha512-z9slj/0vOwBDBjN3L4z4ZYaA+pG56d6p3kTUhFRYGvXbXMWhXmb/FIxREZCD06DYUwDKKnj2T80+Pb71CQ0KEg== dependencies: "@jest/test-result" "30.0.5" @@ -2596,9 +3143,19 @@ jest-watcher@30.0.5: jest-util "30.0.5" string-length "^4.0.2" +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + jest-worker@30.0.5: version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-30.0.5.tgz#0b85cbab10610303e8d84e214f94d8f052c3cd04" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-30.0.5.tgz" integrity sha512-ojRXsWzEP16NdUuBw/4H/zkZdHOa7MMYCk4E430l+8fELeLg/mqmMlRhjL7UNZvQrDmnovWZV4DxX03fZF48fQ== dependencies: "@types/node" "*" @@ -2607,9 +3164,9 @@ jest-worker@30.0.5: merge-stream "^2.0.0" supports-color "^8.1.1" -jest@^30.0.0: +"jest@^29.0.0 || ^30.0.0", "jest@^29.5.0 || ^30.0.5", jest@^30.0.0: version "30.0.5" - resolved "https://registry.yarnpkg.com/jest/-/jest-30.0.5.tgz#ee62729fb77829790d67c660d852350fbde315ce" + resolved "https://registry.npmjs.org/jest/-/jest-30.0.5.tgz" integrity sha512-y2mfcJywuTUkvLm2Lp1/pFX8kTgMO5yyQGq/Sk/n2mN7XWYp4JsCZ/QXW34M8YScgk8bPZlREH04f6blPnoHnQ== dependencies: "@jest/core" "30.0.5" @@ -2617,14 +3174,24 @@ jest@^30.0.0: import-local "^3.2.0" jest-cli "30.0.5" +jest@^29.5.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + js-tokens@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@^3.13.1: version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== dependencies: argparse "^1.0.7" @@ -2632,64 +3199,69 @@ js-yaml@^3.13.1: jsesc@^3.0.2: version "3.1.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz" integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== json-parse-even-better-errors@^2.3.0: version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== json-schema-traverse@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== json5@^2.2.3: version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== jssha@3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/jssha/-/jssha-3.2.0.tgz#88ec50b866dd1411deaddbe6b3e3692e4c710f16" + resolved "https://registry.npmjs.org/jssha/-/jssha-3.2.0.tgz" integrity sha512-QuruyBENDWdN4tZwJbQq7/eAK85FqrI4oDbXjy5IBhYD+2pTJyBUWZe8ctWaCkrV0gy6AaelgOZZBMeswEa/6Q== +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + leven@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== lines-and-columns@^1.1.6: version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== locate-path@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== dependencies: p-locate "^4.1.0" lodash.memoize@^4.1.2: version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== lodash.truncate@^4.4.2: version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + resolved "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz" integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== lodash@^4.17.21: version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== log-symbols@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== dependencies: chalk "^4.1.0" @@ -2697,65 +3269,65 @@ log-symbols@^4.1.0: long@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + resolved "https://registry.npmjs.org/long/-/long-4.0.0.tgz" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== +lru_map@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/lru_map/-/lru_map-0.4.1.tgz" + integrity sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg== + lru-cache@^10.2.0: version "10.4.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== lru-cache@^5.1.1: version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== dependencies: yallist "^3.0.2" -lru_map@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.4.1.tgz#f7b4046283c79fb7370c36f8fca6aee4324b0a98" - integrity sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg== - make-dir@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + resolved "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz" integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== dependencies: semver "^7.5.3" make-error@^1.1.1, make-error@^1.3.6: version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== makeerror@1.0.12: version "1.0.12" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz" integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== dependencies: tmpl "1.0.5" math-intrinsics@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz" integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== merge-options@^3.0.4: version "3.0.4" - resolved "https://registry.yarnpkg.com/merge-options/-/merge-options-3.0.4.tgz#84709c2aa2a4b24c1981f66c179fe5565cc6dbb7" + resolved "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz" integrity sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ== dependencies: is-plain-obj "^2.1.0" merge-stream@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -micromatch@^4.0.8: +micromatch@^4.0.4, micromatch@^4.0.8: version "4.0.8" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: braces "^3.0.3" @@ -2763,138 +3335,138 @@ micromatch@^4.0.8: mime-db@1.52.0: version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== mime-types@^2.1.12: version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" mimic-fn@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== minimatch@^3.0.4, minimatch@^3.1.1: version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" minimatch@^5.0.1: version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz" integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== dependencies: brace-expansion "^2.0.1" minimatch@^9.0.4: version "9.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz" integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== dependencies: brace-expansion "^2.0.1" minimist@^1.2.5: version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== "minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: version "7.1.2" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz" integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== ms@^2.1.3: version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== multiformats@^9.0.4, multiformats@^9.4.2, multiformats@^9.4.7, multiformats@^9.5.4: version "9.9.0" - resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37" + resolved "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz" integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg== murmurhash3js-revisited@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/murmurhash3js-revisited/-/murmurhash3js-revisited-3.0.0.tgz#6bd36e25de8f73394222adc6e41fa3fac08a5869" + resolved "https://registry.npmjs.org/murmurhash3js-revisited/-/murmurhash3js-revisited-3.0.0.tgz" integrity sha512-/sF3ee6zvScXMb1XFJ8gDsSnY+X8PbOyjIuBhtgis10W2Jx4ZjIhikUCIF9c4gpJxVnQIsPAFrSwTCuAjicP6g== mute-stream@0.0.8: version "0.0.8" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== napi-postinstall@^0.3.0: version "0.3.3" - resolved "https://registry.yarnpkg.com/napi-postinstall/-/napi-postinstall-0.3.3.tgz#93d045c6b576803ead126711d3093995198c6eb9" + resolved "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz" integrity sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow== natural-compare@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== neo-async@^2.6.2: version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== node-fetch@^2.6.1, node-fetch@^2.6.9: version "2.7.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== dependencies: whatwg-url "^5.0.0" node-inspect-extracted@^2.0.0: version "2.0.2" - resolved "https://registry.yarnpkg.com/node-inspect-extracted/-/node-inspect-extracted-2.0.2.tgz#e5500e79f6bc03517175881c991f3bfaea67115a" + resolved "https://registry.npmjs.org/node-inspect-extracted/-/node-inspect-extracted-2.0.2.tgz" integrity sha512-8qm9+tu/GmbA/uWQRs6ad8KstyhH94a0pXpRVGHaJ9wHlJbetCYoCwXbKILaaMcE+wgbgpOpzcCnipkL8vTqxA== node-int64@^0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz" integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== node-releases@^2.0.19: version "2.0.19" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz" integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== normalize-path@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== npm-run-path@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== dependencies: path-key "^3.0.0" once@^1.3.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== dependencies: mimic-fn "^2.1.0" ora@^5.4.1: version "5.4.1" - resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + resolved "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz" integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== dependencies: bl "^4.1.0" @@ -2909,38 +3481,38 @@ ora@^5.4.1: p-limit@^2.2.0: version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== dependencies: p-try "^2.0.0" p-limit@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: yocto-queue "^0.1.0" p-locate@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== dependencies: p-limit "^2.2.0" p-try@^2.0.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== package-json-from-dist@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + resolved "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz" integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== parse-json@^5.2.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== dependencies: "@babel/code-frame" "^7.0.0" @@ -2950,27 +3522,32 @@ parse-json@^5.2.0: path-exists@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== path-is-absolute@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== path-normalize@^6.0.13: version "6.0.13" - resolved "https://registry.yarnpkg.com/path-normalize/-/path-normalize-6.0.13.tgz#f80575c85ef041366040b00cdbeea97b8e255231" + resolved "https://registry.npmjs.org/path-normalize/-/path-normalize-6.0.13.tgz" integrity sha512-PfC1Pc+IEhI77UEN731pj2nMs9gHAV36IA6IW6VdXWjoQesf+jtO9hdMUqTRS6mwR0T5rmyUrQzd5vw0VwL1Lw== +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + path-scurry@^1.11.1: version "1.11.1" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + resolved "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz" integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== dependencies: lru-cache "^10.2.0" @@ -2978,48 +3555,65 @@ path-scurry@^1.11.1: pegjs@^0.10.0: version "0.10.0" - resolved "https://registry.yarnpkg.com/pegjs/-/pegjs-0.10.0.tgz#cf8bafae6eddff4b5a7efb185269eaaf4610ddbd" + resolved "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz" integrity sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow== picocolors@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== -picomatch@^2.0.4, picomatch@^2.3.1: +picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== picomatch@^4.0.2: version "4.0.3" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz" integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== -pirates@^4.0.7: +pirates@^4.0.4, pirates@^4.0.7: version "4.0.7" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" + resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz" integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== pkg-dir@^4.2.0: version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz" integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== dependencies: find-up "^4.0.0" -pretty-format@30.0.5, pretty-format@^30.0.0: +pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +pretty-format@^30.0.0, pretty-format@30.0.5: version "30.0.5" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.0.5.tgz#e001649d472800396c1209684483e18a4d250360" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz" integrity sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw== dependencies: "@jest/schemas" "30.0.5" ansi-styles "^5.2.0" react-is "^18.3.1" +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + protobufjs@^6.10.2: version "6.11.4" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.4.tgz#29a412c38bf70d89e537b6d02d904a6f448173aa" + resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz" integrity sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw== dependencies: "@protobufjs/aspromise" "^1.1.2" @@ -3038,22 +3632,27 @@ protobufjs@^6.10.2: proxy-from-env@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== +pure-rand@^6.0.0: + version "6.1.0" + resolved "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + pure-rand@^7.0.0: version "7.0.1" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-7.0.1.tgz#6f53a5a9e3e4a47445822af96821ca509ed37566" + resolved "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz" integrity sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ== qrcode-terminal@^0.12.0: version "0.12.0" - resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" + resolved "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz" integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ== rabin-wasm@^0.1.4: version "0.1.5" - resolved "https://registry.yarnpkg.com/rabin-wasm/-/rabin-wasm-0.1.5.tgz#5b625ca007d6a2cbc1456c78ae71d550addbc9c9" + resolved "https://registry.npmjs.org/rabin-wasm/-/rabin-wasm-0.1.5.tgz" integrity sha512-uWgQTo7pim1Rnj5TuWcCewRDTf0PEFTSlaUjWP4eY9EbLV9em08v89oCz/WO+wRxpYuO36XEHp4wgYQnAgOHzA== dependencies: "@assemblyscript/loader" "^0.9.4" @@ -3063,14 +3662,14 @@ rabin-wasm@^0.1.4: node-fetch "^2.6.1" readable-stream "^3.6.0" -react-is@^18.3.1: +react-is@^18.0.0, react-is@^18.3.1: version "18.3.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== dependencies: inherits "^2.0.3" @@ -3079,29 +3678,43 @@ readable-stream@^3.4.0, readable-stream@^3.6.0: require-directory@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== require-from-string@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== resolve-cwd@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz" integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== dependencies: resolve-from "^5.0.0" resolve-from@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== +resolve.exports@^2.0.0: + version "2.0.3" + resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz" + integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== + +resolve@^1.20.0: + version "1.22.10" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + dependencies: + is-core-module "^2.16.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + restore-cursor@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz" integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== dependencies: onetime "^5.1.0" @@ -3109,66 +3722,81 @@ restore-cursor@^3.1.0: run-async@^2.4.0: version "2.4.1" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + resolved "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz" integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== rxjs@^7.5.5: version "7.8.2" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" + resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz" integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== dependencies: tslib "^2.1.0" safe-buffer@~5.2.0: version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -semver@^6.3.1: +semver@^6.3.0, semver@^6.3.1: version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.5.3, semver@^7.5.4, semver@^7.7.2: +semver@^7.5.3: + version "7.7.2" + resolved "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + +semver@^7.5.4: version "7.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" + resolved "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + +semver@^7.7.2: + version "7.7.2" + resolved "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz" integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== shebang-command@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== dependencies: shebang-regex "^3.0.0" shebang-regex@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -signal-exit@^3.0.2, signal-exit@^3.0.3: +signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== signal-exit@^4.0.1: version "4.1.0" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + slash@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== slice-ansi@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz" integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== dependencies: ansi-styles "^4.0.0" @@ -3177,7 +3805,7 @@ slice-ansi@^4.0.0: source-map-support@0.5.13: version "0.5.13" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz" integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== dependencies: buffer-from "^1.0.0" @@ -3185,29 +3813,36 @@ source-map-support@0.5.13: source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== sparse-array@^1.3.1: version "1.3.2" - resolved "https://registry.yarnpkg.com/sparse-array/-/sparse-array-1.3.2.tgz#0e1a8b71706d356bc916fe754ff496d450ec20b0" + resolved "https://registry.npmjs.org/sparse-array/-/sparse-array-1.3.2.tgz" integrity sha512-ZT711fePGn3+kQyLuv1fpd3rNSkNF8vd5Kv2D+qnOANeyKs3fx6bUMGWRPvgTTcYV64QMqZKZwcuaQSP3AZ0tg== sprintf-js@~1.0.2: version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== -stack-utils@^2.0.6: +stack-utils@^2.0.3, stack-utils@^2.0.6: version "2.0.6" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz" integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== dependencies: escape-string-regexp "^2.0.0" -string-length@^4.0.2: +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string-length@^4.0.1, string-length@^4.0.2: version "4.0.2" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== dependencies: char-regex "^1.0.2" @@ -3215,7 +3850,7 @@ string-length@^4.0.2: "string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== dependencies: emoji-regex "^8.0.0" @@ -3224,7 +3859,7 @@ string-length@^4.0.2: string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== dependencies: emoji-regex "^8.0.0" @@ -3233,85 +3868,90 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz" integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== dependencies: eastasianwidth "^0.2.0" emoji-regex "^9.2.2" strip-ansi "^7.0.1" -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - "strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" strip-ansi@^7.0.1: version "7.1.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz" integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== dependencies: ansi-regex "^6.0.1" strip-bom@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz" integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== strip-final-newline@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== strip-json-comments@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== supports-color@^7.1.0: version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + supports-color@^8.1.1: version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== dependencies: has-flag "^4.0.0" +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + symbol.inspect@1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/symbol.inspect/-/symbol.inspect-1.0.1.tgz#e13125b8038c4996eb0dfa1567332ad4dcd0763f" + resolved "https://registry.npmjs.org/symbol.inspect/-/symbol.inspect-1.0.1.tgz" integrity sha512-YQSL4duoHmLhsTD1Pw8RW6TZ5MaTX5rXJnqacJottr2P2LZBF/Yvrc3ku4NUpMOm8aM0KOCqM+UAkMA5HWQCzQ== synckit@^0.11.8: version "0.11.11" - resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.11.tgz#c0b619cf258a97faa209155d9cd1699b5c998cb0" + resolved "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz" integrity sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw== dependencies: "@pkgr/core" "^0.2.9" table@^6.9.0: version "6.9.0" - resolved "https://registry.yarnpkg.com/table/-/table-6.9.0.tgz#50040afa6264141c7566b3b81d4d82c47a8668f5" + resolved "https://registry.npmjs.org/table/-/table-6.9.0.tgz" integrity sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A== dependencies: ajv "^8.0.1" @@ -3322,12 +3962,12 @@ table@^6.9.0: teslabot@^1.3.0, teslabot@^1.5.0: version "1.5.0" - resolved "https://registry.yarnpkg.com/teslabot/-/teslabot-1.5.0.tgz#70f544516699ca5f696d8ae94f3d12cd495d5cd6" + resolved "https://registry.npmjs.org/teslabot/-/teslabot-1.5.0.tgz" integrity sha512-e2MmELhCgrgZEGo7PQu/6bmYG36IDH+YrBI1iGm6jovXkeDIGa3pZ2WSqRjzkuw2vt1EqfkZoV5GpXgqL8QJVg== test-exclude@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz" integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== dependencies: "@istanbuljs/schema" "^0.1.2" @@ -3336,33 +3976,33 @@ test-exclude@^6.0.0: through@^2.3.6: version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== tmpl@1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz" integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== to-regex-range@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== dependencies: is-number "^7.0.0" -ton-assembly@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/ton-assembly/-/ton-assembly-0.1.0.tgz#a3f1da50c2d2be1cd0f9837a9399ea80f80021eb" - integrity sha512-2k0vIKleGMXXu6yXV8L1eICClGZVwqfs4G0FgBCgC6Qgani6pAqoLGNBNjcdw3554Ua3E+1K4oz/oW+GhZfm+w== +ton-assembly@0.6.1: + version "0.6.1" + resolved "https://registry.npmjs.org/ton-assembly/-/ton-assembly-0.6.1.tgz" + integrity sha512-HZNDD2Cy8DQ9UY+8eCgCFY9RBnHEA+Abxo6chDtZQqsX1xo97UZzhMWzK+bYoe1w9gsGugi+1kOH7cpjzDN6jQ== dependencies: - "@ton/core" "^0.60.1" "@tonstudio/parser-runtime" "^0.0.1" cac "^6.7.14" + ton-source-map "^0.2.2" ton-lite-client@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/ton-lite-client/-/ton-lite-client-3.1.1.tgz#9b5655eb56c84164ebf2075117077c6d21c17ce6" + resolved "https://registry.npmjs.org/ton-lite-client/-/ton-lite-client-3.1.1.tgz" integrity sha512-jhgwRC0txsekBact1rFwgGE3DdgRnMDk2htHZjzLgO9PupdVLAkoFJJ3K9LvtXgY7bKFRcTI+GZWCZk2xRJ0Ig== dependencies: adnl "^1.0.3" @@ -3372,9 +4012,14 @@ ton-lite-client@^3.1.1: ton-tl "^1.0.1" tweetnacl "^1.0.3" +ton-source-map@^0.2.2: + version "0.2.2" + resolved "https://registry.npmjs.org/ton-source-map/-/ton-source-map-0.2.2.tgz" + integrity sha512-T9as2Cmv5aqFbELd0ZxIyY3NRPGxf3ltpVN8rm+uIXMMDlNaGW3Wf6jFcaJYwkRNB2eR52PhNbt5tI5lwgL1Cg== + ton-tl@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/ton-tl/-/ton-tl-1.0.1.tgz#210756ca6a136a0f405c29733dce182c4e1fc1f6" + resolved "https://registry.npmjs.org/ton-tl/-/ton-tl-1.0.1.tgz" integrity sha512-dAHJSWEW0CRNm/sdWVhola9/OZc/yHmLOXlSNr9I6l0WaVZmGhwkmDuzvMm1ZJ3Dvhf5tYN+iAUSSgmf8Q+P0g== dependencies: "@types/bn.js" "^5.1.0" @@ -3386,12 +4031,12 @@ ton-tl@^1.0.1: tr46@~0.0.3: version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== ts-jest@^29.4.0: version "29.4.1" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.4.1.tgz#42d33beb74657751d315efb9a871fe99e3b9b519" + resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.1.tgz" integrity sha512-SaeUtjfpg9Uqu8IbeDKtdaS0g8lS6FT6OzM3ezrDfErPJPHNDo/Ey+VFGP1bQIDfagYDLyRpd7O15XpG1Es2Uw== dependencies: bs-logger "^0.2.6" @@ -3404,9 +4049,9 @@ ts-jest@^29.4.0: type-fest "^4.41.0" yargs-parser "^21.1.1" -ts-node@^10.9.1, ts-node@^10.9.2: +ts-node@^10.9.1, ts-node@^10.9.2, ts-node@>=9.0.0: version "10.9.2" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz" integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== dependencies: "@cspotcode/source-map-support" "^0.8.0" @@ -3423,66 +4068,61 @@ ts-node@^10.9.1, ts-node@^10.9.2: v8-compile-cache-lib "^3.0.1" yn "3.1.1" -tslib@^2.1.0, tslib@^2.4.0: +tslib@^2.1.0: version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== tweetnacl-util@^0.15.1: version "0.15.1" - resolved "https://registry.yarnpkg.com/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz#b80fcdb5c97bcc508be18c44a4be50f022eea00b" + resolved "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz" integrity sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw== -tweetnacl@1.0.3, tweetnacl@^1.0.3: +tweetnacl@^1.0.3, tweetnacl@1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" + resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz" integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== type-detect@4.0.8: version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== type-fest@^0.21.3: version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== type-fest@^4.41.0: version "4.41.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz" integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== -typescript@^5.9.2: +typescript@^5.9.2, typescript@>=2.7, "typescript@>=4.3 <6": version "5.9.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz" integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A== uglify-js@^3.1.4: version "3.19.3" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" + resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz" integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== uint8arrays@^3.0.0: version "3.1.1" - resolved "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-3.1.1.tgz#2d8762acce159ccd9936057572dade9459f65ae0" + resolved "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz" integrity sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg== dependencies: multiformats "^9.4.2" undici-types@~6.21.0: version "6.21.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz" integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== -undici-types@~7.10.0: - version "7.10.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.10.0.tgz#4ac2e058ce56b462b056e629cc6a02393d3ff350" - integrity sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag== - unrs-resolver@^1.7.11: version "1.11.1" - resolved "https://registry.yarnpkg.com/unrs-resolver/-/unrs-resolver-1.11.1.tgz#be9cd8686c99ef53ecb96df2a473c64d304048a9" + resolved "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz" integrity sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg== dependencies: napi-postinstall "^0.3.0" @@ -3509,7 +4149,7 @@ unrs-resolver@^1.7.11: update-browserslist-db@^1.1.3: version "1.1.3" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" + resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz" integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== dependencies: escalade "^3.2.0" @@ -3517,17 +4157,17 @@ update-browserslist-db@^1.1.3: util-deprecate@^1.0.1: version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== v8-compile-cache-lib@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== v8-to-istanbul@^9.0.1: version "9.3.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz" integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== dependencies: "@jridgewell/trace-mapping" "^0.3.12" @@ -3536,26 +4176,26 @@ v8-to-istanbul@^9.0.1: walker@^1.0.8: version "1.0.8" - resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz" integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== dependencies: makeerror "1.0.12" wcwidth@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + resolved "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz" integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== dependencies: defaults "^1.0.3" webidl-conversions@^3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== whatwg-url@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== dependencies: tr46 "~0.0.3" @@ -3563,19 +4203,19 @@ whatwg-url@^5.0.0: which@^2.0.1: version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" wordwrap@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: ansi-styles "^4.0.0" @@ -3584,7 +4224,7 @@ wordwrap@^1.0.0: wrap-ansi@^6.0.1: version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz" integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== dependencies: ansi-styles "^4.0.0" @@ -3593,7 +4233,7 @@ wrap-ansi@^6.0.1: wrap-ansi@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: ansi-styles "^4.0.0" @@ -3602,7 +4242,7 @@ wrap-ansi@^7.0.0: wrap-ansi@^8.1.0: version "8.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz" integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== dependencies: ansi-styles "^6.1.0" @@ -3611,45 +4251,53 @@ wrap-ansi@^8.1.0: wrappy@1: version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + write-file-atomic@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-5.0.1.tgz#68df4717c55c6fa4281a7860b4c2ba0a6d2b11e7" + resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz" integrity sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw== dependencies: imurmurhash "^0.1.4" signal-exit "^4.0.1" -ws@^8.8.1: +ws@*, ws@^8.8.1: version "8.18.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472" + resolved "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz" integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg== y18n@^5.0.5: version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== yallist@^3.0.2: version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== yaml@^2.7.1: version "2.8.1" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.1.tgz#1870aa02b631f7e8328b93f8bc574fac5d6c4d79" + resolved "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz" integrity sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw== yargs-parser@^21.1.1: version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== -yargs@^17.7.2: +yargs@^17.3.1, yargs@^17.7.2: version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== dependencies: cliui "^8.0.1" @@ -3662,15 +4310,15 @@ yargs@^17.7.2: yn@3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== yocto-queue@^0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== zod@^3.21.4, zod@^3.22.4, zod@^3.24.2: version "3.25.76" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34" + resolved "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz" integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== diff --git a/core/src/block_strider/mod.rs b/core/src/block_strider/mod.rs index 55000accbe..333219b459 100644 --- a/core/src/block_strider/mod.rs +++ b/core/src/block_strider/mod.rs @@ -36,9 +36,10 @@ pub use self::state_applier::ShardStateApplier; pub use self::subscriber::test::PrintSubscriber; pub use self::subscriber::{ ArchiveSubscriber, ArchiveSubscriberContext, ArchiveSubscriberExt, BlockSubscriber, - BlockSubscriberContext, BlockSubscriberExt, ChainSubscriber, DelayedTasks, - DelayedTasksJoinHandle, DelayedTasksSpawner, MetricsSubscriber, NoopSubscriber, - StateSubscriber, StateSubscriberContext, StateSubscriberExt, + BlockSubscriberContext, BlockSubscriberExt, BoxBlockSubscriber, BoxStateSubscriber, + ChainSubscriber, DelayedTasks, DelayedTasksJoinHandle, DelayedTasksSpawner, MetricsSubscriber, + NoopSubscriber, OptionHandleFut, OptionPrepareFut, StateSubscriber, StateSubscriberContext, + StateSubscriberExt, }; use crate::storage::CoreStorage; diff --git a/core/src/block_strider/subscriber/box_subscriber.rs b/core/src/block_strider/subscriber/box_subscriber.rs new file mode 100644 index 0000000000..25f5b92e48 --- /dev/null +++ b/core/src/block_strider/subscriber/box_subscriber.rs @@ -0,0 +1,375 @@ +use std::any::Any; +use std::sync::atomic::{AtomicPtr, Ordering}; + +use anyhow::Result; +use futures_util::FutureExt; +use futures_util::future::BoxFuture; + +use super::{StateSubscriber, StateSubscriberContext}; +use crate::block_strider::subscriber::{BlockSubscriber, BlockSubscriberContext}; + +// === Boxed BlockSubscriber === + +pub struct BoxBlockSubscriber { + data: AtomicPtr<()>, + vtable: &'static BlockVtable, +} + +impl BoxBlockSubscriber { + pub fn new(subscriber: S) -> Self { + let ptr = Box::into_raw(Box::new(subscriber)); + + Self { + data: AtomicPtr::new(ptr.cast()), + vtable: const { BlockVtable::new::() }, + } + } +} + +impl BlockSubscriber for BoxBlockSubscriber { + type Prepared = BoxPrepared; + + type PrepareBlockFut<'a> = PrepareBlockFut<'a>; + type HandleBlockFut<'a> = HandleBlockFut<'a>; + + #[inline] + fn prepare_block<'a>(&'a self, cx: &'a BlockSubscriberContext) -> Self::PrepareBlockFut<'a> { + unsafe { (self.vtable.prepare_block)(&self.data, cx) } + } + + #[inline] + fn handle_block<'a>( + &'a self, + cx: &'a BlockSubscriberContext, + prepared: Self::Prepared, + ) -> Self::HandleBlockFut<'a> { + unsafe { (self.vtable.handle_block)(&self.data, cx, prepared) } + } +} + +impl Drop for BoxBlockSubscriber { + fn drop(&mut self) { + unsafe { (self.vtable.drop)(&mut self.data) } + } +} + +// Vtable must enforce this behavior +unsafe impl Send for BoxBlockSubscriber {} +unsafe impl Sync for BoxBlockSubscriber {} + +struct BlockVtable { + prepare_block: PrepareBlockFn, + handle_block: HandleBlockFn, + drop: DropFn, +} + +impl BlockVtable { + const fn new() -> &'static Self { + &Self { + prepare_block: |ptr, cx| { + let subscriber = unsafe { &*ptr.load(Ordering::Relaxed).cast::() }; + subscriber + .prepare_block(cx) + .map(|result| result.map(|data| Box::new(data) as BoxPrepared)) + .boxed() + }, + handle_block: |ptr, cx, prepared| { + let subscriber = unsafe { &*ptr.load(Ordering::Relaxed).cast::() }; + match prepared.downcast::() { + Ok(prepared) => subscriber.handle_block(cx, *prepared).boxed(), + Err(_) => Box::pin(futures_util::future::ready(Err(anyhow::Error::from( + PreparedTypeMismatch, + )))), + } + }, + drop: |ptr| { + drop(unsafe { Box::::from_raw(ptr.get_mut().cast::()) }); + }, + } + } +} + +type BoxPrepared = Box; + +type PrepareBlockFn = + for<'a> unsafe fn(&AtomicPtr<()>, &'a BlockSubscriberContext) -> PrepareBlockFut<'a>; +type HandleBlockFn = for<'a> unsafe fn( + &AtomicPtr<()>, + &'a BlockSubscriberContext, + BoxPrepared, +) -> HandleBlockFut<'a>; + +type PrepareBlockFut<'a> = BoxFuture<'a, Result>; +type HandleBlockFut<'a> = BoxFuture<'a, Result<()>>; + +#[derive(thiserror::Error, Debug)] +#[error("prepared type mismatch")] +struct PreparedTypeMismatch; + +// === Boxed StateSubscriber === + +pub struct BoxStateSubscriber { + data: AtomicPtr<()>, + vtable: &'static StateVtable, +} + +impl BoxStateSubscriber { + pub fn new(subscriber: S) -> Self { + let ptr = Box::into_raw(Box::new(subscriber)); + + Self { + data: AtomicPtr::new(ptr.cast()), + vtable: const { StateVtable::new::() }, + } + } +} + +impl StateSubscriber for BoxStateSubscriber { + type HandleStateFut<'a> = HandleStateFut<'a>; + + #[inline] + fn handle_state<'a>(&'a self, cx: &'a StateSubscriberContext) -> Self::HandleStateFut<'a> { + unsafe { (self.vtable.handle_state)(&self.data, cx) } + } +} + +impl Drop for BoxStateSubscriber { + fn drop(&mut self) { + unsafe { (self.vtable.drop)(&mut self.data) } + } +} + +// Vtable must enforce this behavior +unsafe impl Send for BoxStateSubscriber {} +unsafe impl Sync for BoxStateSubscriber {} + +struct StateVtable { + handle_state: HandleStateFn, + drop: DropFn, +} + +impl StateVtable { + const fn new() -> &'static Self { + &Self { + handle_state: |ptr, cx| { + let provider = unsafe { &*ptr.load(Ordering::Relaxed).cast::() }; + provider.handle_state(cx).boxed() + }, + drop: |ptr| { + drop(unsafe { Box::::from_raw(ptr.get_mut().cast::()) }); + }, + } + } +} + +type HandleStateFn = + for<'a> unsafe fn(&AtomicPtr<()>, &'a StateSubscriberContext) -> HandleStateFut<'a>; + +type HandleStateFut<'a> = BoxFuture<'a, Result<()>>; + +// === Common Stuff === + +type DropFn = unsafe fn(&mut AtomicPtr<()>); + +#[cfg(test)] +mod tests { + use std::sync::Arc; + use std::sync::atomic::AtomicUsize; + + use tycho_block_util::archive::ArchiveData; + use tycho_block_util::block::BlockStuff; + use tycho_block_util::state::{MinRefMcStateTracker, ShardStateStuff}; + use tycho_types::cell::{Cell, CellBuilder, CellFamily, Lazy}; + use tycho_types::models::{ShardIdent, ShardStateUnsplit}; + + use super::*; + use crate::block_strider::DelayedTasks; + + #[tokio::test] + async fn boxed_block_subscriber_works() -> Result<()> { + struct SubscriberState { + prepare_block_called: AtomicUsize, + handle_block_called: AtomicUsize, + dropped: AtomicUsize, + } + + #[derive(Debug, PartialEq, Eq)] + struct Prepared(u32); + + struct Subscriber { + state: Arc, + } + + impl Drop for Subscriber { + fn drop(&mut self) { + self.state.dropped.fetch_add(1, Ordering::Relaxed); + } + } + + impl BlockSubscriber for Subscriber { + type Prepared = Prepared; + type PrepareBlockFut<'a> = futures_util::future::Ready>; + type HandleBlockFut<'a> = futures_util::future::Ready>; + + fn prepare_block<'a>( + &'a self, + _cx: &'a BlockSubscriberContext, + ) -> Self::PrepareBlockFut<'a> { + self.state + .prepare_block_called + .fetch_add(1, Ordering::Relaxed); + futures_util::future::ready(Ok(Prepared(123))) + } + + fn handle_block<'a>( + &'a self, + _cx: &'a BlockSubscriberContext, + _prepared: Self::Prepared, + ) -> Self::HandleBlockFut<'a> { + self.state + .handle_block_called + .fetch_add(1, Ordering::Relaxed); + futures_util::future::ready(Ok(())) + } + } + + let state = Arc::new(SubscriberState { + prepare_block_called: AtomicUsize::new(0), + handle_block_called: AtomicUsize::new(0), + dropped: AtomicUsize::new(0), + }); + let boxed = BoxBlockSubscriber::new(Subscriber { + state: state.clone(), + }); + + let cx = BlockSubscriberContext { + mc_block_id: Default::default(), + mc_is_key_block: false, + is_key_block: false, + is_top_block: true, + block: BlockStuff::new_empty(ShardIdent::MASTERCHAIN, 0), + archive_data: ArchiveData::Existing, + delayed: DelayedTasks::new().1, + }; + + assert_eq!(state.prepare_block_called.load(Ordering::Relaxed), 0); + assert_eq!(state.handle_block_called.load(Ordering::Relaxed), 0); + assert_eq!(state.dropped.load(Ordering::Relaxed), 0); + + for i in 0..2 { + let res = boxed.prepare_block(&cx).await.unwrap(); + assert_eq!(res.downcast_ref::(), Some(&Prepared(123))); + assert_eq!(state.prepare_block_called.load(Ordering::Relaxed), i + 1); + assert_eq!(state.handle_block_called.load(Ordering::Relaxed), i); + assert_eq!(state.dropped.load(Ordering::Relaxed), 0); + + boxed.handle_block(&cx, res).await.unwrap(); + assert_eq!(state.prepare_block_called.load(Ordering::Relaxed), i + 1); + assert_eq!(state.handle_block_called.load(Ordering::Relaxed), i + 1); + assert_eq!(state.dropped.load(Ordering::Relaxed), 0); + } + + assert_eq!(Arc::strong_count(&state), 2); + drop(boxed); + + assert_eq!(state.prepare_block_called.load(Ordering::Acquire), 2); + assert_eq!(state.handle_block_called.load(Ordering::Acquire), 2); + assert_eq!(state.dropped.load(Ordering::Acquire), 1); + + assert_eq!(Arc::strong_count(&state), 1); + + Ok(()) + } + + #[tokio::test] + async fn boxed_state_subscriber_works() -> Result<()> { + struct SubscriberState { + handle_state_called: AtomicUsize, + dropped: AtomicUsize, + } + + struct Subscriber { + state: Arc, + } + + impl Drop for Subscriber { + fn drop(&mut self) { + self.state.dropped.fetch_add(1, Ordering::Relaxed); + } + } + + impl StateSubscriber for Subscriber { + type HandleStateFut<'a> = futures_util::future::Ready>; + + fn handle_state<'a>( + &'a self, + _cx: &'a StateSubscriberContext, + ) -> Self::HandleStateFut<'a> { + self.state + .handle_state_called + .fetch_add(1, Ordering::Relaxed); + futures_util::future::ready(Ok(())) + } + } + + let state = Arc::new(SubscriberState { + handle_state_called: AtomicUsize::new(0), + dropped: AtomicUsize::new(0), + }); + let boxed = BoxStateSubscriber::new(Subscriber { + state: state.clone(), + }); + + let cx = StateSubscriberContext { + mc_block_id: Default::default(), + mc_is_key_block: false, + is_key_block: false, + block: BlockStuff::new_empty(ShardIdent::MASTERCHAIN, 0), + archive_data: ArchiveData::Existing, + state: ShardStateStuff::from_root( + &Default::default(), + CellBuilder::build_from(ShardStateUnsplit { + global_id: 0, + shard_ident: ShardIdent::MASTERCHAIN, + seqno: 0, + vert_seqno: 0, + gen_utime: 0, + gen_utime_ms: 0, + gen_lt: 0, + min_ref_mc_seqno: 0, + processed_upto: Lazy::from_raw(Cell::empty_cell())?, + before_split: false, + accounts: Lazy::from_raw(Cell::empty_cell())?, + overload_history: 0, + underload_history: 0, + total_balance: Default::default(), + total_validator_fees: Default::default(), + libraries: Default::default(), + master_ref: None, + custom: None, + })?, + MinRefMcStateTracker::new().insert_untracked(), + )?, + delayed: DelayedTasks::new().1, + }; + + assert_eq!(state.handle_state_called.load(Ordering::Relaxed), 0); + assert_eq!(state.dropped.load(Ordering::Relaxed), 0); + + for i in 0..2 { + boxed.handle_state(&cx).await.unwrap(); + assert_eq!(state.handle_state_called.load(Ordering::Relaxed), i + 1); + assert_eq!(state.dropped.load(Ordering::Relaxed), 0); + } + + assert_eq!(Arc::strong_count(&state), 2); + drop(boxed); + + assert_eq!(state.handle_state_called.load(Ordering::Acquire), 2); + assert_eq!(state.dropped.load(Ordering::Acquire), 1); + + assert_eq!(Arc::strong_count(&state), 1); + + Ok(()) + } +} diff --git a/core/src/block_strider/subscriber/mod.rs b/core/src/block_strider/subscriber/mod.rs index 9c94d0afbd..df5330976c 100644 --- a/core/src/block_strider/subscriber/mod.rs +++ b/core/src/block_strider/subscriber/mod.rs @@ -8,12 +8,14 @@ use tycho_block_util::block::BlockStuff; use tycho_block_util::state::ShardStateStuff; use tycho_types::models::*; +pub use self::box_subscriber::{BoxBlockSubscriber, BoxStateSubscriber}; pub use self::futures::{ DelayedTasks, DelayedTasksJoinHandle, DelayedTasksSpawner, OptionHandleFut, OptionPrepareFut, }; pub use self::metrics_subscriber::MetricsSubscriber; use crate::storage::CoreStorage; +mod box_subscriber; mod futures; mod metrics_subscriber; @@ -120,10 +122,19 @@ impl BlockSubscriber for Arc { } pub trait BlockSubscriberExt: Sized { + fn boxed(self) -> BoxBlockSubscriber; + fn chain(self, other: T) -> ChainSubscriber; } impl BlockSubscriberExt for B { + fn boxed(self) -> BoxBlockSubscriber { + castaway::match_type!(self, { + BoxBlockSubscriber as subscriber => subscriber, + subscriber => BoxBlockSubscriber::new(subscriber), + }) + } + fn chain(self, other: T) -> ChainSubscriber { ChainSubscriber { left: self, @@ -184,10 +195,19 @@ impl StateSubscriber for Arc { } pub trait StateSubscriberExt: Sized { + fn boxed(self) -> BoxStateSubscriber; + fn chain(self, other: T) -> ChainSubscriber; } impl StateSubscriberExt for B { + fn boxed(self) -> BoxStateSubscriber { + castaway::match_type!(self, { + BoxStateSubscriber as subscriber => subscriber, + subscriber => BoxStateSubscriber::new(subscriber), + }) + } + fn chain(self, other: T) -> ChainSubscriber { ChainSubscriber { left: self, diff --git a/scripts/build-contracts.sh b/scripts/build-contracts.sh index 05ecc4e58f..91d3ad33eb 100755 --- a/scripts/build-contracts.sh +++ b/scripts/build-contracts.sh @@ -19,3 +19,4 @@ yarn build --all copy_code Elector copy_code ElectorPoA copy_code Config +copy_code SlasherStub diff --git a/scripts/gen-dashboard.py b/scripts/gen-dashboard.py index 4a0c8415fe..655590ed62 100755 --- a/scripts/gen-dashboard.py +++ b/scripts/gen-dashboard.py @@ -2744,6 +2744,21 @@ def validator() -> RowPanel: "tycho_validator_invalid_signatures_cached_total", "Number of cached invalid signatures", ), + create_heatmap_panel( + "tycho_validator_collector_get_stats_for_blocks_time", + "Collector: get stats for blocks", + ), + create_heatmap_panel( + "tycho_validator_collector_truncate_range_time", "Collector: truncate range" + ), + create_gauge_panel( + "tycho_validator_collector_valid_sigs_total_count", + "Collector: total valid signatures in stats", + ), + create_gauge_panel( + "tycho_validator_collector_invalid_sigs_total_count", + "Collector: total invalid signatures in stats", + ), ] return create_row("Validator", metrics) diff --git a/slasher-traits/Cargo.toml b/slasher-traits/Cargo.toml new file mode 100644 index 0000000000..3d765d57ee --- /dev/null +++ b/slasher-traits/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "tycho-slasher-traits" +description = "Tycho slasher traits." +include = ["src/**/*.rs", "src/**/*.tl", "./LICENSE-*", "./README.md"] +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +repository.workspace = true +license.workspace = true + +[dependencies] +# crates.io deps +indexmap = { workspace = true } + +# local deps +tycho-types = { workspace = true } +tycho-util = { workspace = true } + +[lints] +workspace = true diff --git a/slasher-traits/LICENSE-APACHE b/slasher-traits/LICENSE-APACHE new file mode 120000 index 0000000000..965b606f33 --- /dev/null +++ b/slasher-traits/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/slasher-traits/LICENSE-MIT b/slasher-traits/LICENSE-MIT new file mode 120000 index 0000000000..76219eb72e --- /dev/null +++ b/slasher-traits/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/slasher-traits/src/lib.rs b/slasher-traits/src/lib.rs new file mode 100644 index 0000000000..02018e8c63 --- /dev/null +++ b/slasher-traits/src/lib.rs @@ -0,0 +1,8 @@ +pub use self::mempool::*; +pub use self::validator::{ + BlockValidationScope, NoopValidatorEventsRecorder, ReceivedSignature, ValidationSessionId, + ValidatorEvents, ValidatorEventsListener, ValidatorSessionScope, +}; + +mod mempool; +mod validator; diff --git a/slasher-traits/src/mempool.rs b/slasher-traits/src/mempool.rs new file mode 100644 index 0000000000..c7a3521ea6 --- /dev/null +++ b/slasher-traits/src/mempool.rs @@ -0,0 +1,9 @@ +use std::sync::Arc; + +#[derive(Debug, Clone)] +pub struct AnchorStats(pub Arc<[AnchorPeerStats]>); + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AnchorPeerStats { + pub points_proven: u16, +} diff --git a/slasher-traits/src/validator.rs b/slasher-traits/src/validator.rs new file mode 100644 index 0000000000..b165f1c4aa --- /dev/null +++ b/slasher-traits/src/validator.rs @@ -0,0 +1,370 @@ +use std::mem::MaybeUninit; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; + +use indexmap::IndexMap; +use tycho_types::models::{BlockId, BlockIdShort, IndexedValidatorDescription}; +use tycho_util::FastHasherState; + +use crate::AnchorStats; + +// TODO: Decide how to be with this collator-defined type +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ValidationSessionId { + pub catchain_seqno: u32, + pub vset_switch_round: u32, +} + +// TEMP +impl From<(u32, u32)> for ValidationSessionId { + #[inline] + fn from(value: (u32, u32)) -> Self { + Self { + // seqno: value.0, + catchain_seqno: value.0, + vset_switch_round: value.1, + } + } +} + +// TEMP +impl Ord for ValidationSessionId { + #[inline] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + (self.catchain_seqno, self.vset_switch_round) + .cmp(&(other.catchain_seqno, other.vset_switch_round)) + } +} + +// TEMP +impl PartialOrd for ValidationSessionId { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +pub struct ValidatorEvents { + listener: Arc, +} + +impl ValidatorEvents { + pub fn new(recorder: Arc) -> Self { + Self { listener: recorder } + } + + pub fn begin_session( + &self, + session_id: ValidationSessionId, + first_mc_seqno: u32, + own_validator_idx: u16, + validators: &[IndexedValidatorDescription], + ) -> ValidatorSessionScope { + self.listener + .on_session_started(session_id, first_mc_seqno, own_validator_idx, validators); + + let mut remap = IndexMap::::with_capacity_and_hasher( + validators.len(), + Default::default(), + ); + for (i, validator) in validators.iter().enumerate() { + remap.insert(validator.validator_idx, i as u16); + } + + ValidatorSessionScope { + recorder: self.listener.clone(), + session_id, + remap_ids: Arc::new(remap), + is_sealed: AtomicBool::new(false), + } + } +} + +pub struct ValidatorSessionScope { + recorder: Arc, + session_id: ValidationSessionId, + remap_ids: Arc>, + is_sealed: AtomicBool, +} + +impl ValidatorSessionScope { + pub fn begin_block(&self, block_id: &BlockId) -> BlockValidationScope { + BlockValidationScope { + recorder: self.recorder.clone(), + session_id: self.session_id, + remap_ids: self.remap_ids.clone(), + block_id: *block_id, + signature_slots: vec![0; self.remap_ids.len()] + .into_iter() + .map(AtomicU8::new) + .collect::>(), + is_sealed: AtomicBool::new(false), + } + } + + pub fn finish(&self) { + if self.seal() { + self.recorder.on_session_finished(self.session_id); + } + } + + fn seal(&self) -> bool { + !self.is_sealed.swap(true, Ordering::Release) + } +} + +impl Drop for ValidatorSessionScope { + fn drop(&mut self) { + self.finish(); + } +} + +pub struct BlockValidationScope { + recorder: Arc, + session_id: ValidationSessionId, + remap_ids: Arc>, + block_id: BlockId, + signature_slots: Box<[AtomicU8]>, + is_sealed: AtomicBool, +} + +impl BlockValidationScope { + pub fn session_id(&self) -> ValidationSessionId { + self.session_id + } + + pub fn block_id(&self) -> &BlockId { + &self.block_id + } + + pub fn receive_signature(&self, validator_idx: u16, is_valid: bool) -> bool { + let mask = if is_valid { + ReceivedSignature::VALID_SIGNATURE_BIT + } else { + ReceivedSignature::INVALID_SIGNATURE_BIT + }; + + let Some(slot_id) = self.remap_ids.get(&validator_idx) else { + return false; + }; + + if let Some(status) = self.signature_slots.get(*slot_id as usize) { + status.fetch_or(mask, Ordering::Release) & mask == 0 + } else { + false + } + } + + pub fn commit(&self) -> bool { + if self.seal() { + // TODO: Use some unsafe magic to make this closer to a NOOP. + let mut signatures = Arc::new_uninit_slice(self.signature_slots.len()); + for (res, slot) in std::iter::zip( + Arc::get_mut(&mut signatures).unwrap(), + &self.signature_slots, + ) { + *res = MaybeUninit::new(ReceivedSignature(slot.load(Ordering::Acquire))); + } + // SAFETY: All items were initialized. + let signatures = unsafe { signatures.assume_init() }; + + self.recorder + .on_block_validated(self.session_id, &self.block_id, signatures); + true + } else { + false + } + } + + pub fn discard(&self) -> bool { + if self.seal() { + self.recorder + .on_block_skipped(self.session_id, &self.block_id); + true + } else { + false + } + } + + fn seal(&self) -> bool { + !self.is_sealed.swap(true, Ordering::Release) + } +} + +impl Drop for BlockValidationScope { + fn drop(&mut self) { + self.discard(); + } +} + +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct ReceivedSignature(pub u8); + +impl ReceivedSignature { + pub const VALID_SIGNATURE_BIT: u8 = 0b01; + pub const INVALID_SIGNATURE_BIT: u8 = 0b10; + + pub fn has_valid_signature(&self) -> bool { + self.0 & Self::VALID_SIGNATURE_BIT != 0 + } + + pub fn has_invalid_signature(&self) -> bool { + self.0 & Self::INVALID_SIGNATURE_BIT != 0 + } +} + +/// Unified event-sink interface for the validator. +/// +/// Implementations can decide whether to perform work inline or forward the +/// event into an async task / channel. No async methods are used here to keep +/// the trait usable in both sync and async contexts. +pub trait ValidatorEventsListener: Send + Sync + 'static { + /// Called exactly once when a new validation session is created. + fn on_session_started( + &self, + session_id: ValidationSessionId, + first_mc_seqno: u32, + own_validator_idx: u16, + validators: &[IndexedValidatorDescription], + ); + + /// Called when the session is complete. + fn on_session_finished(&self, session_id: ValidationSessionId); + + /// Called when anchor is imported + fn on_anchor_import( + &self, + session_id: ValidationSessionId, + block_id: &BlockIdShort, + anchor_id: u32, + anchor_stats: AnchorStats, + ); + + /// Called when validation is complete for a block. + fn on_block_validated( + &self, + session_id: ValidationSessionId, + block_id: &BlockId, + signatures: Arc<[ReceivedSignature]>, + ); + + /// Called when validation is skipped for a block. + fn on_block_skipped(&self, session_id: ValidationSessionId, block_id: &BlockId); +} + +#[derive(Debug, Clone, Copy)] +pub struct NoopValidatorEventsRecorder; + +impl ValidatorEventsListener for NoopValidatorEventsRecorder { + fn on_session_started( + &self, + _session_id: ValidationSessionId, + _first_mc_seqno: u32, + _own_validator_idx: u16, + _validators: &[IndexedValidatorDescription], + ) { + } + + fn on_session_finished(&self, _session_id: ValidationSessionId) {} + + fn on_anchor_import( + &self, + _session_id: ValidationSessionId, + _block_id: &BlockIdShort, + _anchor_id: u32, + _anchor_stats: AnchorStats, + ) { + } + + fn on_block_validated( + &self, + _session_id: ValidationSessionId, + _block_id: &BlockId, + _signatures: Arc<[ReceivedSignature]>, + ) { + } + + fn on_block_skipped(&self, _session_id: ValidationSessionId, _block_id: &BlockId) {} +} + +macro_rules! impl_recorder_for_tuples { + ($(($($ty:ident: $n:tt),+)),*$(,)?) => { + $(impl<$($ty),+> ValidatorEventsListener for ($($ty,)+) + where + $($ty: ValidatorEventsListener,)+ + { + fn on_session_started( + &self, + session_id: ValidationSessionId, + first_mc_seqno: u32, + own_validator_idx: u16, + validators: &[IndexedValidatorDescription], + ) { + $(self.$n.on_session_started(session_id, first_mc_seqno, own_validator_idx, validators);)+ + } + + fn on_session_finished(&self, session_id: ValidationSessionId) { + $(self.$n.on_session_finished(session_id);)+ + } + + fn on_anchor_import(&self, + session_id: ValidationSessionId, + block_id: &BlockIdShort, + anchor_id: u32, + anchor_stats: AnchorStats, + ) { + impl_recorder_for_tuples!(@call_on_anchor_import self, + session_id, + block_id, + anchor_id, + anchor_stats, + $($n)+ + ); + } + + fn on_block_validated( + &self, + session_id: ValidationSessionId, + block_id: &BlockId, + signatures: Arc<[ReceivedSignature]>, + ) { + impl_recorder_for_tuples!(@call_on_validated self, + session_id, + block_id, + signatures, + $($n)+ + ); + } + + fn on_block_skipped(&self, session_id: ValidationSessionId, block_id: &BlockId) { + $(self.$n.on_block_skipped(session_id, block_id);)+ + } + })* + }; + + (@call_on_anchor_import $self:ident, $sid:ident, $block_id:ident, $anchor_id:ident, $anchor_stats:ident, $n:tt $($rest:tt)+) => { + $self.$n.on_anchor_import($sid, $block_id, $anchor_id, $anchor_stats.clone()); + impl_recorder_for_tuples!(@call_on_anchor_import $self, $sid, $block_id, $anchor_id, $anchor_stats, $($rest)+) + }; + (@call_on_validated $self:ident, $sid:ident, $block_id:ident, $signatures:ident, $n:tt $($rest:tt)+) => { + $self.$n.on_block_validated($sid, $block_id, $signatures.clone()); + impl_recorder_for_tuples!(@call_on_validated $self, $sid, $block_id, $signatures, $($rest)+) + }; + (@call_on_anchor_import $self:ident, $sid:ident, $block_id:ident, $anchor_id:ident, $anchor_stats:ident, $n:tt) => { + $self.$n.on_anchor_import($sid, $block_id, $anchor_id, $anchor_stats); + }; + (@call_on_validated $self:ident, $sid:ident, $block_id:ident, $signatures:ident, $n:tt) => { + $self.$n.on_block_validated($sid, $block_id, $signatures); + }; +} + +impl_recorder_for_tuples! { + (T0: 0), + (T0: 0, T1: 1), + (T0: 0, T1: 1, T2: 2), + (T0: 0, T1: 1, T2: 2, T3: 3), + (T0: 0, T1: 1, T2: 2, T3: 3, T4: 4), + (T0: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5), + (T0: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6), +} diff --git a/slasher/Cargo.toml b/slasher/Cargo.toml new file mode 100644 index 0000000000..b6bb4a546f --- /dev/null +++ b/slasher/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "tycho-slasher" +description = "Tycho slasher implementation." +include = ["src/**/*.rs", "src/**/*.tl", "./LICENSE-*", "./README.md"] +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +repository.workspace = true +license.workspace = true + +[dependencies] +# crates.io deps +anyhow = { workspace = true } +arc-swap = { workspace = true } +dashmap = { workspace = true } +futures-util = { workspace = true } +metrics = { workspace = true } +parking_lot = { workspace = true } +scopeguard = { workspace = true } +serde = { workspace = true } +tl-proto = { workspace = true } +tokio = { workspace = true, features = ["sync"] } +tokio-util = { workspace = true } +tracing = { workspace = true } +tycho-crypto = { workspace = true } +tycho-types = { workspace = true, features = ["abi", "models"] } +weedb = { workspace = true } + +# local deps +tycho-block-util = { workspace = true } +tycho-core = { workspace = true } +tycho-slasher-traits = { workspace = true } +tycho-storage = { workspace = true } +tycho-util = { workspace = true } + +[lints] +workspace = true + +[dev-dependencies] +tokio = { workspace = true, features = ["rt", "macros", "sync"] } +tycho-storage = { workspace = true, features = ["test"] } diff --git a/slasher/LICENSE-APACHE b/slasher/LICENSE-APACHE new file mode 120000 index 0000000000..965b606f33 --- /dev/null +++ b/slasher/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/slasher/LICENSE-MIT b/slasher/LICENSE-MIT new file mode 120000 index 0000000000..76219eb72e --- /dev/null +++ b/slasher/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/slasher/src/analyzer.rs b/slasher/src/analyzer.rs new file mode 100644 index 0000000000..22ec105d2f --- /dev/null +++ b/slasher/src/analyzer.rs @@ -0,0 +1,188 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use tycho_slasher_traits::ValidationSessionId; +use tycho_types::cell::HashBytes; + +use crate::BlocksBatch; + +#[derive(Debug, PartialEq, Eq)] +pub struct ObservedBlocksBatch { + pub observer_validator_idx: u16, + pub batch: BlocksBatch, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SessionMeta { + pub session_id: ValidationSessionId, + pub epoch_start_session_id: ValidationSessionId, + pub validator_indices: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VsetEpoch { + pub start_session_id: ValidationSessionId, + pub vset_hash: HashBytes, + pub next_epoch_start_session_id: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SessionPenaltyReport { + pub session_id: ValidationSessionId, + pub epoch_start_session_id: ValidationSessionId, + pub session_weight: u32, + pub validators: Box<[SessionValidatorScore]>, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SessionValidatorScore { + pub validator_idx: u16, + pub earned_points: u64, + pub max_points: u64, + pub is_bad: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VsetPenaltyReport { + pub epoch_start_session_id: ValidationSessionId, + pub vset_hash: HashBytes, + pub validators: Box<[VsetValidatorPenalty]>, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VsetValidatorPenalty { + pub validator_idx: u16, + pub bad_sessions_weight: u64, + pub total_sessions_weight: u64, + pub is_bad: bool, +} + +pub fn analyze_session( + meta: &SessionMeta, + batches: &[ObservedBlocksBatch], +) -> SessionPenaltyReport { + let mut committed_blocks = BTreeSet::new(); + let mut observed_validators = BTreeSet::new(); + // observer -> observed : points * session weight + let mut validator_points = BTreeMap::<(u16, u16), u64>::new(); + + for item in batches { + observed_validators.insert(item.observer_validator_idx); + + for offset in 0..item.batch.committed_blocks.len() { + if !item.batch.committed_blocks.get(offset) { + continue; + } + + committed_blocks.insert(item.batch.start_seqno + offset as u32); + + for history in &item.batch.signatures_history { + let bit_offset = offset * 2; + let has_invalid_signature = history.bits.get(bit_offset); + let has_valid_signature = history.bits.get(bit_offset + 1); + + if has_invalid_signature && has_valid_signature { + tracing::warn!( + "slasher analyzer invariant violated: observer {} saw validator {} as both valid and invalid in session {:?}", + item.observer_validator_idx, + history.validator_idx, + meta.session_id, + ); + continue; + } + + if has_valid_signature { + *validator_points + .entry((item.observer_validator_idx, history.validator_idx)) + .or_default() += 1; + } + } + } + } + + let session_weight = committed_blocks.len() as u64; + + let mut validator_indices = meta.validator_indices.clone(); + validator_indices.sort(); + validator_indices.dedup(); + + let validators = validator_indices + .into_iter() + .map(|validator_idx| { + let max_rows = observed_validators.len() as u64 + - u64::from(observed_validators.contains(&validator_idx)); + let max_session_points = max_rows.saturating_mul(session_weight); + //.saturating_mul(session_weight); + + let earned_points = observed_validators + .iter() + .copied() + .filter(|observer| *observer != validator_idx) + .map(|observer| { + validator_points + .get(&(observer, validator_idx)) + .copied() + .unwrap_or_default() + }) + .sum::() + .saturating_mul(session_weight); + + SessionValidatorScore { + validator_idx, + earned_points, + max_points: max_session_points, + is_bad: max_session_points > 0 + && earned_points.saturating_mul(2) < max_session_points, + } + }) + .collect::>() + .into_boxed_slice(); + + SessionPenaltyReport { + session_id: meta.session_id, + epoch_start_session_id: meta.epoch_start_session_id, + session_weight: session_weight as u32, + validators, + } +} + +pub fn analyze_vset_epoch( + epoch: &VsetEpoch, + session_reports: &[SessionPenaltyReport], + bad_sessions_weight_threshold: u64, +) -> VsetPenaltyReport { + let mut validators = BTreeMap::::new(); + + for report in session_reports { + let session_weight = u64::from(report.session_weight); + + for item in &report.validators { + let penalty = validators + .entry(item.validator_idx) + .or_insert(VsetValidatorPenalty { + validator_idx: item.validator_idx, + bad_sessions_weight: 0, + total_sessions_weight: 0, + is_bad: false, + }); + penalty.total_sessions_weight = + penalty.total_sessions_weight.saturating_add(session_weight); + if item.is_bad { + penalty.bad_sessions_weight = + penalty.bad_sessions_weight.saturating_add(session_weight); + } + } + } + + for item in validators.values_mut() { + item.is_bad = item.bad_sessions_weight > bad_sessions_weight_threshold; + } + + VsetPenaltyReport { + epoch_start_session_id: epoch.start_session_id, + vset_hash: epoch.vset_hash, + validators: validators + .into_values() + .collect::>() + .into_boxed_slice(), + } +} diff --git a/slasher/src/bc/mod.rs b/slasher/src/bc/mod.rs new file mode 100644 index 0000000000..f617f8f469 --- /dev/null +++ b/slasher/src/bc/mod.rs @@ -0,0 +1,280 @@ +use std::num::NonZeroU32; +use std::ops::{Range, RangeInclusive}; +use std::time::Duration; + +use anyhow::Result; +use tokio::sync::oneshot; +use tycho_crypto::ed25519; +use tycho_slasher_traits::{AnchorPeerStats, AnchorStats, ReceivedSignature, ValidationSessionId}; +use tycho_types::cell::{HashBytes, Lazy}; +use tycho_types::models::{ + BlockchainConfigParams, OwnedMessage, SignatureContext, StdAddr, Transaction, +}; +use tycho_util::FastDashMap; + +pub use self::stub_contract::StubSlasherContract; +use crate::tracing_targets; +use crate::util::BitSet; + +mod stub_contract; + +#[derive(Clone, Copy)] +pub struct EncodeBlocksBatchMessage<'a> { + pub address: &'a StdAddr, + pub session_id: ValidationSessionId, + pub batch: &'a BlocksBatch, + pub validator_idx: u16, + pub signature_context: SignatureContext, + pub keypair: &'a ed25519::KeyPair, + pub ttl: Duration, +} + +pub trait SlasherContract: Send + Sync + 'static { + fn default_batch_size(&self) -> NonZeroU32; + + fn find_params(&self, config: &BlockchainConfigParams) -> Result>; + + fn encode_blocks_batch_message( + &self, + params: &EncodeBlocksBatchMessage<'_>, + ) -> Result; + + fn decode_event(&self, tx: &Transaction) -> Result>; +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SlasherParams { + /// Address in masterchain. + pub address: HashBytes, + /// Blocks batch size. + pub blocks_batch_size: NonZeroU32, +} + +pub struct SignedMessage { + pub message: Lazy, + pub expire_at: u32, +} + +pub struct ContractSubscription { + address: StdAddr, + pending_messages: FastDashMap, +} + +impl ContractSubscription { + pub fn new(address: &StdAddr) -> Self { + Self { + address: address.clone(), + pending_messages: Default::default(), + } + } + + pub fn address(&self) -> &StdAddr { + &self.address + } + + pub fn track_message( + &self, + msg_hash: &HashBytes, + expire_at: u32, + ) -> Result> { + use dashmap::mapref::entry::Entry; + + let (tx, rx) = oneshot::channel(); + match self.pending_messages.entry(*msg_hash) { + Entry::Vacant(entry) => { + entry.insert(PendingMessage { expire_at, tx }); + Ok(rx) + } + Entry::Occupied(_) => anyhow::bail!("duplicate external message: {msg_hash}"), + } + } + + pub fn handle_account_transaction( + &self, + tx_hash: &HashBytes, + tx: &Transaction, + ) -> Result { + let Some(in_msg) = &tx.in_msg else { + return Ok(false); + }; + let msg_hash = in_msg.repr_hash(); + + if let Some((_, pending)) = self.pending_messages.remove(msg_hash) { + pending + .tx + .send(MessageDeliveryStatus::Sent { tx_hash: *tx_hash }) + .ok(); + return Ok(true); + } + Ok(false) + } + + pub fn cleanup_expired_messages(&self, now_sec: u32) { + let expired = self + .pending_messages + .iter() + .filter_map(|entry| (entry.expire_at < now_sec).then_some(*entry.key())) + .collect::>(); + + let dropped = expired.len(); + for msg_hash in expired { + if let Some((_, pending)) = self.pending_messages.remove(&msg_hash) { + pending.tx.send(MessageDeliveryStatus::Expired).ok(); + } + } + + if dropped > 0 { + tracing::warn!( + target: tracing_targets::SLASHER, + dropped, + "dropped pending messages" + ); + } + } +} + +struct PendingMessage { + expire_at: u32, + tx: oneshot::Sender, +} + +#[derive(Debug, Clone, Copy)] +pub enum MessageDeliveryStatus { + Sent { tx_hash: HashBytes }, + Expired, +} + +// TODO: Add mempool batches or votes here +#[derive(Debug, PartialEq, Eq)] +pub enum SlasherContractEvent { + SubmitBlocksBatch(SubmitBlocksBatch), +} + +#[derive(Debug, PartialEq, Eq)] +pub struct SubmitBlocksBatch { + pub session_id: ValidationSessionId, + pub validator_idx: u16, + pub blocks_batch: BlocksBatch, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct BlocksBatch { + pub start_seqno: u32, + pub anchor_range: Option>, + pub committed_blocks: BitSet, + pub signatures_history: Box<[SignatureHistory]>, + /// sorted by `validator_idx` since mempool output + pub anchor_stats_history: Box<[AnchorStatsHistory]>, +} + +impl BlocksBatch { + pub fn new(start_seqno: u32, len: NonZeroU32, map_ids: &[u16]) -> Self { + let len = len.get() as usize; + + let mut anchor_stats_history = map_ids + .iter() + .map(|validator_idx| AnchorStatsHistory { + validator_idx: *validator_idx, + stats: AnchorPeerStats { points_proven: 0 }, + }) + .collect::>(); + anchor_stats_history.sort_unstable_by_key(|a| a.validator_idx); + + Self { + start_seqno, + anchor_range: None, + committed_blocks: BitSet::with_capacity(len), + signatures_history: map_ids + .iter() + .map(|validator_idx| SignatureHistory { + validator_idx: *validator_idx, + bits: BitSet::with_capacity(len * 2), + }) + .collect::>(), + anchor_stats_history: anchor_stats_history.into_boxed_slice(), + } + } + + pub fn is_empty(&self) -> bool { + self.committed_blocks.is_zero() && self.anchor_range.is_none() + } + + pub fn start_seqno(&self) -> u32 { + self.start_seqno + } + + pub fn seqno_after(&self) -> u32 { + self.start_seqno + .saturating_add(self.committed_blocks.len() as u32) + } + + pub fn seqno_range(&self) -> Range { + self.start_seqno..self.seqno_after() + } + + pub fn contains_seqno(&self, seqno: u32) -> bool { + self.seqno_range().contains(&seqno) + } + + pub fn committed_block_count(&self) -> usize { + (0..self.committed_blocks.len()) + .filter(|offset| self.committed_blocks.get(*offset)) + .count() + } + + pub fn validator_count(&self) -> usize { + self.signatures_history.len() + } + + pub fn push_anchor_stats(&mut self, anchor_id: u32, anchor_stats: &AnchorStats) -> bool { + if (self.anchor_range.as_ref()).is_some_and(|r| r.contains(&anchor_id)) + || anchor_stats.0.len() != self.anchor_stats_history.len() + { + return false; + } + + match &mut self.anchor_range { + None => self.anchor_range = Some(anchor_id..=anchor_id), + Some(exist) => { + *exist = *exist.start()..=*exist.end().max(&anchor_id); + } + }; + + for (history, received) in std::iter::zip(&mut self.anchor_stats_history, &*anchor_stats.0) + { + history.stats.points_proven = + (history.stats.points_proven).saturating_add(received.points_proven); + } + + true + } + + pub fn commit_signatures(&mut self, mut seqno: u32, signatures: &[ReceivedSignature]) -> bool { + if !self.contains_seqno(seqno) || signatures.len() != self.signatures_history.len() { + return false; + } + + seqno -= self.start_seqno; + + self.committed_blocks.set(seqno as usize, true); + for (history, received) in std::iter::zip(&mut self.signatures_history, signatures) { + let idx = (seqno as usize) * 2; + history.bits.set(idx, received.has_invalid_signature()); + history.bits.set(idx + 1, received.has_valid_signature()); + } + + true + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct SignatureHistory { + pub validator_idx: u16, + pub bits: BitSet, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct AnchorStatsHistory { + pub validator_idx: u16, + pub stats: AnchorPeerStats, +} diff --git a/slasher/src/bc/stub_contract.rs b/slasher/src/bc/stub_contract.rs new file mode 100644 index 0000000000..e8feea0255 --- /dev/null +++ b/slasher/src/bc/stub_contract.rs @@ -0,0 +1,313 @@ +use std::num::{NonZeroU8, NonZeroU32}; + +use anyhow::{Context, Result, anyhow}; +use tycho_slasher_traits::{AnchorPeerStats, ValidationSessionId}; +use tycho_types::cell::Lazy; +use tycho_types::dict; +use tycho_types::models::{ + BlockchainConfigParams, ComputePhase, ExtInMsgInfo, Message, MsgInfo, OwnedMessage, TxInfo, +}; +use tycho_types::prelude::*; + +use super::{ + AnchorStatsHistory, BlocksBatch, SignatureHistory, SignedMessage, SlasherContract, + SlasherContractEvent, SlasherParams, SubmitBlocksBatch, +}; +use crate::util::BitSet; + +/// ```tlb +/// slasher_params#01 +/// address:bits256 +/// blocks_batch_size:uint8 +/// { blocks_batch_size > 0 } +/// = ConfigParam 666; +/// ``` +#[derive(Debug, Store, Load)] +#[tlb(tag = "#01")] +pub struct StubSlasherParams { + pub address: HashBytes, + pub blocks_batch_size: NonZeroU8, +} + +impl StubSlasherParams { + pub const IDX: u32 = 666; +} + +pub struct StubSlasherContract; + +impl SlasherContract for StubSlasherContract { + fn default_batch_size(&self) -> NonZeroU32 { + NonZeroU32::new(10).unwrap() + } + + fn find_params(&self, config: &BlockchainConfigParams) -> Result> { + let Some(raw) = config.get_raw_cell_ref(StubSlasherParams::IDX)? else { + return Ok(None); + }; + let params = raw.parse::()?; + Ok(Some(SlasherParams { + address: params.address, + blocks_batch_size: params.blocks_batch_size.into(), + })) + } + + fn encode_blocks_batch_message( + &self, + params: &super::EncodeBlocksBatchMessage<'_>, + ) -> Result { + let cell = CellBuilder::build_from(BlocksBatchBc::wrap(params.batch)) + .context("failed to serialize blocks batch")?; + + let now = tycho_util::time::now_millis(); + let expire_at = (now / 1000).saturating_add(params.ttl.as_secs()) as u32; + let body_to_sign = { + let mut b = CellBuilder::new(); + b.store_u64(now)?; + b.store_u32(expire_at)?; + b.store_u32(params.session_id.catchain_seqno)?; + b.store_u32(params.session_id.vset_switch_round)?; + b.store_u16(params.validator_idx)?; + b.store_reference(cell)?; + b.build()? + }; + + let signature = params.keypair.sign_raw( + ¶ms + .signature_context + .apply(body_to_sign.repr_hash().as_array()), + ); + let body = { + let mut b = CellBuilder::new(); + b.store_raw(&signature, 512)?; + b.store_slice(body_to_sign.as_slice()?)?; + b.build()? + }; + + let message = Lazy::new(&OwnedMessage { + info: MsgInfo::ExtIn(ExtInMsgInfo { + dst: params.address.clone().into(), + ..Default::default() + }), + init: None, + body: body.into(), + layout: None, + })?; + + Ok(SignedMessage { message, expire_at }) + } + + fn decode_event( + &self, + tx: &tycho_types::models::Transaction, + ) -> Result> { + 'check: { + if let TxInfo::Ordinary(info) = tx.load_info()? + && let ComputePhase::Executed(ph) = info.compute_phase + && ph.exit_code == 0 + { + break 'check; + } + return Ok(None); + }; + + let Some(in_msg) = &tx.in_msg else { + return Ok(None); + }; + let msg = in_msg.parse::>()?; + if !msg.info.is_external_in() { + return Ok(None); + } + + // TODO: Add message op + let mut body = msg.body; + body.skip_first(512 + 64 + 32, 0)?; + let catchain_seqno = body.load_u32()?; + let vset_switch_round = body.load_u32()?; + let session_id = ValidationSessionId { + vset_switch_round, + catchain_seqno, + }; + let validator_idx = body.load_u16()?; + let mut batch_cs = body.load_reference_as_slice()?; + let BlocksBatchBc(blocks_batch) = <_>::load_from(&mut batch_cs)?; + if !body.is_empty() || !batch_cs.is_empty() { + return Err(tycho_types::error::Error::CellOverflow.into()); + } + + Ok(Some(SlasherContractEvent::SubmitBlocksBatch( + SubmitBlocksBatch { + session_id, + validator_idx, + blocks_batch, + }, + ))) + } +} + +#[repr(transparent)] +struct BlocksBatchBc(BlocksBatch); + +impl BlocksBatchBc { + fn wrap(inner: &BlocksBatch) -> &Self { + // SAFETY: `BlocksBatchBc` has the same layout as `BlocksBatch`. + unsafe { &*(inner as *const BlocksBatch).cast::() } + } +} + +impl<'a> Load<'a> for BlocksBatchBc { + fn load_from(slice: &mut CellSlice<'a>) -> Result { + let start_seqno = slice.load_u32()?; + let anchor_range = + Some(slice.load_u32()?..=slice.load_u32()?).filter(|range| *range.end() > 0); + + let block_count = slice.size_bits() as usize; + let committed_blocks = BitSet::load_from_cs(block_count, slice)?; + + let mut zipped = Vec::new(); + + let dict = Dict::>::from_raw(Some(slice.load_reference_cloned()?)); + for entry in dict.iter() { + let (validator_idx, mut cs) = entry?; + let bits = BitSet::load_from_cs(block_count * 2, &mut cs)?; + let points_proven = cs.load_u16()?; + if !cs.is_empty() { + return Err(tycho_types::error::Error::CellOverflow); + } + zipped.push((validator_idx, bits, points_proven)); + } + + let anchor_stats_history = zipped + .iter() + .map(|(validator_idx, _, points_proven)| AnchorStatsHistory { + validator_idx: *validator_idx, + stats: AnchorPeerStats { + points_proven: *points_proven, + }, + }) + .collect::>(); + + let signatures_history = zipped + .into_iter() + .map(|(validator_idx, bits, _)| SignatureHistory { + validator_idx, + bits, + }) + .collect::>(); + + Ok(Self(BlocksBatch { + start_seqno, + anchor_range, + committed_blocks, + signatures_history: signatures_history.into_boxed_slice(), + anchor_stats_history: anchor_stats_history.into_boxed_slice(), + })) + } +} + +impl Store for BlocksBatchBc { + fn store_into( + &self, + builder: &mut CellBuilder, + context: &dyn CellContext, + ) -> Result<(), tycho_types::error::Error> { + let batch = &self.0; + + builder.store_u32(batch.start_seqno)?; + if let Some(anchor_range) = self.0.anchor_range.as_ref() { + builder.store_u32(*anchor_range.start())?; + builder.store_u32(*anchor_range.end())?; + } else { + builder.store_u32(0)?; + builder.store_u32(0)?; + }; + + batch.committed_blocks.store_into(builder, context)?; + + // A subset contains items in no particular order, + // so we need to sort by them to simplify remapping to vset. + let mut signatures = (batch.signatures_history.iter()).collect::>(); + signatures.sort_unstable_by_key(|a| a.validator_idx); + + assert_eq!( + signatures.len(), + batch.anchor_stats_history.len(), + "anchor and signature stats must have the same length, but {} != {}", + signatures.len(), + batch.anchor_stats_history.len(), + ); + + let zipped = signatures + .iter() + .zip(&batch.anchor_stats_history) + .map(|(s, a)| { + if s.validator_idx == a.validator_idx { + Ok((s.validator_idx, (&s.bits, a.stats.points_proven))) + } else { + Err(anyhow!( + "expected {} got {}", + s.validator_idx, + a.validator_idx + )) + } + }) + .collect::>>() + .expect("anchor stats must share validator_idx with signature history"); + + let Some(dict_root) = dict::build_dict_from_sorted_iter(zipped, context)? else { + // Subset must not be empty. + return Err(tycho_types::error::Error::InvalidData); + }; + builder.store_reference(dict_root) + } +} + +#[cfg(test)] +mod tests { + use tycho_slasher_traits::ReceivedSignature; + + use super::*; + + #[test] + fn blocks_batch_cell() { + let mut batch = BlocksBatch::new(230, NonZeroU32::new(10).unwrap(), &[5, 10, 12, 3]); + + for (seqno, signatures) in [ + (230, [ + ReceivedSignature(ReceivedSignature::VALID_SIGNATURE_BIT), + ReceivedSignature(ReceivedSignature::VALID_SIGNATURE_BIT), + ReceivedSignature(0), + ReceivedSignature(ReceivedSignature::INVALID_SIGNATURE_BIT), + ]), + (231, [ + ReceivedSignature(0), + ReceivedSignature(ReceivedSignature::VALID_SIGNATURE_BIT), + ReceivedSignature(ReceivedSignature::INVALID_SIGNATURE_BIT), + ReceivedSignature(ReceivedSignature::VALID_SIGNATURE_BIT), + ]), + (233, [ + ReceivedSignature(ReceivedSignature::VALID_SIGNATURE_BIT), + ReceivedSignature(ReceivedSignature::VALID_SIGNATURE_BIT), + ReceivedSignature(ReceivedSignature::VALID_SIGNATURE_BIT), + ReceivedSignature(ReceivedSignature::VALID_SIGNATURE_BIT), + ]), + (234, [ + ReceivedSignature(ReceivedSignature::VALID_SIGNATURE_BIT), + ReceivedSignature(ReceivedSignature::VALID_SIGNATURE_BIT), + ReceivedSignature(ReceivedSignature::VALID_SIGNATURE_BIT), + ReceivedSignature(ReceivedSignature::VALID_SIGNATURE_BIT), + ]), + (239, [ + ReceivedSignature(ReceivedSignature::VALID_SIGNATURE_BIT), + ReceivedSignature(ReceivedSignature::VALID_SIGNATURE_BIT), + ReceivedSignature(ReceivedSignature::VALID_SIGNATURE_BIT), + ReceivedSignature(ReceivedSignature::VALID_SIGNATURE_BIT), + ]), + ] { + let committed = batch.commit_signatures(seqno, &signatures); + assert!(committed); + } + + let cell = CellBuilder::build_from(BlocksBatchBc::wrap(&batch)).unwrap(); + println!("{}", Boc::encode_base64(cell)); + } +} diff --git a/slasher/src/collector/validator_events.rs b/slasher/src/collector/validator_events.rs new file mode 100644 index 0000000000..24734b7015 --- /dev/null +++ b/slasher/src/collector/validator_events.rs @@ -0,0 +1,419 @@ +use std::collections::VecDeque; +use std::num::NonZeroU32; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::{Arc, Mutex}; + +use anyhow::Result; +use tokio::sync::mpsc; +use tracing::instrument; +use tycho_crypto::ed25519; +use tycho_slasher_traits::{ + AnchorStats, ReceivedSignature, ValidationSessionId, ValidatorEventsListener, +}; +use tycho_types::models::{BlockId, BlockIdShort, IndexedValidatorDescription}; +use tycho_util::{DashMapEntry, FastDashMap}; + +use crate::bc::BlocksBatch; +use crate::tracing_targets; + +const INIT_QUEUE_CAPACITY: usize = 3; + +pub trait BlockBatchesStore { + fn known_batch_size(&self) -> AtomicU32; +} + +pub struct ValidatorEventsCollector { + default_batch_size: AtomicU32, + sessions: FastDashMap, + init_queue: Mutex>, + init_queue_capacity: usize, +} + +#[derive(Debug, Clone)] +pub struct ValidatorSessionInfo { + pub session_id: ValidationSessionId, + pub first_mc_seqno: u32, + pub own_validator_idx: u16, + pub validators: Arc<[IndexedValidatorDescription]>, +} + +struct SessionState { + batch_size: NonZeroU32, + /// Maps each subset item with its original vset index. + validator_indices: Box<[u16]>, + current_batch: BlocksBatch, + /// Imported anchors can arrive one batch window before block callbacks rotate the session. + next_batch: BlocksBatch, + first_seqno: u32, + next_expected_seqno: u32, + complete_batches: Option>, +} + +pub type BlocksBatchTx = mpsc::UnboundedSender; +pub type BlocksBatchRx = mpsc::UnboundedReceiver; + +// === Collector impl === + +impl ValidatorEventsCollector { + pub fn new(default_batch_size: NonZeroU32) -> Self { + let init_queue_capacity = INIT_QUEUE_CAPACITY; + let init_queue = Mutex::new(VecDeque::with_capacity(init_queue_capacity)); + + Self { + default_batch_size: AtomicU32::new(default_batch_size.get()), + sessions: Default::default(), + init_queue, + init_queue_capacity, + } + } + + pub fn pop_session_to_init(&self, mc_seqno: u32) -> Option { + let mut queue = self.init_queue.lock().unwrap(); + if let Some(info) = queue.front() + && info.first_mc_seqno > mc_seqno + { + return None; + } + queue.pop_front() + } + + fn push_session_to_init(&self, info: ValidatorSessionInfo) { + let mut items = self.init_queue.lock().unwrap(); + if items.len() >= self.init_queue_capacity + && let Some(info) = items.pop_front() + { + tracing::warn!( + target: tracing_targets::SLASHER, + session_id = ?info.session_id, + "session info dropped from init queue" + ); + } + items.push_back(info); + } + + pub fn set_default_batch_size(&self, batch_size: NonZeroU32) { + self.default_batch_size + .store(batch_size.get(), Ordering::Release); + } + + pub fn init_session( + &self, + session_id: ValidationSessionId, + batch_size: NonZeroU32, + complete_batches: BlocksBatchTx, + ) -> bool { + let Some(mut session) = self.sessions.get_mut(&session_id) else { + return false; + }; + + // Reset the current batch if its size has changed. + // TODO: Split or grow the previous batch to not discard events. + if session.batch_size != batch_size { + session.batch_size = batch_size; + let next_expected_seqno = session.next_expected_seqno; + session.reset_batches(next_expected_seqno); + } + + session.complete_batches = Some(complete_batches); + + true + } + + pub fn skip_session(&self, session_id: ValidationSessionId) -> bool { + self.sessions.remove(&session_id).is_some() + } +} + +impl ValidatorEventsListener for ValidatorEventsCollector { + #[instrument(skip_all, fields(session_id = ?session_id))] + fn on_session_started( + &self, + session_id: ValidationSessionId, + first_mc_seqno: u32, + own_validator_idx: u16, + validators: &[IndexedValidatorDescription], + ) { + tracing::debug!( + target: tracing_targets::SLASHER, + first_mc_seqno, + "on_session_started" + ); + + let validator_indices = validators + .iter() + .map(|item| item.validator_idx) + .collect::>(); + + let batch_size = NonZeroU32::new(self.default_batch_size.load(Ordering::Acquire)).unwrap(); + let current_batch = BlocksBatch::new(first_mc_seqno, batch_size, &validator_indices); + let next_batch = + BlocksBatch::new(current_batch.seqno_after(), batch_size, &validator_indices); + + let validators = Arc::<[IndexedValidatorDescription]>::from(validators); + + if let DashMapEntry::Vacant(v) = self.sessions.entry(session_id) { + v.insert(SessionState { + batch_size, + validator_indices, + current_batch, + next_batch, + first_seqno: first_mc_seqno, + next_expected_seqno: first_mc_seqno, + // Will be initialized later via `init_session`. + complete_batches: None, + }); + + self.push_session_to_init(ValidatorSessionInfo { + session_id, + first_mc_seqno, + own_validator_idx, + validators, + }); + } else { + tracing::warn!(target: tracing_targets::SLASHER, "duplicate session"); + } + } + + #[instrument(skip_all, fields(session_id = ?session_id))] + fn on_session_finished(&self, session_id: ValidationSessionId) { + tracing::debug!(target: tracing_targets::SLASHER, "on_session_finished"); + if let Some((_, session)) = self.sessions.remove(&session_id) + && let Err(e) = session.commit_final_batch() + { + tracing::warn!( + target: tracing_targets::SLASHER, + "failed to commit blocks batch on finish: {e:?}" + ); + } + } + + #[instrument(skip_all, fields(session_id = ?session_id))] + fn on_anchor_import( + &self, + session_id: ValidationSessionId, + block_id: &BlockIdShort, + anchor_id: u32, + anchor_stats: AnchorStats, + ) { + if !block_id.is_masterchain() { + // Ignore for non-masterchain blocks (just in case). + return; + } + + tracing::debug!( + target: tracing_targets::SLASHER, + %block_id, + "on_anchor_import" + ); + let Some(mut session) = self.sessions.get_mut(&session_id) else { + tracing::warn!( + target: tracing_targets::SLASHER, + "session not found, ignoring on_anchor_import event" + ); + return; + }; + session.push_anchor_stats(block_id.seqno, anchor_id, &anchor_stats); + } + + #[instrument(skip_all, fields(session_id = ?session_id))] + fn on_block_validated( + &self, + session_id: ValidationSessionId, + block_id: &BlockId, + signatures: Arc<[ReceivedSignature]>, + ) { + if !block_id.is_masterchain() { + // Ignore for non-masterchain blocks (just in case). + return; + } + + tracing::debug!( + target: tracing_targets::SLASHER, + %block_id, + "on_block_validated" + ); + let Some(mut session) = self.sessions.get_mut(&session_id) else { + tracing::warn!( + target: tracing_targets::SLASHER, + "session not found, ignoring on_block_validated event" + ); + return; + }; + session.handle_block(block_id.seqno, Some(signatures.as_ref())); + } + + #[instrument(skip_all, fields(session_id = ?session_id))] + fn on_block_skipped(&self, session_id: ValidationSessionId, block_id: &BlockId) { + if !block_id.is_masterchain() { + // Ignore for non-masterchain blocks (just in case). + return; + } + + tracing::debug!( + target: tracing_targets::SLASHER, + %block_id, + "on_block_skipped" + ); + let Some(mut session) = self.sessions.get_mut(&session_id) else { + tracing::warn!( + target: tracing_targets::SLASHER, + "session not found, ignoring on_block_skipped event" + ); + return; + }; + session.handle_block(block_id.seqno, None); + } +} + +// === Validator session info impl === + +impl ValidatorSessionInfo { + pub fn can_participate(&self, public_key: &ed25519::PublicKey) -> bool { + let Some(desc) = self + .validators + .iter() + .find(|item| item.validator_idx == self.own_validator_idx) + else { + return false; + }; + + public_key.as_bytes() == desc.public_key.as_array() + } +} + +// === Session state impl === + +impl SessionState { + fn push_anchor_stats( + &mut self, + seqno: u32, + anchor_id: u32, + anchor_stats: &AnchorStats, + ) -> bool { + if self.current_batch.contains_seqno(seqno) { + (self.current_batch).push_anchor_stats(anchor_id, anchor_stats) + } else if self.next_batch.contains_seqno(seqno) { + (self.next_batch).push_anchor_stats(anchor_id, anchor_stats) + } else { + tracing::warn!( + target: tracing_targets::SLASHER, + anchor_id, + seqno, + current_batch_seqnos = ?self.current_batch.seqno_range(), + next_batch_seqnos = ?self.next_batch.seqno_range(), + "anchor import outside block batches" + ); + false + } + } + + fn handle_block(&mut self, seqno: u32, signatures: Option<&[ReceivedSignature]>) -> bool { + let batches = match self.try_advance_current_batch(seqno) { + AdvanceBlockStatus::TooOld => return false, + AdvanceBlockStatus::Unchanged => [None, None], + AdvanceBlockStatus::Rotated { first, second } => [Some(first), second], + }; + + let event_type = match signatures { + Some(signatures) => { + self.current_batch.commit_signatures(seqno, signatures); + "validated" + } + None => "skipped", + }; + + for (batch, ith) in batches.into_iter().flatten().zip(["1st", "2nd"]) { + if let Err(e) = self.commit_batch(batch) { + tracing::error!( + target: tracing_targets::SLASHER, + event_type, + "{ith} blocks batch failed to commit: {e:?}" + ); + } + } + true + } + + fn try_advance_current_batch(&mut self, seqno: u32) -> AdvanceBlockStatus { + if seqno < self.next_expected_seqno { + return AdvanceBlockStatus::TooOld; + } else if self.current_batch.contains_seqno(seqno) { + return AdvanceBlockStatus::Unchanged; + } + + self.next_expected_seqno = seqno + 1; + + if self.next_batch.contains_seqno(seqno) { + let next = self.make_batch(self.next_batch.seqno_after()); + let current = std::mem::replace(&mut self.next_batch, next); + + AdvanceBlockStatus::Rotated { + first: std::mem::replace(&mut self.current_batch, current), + second: None, + } + } else { + self.reset_batches(seqno) + } + } + + fn commit_batch(&self, batch: BlocksBatch) -> Result<()> { + Self::commit_batch_impl(&self.complete_batches, batch) + } + + fn commit_final_batch(self) -> Result<()> { + Self::commit_batch_impl(&self.complete_batches, self.current_batch)?; + Self::commit_batch_impl(&self.complete_batches, self.next_batch) + } + + fn commit_batch_impl( + complete_batches: &Option, + batch: BlocksBatch, + ) -> Result<()> { + if batch.is_empty() { + return Ok(()); + } + + let Some(tx) = complete_batches else { + anyhow::bail!("not initialized"); + }; + + if tx.send(batch).is_err() { + anyhow::bail!("channel closed"); + } + Ok(()) + } + + fn reset_batches(&mut self, seqno: u32) -> AdvanceBlockStatus { + let current = self.make_batch(self.align_seqno(seqno)); + let next = self.make_batch(current.seqno_after()); + + AdvanceBlockStatus::Rotated { + first: std::mem::replace(&mut self.current_batch, current), + second: Some(std::mem::replace(&mut self.next_batch, next)), + } + } + + fn make_batch(&self, start_seqno: u32) -> BlocksBatch { + BlocksBatch::new(start_seqno, self.batch_size, &self.validator_indices) + } + + fn align_seqno(&self, seqno: u32) -> u32 { + assert!(seqno >= self.first_seqno); + + // Example: + // batch_size = 100 + // first_seqno = 101 + // seqno = 250 + // result = 250 - (250 - 101) % 100 = 201 + seqno - (seqno - self.first_seqno) % self.batch_size.get() + } +} + +enum AdvanceBlockStatus { + TooOld, + Unchanged, + Rotated { + first: BlocksBatch, + second: Option, + }, +} diff --git a/slasher/src/lib.rs b/slasher/src/lib.rs new file mode 100644 index 0000000000..f8a6d69e9b --- /dev/null +++ b/slasher/src/lib.rs @@ -0,0 +1,562 @@ +use std::sync::Arc; +use std::time::Duration; + +use anyhow::{Context, Result}; +use arc_swap::{ArcSwap, ArcSwapOption}; +use futures_util::future::BoxFuture; +use serde::{Deserialize, Serialize}; +use tokio::sync::mpsc; +use tokio_util::sync::CancellationToken; +use tracing::instrument; +use tycho_block_util::config::BlockchainConfigExt; +use tycho_core::block_strider::{StateSubscriber, StateSubscriberContext}; +use tycho_core::blockchain_rpc::BlockchainRpcClient; +use tycho_crypto::ed25519; +use tycho_slasher_traits::{ValidationSessionId, ValidatorEventsListener}; +use tycho_storage::StorageContext; +use tycho_types::boc::Boc; +use tycho_types::models::{SignatureContext, StdAddr}; +use tycho_util::config::PartialConfig; +use tycho_util::futures::JoinTask; +use tycho_util::serde_helpers; + +pub use self::analyzer::{ + SessionPenaltyReport, SessionValidatorScore, VsetPenaltyReport, VsetValidatorPenalty, +}; +pub use self::bc::{ + BlocksBatch, ContractSubscription, EncodeBlocksBatchMessage, MessageDeliveryStatus, + SignatureHistory, SignedMessage, SlasherContract, StubSlasherContract, +}; +use self::collector::{ValidatorEventsCollector, ValidatorSessionInfo}; +use self::storage::SlasherStorage; +use self::util::AtomicValidationSessionId; + +mod analyzer; +pub mod collector { + pub use self::validator_events::*; + + mod validator_events; + // TODO: mod mempool_events; +} + +mod bc; +mod storage; +mod tracing_targets; +mod util; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialConfig)] +#[serde(default)] +pub struct SlasherConfig { + /// TTL of messages to the slasher contract. + /// + /// Default: `30s` + #[serde(with = "serde_helpers::humantime")] + pub message_ttl: Duration, + /// Interval between message delivery attempts. + /// + /// Default: `1s` + #[serde(with = "serde_helpers::humantime")] + pub message_retry_interval: Duration, + + /// Additional time to wait for the previous batch delivery. + /// + /// Default: `5s` + #[serde(with = "serde_helpers::humantime")] + pub prev_delivery_timeout: Option, + + /// Absolute threshold of bad-session weight after which validator is bad in a vset epoch. + /// + /// Default: `100` + pub bad_sessions_weight_threshold: u64, +} + +impl Default for SlasherConfig { + fn default() -> Self { + Self { + message_ttl: Duration::from_secs(30), + message_retry_interval: Duration::from_secs(1), + prev_delivery_timeout: Some(Duration::from_secs(5)), + bad_sessions_weight_threshold: 100, + } + } +} + +pub struct Slasher { + validator_events_collector: Arc, + shared: Arc, + cancellation_token: CancellationToken, +} + +impl Slasher { + pub fn new( + node_keys: Arc, + contract: C, + blockchain_rpc_client: BlockchainRpcClient, + storage_context: &StorageContext, + config: SlasherConfig, + ) -> Result { + let storage = + SlasherStorage::open(storage_context).context("failed to open slasher storage")?; + + let collector = Arc::new(ValidatorEventsCollector::new(contract.default_batch_size())); + + Ok(Self { + validator_events_collector: collector, + shared: Arc::new(SlasherSharedState { + config, + node_keys, + contract: Box::new(contract), + subscription: ArcSwapOption::empty(), + blockchain_rpc_client, + storage, + known_session_id: AtomicValidationSessionId::new(ValidationSessionId { + vset_switch_round: 0, + catchain_seqno: 0, + }), + signature_context: ArcSwap::new(Arc::new(SignatureContext { + global_id: 0, + capabilities: Default::default(), + })), + }), + cancellation_token: Default::default(), + }) + } + + pub fn validator_events_listener(&self) -> Arc { + self.validator_events_collector.clone() + } + + async fn handle_state_impl(&self, cx: &StateSubscriberContext) -> Result<()> { + if !cx.block.id().is_masterchain() { + return Ok(()); + } + let mc_seqno = cx.block.id().seqno; + + let this = self.shared.as_ref(); + let state_extra = cx.state.state_extra()?; + + // Sync signature context (TODO: do it only when config changes) + let global = state_extra.config.get_global_version()?; + self.shared + .signature_context + .store(Arc::new(SignatureContext { + global_id: cx.block.as_ref().global_id, + capabilities: global.capabilities, + })); + + // Check config updates (TODO: do it only when config changes) + let Some(slasher_params) = this + .contract + .find_params(&state_extra.config) + .context("failed to find slasher params")? + else { + return Ok(()); + }; + self.validator_events_collector + .set_default_batch_size(slasher_params.blocks_batch_size); + let slasher_address = StdAddr::new_masterchain(slasher_params.address); + + let catchain_seqno = state_extra.validator_info.catchain_seqno; + let vset_switch_round = state_extra.consensus_info.vset_switch_round; + + let current_session_id = ValidationSessionId { + vset_switch_round, + catchain_seqno, + }; + let current_vset_hash = *state_extra + .config + .get_current_validator_set_raw()? + .repr_hash(); + + tracing::trace!( + target: tracing_targets::SLASHER, + ?slasher_params, + ?current_session_id, + current_vset_hash = %current_vset_hash, + ); + + // TODO: Add metrics. + if current_session_id != this.known_session_id.load() { + tracing::info!( + target: tracing_targets::SLASHER, + old_session_id = ?this.known_session_id.load(), + ?current_session_id, + "slasher observed validation session change", + ); + this.known_session_id.set(current_session_id); + } + this.storage + .update_current_vset_epoch(current_session_id, current_vset_hash)?; + + // Handle subscription + let subscription = match this.subscription.load_full() { + Some(s) if s.address() == &slasher_address => s, + _ => { + tracing::info!( + target: tracing_targets::SLASHER, + %slasher_address, + "slasher address changed" + ); + let s = Arc::new(ContractSubscription::new(&slasher_address)); + this.subscription.store(Some(s.clone())); + s + } + }; + + subscription.cleanup_expired_messages(cx.block.load_info()?.gen_utime); + + let extra = cx.block.load_extra()?.account_blocks.load()?; + if let Some((_, account_block)) = extra.get(slasher_address.address)? { + for entry in account_block.transactions.iter() { + let (_, _, tx) = entry?; + let tx_hash = tx.repr_hash(); + let tx = tx.load()?; + + tracing::debug!( + target: tracing_targets::SLASHER, + %tx_hash, + msg_hash = ?tx.in_msg.as_ref().map(|msg| msg.repr_hash()), + "found slasher transaction", + ); + + let matched_own_message = subscription.handle_account_transaction(tx_hash, &tx)?; + + match self.shared.contract.decode_event(&tx) { + Ok(Some(event)) => match event { + bc::SlasherContractEvent::SubmitBlocksBatch(submitted) => { + let batch = &submitted.blocks_batch; + tracing::info!( + target: tracing_targets::SLASHER, + %tx_hash, + session_id = ?submitted.session_id, + validator_idx = submitted.validator_idx, + batch_start_seqno = batch.start_seqno(), + batch_seqno_after = batch.seqno_after(), + batch_slots = batch.committed_blocks.len(), + committed_blocks = batch.committed_block_count(), + validators = batch.validator_count(), + "{}", + if matched_own_message { + "own blocks batch committed by slasher" + } else { + "received blocks batch from validator" + } + ); + + // TODO: Move into blocking. + if !this.storage.store_blocks_batch( + submitted.session_id, + submitted.validator_idx, + &submitted.blocks_batch, + )? { + tracing::warn!( + target: tracing_targets::SLASHER, + session_id = ?submitted.session_id, + current_vset_hash = %current_vset_hash, + "ignoring observed blocks batch without known epoch" + ); + } + tokio::task::yield_now().await; + } + }, + Ok(None) => {} + Err(e) => tracing::warn!( + target: tracing_targets::SLASHER, + %tx_hash, + "failed to parse slasher event: {e:?}" + ), + } + } + } + + self.shared.analyze_closed_vset_epochs()?; + + while let Some(session_info) = self + .validator_events_collector + .pop_session_to_init(mc_seqno) + { + let session_id = session_info.session_id; + tracing::info!( + target: tracing_targets::SLASHER, + ?session_id, + "found session to init" + ); + if !session_info.can_participate(&this.node_keys.public_key) { + tracing::info!( + target: tracing_targets::SLASHER, + ?session_id, + "skipping session" + ); + continue; + } + + let (tx, rx) = mpsc::unbounded_channel::(); + if !self.validator_events_collector.init_session( + session_id, + slasher_params.blocks_batch_size, + tx, + ) { + tracing::warn!( + target: tracing_targets::SLASHER, + ?session_id, + "session removed before init" + ); + continue; + } + + let token = self.cancellation_token.clone(); + let shared = self.shared.clone(); + tokio::task::spawn( + token.run_until_cancelled_owned(shared.send_batches_to_contract(session_info, rx)), + ); + } + + Ok(()) + } +} + +impl Drop for Slasher { + fn drop(&mut self) { + self.cancellation_token.cancel(); + } +} + +impl StateSubscriber for Slasher { + type HandleStateFut<'a> = BoxFuture<'a, Result<()>>; + + #[inline] + fn handle_state<'a>(&'a self, cx: &'a StateSubscriberContext) -> Self::HandleStateFut<'a> { + Box::pin(self.handle_state_impl(cx)) + } +} + +struct SlasherSharedState { + config: SlasherConfig, + node_keys: Arc, + contract: Box, + subscription: ArcSwapOption, + blockchain_rpc_client: BlockchainRpcClient, + storage: SlasherStorage, + known_session_id: AtomicValidationSessionId, + signature_context: ArcSwap, +} + +impl SlasherSharedState { + fn analyze_closed_vset_epochs(&self) -> Result<()> { + let snapshot = self.storage.snapshot(); + let closed_vset_epoches = snapshot.load_closed_vset_epochs()?; + if closed_vset_epoches.is_empty() { + tracing::warn!( + target: tracing_targets::SLASHER, + "closes vset epoches not found" + ); + return Ok(()); + } + + for epoch in closed_vset_epoches { + if snapshot.load_vset_report(epoch.start_session_id)?.is_some() { + continue; + } + + tracing::info!( + target: tracing_targets::SLASHER, + vset_hash = ?epoch.vset_hash, + start_id = ?epoch.start_session_id, + "analyzing closed vset epoch" + ); + + let mut session_reports = Vec::new(); + for meta in snapshot.load_sessions_for_epoch(epoch.start_session_id)? { + let report = match snapshot.load_session_report(meta.session_id)? { + Some(report) => report, + None => { + let batches = + snapshot.load_observed_batches_for_session(meta.session_id)?; + let report = analyzer::analyze_session(&meta, &batches); + self.storage.store_session_report(&report)?; + report + } + }; + Self::log_session_report(&report); + session_reports.push(report); + } + + let report = analyzer::analyze_vset_epoch( + &epoch, + &session_reports, + self.config.bad_sessions_weight_threshold, + ); + self.storage.store_vset_report(&report)?; + Self::log_vset_report(&report); + } + + Ok(()) + } + + fn log_session_report(report: &SessionPenaltyReport) { + for item in &report.validators { + tracing::info!( + target: tracing_targets::SLASHER, + session_id = ?report.session_id, + epoch_start_session_id = ?report.epoch_start_session_id, + validator_idx = item.validator_idx, + earned_points = item.earned_points, + max_points = item.max_points, + session_weight = report.session_weight, + is_bad = item.is_bad, + "scored validator in validation session", + ); + } + } + + fn log_vset_report(report: &VsetPenaltyReport) { + let bad_validator_indices = report + .validators + .iter() + .filter(|item| item.is_bad) + .map(|item| item.validator_idx) + .collect::>(); + + tracing::info!( + target: tracing_targets::SLASHER, + epoch_start_session_id = ?report.epoch_start_session_id, + vset_hash = %report.vset_hash, + bad_validator_indices = ?bad_validator_indices, + "finished scoring closed vset epoch", + ); + + for item in &report.validators { + tracing::info!( + target: tracing_targets::SLASHER, + epoch_start_session_id = ?report.epoch_start_session_id, + vset_hash = %report.vset_hash, + validator_idx = item.validator_idx, + bad_sessions_weight = item.bad_sessions_weight, + total_sessions_weight = item.total_sessions_weight, + is_bad = item.is_bad, + "computed final validator verdict in vset epoch", + ); + } + } + + #[instrument(skip_all, fields(session_id = ?info.session_id))] + async fn send_batches_to_contract( + self: Arc, + info: ValidatorSessionInfo, + mut rx: collector::BlocksBatchRx, + ) { + tracing::info!(target: tracing_targets::SLASHER, "started"); + scopeguard::defer!(tracing::info!(target: tracing_targets::SLASHER, "finished")); + + let mut send_task = None; + + while let Some(batch) = rx.recv().await { + if let Some(send_task) = send_task.take() + && let Some(timeout) = self.config.prev_delivery_timeout + && tokio::time::timeout(timeout, send_task).await.is_err() + { + tracing::warn!( + target: tracing_targets::SLASHER, + "timeout on waiting for the previous batch to be delivered" + ); + } + + send_task = Some(JoinTask::new(self.clone().deliver_batch_message( + info.session_id, + info.own_validator_idx, + batch, + ))); + } + } + + async fn deliver_batch_message( + self: Arc, + session_id: ValidationSessionId, + validator_idx: u16, + batch: BlocksBatch, + ) { + loop { + let Some(subscription) = self.subscription.load_full() else { + tracing::warn!(target: tracing_targets::SLASHER, "no slasher contract subscription"); + break; + }; + + let params = EncodeBlocksBatchMessage { + address: subscription.address(), + session_id, + batch: &batch, + validator_idx, + signature_context: **self.signature_context.load(), + keypair: &self.node_keys, + ttl: self.config.message_ttl, + }; + + let signed = match self.contract.encode_blocks_batch_message(¶ms) { + Ok(signed) => signed, + Err(e) => { + tracing::error!( + target: tracing_targets::SLASHER, + "failed to encode batch message: {e:?}" + ); + return; + } + }; + let msg_hash = *signed.message.repr_hash(); + let boc = Boc::encode(signed.message.into_inner()); + + match subscription.track_message(&msg_hash, signed.expire_at) { + Ok(res) => { + tracing::info!( + target: tracing_targets::SLASHER, + %msg_hash, + address = %params.address, + session_id = ?params.session_id, + validator_idx = params.validator_idx, + batch_start_seqno = batch.start_seqno(), + batch_seqno_after = batch.seqno_after(), + batch_slots = batch.committed_blocks.len(), + committed_blocks = batch.committed_block_count(), + validators = batch.validator_count(), + "sending own blocks batch to slasher" + ); + self.blockchain_rpc_client + .broadcast_external_message(&boc) + .await; + drop(boc); + + match res.await { + Ok(MessageDeliveryStatus::Sent { tx_hash }) => { + tracing::info!( + target: tracing_targets::SLASHER, + %tx_hash, + session_id = ?params.session_id, + validator_idx = params.validator_idx, + batch_start_seqno = batch.start_seqno(), + batch_seqno_after = batch.seqno_after(), + batch_slots = batch.committed_blocks.len(), + committed_blocks = batch.committed_block_count(), + validators = batch.validator_count(), + "own blocks batch delivered" + ); + return; + } + Ok(MessageDeliveryStatus::Expired) => { + // TODO: Execute transaction locally to guess the reason. + tracing::warn!( + target: tracing_targets::SLASHER, + "batch message expired" + ); + } + Err(_) => return, + } + } + Err(e) => tracing::warn!( + target: tracing_targets::SLASHER, + "failed to track message: {e:?}" + ), + } + + tokio::time::sleep(self.config.message_retry_interval).await; + } + } +} diff --git a/slasher/src/proto.tl b/slasher/src/proto.tl new file mode 100644 index 0000000000..03ff34598c --- /dev/null +++ b/slasher/src/proto.tl @@ -0,0 +1,111 @@ +---types--- + +/** +* @param start_seqno seqno of the first block in batch +* @param committed_blocks bitset with committed blocks +* @param entries all non-empty histories for unique validator indexes +*/ +slasher.blocksBatch + start_seqno:int + min_anchor:int + max_anchor:int + committed_blocks:bitset + entries:(vector slasher.signatureHistory) + anchor_stats_history:(vector slasher.anchorStatsHistory) + = slasher.BlocksBatch; + +/** +* @param validator_idx validator index relative to the validator set +* @param bits history bits (2 for each block) +*/ +slasher.signatureHistory + validator_idx:int + bits:bitset + = slasher.SignatureHistory; + +/** +* @param validator_idx validator index relative to the validator set +* @param points_proven occurrences of a point having the next one as a proof +*/ +slasher.anchorStatsHistory + validator_idx:int + points_proven:int + = slasher.AnchorStatsHistory; + +/** +* @param catchain_seqno validation session catchain seqno +* @param vset_switch_round validation session vset switch round +* @param epoch_catchain_seqno catchain seqno of the vset epoch start +* @param epoch_vset_switch_round vset switch round of the vset epoch start +* @param session_weight unique committed blocks observed in the session +* @param validators per-validator weighted scores in the session +*/ +slasher.sessionPenaltyReport + catchain_seqno:int + vset_switch_round:int + epoch_catchain_seqno:int + epoch_vset_switch_round:int + session_weight:int + validators:(vector slasher.sessionValidatorScore) + = slasher.SessionPenaltyReport; + +/** +* @param validator_idx validator index relative to the validator set +* @param earned_points weighted points received from observed validators +* @param max_points weighted maximum possible score from observed validators +* @param is_bad whether validator is bad in this session +*/ +slasher.sessionValidatorScore + validator_idx:int + earned_points:long + max_points:long + is_bad:int + = slasher.SessionValidatorScore; + +/** +* @param epoch_catchain_seqno catchain seqno of the vset epoch start +* @param epoch_vset_switch_round vset switch round of the vset epoch start +* @param vset_hash validator set hash +* @param validators per-validator verdict in the epoch +*/ +slasher.vsetPenaltyReport + epoch_catchain_seqno:int + epoch_vset_switch_round:int + vset_hash:int256 + validators:(vector slasher.vsetValidatorPenalty) + = slasher.VsetPenaltyReport; + +/** +* @param validator_idx validator index relative to the validator set +* @param bad_sessions_weight sum of session weights where validator was bad +* @param total_sessions_weight total observed session weight for validator +* @param is_bad final epoch verdict +*/ +slasher.vsetValidatorPenalty + validator_idx:int + bad_sessions_weight:long + total_sessions_weight:long + is_bad:int + = slasher.VsetValidatorPenalty; + +/** +* @param vset_hash validator set hash +* @param has_next_epoch whether epoch is already closed +* @param next_epoch_catchain_seqno next epoch start catchain seqno +* @param next_epoch_vset_switch_round next epoch start vset switch round +*/ +slasher.vsetEpoch + vset_hash:int256 + has_next_epoch:int + next_epoch_catchain_seqno:int + next_epoch_vset_switch_round:int + = slasher.VsetEpoch; + +/** +* @param validator_indices validator indices participating in the session +*/ +slasher.sessionMeta + validator_indices:(vector int) + = slasher.SessionMeta; + +bitset length:int data:bytes = BitSet; diff --git a/slasher/src/storage/db.rs b/slasher/src/storage/db.rs new file mode 100644 index 0000000000..542d1f313a --- /dev/null +++ b/slasher/src/storage/db.rs @@ -0,0 +1,159 @@ +use tycho_storage::kv::{ + Migrations, NamedTables, StateVersionProvider, TableContext, WithMigrations, +}; +use tycho_util::sync::CancellationFlag; +use weedb::{MigrationError, Semver, WeeDb}; + +pub type SlasherDb = WeeDb; + +impl NamedTables for SlasherTables { + const NAME: &'static str = "slasher"; +} + +impl WithMigrations for SlasherTables { + const VERSION: Semver = [0, 1, 0]; + + type VersionProvider = StateVersionProvider; + + fn new_version_provider() -> Self::VersionProvider { + StateVersionProvider::new::() + } + + fn register_migrations( + _migrations: &mut Migrations, + _cancelled: CancellationFlag, + ) -> Result<(), MigrationError> { + Ok(()) + } +} + +weedb::tables! { + pub struct SlasherTables { + pub state: tables::State, + pub vset_epochs: tables::VsetEpochs, + pub session_meta: tables::SessionMeta, + pub block_batches: tables::BlockBatches, + pub session_reports: tables::SessionReports, + pub vset_reports: tables::VsetReports, + } +} + +pub mod tables { + use tycho_storage::kv::{ + TableContext, default_block_based_table_factory, optimize_for_point_lookup, + zstd_block_based_table_factory, + }; + use weedb::rocksdb::Options; + use weedb::{ColumnFamily, ColumnFamilyOptions}; + + /// Stores generic node parameters. + pub struct State; + + impl ColumnFamily for State { + const NAME: &'static str = "state"; + } + + impl ColumnFamilyOptions for State { + fn options(opts: &mut Options, ctx: &mut TableContext) { + default_block_based_table_factory(opts, ctx); + + opts.set_optimize_filters_for_hits(true); + optimize_for_point_lookup(opts, ctx); + } + } + + /// Stores validator-set epochs keyed by their start validation session. + pub struct VsetEpochs; + + impl VsetEpochs { + pub const KEY_LEN: usize = 4 + 4; + } + + impl ColumnFamily for VsetEpochs { + const NAME: &'static str = "vset_epochs"; + } + + impl ColumnFamilyOptions for VsetEpochs { + fn options(opts: &mut Options, ctx: &mut TableContext) { + default_block_based_table_factory(opts, ctx); + + opts.set_optimize_filters_for_hits(true); + optimize_for_point_lookup(opts, ctx); + } + } + + /// Stores session metadata grouped by epoch. + pub struct SessionMeta; + + impl SessionMeta { + pub const KEY_LEN: usize = VsetEpochs::KEY_LEN + 4 + 4; + } + + impl ColumnFamily for SessionMeta { + const NAME: &'static str = "session_meta"; + } + + impl ColumnFamilyOptions for SessionMeta { + fn options(opts: &mut Options, ctx: &mut TableContext) { + default_block_based_table_factory(opts, ctx); + + opts.set_optimize_filters_for_hits(true); + optimize_for_point_lookup(opts, ctx); + } + } + + /// Block batches submitted by validators + /// - Key: `session_id: (catchain_seqno u32 BE, vset_switch_round u32 BE), validator_idx: u16 BE, start_block: u32 BE` + /// - Value: blocks batch + pub struct BlockBatches; + + impl BlockBatches { + pub const KEY_LEN: usize = 4 + 4 + 2 + 4; + } + + impl ColumnFamily for BlockBatches { + const NAME: &'static str = "block_batches"; + } + + impl ColumnFamilyOptions for BlockBatches { + fn options(opts: &mut Options, ctx: &mut TableContext) { + zstd_block_based_table_factory(opts, ctx); + } + } + + /// Cached analyzer result for a single validation session. + pub struct SessionReports; + + impl SessionReports { + pub const KEY_LEN: usize = 4 + 4; + } + + impl ColumnFamily for SessionReports { + const NAME: &'static str = "session_reports"; + } + + impl ColumnFamilyOptions for SessionReports { + fn options(opts: &mut Options, ctx: &mut TableContext) { + default_block_based_table_factory(opts, ctx); + + opts.set_optimize_filters_for_hits(true); + optimize_for_point_lookup(opts, ctx); + } + } + + /// Final analyzer result for a closed validator-set epoch. + pub struct VsetReports; + + impl ColumnFamily for VsetReports { + const NAME: &'static str = "vset_reports"; + } + + impl ColumnFamilyOptions for VsetReports { + fn options(opts: &mut Options, ctx: &mut TableContext) { + default_block_based_table_factory(opts, ctx); + + opts.set_optimize_filters_for_hits(true); + optimize_for_point_lookup(opts, ctx); + } + } +} diff --git a/slasher/src/storage/mod.rs b/slasher/src/storage/mod.rs new file mode 100644 index 0000000000..3c0c10167e --- /dev/null +++ b/slasher/src/storage/mod.rs @@ -0,0 +1,431 @@ +use std::sync::Arc; + +use anyhow::{Context, Result}; +use tycho_slasher_traits::ValidationSessionId; +use tycho_storage::StorageContext; +use tycho_types::cell::HashBytes; +use weedb::OwnedSnapshot; + +use self::db::{SlasherDb, tables}; +use self::models::{ + StoredBlocksBatch, StoredSessionMeta, StoredSessionPenaltyReport, StoredVsetEpoch, + StoredVsetPenaltyReport, +}; +use crate::BlocksBatch; +use crate::analyzer::{ + ObservedBlocksBatch, SessionMeta, SessionPenaltyReport, VsetEpoch, VsetPenaltyReport, +}; + +pub mod db; +pub mod models; + +const SLASHER_DB_SUBDIR: &str = "slasher"; + +#[derive(Clone)] +#[repr(transparent)] +pub struct SlasherStorage { + inner: Arc, +} + +impl SlasherStorage { + pub fn open(ctx: &StorageContext) -> Result { + let db = ctx.open_preconfigured(SLASHER_DB_SUBDIR)?; + + Ok(Self { + inner: Arc::new(Inner { db }), + }) + } + + pub fn snapshot(&self) -> SlasherStorageSnapshot { + SlasherStorageSnapshot { + db: self.inner.db.clone(), + snapshot: Arc::new(self.inner.db.owned_snapshot()), + } + } + + pub fn update_current_vset_epoch( + &self, + current_session_id: ValidationSessionId, + current_vset_hash: HashBytes, + ) -> Result<()> { + let latest = self.load_latest_vset_epoch()?; + + match latest { + // just same vset. do nothing + Some(epoch) if epoch.vset_hash == current_vset_hash => Ok(()), + // we have new session. old persists for analyze + Some(mut epoch) => { + if epoch.next_epoch_start_session_id.is_none() { + epoch.next_epoch_start_session_id = Some(current_session_id); + self.store_vset_epoch(&epoch)?; + } + + self.store_vset_epoch(&VsetEpoch { + start_session_id: current_session_id, + vset_hash: current_vset_hash, + next_epoch_start_session_id: None, + }) + } + // Cold start. Save first vset epoch + None => self.store_vset_epoch(&VsetEpoch { + start_session_id: current_session_id, + vset_hash: current_vset_hash, + next_epoch_start_session_id: None, + }), + } + } + + pub fn store_blocks_batch( + &self, + session_id: ValidationSessionId, + observer_validator_idx: u16, + batch: &BlocksBatch, + ) -> Result { + let Some(epoch) = self.find_epoch_for_session(session_id)? else { + return Ok(false); + }; + + let key = block_batches_key(session_id, observer_validator_idx, batch.start_seqno); + let value = tl_proto::serialize(StoredBlocksBatch::wrap(batch)); + self.inner.db.block_batches.insert(key.as_slice(), value)?; + + let mut validator_indices = batch + .signatures_history + .iter() + .map(|item| item.validator_idx) + .collect::>(); + + validator_indices.sort(); + validator_indices.dedup(); + + // TODO: just upsert for now, maybe we can load and then save if absent + self.store_session_meta(&SessionMeta { + session_id, + epoch_start_session_id: epoch.start_session_id, + validator_indices, + })?; + + self.clear_intermediate_data(&epoch, &session_id)?; + + Ok(true) + } + + pub fn store_session_report(&self, report: &SessionPenaltyReport) -> Result<()> { + let key = session_key(report.session_id); + let value = tl_proto::serialize(StoredSessionPenaltyReport::wrap(report)); + self.inner + .db + .session_reports + .insert(key.as_slice(), value)?; + Ok(()) + } + + pub fn store_vset_report(&self, report: &VsetPenaltyReport) -> Result<()> { + let key = session_key(report.epoch_start_session_id); + let value = tl_proto::serialize(StoredVsetPenaltyReport::wrap(report)); + self.inner.db.vset_reports.insert(key.as_slice(), value)?; + Ok(()) + } + + fn store_vset_epoch(&self, epoch: &VsetEpoch) -> Result<()> { + let key = session_key(epoch.start_session_id); + let value = tl_proto::serialize(StoredVsetEpoch::wrap(epoch)); + self.inner.db.vset_epochs.insert(key.as_slice(), value)?; + Ok(()) + } + + fn store_session_meta(&self, meta: &SessionMeta) -> Result<()> { + let key = session_meta_key(meta.epoch_start_session_id, meta.session_id); + let value = tl_proto::serialize(StoredSessionMeta::wrap(meta)); + self.inner.db.session_meta.insert(key.as_slice(), value)?; + Ok(()) + } + + // todo: should we clean after each batch or just after session + fn clear_intermediate_data( + &self, + epoch: &VsetEpoch, + session_id: &ValidationSessionId, + ) -> Result<()> { + self.delete_session_report(*session_id)?; + self.delete_vset_report(epoch.start_session_id)?; + Ok(()) + } + + fn delete_session_report(&self, session_id: ValidationSessionId) -> Result<()> { + self.delete_by_key( + &self.inner.db.session_reports.cf(), + session_key(session_id).as_slice(), + self.inner.db.session_reports.write_config(), + ) + } + + fn delete_vset_report(&self, epoch_start_session_id: ValidationSessionId) -> Result<()> { + self.delete_by_key( + &self.inner.db.vset_reports.cf(), + session_key(epoch_start_session_id).as_slice(), + self.inner.db.vset_reports.write_config(), + ) + } + + fn delete_by_key( + &self, + cf: &impl weedb::rocksdb::AsColumnFamilyRef, + key: &[u8], + write_config: &weedb::rocksdb::WriteOptions, + ) -> Result<()> { + self.inner + .db + .rocksdb() + .delete_cf_opt(cf, key, write_config)?; + Ok(()) + } + + fn load_latest_vset_epoch(&self) -> Result> { + let table = &self.inner.db.vset_epochs; + let read_config = table.new_read_config(); + let cf = table.cf(); + let mut iter = self + .inner + .db + .rocksdb() + .raw_iterator_cf_opt(&cf, read_config); + iter.seek_to_last(); + + let epoch = match iter.item() { + Some((key, value)) => Some(parse_vset_epoch(key, value)?), + None => { + iter.status()?; + None + } + }; + Ok(epoch) + } + + fn find_epoch_for_session(&self, session_id: ValidationSessionId) -> Result> { + let table = &self.inner.db.vset_epochs; + let read_config = table.new_read_config(); + let cf = table.cf(); + let mut iter = self + .inner + .db + .rocksdb() + .raw_iterator_cf_opt(&cf, read_config); + let key = session_key(session_id); + iter.seek_for_prev(key.as_slice()); + + let epoch = match iter.item() { + Some((key, value)) => Some(parse_vset_epoch(key, value)?), + None => { + iter.status()?; + None + } + }; + Ok(epoch) + } +} + +struct Inner { + db: SlasherDb, +} + +#[derive(Clone)] +pub struct SlasherStorageSnapshot { + db: SlasherDb, + snapshot: Arc, +} + +impl SlasherStorageSnapshot { + pub fn load_closed_vset_epochs(&self) -> Result> { + let table = &self.db.vset_epochs; + let mut read_config = table.new_read_config(); + read_config.set_snapshot(self.snapshot.as_ref()); + + let cf = table.cf(); + let mut iter = self.db.rocksdb().raw_iterator_cf_opt(&cf, read_config); + iter.seek_to_first(); + + let mut items = Vec::new(); + while let Some((key, value)) = iter.item() { + let epoch = parse_vset_epoch(key, value)?; + if epoch.next_epoch_start_session_id.is_some() { + items.push(epoch); + } + iter.next(); + } + iter.status()?; + + Ok(items) + } + + pub fn load_sessions_for_epoch( + &self, + epoch_start_session_id: ValidationSessionId, + ) -> Result> { + let table = &self.db.session_meta; + let mut read_config = table.new_read_config(); + read_config.set_snapshot(self.snapshot.as_ref()); + + let prefix = session_key(epoch_start_session_id); + read_config.set_iterate_lower_bound(prefix.as_slice()); + + let cf = table.cf(); + let mut iter = self.db.rocksdb().raw_iterator_cf_opt(&cf, read_config); + iter.seek(prefix.as_slice()); + + let mut items = Vec::new(); + while let Some((key, value)) = iter.item() { + if &key[..tables::VsetEpochs::KEY_LEN] != prefix.as_slice() { + break; + } + + items.push(parse_session_meta(key, value)?); + iter.next(); + } + iter.status()?; + + Ok(items) + } + + pub fn load_observed_batches_for_session( + &self, + session_id: ValidationSessionId, + ) -> Result> { + let table = &self.db.block_batches; + let mut read_config = table.new_read_config(); + read_config.set_snapshot(self.snapshot.as_ref()); + + let prefix = session_key(session_id); + read_config.set_iterate_lower_bound(prefix.as_slice()); + + let cf = table.cf(); + let mut iter = self.db.rocksdb().raw_iterator_cf_opt(&cf, read_config); + iter.seek(prefix.as_slice()); + + let mut items = Vec::new(); + while let Some((key, value)) = iter.item() { + if &key[..tables::SessionReports::KEY_LEN] != prefix.as_slice() { + break; + } + + let batch = tl_proto::deserialize::(value) + .context("failed to deserialize slasher blocks batch")? + .0; + items.push(ObservedBlocksBatch { + observer_validator_idx: parse_observer_validator_idx(key), + batch, + }); + iter.next(); + } + iter.status()?; + + Ok(items) + } + + pub fn load_session_report( + &self, + session_id: ValidationSessionId, + ) -> Result> { + let table = &self.db.session_reports; + let mut read_config = table.new_read_config(); + read_config.set_snapshot(self.snapshot.as_ref()); + + let key = session_key(session_id); + let Some(value) = + self.db + .rocksdb() + .get_pinned_cf_opt(&table.cf(), key.as_slice(), &read_config)? + else { + return Ok(None); + }; + + let report = tl_proto::deserialize::(value.as_ref()) + .context("failed to deserialize slasher session report")? + .0; + Ok(Some(report)) + } + + pub fn load_vset_report( + &self, + epoch_start_session_id: ValidationSessionId, + ) -> Result> { + let table = &self.db.vset_reports; + let mut read_config = table.new_read_config(); + read_config.set_snapshot(self.snapshot.as_ref()); + + let key = session_key(epoch_start_session_id); + let Some(value) = + self.db + .rocksdb() + .get_pinned_cf_opt(&table.cf(), key.as_slice(), &read_config)? + else { + return Ok(None); + }; + + let report = tl_proto::deserialize::(value.as_ref()) + .context("failed to deserialize slasher vset report")? + .0; + Ok(Some(report)) + } +} + +fn session_key(session_id: ValidationSessionId) -> [u8; tables::SessionReports::KEY_LEN] { + let mut key = [0u8; tables::SessionReports::KEY_LEN]; + key[..4].copy_from_slice(&session_id.catchain_seqno.to_be_bytes()); + key[4..8].copy_from_slice(&session_id.vset_switch_round.to_be_bytes()); + key +} + +fn session_meta_key( + epoch_start_session_id: ValidationSessionId, + session_id: ValidationSessionId, +) -> [u8; tables::SessionMeta::KEY_LEN] { + let mut key = [0u8; tables::SessionMeta::KEY_LEN]; + key[..8].copy_from_slice(&session_key(epoch_start_session_id)); + key[8..12].copy_from_slice(&session_id.catchain_seqno.to_be_bytes()); + key[12..16].copy_from_slice(&session_id.vset_switch_round.to_be_bytes()); + key +} + +fn block_batches_key( + session_id: ValidationSessionId, + observer_validator_idx: u16, + start_seqno: u32, +) -> [u8; tables::BlockBatches::KEY_LEN] { + let mut key = [0u8; tables::BlockBatches::KEY_LEN]; + key[..8].copy_from_slice(&session_key(session_id)); + key[8..10].copy_from_slice(&observer_validator_idx.to_be_bytes()); + key[10..14].copy_from_slice(&start_seqno.to_be_bytes()); + key +} + +fn parse_session_id_prefix(key: &[u8]) -> ValidationSessionId { + ValidationSessionId { + catchain_seqno: u32::from_be_bytes(key[..4].try_into().unwrap()), + vset_switch_round: u32::from_be_bytes(key[4..8].try_into().unwrap()), + } +} + +fn parse_observer_validator_idx(key: &[u8]) -> u16 { + u16::from_be_bytes(key[8..10].try_into().unwrap()) +} + +fn parse_vset_epoch(key: &[u8], value: &[u8]) -> Result { + let mut epoch = tl_proto::deserialize::(value) + .context("failed to deserialize slasher vset epoch")? + .0; + epoch.start_session_id = parse_session_id_prefix(key); + Ok(epoch) +} + +fn parse_session_meta(key: &[u8], value: &[u8]) -> Result { + let mut meta = tl_proto::deserialize::(value) + .context("failed to deserialize slasher session meta")? + .0; + meta.epoch_start_session_id = parse_session_id_prefix(&key[..8]); + meta.session_id = ValidationSessionId { + catchain_seqno: u32::from_be_bytes(key[8..12].try_into().unwrap()), + vset_switch_round: u32::from_be_bytes(key[12..16].try_into().unwrap()), + }; + Ok(meta) +} diff --git a/slasher/src/storage/models.rs b/slasher/src/storage/models.rs new file mode 100644 index 0000000000..83d9cd988e --- /dev/null +++ b/slasher/src/storage/models.rs @@ -0,0 +1,445 @@ +use tl_proto::{TlError, TlPacket, TlRead, TlResult, TlWrite}; +use tycho_slasher_traits::{AnchorPeerStats, ValidationSessionId}; +use tycho_types::cell::HashBytes; +use tycho_util::FastHashSet; + +use crate::analyzer::{ + SessionMeta, SessionPenaltyReport, SessionValidatorScore, VsetEpoch, VsetPenaltyReport, + VsetValidatorPenalty, +}; +use crate::bc::AnchorStatsHistory; +use crate::util::BitSet; +use crate::{BlocksBatch, SignatureHistory}; + +#[repr(transparent)] +pub struct StoredBlocksBatch(pub BlocksBatch); + +impl StoredBlocksBatch { + pub const TL_ID: u32 = tl_proto::id!("slasher.blocksBatch", scheme = "proto.tl"); + + const MAX_SAFE_COMMITTED_BLOCKS: usize = 500; + const MAX_SAFE_HISTORY_COUNT: usize = 1000; + + #[inline] + pub const fn wrap(inner: &BlocksBatch) -> &Self { + // SAFETY: `StoredBlocksBatch` has the same layout as `BlocksBatch`. + unsafe { &*(inner as *const BlocksBatch).cast::() } + } +} + +impl TlWrite for StoredBlocksBatch { + type Repr = tl_proto::Boxed; + + fn max_size_hint(&self) -> usize { + 4 + 4 + + (4 + 4) + + self.0.committed_blocks.max_size_hint() + + 4 + + (self.0.signatures_history) + .iter() + .map(|item| 4 + item.bits.max_size_hint()) + .sum::() + + 4 + + (4 + 4) * self.0.anchor_stats_history.len() + } + + fn write_to(&self, packet: &mut P) { + packet.write_u32(Self::TL_ID); + packet.write_u32(self.0.start_seqno); + if let Some(anchor_range) = self.0.anchor_range.as_ref() { + packet.write_u32(*anchor_range.start()); + packet.write_u32(*anchor_range.end()); + } else { + packet.write_u32(0); + packet.write_u32(0); + }; + + self.0.committed_blocks.write_to(packet); + packet.write_u32(self.0.signatures_history.len() as u32); + for item in &self.0.signatures_history { + packet.write_u32(item.validator_idx as u32); + item.bits.write_to(packet); + } + + assert_eq!( + self.0.signatures_history.len(), + self.0.anchor_stats_history.len(), + "signature history and anchor stats must have the same length" + ); + packet.write_u32(self.0.anchor_stats_history.len() as u32); + for entry in &self.0.anchor_stats_history { + packet.write_u32(entry.validator_idx as u32); + packet.write_u32(entry.stats.points_proven as u32); + } + } +} + +impl<'tl> TlRead<'tl> for StoredBlocksBatch { + type Repr = tl_proto::Boxed; + + fn read_from(packet: &mut &'tl [u8]) -> TlResult { + if u32::read_from(packet)? != Self::TL_ID { + return Err(TlError::UnknownConstructor); + } + + let start_seqno = u32::read_from(packet)?; + let anchor_range = Some(u32::read_from(packet)?..=u32::read_from(packet)?) + .filter(|range| *range.end() > 0); + + let committed_blocks = BitSet::read_from(packet)?; + let block_count = committed_blocks.len(); + if start_seqno.checked_add(block_count as u32).is_none() { + return Err(TlError::InvalidData); + } + + let history_count = u32::read_from(packet)? as usize; + if history_count > Self::MAX_SAFE_HISTORY_COUNT + || block_count > Self::MAX_SAFE_COMMITTED_BLOCKS + { + return Err(TlError::InvalidData); + } + + let mut signatures_history = Vec::with_capacity(history_count); + let mut unique_indices = + FastHashSet::with_capacity_and_hasher(history_count, Default::default()); + for _ in 0..history_count { + let Ok(validator_idx) = u16::try_from(u32::read_from(packet)?) else { + return Err(TlError::InvalidData); + }; + if !unique_indices.insert(validator_idx) { + return Err(TlError::InvalidData); + } + let bits = BitSet::read_from(packet)?; + if bits.len() != block_count * 2 { + return Err(TlError::InvalidData); + } + signatures_history.push(SignatureHistory { + validator_idx, + bits, + }); + } + + if u32::read_from(packet)? as usize != history_count { + return Err(TlError::InvalidData); + } + let mut anchor_stats_history = Vec::with_capacity(history_count); + for _ in 0..history_count { + let validator_idx = + u16::try_from(u32::read_from(packet)?).map_err(|_e| TlError::InvalidData)?; + let points_proven = + u16::try_from(u32::read_from(packet)?).map_err(|_e| TlError::InvalidData)?; + anchor_stats_history.push(AnchorStatsHistory { + validator_idx, + stats: AnchorPeerStats { points_proven }, + }); + } + + Ok(Self(BlocksBatch { + start_seqno, + anchor_range, + committed_blocks, + signatures_history: signatures_history.into_boxed_slice(), + anchor_stats_history: anchor_stats_history.into_boxed_slice(), + })) + } +} + +#[repr(transparent)] +pub struct StoredVsetEpoch(pub VsetEpoch); + +impl StoredVsetEpoch { + pub const TL_ID: u32 = tl_proto::id!("slasher.vsetEpoch", scheme = "proto.tl"); + + #[inline] + pub const fn wrap(inner: &VsetEpoch) -> &Self { + // SAFETY: `StoredVsetEpoch` has the same layout as `VsetEpoch`. + unsafe { &*(inner as *const VsetEpoch).cast::() } + } +} + +impl TlWrite for StoredVsetEpoch { + type Repr = tl_proto::Boxed; + + fn max_size_hint(&self) -> usize { + 4 + 32 + 4 + 4 + 4 + } + + fn write_to(&self, packet: &mut P) { + packet.write_u32(Self::TL_ID); + packet.write_raw_slice(&self.0.vset_hash.0); + packet.write_u32(u32::from(self.0.next_epoch_start_session_id.is_some())); + let next_session_id = self + .0 + .next_epoch_start_session_id + .unwrap_or(ValidationSessionId { + catchain_seqno: 0, + vset_switch_round: 0, + }); + packet.write_u32(next_session_id.catchain_seqno); + packet.write_u32(next_session_id.vset_switch_round); + } +} + +impl<'tl> TlRead<'tl> for StoredVsetEpoch { + type Repr = tl_proto::Boxed; + + fn read_from(packet: &mut &'tl [u8]) -> TlResult { + if u32::read_from(packet)? != Self::TL_ID { + return Err(TlError::UnknownConstructor); + } + + let vset_hash = read_hash_bytes(packet)?; + let has_next_epoch = u32::read_from(packet)? != 0; + let next_session_id = ValidationSessionId { + catchain_seqno: u32::read_from(packet)?, + vset_switch_round: u32::read_from(packet)?, + }; + + Ok(Self(VsetEpoch { + start_session_id: ValidationSessionId { + catchain_seqno: 0, + vset_switch_round: 0, + }, + vset_hash, + next_epoch_start_session_id: has_next_epoch.then_some(next_session_id), + })) + } +} + +#[repr(transparent)] +pub struct StoredSessionMeta(pub SessionMeta); + +impl StoredSessionMeta { + pub const TL_ID: u32 = tl_proto::id!("slasher.sessionMeta", scheme = "proto.tl"); + + #[inline] + pub const fn wrap(inner: &SessionMeta) -> &Self { + // SAFETY: `StoredSessionMeta` has the same layout as `SessionMeta`. + unsafe { &*(inner as *const SessionMeta).cast::() } + } +} + +impl TlWrite for StoredSessionMeta { + type Repr = tl_proto::Boxed; + + fn max_size_hint(&self) -> usize { + 4 + 4 + self.0.validator_indices.len() * 4 + } + + fn write_to(&self, packet: &mut P) { + packet.write_u32(Self::TL_ID); + packet.write_u32(self.0.validator_indices.len() as u32); + for validator_idx in &self.0.validator_indices { + packet.write_u32(u32::from(*validator_idx)); + } + } +} + +impl<'tl> TlRead<'tl> for StoredSessionMeta { + type Repr = tl_proto::Boxed; + + fn read_from(packet: &mut &'tl [u8]) -> TlResult { + if u32::read_from(packet)? != Self::TL_ID { + return Err(TlError::UnknownConstructor); + } + + let validator_count = u32::read_from(packet)? as usize; + let mut validator_indices = Vec::with_capacity(validator_count); + let mut unique_indices = + FastHashSet::with_capacity_and_hasher(validator_count, Default::default()); + for _ in 0..validator_count { + let Ok(validator_idx) = u16::try_from(u32::read_from(packet)?) else { + return Err(TlError::InvalidData); + }; + if !unique_indices.insert(validator_idx) { + return Err(TlError::InvalidData); + } + validator_indices.push(validator_idx); + } + + Ok(Self(SessionMeta { + session_id: ValidationSessionId { + catchain_seqno: 0, + vset_switch_round: 0, + }, + epoch_start_session_id: ValidationSessionId { + catchain_seqno: 0, + vset_switch_round: 0, + }, + validator_indices, + })) + } +} + +#[repr(transparent)] +pub struct StoredSessionPenaltyReport(pub SessionPenaltyReport); + +impl StoredSessionPenaltyReport { + pub const TL_ID: u32 = tl_proto::id!("slasher.sessionPenaltyReport", scheme = "proto.tl"); + + #[inline] + pub const fn wrap(inner: &SessionPenaltyReport) -> &Self { + // SAFETY: `StoredSessionPenaltyReport` has the same layout as `SessionPenaltyReport`. + unsafe { &*(inner as *const SessionPenaltyReport).cast::() } + } +} + +impl TlWrite for StoredSessionPenaltyReport { + type Repr = tl_proto::Boxed; + + fn max_size_hint(&self) -> usize { + 4 + 4 + 4 + 4 + 4 + 4 + self.0.validators.len() * (4 + 8 + 8 + 4) + } + + fn write_to(&self, packet: &mut P) { + packet.write_u32(Self::TL_ID); + packet.write_u32(self.0.session_id.catchain_seqno); + packet.write_u32(self.0.session_id.vset_switch_round); + packet.write_u32(self.0.epoch_start_session_id.catchain_seqno); + packet.write_u32(self.0.epoch_start_session_id.vset_switch_round); + packet.write_u32(self.0.session_weight); + packet.write_u32(self.0.validators.len() as u32); + for item in &self.0.validators { + packet.write_u32(u32::from(item.validator_idx)); + packet.write_u64(item.earned_points); + packet.write_u64(item.max_points); + packet.write_u32(u32::from(item.is_bad)); + } + } +} + +impl<'tl> TlRead<'tl> for StoredSessionPenaltyReport { + type Repr = tl_proto::Boxed; + + fn read_from(packet: &mut &'tl [u8]) -> TlResult { + if u32::read_from(packet)? != Self::TL_ID { + return Err(TlError::UnknownConstructor); + } + + let session_id = ValidationSessionId { + catchain_seqno: u32::read_from(packet)?, + vset_switch_round: u32::read_from(packet)?, + }; + let epoch_start_session_id = ValidationSessionId { + catchain_seqno: u32::read_from(packet)?, + vset_switch_round: u32::read_from(packet)?, + }; + let session_weight = u32::read_from(packet)?; + let validator_count = u32::read_from(packet)? as usize; + + let mut validators = Vec::with_capacity(validator_count); + let mut unique_indices = + FastHashSet::with_capacity_and_hasher(validator_count, Default::default()); + for _ in 0..validator_count { + let Ok(validator_idx) = u16::try_from(u32::read_from(packet)?) else { + return Err(TlError::InvalidData); + }; + if !unique_indices.insert(validator_idx) { + return Err(TlError::InvalidData); + } + + validators.push(SessionValidatorScore { + validator_idx, + earned_points: u64::read_from(packet)?, + max_points: u64::read_from(packet)?, + is_bad: u32::read_from(packet)? != 0, + }); + } + + Ok(Self(SessionPenaltyReport { + session_id, + epoch_start_session_id, + session_weight, + validators: validators.into_boxed_slice(), + })) + } +} + +#[repr(transparent)] +pub struct StoredVsetPenaltyReport(pub VsetPenaltyReport); + +impl StoredVsetPenaltyReport { + pub const TL_ID: u32 = tl_proto::id!("slasher.vsetPenaltyReport", scheme = "proto.tl"); + + #[inline] + pub const fn wrap(inner: &VsetPenaltyReport) -> &Self { + // SAFETY: `StoredVsetPenaltyReport` has the same layout as `VsetPenaltyReport`. + unsafe { &*(inner as *const VsetPenaltyReport).cast::() } + } +} + +impl TlWrite for StoredVsetPenaltyReport { + type Repr = tl_proto::Boxed; + + fn max_size_hint(&self) -> usize { + 4 + 4 + 4 + 32 + 4 + self.0.validators.len() * (4 + 8 + 8 + 4) + } + + fn write_to(&self, packet: &mut P) { + packet.write_u32(Self::TL_ID); + packet.write_u32(self.0.epoch_start_session_id.catchain_seqno); + packet.write_u32(self.0.epoch_start_session_id.vset_switch_round); + packet.write_raw_slice(&self.0.vset_hash.0); + packet.write_u32(self.0.validators.len() as u32); + for item in &self.0.validators { + packet.write_u32(u32::from(item.validator_idx)); + packet.write_u64(item.bad_sessions_weight); + packet.write_u64(item.total_sessions_weight); + packet.write_u32(u32::from(item.is_bad)); + } + } +} + +impl<'tl> TlRead<'tl> for StoredVsetPenaltyReport { + type Repr = tl_proto::Boxed; + + fn read_from(packet: &mut &'tl [u8]) -> TlResult { + if u32::read_from(packet)? != Self::TL_ID { + return Err(TlError::UnknownConstructor); + } + + let epoch_start_session_id = ValidationSessionId { + catchain_seqno: u32::read_from(packet)?, + vset_switch_round: u32::read_from(packet)?, + }; + let vset_hash = read_hash_bytes(packet)?; + let validator_count = u32::read_from(packet)? as usize; + + let mut validators = Vec::with_capacity(validator_count); + let mut unique_indices = + FastHashSet::with_capacity_and_hasher(validator_count, Default::default()); + for _ in 0..validator_count { + let Ok(validator_idx) = u16::try_from(u32::read_from(packet)?) else { + return Err(TlError::InvalidData); + }; + if !unique_indices.insert(validator_idx) { + return Err(TlError::InvalidData); + } + + validators.push(VsetValidatorPenalty { + validator_idx, + bad_sessions_weight: u64::read_from(packet)?, + total_sessions_weight: u64::read_from(packet)?, + is_bad: u32::read_from(packet)? != 0, + }); + } + + Ok(Self(VsetPenaltyReport { + epoch_start_session_id, + vset_hash, + validators: validators.into_boxed_slice(), + })) + } +} + +fn read_hash_bytes(packet: &mut &[u8]) -> TlResult { + if packet.len() < size_of::() { + return Err(TlError::UnexpectedEof); + } + + let (hash, tail) = packet.split_at(size_of::()); + *packet = tail; + let mut bytes = [0; size_of::()]; + bytes.copy_from_slice(hash); + Ok(HashBytes(bytes)) +} diff --git a/slasher/src/tracing_targets.rs b/slasher/src/tracing_targets.rs new file mode 100644 index 0000000000..afd2daf608 --- /dev/null +++ b/slasher/src/tracing_targets.rs @@ -0,0 +1 @@ +pub const SLASHER: &str = "slasher"; diff --git a/slasher/src/util.rs b/slasher/src/util.rs new file mode 100644 index 0000000000..300b801025 --- /dev/null +++ b/slasher/src/util.rs @@ -0,0 +1,362 @@ +use std::ptr::NonNull; +use std::sync::atomic::{AtomicU64, Ordering}; + +use tl_proto::{TlError, TlRead, TlResult, TlWrite}; +use tycho_slasher_traits::ValidationSessionId; +use tycho_types::prelude::*; + +// === AtomicValidationSessionId === + +pub struct AtomicValidationSessionId(AtomicU64); + +impl AtomicValidationSessionId { + pub const fn new(value: ValidationSessionId) -> Self { + Self(AtomicU64::new(Self::pack_id(value))) + } + + pub fn set(&self, value: ValidationSessionId) { + self.0.store(Self::pack_id(value), Ordering::Release); + } + + pub fn load(&self) -> ValidationSessionId { + Self::unpack_id(self.0.load(Ordering::Acquire)) + } + + #[inline] + const fn pack_id(value: ValidationSessionId) -> u64 { + const _: () = const { + let id = ValidationSessionId { + catchain_seqno: 0, + vset_switch_round: 0, + }; + assert!(std::mem::size_of_val(&id.catchain_seqno) == 4); + assert!(std::mem::size_of_val(&id.vset_switch_round) == 4); + }; + + ((value.catchain_seqno as u64) << 32) | (value.vset_switch_round as u64) + } + + #[inline] + const fn unpack_id(value: u64) -> ValidationSessionId { + ValidationSessionId { + catchain_seqno: (value >> 32) as u32, + vset_switch_round: value as u32, + } + } +} + +// === BitSet === + +pub struct BitSet { + data: Option>, + length: usize, +} + +unsafe impl Send for BitSet {} +unsafe impl Sync for BitSet {} + +impl BitSet { + pub const EMPTY: Self = Self { + data: None, + length: 0, + }; + + pub const BLOCK_BITS: usize = std::mem::size_of::() * 8; + + pub fn with_capacity(bits: usize) -> Self { + if bits == 0 { + return Self::EMPTY; + } + + let data = Vec::::into_boxed_slice(vec![0; block_count(bits)]); + + Self { + data: Some(unsafe { NonNull::new_unchecked(Box::into_raw(data)).cast() }), + length: bits, + } + } + + pub fn load_from_cs( + bits: usize, + cs: &mut CellSlice<'_>, + ) -> Result { + if bits == 0 { + return Ok(Self::EMPTY); + } + if bits > tycho_types::cell::MAX_BIT_LEN as usize { + return Err(tycho_types::error::Error::CellUnderflow); + } + let mut buffer = [0u8; 128]; + let bytes = cs.load_raw(&mut buffer, bits as u16)?; + debug_assert_eq!(bytes.len(), bits.div_ceil(8)); + + let (chunks, tail) = bytes.as_chunks::<{ Self::BLOCK_BITS / 8 }>(); + + let mut data: Vec = vec![0; block_count(bits)]; + for (data, chunk) in std::iter::zip(&mut data, chunks) { + *data = Block::from_be_bytes(*chunk).reverse_bits(); + } + if let Some(data) = data.last_mut() + && !tail.is_empty() + { + let mut buffer = [0u8; Self::BLOCK_BITS / 8]; + buffer[0..tail.len()].copy_from_slice(tail); + *data = Block::from_be_bytes(buffer).reverse_bits(); + } + + Ok(Self { + data: Some(unsafe { + NonNull::new_unchecked(Box::into_raw(data.into_boxed_slice())).cast() + }), + length: bits, + }) + } + + pub fn len(&self) -> usize { + self.length + } + + pub fn is_zero(&self) -> bool { + self.as_slice().iter().all(|item| *item == 0) + } + + pub fn get(&self, bit: usize) -> bool { + assert!( + bit < self.length, + "get at index {bit} exceeds bitset size {}", + self.length + ); + + let Some(data) = self.data else { + return false; + }; + + let block = bit / Self::BLOCK_BITS; + let rem = bit % Self::BLOCK_BITS; + + // SAFETY: `bit` is whithin the range. + unsafe { (*data.as_ptr().add(block) & (1 << rem)) != 0 } + } + + pub fn set(&mut self, bit: usize, enabled: bool) { + assert!( + bit < self.length, + "set at index {bit} exceeds bitset size {}", + self.length + ); + + // SAFETY: `bit` is whithin the range. + unsafe { self.set_unchecked(bit, enabled) } + } + + unsafe fn set_unchecked(&mut self, bit: usize, enabled: bool) { + let Some(data) = self.data else { + return; + }; + + let block = bit / Self::BLOCK_BITS; + let rem = bit % Self::BLOCK_BITS; + + let block = unsafe { &mut *data.as_ptr().add(block) }; + if enabled { + *block |= 1 << rem; + } else { + *block &= !(1 << rem); + } + } + + pub fn as_slice(&self) -> &[Block] { + match self.data { + Some(data) => { + // SAFETY: Data was allocated for this exact block count. + unsafe { std::slice::from_raw_parts(data.as_ptr(), block_count(self.length)) } + } + None => &[], + } + } +} + +impl std::fmt::Debug for BitSet { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut remaining_bits = self.length; + for block in self.as_slice() { + let bits = std::cmp::min(remaining_bits, Self::BLOCK_BITS); + remaining_bits -= bits; + + let block = block.reverse_bits() >> (Self::BLOCK_BITS - bits); + write!(f, "{block:0bits$b}")?; + } + Ok(()) + } +} + +impl Eq for BitSet {} +impl PartialEq for BitSet { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.length == other.length && self.as_slice() == other.as_slice() + } +} + +impl Drop for BitSet { + fn drop(&mut self) { + if let Some(data) = self.data { + drop(unsafe { + Box::<[Block]>::from_raw(std::ptr::slice_from_raw_parts_mut( + data.as_ptr(), + block_count(self.length), + )) + }); + } + } +} + +impl Store for BitSet { + fn store_into( + &self, + b: &mut CellBuilder, + _: &dyn CellContext, + ) -> Result<(), tycho_types::error::Error> { + let Ok::(mut remaining_bits) = self.length.try_into() else { + return Err(tycho_types::error::Error::CellOverflow); + }; + + for block in self.as_slice() { + let bits = std::cmp::min(remaining_bits, Self::BLOCK_BITS as u16); + remaining_bits -= bits; + + let block = block.reverse_bits() >> (Self::BLOCK_BITS - bits as usize); + b.store_uint(block, bits)?; + } + + Ok(()) + } +} + +impl TlWrite for BitSet { + type Repr = tl_proto::Bare; + + fn max_size_hint(&self) -> usize { + 4 + tl_proto::bytes_max_size_hint(std::mem::size_of_val(self.as_slice())) + } + + fn write_to

(&self, packet: &mut P) + where + P: tl_proto::TlPacket, + { + packet.write_u32(self.length as u32); + + let bytes = match self.data { + Some(data) => unsafe { + std::slice::from_raw_parts( + data.as_ptr().cast::(), + block_count(self.length) * std::mem::size_of::(), + ) + }, + None => &[], + }; + <&[u8] as TlWrite>::write_to(&bytes, packet); + } +} + +impl<'tl> TlRead<'tl> for BitSet { + type Repr = tl_proto::Bare; + + fn read_from(packet: &mut &'tl [u8]) -> TlResult { + let length = u32::read_from(packet)? as usize; + let bytes = <&[u8]>::read_from(packet)?; + + let block_count = block_count(length); + let Some(expected_byte_count) = block_count.checked_mul(std::mem::size_of::()) + else { + return Err(TlError::InvalidData); + }; + if expected_byte_count != bytes.len() { + return Err(TlError::InvalidData); + } + + if block_count == 0 { + return Ok(Self::EMPTY); + } + + let mut data = Box::<[Block]>::new_uninit_slice(block_count); + debug_assert_eq!( + data.len() * std::mem::size_of::(), + expected_byte_count + ); + + // SAFETY: `data` has the exact same number of bytes allocated. + unsafe { + std::ptr::copy_nonoverlapping( + bytes.as_ptr(), + data.as_mut_ptr().cast::(), + expected_byte_count, + ); + } + + Ok(Self { + // SAFETY: We are constructing a non-null pointer right out of the `Box`. + data: Some(unsafe { NonNull::new_unchecked(Box::into_raw(data).cast::()) }), + length, + }) + } +} + +fn block_count(bits: usize) -> usize { + bits.div_ceil(BitSet::BLOCK_BITS) +} + +type Block = u64; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn bitset_cell_repr() { + // Empty bitset + let cell = CellBuilder::build_from(BitSet::EMPTY).unwrap(); + assert!(cell.is_empty()); + let parsed = BitSet::load_from_cs(0, &mut cell.as_slice().unwrap()).unwrap(); + assert_eq!(BitSet::EMPTY, parsed); + + // Not-empty bitset + let mut bitset = BitSet::with_capacity(100); + for i in 0..bitset.len() { + // Some random but distinct pattern to catch alignment bugs. + if i % 7 == 0 || (50..=90).contains(&i) { + bitset.set(i, true); + } + } + + let cell = CellBuilder::build_from(&bitset).unwrap(); + assert_eq!(cell.bit_len() as usize, bitset.len()); + + let parsed = BitSet::load_from_cs(100, &mut cell.as_slice().unwrap()).unwrap(); + assert_eq!(bitset, parsed); + } + + #[test] + fn bitset_tl_repr() { + // Empty bitset + let stored = tl_proto::serialize(&BitSet::EMPTY); + let parsed = tl_proto::deserialize::(&stored).unwrap(); + assert_eq!(BitSet::EMPTY, parsed); + + // Not-empty bitset + let mut bitset = BitSet::with_capacity(100); + for i in 0..bitset.len() { + // Some random but distinct pattern to catch alignment bugs. + if i % 7 == 0 || (50..=90).contains(&i) { + bitset.set(i, true); + } + } + + println!("{bitset:?}"); + + let stored = tl_proto::serialize(&bitset); + let parsed = tl_proto::deserialize::(&stored).unwrap(); + assert_eq!(bitset, parsed); + } +}