From 4376be886d9e903489e6994afa924cbb4e545887 Mon Sep 17 00:00:00 2001 From: Pessina Date: Wed, 10 Sep 2025 20:17:47 +0500 Subject: [PATCH 01/32] Basic Adder pallet added --- Cargo.lock | 1 + Cargo.toml | 2 + pallets/adder-felipe/Cargo.toml | 46 ++++++++++ pallets/adder-felipe/src/lib.rs | 145 ++++++++++++++++++++++++++++++++ runtime/hydradx/Cargo.toml | 4 + runtime/hydradx/src/assets.rs | 9 ++ runtime/hydradx/src/lib.rs | 2 + 7 files changed, 209 insertions(+) create mode 100644 pallets/adder-felipe/Cargo.toml create mode 100644 pallets/adder-felipe/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 37d76fc81a..cdcce73ef9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5582,6 +5582,7 @@ dependencies = [ "orml-xcm", "orml-xcm-support", "orml-xtokens", + "pallet-adder-felipe", "pallet-asset-registry", "pallet-aura", "pallet-authorship", diff --git a/Cargo.toml b/Cargo.toml index 384fadc09b..47ee2bfe90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ members = [ 'pallets/broadcast', 'liquidation-worker-support', 'pallets/hsm', + 'pallets/adder-felipe', ] resolver = "2" @@ -157,6 +158,7 @@ pallet-liquidation = { path = "pallets/liquidation", default-features = false } pallet-broadcast = { path = "pallets/broadcast", default-features = false } liquidation-worker-support = { path = "liquidation-worker-support", default-features = false } pallet-hsm = { path = "pallets/hsm", default-features = false } +pallet-adder-felipe = { path = "pallets/adder-felipe", default-features = false } pallet-parameters = { path = "pallets/parameters", default-features = false } hydra-dx-build-script-utils = { path = "utils/build-script-utils", default-features = false } diff --git a/pallets/adder-felipe/Cargo.toml b/pallets/adder-felipe/Cargo.toml new file mode 100644 index 0000000000..6dbc1f378d --- /dev/null +++ b/pallets/adder-felipe/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "pallet-adder-felipe" +version = "1.0.0" +description = "Simple adder pallet for demonstration" +authors = ["GalacticCouncil"] +edition = "2021" +homepage = "https://github.com/galacticcouncil/hydration-node" +license = "Apache 2.0" +repository = "https://github.com/galacticcouncil/hydration-node" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true } +scale-info = { workspace = true } + +# Substrate dependencies +sp-std = { workspace = true } +sp-runtime = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } + +# Optionals +frame-benchmarking = { workspace = true, optional = true } + +[dev-dependencies] +sp-io = { workspace = true } +sp-core = { workspace = true } + +[features] +default = ["std"] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-system/runtime-benchmarks", + "frame-support/runtime-benchmarks", +] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "sp-std/std", + "sp-runtime/std", + "scale-info/std", +] +try-runtime = ["frame-support/try-runtime"] \ No newline at end of file diff --git a/pallets/adder-felipe/src/lib.rs b/pallets/adder-felipe/src/lib.rs new file mode 100644 index 0000000000..26f8aec50b --- /dev/null +++ b/pallets/adder-felipe/src/lib.rs @@ -0,0 +1,145 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +// Every pallet uses the `pub use pallet::*` pattern +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use sp_std::vec::Vec; + + /// Configure the pallet by specifying parameters and types it depends on + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Maximum value that can be added (for safety) + #[pallet::constant] + type MaxValue: Get; + } + + /// The pallet's storage items + #[pallet::storage] + #[pallet::getter(fn last_result)] + pub type LastResult = StorageValue<_, u128, ValueQuery>; + + /// Storage for results by account + #[pallet::storage] + #[pallet::getter(fn user_results)] + pub type UserResults = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + u128, + ValueQuery + >; + + /// Pallets use events to inform users about important changes + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Addition performed successfully [who, x, y, result] + AdditionPerformed { + who: T::AccountId, + x: u128, + y: u128, + result: u128, + }, + } + + /// Errors that can occur in the pallet + #[pallet::error] + pub enum Error { + /// Arithmetic overflow occurred + Overflow, + /// Value exceeds maximum allowed + ValueTooLarge, + } + + /// The pallet struct + #[pallet::pallet] + pub struct Pallet(_); + + /// Dispatchable functions (extrinsics) - these can be called from outside + #[pallet::call] + impl Pallet { + /// Add two numbers together + /// + /// This function adds x and y, stores the result, and emits an event + #[pallet::call_index(0)] + #[pallet::weight(Weight::from_parts(10_000, 0))] + pub fn add( + origin: OriginFor, + x: u128, + y: u128, + ) -> DispatchResult { + // Ensure the call is from a signed account + let who = ensure_signed(origin)?; + + // Check values don't exceed maximum + ensure!(x <= T::MaxValue::get(), Error::::ValueTooLarge); + ensure!(y <= T::MaxValue::get(), Error::::ValueTooLarge); + + // Perform addition with overflow check + let result = x.checked_add(y) + .ok_or(Error::::Overflow)?; + + // Store the result + >::put(result); + >::insert(&who, result); + + // Emit an event + Self::deposit_event(Event::AdditionPerformed { + who, + x, + y, + result, + }); + + Ok(()) + } + + /// Add multiple numbers (demonstrating more complex logic) + #[pallet::call_index(1)] + #[pallet::weight(Weight::from_parts(10_000 * numbers.len() as u64, 0))] + pub fn add_multiple( + origin: OriginFor, + numbers: Vec, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + // Use internal helper function + let result = Self::calculate_sum(&numbers)?; + + >::put(result); + >::insert(&who, result); + + Self::deposit_event(Event::AdditionPerformed { + who, + x: 0, // You might emit different event here + y: 0, + result, + }); + + Ok(()) + } + } + + /// Helper functions (not dispatchable) + impl Pallet { + /// Internal function to calculate sum + pub fn calculate_sum(numbers: &[u128]) -> Result> { + numbers.iter().try_fold(0u128, |acc, &num| { + ensure!(num <= T::MaxValue::get(), Error::::ValueTooLarge); + acc.checked_add(num).ok_or(Error::::Overflow) + }) + } + + /// Public read-only function (can be called without transaction) + pub fn get_sum_without_storing(x: u128, y: u128) -> Option { + x.checked_add(y) + } + } +} \ No newline at end of file diff --git a/runtime/hydradx/Cargo.toml b/runtime/hydradx/Cargo.toml index 94d79a7f81..5082fcff6a 100644 --- a/runtime/hydradx/Cargo.toml +++ b/runtime/hydradx/Cargo.toml @@ -63,6 +63,7 @@ pallet-route-executor = { workspace = true } pallet-staking = { workspace = true } pallet-liquidation = { workspace = true } pallet-hsm = { workspace = true } +pallet-adder-felipe = { workspace = true } pallet-parameters = { workspace = true } # pallets @@ -241,6 +242,7 @@ runtime-benchmarks = [ "pallet-lbp/runtime-benchmarks", "pallet-xyk/runtime-benchmarks", "pallet-hsm/runtime-benchmarks", + "pallet-adder-felipe/runtime-benchmarks", "pallet-elections-phragmen/runtime-benchmarks", "pallet-referrals/runtime-benchmarks", "pallet-evm-accounts/runtime-benchmarks", @@ -336,6 +338,7 @@ std = [ "pallet-duster/std", "pallet-duster-rpc-runtime-api/std", "pallet-hsm/std", + "pallet-adder-felipe/std", "pallet-parameters/std", "warehouse-liquidity-mining/std", "sp-api/std", @@ -457,6 +460,7 @@ try-runtime = [ "pallet-evm-chain-id/try-runtime", "pallet-xyk/try-runtime", "pallet-hsm/try-runtime", + "pallet-adder-felipe/try-runtime", "pallet-referrals/try-runtime", "pallet-evm-accounts/try-runtime", "pallet-xyk-liquidity-mining/try-runtime", diff --git a/runtime/hydradx/src/assets.rs b/runtime/hydradx/src/assets.rs index a809766a64..3fd867c818 100644 --- a/runtime/hydradx/src/assets.rs +++ b/runtime/hydradx/src/assets.rs @@ -1832,6 +1832,15 @@ impl pallet_hsm::Config for Runtime { type BenchmarkHelper = helpers::benchmark_helpers::HsmBenchmarkHelper; } +parameter_types! { + pub const AdderMaxValue: u128 = 1_000_000_000_000_000; // 1 million with 9 decimals +} + +impl pallet_adder_felipe::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type MaxValue = AdderMaxValue; +} + pub struct ConvertViaOmnipool(PhantomData); impl Convert for ConvertViaOmnipool where diff --git a/runtime/hydradx/src/lib.rs b/runtime/hydradx/src/lib.rs index 0fe87abb1f..4e3ae6e88e 100644 --- a/runtime/hydradx/src/lib.rs +++ b/runtime/hydradx/src/lib.rs @@ -192,6 +192,7 @@ construct_runtime!( Liquidation: pallet_liquidation = 76, HSM: pallet_hsm = 82, Parameters: pallet_parameters = 83, + AdderFelipe: pallet_adder_felipe = 84, // ORML related modules Tokens: orml_tokens = 77, @@ -341,6 +342,7 @@ mod benches { [pallet_whitelist, Whitelist] [pallet_dispatcher, Dispatcher] [pallet_hsm, HSM] + [pallet_adder_felipe, AdderFelipe] [pallet_dynamic_fees, DynamicFees] [ismp_parachain, IsmpParachain] [pallet_token_gateway, TokenGateway] From f1497a751ee97a2a1815367511cd7647788ec1ff Mon Sep 17 00:00:00 2001 From: Pessina Date: Wed, 10 Sep 2025 22:40:37 +0500 Subject: [PATCH 02/32] Include buildEvmTx --- Cargo.lock | 53 +++++++-- Cargo.toml | 1 + pallets/build-evm-tx/Cargo.toml | 53 +++++++++ pallets/build-evm-tx/src/lib.rs | 195 ++++++++++++++++++++++++++++++++ runtime/hydradx/Cargo.toml | 4 + runtime/hydradx/src/assets.rs | 9 ++ runtime/hydradx/src/lib.rs | 2 + 7 files changed, 310 insertions(+), 7 deletions(-) create mode 100644 pallets/build-evm-tx/Cargo.toml create mode 100644 pallets/build-evm-tx/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index cdcce73ef9..9c2e79429d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3750,7 +3750,7 @@ dependencies = [ "hash-db", "hash256-std-hasher", "parity-scale-codec", - "rlp", + "rlp 0.5.2", "scale-info", "serde", "sha3", @@ -3836,7 +3836,7 @@ dependencies = [ "log", "parity-scale-codec", "primitive-types 0.12.2", - "rlp", + "rlp 0.5.2", "scale-info", "serde", "sha3", @@ -4072,8 +4072,8 @@ dependencies = [ "pallet-evm", "parity-scale-codec", "prometheus", - "rand 0.8.5", - "rlp", + "rand", + "rlp 0.5.2", "sc-client-api", "sc-consensus-aura", "sc-network", @@ -4112,7 +4112,7 @@ dependencies = [ "ethereum", "ethereum-types 0.14.1", "jsonrpsee", - "rlp", + "rlp 0.5.2", "rustc-hex", "serde", "serde_json", @@ -5590,6 +5590,7 @@ dependencies = [ "pallet-balances", "pallet-bonds", "pallet-broadcast", + "pallet-build-evm-tx", "pallet-circuit-breaker", "pallet-claims", "pallet-collator-rewards", @@ -5927,7 +5928,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" dependencies = [ - "rlp", + "rlp 0.5.2", ] [[package]] @@ -8059,7 +8060,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 3.1.0", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 2.0.90", @@ -8885,6 +8886,23 @@ dependencies = [ "sp-runtime", ] +[[package]] +name = "pallet-build-evm-tx" +version = "1.0.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "hex", + "parity-scale-codec", + "scale-info", + "signet-rs", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-child-bounties" version = "37.0.0" @@ -14415,6 +14433,16 @@ dependencies = [ "rustc-hex", ] +[[package]] +name = "rlp" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa24e92bb2a83198bb76d661a71df9f7076b8c420b8696e4d3d97d50d94479e3" +dependencies = [ + "bytes", + "rustc-hex", +] + [[package]] name = "rlp-derive" version = "0.1.0" @@ -16797,6 +16825,17 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "signet-rs" +version = "0.2.5" +source = "git+https://github.com/sig-net/signet.rs?branch=pre-compiled#6945eb902ac6b78c7511611bbc2e71a7c380a880" +dependencies = [ + "hex", + "rlp 0.6.1", + "serde", + "serde_json", +] + [[package]] name = "simba" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index 47ee2bfe90..916f6cc755 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -159,6 +159,7 @@ pallet-broadcast = { path = "pallets/broadcast", default-features = false } liquidation-worker-support = { path = "liquidation-worker-support", default-features = false } pallet-hsm = { path = "pallets/hsm", default-features = false } pallet-adder-felipe = { path = "pallets/adder-felipe", default-features = false } +pallet-build-evm-tx = { path = "pallets/build-evm-tx", default-features = false } pallet-parameters = { path = "pallets/parameters", default-features = false } hydra-dx-build-script-utils = { path = "utils/build-script-utils", default-features = false } diff --git a/pallets/build-evm-tx/Cargo.toml b/pallets/build-evm-tx/Cargo.toml new file mode 100644 index 0000000000..33cc680154 --- /dev/null +++ b/pallets/build-evm-tx/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "pallet-build-evm-tx" +version = "1.0.0" +description = "Pallet for building RLP serialized EVM transactions" +authors = ["GalacticCouncil"] +edition = "2021" +homepage = "https://github.com/galacticcouncil/hydration-node" +license = "Apache 2.0" +repository = "https://github.com/galacticcouncil/hydration-node" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true } +scale-info = { workspace = true } + +# Substrate dependencies +sp-std = { workspace = true } +sp-runtime = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } + +# External dependencies +signet-rs = { git = "https://github.com/sig-net/signet.rs", branch = "pre-compiled", default-features = false, features = ["evm"] } +hex = { version = "0.4", default-features = false, features = ["alloc"] } +sp-io = { workspace = true } + +# Optionals +frame-benchmarking = { workspace = true, optional = true } + +[dev-dependencies] +sp-io = { workspace = true } +sp-core = { workspace = true } + +[features] +default = ["std"] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-system/runtime-benchmarks", + "frame-support/runtime-benchmarks", +] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "sp-std/std", + "sp-runtime/std", + "sp-io/std", + "scale-info/std", + "hex/std", +] +try-runtime = ["frame-support/try-runtime"] \ No newline at end of file diff --git a/pallets/build-evm-tx/src/lib.rs b/pallets/build-evm-tx/src/lib.rs new file mode 100644 index 0000000000..92bff48e83 --- /dev/null +++ b/pallets/build-evm-tx/src/lib.rs @@ -0,0 +1,195 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use sp_std::vec::Vec; + use signet_rs::{TransactionBuilder, TxBuilder, EVM}; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + #[pallet::constant] + type MaxDataLength: Get; + } + + #[pallet::storage] + #[pallet::getter(fn last_built_tx)] + pub type LastBuiltTx = StorageValue<_, BoundedVec, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn tx_by_account)] + pub type TxByAccount = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + BoundedVec, + ValueQuery + >; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + TransactionBuilt { + who: T::AccountId, + tx_hash: [u8; 32], + rlp_length: u32, + }, + } + + #[pallet::error] + pub enum Error { + InvalidAddress, + DataTooLong, + BuildFailed, + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(Weight::from_parts(10_000, 0))] + pub fn build_evm_transaction( + origin: OriginFor, + to_address: Vec, + value: u128, + data: Vec, + nonce: u64, + gas_limit: u128, + max_fee_per_gas: u128, + max_priority_fee_per_gas: u128, + chain_id: u64, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + ensure!(data.len() <= T::MaxDataLength::get() as usize, Error::::DataTooLong); + ensure!(to_address.len() == 20, Error::::InvalidAddress); + + let mut to_array = [0u8; 20]; + to_array.copy_from_slice(&to_address[0..20]); + + let evm_tx = TransactionBuilder::new::() + .nonce(nonce) + .to(to_array) + .value(value) + .input(data) + .max_priority_fee_per_gas(max_priority_fee_per_gas) + .max_fee_per_gas(max_fee_per_gas) + .gas_limit(gas_limit) + .chain_id(chain_id) + .build(); + + let rlp_encoded = evm_tx.build_for_signing(); + + let tx_hash = sp_io::hashing::keccak_256(&rlp_encoded); + + let bounded_tx = BoundedVec::::try_from(rlp_encoded.clone()) + .map_err(|_| Error::::DataTooLong)?; + + >::put(&bounded_tx); + >::insert(&who, &bounded_tx); + + Self::deposit_event(Event::TransactionBuilt { + who, + tx_hash, + rlp_length: rlp_encoded.len() as u32, + }); + + Ok(()) + } + + #[pallet::call_index(1)] + #[pallet::weight(Weight::from_parts(10_000, 0))] + pub fn build_evm_contract_creation( + origin: OriginFor, + value: u128, + init_code: Vec, + nonce: u64, + gas_limit: u128, + max_fee_per_gas: u128, + max_priority_fee_per_gas: u128, + chain_id: u64, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + ensure!(init_code.len() <= T::MaxDataLength::get() as usize, Error::::DataTooLong); + + let evm_tx = TransactionBuilder::new::() + .nonce(nonce) + .value(value) + .input(init_code) + .max_priority_fee_per_gas(max_priority_fee_per_gas) + .max_fee_per_gas(max_fee_per_gas) + .gas_limit(gas_limit) + .chain_id(chain_id) + .build(); + + let rlp_encoded = evm_tx.build_for_signing(); + + let tx_hash = sp_io::hashing::keccak_256(&rlp_encoded); + + let bounded_tx = BoundedVec::::try_from(rlp_encoded.clone()) + .map_err(|_| Error::::DataTooLong)?; + + >::put(&bounded_tx); + >::insert(&who, &bounded_tx); + + Self::deposit_event(Event::TransactionBuilt { + who, + tx_hash, + rlp_length: rlp_encoded.len() as u32, + }); + + Ok(()) + } + } + + impl Pallet { + pub fn get_rlp_encoded_tx( + to_address: Option>, + value: u128, + data: Vec, + nonce: u64, + gas_limit: u128, + max_fee_per_gas: u128, + max_priority_fee_per_gas: u128, + chain_id: u64, + ) -> Result, DispatchError> { + ensure!(data.len() <= T::MaxDataLength::get() as usize, Error::::DataTooLong); + + let mut builder = TransactionBuilder::new::() + .nonce(nonce) + .value(value) + .input(data) + .max_priority_fee_per_gas(max_priority_fee_per_gas) + .max_fee_per_gas(max_fee_per_gas) + .gas_limit(gas_limit) + .chain_id(chain_id); + + if let Some(addr) = to_address { + ensure!(addr.len() == 20, Error::::InvalidAddress); + let mut to_array = [0u8; 20]; + to_array.copy_from_slice(&addr[0..20]); + builder = builder.to(to_array); + } + + let evm_tx = builder.build(); + Ok(evm_tx.build_for_signing()) + } + + pub fn parse_ethereum_address(address_hex: &[u8]) -> Result<[u8; 20], DispatchError> { + ensure!(address_hex.len() == 40, Error::::InvalidAddress); + + let mut result = [0u8; 20]; + hex::decode_to_slice(address_hex, &mut result) + .map_err(|_| Error::::InvalidAddress)?; + Ok(result) + } + } +} \ No newline at end of file diff --git a/runtime/hydradx/Cargo.toml b/runtime/hydradx/Cargo.toml index 5082fcff6a..a1e1a653a8 100644 --- a/runtime/hydradx/Cargo.toml +++ b/runtime/hydradx/Cargo.toml @@ -64,6 +64,7 @@ pallet-staking = { workspace = true } pallet-liquidation = { workspace = true } pallet-hsm = { workspace = true } pallet-adder-felipe = { workspace = true } +pallet-build-evm-tx = { workspace = true } pallet-parameters = { workspace = true } # pallets @@ -243,6 +244,7 @@ runtime-benchmarks = [ "pallet-xyk/runtime-benchmarks", "pallet-hsm/runtime-benchmarks", "pallet-adder-felipe/runtime-benchmarks", + "pallet-build-evm-tx/runtime-benchmarks", "pallet-elections-phragmen/runtime-benchmarks", "pallet-referrals/runtime-benchmarks", "pallet-evm-accounts/runtime-benchmarks", @@ -339,6 +341,7 @@ std = [ "pallet-duster-rpc-runtime-api/std", "pallet-hsm/std", "pallet-adder-felipe/std", + "pallet-build-evm-tx/std", "pallet-parameters/std", "warehouse-liquidity-mining/std", "sp-api/std", @@ -461,6 +464,7 @@ try-runtime = [ "pallet-xyk/try-runtime", "pallet-hsm/try-runtime", "pallet-adder-felipe/try-runtime", + "pallet-build-evm-tx/try-runtime", "pallet-referrals/try-runtime", "pallet-evm-accounts/try-runtime", "pallet-xyk-liquidity-mining/try-runtime", diff --git a/runtime/hydradx/src/assets.rs b/runtime/hydradx/src/assets.rs index 3fd867c818..339d136aab 100644 --- a/runtime/hydradx/src/assets.rs +++ b/runtime/hydradx/src/assets.rs @@ -1841,6 +1841,15 @@ impl pallet_adder_felipe::Config for Runtime { type MaxValue = AdderMaxValue; } +parameter_types! { + pub const MaxEvmDataLength: u32 = 10_000; +} + +impl pallet_build_evm_tx::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type MaxDataLength = MaxEvmDataLength; +} + pub struct ConvertViaOmnipool(PhantomData); impl Convert for ConvertViaOmnipool where diff --git a/runtime/hydradx/src/lib.rs b/runtime/hydradx/src/lib.rs index 4e3ae6e88e..1e3e415149 100644 --- a/runtime/hydradx/src/lib.rs +++ b/runtime/hydradx/src/lib.rs @@ -193,6 +193,7 @@ construct_runtime!( HSM: pallet_hsm = 82, Parameters: pallet_parameters = 83, AdderFelipe: pallet_adder_felipe = 84, + BuildEvmTx: pallet_build_evm_tx = 85, // ORML related modules Tokens: orml_tokens = 77, @@ -343,6 +344,7 @@ mod benches { [pallet_dispatcher, Dispatcher] [pallet_hsm, HSM] [pallet_adder_felipe, AdderFelipe] + [pallet_build_evm_tx, BuildEvmTx] [pallet_dynamic_fees, DynamicFees] [ismp_parachain, IsmpParachain] [pallet_token_gateway, TokenGateway] From 83b13e1c20a88bd12b7ff6e6d812440240d31ad0 Mon Sep 17 00:00:00 2001 From: Pessina Date: Sat, 13 Sep 2025 19:04:01 +0500 Subject: [PATCH 03/32] Remove felipe demo pallet, include test for buildEvmTx --- Cargo.lock | 624 +++++++++++-------- Cargo.toml | 2 - pallets/adder-felipe/Cargo.toml | 46 -- pallets/adder-felipe/src/lib.rs | 145 ----- pallets/build-evm-tx/Cargo.toml | 6 + pallets/build-evm-tx/src/lib.rs | 149 +---- pallets/build-evm-tx/src/tests/mock.rs | 70 +++ pallets/build-evm-tx/src/tests/mod.rs | 4 + pallets/build-evm-tx/src/tests/tests.rs | 142 +++++ pallets/build-evm-tx/src/tests/validation.rs | 294 +++++++++ runtime/hydradx/Cargo.toml | 4 - runtime/hydradx/src/assets.rs | 8 - runtime/hydradx/src/lib.rs | 2 - 13 files changed, 913 insertions(+), 583 deletions(-) delete mode 100644 pallets/adder-felipe/Cargo.toml delete mode 100644 pallets/adder-felipe/src/lib.rs create mode 100644 pallets/build-evm-tx/src/tests/mock.rs create mode 100644 pallets/build-evm-tx/src/tests/mod.rs create mode 100644 pallets/build-evm-tx/src/tests/tests.rs create mode 100644 pallets/build-evm-tx/src/tests/validation.rs diff --git a/Cargo.lock b/Cargo.lock index 9c2e79429d..de33474419 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,44 +120,125 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] -name = "alloy-primitives" -version = "0.4.2" +name = "alloy-consensus" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0628ec0ba5b98b3370bb6be17b12f23bfce8ee4ad83823325a20546d9b03b78" +checksum = "e88e1edea70787c33e11197d3f32ae380f3db19e6e061e539a5bcf8184a6b326" dependencies = [ + "alloy-eips", + "alloy-primitives", "alloy-rlp", - "bytes", - "cfg-if", + "alloy-trie", + "auto_impl", + "c-kzg", + "derive_more 1.0.0", +] + +[[package]] +name = "alloy-core" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c618bd382f0bc2ac26a7e4bfae01c9b015ca8f21b37ca40059ae35a7e62b3dc6" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-primitives", + "alloy-rlp", + "alloy-sol-types", +] + +[[package]] +name = "alloy-dyn-abi" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41056bde53ae10ffbbf11618efbe1e0290859e5eab0fe9ef82ebdb62f12a866f" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-type-parser", + "alloy-sol-types", "const-hex", - "derive_more 0.99.18", - "hex-literal", "itoa", - "proptest", - "rand 0.8.5", - "ruint", "serde", - "tiny-keccak", + "serde_json", + "winnow 0.6.20", +] + +[[package]] +name = "alloy-eip2930" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0069cf0642457f87a01a014f6dc29d5d893cd4fd8fddf0c3cdfad1bb3ebafc41" +dependencies = [ + "alloy-primitives", + "alloy-rlp", +] + +[[package]] +name = "alloy-eip7702" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c986539255fb839d1533c128e190e557e52ff652c9ef62939e233a81dd93f7e" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "derive_more 1.0.0", +] + +[[package]] +name = "alloy-eips" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9fadfe089e9ccc0650473f2d4ef0a28bc015bbca5631d9f0f09e49b557fdb3" +dependencies = [ + "alloy-eip2930", + "alloy-eip7702", + "alloy-primitives", + "alloy-rlp", + "c-kzg", + "derive_more 1.0.0", + "once_cell", + "serde", + "sha2 0.10.8", +] + +[[package]] +name = "alloy-json-abi" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c357da577dfb56998d01f574d81ad7a1958d248740a7981b205d69d65a7da404" +dependencies = [ + "alloy-primitives", + "alloy-sol-type-parser", + "serde", + "serde_json", ] [[package]] name = "alloy-primitives" -version = "0.7.7" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccb3ead547f4532bc8af961649942f0b9c16ee9226e26caa3f38420651cc0bf4" +checksum = "6259a506ab13e1d658796c31e6e39d2e2ee89243bcc505ddc613b35732e0a430" dependencies = [ "alloy-rlp", "bytes", "cfg-if", "const-hex", - "derive_more 0.99.18", + "derive_more 1.0.0", + "foldhash", + "hashbrown 0.15.2", "hex-literal", + "indexmap 2.7.0", "itoa", "k256", "keccak-asm", + "paste", "proptest", - "rand 0.8.5", + "rand", "ruint", + "rustc-hash 2.1.0", "serde", + "sha3", "tiny-keccak", ] @@ -167,95 +248,106 @@ version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f70d83b765fdc080dbcd4f4db70d8d23fe4761f2f02ebfa9146b833900634b4" dependencies = [ + "alloy-rlp-derive", "arrayvec 0.7.4", "bytes", ] [[package]] -name = "alloy-sol-macro" -version = "0.4.2" +name = "alloy-rlp-derive" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a98ad1696a2e17f010ae8e43e9f2a1e930ed176a8e3ff77acfeff6dfb07b42c" +checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" dependencies = [ - "const-hex", - "dunce", - "heck 0.4.1", - "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.90", - "syn-solidity 0.4.2", - "tiny-keccak", + "syn 2.0.106", ] [[package]] name = "alloy-sol-macro" -version = "0.7.7" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b40397ddcdcc266f59f959770f601ce1280e699a91fc1862f29cef91707cd09" +checksum = "e10ae8e9a91d328ae954c22542415303919aabe976fe7a92eb06db1b68fd59f2" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", - "proc-macro-error", + "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] name = "alloy-sol-macro-expander" -version = "0.7.7" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "867a5469d61480fea08c7333ffeca52d5b621f5ca2e44f271b117ec1fc9a0525" +checksum = "83ad5da86c127751bc607c174d6c9fe9b85ef0889a9ca0c641735d77d4f98f26" dependencies = [ "alloy-sol-macro-input", "const-hex", "heck 0.5.0", "indexmap 2.7.0", - "proc-macro-error", + "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.90", - "syn-solidity 0.7.7", + "syn 2.0.106", + "syn-solidity", "tiny-keccak", ] [[package]] name = "alloy-sol-macro-input" -version = "0.7.7" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e482dc33a32b6fadbc0f599adea520bd3aaa585c141a80b404d0a3e3fa72528" +checksum = "ba3d30f0d3f9ba3b7686f3ff1de9ee312647aac705604417a2f40c604f409a9e" dependencies = [ "const-hex", "dunce", "heck 0.5.0", + "macro-string", "proc-macro2", "quote", - "syn 2.0.90", - "syn-solidity 0.7.7", + "syn 2.0.106", + "syn-solidity", ] [[package]] -name = "alloy-sol-types" -version = "0.4.2" +name = "alloy-sol-type-parser" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98d7107bed88e8f09f0ddcc3335622d87bfb6821f3e0c7473329fb1cfad5e015" +checksum = "6d162f8524adfdfb0e4bd0505c734c985f3e2474eb022af32eef0d52a4f3935c" dependencies = [ - "alloy-primitives 0.4.2", - "alloy-sol-macro 0.4.2", - "const-hex", "serde", + "winnow 0.7.13", ] [[package]] name = "alloy-sol-types" -version = "0.7.7" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91ca40fa20793ae9c3841b83e74569d1cc9af29a2f5237314fd3452d51e38c7" +checksum = "1174cafd6c6d810711b4e00383037bdb458efc4fe3dbafafa16567e0320c54d8" dependencies = [ - "alloy-primitives 0.7.7", - "alloy-sol-macro 0.7.7", + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-macro", "const-hex", + "serde", +] + +[[package]] +name = "alloy-trie" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a94854e420f07e962f7807485856cde359ab99ab6413883e15235ad996e8b" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "arrayvec 0.7.4", + "derive_more 1.0.0", + "nybbles", + "smallvec", + "tracing", ] [[package]] @@ -360,7 +452,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -374,18 +466,6 @@ dependencies = [ "ark-std 0.4.0", ] -[[package]] -name = "ark-bls12-377-ext" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20c7021f180a0cbea0380eba97c2af3c57074cdaffe0eef7e840e1c9f2841e55" -dependencies = [ - "ark-bls12-377", - "ark-ec", - "ark-models-ext", - "ark-std 0.4.0", -] - [[package]] name = "ark-bls12-381" version = "0.4.0" @@ -398,45 +478,6 @@ dependencies = [ "ark-std 0.4.0", ] -[[package]] -name = "ark-bls12-381-ext" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1dc4b3d08f19e8ec06e949712f95b8361e43f1391d94f65e4234df03480631c" -dependencies = [ - "ark-bls12-381", - "ark-ec", - "ark-ff 0.4.2", - "ark-models-ext", - "ark-serialize 0.4.2", - "ark-std 0.4.0", -] - -[[package]] -name = "ark-bw6-761" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e0605daf0cc5aa2034b78d008aaf159f56901d92a52ee4f6ecdfdac4f426700" -dependencies = [ - "ark-bls12-377", - "ark-ec", - "ark-ff 0.4.2", - "ark-std 0.4.0", -] - -[[package]] -name = "ark-bw6-761-ext" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccee5fba47266f460067588ee1bf070a9c760bf2050c1c509982c5719aadb4f2" -dependencies = [ - "ark-bw6-761", - "ark-ec", - "ark-ff 0.4.2", - "ark-models-ext", - "ark-std 0.4.0", -] - [[package]] name = "ark-ec" version = "0.4.2" @@ -523,6 +564,24 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + [[package]] name = "ark-ff" version = "0.4.2" @@ -638,6 +697,16 @@ dependencies = [ "digest 0.9.0", ] +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + [[package]] name = "ark-serialize" version = "0.4.2" @@ -668,7 +737,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ "num-traits", - "rand 0.8.5", + "rand", ] [[package]] @@ -761,7 +830,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", "synstructure 0.13.1", ] @@ -784,7 +853,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -1016,7 +1085,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -1057,14 +1126,13 @@ dependencies = [ [[package]] name = "auto_impl" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.106", ] [[package]] @@ -1175,7 +1243,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -1349,6 +1417,18 @@ dependencies = [ "tracing", ] +[[package]] +name = "blst" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fd49896f12ac9b6dcd7a5998466b9b58263a695a3dd1ecc1aaca2e12a90b080" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + [[package]] name = "bounded-collections" version = "0.2.0" @@ -1702,6 +1782,9 @@ name = "bytes" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +dependencies = [ + "serde", +] [[package]] name = "bzip2-sys" @@ -1714,6 +1797,21 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "c-kzg" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0307f72feab3300336fb803a57134159f6e20139af1357f36c54cb90d8e8928" +dependencies = [ + "blst", + "cc", + "glob", + "hex", + "libc", + "once_cell", + "serde", +] + [[package]] name = "c2-chacha" version = "0.3.3" @@ -1990,7 +2088,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -2099,9 +2197,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.14.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e22e0ed40b96a48d3db274f72fd365bd78f67af39b6bbd47e8a15e1c6207ff" +checksum = "dccd746bf9b1038c0507b7cec21eb2b11222db96a2902c96e8c185d6d20fb9c4" dependencies = [ "cfg-if", "cpufeatures", @@ -2819,7 +2917,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -3151,7 +3249,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -3191,7 +3289,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -3208,7 +3306,7 @@ checksum = "5c6888cd161769d65134846d4d4981d5a6654307cc46ec83fb917e530aea5f84" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -3316,7 +3414,7 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -3329,7 +3427,28 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.0", - "syn 2.0.90", + "syn 2.0.106", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "unicode-xid", ] [[package]] @@ -3445,7 +3564,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -3469,7 +3588,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.90", + "syn 2.0.106", "termcolor", "toml 0.8.19", "walkdir", @@ -3634,7 +3753,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -3654,7 +3773,7 @@ checksum = "f95e2801cd355d4a1a3e3953ce6ee5ae9603a5c833455343a8bfe3f44d418246" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -3665,7 +3784,7 @@ checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -3897,7 +4016,7 @@ dependencies = [ "prettyplease 0.2.25", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -3976,7 +4095,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -4512,7 +4631,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -4656,7 +4775,7 @@ dependencies = [ "proc-macro2", "quote", "sp-crypto-hashing", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -4668,7 +4787,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -4678,7 +4797,7 @@ source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409- dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -4866,7 +4985,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -5176,6 +5295,7 @@ dependencies = [ "allocator-api2", "equivalent", "foldhash", + "serde", ] [[package]] @@ -5210,6 +5330,9 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "hex-conservative" @@ -5582,7 +5705,6 @@ dependencies = [ "orml-xcm", "orml-xcm-support", "orml-xtokens", - "pallet-adder-felipe", "pallet-asset-registry", "pallet-aura", "pallet-authorship", @@ -5989,6 +6111,7 @@ checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown 0.15.2", + "serde", ] [[package]] @@ -6325,7 +6448,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -6831,7 +6954,7 @@ dependencies = [ "proc-macro-warning 0.4.2", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -7237,6 +7360,17 @@ dependencies = [ "libc", ] +[[package]] +name = "macro-string" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "macro_magic" version = "0.5.1" @@ -7246,7 +7380,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -7260,7 +7394,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -7271,7 +7405,7 @@ checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -7282,7 +7416,7 @@ checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -7298,7 +7432,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -7571,7 +7705,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -8060,10 +8194,10 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -8072,6 +8206,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +[[package]] +name = "nybbles" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8983bb634df7248924ee0c4c3a749609b5abcb082c28fffe3254b3eb3602b307" +dependencies = [ + "const-hex", + "smallvec", +] + [[package]] name = "object" version = "0.30.4" @@ -8167,7 +8311,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -8404,26 +8548,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" -[[package]] -name = "pallet-alliance" -version = "37.0.0" -source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409-patch6#1ed662757b03c4f17dc2429c1df84e975d98513a" -dependencies = [ - "array-bytes", - "frame-benchmarking", - "frame-support", - "frame-system", - "log", - "pallet-collective", - "pallet-identity", - "parity-scale-codec", - "scale-info", - "sp-core", - "sp-crypto-hashing", - "sp-io", - "sp-runtime", -] - [[package]] name = "pallet-asset-conversion" version = "20.0.0" @@ -8890,6 +9014,11 @@ dependencies = [ name = "pallet-build-evm-tx" version = "1.0.0" dependencies = [ + "alloy-consensus", + "alloy-core", + "alloy-primitives", + "alloy-rlp", + "alloy-sol-types", "frame-benchmarking", "frame-support", "frame-system", @@ -11734,7 +11863,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -11775,7 +11904,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -13368,7 +13497,7 @@ dependencies = [ "polkavm-common 0.10.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -13377,18 +13506,8 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ - "polkavm-derive-impl 0.9.0", - "syn 2.0.90", -] - -[[package]] -name = "polkavm-derive-impl-macro" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9324fe036de37c17829af233b46ef6b5562d4a0c09bb7fdb9f8378856dee30cf" -dependencies = [ - "polkavm-derive-impl 0.10.0", - "syn 2.0.90", + "polkavm-derive-impl", + "syn 2.0.106", ] [[package]] @@ -13625,7 +13744,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -13726,6 +13845,28 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "proc-macro-warning" version = "0.4.2" @@ -13734,7 +13875,7 @@ checksum = "3d1eaa7fa0aa1929ffdf7eeb6eac234dde6268914a14ad44d23521ab6a9b258e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -13745,7 +13886,7 @@ checksum = "834da187cfe638ae8abb0203f0b33e5ccdb02a28e7199f2f47b3e2754f50edca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -13791,7 +13932,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -13873,7 +14014,7 @@ dependencies = [ "prost 0.12.6", "prost-types 0.12.6", "regex", - "syn 2.0.90", + "syn 2.0.106", "tempfile", ] @@ -13900,7 +14041,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -14098,18 +14239,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_chacha", + "rand_core", + "serde", ] [[package]] @@ -14292,7 +14424,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -14637,9 +14769,9 @@ dependencies = [ [[package]] name = "ruint" -version = "1.16.0" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecb38f82477f20c5c3d62ef52d7c4e536e38ea9b73fb570a20c5cae0e14bcf6" +checksum = "f5ef8fb1dd8de3870cb8400d51b4c2023854bbafd5431a3ac7e7317243e22d2f" dependencies = [ "alloy-rlp", "ark-ff 0.3.0", @@ -14653,9 +14785,8 @@ dependencies = [ "parity-scale-codec", "primitive-types 0.12.2", "proptest", - "rand 0.8.5", - "rand 0.9.2", - "rlp", + "rand", + "rlp 0.5.2", "ruint-macro", "serde", "valuable", @@ -14846,15 +14977,6 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver 0.9.0", -] - [[package]] name = "rustc_version" version = "0.3.3" @@ -15245,7 +15367,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -16252,7 +16374,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -16574,15 +16696,6 @@ dependencies = [ "semver-parser 0.7.0", ] -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser 0.7.0", -] - [[package]] name = "semver" version = "0.11.0" @@ -16667,7 +16780,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -17468,7 +17581,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -17743,7 +17856,7 @@ source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409- dependencies = [ "quote", "sp-crypto-hashing", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -17762,7 +17875,7 @@ source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409- dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -17992,7 +18105,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -18177,7 +18290,7 @@ dependencies = [ "parity-scale-codec", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -18502,7 +18615,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -18515,7 +18628,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -18687,9 +18800,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.90" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -18698,26 +18811,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b837ef12ab88835251726eb12237655e61ec8dc8a280085d1961cdc3dfd047" -dependencies = [ - "paste", - "proc-macro2", - "quote", - "syn 2.0.90", -] - -[[package]] -name = "syn-solidity" -version = "0.7.7" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c837dc8852cb7074e46b444afb81783140dab12c58867b49fb3898fbafedf7ea" +checksum = "4560533fbd6914b94a8fb5cc803ed6801c3455668db3b810702c57612bac9412" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -18740,7 +18841,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -18894,7 +18995,7 @@ checksum = "e4c60d69f36615a077cc7663b9cb8e42275722d23e58a7fa3d2c7f2915d09d04" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -18905,7 +19006,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -19087,7 +19188,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -19290,7 +19391,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -19333,7 +19434,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -19791,7 +19892,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", "wasm-bindgen-shared", ] @@ -19825,7 +19926,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -20669,6 +20770,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" @@ -20786,7 +20896,7 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -20887,7 +20997,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -20907,7 +21017,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 916f6cc755..8be69c5945 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,6 @@ members = [ 'pallets/broadcast', 'liquidation-worker-support', 'pallets/hsm', - 'pallets/adder-felipe', ] resolver = "2" @@ -158,7 +157,6 @@ pallet-liquidation = { path = "pallets/liquidation", default-features = false } pallet-broadcast = { path = "pallets/broadcast", default-features = false } liquidation-worker-support = { path = "liquidation-worker-support", default-features = false } pallet-hsm = { path = "pallets/hsm", default-features = false } -pallet-adder-felipe = { path = "pallets/adder-felipe", default-features = false } pallet-build-evm-tx = { path = "pallets/build-evm-tx", default-features = false } pallet-parameters = { path = "pallets/parameters", default-features = false } diff --git a/pallets/adder-felipe/Cargo.toml b/pallets/adder-felipe/Cargo.toml deleted file mode 100644 index 6dbc1f378d..0000000000 --- a/pallets/adder-felipe/Cargo.toml +++ /dev/null @@ -1,46 +0,0 @@ -[package] -name = "pallet-adder-felipe" -version = "1.0.0" -description = "Simple adder pallet for demonstration" -authors = ["GalacticCouncil"] -edition = "2021" -homepage = "https://github.com/galacticcouncil/hydration-node" -license = "Apache 2.0" -repository = "https://github.com/galacticcouncil/hydration-node" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -codec = { workspace = true } -scale-info = { workspace = true } - -# Substrate dependencies -sp-std = { workspace = true } -sp-runtime = { workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } - -# Optionals -frame-benchmarking = { workspace = true, optional = true } - -[dev-dependencies] -sp-io = { workspace = true } -sp-core = { workspace = true } - -[features] -default = ["std"] -runtime-benchmarks = [ - "frame-benchmarking", - "frame-system/runtime-benchmarks", - "frame-support/runtime-benchmarks", -] -std = [ - "codec/std", - "frame-support/std", - "frame-system/std", - "sp-std/std", - "sp-runtime/std", - "scale-info/std", -] -try-runtime = ["frame-support/try-runtime"] \ No newline at end of file diff --git a/pallets/adder-felipe/src/lib.rs b/pallets/adder-felipe/src/lib.rs deleted file mode 100644 index 26f8aec50b..0000000000 --- a/pallets/adder-felipe/src/lib.rs +++ /dev/null @@ -1,145 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std)] - -// Every pallet uses the `pub use pallet::*` pattern -pub use pallet::*; - -#[frame_support::pallet] -pub mod pallet { - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; - use sp_std::vec::Vec; - - /// Configure the pallet by specifying parameters and types it depends on - #[pallet::config] - pub trait Config: frame_system::Config { - /// The overarching event type - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - - /// Maximum value that can be added (for safety) - #[pallet::constant] - type MaxValue: Get; - } - - /// The pallet's storage items - #[pallet::storage] - #[pallet::getter(fn last_result)] - pub type LastResult = StorageValue<_, u128, ValueQuery>; - - /// Storage for results by account - #[pallet::storage] - #[pallet::getter(fn user_results)] - pub type UserResults = StorageMap< - _, - Blake2_128Concat, - T::AccountId, - u128, - ValueQuery - >; - - /// Pallets use events to inform users about important changes - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - /// Addition performed successfully [who, x, y, result] - AdditionPerformed { - who: T::AccountId, - x: u128, - y: u128, - result: u128, - }, - } - - /// Errors that can occur in the pallet - #[pallet::error] - pub enum Error { - /// Arithmetic overflow occurred - Overflow, - /// Value exceeds maximum allowed - ValueTooLarge, - } - - /// The pallet struct - #[pallet::pallet] - pub struct Pallet(_); - - /// Dispatchable functions (extrinsics) - these can be called from outside - #[pallet::call] - impl Pallet { - /// Add two numbers together - /// - /// This function adds x and y, stores the result, and emits an event - #[pallet::call_index(0)] - #[pallet::weight(Weight::from_parts(10_000, 0))] - pub fn add( - origin: OriginFor, - x: u128, - y: u128, - ) -> DispatchResult { - // Ensure the call is from a signed account - let who = ensure_signed(origin)?; - - // Check values don't exceed maximum - ensure!(x <= T::MaxValue::get(), Error::::ValueTooLarge); - ensure!(y <= T::MaxValue::get(), Error::::ValueTooLarge); - - // Perform addition with overflow check - let result = x.checked_add(y) - .ok_or(Error::::Overflow)?; - - // Store the result - >::put(result); - >::insert(&who, result); - - // Emit an event - Self::deposit_event(Event::AdditionPerformed { - who, - x, - y, - result, - }); - - Ok(()) - } - - /// Add multiple numbers (demonstrating more complex logic) - #[pallet::call_index(1)] - #[pallet::weight(Weight::from_parts(10_000 * numbers.len() as u64, 0))] - pub fn add_multiple( - origin: OriginFor, - numbers: Vec, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - // Use internal helper function - let result = Self::calculate_sum(&numbers)?; - - >::put(result); - >::insert(&who, result); - - Self::deposit_event(Event::AdditionPerformed { - who, - x: 0, // You might emit different event here - y: 0, - result, - }); - - Ok(()) - } - } - - /// Helper functions (not dispatchable) - impl Pallet { - /// Internal function to calculate sum - pub fn calculate_sum(numbers: &[u128]) -> Result> { - numbers.iter().try_fold(0u128, |acc, &num| { - ensure!(num <= T::MaxValue::get(), Error::::ValueTooLarge); - acc.checked_add(num).ok_or(Error::::Overflow) - }) - } - - /// Public read-only function (can be called without transaction) - pub fn get_sum_without_storing(x: u128, y: u128) -> Option { - x.checked_add(y) - } - } -} \ No newline at end of file diff --git a/pallets/build-evm-tx/Cargo.toml b/pallets/build-evm-tx/Cargo.toml index 33cc680154..aacd7af5e5 100644 --- a/pallets/build-evm-tx/Cargo.toml +++ b/pallets/build-evm-tx/Cargo.toml @@ -32,6 +32,12 @@ frame-benchmarking = { workspace = true, optional = true } [dev-dependencies] sp-io = { workspace = true } sp-core = { workspace = true } +# For transaction validation tests +alloy-consensus = "0.8" +alloy-primitives = "0.8" +alloy-rlp = "0.3" +alloy-sol-types = "0.8" +alloy-core = "0.8" [features] default = ["std"] diff --git a/pallets/build-evm-tx/src/lib.rs b/pallets/build-evm-tx/src/lib.rs index 92bff48e83..4661a2491d 100644 --- a/pallets/build-evm-tx/src/lib.rs +++ b/pallets/build-evm-tx/src/lib.rs @@ -2,6 +2,9 @@ pub use pallet::*; +#[cfg(test)] +mod tests; + #[frame_support::pallet] pub mod pallet { use frame_support::pallet_prelude::*; @@ -17,35 +20,24 @@ pub mod pallet { type MaxDataLength: Get; } - #[pallet::storage] - #[pallet::getter(fn last_built_tx)] - pub type LastBuiltTx = StorageValue<_, BoundedVec, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn tx_by_account)] - pub type TxByAccount = StorageMap< - _, - Blake2_128Concat, - T::AccountId, - BoundedVec, - ValueQuery - >; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { TransactionBuilt { who: T::AccountId, - tx_hash: [u8; 32], - rlp_length: u32, + rlp_data: Vec, }, } #[pallet::error] pub enum Error { - InvalidAddress, + /// The provided address is not exactly 20 bytes + InvalidAddressLength, + /// Transaction data exceeds the maximum allowed length DataTooLong, - BuildFailed, + /// Max priority fee per gas is greater than max fee per gas + InvalidGasPrice, } #[pallet::pallet] @@ -53,11 +45,15 @@ pub mod pallet { #[pallet::call] impl Pallet { + /// Build an EVM transaction (regular or contract creation). + /// + /// - `to_address`: Some(address) for regular transaction, None for contract creation + /// - `data`: Transaction data (call data for regular tx, init code for contract creation) #[pallet::call_index(0)] #[pallet::weight(Weight::from_parts(10_000, 0))] pub fn build_evm_transaction( origin: OriginFor, - to_address: Vec, + to_address: Option>, value: u128, data: Vec, nonce: u64, @@ -68,100 +64,16 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; + // Validate data length ensure!(data.len() <= T::MaxDataLength::get() as usize, Error::::DataTooLong); - ensure!(to_address.len() == 20, Error::::InvalidAddress); - - let mut to_array = [0u8; 20]; - to_array.copy_from_slice(&to_address[0..20]); - - let evm_tx = TransactionBuilder::new::() - .nonce(nonce) - .to(to_array) - .value(value) - .input(data) - .max_priority_fee_per_gas(max_priority_fee_per_gas) - .max_fee_per_gas(max_fee_per_gas) - .gas_limit(gas_limit) - .chain_id(chain_id) - .build(); - - let rlp_encoded = evm_tx.build_for_signing(); - - let tx_hash = sp_io::hashing::keccak_256(&rlp_encoded); - - let bounded_tx = BoundedVec::::try_from(rlp_encoded.clone()) - .map_err(|_| Error::::DataTooLong)?; - - >::put(&bounded_tx); - >::insert(&who, &bounded_tx); - - Self::deposit_event(Event::TransactionBuilt { - who, - tx_hash, - rlp_length: rlp_encoded.len() as u32, - }); - - Ok(()) - } - - #[pallet::call_index(1)] - #[pallet::weight(Weight::from_parts(10_000, 0))] - pub fn build_evm_contract_creation( - origin: OriginFor, - value: u128, - init_code: Vec, - nonce: u64, - gas_limit: u128, - max_fee_per_gas: u128, - max_priority_fee_per_gas: u128, - chain_id: u64, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - ensure!(init_code.len() <= T::MaxDataLength::get() as usize, Error::::DataTooLong); - - let evm_tx = TransactionBuilder::new::() - .nonce(nonce) - .value(value) - .input(init_code) - .max_priority_fee_per_gas(max_priority_fee_per_gas) - .max_fee_per_gas(max_fee_per_gas) - .gas_limit(gas_limit) - .chain_id(chain_id) - .build(); - - let rlp_encoded = evm_tx.build_for_signing(); - - let tx_hash = sp_io::hashing::keccak_256(&rlp_encoded); - - let bounded_tx = BoundedVec::::try_from(rlp_encoded.clone()) - .map_err(|_| Error::::DataTooLong)?; - >::put(&bounded_tx); - >::insert(&who, &bounded_tx); - - Self::deposit_event(Event::TransactionBuilt { - who, - tx_hash, - rlp_length: rlp_encoded.len() as u32, - }); + // Validate to_address if provided + if let Some(ref addr) = to_address { + ensure!(addr.len() == 20, Error::::InvalidAddressLength); + } - Ok(()) - } - } - - impl Pallet { - pub fn get_rlp_encoded_tx( - to_address: Option>, - value: u128, - data: Vec, - nonce: u64, - gas_limit: u128, - max_fee_per_gas: u128, - max_priority_fee_per_gas: u128, - chain_id: u64, - ) -> Result, DispatchError> { - ensure!(data.len() <= T::MaxDataLength::get() as usize, Error::::DataTooLong); + // Validate gas prices relationship + ensure!(max_priority_fee_per_gas <= max_fee_per_gas, Error::::InvalidGasPrice); let mut builder = TransactionBuilder::new::() .nonce(nonce) @@ -172,24 +84,23 @@ pub mod pallet { .gas_limit(gas_limit) .chain_id(chain_id); + // Add 'to' address if provided (regular transaction) + // Omit for contract creation if let Some(addr) = to_address { - ensure!(addr.len() == 20, Error::::InvalidAddress); let mut to_array = [0u8; 20]; to_array.copy_from_slice(&addr[0..20]); builder = builder.to(to_array); } let evm_tx = builder.build(); - Ok(evm_tx.build_for_signing()) - } - - pub fn parse_ethereum_address(address_hex: &[u8]) -> Result<[u8; 20], DispatchError> { - ensure!(address_hex.len() == 40, Error::::InvalidAddress); + let rlp_encoded = evm_tx.build_for_signing(); - let mut result = [0u8; 20]; - hex::decode_to_slice(address_hex, &mut result) - .map_err(|_| Error::::InvalidAddress)?; - Ok(result) + Self::deposit_event(Event::TransactionBuilt { + who, + rlp_data: rlp_encoded, + }); + + Ok(()) } } } \ No newline at end of file diff --git a/pallets/build-evm-tx/src/tests/mock.rs b/pallets/build-evm-tx/src/tests/mock.rs new file mode 100644 index 0000000000..1f76ecfdd2 --- /dev/null +++ b/pallets/build-evm-tx/src/tests/mock.rs @@ -0,0 +1,70 @@ +// Mock runtime for testing build-evm-tx pallet + +use crate::{self as build_evm_tx}; +use frame_support::{parameter_types, traits::Everything}; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; + +pub type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet +frame_support::construct_runtime!( + pub enum Test { + System: frame_system, + BuildEvmTx: build_evm_tx, + } +); + +// System pallet configuration +impl frame_system::Config for Test { + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeTask = RuntimeTask; + type Nonce = u64; + type Block = Block; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = frame_support::traits::ConstU64<250>; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; + type SingleBlockMigrations = (); + type MultiBlockMigrator = (); + type PreInherents = (); + type PostInherents = (); + type PostTransactions = (); +} + +// Build EVM TX pallet configuration +parameter_types! { + pub const MaxDataLength: u32 = 100_000; +} + +impl build_evm_tx::Config for Test { + type RuntimeEvent = RuntimeEvent; + type MaxDataLength = MaxDataLength; +} + +// Test externalities initialization +pub fn new_test_ext() -> sp_io::TestExternalities { + frame_system::GenesisConfig::::default() + .build_storage() + .unwrap() + .into() +} \ No newline at end of file diff --git a/pallets/build-evm-tx/src/tests/mod.rs b/pallets/build-evm-tx/src/tests/mod.rs new file mode 100644 index 0000000000..1d1eb13f8b --- /dev/null +++ b/pallets/build-evm-tx/src/tests/mod.rs @@ -0,0 +1,4 @@ +pub mod mock; +#[allow(clippy::module_inception)] +mod tests; +mod validation; \ No newline at end of file diff --git a/pallets/build-evm-tx/src/tests/tests.rs b/pallets/build-evm-tx/src/tests/tests.rs new file mode 100644 index 0000000000..55eda6c554 --- /dev/null +++ b/pallets/build-evm-tx/src/tests/tests.rs @@ -0,0 +1,142 @@ +// Essential input validation tests for build-evm-tx pallet + +use crate::Error; +use frame_support::{assert_noop, assert_ok}; + +use super::mock::*; + +#[test] +fn invalid_address_length_fails() { + new_test_ext().execute_with(|| { + // Test with address shorter than 20 bytes + let short_address = vec![1u8; 19]; + assert_noop!( + BuildEvmTx::build_evm_transaction( + RuntimeOrigin::signed(1), + Some(short_address), + 1000u128, + vec![], + 1u64, + 21000u128, + 20u128, + 10u128, + 1u64, + ), + Error::::InvalidAddressLength + ); + + // Test with address longer than 20 bytes + let long_address = vec![1u8; 21]; + assert_noop!( + BuildEvmTx::build_evm_transaction( + RuntimeOrigin::signed(1), + Some(long_address), + 1000u128, + vec![], + 1u64, + 21000u128, + 20u128, + 10u128, + 1u64, + ), + Error::::InvalidAddressLength + ); + + // Verify exactly 20 bytes works + let valid_address = vec![1u8; 20]; + assert_ok!(BuildEvmTx::build_evm_transaction( + RuntimeOrigin::signed(1), + Some(valid_address), + 1000u128, + vec![], + 1u64, + 21000u128, + 20u128, + 10u128, + 1u64, + )); + }); +} + +#[test] +fn data_too_long_fails() { + new_test_ext().execute_with(|| { + // Create data that exceeds MaxDataLength + let large_data = vec![0u8; 100_001]; + assert_noop!( + BuildEvmTx::build_evm_transaction( + RuntimeOrigin::signed(1), + None, + 0u128, + large_data, + 1u64, + 21000u128, + 20u128, + 10u128, + 1u64, + ), + Error::::DataTooLong + ); + + // Verify data at exact limit works + let max_data = vec![0u8; 100_000]; + assert_ok!(BuildEvmTx::build_evm_transaction( + RuntimeOrigin::signed(1), + None, + 0u128, + max_data, + 1u64, + 21000u128, + 20u128, + 10u128, + 1u64, + )); + }); +} + +#[test] +fn invalid_gas_price_relationship_fails() { + new_test_ext().execute_with(|| { + // Test when max_priority_fee > max_fee + assert_noop!( + BuildEvmTx::build_evm_transaction( + RuntimeOrigin::signed(1), + None, + 0u128, + vec![], + 1u64, + 21000u128, + 10u128, // max_fee_per_gas + 20u128, // max_priority_fee_per_gas > max_fee_per_gas + 1u64, + ), + Error::::InvalidGasPrice + ); + + // Verify equal values work (priority fee can equal max fee) + assert_ok!(BuildEvmTx::build_evm_transaction( + RuntimeOrigin::signed(1), + None, + 0u128, + vec![], + 1u64, + 21000u128, + 20u128, // max_fee_per_gas + 20u128, // max_priority_fee_per_gas = max_fee_per_gas + 1u64, + )); + + // Verify priority fee less than max fee works + assert_ok!(BuildEvmTx::build_evm_transaction( + RuntimeOrigin::signed(1), + None, + 0u128, + vec![], + 2u64, + 21000u128, + 20u128, // max_fee_per_gas + 10u128, // max_priority_fee_per_gas < max_fee_per_gas + 1u64, + )); + }); +} \ No newline at end of file diff --git a/pallets/build-evm-tx/src/tests/validation.rs b/pallets/build-evm-tx/src/tests/validation.rs new file mode 100644 index 0000000000..eb64ae2824 --- /dev/null +++ b/pallets/build-evm-tx/src/tests/validation.rs @@ -0,0 +1,294 @@ +// Validation tests comparing with reference Ethereum implementation + +#[cfg(test)] +mod validation { + use super::super::mock::*; + use crate::Event; + use frame_support::assert_ok; + + // Alloy reference implementation + use alloy_consensus::TxEip1559; + use alloy_primitives::{Address, Bytes, TxKind, U256, FixedBytes}; + use alloy_rlp::Encodable; + use alloy_sol_types::{sol, SolCall}; + + // Define ERC20 interface for encoding function calls + sol! { + interface IERC20 { + function transfer(address to, uint256 amount) external returns (bool); + function approve(address spender, uint256 amount) external returns (bool); + function balanceOf(address account) external view returns (uint256); + } + } + + // Define a simple Uniswap V2 Router interface for complex calls + sol! { + interface IUniswapV2Router { + function swapExactETHForTokens( + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external payable returns (uint256[] memory amounts); + } + } + + #[test] + fn validate_erc20_transfer_transaction() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + + // Transaction parameters + let token_address = vec![0x42; 20]; // ERC20 token address + let value = 0u128; // No ETH value for ERC20 transfer + + // Properly encode an ERC20 transfer call + let recipient = Address::from([0x11; 20]); + let amount = U256::from(1000000u64); // 1 million tokens + let transfer_call = IERC20::transferCall { + to: recipient, + amount, + }; + let encoded_data = transfer_call.abi_encode(); + + let nonce = 5u64; + let gas_limit = 65000u128; + let max_fee_per_gas = 20000000000u128; // 20 gwei + let max_priority_fee_per_gas = 1000000000u128; // 1 gwei + let chain_id = 1u64; // Ethereum mainnet + + // Build transaction with our pallet + assert_ok!(BuildEvmTx::build_evm_transaction( + RuntimeOrigin::signed(1), + Some(token_address.clone()), + value, + encoded_data.clone(), + nonce, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + chain_id, + )); + + // Get the RLP data from the emitted event + let events = System::events(); + let our_rlp = events + .iter() + .find_map(|record| match &record.event { + RuntimeEvent::BuildEvmTx(Event::TransactionBuilt { rlp_data, .. }) => { + Some(rlp_data.clone()) + } + _ => None, + }) + .expect("TransactionBuilt event not found"); + + // Build the same transaction with Alloy reference implementation + let mut to_array = [0u8; 20]; + to_array.copy_from_slice(&token_address); + + let reference_tx = TxEip1559 { + chain_id, + nonce, + gas_limit: gas_limit as u64, + max_fee_per_gas: max_fee_per_gas as u128, + max_priority_fee_per_gas: max_priority_fee_per_gas as u128, + to: TxKind::Call(Address::from(to_array)), + value: U256::from(value), + input: Bytes::from(encoded_data), + access_list: Default::default(), + }; + + // Encode the reference transaction + let mut reference_rlp = Vec::new(); + reference_tx.encode(&mut reference_rlp); + + // Add EIP-1559 transaction type prefix (0x02) + let mut prefixed_reference_rlp = vec![0x02]; + prefixed_reference_rlp.extend_from_slice(&reference_rlp); + + // Compare the RLP outputs + assert_eq!( + our_rlp, prefixed_reference_rlp, + "ERC20 transfer RLP encoding mismatch!\nOur RLP: {:?}\nReference RLP: {:?}", + hex::encode(&our_rlp), + hex::encode(&prefixed_reference_rlp) + ); + }); + } + + #[test] + fn validate_contract_creation_with_real_bytecode() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + + // Contract creation parameters + let value = 0u128; + + // Real minimal contract bytecode (stores a value and allows retrieval) + // This is the bytecode for a simple storage contract: + // contract SimpleStorage { + // uint256 public storedData; + // constructor(uint256 initialValue) { + // storedData = initialValue; + // } + // } + // Bytecode includes constructor logic and runtime code + let init_code = hex::decode( + "608060405234801561001057600080fd5b506040516101583803806101588339818101604052810190610032919061007a565b80600081905550506100a7565b600080fd5b6000819050919050565b61005781610044565b811461006257600080fd5b50565b6000815190506100748161004e565b92915050565b6000602082840312156100905761008f61003f565b5b600061009e84828501610065565b91505092915050565b60a2806100b66000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80632a1afcd914602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60005481565b6000819050919050565b6057816046565b82525050565b6000602082019050607060008301846050565b9291505056fea2646970667358221220" + ).expect("Valid hex"); + + let nonce = 0u64; + let gas_limit = 200000u128; + let max_fee_per_gas = 30000000000u128; // 30 gwei + let max_priority_fee_per_gas = 2000000000u128; // 2 gwei + let chain_id = 137u64; // Polygon + + // Build contract creation with our pallet + assert_ok!(BuildEvmTx::build_evm_transaction( + RuntimeOrigin::signed(2), + None, // No to_address for contract creation + value, + init_code.clone(), + nonce, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + chain_id, + )); + + // Get the RLP data from the emitted event + let events = System::events(); + let our_rlp = events + .iter() + .find_map(|record| match &record.event { + RuntimeEvent::BuildEvmTx(Event::TransactionBuilt { rlp_data, .. }) => { + Some(rlp_data.clone()) + } + _ => None, + }) + .expect("TransactionBuilt event not found"); + + // Build the same transaction with Alloy reference implementation + let reference_tx = TxEip1559 { + chain_id, + nonce, + gas_limit: gas_limit as u64, + max_fee_per_gas: max_fee_per_gas as u128, + max_priority_fee_per_gas: max_priority_fee_per_gas as u128, + to: TxKind::Create, // Contract creation + value: U256::from(value), + input: Bytes::from(init_code), + access_list: Default::default(), + }; + + // Encode the reference transaction + let mut reference_rlp = Vec::new(); + reference_tx.encode(&mut reference_rlp); + + // Add EIP-1559 transaction type prefix (0x02) + let mut prefixed_reference_rlp = vec![0x02]; + prefixed_reference_rlp.extend_from_slice(&reference_rlp); + + // Compare the RLP outputs + assert_eq!( + our_rlp, prefixed_reference_rlp, + "Contract creation RLP encoding mismatch!\nOur RLP: {:?}\nReference RLP: {:?}", + hex::encode(&our_rlp), + hex::encode(&prefixed_reference_rlp) + ); + }); + } + + #[test] + fn validate_uniswap_swap_transaction() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + + // Uniswap V2 Router address (mainnet) + let router_address = hex::decode("7a250d5630B4cF539739dF2C5dAcb4c659F2488D") + .expect("Valid hex"); + + // ETH to token swap parameters + let value = 1000000000000000000u128; // 1 ETH + + // Encode swapExactETHForTokens call + let amount_out_min = U256::from(1000000000u64); // Minimum tokens to receive + let weth_address = Address::from(FixedBytes::from_slice(&hex::decode("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").expect("Valid hex"))); + let token_address = Address::from(FixedBytes::from_slice(&hex::decode("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").expect("Valid hex"))); // USDC + let path = vec![weth_address, token_address]; + let recipient = Address::from([0x33; 20]); + let deadline = U256::from(1800000000u64); // Unix timestamp + + let swap_call = IUniswapV2Router::swapExactETHForTokensCall { + amountOutMin: amount_out_min, + path, + to: recipient, + deadline, + }; + let encoded_data = swap_call.abi_encode(); + + let nonce = 42u64; + let gas_limit = 200000u128; + let max_fee_per_gas = 50000000000u128; // 50 gwei + let max_priority_fee_per_gas = 3000000000u128; // 3 gwei + let chain_id = 1u64; // Ethereum mainnet + + // Build transaction with our pallet + assert_ok!(BuildEvmTx::build_evm_transaction( + RuntimeOrigin::signed(3), + Some(router_address.clone()), + value, + encoded_data.clone(), + nonce, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + chain_id, + )); + + // Get the RLP data from the emitted event + let events = System::events(); + let our_rlp = events + .iter() + .find_map(|record| match &record.event { + RuntimeEvent::BuildEvmTx(Event::TransactionBuilt { rlp_data, .. }) => { + Some(rlp_data.clone()) + } + _ => None, + }) + .expect("TransactionBuilt event not found"); + + // Build the same transaction with Alloy reference implementation + let mut to_array = [0u8; 20]; + to_array.copy_from_slice(&router_address); + + let reference_tx = TxEip1559 { + chain_id, + nonce, + gas_limit: gas_limit as u64, + max_fee_per_gas: max_fee_per_gas as u128, + max_priority_fee_per_gas: max_priority_fee_per_gas as u128, + to: TxKind::Call(Address::from(to_array)), + value: U256::from(value), + input: Bytes::from(encoded_data), + access_list: Default::default(), + }; + + // Encode the reference transaction + let mut reference_rlp = Vec::new(); + reference_tx.encode(&mut reference_rlp); + + // Add EIP-1559 transaction type prefix (0x02) + let mut prefixed_reference_rlp = vec![0x02]; + prefixed_reference_rlp.extend_from_slice(&reference_rlp); + + // Compare the RLP outputs + assert_eq!( + our_rlp, prefixed_reference_rlp, + "Uniswap swap RLP encoding mismatch!\nOur RLP: {:?}\nReference RLP: {:?}", + hex::encode(&our_rlp), + hex::encode(&prefixed_reference_rlp) + ); + }); + } +} \ No newline at end of file diff --git a/runtime/hydradx/Cargo.toml b/runtime/hydradx/Cargo.toml index a1e1a653a8..6959f82cea 100644 --- a/runtime/hydradx/Cargo.toml +++ b/runtime/hydradx/Cargo.toml @@ -63,7 +63,6 @@ pallet-route-executor = { workspace = true } pallet-staking = { workspace = true } pallet-liquidation = { workspace = true } pallet-hsm = { workspace = true } -pallet-adder-felipe = { workspace = true } pallet-build-evm-tx = { workspace = true } pallet-parameters = { workspace = true } @@ -243,7 +242,6 @@ runtime-benchmarks = [ "pallet-lbp/runtime-benchmarks", "pallet-xyk/runtime-benchmarks", "pallet-hsm/runtime-benchmarks", - "pallet-adder-felipe/runtime-benchmarks", "pallet-build-evm-tx/runtime-benchmarks", "pallet-elections-phragmen/runtime-benchmarks", "pallet-referrals/runtime-benchmarks", @@ -340,7 +338,6 @@ std = [ "pallet-duster/std", "pallet-duster-rpc-runtime-api/std", "pallet-hsm/std", - "pallet-adder-felipe/std", "pallet-build-evm-tx/std", "pallet-parameters/std", "warehouse-liquidity-mining/std", @@ -463,7 +460,6 @@ try-runtime = [ "pallet-evm-chain-id/try-runtime", "pallet-xyk/try-runtime", "pallet-hsm/try-runtime", - "pallet-adder-felipe/try-runtime", "pallet-build-evm-tx/try-runtime", "pallet-referrals/try-runtime", "pallet-evm-accounts/try-runtime", diff --git a/runtime/hydradx/src/assets.rs b/runtime/hydradx/src/assets.rs index 339d136aab..54a03fa67f 100644 --- a/runtime/hydradx/src/assets.rs +++ b/runtime/hydradx/src/assets.rs @@ -1832,14 +1832,6 @@ impl pallet_hsm::Config for Runtime { type BenchmarkHelper = helpers::benchmark_helpers::HsmBenchmarkHelper; } -parameter_types! { - pub const AdderMaxValue: u128 = 1_000_000_000_000_000; // 1 million with 9 decimals -} - -impl pallet_adder_felipe::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type MaxValue = AdderMaxValue; -} parameter_types! { pub const MaxEvmDataLength: u32 = 10_000; diff --git a/runtime/hydradx/src/lib.rs b/runtime/hydradx/src/lib.rs index 1e3e415149..4fb8ff8bd8 100644 --- a/runtime/hydradx/src/lib.rs +++ b/runtime/hydradx/src/lib.rs @@ -192,7 +192,6 @@ construct_runtime!( Liquidation: pallet_liquidation = 76, HSM: pallet_hsm = 82, Parameters: pallet_parameters = 83, - AdderFelipe: pallet_adder_felipe = 84, BuildEvmTx: pallet_build_evm_tx = 85, // ORML related modules @@ -343,7 +342,6 @@ mod benches { [pallet_whitelist, Whitelist] [pallet_dispatcher, Dispatcher] [pallet_hsm, HSM] - [pallet_adder_felipe, AdderFelipe] [pallet_build_evm_tx, BuildEvmTx] [pallet_dynamic_fees, DynamicFees] [ismp_parachain, IsmpParachain] From 449fca14acf9b31611ff4564f8f5d4431c26736c Mon Sep 17 00:00:00 2001 From: Pessina Date: Sat, 13 Sep 2025 19:12:08 +0500 Subject: [PATCH 04/32] Update test file --- pallets/build-evm-tx/src/tests/validation.rs | 190 +------------------ 1 file changed, 1 insertion(+), 189 deletions(-) diff --git a/pallets/build-evm-tx/src/tests/validation.rs b/pallets/build-evm-tx/src/tests/validation.rs index eb64ae2824..8a8a720bc2 100644 --- a/pallets/build-evm-tx/src/tests/validation.rs +++ b/pallets/build-evm-tx/src/tests/validation.rs @@ -8,7 +8,7 @@ mod validation { // Alloy reference implementation use alloy_consensus::TxEip1559; - use alloy_primitives::{Address, Bytes, TxKind, U256, FixedBytes}; + use alloy_primitives::{Address, Bytes, TxKind, U256}; use alloy_rlp::Encodable; use alloy_sol_types::{sol, SolCall}; @@ -21,18 +21,6 @@ mod validation { } } - // Define a simple Uniswap V2 Router interface for complex calls - sol! { - interface IUniswapV2Router { - function swapExactETHForTokens( - uint256 amountOutMin, - address[] calldata path, - address to, - uint256 deadline - ) external payable returns (uint256[] memory amounts); - } - } - #[test] fn validate_erc20_transfer_transaction() { new_test_ext().execute_with(|| { @@ -115,180 +103,4 @@ mod validation { ); }); } - - #[test] - fn validate_contract_creation_with_real_bytecode() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - - // Contract creation parameters - let value = 0u128; - - // Real minimal contract bytecode (stores a value and allows retrieval) - // This is the bytecode for a simple storage contract: - // contract SimpleStorage { - // uint256 public storedData; - // constructor(uint256 initialValue) { - // storedData = initialValue; - // } - // } - // Bytecode includes constructor logic and runtime code - let init_code = hex::decode( - "608060405234801561001057600080fd5b506040516101583803806101588339818101604052810190610032919061007a565b80600081905550506100a7565b600080fd5b6000819050919050565b61005781610044565b811461006257600080fd5b50565b6000815190506100748161004e565b92915050565b6000602082840312156100905761008f61003f565b5b600061009e84828501610065565b91505092915050565b60a2806100b66000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80632a1afcd914602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60005481565b6000819050919050565b6057816046565b82525050565b6000602082019050607060008301846050565b9291505056fea2646970667358221220" - ).expect("Valid hex"); - - let nonce = 0u64; - let gas_limit = 200000u128; - let max_fee_per_gas = 30000000000u128; // 30 gwei - let max_priority_fee_per_gas = 2000000000u128; // 2 gwei - let chain_id = 137u64; // Polygon - - // Build contract creation with our pallet - assert_ok!(BuildEvmTx::build_evm_transaction( - RuntimeOrigin::signed(2), - None, // No to_address for contract creation - value, - init_code.clone(), - nonce, - gas_limit, - max_fee_per_gas, - max_priority_fee_per_gas, - chain_id, - )); - - // Get the RLP data from the emitted event - let events = System::events(); - let our_rlp = events - .iter() - .find_map(|record| match &record.event { - RuntimeEvent::BuildEvmTx(Event::TransactionBuilt { rlp_data, .. }) => { - Some(rlp_data.clone()) - } - _ => None, - }) - .expect("TransactionBuilt event not found"); - - // Build the same transaction with Alloy reference implementation - let reference_tx = TxEip1559 { - chain_id, - nonce, - gas_limit: gas_limit as u64, - max_fee_per_gas: max_fee_per_gas as u128, - max_priority_fee_per_gas: max_priority_fee_per_gas as u128, - to: TxKind::Create, // Contract creation - value: U256::from(value), - input: Bytes::from(init_code), - access_list: Default::default(), - }; - - // Encode the reference transaction - let mut reference_rlp = Vec::new(); - reference_tx.encode(&mut reference_rlp); - - // Add EIP-1559 transaction type prefix (0x02) - let mut prefixed_reference_rlp = vec![0x02]; - prefixed_reference_rlp.extend_from_slice(&reference_rlp); - - // Compare the RLP outputs - assert_eq!( - our_rlp, prefixed_reference_rlp, - "Contract creation RLP encoding mismatch!\nOur RLP: {:?}\nReference RLP: {:?}", - hex::encode(&our_rlp), - hex::encode(&prefixed_reference_rlp) - ); - }); - } - - #[test] - fn validate_uniswap_swap_transaction() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - - // Uniswap V2 Router address (mainnet) - let router_address = hex::decode("7a250d5630B4cF539739dF2C5dAcb4c659F2488D") - .expect("Valid hex"); - - // ETH to token swap parameters - let value = 1000000000000000000u128; // 1 ETH - - // Encode swapExactETHForTokens call - let amount_out_min = U256::from(1000000000u64); // Minimum tokens to receive - let weth_address = Address::from(FixedBytes::from_slice(&hex::decode("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").expect("Valid hex"))); - let token_address = Address::from(FixedBytes::from_slice(&hex::decode("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").expect("Valid hex"))); // USDC - let path = vec![weth_address, token_address]; - let recipient = Address::from([0x33; 20]); - let deadline = U256::from(1800000000u64); // Unix timestamp - - let swap_call = IUniswapV2Router::swapExactETHForTokensCall { - amountOutMin: amount_out_min, - path, - to: recipient, - deadline, - }; - let encoded_data = swap_call.abi_encode(); - - let nonce = 42u64; - let gas_limit = 200000u128; - let max_fee_per_gas = 50000000000u128; // 50 gwei - let max_priority_fee_per_gas = 3000000000u128; // 3 gwei - let chain_id = 1u64; // Ethereum mainnet - - // Build transaction with our pallet - assert_ok!(BuildEvmTx::build_evm_transaction( - RuntimeOrigin::signed(3), - Some(router_address.clone()), - value, - encoded_data.clone(), - nonce, - gas_limit, - max_fee_per_gas, - max_priority_fee_per_gas, - chain_id, - )); - - // Get the RLP data from the emitted event - let events = System::events(); - let our_rlp = events - .iter() - .find_map(|record| match &record.event { - RuntimeEvent::BuildEvmTx(Event::TransactionBuilt { rlp_data, .. }) => { - Some(rlp_data.clone()) - } - _ => None, - }) - .expect("TransactionBuilt event not found"); - - // Build the same transaction with Alloy reference implementation - let mut to_array = [0u8; 20]; - to_array.copy_from_slice(&router_address); - - let reference_tx = TxEip1559 { - chain_id, - nonce, - gas_limit: gas_limit as u64, - max_fee_per_gas: max_fee_per_gas as u128, - max_priority_fee_per_gas: max_priority_fee_per_gas as u128, - to: TxKind::Call(Address::from(to_array)), - value: U256::from(value), - input: Bytes::from(encoded_data), - access_list: Default::default(), - }; - - // Encode the reference transaction - let mut reference_rlp = Vec::new(); - reference_tx.encode(&mut reference_rlp); - - // Add EIP-1559 transaction type prefix (0x02) - let mut prefixed_reference_rlp = vec![0x02]; - prefixed_reference_rlp.extend_from_slice(&reference_rlp); - - // Compare the RLP outputs - assert_eq!( - our_rlp, prefixed_reference_rlp, - "Uniswap swap RLP encoding mismatch!\nOur RLP: {:?}\nReference RLP: {:?}", - hex::encode(&our_rlp), - hex::encode(&prefixed_reference_rlp) - ); - }); - } } \ No newline at end of file From 33566d9011694e3dea3271c1ddd5d9c5b9c2fbc8 Mon Sep 17 00:00:00 2001 From: Pessina Date: Sat, 13 Sep 2025 23:40:44 +0500 Subject: [PATCH 05/32] Replace signet.rs with alloy --- Cargo.lock | 384 +++++-------------- Cargo.toml | 1 + pallets/build-evm-tx/Cargo.toml | 20 +- pallets/build-evm-tx/src/lib.rs | 144 ++++--- pallets/build-evm-tx/src/tests/mock.rs | 51 ++- pallets/build-evm-tx/src/tests/tests.rs | 148 ++----- pallets/build-evm-tx/src/tests/validation.rs | 138 +++---- runtime/hydradx/src/assets.rs | 1 - runtime/hydradx/src/lib.rs | 2 +- 9 files changed, 295 insertions(+), 594 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de33474419..d88cd3e6d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,7 +98,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.3.3", + "getrandom 0.2.11", "once_cell", "version_check", "zerocopy", @@ -134,36 +134,6 @@ dependencies = [ "derive_more 1.0.0", ] -[[package]] -name = "alloy-core" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c618bd382f0bc2ac26a7e4bfae01c9b015ca8f21b37ca40059ae35a7e62b3dc6" -dependencies = [ - "alloy-dyn-abi", - "alloy-json-abi", - "alloy-primitives", - "alloy-rlp", - "alloy-sol-types", -] - -[[package]] -name = "alloy-dyn-abi" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41056bde53ae10ffbbf11618efbe1e0290859e5eab0fe9ef82ebdb62f12a866f" -dependencies = [ - "alloy-json-abi", - "alloy-primitives", - "alloy-sol-type-parser", - "alloy-sol-types", - "const-hex", - "itoa", - "serde", - "serde_json", - "winnow 0.6.20", -] - [[package]] name = "alloy-eip2930" version = "0.1.0" @@ -202,39 +172,26 @@ dependencies = [ "sha2 0.10.8", ] -[[package]] -name = "alloy-json-abi" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c357da577dfb56998d01f574d81ad7a1958d248740a7981b205d69d65a7da404" -dependencies = [ - "alloy-primitives", - "alloy-sol-type-parser", - "serde", - "serde_json", -] - [[package]] name = "alloy-primitives" -version = "0.8.15" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6259a506ab13e1d658796c31e6e39d2e2ee89243bcc505ddc613b35732e0a430" +checksum = "8c77490fe91a0ce933a1f219029521f20fc28c2c0ca95d53fa4da9c00b8d9d4e" dependencies = [ "alloy-rlp", "bytes", "cfg-if", "const-hex", - "derive_more 1.0.0", + "derive_more 2.0.1", "foldhash", "hashbrown 0.15.2", - "hex-literal", "indexmap 2.7.0", "itoa", "k256", "keccak-asm", "paste", "proptest", - "rand", + "rand 0.8.5", "ruint", "rustc-hash 2.1.0", "serde", @@ -264,77 +221,6 @@ dependencies = [ "syn 2.0.106", ] -[[package]] -name = "alloy-sol-macro" -version = "0.8.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10ae8e9a91d328ae954c22542415303919aabe976fe7a92eb06db1b68fd59f2" -dependencies = [ - "alloy-sol-macro-expander", - "alloy-sol-macro-input", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "alloy-sol-macro-expander" -version = "0.8.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83ad5da86c127751bc607c174d6c9fe9b85ef0889a9ca0c641735d77d4f98f26" -dependencies = [ - "alloy-sol-macro-input", - "const-hex", - "heck 0.5.0", - "indexmap 2.7.0", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn 2.0.106", - "syn-solidity", - "tiny-keccak", -] - -[[package]] -name = "alloy-sol-macro-input" -version = "0.8.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3d30f0d3f9ba3b7686f3ff1de9ee312647aac705604417a2f40c604f409a9e" -dependencies = [ - "const-hex", - "dunce", - "heck 0.5.0", - "macro-string", - "proc-macro2", - "quote", - "syn 2.0.106", - "syn-solidity", -] - -[[package]] -name = "alloy-sol-type-parser" -version = "0.8.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d162f8524adfdfb0e4bd0505c734c985f3e2474eb022af32eef0d52a4f3935c" -dependencies = [ - "serde", - "winnow 0.7.13", -] - -[[package]] -name = "alloy-sol-types" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1174cafd6c6d810711b4e00383037bdb458efc4fe3dbafafa16567e0320c54d8" -dependencies = [ - "alloy-json-abi", - "alloy-primitives", - "alloy-sol-macro", - "const-hex", - "serde", -] - [[package]] name = "alloy-trie" version = "0.7.9" @@ -737,7 +623,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ "num-traits", - "rand", + "rand 0.8.5", ] [[package]] @@ -748,7 +634,6 @@ checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ "num-traits", "rand 0.8.5", - "rayon", ] [[package]] @@ -1300,9 +1185,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "bitvec" @@ -1782,9 +1667,6 @@ name = "bytes" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" -dependencies = [ - "serde", -] [[package]] name = "bzip2-sys" @@ -1880,12 +1762,14 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.94" +version = "1.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7" +checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44" dependencies = [ + "find-msvc-tools", "jobserver", "libc", + "shlex", ] [[package]] @@ -3436,7 +3320,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" dependencies = [ - "derive_more-impl", + "derive_more-impl 1.0.0", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl 2.0.1", ] [[package]] @@ -3451,24 +3344,15 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "derive_more" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl", -] - [[package]] name = "derive_more-impl" -version = "1.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", "unicode-xid", ] @@ -3612,12 +3496,6 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - [[package]] name = "dyn-clonable" version = "0.9.0" @@ -3702,9 +3580,9 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "elliptic-curve" @@ -3869,7 +3747,7 @@ dependencies = [ "hash-db", "hash256-std-hasher", "parity-scale-codec", - "rlp 0.5.2", + "rlp", "scale-info", "serde", "sha3", @@ -3955,7 +3833,7 @@ dependencies = [ "log", "parity-scale-codec", "primitive-types 0.12.2", - "rlp 0.5.2", + "rlp", "scale-info", "serde", "sha3", @@ -4191,8 +4069,8 @@ dependencies = [ "pallet-evm", "parity-scale-codec", "prometheus", - "rand", - "rlp 0.5.2", + "rand 0.8.5", + "rlp", "sc-client-api", "sc-consensus-aura", "sc-network", @@ -4231,7 +4109,7 @@ dependencies = [ "ethereum", "ethereum-types 0.14.1", "jsonrpsee", - "rlp 0.5.2", + "rlp", "rustc-hex", "serde", "serde_json", @@ -4329,6 +4207,12 @@ dependencies = [ "scale-info", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" + [[package]] name = "fixed" version = "1.15.0" @@ -4377,9 +4261,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "foreign-types" @@ -5093,7 +4977,7 @@ dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasi 0.14.5+wasi-0.2.4", ] [[package]] @@ -5295,7 +5179,6 @@ dependencies = [ "allocator-api2", "equivalent", "foldhash", - "serde", ] [[package]] @@ -5330,9 +5213,6 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -dependencies = [ - "serde", -] [[package]] name = "hex-conservative" @@ -6050,7 +5930,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" dependencies = [ - "rlp 0.5.2", + "rlp", ] [[package]] @@ -6111,7 +5991,6 @@ checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown 0.15.2", - "serde", ] [[package]] @@ -6332,10 +6211,11 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.27" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ + "getrandom 0.3.3", "libc", ] @@ -6603,9 +6483,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.168" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libloading" @@ -7063,7 +6943,7 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.4", "libc", "redox_syscall 0.4.1", ] @@ -7360,17 +7240,6 @@ dependencies = [ "libc", ] -[[package]] -name = "macro-string" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "macro_magic" version = "0.5.1" @@ -8000,7 +7869,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.4", "cfg-if", "cfg_aliases", "libc", @@ -8266,9 +8135,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "oorandom" @@ -8294,7 +8163,7 @@ version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.4", "cfg-if", "foreign-types", "libc", @@ -9015,17 +8884,14 @@ name = "pallet-build-evm-tx" version = "1.0.0" dependencies = [ "alloy-consensus", - "alloy-core", "alloy-primitives", "alloy-rlp", - "alloy-sol-types", + "derive_more 1.0.0", "frame-benchmarking", "frame-support", "frame-system", - "hex", "parity-scale-codec", "scale-info", - "signet-rs", "sp-core", "sp-io", "sp-runtime", @@ -13845,28 +13711,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "proc-macro-warning" version = "0.4.2" @@ -13937,17 +13781,17 @@ dependencies = [ [[package]] name = "proptest" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" +checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.6.0", + "bitflags 2.9.4", "lazy_static", "num-traits", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand 0.9.2", + "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax 0.8.2", "rusty-fork", @@ -14239,9 +14083,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", - "serde", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -14303,11 +14156,11 @@ dependencies = [ [[package]] name = "rand_xorshift" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "rand_core 0.6.4", + "rand_core 0.9.3", ] [[package]] @@ -14325,7 +14178,7 @@ version = "11.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb9ee317cfe3fbd54b36a511efc1edd42e216903c9cd575e686dd68a2ba90d8d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.4", ] [[package]] @@ -14565,16 +14418,6 @@ dependencies = [ "rustc-hex", ] -[[package]] -name = "rlp" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa24e92bb2a83198bb76d661a71df9f7076b8c420b8696e4d3d97d50d94479e3" -dependencies = [ - "bytes", - "rustc-hex", -] - [[package]] name = "rlp-derive" version = "0.1.0" @@ -14769,9 +14612,9 @@ dependencies = [ [[package]] name = "ruint" -version = "1.12.4" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5ef8fb1dd8de3870cb8400d51b4c2023854bbafd5431a3ac7e7317243e22d2f" +checksum = "9ecb38f82477f20c5c3d62ef52d7c4e536e38ea9b73fb570a20c5cae0e14bcf6" dependencies = [ "alloy-rlp", "ark-ff 0.3.0", @@ -14785,8 +14628,9 @@ dependencies = [ "parity-scale-codec", "primitive-types 0.12.2", "proptest", - "rand", - "rlp 0.5.2", + "rand 0.8.5", + "rand 0.9.2", + "rlp", "ruint-macro", "serde", "valuable", @@ -15038,7 +14882,7 @@ version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.4", "errno", "libc", "linux-raw-sys 0.4.12", @@ -16034,7 +15878,7 @@ dependencies = [ "multiaddr 0.18.2", "multihash 0.19.1", "rand 0.8.5", - "thiserror 1.0.63", + "thiserror", "zeroize", ] @@ -16669,7 +16513,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.4", "core-foundation", "core-foundation-sys", "libc", @@ -16915,9 +16759,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" @@ -16938,17 +16782,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "signet-rs" -version = "0.2.5" -source = "git+https://github.com/sig-net/signet.rs?branch=pre-compiled#6945eb902ac6b78c7511611bbc2e71a7c380a880" -dependencies = [ - "hex", - "rlp 0.6.1", - "serde", - "serde_json", -] - [[package]] name = "simba" version = "0.8.1" @@ -16988,7 +16821,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cae9a3fcdadafb6d97f4c0e007e4247b114ee0f119f650c3cbf3a8b3a1479694" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.4", ] [[package]] @@ -18809,18 +18642,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "syn-solidity" -version = "0.8.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4560533fbd6914b94a8fb5cc803ed6801c3455668db3b810702c57612bac9412" -dependencies = [ - "paste", - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "synstructure" version = "0.12.6" @@ -19349,7 +19170,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.4", "bytes", "http 1.2.0", "http-body 1.0.1", @@ -19864,11 +19685,20 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" -version = "0.14.2+wasi-0.2.4" +version = "0.14.5+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4494f6290a82f5fe584817a676a34b9d6763e8d9d18204009fb31dceca98fd4" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.0+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "03fa2761397e5bd52002cd7e73110c71af2109aca4e521a9f40473fe685b0a24" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] @@ -20770,15 +20600,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winnow" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" -dependencies = [ - "memchr", -] - [[package]] name = "winreg" version = "0.50.0" @@ -20790,13 +20611,10 @@ dependencies = [ ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.6.0", -] +checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" [[package]] name = "wyz" diff --git a/Cargo.toml b/Cargo.toml index 8be69c5945..704fad2d02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ members = [ 'pallets/broadcast', 'liquidation-worker-support', 'pallets/hsm', + 'pallets/build-evm-tx', ] resolver = "2" diff --git a/pallets/build-evm-tx/Cargo.toml b/pallets/build-evm-tx/Cargo.toml index aacd7af5e5..ab21f36f72 100644 --- a/pallets/build-evm-tx/Cargo.toml +++ b/pallets/build-evm-tx/Cargo.toml @@ -20,24 +20,19 @@ sp-std = { workspace = true } sp-runtime = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } - -# External dependencies -signet-rs = { git = "https://github.com/sig-net/signet.rs", branch = "pre-compiled", default-features = false, features = ["evm"] } -hex = { version = "0.4", default-features = false, features = ["alloc"] } sp-io = { workspace = true } +alloy-consensus = { version = "0.8", default-features = false } +alloy-primitives = { version = "0.8", default-features = false } +alloy-rlp = { version = "0.3", default-features = false } +derive_more = { version = "1.0", default-features = false, features = ["display"] } + # Optionals frame-benchmarking = { workspace = true, optional = true } [dev-dependencies] sp-io = { workspace = true } sp-core = { workspace = true } -# For transaction validation tests -alloy-consensus = "0.8" -alloy-primitives = "0.8" -alloy-rlp = "0.3" -alloy-sol-types = "0.8" -alloy-core = "0.8" [features] default = ["std"] @@ -54,6 +49,9 @@ std = [ "sp-runtime/std", "sp-io/std", "scale-info/std", - "hex/std", + "alloy-consensus/std", + "alloy-primitives/std", + "alloy-rlp/std", + "derive_more/std", ] try-runtime = ["frame-support/try-runtime"] \ No newline at end of file diff --git a/pallets/build-evm-tx/src/lib.rs b/pallets/build-evm-tx/src/lib.rs index 4661a2491d..ad0fbf66c8 100644 --- a/pallets/build-evm-tx/src/lib.rs +++ b/pallets/build-evm-tx/src/lib.rs @@ -1,5 +1,12 @@ #![cfg_attr(not(feature = "std"), no_std)] +use frame_support::pallet_prelude::*; +use sp_std::vec::Vec; + +use alloy_consensus::{TxEip1559, TxType}; +use alloy_primitives::{Address, Bytes, TxKind, U256}; +use alloy_rlp::Encodable; + pub use pallet::*; #[cfg(test)] @@ -7,100 +14,91 @@ mod tests; #[frame_support::pallet] pub mod pallet { - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; - use sp_std::vec::Vec; - use signet_rs::{TransactionBuilder, TxBuilder, EVM}; - + use super::*; + + #[pallet::pallet] + pub struct Pallet(_); + #[pallet::config] pub trait Config: frame_system::Config { - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - + /// Maximum length of transaction data #[pallet::constant] type MaxDataLength: Get; } - - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - TransactionBuilt { - who: T::AccountId, - rlp_data: Vec, - }, - } - + #[pallet::error] pub enum Error { - /// The provided address is not exactly 20 bytes - InvalidAddressLength, - /// Transaction data exceeds the maximum allowed length + /// Transaction data exceeds maximum allowed length DataTooLong, - /// Max priority fee per gas is greater than max fee per gas + /// Invalid address format - must be exactly 20 bytes + InvalidAddress, + /// Priority fee cannot exceed max fee per gas (EIP-1559 requirement) InvalidGasPrice, } - - #[pallet::pallet] - pub struct Pallet(_); - - #[pallet::call] + impl Pallet { - /// Build an EVM transaction (regular or contract creation). - /// - /// - `to_address`: Some(address) for regular transaction, None for contract creation - /// - `data`: Transaction data (call data for regular tx, init code for contract creation) - #[pallet::call_index(0)] - #[pallet::weight(Weight::from_parts(10_000, 0))] + /// Build an EIP-1559 EVM transaction and return the RLP-encoded data + /// + /// This function is called by other pallets to generate RLP-encoded + /// EVM transactions for cross-chain operations. + /// + /// # Parameters + /// - `to_address`: Optional recipient address (None for contract creation) + /// - `value`: ETH value in wei + /// - `data`: Transaction data/calldata + /// - `nonce`: Transaction nonce + /// - `gas_limit`: Maximum gas units for transaction + /// - `max_fee_per_gas`: Maximum total fee per gas (base + priority) + /// - `max_priority_fee_per_gas`: Maximum priority fee (tip) per gas + /// - `chain_id`: Target EVM chain ID + /// + /// # Returns + /// RLP-encoded transaction data with EIP-2718 type prefix (0x02 for EIP-1559) pub fn build_evm_transaction( - origin: OriginFor, to_address: Option>, value: u128, data: Vec, nonce: u64, - gas_limit: u128, + gas_limit: u64, max_fee_per_gas: u128, max_priority_fee_per_gas: u128, chain_id: u64, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - // Validate data length + ) -> Result, DispatchError> { + // Validate data doesn't exceed maximum size ensure!(data.len() <= T::MaxDataLength::get() as usize, Error::::DataTooLong); - - // Validate to_address if provided - if let Some(ref addr) = to_address { - ensure!(addr.len() == 20, Error::::InvalidAddressLength); - } - - // Validate gas prices relationship + + // Validate EIP-1559 gas price relationship ensure!(max_priority_fee_per_gas <= max_fee_per_gas, Error::::InvalidGasPrice); - - let mut builder = TransactionBuilder::new::() - .nonce(nonce) - .value(value) - .input(data) - .max_priority_fee_per_gas(max_priority_fee_per_gas) - .max_fee_per_gas(max_fee_per_gas) - .gas_limit(gas_limit) - .chain_id(chain_id); - - // Add 'to' address if provided (regular transaction) - // Omit for contract creation - if let Some(addr) = to_address { - let mut to_array = [0u8; 20]; - to_array.copy_from_slice(&addr[0..20]); - builder = builder.to(to_array); - } - - let evm_tx = builder.build(); - let rlp_encoded = evm_tx.build_for_signing(); - - Self::deposit_event(Event::TransactionBuilt { - who, - rlp_data: rlp_encoded, - }); - - Ok(()) + + // Parse destination address or mark as contract creation + let to = match to_address { + Some(addr) => { + let address = Address::try_from(addr.as_slice()) + .map_err(|_| Error::::InvalidAddress)?; + TxKind::Call(address) + } + None => TxKind::Create, + }; + + // Construct the EIP-1559 transaction + let tx = TxEip1559 { + chain_id, + nonce, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + to, + value: U256::from(value), + input: Bytes::from(data), + access_list: Default::default(), + }; + + // Encode transaction to RLP format with EIP-2718 type prefix + let mut typed_tx = Vec::new(); + typed_tx.push(TxType::Eip1559 as u8); + tx.encode(&mut typed_tx); + + Ok(typed_tx) } } } \ No newline at end of file diff --git a/pallets/build-evm-tx/src/tests/mock.rs b/pallets/build-evm-tx/src/tests/mock.rs index 1f76ecfdd2..b45a304685 100644 --- a/pallets/build-evm-tx/src/tests/mock.rs +++ b/pallets/build-evm-tx/src/tests/mock.rs @@ -1,49 +1,49 @@ -// Mock runtime for testing build-evm-tx pallet - -use crate::{self as build_evm_tx}; -use frame_support::{parameter_types, traits::Everything}; +use crate as pallet_build_evm_tx; +use frame_support::{ + construct_runtime, parameter_types, + traits::{ConstU16, ConstU32, ConstU64}, +}; use sp_core::H256; use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, BuildStorage, }; -pub type Block = frame_system::mocking::MockBlock; +type Block = frame_system::mocking::MockBlock; -// Configure a mock runtime to test the pallet -frame_support::construct_runtime!( - pub enum Test { +construct_runtime!( + pub enum Test + { System: frame_system, - BuildEvmTx: build_evm_tx, + BuildEvmTx: pallet_build_evm_tx, } ); -// System pallet configuration impl frame_system::Config for Test { - type BaseCallFilter = Everything; + type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); type BlockLength = (); + type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; - type RuntimeTask = RuntimeTask; type Nonce = u64; - type Block = Block; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; + type Block = Block; type RuntimeEvent = RuntimeEvent; - type BlockHashCount = frame_support::traits::ConstU64<250>; - type DbWeight = (); + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); type SystemWeightInfo = (); - type SS58Prefix = (); + type SS58Prefix = ConstU16<42>; type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; + type MaxConsumers = ConstU32<16>; + type RuntimeTask = (); type SingleBlockMigrations = (); type MultiBlockMigrator = (); type PreInherents = (); @@ -51,20 +51,19 @@ impl frame_system::Config for Test { type PostTransactions = (); } -// Build EVM TX pallet configuration parameter_types! { pub const MaxDataLength: u32 = 100_000; } -impl build_evm_tx::Config for Test { - type RuntimeEvent = RuntimeEvent; +impl pallet_build_evm_tx::Config for Test { type MaxDataLength = MaxDataLength; } -// Test externalities initialization pub fn new_test_ext() -> sp_io::TestExternalities { - frame_system::GenesisConfig::::default() - .build_storage() - .unwrap() - .into() -} \ No newline at end of file + let t = as BuildStorage>::build_storage( + &frame_system::GenesisConfig::default(), + ) + .unwrap(); + sp_io::TestExternalities::new(t) +} + diff --git a/pallets/build-evm-tx/src/tests/tests.rs b/pallets/build-evm-tx/src/tests/tests.rs index 55eda6c554..7cb730606c 100644 --- a/pallets/build-evm-tx/src/tests/tests.rs +++ b/pallets/build-evm-tx/src/tests/tests.rs @@ -1,142 +1,60 @@ -// Essential input validation tests for build-evm-tx pallet - -use crate::Error; -use frame_support::{assert_noop, assert_ok}; - use super::mock::*; +use crate::Error; #[test] fn invalid_address_length_fails() { new_test_ext().execute_with(|| { - // Test with address shorter than 20 bytes - let short_address = vec![1u8; 19]; - assert_noop!( - BuildEvmTx::build_evm_transaction( - RuntimeOrigin::signed(1), - Some(short_address), - 1000u128, - vec![], - 1u64, - 21000u128, - 20u128, - 10u128, - 1u64, - ), - Error::::InvalidAddressLength - ); + let invalid_address = vec![0x42; 19]; // Should be 20 bytes - // Test with address longer than 20 bytes - let long_address = vec![1u8; 21]; - assert_noop!( - BuildEvmTx::build_evm_transaction( - RuntimeOrigin::signed(1), - Some(long_address), - 1000u128, - vec![], - 1u64, - 21000u128, - 20u128, - 10u128, - 1u64, - ), - Error::::InvalidAddressLength + let result = BuildEvmTx::build_evm_transaction( + Some(invalid_address), + 0, + vec![], + 0, + 21000, + 20000000000, + 1000000000, + 1, ); - // Verify exactly 20 bytes works - let valid_address = vec![1u8; 20]; - assert_ok!(BuildEvmTx::build_evm_transaction( - RuntimeOrigin::signed(1), - Some(valid_address), - 1000u128, - vec![], - 1u64, - 21000u128, - 20u128, - 10u128, - 1u64, - )); + assert_eq!(result, Err(Error::::InvalidAddress.into())); }); } #[test] fn data_too_long_fails() { new_test_ext().execute_with(|| { - // Create data that exceeds MaxDataLength - let large_data = vec![0u8; 100_001]; - assert_noop!( - BuildEvmTx::build_evm_transaction( - RuntimeOrigin::signed(1), - None, - 0u128, - large_data, - 1u64, - 21000u128, - 20u128, - 10u128, - 1u64, - ), - Error::::DataTooLong - ); + let large_data = vec![0xff; 100_001]; // Exceeds MaxDataLength - // Verify data at exact limit works - let max_data = vec![0u8; 100_000]; - assert_ok!(BuildEvmTx::build_evm_transaction( - RuntimeOrigin::signed(1), + let result = BuildEvmTx::build_evm_transaction( None, - 0u128, - max_data, - 1u64, - 21000u128, - 20u128, - 10u128, - 1u64, - )); + 0, + large_data, + 0, + 21000, + 20000000000, + 1000000000, + 1, + ); + + assert_eq!(result, Err(Error::::DataTooLong.into())); }); } #[test] fn invalid_gas_price_relationship_fails() { new_test_ext().execute_with(|| { - // Test when max_priority_fee > max_fee - assert_noop!( - BuildEvmTx::build_evm_transaction( - RuntimeOrigin::signed(1), - None, - 0u128, - vec![], - 1u64, - 21000u128, - 10u128, // max_fee_per_gas - 20u128, // max_priority_fee_per_gas > max_fee_per_gas - 1u64, - ), - Error::::InvalidGasPrice - ); - - // Verify equal values work (priority fee can equal max fee) - assert_ok!(BuildEvmTx::build_evm_transaction( - RuntimeOrigin::signed(1), + let result = BuildEvmTx::build_evm_transaction( None, - 0u128, + 0, vec![], - 1u64, - 21000u128, - 20u128, // max_fee_per_gas - 20u128, // max_priority_fee_per_gas = max_fee_per_gas - 1u64, - )); + 0, + 21000, + 20000000000, // max_fee_per_gas + 30000000000, // max_priority_fee_per_gas (higher than max_fee) + 1, + ); - // Verify priority fee less than max fee works - assert_ok!(BuildEvmTx::build_evm_transaction( - RuntimeOrigin::signed(1), - None, - 0u128, - vec![], - 2u64, - 21000u128, - 20u128, // max_fee_per_gas - 10u128, // max_priority_fee_per_gas < max_fee_per_gas - 1u64, - )); + assert_eq!(result, Err(Error::::InvalidGasPrice.into())); }); } \ No newline at end of file diff --git a/pallets/build-evm-tx/src/tests/validation.rs b/pallets/build-evm-tx/src/tests/validation.rs index 8a8a720bc2..79120d64e7 100644 --- a/pallets/build-evm-tx/src/tests/validation.rs +++ b/pallets/build-evm-tx/src/tests/validation.rs @@ -1,105 +1,75 @@ -// Validation tests comparing with reference Ethereum implementation +// Validation test comparing pallet output with pre-computed Alloy RLP #[cfg(test)] mod validation { use super::super::mock::*; - use crate::Event; - use frame_support::assert_ok; - - // Alloy reference implementation - use alloy_consensus::TxEip1559; - use alloy_primitives::{Address, Bytes, TxKind, U256}; - use alloy_rlp::Encodable; - use alloy_sol_types::{sol, SolCall}; - - // Define ERC20 interface for encoding function calls - sol! { - interface IERC20 { - function transfer(address to, uint256 amount) external returns (bool); - function approve(address spender, uint256 amount) external returns (bool); - function balanceOf(address account) external view returns (uint256); - } - } #[test] - fn validate_erc20_transfer_transaction() { + fn validate_against_precomputed_alloy_rlp() { new_test_ext().execute_with(|| { - System::set_block_number(1); - - // Transaction parameters - let token_address = vec![0x42; 20]; // ERC20 token address - let value = 0u128; // No ETH value for ERC20 transfer - - // Properly encode an ERC20 transfer call - let recipient = Address::from([0x11; 20]); - let amount = U256::from(1000000u64); // 1 million tokens - let transfer_call = IERC20::transferCall { - to: recipient, - amount, - }; - let encoded_data = transfer_call.abi_encode(); - + // Fixed transaction parameters + let to_address = vec![ + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + ]; + let value = 1000000000000000000u128; // 1 ETH + let data = vec![0x12, 0x34, 0x56, 0x78]; let nonce = 5u64; - let gas_limit = 65000u128; + let gas_limit = 21000u64; let max_fee_per_gas = 20000000000u128; // 20 gwei let max_priority_fee_per_gas = 1000000000u128; // 1 gwei let chain_id = 1u64; // Ethereum mainnet - - // Build transaction with our pallet - assert_ok!(BuildEvmTx::build_evm_transaction( - RuntimeOrigin::signed(1), - Some(token_address.clone()), + + // Pre-computed RLP using Alloy directly with the above parameters + // This was generated using: + // let tx = TxEip1559 { + // chain_id: 1, + // nonce: 5, + // gas_limit: 21000, + // max_fee_per_gas: 20000000000, + // max_priority_fee_per_gas: 1000000000, + // to: TxKind::Call(Address::from([0x11; 20])), + // value: U256::from(1000000000000000000u128), + // input: Bytes::from(vec![0x12, 0x34, 0x56, 0x78]), + // access_list: Default::default(), + // }; + // let mut rlp = Vec::new(); + // tx.encode(&mut rlp); + // prepend with 0x02 for EIP-1559 + let expected_rlp = vec![ + 0x02, // EIP-1559 transaction type + 0xf4, // RLP list header (correct length) + 0x01, // chain_id + 0x05, // nonce + 0x84, 0x3b, 0x9a, 0xca, 0x00, // max_priority_fee_per_gas (1 gwei) + 0x85, 0x04, 0xa8, 0x17, 0xc8, 0x00, // max_fee_per_gas (20 gwei) + 0x82, 0x52, 0x08, // gas_limit (21000) + 0x94, // to address prefix + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // to address + 0x88, 0x0d, 0xe0, 0xb6, 0xb3, 0xa7, 0x64, 0x00, 0x00, // value (1 ETH) + 0x84, 0x12, 0x34, 0x56, 0x78, // data + 0xc0, // empty access_list + ]; + + // Test the public function that other pallets call + let returned_rlp = BuildEvmTx::build_evm_transaction( + Some(to_address), value, - encoded_data.clone(), + data, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas, chain_id, - )); - - // Get the RLP data from the emitted event - let events = System::events(); - let our_rlp = events - .iter() - .find_map(|record| match &record.event { - RuntimeEvent::BuildEvmTx(Event::TransactionBuilt { rlp_data, .. }) => { - Some(rlp_data.clone()) - } - _ => None, - }) - .expect("TransactionBuilt event not found"); - - // Build the same transaction with Alloy reference implementation - let mut to_array = [0u8; 20]; - to_array.copy_from_slice(&token_address); - - let reference_tx = TxEip1559 { - chain_id, - nonce, - gas_limit: gas_limit as u64, - max_fee_per_gas: max_fee_per_gas as u128, - max_priority_fee_per_gas: max_priority_fee_per_gas as u128, - to: TxKind::Call(Address::from(to_array)), - value: U256::from(value), - input: Bytes::from(encoded_data), - access_list: Default::default(), - }; - - // Encode the reference transaction - let mut reference_rlp = Vec::new(); - reference_tx.encode(&mut reference_rlp); - - // Add EIP-1559 transaction type prefix (0x02) - let mut prefixed_reference_rlp = vec![0x02]; - prefixed_reference_rlp.extend_from_slice(&reference_rlp); - - // Compare the RLP outputs + ).expect("Failed to build transaction"); + + // Verify the returned RLP matches expected assert_eq!( - our_rlp, prefixed_reference_rlp, - "ERC20 transfer RLP encoding mismatch!\nOur RLP: {:?}\nReference RLP: {:?}", - hex::encode(&our_rlp), - hex::encode(&prefixed_reference_rlp) + returned_rlp, expected_rlp, + "RLP mismatch!\nGot: {:?}\nExpected: {:?}", + alloy_primitives::hex::encode(&returned_rlp), + alloy_primitives::hex::encode(&expected_rlp) ); }); } diff --git a/runtime/hydradx/src/assets.rs b/runtime/hydradx/src/assets.rs index 54a03fa67f..4f2888edc2 100644 --- a/runtime/hydradx/src/assets.rs +++ b/runtime/hydradx/src/assets.rs @@ -1838,7 +1838,6 @@ parameter_types! { } impl pallet_build_evm_tx::Config for Runtime { - type RuntimeEvent = RuntimeEvent; type MaxDataLength = MaxEvmDataLength; } diff --git a/runtime/hydradx/src/lib.rs b/runtime/hydradx/src/lib.rs index 4fb8ff8bd8..fc85dc3032 100644 --- a/runtime/hydradx/src/lib.rs +++ b/runtime/hydradx/src/lib.rs @@ -192,7 +192,7 @@ construct_runtime!( Liquidation: pallet_liquidation = 76, HSM: pallet_hsm = 82, Parameters: pallet_parameters = 83, - BuildEvmTx: pallet_build_evm_tx = 85, + BuildEvmTx: pallet_build_evm_tx = 86, // ORML related modules Tokens: orml_tokens = 77, From 5b1188763b5550826408085b9bd43687894a9ca0 Mon Sep 17 00:00:00 2001 From: Pessina Date: Sun, 14 Sep 2025 00:43:15 +0500 Subject: [PATCH 06/32] Clean up --- pallets/build-evm-tx/src/lib.rs | 14 ++------ pallets/build-evm-tx/src/tests/mock.rs | 22 ++++++++---- pallets/build-evm-tx/src/tests/mod.rs | 2 +- pallets/build-evm-tx/src/tests/tests.rs | 36 +++++--------------- pallets/build-evm-tx/src/tests/validation.rs | 35 +++++++------------ runtime/hydradx/src/lib.rs | 2 +- 6 files changed, 42 insertions(+), 69 deletions(-) diff --git a/pallets/build-evm-tx/src/lib.rs b/pallets/build-evm-tx/src/lib.rs index ad0fbf66c8..1062ec35bc 100644 --- a/pallets/build-evm-tx/src/lib.rs +++ b/pallets/build-evm-tx/src/lib.rs @@ -39,9 +39,6 @@ pub mod pallet { impl Pallet { /// Build an EIP-1559 EVM transaction and return the RLP-encoded data /// - /// This function is called by other pallets to generate RLP-encoded - /// EVM transactions for cross-chain operations. - /// /// # Parameters /// - `to_address`: Optional recipient address (None for contract creation) /// - `value`: ETH value in wei @@ -54,7 +51,7 @@ pub mod pallet { /// /// # Returns /// RLP-encoded transaction data with EIP-2718 type prefix (0x02 for EIP-1559) - pub fn build_evm_transaction( + pub fn build_evm_tx( to_address: Option>, value: u128, data: Vec, @@ -64,23 +61,18 @@ pub mod pallet { max_priority_fee_per_gas: u128, chain_id: u64, ) -> Result, DispatchError> { - // Validate data doesn't exceed maximum size ensure!(data.len() <= T::MaxDataLength::get() as usize, Error::::DataTooLong); - // Validate EIP-1559 gas price relationship ensure!(max_priority_fee_per_gas <= max_fee_per_gas, Error::::InvalidGasPrice); - // Parse destination address or mark as contract creation let to = match to_address { Some(addr) => { - let address = Address::try_from(addr.as_slice()) - .map_err(|_| Error::::InvalidAddress)?; + let address = Address::try_from(addr.as_slice()).map_err(|_| Error::::InvalidAddress)?; TxKind::Call(address) } None => TxKind::Create, }; - // Construct the EIP-1559 transaction let tx = TxEip1559 { chain_id, nonce, @@ -101,4 +93,4 @@ pub mod pallet { Ok(typed_tx) } } -} \ No newline at end of file +} diff --git a/pallets/build-evm-tx/src/tests/mock.rs b/pallets/build-evm-tx/src/tests/mock.rs index b45a304685..22ca11ed2e 100644 --- a/pallets/build-evm-tx/src/tests/mock.rs +++ b/pallets/build-evm-tx/src/tests/mock.rs @@ -59,11 +59,19 @@ impl pallet_build_evm_tx::Config for Test { type MaxDataLength = MaxDataLength; } -pub fn new_test_ext() -> sp_io::TestExternalities { - let t = as BuildStorage>::build_storage( - &frame_system::GenesisConfig::default(), - ) - .unwrap(); - sp_io::TestExternalities::new(t) -} +#[derive(Default)] +pub struct ExtBuilder {} + +impl ExtBuilder { + pub fn build(self) -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + let mut r: sp_io::TestExternalities = t.into(); + r.execute_with(|| { + System::set_block_number(1); + }); + + r + } +} diff --git a/pallets/build-evm-tx/src/tests/mod.rs b/pallets/build-evm-tx/src/tests/mod.rs index 1d1eb13f8b..9dc9f19563 100644 --- a/pallets/build-evm-tx/src/tests/mod.rs +++ b/pallets/build-evm-tx/src/tests/mod.rs @@ -1,4 +1,4 @@ pub mod mock; #[allow(clippy::module_inception)] mod tests; -mod validation; \ No newline at end of file +mod validation; diff --git a/pallets/build-evm-tx/src/tests/tests.rs b/pallets/build-evm-tx/src/tests/tests.rs index 7cb730606c..8d62fabcf3 100644 --- a/pallets/build-evm-tx/src/tests/tests.rs +++ b/pallets/build-evm-tx/src/tests/tests.rs @@ -3,19 +3,10 @@ use crate::Error; #[test] fn invalid_address_length_fails() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build().execute_with(|| { let invalid_address = vec![0x42; 19]; // Should be 20 bytes - let result = BuildEvmTx::build_evm_transaction( - Some(invalid_address), - 0, - vec![], - 0, - 21000, - 20000000000, - 1000000000, - 1, - ); + let result = BuildEvmTx::build_evm_tx(Some(invalid_address), 0, vec![], 0, 21000, 20000000000, 1000000000, 1); assert_eq!(result, Err(Error::::InvalidAddress.into())); }); @@ -23,19 +14,10 @@ fn invalid_address_length_fails() { #[test] fn data_too_long_fails() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build().execute_with(|| { let large_data = vec![0xff; 100_001]; // Exceeds MaxDataLength - let result = BuildEvmTx::build_evm_transaction( - None, - 0, - large_data, - 0, - 21000, - 20000000000, - 1000000000, - 1, - ); + let result = BuildEvmTx::build_evm_tx(None, 0, large_data, 0, 21000, 20000000000, 1000000000, 1); assert_eq!(result, Err(Error::::DataTooLong.into())); }); @@ -43,18 +25,18 @@ fn data_too_long_fails() { #[test] fn invalid_gas_price_relationship_fails() { - new_test_ext().execute_with(|| { - let result = BuildEvmTx::build_evm_transaction( + ExtBuilder::default().build().execute_with(|| { + let result = BuildEvmTx::build_evm_tx( None, 0, vec![], 0, 21000, - 20000000000, // max_fee_per_gas - 30000000000, // max_priority_fee_per_gas (higher than max_fee) + 20000000000, // max_fee_per_gas + 30000000000, // max_priority_fee_per_gas (higher than max_fee) 1, ); assert_eq!(result, Err(Error::::InvalidGasPrice.into())); }); -} \ No newline at end of file +} diff --git a/pallets/build-evm-tx/src/tests/validation.rs b/pallets/build-evm-tx/src/tests/validation.rs index 79120d64e7..1ed5709b1d 100644 --- a/pallets/build-evm-tx/src/tests/validation.rs +++ b/pallets/build-evm-tx/src/tests/validation.rs @@ -1,16 +1,14 @@ -// Validation test comparing pallet output with pre-computed Alloy RLP - #[cfg(test)] mod validation { use super::super::mock::*; #[test] fn validate_against_precomputed_alloy_rlp() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build().execute_with(|| { // Fixed transaction parameters let to_address = vec![ - 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, - 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, ]; let value = 1000000000000000000u128; // 1 ETH let data = vec![0x12, 0x34, 0x56, 0x78]; @@ -37,23 +35,14 @@ mod validation { // tx.encode(&mut rlp); // prepend with 0x02 for EIP-1559 let expected_rlp = vec![ - 0x02, // EIP-1559 transaction type - 0xf4, // RLP list header (correct length) - 0x01, // chain_id - 0x05, // nonce - 0x84, 0x3b, 0x9a, 0xca, 0x00, // max_priority_fee_per_gas (1 gwei) - 0x85, 0x04, 0xa8, 0x17, 0xc8, 0x00, // max_fee_per_gas (20 gwei) - 0x82, 0x52, 0x08, // gas_limit (21000) - 0x94, // to address prefix - 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, - 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // to address - 0x88, 0x0d, 0xe0, 0xb6, 0xb3, 0xa7, 0x64, 0x00, 0x00, // value (1 ETH) - 0x84, 0x12, 0x34, 0x56, 0x78, // data - 0xc0, // empty access_list + 0x02, 0xf4, 0x01, 0x05, 0x84, 0x3b, 0x9a, 0xca, 0x00, 0x85, 0x04, 0xa8, 0x17, 0xc8, 0x00, 0x82, 0x52, + 0x08, 0x94, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x88, 0x0d, 0xe0, 0xb6, 0xb3, 0xa7, 0x64, 0x00, 0x00, 0x84, 0x12, 0x34, + 0x56, 0x78, 0xc0, ]; // Test the public function that other pallets call - let returned_rlp = BuildEvmTx::build_evm_transaction( + let returned_rlp = BuildEvmTx::build_evm_tx( Some(to_address), value, data, @@ -62,15 +51,17 @@ mod validation { max_fee_per_gas, max_priority_fee_per_gas, chain_id, - ).expect("Failed to build transaction"); + ) + .expect("Failed to build transaction"); // Verify the returned RLP matches expected assert_eq!( - returned_rlp, expected_rlp, + returned_rlp, + expected_rlp, "RLP mismatch!\nGot: {:?}\nExpected: {:?}", alloy_primitives::hex::encode(&returned_rlp), alloy_primitives::hex::encode(&expected_rlp) ); }); } -} \ No newline at end of file +} diff --git a/runtime/hydradx/src/lib.rs b/runtime/hydradx/src/lib.rs index fc85dc3032..928ea0f862 100644 --- a/runtime/hydradx/src/lib.rs +++ b/runtime/hydradx/src/lib.rs @@ -192,7 +192,7 @@ construct_runtime!( Liquidation: pallet_liquidation = 76, HSM: pallet_hsm = 82, Parameters: pallet_parameters = 83, - BuildEvmTx: pallet_build_evm_tx = 86, + BuildEvmTx: pallet_build_evm_tx = 84, // ORML related modules Tokens: orml_tokens = 77, From 015ea5d012c0698ec9473ca3d1858d454da17a26 Mon Sep 17 00:00:00 2001 From: Pessina Date: Sun, 14 Sep 2025 01:43:04 +0500 Subject: [PATCH 07/32] Fix test --- Cargo.lock | 429 ++++++++++++++----------------------- Cargo.toml | 1 - runtime/hydradx/src/lib.rs | 2 +- 3 files changed, 165 insertions(+), 267 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d88cd3e6d4..230d893365 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,7 +98,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.2.11", + "getrandom", "once_cell", "version_check", "zerocopy", @@ -174,24 +174,25 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.8.25" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c77490fe91a0ce933a1f219029521f20fc28c2c0ca95d53fa4da9c00b8d9d4e" +checksum = "6259a506ab13e1d658796c31e6e39d2e2ee89243bcc505ddc613b35732e0a430" dependencies = [ "alloy-rlp", "bytes", "cfg-if", "const-hex", - "derive_more 2.0.1", + "derive_more 1.0.0", "foldhash", "hashbrown 0.15.2", + "hex-literal", "indexmap 2.7.0", "itoa", "k256", "keccak-asm", "paste", "proptest", - "rand 0.8.5", + "rand", "ruint", "rustc-hash 2.1.0", "serde", @@ -623,7 +624,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ "num-traits", - "rand 0.8.5", + "rand", ] [[package]] @@ -633,7 +634,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ "num-traits", - "rand 0.8.5", + "rand", ] [[package]] @@ -1142,18 +1143,18 @@ dependencies = [ [[package]] name = "bit-set" -version = "0.8.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.8.0" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitcoin-internals" @@ -1185,9 +1186,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bitvec" @@ -1762,14 +1763,12 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.37" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44" +checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7" dependencies = [ - "find-msvc-tools", "jobserver", "libc", - "shlex", ] [[package]] @@ -1989,7 +1988,7 @@ checksum = "71367d3385c716342014ad17e3d19f7788ae514885a1f4c24f500260fb365e1a" dependencies = [ "libc", "once_cell", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] @@ -2113,7 +2112,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom 0.2.11", + "getrandom", "once_cell", "tiny-keccak", ] @@ -2414,7 +2413,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array 0.14.7", - "rand_core 0.6.4", + "rand_core", "subtle 2.6.1", "zeroize", ] @@ -2426,7 +2425,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array 0.14.7", - "rand_core 0.6.4", + "rand_core", "typenum", ] @@ -2675,7 +2674,7 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-overseer", "polkadot-primitives", - "rand 0.8.5", + "rand", "sc-client-api", "sc-consensus", "sp-api", @@ -3072,7 +3071,7 @@ dependencies = [ "parity-scale-codec", "pin-project", "polkadot-overseer", - "rand 0.8.5", + "rand", "sc-client-api", "sc-rpc-api", "sc-service", @@ -3144,7 +3143,7 @@ checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" dependencies = [ "byteorder", "digest 0.9.0", - "rand_core 0.6.4", + "rand_core", "subtle-ng", "zeroize", ] @@ -3320,16 +3319,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" dependencies = [ - "derive_more-impl 1.0.0", -] - -[[package]] -name = "derive_more" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" -dependencies = [ - "derive_more-impl 2.0.1", + "derive_more-impl", ] [[package]] @@ -3344,18 +3334,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "derive_more-impl" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", - "unicode-xid", -] - [[package]] name = "diff" version = "0.1.13" @@ -3556,7 +3534,7 @@ checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", - "rand_core 0.6.4", + "rand_core", "serde", "sha2 0.10.8", "subtle 2.6.1", @@ -3573,16 +3551,16 @@ dependencies = [ "ed25519", "hashbrown 0.14.3", "hex", - "rand_core 0.6.4", + "rand_core", "sha2 0.10.8", "zeroize", ] [[package]] name = "either" -version = "1.15.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "elliptic-curve" @@ -3597,7 +3575,7 @@ dependencies = [ "generic-array 0.14.7", "group", "pkcs8", - "rand_core 0.6.4", + "rand_core", "sec1", "serdect", "subtle 2.6.1", @@ -4069,7 +4047,7 @@ dependencies = [ "pallet-evm", "parity-scale-codec", "prometheus", - "rand 0.8.5", + "rand", "rlp", "sc-client-api", "sc-consensus-aura", @@ -4149,7 +4127,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ - "rand_core 0.6.4", + "rand_core", "subtle 2.6.1", ] @@ -4207,12 +4185,6 @@ dependencies = [ "scale-info", ] -[[package]] -name = "find-msvc-tools" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" - [[package]] name = "fixed" version = "1.15.0" @@ -4233,7 +4205,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ "byteorder", - "rand 0.8.5", + "rand", "rustc-hex", "static_assertions", ] @@ -4261,9 +4233,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.5" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" [[package]] name = "foreign-types" @@ -4463,7 +4435,7 @@ dependencies = [ "linked-hash-map", "log", "parity-scale-codec", - "rand 0.8.5", + "rand", "rand_pcg", "sc-block-builder", "sc-chain-spec", @@ -4965,19 +4937,7 @@ checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi 0.14.5+wasi-0.2.4", + "wasi", ] [[package]] @@ -4986,8 +4946,8 @@ version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea1015b5a70616b688dc230cfe50c8af89d972cb132d5a622814d29773b10b9" dependencies = [ - "rand 0.8.5", - "rand_core 0.6.4", + "rand", + "rand_core", ] [[package]] @@ -5052,7 +5012,7 @@ dependencies = [ "parking_lot 0.12.3", "portable-atomic", "quanta", - "rand 0.8.5", + "rand", "smallvec", "spinning_top", ] @@ -5064,7 +5024,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core 0.6.4", + "rand_core", "subtle 2.6.1", ] @@ -5378,7 +5338,7 @@ dependencies = [ "parity-scale-codec", "primitive-types 0.12.2", "proptest", - "rand 0.8.5", + "rand", "rand_xoshiro", "rug", "scale-info", @@ -5909,7 +5869,7 @@ dependencies = [ "http 0.2.11", "hyper 0.14.31", "log", - "rand 0.8.5", + "rand", "tokio", "url", "xmltree", @@ -6211,11 +6171,10 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.34" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" dependencies = [ - "getrandom 0.3.3", "libc", ] @@ -6283,7 +6242,7 @@ dependencies = [ "jsonrpsee-types", "parking_lot 0.12.3", "pin-project", - "rand 0.8.5", + "rand", "rustc-hash 2.1.0", "serde", "serde_json", @@ -6483,9 +6442,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libloading" @@ -6513,7 +6472,7 @@ dependencies = [ "either", "futures", "futures-timer", - "getrandom 0.2.11", + "getrandom", "instant", "libp2p-allow-block-list", "libp2p-connection-limits", @@ -6584,7 +6543,7 @@ dependencies = [ "parking_lot 0.12.3", "pin-project", "quick-protobuf", - "rand 0.8.5", + "rand", "rw-stream-sink", "smallvec", "thiserror 1.0.63", @@ -6642,7 +6601,7 @@ dependencies = [ "hkdf", "multihash 0.19.1", "quick-protobuf", - "rand 0.8.5", + "rand", "sha2 0.10.8", "thiserror 1.0.63", "tracing", @@ -6669,7 +6628,7 @@ dependencies = [ "log", "quick-protobuf", "quick-protobuf-codec", - "rand 0.8.5", + "rand", "sha2 0.10.8", "smallvec", "thiserror 1.0.63", @@ -6691,7 +6650,7 @@ dependencies = [ "libp2p-identity", "libp2p-swarm", "log", - "rand 0.8.5", + "rand", "smallvec", "socket2 0.5.8", "tokio", @@ -6732,7 +6691,7 @@ dependencies = [ "multihash 0.19.1", "once_cell", "quick-protobuf", - "rand 0.8.5", + "rand", "sha2 0.10.8", "snow", "static_assertions", @@ -6755,7 +6714,7 @@ dependencies = [ "libp2p-identity", "libp2p-swarm", "log", - "rand 0.8.5", + "rand", "void", ] @@ -6775,7 +6734,7 @@ dependencies = [ "log", "parking_lot 0.12.3", "quinn 0.10.2", - "rand 0.8.5", + "rand", "ring 0.16.20", "rustls 0.21.10", "socket2 0.5.8", @@ -6796,7 +6755,7 @@ dependencies = [ "libp2p-identity", "libp2p-swarm", "log", - "rand 0.8.5", + "rand", "smallvec", "void", ] @@ -6818,7 +6777,7 @@ dependencies = [ "log", "multistream-select", "once_cell", - "rand 0.8.5", + "rand", "smallvec", "tokio", "void", @@ -6943,7 +6902,7 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.6.0", "libc", "redox_syscall 0.4.1", ] @@ -6976,7 +6935,7 @@ dependencies = [ "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", - "rand 0.8.5", + "rand", "serde", "sha2 0.9.9", "typenum", @@ -7133,7 +7092,7 @@ dependencies = [ "prost 0.12.6", "prost-build 0.11.9", "quinn 0.9.4", - "rand 0.8.5", + "rand", "rcgen", "ring 0.16.20", "rustls 0.20.9", @@ -7423,7 +7382,7 @@ checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" dependencies = [ "byteorder", "keccak", - "rand_core 0.6.4", + "rand_core", "zeroize", ] @@ -7434,7 +7393,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69672161530e8aeca1d1400fbf3f1a1747ff60ea604265a4e906c2442df20532" dependencies = [ "futures", - "rand 0.8.5", + "rand", "thrift", ] @@ -7460,7 +7419,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.52.0", ] @@ -7481,8 +7440,8 @@ dependencies = [ "lioness", "log", "parking_lot 0.12.3", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand", + "rand_chacha", "rand_distr", "subtle 2.6.1", "thiserror 1.0.63", @@ -7765,7 +7724,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bddcd3bf5144b6392de80e04c347cd7fab2508f6df16a85fc496ecd5cec39bc" dependencies = [ - "rand 0.8.5", + "rand", ] [[package]] @@ -7869,7 +7828,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.6.0", "cfg-if", "cfg_aliases", "libc", @@ -8135,9 +8094,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" @@ -8163,7 +8122,7 @@ version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.6.0", "cfg-if", "foreign-types", "libc", @@ -9003,7 +8962,7 @@ dependencies = [ "pallet-balances", "pallet-session", "parity-scale-codec", - "rand 0.8.5", + "rand", "scale-info", "sp-runtime", "sp-staking", @@ -9221,7 +9180,7 @@ dependencies = [ "pretty_assertions", "primitive-types 0.12.2", "primitives", - "rand 0.8.5", + "rand", "scale-info", "smallvec", "sp-api", @@ -9414,7 +9373,7 @@ dependencies = [ "log", "pallet-election-provider-support-benchmarking", "parity-scale-codec", - "rand 0.8.5", + "rand", "scale-info", "sp-arithmetic", "sp-core", @@ -9996,7 +9955,7 @@ dependencies = [ "pretty_assertions", "primitives", "proptest", - "rand 0.8.5", + "rand", "scale-info", "sp-arithmetic", "sp-core", @@ -10850,7 +10809,7 @@ dependencies = [ "pallet-session", "pallet-staking 38.0.0", "parity-scale-codec", - "rand 0.8.5", + "rand", "sp-runtime", "sp-session", ] @@ -10877,7 +10836,7 @@ dependencies = [ "frame-system", "log", "parity-scale-codec", - "rand_chacha 0.3.1", + "rand_chacha", "scale-info", "sp-arithmetic", "sp-io", @@ -10952,7 +10911,7 @@ dependencies = [ "pallet-authorship", "pallet-session", "parity-scale-codec", - "rand_chacha 0.3.1", + "rand_chacha", "scale-info", "serde", "sp-application-crypto", @@ -11495,8 +11454,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e69bf016dc406eff7d53a7d3f7cf1c2e72c82b9088aac1118591e36dd2cd3e9" dependencies = [ "bitcoin_hashes 0.13.0", - "rand 0.8.5", - "rand_core 0.6.4", + "rand", + "rand_core", "serde", "unicode-normalization", ] @@ -11522,7 +11481,7 @@ dependencies = [ "lz4", "memmap2 0.5.10", "parking_lot 0.12.3", - "rand 0.8.5", + "rand", "siphasher", "snap", "winapi", @@ -11657,7 +11616,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", - "rand_core 0.6.4", + "rand_core", "subtle 2.6.1", ] @@ -11862,7 +11821,7 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-util", "polkadot-primitives", - "rand 0.8.5", + "rand", "tracing-gum", ] @@ -11878,7 +11837,7 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-util", "polkadot-primitives", - "rand 0.8.5", + "rand", "tracing-gum", ] @@ -11897,7 +11856,7 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-util", "polkadot-primitives", - "rand 0.8.5", + "rand", "sc-network", "schnellru", "sp-core", @@ -11921,7 +11880,7 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-util", "polkadot-primitives", - "rand 0.8.5", + "rand", "sc-network", "schnellru", "thiserror 1.0.63", @@ -12051,8 +12010,8 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-util", "polkadot-primitives", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand", + "rand_chacha", "sc-network", "sc-network-common", "sp-application-crypto", @@ -12122,9 +12081,9 @@ dependencies = [ "polkadot-node-subsystem-util", "polkadot-overseer", "polkadot-primitives", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_core 0.6.4", + "rand", + "rand_chacha", + "rand_core", "sc-keystore", "schnellru", "schnorrkel 0.11.4", @@ -12334,7 +12293,7 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-parachain-primitives", "polkadot-primitives", - "rand 0.8.5", + "rand", "slotmap", "sp-core", "tempfile", @@ -12454,7 +12413,7 @@ dependencies = [ "polkadot-node-jaeger", "polkadot-node-primitives", "polkadot-primitives", - "rand 0.8.5", + "rand", "sc-authority-discovery", "sc-network", "sc-network-types", @@ -12556,7 +12515,7 @@ dependencies = [ "polkadot-overseer", "polkadot-primitives", "prioritized-metered-channel", - "rand 0.8.5", + "rand", "sc-client-api", "schnellru", "sp-application-crypto", @@ -12825,8 +12784,8 @@ dependencies = [ "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-metrics", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand", + "rand_chacha", "scale-info", "serde", "sp-api", @@ -13781,17 +13740,17 @@ dependencies = [ [[package]] name = "proptest" -version = "1.7.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.9.4", + "bitflags 2.6.0", "lazy_static", "num-traits", - "rand 0.9.2", - "rand_chacha 0.9.0", + "rand", + "rand_chacha", "rand_xorshift", "regex-syntax 0.8.2", "rusty-fork", @@ -13925,7 +13884,7 @@ dependencies = [ "libc", "once_cell", "raw-cpuid", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "web-sys", "winapi", ] @@ -14001,7 +13960,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94b0b33c13a79f669c85defaf4c275dc86a0c0372807d0ca3d78e0bb87274863" dependencies = [ "bytes", - "rand 0.8.5", + "rand", "ring 0.16.20", "rustc-hash 1.1.0", "rustls 0.20.9", @@ -14019,7 +13978,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "141bf7dfde2fbc246bfd3fe12f2455aa24b0fbd9af535d8c86c7bd1381ff2b1a" dependencies = [ "bytes", - "rand 0.8.5", + "rand", "ring 0.16.20", "rustc-hash 1.1.0", "rustls 0.21.10", @@ -14064,12 +14023,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - [[package]] name = "radium" version = "0.7.0" @@ -14083,18 +14036,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_chacha", + "rand_core", ] [[package]] @@ -14104,17 +14047,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.3", + "rand_core", ] [[package]] @@ -14123,16 +14056,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.11", -] - -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.3", + "getrandom", ] [[package]] @@ -14142,7 +14066,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" dependencies = [ "num-traits", - "rand 0.8.5", + "rand", ] [[package]] @@ -14151,16 +14075,16 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" dependencies = [ - "rand_core 0.6.4", + "rand_core", ] [[package]] name = "rand_xorshift" -version = "0.4.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ - "rand_core 0.9.3", + "rand_core", ] [[package]] @@ -14169,7 +14093,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" dependencies = [ - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -14178,7 +14102,7 @@ version = "11.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb9ee317cfe3fbd54b36a511efc1edd42e216903c9cd575e686dd68a2ba90d8d" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.6.0", ] [[package]] @@ -14243,7 +14167,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ - "getrandom 0.2.11", + "getrandom", "libredox", "thiserror 1.0.63", ] @@ -14391,7 +14315,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" dependencies = [ "cc", - "getrandom 0.2.11", + "getrandom", "libc", "spin 0.9.8", "untrusted 0.9.0", @@ -14612,9 +14536,9 @@ dependencies = [ [[package]] name = "ruint" -version = "1.16.0" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecb38f82477f20c5c3d62ef52d7c4e536e38ea9b73fb570a20c5cae0e14bcf6" +checksum = "f5ef8fb1dd8de3870cb8400d51b4c2023854bbafd5431a3ac7e7317243e22d2f" dependencies = [ "alloy-rlp", "ark-ff 0.3.0", @@ -14628,8 +14552,7 @@ dependencies = [ "parity-scale-codec", "primitive-types 0.12.2", "proptest", - "rand 0.8.5", - "rand 0.9.2", + "rand", "rlp", "ruint-macro", "serde", @@ -14882,7 +14805,7 @@ version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys 0.4.12", @@ -15125,7 +15048,7 @@ dependencies = [ "parity-scale-codec", "prost 0.12.6", "prost-build 0.12.6", - "rand 0.8.5", + "rand", "sc-client-api", "sc-network", "sc-network-types", @@ -15230,7 +15153,7 @@ dependencies = [ "names", "parity-bip39", "parity-scale-codec", - "rand 0.8.5", + "rand", "regex", "rpassword", "sc-client-api", @@ -15504,7 +15427,7 @@ dependencies = [ "log", "parity-scale-codec", "parking_lot 0.12.3", - "rand 0.8.5", + "rand", "sc-block-builder", "sc-chain-spec", "sc-client-api", @@ -15728,7 +15651,7 @@ dependencies = [ "pin-project", "prost 0.12.6", "prost-build 0.12.6", - "rand 0.8.5", + "rand", "sc-client-api", "sc-network-common", "sc-network-types", @@ -15877,7 +15800,7 @@ dependencies = [ "log", "multiaddr 0.18.2", "multihash 0.19.1", - "rand 0.8.5", + "rand", "thiserror", "zeroize", ] @@ -15899,7 +15822,7 @@ dependencies = [ "once_cell", "parity-scale-codec", "parking_lot 0.12.3", - "rand 0.8.5", + "rand", "sc-client-api", "sc-network", "sc-network-common", @@ -16014,7 +15937,7 @@ dependencies = [ "log", "parity-scale-codec", "parking_lot 0.12.3", - "rand 0.8.5", + "rand", "sc-chain-spec", "sc-client-api", "sc-rpc", @@ -16048,7 +15971,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "pin-project", - "rand 0.8.5", + "rand", "sc-chain-spec", "sc-client-api", "sc-client-db", @@ -16149,7 +16072,7 @@ dependencies = [ "futures", "libc", "log", - "rand 0.8.5", + "rand", "rand_pcg", "regex", "sc-telemetry", @@ -16172,7 +16095,7 @@ dependencies = [ "log", "parking_lot 0.12.3", "pin-project", - "rand 0.8.5", + "rand", "sc-network", "sc-utils", "serde", @@ -16364,7 +16287,7 @@ dependencies = [ "arrayvec 0.7.4", "curve25519-dalek-ng", "merlin", - "rand_core 0.6.4", + "rand_core", "sha2 0.9.9", "subtle-ng", "zeroize", @@ -16382,7 +16305,7 @@ dependencies = [ "curve25519-dalek", "getrandom_or_panic", "merlin", - "rand_core 0.6.4", + "rand_core", "serde_bytes", "sha2 0.10.8", "subtle 2.6.1", @@ -16451,7 +16374,7 @@ dependencies = [ "crc", "fxhash", "log", - "rand 0.8.5", + "rand", "slab", "thiserror 1.0.63", ] @@ -16513,7 +16436,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -16759,9 +16682,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" [[package]] name = "signal-hook-registry" @@ -16779,7 +16702,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest 0.10.7", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -16821,7 +16744,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cae9a3fcdadafb6d97f4c0e007e4247b114ee0f119f650c3cbf3a8b3a1479694" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.6.0", ] [[package]] @@ -16942,8 +16865,8 @@ dependencies = [ "pbkdf2", "pin-project", "poly1305", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand", + "rand_chacha", "ruzstd", "schnorrkel 0.10.2", "serde", @@ -16985,8 +16908,8 @@ dependencies = [ "no-std-net", "parking_lot 0.12.3", "pin-project", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand", + "rand_chacha", "serde", "serde_json", "siphasher", @@ -17012,7 +16935,7 @@ dependencies = [ "blake2 0.10.6", "chacha20poly1305", "curve25519-dalek", - "rand_core 0.6.4", + "rand_core", "ring 0.17.7", "rustc_version 0.4.0", "sha2 0.10.8", @@ -17361,7 +17284,7 @@ dependencies = [ "futures", "httparse", "log", - "rand 0.8.5", + "rand", "sha-1 0.9.8", ] @@ -17377,7 +17300,7 @@ dependencies = [ "http 1.2.0", "httparse", "log", - "rand 0.8.5", + "rand", "sha1", ] @@ -17621,7 +17544,7 @@ dependencies = [ "parking_lot 0.12.3", "paste", "primitive-types 0.12.2", - "rand 0.8.5", + "rand", "scale-info", "schnorrkel 0.11.4", "secp256k1", @@ -17896,7 +17819,7 @@ dependencies = [ "num-traits", "parity-scale-codec", "paste", - "rand 0.8.5", + "rand", "scale-info", "serde", "simple-mermaid", @@ -17977,7 +17900,7 @@ dependencies = [ "log", "parity-scale-codec", "parking_lot 0.12.3", - "rand 0.8.5", + "rand", "smallvec", "sp-core", "sp-externalities", @@ -17998,7 +17921,7 @@ dependencies = [ "ed25519-dalek", "hkdf", "parity-scale-codec", - "rand 0.8.5", + "rand", "scale-info", "sha2 0.10.8", "sp-api", @@ -18087,7 +18010,7 @@ dependencies = [ "nohash-hasher", "parity-scale-codec", "parking_lot 0.12.3", - "rand 0.8.5", + "rand", "scale-info", "schnellru", "sp-core", @@ -18485,7 +18408,7 @@ dependencies = [ "byteorder", "crunchy", "lazy_static", - "rand 0.8.5", + "rand", "rustc-hex", ] @@ -19019,7 +18942,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f" dependencies = [ "pin-project", - "rand 0.8.5", + "rand", "tokio", ] @@ -19170,7 +19093,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.6.0", "bytes", "http 1.2.0", "http-body 1.0.1", @@ -19326,7 +19249,7 @@ dependencies = [ "idna 0.2.3", "ipnet", "lazy_static", - "rand 0.8.5", + "rand", "smallvec", "socket2 0.4.10", "thiserror 1.0.63", @@ -19352,7 +19275,7 @@ dependencies = [ "idna 0.4.0", "ipnet", "once_cell", - "rand 0.8.5", + "rand", "smallvec", "thiserror 1.0.63", "tinyvec", @@ -19373,7 +19296,7 @@ dependencies = [ "lru-cache", "once_cell", "parking_lot 0.12.3", - "rand 0.8.5", + "rand", "resolv-conf", "smallvec", "thiserror 1.0.63", @@ -19421,7 +19344,7 @@ dependencies = [ "http 0.2.11", "httparse", "log", - "rand 0.8.5", + "rand", "rustls 0.21.10", "sha1", "thiserror 1.0.63", @@ -19443,7 +19366,7 @@ checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if", "digest 0.10.7", - "rand 0.8.5", + "rand", "static_assertions", ] @@ -19634,9 +19557,9 @@ dependencies = [ "arrayref", "constcat", "digest 0.10.7", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_core 0.6.4", + "rand", + "rand_chacha", + "rand_core", "sha2 0.10.8", "sha3", "thiserror 1.0.63", @@ -19683,24 +19606,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasi" -version = "0.14.5+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4494f6290a82f5fe584817a676a34b9d6763e8d9d18204009fb31dceca98fd4" -dependencies = [ - "wasip2", -] - -[[package]] -name = "wasip2" -version = "1.0.0+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03fa2761397e5bd52002cd7e73110c71af2109aca4e521a9f40473fe685b0a24" -dependencies = [ - "wit-bindgen", -] - [[package]] name = "wasm-bindgen" version = "0.2.89" @@ -20096,7 +20001,7 @@ dependencies = [ "memfd", "memoffset 0.8.0", "paste", - "rand 0.8.5", + "rand", "rustix 0.36.17", "wasmtime-asm-macros", "wasmtime-environ", @@ -20610,12 +20515,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "wit-bindgen" -version = "0.45.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" - [[package]] name = "wyz" version = "0.5.1" @@ -20632,7 +20531,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" dependencies = [ "curve25519-dalek", - "rand_core 0.6.4", + "rand_core", "serde", "zeroize", ] @@ -20779,7 +20678,7 @@ dependencies = [ "nohash-hasher", "parking_lot 0.12.3", "pin-project", - "rand 0.8.5", + "rand", "static_assertions", ] diff --git a/Cargo.toml b/Cargo.toml index 704fad2d02..8be69c5945 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,6 @@ members = [ 'pallets/broadcast', 'liquidation-worker-support', 'pallets/hsm', - 'pallets/build-evm-tx', ] resolver = "2" diff --git a/runtime/hydradx/src/lib.rs b/runtime/hydradx/src/lib.rs index 928ea0f862..fc85dc3032 100644 --- a/runtime/hydradx/src/lib.rs +++ b/runtime/hydradx/src/lib.rs @@ -192,7 +192,7 @@ construct_runtime!( Liquidation: pallet_liquidation = 76, HSM: pallet_hsm = 82, Parameters: pallet_parameters = 83, - BuildEvmTx: pallet_build_evm_tx = 84, + BuildEvmTx: pallet_build_evm_tx = 86, // ORML related modules Tokens: orml_tokens = 77, From 6550d379f5703b2774cf79d04a89164db454f604 Mon Sep 17 00:00:00 2001 From: Pessina Date: Sun, 14 Sep 2025 12:42:10 +0500 Subject: [PATCH 08/32] Emit event from build-evm-tx pallet --- Cargo.toml | 1 + pallets/build-evm-tx/src/lib.rs | 21 ++++++- pallets/build-evm-tx/src/tests/mock.rs | 1 + pallets/build-evm-tx/src/tests/tests.rs | 58 +++++++++++++++++++- pallets/build-evm-tx/src/tests/validation.rs | 1 + runtime/hydradx/src/assets.rs | 2 +- 6 files changed, 79 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8be69c5945..704fad2d02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ members = [ 'pallets/broadcast', 'liquidation-worker-support', 'pallets/hsm', + 'pallets/build-evm-tx', ] resolver = "2" diff --git a/pallets/build-evm-tx/src/lib.rs b/pallets/build-evm-tx/src/lib.rs index 1062ec35bc..1627900f7e 100644 --- a/pallets/build-evm-tx/src/lib.rs +++ b/pallets/build-evm-tx/src/lib.rs @@ -21,11 +21,22 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// Maximum length of transaction data #[pallet::constant] type MaxDataLength: Get; } + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + EvmTransactionBuilt { + who: T::AccountId, + rlp_transaction: Vec, + }, + } + #[pallet::error] pub enum Error { /// Transaction data exceeds maximum allowed length @@ -40,6 +51,7 @@ pub mod pallet { /// Build an EIP-1559 EVM transaction and return the RLP-encoded data /// /// # Parameters + /// - `who`: Optional account ID to emit event for /// - `to_address`: Optional recipient address (None for contract creation) /// - `value`: ETH value in wei /// - `data`: Transaction data/calldata @@ -52,6 +64,7 @@ pub mod pallet { /// # Returns /// RLP-encoded transaction data with EIP-2718 type prefix (0x02 for EIP-1559) pub fn build_evm_tx( + who: Option, to_address: Option>, value: u128, data: Vec, @@ -85,11 +98,17 @@ pub mod pallet { access_list: Default::default(), }; - // Encode transaction to RLP format with EIP-2718 type prefix let mut typed_tx = Vec::new(); typed_tx.push(TxType::Eip1559 as u8); tx.encode(&mut typed_tx); + if let Some(account) = who { + Self::deposit_event(Event::EvmTransactionBuilt { + who: account, + rlp_transaction: typed_tx.clone(), + }); + } + Ok(typed_tx) } } diff --git a/pallets/build-evm-tx/src/tests/mock.rs b/pallets/build-evm-tx/src/tests/mock.rs index 22ca11ed2e..f33eafcb3c 100644 --- a/pallets/build-evm-tx/src/tests/mock.rs +++ b/pallets/build-evm-tx/src/tests/mock.rs @@ -56,6 +56,7 @@ parameter_types! { } impl pallet_build_evm_tx::Config for Test { + type RuntimeEvent = RuntimeEvent; type MaxDataLength = MaxDataLength; } diff --git a/pallets/build-evm-tx/src/tests/tests.rs b/pallets/build-evm-tx/src/tests/tests.rs index 8d62fabcf3..cdfb49c94d 100644 --- a/pallets/build-evm-tx/src/tests/tests.rs +++ b/pallets/build-evm-tx/src/tests/tests.rs @@ -1,12 +1,12 @@ use super::mock::*; -use crate::Error; +use crate::{Error, Event}; #[test] fn invalid_address_length_fails() { ExtBuilder::default().build().execute_with(|| { let invalid_address = vec![0x42; 19]; // Should be 20 bytes - let result = BuildEvmTx::build_evm_tx(Some(invalid_address), 0, vec![], 0, 21000, 20000000000, 1000000000, 1); + let result = BuildEvmTx::build_evm_tx(None, Some(invalid_address), 0, vec![], 0, 21000, 20000000000, 1000000000, 1); assert_eq!(result, Err(Error::::InvalidAddress.into())); }); @@ -17,7 +17,7 @@ fn data_too_long_fails() { ExtBuilder::default().build().execute_with(|| { let large_data = vec![0xff; 100_001]; // Exceeds MaxDataLength - let result = BuildEvmTx::build_evm_tx(None, 0, large_data, 0, 21000, 20000000000, 1000000000, 1); + let result = BuildEvmTx::build_evm_tx(None, None, 0, large_data, 0, 21000, 20000000000, 1000000000, 1); assert_eq!(result, Err(Error::::DataTooLong.into())); }); @@ -27,6 +27,7 @@ fn data_too_long_fails() { fn invalid_gas_price_relationship_fails() { ExtBuilder::default().build().execute_with(|| { let result = BuildEvmTx::build_evm_tx( + None, None, 0, vec![], @@ -40,3 +41,54 @@ fn invalid_gas_price_relationship_fails() { assert_eq!(result, Err(Error::::InvalidGasPrice.into())); }); } + + +#[test] +fn build_evm_tx_helper_emits_event_when_who_provided() { + ExtBuilder::default().build().execute_with(|| { + let to_address = vec![0xaa; 20]; + let who = 42u64; + + // Call helper directly with Some(who) + let rlp = BuildEvmTx::build_evm_tx( + Some(who), + Some(to_address), + 1000, + vec![], + 1, + 21000, + 20_000_000_000, + 1_000_000_000, + 1 + ).unwrap(); + + // Verify event was emitted from the helper + System::assert_has_event(RuntimeEvent::BuildEvmTx(Event::EvmTransactionBuilt { + who, + rlp_transaction: rlp, + })); + }); +} + +#[test] +fn build_evm_tx_helper_no_event_when_who_none() { + ExtBuilder::default().build().execute_with(|| { + let to_address = vec![0xbb; 20]; + + // Call helper with None + BuildEvmTx::build_evm_tx( + None, + Some(to_address), + 2000, + vec![], + 2, + 21000, + 20_000_000_000, + 1_000_000_000, + 1 + ).unwrap(); + + // Verify no events were emitted + assert_eq!(System::events().len(), 0); + }); +} diff --git a/pallets/build-evm-tx/src/tests/validation.rs b/pallets/build-evm-tx/src/tests/validation.rs index 1ed5709b1d..ff9a0e1bdc 100644 --- a/pallets/build-evm-tx/src/tests/validation.rs +++ b/pallets/build-evm-tx/src/tests/validation.rs @@ -43,6 +43,7 @@ mod validation { // Test the public function that other pallets call let returned_rlp = BuildEvmTx::build_evm_tx( + None, // No who, so no event emitted Some(to_address), value, data, diff --git a/runtime/hydradx/src/assets.rs b/runtime/hydradx/src/assets.rs index 4f2888edc2..10774cbbac 100644 --- a/runtime/hydradx/src/assets.rs +++ b/runtime/hydradx/src/assets.rs @@ -1832,12 +1832,12 @@ impl pallet_hsm::Config for Runtime { type BenchmarkHelper = helpers::benchmark_helpers::HsmBenchmarkHelper; } - parameter_types! { pub const MaxEvmDataLength: u32 = 10_000; } impl pallet_build_evm_tx::Config for Runtime { + type RuntimeEvent = RuntimeEvent; type MaxDataLength = MaxEvmDataLength; } From f16dc4bbc4f96e9f0977ca0ab28bd06d05f4718b Mon Sep 17 00:00:00 2001 From: Pessina Date: Sun, 14 Sep 2025 14:43:20 +0500 Subject: [PATCH 09/32] Include docs --- pallets/build-evm-tx/README.md | 33 +++++++++++++++++++++++++++++++++ pallets/build-evm-tx/src/lib.rs | 17 +++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 pallets/build-evm-tx/README.md diff --git a/pallets/build-evm-tx/README.md b/pallets/build-evm-tx/README.md new file mode 100644 index 0000000000..71d9d70272 --- /dev/null +++ b/pallets/build-evm-tx/README.md @@ -0,0 +1,33 @@ +# pallet-build-evm-tx + +## Overview + +The build-evm-tx pallet provides functionality to construct EIP-1559 compliant EVM transactions and encode them using RLP serialization. + +### Usage + +Other pallets can use the helper function to build EVM transactions: + +```rust +let rlp_encoded = pallet_build_evm_tx::Pallet::::build_evm_tx( + Some(who), // Optional account for event emission + Some(to_address), // Optional recipient (None for contract creation) + value, // ETH value in wei + data, // Transaction data/calldata + nonce, // Transaction nonce + gas_limit, // Gas limit + max_fee_per_gas, // Maximum total fee per gas + max_priority_fee, // Maximum priority fee (tip) per gas + chain_id, // Target chain ID +)?; +``` + +### Configuration + +The pallet requires configuring the maximum data length + +```rust +parameter_types! { + pub const MaxEvmDataLength: u32 = 10_000; +} +``` diff --git a/pallets/build-evm-tx/src/lib.rs b/pallets/build-evm-tx/src/lib.rs index 1627900f7e..7e3ac7b71f 100644 --- a/pallets/build-evm-tx/src/lib.rs +++ b/pallets/build-evm-tx/src/lib.rs @@ -1,3 +1,20 @@ +// This file is part of HydraDX. + +// Copyright (C) 2020-2021 Intergalactic, Limited (GIB). +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #![cfg_attr(not(feature = "std"), no_std)] use frame_support::pallet_prelude::*; From cd92721f65f1404fe291e082a56aad0de0be8b37 Mon Sep 17 00:00:00 2001 From: Pessina Date: Sun, 14 Sep 2025 14:50:30 +0500 Subject: [PATCH 10/32] Update max tx data length --- pallets/build-evm-tx/README.md | 2 +- runtime/hydradx/src/assets.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/build-evm-tx/README.md b/pallets/build-evm-tx/README.md index 71d9d70272..f178e99196 100644 --- a/pallets/build-evm-tx/README.md +++ b/pallets/build-evm-tx/README.md @@ -28,6 +28,6 @@ The pallet requires configuring the maximum data length ```rust parameter_types! { - pub const MaxEvmDataLength: u32 = 10_000; + pub const MaxEvmDataLength: u32 = 100_000; } ``` diff --git a/runtime/hydradx/src/assets.rs b/runtime/hydradx/src/assets.rs index 10774cbbac..5261719216 100644 --- a/runtime/hydradx/src/assets.rs +++ b/runtime/hydradx/src/assets.rs @@ -1833,7 +1833,7 @@ impl pallet_hsm::Config for Runtime { } parameter_types! { - pub const MaxEvmDataLength: u32 = 10_000; + pub const MaxEvmDataLength: u32 = 100_000; } impl pallet_build_evm_tx::Config for Runtime { From ba3de57a91deab10127896ff55ef0ad8eb1d6548 Mon Sep 17 00:00:00 2001 From: Pessina Date: Sun, 14 Sep 2025 14:57:49 +0500 Subject: [PATCH 11/32] Remove useless comments --- pallets/build-evm-tx/src/tests/tests.rs | 27 ++++++++++++-------- pallets/build-evm-tx/src/tests/validation.rs | 3 --- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/pallets/build-evm-tx/src/tests/tests.rs b/pallets/build-evm-tx/src/tests/tests.rs index cdfb49c94d..b345280076 100644 --- a/pallets/build-evm-tx/src/tests/tests.rs +++ b/pallets/build-evm-tx/src/tests/tests.rs @@ -6,7 +6,17 @@ fn invalid_address_length_fails() { ExtBuilder::default().build().execute_with(|| { let invalid_address = vec![0x42; 19]; // Should be 20 bytes - let result = BuildEvmTx::build_evm_tx(None, Some(invalid_address), 0, vec![], 0, 21000, 20000000000, 1000000000, 1); + let result = BuildEvmTx::build_evm_tx( + None, + Some(invalid_address), + 0, + vec![], + 0, + 21000, + 20000000000, + 1000000000, + 1, + ); assert_eq!(result, Err(Error::::InvalidAddress.into())); }); @@ -42,14 +52,12 @@ fn invalid_gas_price_relationship_fails() { }); } - #[test] fn build_evm_tx_helper_emits_event_when_who_provided() { ExtBuilder::default().build().execute_with(|| { let to_address = vec![0xaa; 20]; let who = 42u64; - // Call helper directly with Some(who) let rlp = BuildEvmTx::build_evm_tx( Some(who), Some(to_address), @@ -59,10 +67,10 @@ fn build_evm_tx_helper_emits_event_when_who_provided() { 21000, 20_000_000_000, 1_000_000_000, - 1 - ).unwrap(); + 1, + ) + .unwrap(); - // Verify event was emitted from the helper System::assert_has_event(RuntimeEvent::BuildEvmTx(Event::EvmTransactionBuilt { who, rlp_transaction: rlp, @@ -75,7 +83,6 @@ fn build_evm_tx_helper_no_event_when_who_none() { ExtBuilder::default().build().execute_with(|| { let to_address = vec![0xbb; 20]; - // Call helper with None BuildEvmTx::build_evm_tx( None, Some(to_address), @@ -85,10 +92,10 @@ fn build_evm_tx_helper_no_event_when_who_none() { 21000, 20_000_000_000, 1_000_000_000, - 1 - ).unwrap(); + 1, + ) + .unwrap(); - // Verify no events were emitted assert_eq!(System::events().len(), 0); }); } diff --git a/pallets/build-evm-tx/src/tests/validation.rs b/pallets/build-evm-tx/src/tests/validation.rs index ff9a0e1bdc..d378f6e2ab 100644 --- a/pallets/build-evm-tx/src/tests/validation.rs +++ b/pallets/build-evm-tx/src/tests/validation.rs @@ -5,7 +5,6 @@ mod validation { #[test] fn validate_against_precomputed_alloy_rlp() { ExtBuilder::default().build().execute_with(|| { - // Fixed transaction parameters let to_address = vec![ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, @@ -41,7 +40,6 @@ mod validation { 0x56, 0x78, 0xc0, ]; - // Test the public function that other pallets call let returned_rlp = BuildEvmTx::build_evm_tx( None, // No who, so no event emitted Some(to_address), @@ -55,7 +53,6 @@ mod validation { ) .expect("Failed to build transaction"); - // Verify the returned RLP matches expected assert_eq!( returned_rlp, expected_rlp, From 8fe49e02cc9c4c85d3993aa2c9a5846bd6a0a1d1 Mon Sep 17 00:00:00 2001 From: Pessina Date: Tue, 16 Sep 2025 15:48:18 +0500 Subject: [PATCH 12/32] PR comments --- pallets/build-evm-tx/src/lib.rs | 26 ++------- pallets/build-evm-tx/src/tests/mock.rs | 1 - pallets/build-evm-tx/src/tests/tests.rs | 55 ++------------------ pallets/build-evm-tx/src/tests/validation.rs | 2 +- runtime/hydradx/src/assets.rs | 1 - 5 files changed, 10 insertions(+), 75 deletions(-) diff --git a/pallets/build-evm-tx/src/lib.rs b/pallets/build-evm-tx/src/lib.rs index 7e3ac7b71f..b12e75aa86 100644 --- a/pallets/build-evm-tx/src/lib.rs +++ b/pallets/build-evm-tx/src/lib.rs @@ -18,6 +18,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use frame_support::pallet_prelude::*; +use frame_system::{ensure_signed, pallet_prelude::OriginFor}; use sp_std::vec::Vec; use alloy_consensus::{TxEip1559, TxType}; @@ -38,22 +39,11 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - /// Maximum length of transaction data #[pallet::constant] type MaxDataLength: Get; } - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - EvmTransactionBuilt { - who: T::AccountId, - rlp_transaction: Vec, - }, - } - #[pallet::error] pub enum Error { /// Transaction data exceeds maximum allowed length @@ -68,7 +58,7 @@ pub mod pallet { /// Build an EIP-1559 EVM transaction and return the RLP-encoded data /// /// # Parameters - /// - `who`: Optional account ID to emit event for + /// - `origin`: The signed origin /// - `to_address`: Optional recipient address (None for contract creation) /// - `value`: ETH value in wei /// - `data`: Transaction data/calldata @@ -81,7 +71,7 @@ pub mod pallet { /// # Returns /// RLP-encoded transaction data with EIP-2718 type prefix (0x02 for EIP-1559) pub fn build_evm_tx( - who: Option, + origin: OriginFor, to_address: Option>, value: u128, data: Vec, @@ -91,8 +81,9 @@ pub mod pallet { max_priority_fee_per_gas: u128, chain_id: u64, ) -> Result, DispatchError> { - ensure!(data.len() <= T::MaxDataLength::get() as usize, Error::::DataTooLong); + ensure_signed(origin)?; + ensure!(data.len() <= T::MaxDataLength::get() as usize, Error::::DataTooLong); ensure!(max_priority_fee_per_gas <= max_fee_per_gas, Error::::InvalidGasPrice); let to = match to_address { @@ -119,13 +110,6 @@ pub mod pallet { typed_tx.push(TxType::Eip1559 as u8); tx.encode(&mut typed_tx); - if let Some(account) = who { - Self::deposit_event(Event::EvmTransactionBuilt { - who: account, - rlp_transaction: typed_tx.clone(), - }); - } - Ok(typed_tx) } } diff --git a/pallets/build-evm-tx/src/tests/mock.rs b/pallets/build-evm-tx/src/tests/mock.rs index f33eafcb3c..22ca11ed2e 100644 --- a/pallets/build-evm-tx/src/tests/mock.rs +++ b/pallets/build-evm-tx/src/tests/mock.rs @@ -56,7 +56,6 @@ parameter_types! { } impl pallet_build_evm_tx::Config for Test { - type RuntimeEvent = RuntimeEvent; type MaxDataLength = MaxDataLength; } diff --git a/pallets/build-evm-tx/src/tests/tests.rs b/pallets/build-evm-tx/src/tests/tests.rs index b345280076..3ab085fbec 100644 --- a/pallets/build-evm-tx/src/tests/tests.rs +++ b/pallets/build-evm-tx/src/tests/tests.rs @@ -1,5 +1,5 @@ use super::mock::*; -use crate::{Error, Event}; +use crate::Error; #[test] fn invalid_address_length_fails() { @@ -7,7 +7,7 @@ fn invalid_address_length_fails() { let invalid_address = vec![0x42; 19]; // Should be 20 bytes let result = BuildEvmTx::build_evm_tx( - None, + RuntimeOrigin::signed(1u64), Some(invalid_address), 0, vec![], @@ -27,7 +27,7 @@ fn data_too_long_fails() { ExtBuilder::default().build().execute_with(|| { let large_data = vec![0xff; 100_001]; // Exceeds MaxDataLength - let result = BuildEvmTx::build_evm_tx(None, None, 0, large_data, 0, 21000, 20000000000, 1000000000, 1); + let result = BuildEvmTx::build_evm_tx(RuntimeOrigin::signed(1u64), None, 0, large_data, 0, 21000, 20000000000, 1000000000, 1); assert_eq!(result, Err(Error::::DataTooLong.into())); }); @@ -37,7 +37,7 @@ fn data_too_long_fails() { fn invalid_gas_price_relationship_fails() { ExtBuilder::default().build().execute_with(|| { let result = BuildEvmTx::build_evm_tx( - None, + RuntimeOrigin::signed(1u64), None, 0, vec![], @@ -52,50 +52,3 @@ fn invalid_gas_price_relationship_fails() { }); } -#[test] -fn build_evm_tx_helper_emits_event_when_who_provided() { - ExtBuilder::default().build().execute_with(|| { - let to_address = vec![0xaa; 20]; - let who = 42u64; - - let rlp = BuildEvmTx::build_evm_tx( - Some(who), - Some(to_address), - 1000, - vec![], - 1, - 21000, - 20_000_000_000, - 1_000_000_000, - 1, - ) - .unwrap(); - - System::assert_has_event(RuntimeEvent::BuildEvmTx(Event::EvmTransactionBuilt { - who, - rlp_transaction: rlp, - })); - }); -} - -#[test] -fn build_evm_tx_helper_no_event_when_who_none() { - ExtBuilder::default().build().execute_with(|| { - let to_address = vec![0xbb; 20]; - - BuildEvmTx::build_evm_tx( - None, - Some(to_address), - 2000, - vec![], - 2, - 21000, - 20_000_000_000, - 1_000_000_000, - 1, - ) - .unwrap(); - - assert_eq!(System::events().len(), 0); - }); -} diff --git a/pallets/build-evm-tx/src/tests/validation.rs b/pallets/build-evm-tx/src/tests/validation.rs index d378f6e2ab..edd0f6b50f 100644 --- a/pallets/build-evm-tx/src/tests/validation.rs +++ b/pallets/build-evm-tx/src/tests/validation.rs @@ -41,7 +41,7 @@ mod validation { ]; let returned_rlp = BuildEvmTx::build_evm_tx( - None, // No who, so no event emitted + RuntimeOrigin::signed(1u64), Some(to_address), value, data, diff --git a/runtime/hydradx/src/assets.rs b/runtime/hydradx/src/assets.rs index 5261719216..d9805713ca 100644 --- a/runtime/hydradx/src/assets.rs +++ b/runtime/hydradx/src/assets.rs @@ -1837,7 +1837,6 @@ parameter_types! { } impl pallet_build_evm_tx::Config for Runtime { - type RuntimeEvent = RuntimeEvent; type MaxDataLength = MaxEvmDataLength; } From eea3629712d187eef951415b2ad6e516890d2ce8 Mon Sep 17 00:00:00 2001 From: Pessina Date: Wed, 17 Sep 2025 23:34:01 +0500 Subject: [PATCH 13/32] Replace alloy with ethereum crate --- Cargo.lock | 439 ++----------------- pallets/build-evm-tx/Cargo.toml | 20 +- pallets/build-evm-tx/src/lib.rs | 41 +- pallets/build-evm-tx/src/tests/validation.rs | 25 +- 4 files changed, 85 insertions(+), 440 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 230d893365..78e159b069 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,124 +119,6 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" -[[package]] -name = "alloy-consensus" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88e1edea70787c33e11197d3f32ae380f3db19e6e061e539a5bcf8184a6b326" -dependencies = [ - "alloy-eips", - "alloy-primitives", - "alloy-rlp", - "alloy-trie", - "auto_impl", - "c-kzg", - "derive_more 1.0.0", -] - -[[package]] -name = "alloy-eip2930" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0069cf0642457f87a01a014f6dc29d5d893cd4fd8fddf0c3cdfad1bb3ebafc41" -dependencies = [ - "alloy-primitives", - "alloy-rlp", -] - -[[package]] -name = "alloy-eip7702" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c986539255fb839d1533c128e190e557e52ff652c9ef62939e233a81dd93f7e" -dependencies = [ - "alloy-primitives", - "alloy-rlp", - "derive_more 1.0.0", -] - -[[package]] -name = "alloy-eips" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9fadfe089e9ccc0650473f2d4ef0a28bc015bbca5631d9f0f09e49b557fdb3" -dependencies = [ - "alloy-eip2930", - "alloy-eip7702", - "alloy-primitives", - "alloy-rlp", - "c-kzg", - "derive_more 1.0.0", - "once_cell", - "serde", - "sha2 0.10.8", -] - -[[package]] -name = "alloy-primitives" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6259a506ab13e1d658796c31e6e39d2e2ee89243bcc505ddc613b35732e0a430" -dependencies = [ - "alloy-rlp", - "bytes", - "cfg-if", - "const-hex", - "derive_more 1.0.0", - "foldhash", - "hashbrown 0.15.2", - "hex-literal", - "indexmap 2.7.0", - "itoa", - "k256", - "keccak-asm", - "paste", - "proptest", - "rand", - "ruint", - "rustc-hash 2.1.0", - "serde", - "sha3", - "tiny-keccak", -] - -[[package]] -name = "alloy-rlp" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f70d83b765fdc080dbcd4f4db70d8d23fe4761f2f02ebfa9146b833900634b4" -dependencies = [ - "alloy-rlp-derive", - "arrayvec 0.7.4", - "bytes", -] - -[[package]] -name = "alloy-rlp-derive" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "alloy-trie" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a94854e420f07e962f7807485856cde359ab99ab6413883e15235ad996e8b" -dependencies = [ - "alloy-primitives", - "alloy-rlp", - "arrayvec 0.7.4", - "derive_more 1.0.0", - "nybbles", - "smallvec", - "tracing", -] - [[package]] name = "always-assert" version = "0.1.3" @@ -349,8 +231,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb00293ba84f51ce3bd026bd0de55899c4e68f0a39a5728cebae3a73ffdc0a4f" dependencies = [ "ark-ec", - "ark-ff 0.4.2", - "ark-std 0.4.0", + "ark-ff", + "ark-std", ] [[package]] @@ -360,9 +242,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" dependencies = [ "ark-ec", - "ark-ff 0.4.2", - "ark-serialize 0.4.2", - "ark-std 0.4.0", + "ark-ff", + "ark-serialize", + "ark-std", ] [[package]] @@ -371,10 +253,10 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" dependencies = [ - "ark-ff 0.4.2", + "ark-ff", "ark-poly", - "ark-serialize 0.4.2", - "ark-std 0.4.0", + "ark-serialize", + "ark-std", "derivative", "hashbrown 0.13.2", "itertools 0.10.5", @@ -451,54 +333,26 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ark-ff" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" -dependencies = [ - "ark-ff-asm 0.3.0", - "ark-ff-macros 0.3.0", - "ark-serialize 0.3.0", - "ark-std 0.3.0", - "derivative", - "num-bigint", - "num-traits", - "paste", - "rustc_version 0.3.3", - "zeroize", -] - [[package]] name = "ark-ff" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" dependencies = [ - "ark-ff-asm 0.4.2", - "ark-ff-macros 0.4.2", - "ark-serialize 0.4.2", - "ark-std 0.4.0", + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", "derivative", "digest 0.10.7", "itertools 0.10.5", "num-bigint", "num-traits", "paste", - "rustc_version 0.4.0", + "rustc_version", "zeroize", ] -[[package]] -name = "ark-ff-asm" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" -dependencies = [ - "quote", - "syn 1.0.109", -] - [[package]] name = "ark-ff-asm" version = "0.4.2" @@ -509,18 +363,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "ark-ff-macros" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" -dependencies = [ - "num-bigint", - "num-traits", - "quote", - "syn 1.0.109", -] - [[package]] name = "ark-ff-macros" version = "0.4.2" @@ -553,9 +395,9 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" dependencies = [ - "ark-ff 0.4.2", - "ark-serialize 0.4.2", - "ark-std 0.4.0", + "ark-ff", + "ark-serialize", + "ark-std", "derivative", "hashbrown 0.13.2", ] @@ -584,16 +426,6 @@ dependencies = [ "digest 0.9.0", ] -[[package]] -name = "ark-serialize" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" -dependencies = [ - "ark-std 0.3.0", - "digest 0.9.0", -] - [[package]] name = "ark-serialize" version = "0.4.2" @@ -601,7 +433,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ "ark-serialize-derive", - "ark-std 0.4.0", + "ark-std", "digest 0.10.7", "num-bigint", ] @@ -617,16 +449,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "ark-std" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" -dependencies = [ - "num-traits", - "rand", -] - [[package]] name = "ark-std" version = "0.4.0" @@ -1303,18 +1125,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "blst" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fd49896f12ac9b6dcd7a5998466b9b58263a695a3dd1ecc1aaca2e12a90b080" -dependencies = [ - "cc", - "glob", - "threadpool", - "zeroize", -] - [[package]] name = "bounded-collections" version = "0.2.0" @@ -1680,21 +1490,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "c-kzg" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0307f72feab3300336fb803a57134159f6e20139af1357f36c54cb90d8e8928" -dependencies = [ - "blst", - "cc", - "glob", - "hex", - "libc", - "once_cell", - "serde", -] - [[package]] name = "c2-chacha" version = "0.3.3" @@ -2078,19 +1873,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "const-hex" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dccd746bf9b1038c0507b7cec21eb2b11222db96a2902c96e8c185d6d20fb9c4" -dependencies = [ - "cfg-if", - "cpufeatures", - "hex", - "proptest", - "serde", -] - [[package]] name = "const-oid" version = "0.9.6" @@ -3119,7 +2901,7 @@ dependencies = [ "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", - "rustc_version 0.4.0", + "rustc_version", "subtle 2.6.1", "zeroize", ] @@ -3309,31 +3091,10 @@ dependencies = [ "convert_case 0.4.0", "proc-macro2", "quote", - "rustc_version 0.4.0", + "rustc_version", "syn 2.0.106", ] -[[package]] -name = "derive_more" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", - "unicode-xid", -] - [[package]] name = "diff" version = "0.1.13" @@ -3908,28 +3669,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" -[[package]] -name = "fastrlp" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" -dependencies = [ - "arrayvec 0.7.4", - "auto_impl", - "bytes", -] - -[[package]] -name = "fastrlp" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" -dependencies = [ - "arrayvec 0.7.4", - "auto_impl", - "bytes", -] - [[package]] name = "fatality" version = "0.1.1" @@ -6365,16 +6104,6 @@ dependencies = [ "cpufeatures", ] -[[package]] -name = "keccak-asm" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" -dependencies = [ - "digest 0.10.7", - "sha3-asm", -] - [[package]] name = "keystream" version = "1.0.0" @@ -8034,16 +7763,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" -[[package]] -name = "nybbles" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8983bb634df7248924ee0c4c3a749609b5abcb082c28fffe3254b3eb3602b307" -dependencies = [ - "const-hex", - "smallvec", -] - [[package]] name = "object" version = "0.30.4" @@ -8842,14 +8561,13 @@ dependencies = [ name = "pallet-build-evm-tx" version = "1.0.0" dependencies = [ - "alloy-consensus", - "alloy-primitives", - "alloy-rlp", - "derive_more 1.0.0", + "ethereum", "frame-benchmarking", "frame-support", "frame-system", + "hex", "parity-scale-codec", + "rlp", "scale-info", "sp-core", "sp-io", @@ -9549,7 +9267,7 @@ dependencies = [ name = "pallet-evm-precompile-call-permit" version = "0.1.4" dependencies = [ - "derive_more 0.99.18", + "derive_more", "evm", "fp-evm", "frame-support", @@ -9652,7 +9370,7 @@ dependencies = [ name = "pallet-genesis-history" version = "2.1.6" dependencies = [ - "derive_more 0.99.18", + "derive_more", "frame-support", "frame-system", "hex-literal", @@ -11846,7 +11564,7 @@ name = "polkadot-availability-distribution" version = "18.0.0" source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409-patch6#1ed662757b03c4f17dc2429c1df84e975d98513a" dependencies = [ - "derive_more 0.99.18", + "derive_more", "fatality", "futures", "parity-scale-codec", @@ -11965,7 +11683,7 @@ name = "polkadot-dispute-distribution" version = "18.0.0" source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409-patch6#1ed662757b03c4f17dc2429c1df84e975d98513a" dependencies = [ - "derive_more 0.99.18", + "derive_more", "fatality", "futures", "futures-timer", @@ -12068,7 +11786,7 @@ version = "18.2.0" source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409-patch6#1ed662757b03c4f17dc2429c1df84e975d98513a" dependencies = [ "bitvec", - "derive_more 0.99.18", + "derive_more", "futures", "futures-timer", "itertools 0.11.0", @@ -12405,7 +12123,7 @@ dependencies = [ "async-channel 1.9.0", "async-trait", "bitvec", - "derive_more 0.99.18", + "derive_more", "fatality", "futures", "hex", @@ -12466,7 +12184,7 @@ source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409- dependencies = [ "async-trait", "bitvec", - "derive_more 0.99.18", + "derive_more", "fatality", "futures", "orchestra", @@ -12495,7 +12213,7 @@ version = "18.0.0" source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409-patch6#1ed662757b03c4f17dc2429c1df84e975d98513a" dependencies = [ "async-trait", - "derive_more 0.99.18", + "derive_more", "fatality", "futures", "futures-channel", @@ -12622,7 +12340,7 @@ version = "14.0.0" source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409-patch6#1ed662757b03c4f17dc2429c1df84e975d98513a" dependencies = [ "bounded-collections", - "derive_more 0.99.18", + "derive_more", "parity-scale-codec", "polkadot-core-primitives", "scale-info", @@ -12762,7 +12480,7 @@ source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409- dependencies = [ "bitflags 1.3.2", "bitvec", - "derive_more 0.99.18", + "derive_more", "frame-benchmarking", "frame-support", "frame-system", @@ -13453,7 +13171,7 @@ name = "precompile-utils" version = "0.1.7" dependencies = [ "affix", - "derive_more 0.99.18", + "derive_more", "environmental", "evm", "fp-evm", @@ -13619,7 +13337,7 @@ checksum = "a172e6cc603231f2cf004232eabcecccc0da53ba576ab286ef7baa0cfc7927ad" dependencies = [ "coarsetime", "crossbeam-queue", - "derive_more 0.99.18", + "derive_more", "futures", "futures-timer", "nanorand", @@ -14178,7 +13896,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87413ebb313323d431e85d0afc5a68222aaed972843537cbfe5f061cf1b4bcab" dependencies = [ - "derive_more 0.99.18", + "derive_more", "fs-err", "static_init", "thiserror 1.0.63", @@ -14534,38 +14252,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "ruint" -version = "1.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5ef8fb1dd8de3870cb8400d51b4c2023854bbafd5431a3ac7e7317243e22d2f" -dependencies = [ - "alloy-rlp", - "ark-ff 0.3.0", - "ark-ff 0.4.2", - "bytes", - "fastrlp 0.3.1", - "fastrlp 0.4.0", - "num-bigint", - "num-integer", - "num-traits", - "parity-scale-codec", - "primitive-types 0.12.2", - "proptest", - "rand", - "rlp", - "ruint-macro", - "serde", - "valuable", - "zeroize", -] - -[[package]] -name = "ruint-macro" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" - [[package]] name = "runtime-integration-tests" version = "1.57.2" @@ -14744,15 +14430,6 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" -[[package]] -name = "rustc_version" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" -dependencies = [ - "semver 0.11.0", -] - [[package]] name = "rustc_version" version = "0.4.0" @@ -16068,7 +15745,7 @@ name = "sc-sysinfo" version = "38.0.0" source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409-patch6#1ed662757b03c4f17dc2429c1df84e975d98513a" dependencies = [ - "derive_more 0.99.18", + "derive_more", "futures", "libc", "log", @@ -16218,7 +15895,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e98f3262c250d90e700bb802eb704e1f841e03331c2eb815e46516c4edbf5b27" dependencies = [ - "derive_more 0.99.18", + "derive_more", "parity-scale-codec", "scale-bits", "scale-type-resolver", @@ -16233,7 +15910,7 @@ checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" dependencies = [ "bitvec", "cfg-if", - "derive_more 0.99.18", + "derive_more", "parity-scale-codec", "scale-info-derive", "serde", @@ -16460,16 +16137,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537" dependencies = [ - "semver-parser 0.7.0", -] - -[[package]] -name = "semver" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" -dependencies = [ - "semver-parser 0.10.3", + "semver-parser", ] [[package]] @@ -16487,15 +16155,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -[[package]] -name = "semver-parser" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" -dependencies = [ - "pest", -] - [[package]] name = "send_wrapper" version = "0.6.0" @@ -16661,16 +16320,6 @@ dependencies = [ "keccak", ] -[[package]] -name = "sha3-asm" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" -dependencies = [ - "cc", - "cfg-if", -] - [[package]] name = "sharded-slab" version = "0.1.7" @@ -16844,7 +16493,7 @@ dependencies = [ "bs58 0.5.1", "chacha20", "crossbeam-queue", - "derive_more 0.99.18", + "derive_more", "ed25519-zebra", "either", "event-listener 2.5.3", @@ -16893,7 +16542,7 @@ dependencies = [ "async-lock 2.8.0", "base64 0.21.5", "blake2-rfc", - "derive_more 0.99.18", + "derive_more", "either", "event-listener 2.5.3", "fnv", @@ -16937,7 +16586,7 @@ dependencies = [ "curve25519-dalek", "rand_core", "ring 0.17.7", - "rustc_version 0.4.0", + "rustc_version", "sha2 0.10.8", "subtle 2.6.1", ] @@ -19551,8 +19200,8 @@ dependencies = [ "ark-bls12-377", "ark-bls12-381", "ark-ec", - "ark-ff 0.4.2", - "ark-serialize 0.4.2", + "ark-ff", + "ark-serialize", "ark-serialize-derive", "arrayref", "constcat", diff --git a/pallets/build-evm-tx/Cargo.toml b/pallets/build-evm-tx/Cargo.toml index ab21f36f72..aeae60fd55 100644 --- a/pallets/build-evm-tx/Cargo.toml +++ b/pallets/build-evm-tx/Cargo.toml @@ -17,22 +17,21 @@ scale-info = { workspace = true } # Substrate dependencies sp-std = { workspace = true } -sp-runtime = { workspace = true } +sp-core = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -sp-io = { workspace = true } -alloy-consensus = { version = "0.8", default-features = false } -alloy-primitives = { version = "0.8", default-features = false } -alloy-rlp = { version = "0.3", default-features = false } -derive_more = { version = "1.0", default-features = false, features = ["display"] } +ethereum = { workspace = true } +rlp = { version = "0.5.2", default-features = false } # Optionals frame-benchmarking = { workspace = true, optional = true } [dev-dependencies] sp-io = { workspace = true } +sp-runtime = { workspace = true } sp-core = { workspace = true } +hex = { workspace = true } [features] default = ["std"] @@ -46,12 +45,9 @@ std = [ "frame-support/std", "frame-system/std", "sp-std/std", - "sp-runtime/std", - "sp-io/std", + "sp-core/std", "scale-info/std", - "alloy-consensus/std", - "alloy-primitives/std", - "alloy-rlp/std", - "derive_more/std", + "ethereum/std", + "rlp/std", ] try-runtime = ["frame-support/try-runtime"] \ No newline at end of file diff --git a/pallets/build-evm-tx/src/lib.rs b/pallets/build-evm-tx/src/lib.rs index b12e75aa86..2de64dd9b0 100644 --- a/pallets/build-evm-tx/src/lib.rs +++ b/pallets/build-evm-tx/src/lib.rs @@ -19,14 +19,15 @@ use frame_support::pallet_prelude::*; use frame_system::{ensure_signed, pallet_prelude::OriginFor}; -use sp_std::vec::Vec; +use sp_std::{convert::TryInto, vec::Vec}; -use alloy_consensus::{TxEip1559, TxType}; -use alloy_primitives::{Address, Bytes, TxKind, U256}; -use alloy_rlp::Encodable; +use ethereum::{EIP1559TransactionMessage, TransactionAction}; +use sp_core::{H160, U256}; pub use pallet::*; +const EIP1559_TX_TYPE: u8 = 0x02; + #[cfg(test)] mod tests; @@ -86,31 +87,31 @@ pub mod pallet { ensure!(data.len() <= T::MaxDataLength::get() as usize, Error::::DataTooLong); ensure!(max_priority_fee_per_gas <= max_fee_per_gas, Error::::InvalidGasPrice); - let to = match to_address { + let action = match to_address { Some(addr) => { - let address = Address::try_from(addr.as_slice()).map_err(|_| Error::::InvalidAddress)?; - TxKind::Call(address) + let address: [u8; 20] = addr.as_slice().try_into().map_err(|_| Error::::InvalidAddress)?; + TransactionAction::Call(H160::from(address)) } - None => TxKind::Create, + None => TransactionAction::Create, }; - let tx = TxEip1559 { + let tx_message = EIP1559TransactionMessage { chain_id, - nonce, - gas_limit, - max_fee_per_gas, - max_priority_fee_per_gas, - to, + nonce: U256::from(nonce), + max_priority_fee_per_gas: U256::from(max_priority_fee_per_gas), + max_fee_per_gas: U256::from(max_fee_per_gas), + gas_limit: U256::from(gas_limit), + action, value: U256::from(value), - input: Bytes::from(data), - access_list: Default::default(), + input: data, + access_list: Vec::new(), }; - let mut typed_tx = Vec::new(); - typed_tx.push(TxType::Eip1559 as u8); - tx.encode(&mut typed_tx); + let mut output = Vec::new(); + output.push(EIP1559_TX_TYPE); + output.extend_from_slice(&rlp::encode(&tx_message)); - Ok(typed_tx) + Ok(output) } } } diff --git a/pallets/build-evm-tx/src/tests/validation.rs b/pallets/build-evm-tx/src/tests/validation.rs index edd0f6b50f..64ece90d70 100644 --- a/pallets/build-evm-tx/src/tests/validation.rs +++ b/pallets/build-evm-tx/src/tests/validation.rs @@ -17,21 +17,20 @@ mod validation { let max_priority_fee_per_gas = 1000000000u128; // 1 gwei let chain_id = 1u64; // Ethereum mainnet - // Pre-computed RLP using Alloy directly with the above parameters + // Pre-computed RLP for EIP-1559 transaction with the above parameters // This was generated using: - // let tx = TxEip1559 { + // let tx = EIP1559TransactionMessage { // chain_id: 1, - // nonce: 5, - // gas_limit: 21000, - // max_fee_per_gas: 20000000000, - // max_priority_fee_per_gas: 1000000000, - // to: TxKind::Call(Address::from([0x11; 20])), + // nonce: U256::from(5), + // gas_limit: U256::from(21000), + // max_fee_per_gas: U256::from(20000000000), + // max_priority_fee_per_gas: U256::from(1000000000), + // action: TransactionAction::Call(H160::from([0x11; 20])), // value: U256::from(1000000000000000000u128), - // input: Bytes::from(vec![0x12, 0x34, 0x56, 0x78]), - // access_list: Default::default(), + // input: vec![0x12, 0x34, 0x56, 0x78], + // access_list: vec![], // }; - // let mut rlp = Vec::new(); - // tx.encode(&mut rlp); + // let encoded = rlp::encode(&tx); // prepend with 0x02 for EIP-1559 let expected_rlp = vec![ 0x02, 0xf4, 0x01, 0x05, 0x84, 0x3b, 0x9a, 0xca, 0x00, 0x85, 0x04, 0xa8, 0x17, 0xc8, 0x00, 0x82, 0x52, @@ -57,8 +56,8 @@ mod validation { returned_rlp, expected_rlp, "RLP mismatch!\nGot: {:?}\nExpected: {:?}", - alloy_primitives::hex::encode(&returned_rlp), - alloy_primitives::hex::encode(&expected_rlp) + hex::encode(&returned_rlp), + hex::encode(&expected_rlp) ); }); } From 17121ac27f370f0b6e29af12165e9f71a8945a3b Mon Sep 17 00:00:00 2001 From: Pessina Date: Sun, 21 Sep 2025 20:35:11 +0700 Subject: [PATCH 14/32] use H160 for address and include access_list on args list --- pallets/build-evm-tx/src/lib.rs | 14 +- pallets/build-evm-tx/src/tests/tests.rs | 22 +-- pallets/build-evm-tx/src/tests/validation.rs | 138 ++++++++++++++++++- 3 files changed, 145 insertions(+), 29 deletions(-) diff --git a/pallets/build-evm-tx/src/lib.rs b/pallets/build-evm-tx/src/lib.rs index 2de64dd9b0..03a944e5f5 100644 --- a/pallets/build-evm-tx/src/lib.rs +++ b/pallets/build-evm-tx/src/lib.rs @@ -19,9 +19,9 @@ use frame_support::pallet_prelude::*; use frame_system::{ensure_signed, pallet_prelude::OriginFor}; -use sp_std::{convert::TryInto, vec::Vec}; +use sp_std::vec::Vec; -use ethereum::{EIP1559TransactionMessage, TransactionAction}; +use ethereum::{AccessListItem, EIP1559TransactionMessage, TransactionAction}; use sp_core::{H160, U256}; pub use pallet::*; @@ -73,13 +73,14 @@ pub mod pallet { /// RLP-encoded transaction data with EIP-2718 type prefix (0x02 for EIP-1559) pub fn build_evm_tx( origin: OriginFor, - to_address: Option>, + to_address: Option, value: u128, data: Vec, nonce: u64, gas_limit: u64, max_fee_per_gas: u128, max_priority_fee_per_gas: u128, + access_list: Vec, chain_id: u64, ) -> Result, DispatchError> { ensure_signed(origin)?; @@ -88,10 +89,7 @@ pub mod pallet { ensure!(max_priority_fee_per_gas <= max_fee_per_gas, Error::::InvalidGasPrice); let action = match to_address { - Some(addr) => { - let address: [u8; 20] = addr.as_slice().try_into().map_err(|_| Error::::InvalidAddress)?; - TransactionAction::Call(H160::from(address)) - } + Some(addr) => TransactionAction::Call(addr), None => TransactionAction::Create, }; @@ -104,7 +102,7 @@ pub mod pallet { action, value: U256::from(value), input: data, - access_list: Vec::new(), + access_list, }; let mut output = Vec::new(); diff --git a/pallets/build-evm-tx/src/tests/tests.rs b/pallets/build-evm-tx/src/tests/tests.rs index 3ab085fbec..0966a569cd 100644 --- a/pallets/build-evm-tx/src/tests/tests.rs +++ b/pallets/build-evm-tx/src/tests/tests.rs @@ -2,33 +2,23 @@ use super::mock::*; use crate::Error; #[test] -fn invalid_address_length_fails() { +fn data_too_long_fails() { ExtBuilder::default().build().execute_with(|| { - let invalid_address = vec![0x42; 19]; // Should be 20 bytes + let large_data = vec![0xff; 100_001]; // Exceeds MaxDataLength let result = BuildEvmTx::build_evm_tx( RuntimeOrigin::signed(1u64), - Some(invalid_address), + None, 0, - vec![], + large_data, 0, 21000, 20000000000, 1000000000, + Vec::new(), 1, ); - assert_eq!(result, Err(Error::::InvalidAddress.into())); - }); -} - -#[test] -fn data_too_long_fails() { - ExtBuilder::default().build().execute_with(|| { - let large_data = vec![0xff; 100_001]; // Exceeds MaxDataLength - - let result = BuildEvmTx::build_evm_tx(RuntimeOrigin::signed(1u64), None, 0, large_data, 0, 21000, 20000000000, 1000000000, 1); - assert_eq!(result, Err(Error::::DataTooLong.into())); }); } @@ -45,10 +35,10 @@ fn invalid_gas_price_relationship_fails() { 21000, 20000000000, // max_fee_per_gas 30000000000, // max_priority_fee_per_gas (higher than max_fee) + Vec::new(), 1, ); assert_eq!(result, Err(Error::::InvalidGasPrice.into())); }); } - diff --git a/pallets/build-evm-tx/src/tests/validation.rs b/pallets/build-evm-tx/src/tests/validation.rs index 64ece90d70..e6ce505f0c 100644 --- a/pallets/build-evm-tx/src/tests/validation.rs +++ b/pallets/build-evm-tx/src/tests/validation.rs @@ -1,14 +1,13 @@ #[cfg(test)] mod validation { use super::super::mock::*; + use ethereum::AccessListItem; + use sp_core::{H160, H256}; #[test] - fn validate_against_precomputed_alloy_rlp() { + fn eip1559_call_basic_no_access_list_matches_reference_rlp() { ExtBuilder::default().build().execute_with(|| { - let to_address = vec![ - 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, - 0x11, 0x11, 0x11, - ]; + let to_address = H160::from([0x11u8; 20]); let value = 1000000000000000000u128; // 1 ETH let data = vec![0x12, 0x34, 0x56, 0x78]; let nonce = 5u64; @@ -48,6 +47,135 @@ mod validation { gas_limit, max_fee_per_gas, max_priority_fee_per_gas, + Vec::new(), + chain_id, + ) + .expect("Failed to build transaction"); + + assert_eq!( + returned_rlp, + expected_rlp, + "RLP mismatch!\nGot: {:?}\nExpected: {:?}", + hex::encode(&returned_rlp), + hex::encode(&expected_rlp) + ); + }); + } + + #[test] + fn eip1559_contract_creation_with_data_matches_reference_rlp() { + ExtBuilder::default().build().execute_with(|| { + let value = 0u128; + let data = vec![0xab, 0xcd]; + let nonce = 10u64; + let gas_limit = 53000u64; + let max_fee_per_gas = 30_000_000_000u128; // 30 gwei + let max_priority_fee_per_gas = 2_000_000_000u128; // 2 gwei + let chain_id = 1u64; + + // Reference generation (using `ethereum` crate): + // let tx = EIP1559TransactionMessage { + // chain_id: 1, + // nonce: U256::from(10), + // gas_limit: U256::from(53000), + // max_fee_per_gas: U256::from(30_000_000_000u128), + // max_priority_fee_per_gas: U256::from(2_000_000_000u128), + // action: TransactionAction::Create, + // value: U256::from(0u128), + // input: vec![0xab, 0xcd], + // access_list: vec![], + // }; + // let mut expected_rlp = vec![0x02]; + // expected_rlp.extend_from_slice(&rlp::encode(&tx)); + + let expected_rlp = vec![ + 0x02, 0xd6, 0x01, 0x0a, 0x84, 0x77, 0x35, 0x94, 0x00, 0x85, 0x06, 0xfc, 0x23, 0xac, 0x00, 0x82, 0xcf, + 0x08, 0x80, 0x80, 0x82, 0xab, 0xcd, 0xc0, + ]; + + let returned_rlp = BuildEvmTx::build_evm_tx( + RuntimeOrigin::signed(1u64), + None, + value, + data, + nonce, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + Vec::new(), + chain_id, + ) + .expect("Failed to build transaction"); + + assert_eq!( + returned_rlp, + expected_rlp, + "RLP mismatch!\nGot: {:?}\nExpected: {:?}", + hex::encode(&returned_rlp), + hex::encode(&expected_rlp) + ); + }); + } + + #[test] + fn eip1559_call_with_non_empty_access_list_matches_reference_rlp() { + ExtBuilder::default().build().execute_with(|| { + let to_address = H160::from([0x22u8; 20]); + let value = 1u128; + let data = Vec::new(); + let nonce = 3u64; + let gas_limit = 500_000u64; + let max_fee_per_gas = 40_000_000_000u128; // 40 gwei + let max_priority_fee_per_gas = 1_500_000_000u128; // 1.5 gwei + let chain_id = 1u64; + + let access_list = vec![AccessListItem { + address: H160::from([0x33u8; 20]), + storage_keys: vec![H256::from([0x44u8; 32]), H256::from([0x55u8; 32])], + }]; + + // Reference generation (using `ethereum` crate): + // let tx = EIP1559TransactionMessage { + // chain_id: 1, + // nonce: U256::from(3), + // gas_limit: U256::from(500_000), + // max_fee_per_gas: U256::from(40_000_000_000u128), + // max_priority_fee_per_gas: U256::from(1_500_000_000u128), + // action: TransactionAction::Call(H160::from([0x22; 20])), + // value: U256::from(1u128), + // input: vec![], + // access_list: vec![AccessListItem { + // address: H160::from([0x33; 20]), + // storage_keys: vec![ + // H256::from([0x44; 32]), + // H256::from([0x55; 32]), + // ], + // }], + // }; + // let mut expected_rlp = vec![0x02]; + // expected_rlp.extend_from_slice(&rlp::encode(&tx)); + + let expected_rlp = vec![ + 0x02, 0xF8, 0x85, 0x01, 0x03, 0x84, 0x59, 0x68, 0x2F, 0x00, 0x85, 0x09, 0x50, 0x2F, 0x90, 0x00, 0x83, + 0x07, 0xA1, 0x20, 0x94, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x01, 0x80, 0xF8, 0x5B, 0xF8, 0x59, 0x94, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0xF8, 0x42, 0xA0, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, + 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, + 0x44, 0xA0, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + ]; + + let returned_rlp = BuildEvmTx::build_evm_tx( + RuntimeOrigin::signed(1u64), + Some(to_address), + value, + data, + nonce, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + access_list, chain_id, ) .expect("Failed to build transaction"); From d4478ca0c9366a5add0ab860bf63456ea4b1c5b4 Mon Sep 17 00:00:00 2001 From: Osman Abdelnasir Date: Mon, 15 Sep 2025 10:35:51 +0400 Subject: [PATCH 15/32] feat: add test pallet --- Cargo.lock | 10 +- Cargo.toml | 1 + pallets/signet/Cargo.toml | 47 ++++++++++ pallets/signet/src/benchmarks.rs | 52 +++++++++++ pallets/signet/src/lib.rs | 97 ++++++++++++++++++++ pallets/signet/src/weights.rs | 53 +++++++++++ runtime/hydradx/Cargo.toml | 4 + runtime/hydradx/src/assets.rs | 5 + runtime/hydradx/src/lib.rs | 2 + runtime/hydradx/src/weights/mod.rs | 1 + runtime/hydradx/src/weights/pallet_signet.rs | 14 +++ 11 files changed, 283 insertions(+), 3 deletions(-) create mode 100644 pallets/signet/Cargo.toml create mode 100644 pallets/signet/src/benchmarks.rs create mode 100644 pallets/signet/src/lib.rs create mode 100644 pallets/signet/src/weights.rs create mode 100644 runtime/hydradx/src/weights/pallet_signet.rs diff --git a/Cargo.lock b/Cargo.lock index 78e159b069..5bba434299 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5345,6 +5345,7 @@ dependencies = [ "pallet-route-executor", "pallet-scheduler", "pallet-session", + "pallet-signet", "pallet-stableswap", "pallet-staking 4.1.5", "pallet-state-trie-migration", @@ -10533,15 +10534,18 @@ dependencies = [ ] [[package]] -name = "pallet-skip-feeless-payment" -version = "13.0.0" -source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409-patch6#1ed662757b03c4f17dc2429c1df84e975d98513a" +name = "pallet-signet" +version = "1.0.0" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", "parity-scale-codec", "scale-info", + "sp-core", + "sp-io", "sp-runtime", + "sp-std", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 704fad2d02..777d5805ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ members = [ 'liquidation-worker-support', 'pallets/hsm', 'pallets/build-evm-tx', + "pallets/signet", ] resolver = "2" diff --git a/pallets/signet/Cargo.toml b/pallets/signet/Cargo.toml new file mode 100644 index 0000000000..bc04dac9d7 --- /dev/null +++ b/pallets/signet/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "pallet-signet" +version = "1.0.0" +authors = ["GalacticCouncil"] +edition = "2021" +license = "Apache-2.0" +repository = "https://github.com/galacticcouncil/hydradx-node" +description = "Signet event emitter pallet" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true } +scale-info = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +# Optional imports for benchmarking +frame-benchmarking = { workspace = true, optional = true } +sp-io = { workspace = true, optional = true } + +[dev-dependencies] +sp-io = { workspace = true } +sp-core = { workspace = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "frame-support/std", + "frame-system/std", + "sp-runtime/std", + "sp-std/std", + "sp-io/std", + "frame-benchmarking?/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-io", +] +try-runtime = ["frame-support/try-runtime"] \ No newline at end of file diff --git a/pallets/signet/src/benchmarks.rs b/pallets/signet/src/benchmarks.rs new file mode 100644 index 0000000000..09f5e32326 --- /dev/null +++ b/pallets/signet/src/benchmarks.rs @@ -0,0 +1,52 @@ +// This file is part of HydraDX-node. + +// Copyright (C) 2020-2024 Intergalactic, Limited (GIB). +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use frame_benchmarking::benchmarks; +use frame_support::BoundedVec; +use frame_system::RawOrigin; +use sp_std::vec; + +benchmarks! { + where_clause { + where + T: Config, + } + + emit_custom_event { + let caller: T::AccountId = frame_benchmarking::whitelisted_caller(); + let message = vec![1u8; 256]; + let value = 1234567890u128; + + }: _(RawOrigin::Signed(caller.clone()), message.clone(), value) + verify { + assert_last_event::(Event::DataEmitted { + who: caller, + message: BoundedVec::try_from(message).unwrap(), + value, + }.into()); + } + + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); +} + +// Helper function to assert last event +fn assert_last_event(generic_event: ::RuntimeEvent) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} \ No newline at end of file diff --git a/pallets/signet/src/lib.rs b/pallets/signet/src/lib.rs new file mode 100644 index 0000000000..f7a128cba1 --- /dev/null +++ b/pallets/signet/src/lib.rs @@ -0,0 +1,97 @@ +// This file is part of HydraDX-node. + +// Copyright (C) 2020-2024 Intergalactic, Limited (GIB). +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::pallet_prelude::*; +use frame_system::pallet_prelude::*; +use sp_std::vec::Vec; + +pub use pallet::*; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarks; + +pub mod weights; +pub use weights::WeightInfo; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Weight information for extrinsics in this pallet + type WeightInfo: WeightInfo; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Custom data was emitted + /// Fields: [who, message, value] + DataEmitted { + who: T::AccountId, + message: BoundedVec>, + value: u128, + }, + } + + #[pallet::error] + pub enum Error { + /// The provided message exceeds the maximum length of 256 bytes + MessageTooLong, + } + + #[pallet::call] + impl Pallet { + /// Emit a custom event with provided data + /// + /// Parameters: + /// - `origin`: The transaction origin (must be signed) + /// - `message`: UTF-8 encoded message (max 256 bytes) + /// - `value`: Numeric value to include in the event + /// + /// Emits `DataEmitted` event when successful + #[pallet::call_index(0)] + #[pallet::weight(::WeightInfo::emit_custom_event())] + pub fn emit_custom_event( + origin: OriginFor, + message: Vec, + value: u128, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + let bounded_message = BoundedVec::>::try_from(message) + .map_err(|_| Error::::MessageTooLong)?; + + Self::deposit_event(Event::DataEmitted { + who, + message: bounded_message, + value, + }); + + Ok(()) + } + } +} \ No newline at end of file diff --git a/pallets/signet/src/weights.rs b/pallets/signet/src/weights.rs new file mode 100644 index 0000000000..a1b24f8147 --- /dev/null +++ b/pallets/signet/src/weights.rs @@ -0,0 +1,53 @@ +// This file is part of HydraDX-node. + +// Copyright (C) 2020-2024 Intergalactic, Limited (GIB). +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{ + traits::Get, + weights::{constants::RocksDbWeight, Weight}, +}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_signet +pub trait WeightInfo { + fn emit_custom_event() -> Weight; +} + +/// Weights for pallet_signet using the HydraDX node and recommended hardware +pub struct HydraWeight(PhantomData); +impl WeightInfo for HydraWeight { + fn emit_custom_event() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_000_000 picoseconds. + Weight::from_parts(2_500_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn emit_custom_event() -> Weight { + Weight::from_parts(2_500_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } +} \ No newline at end of file diff --git a/runtime/hydradx/Cargo.toml b/runtime/hydradx/Cargo.toml index 6959f82cea..b14c50922f 100644 --- a/runtime/hydradx/Cargo.toml +++ b/runtime/hydradx/Cargo.toml @@ -92,6 +92,7 @@ pallet-uniques = { workspace = true } pallet-whitelist = { workspace = true } pallet-message-queue = { workspace = true } pallet-state-trie-migration = { workspace = true } +pallet-signet = { path = "../../pallets/signet", default-features = false } # ORML dependencies orml-tokens = { workspace = true } @@ -258,6 +259,7 @@ runtime-benchmarks = [ "pallet-xcm-benchmarks/runtime-benchmarks", "ismp-parachain/runtime-benchmarks", "pallet-token-gateway/runtime-benchmarks", + "pallet-signet/runtime-benchmarks", ] std = [ "codec/std", @@ -393,6 +395,7 @@ std = [ "ismp/std", "ismp-parachain/std", "ismp-parachain-runtime-api/std", + "pallet-signet/std", ] try-runtime = [ "frame-try-runtime", @@ -472,6 +475,7 @@ try-runtime = [ "pallet-whitelist/try-runtime", "pallet-broadcast/try-runtime", "pallet-dispatcher/try-runtime", + "pallet-signet/try-runtime", ] metadata-hash = [ diff --git a/runtime/hydradx/src/assets.rs b/runtime/hydradx/src/assets.rs index d9805713ca..0f73fa24c1 100644 --- a/runtime/hydradx/src/assets.rs +++ b/runtime/hydradx/src/assets.rs @@ -1840,6 +1840,11 @@ impl pallet_build_evm_tx::Config for Runtime { type MaxDataLength = MaxEvmDataLength; } +impl pallet_signet::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = weights::pallet_signet::HydraWeight; +} + pub struct ConvertViaOmnipool(PhantomData); impl Convert for ConvertViaOmnipool where diff --git a/runtime/hydradx/src/lib.rs b/runtime/hydradx/src/lib.rs index fc85dc3032..4992ba3925 100644 --- a/runtime/hydradx/src/lib.rs +++ b/runtime/hydradx/src/lib.rs @@ -193,6 +193,7 @@ construct_runtime!( HSM: pallet_hsm = 82, Parameters: pallet_parameters = 83, BuildEvmTx: pallet_build_evm_tx = 86, + Signet: pallet_signet = 84, // ORML related modules Tokens: orml_tokens = 77, @@ -346,6 +347,7 @@ mod benches { [pallet_dynamic_fees, DynamicFees] [ismp_parachain, IsmpParachain] [pallet_token_gateway, TokenGateway] + [pallet_signet, Signet] ); } diff --git a/runtime/hydradx/src/weights/mod.rs b/runtime/hydradx/src/weights/mod.rs index 70670db723..2f7e1af181 100644 --- a/runtime/hydradx/src/weights/mod.rs +++ b/runtime/hydradx/src/weights/mod.rs @@ -53,3 +53,4 @@ pub mod pallet_xcm; pub mod pallet_xyk; pub mod pallet_xyk_liquidity_mining; pub mod xcm; +pub mod pallet_signet; diff --git a/runtime/hydradx/src/weights/pallet_signet.rs b/runtime/hydradx/src/weights/pallet_signet.rs new file mode 100644 index 0000000000..0618028e69 --- /dev/null +++ b/runtime/hydradx/src/weights/pallet_signet.rs @@ -0,0 +1,14 @@ +use frame_support::weights::Weight; +use sp_runtime::traits::Get; +use sp_std::marker::PhantomData; + +pub use pallet_signet::weights::*; + +pub struct HydraWeight(PhantomData); + +impl pallet_signet::weights::WeightInfo for HydraWeight { + fn emit_custom_event() -> Weight { + Weight::from_parts(2_500_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } +} \ No newline at end of file From 7c021f8122731eb611a9df175ebde5c1fceaff86 Mon Sep 17 00:00:00 2001 From: Osman Abdelnasir Date: Mon, 15 Sep 2025 11:05:48 +0400 Subject: [PATCH 16/32] feat: admin account --- pallets/signet/src/benchmarks.rs | 55 +++------- pallets/signet/src/lib.rs | 90 +++++++++++----- pallets/signet/src/tests.rs | 179 +++++++++++++++++++++++++++++++ pallets/signet/src/weights.rs | 49 ++------- 4 files changed, 266 insertions(+), 107 deletions(-) create mode 100644 pallets/signet/src/tests.rs diff --git a/pallets/signet/src/benchmarks.rs b/pallets/signet/src/benchmarks.rs index 09f5e32326..a9b920dcda 100644 --- a/pallets/signet/src/benchmarks.rs +++ b/pallets/signet/src/benchmarks.rs @@ -1,52 +1,29 @@ -// This file is part of HydraDX-node. - -// Copyright (C) 2020-2024 Intergalactic, Limited (GIB). -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - #![cfg(feature = "runtime-benchmarks")] use super::*; -use frame_benchmarking::benchmarks; -use frame_support::BoundedVec; +use frame_benchmarking::{benchmarks, whitelisted_caller}; use frame_system::RawOrigin; -use sp_std::vec; benchmarks! { - where_clause { - where - T: Config, + initialize { + let admin: T::AccountId = whitelisted_caller(); + }: _(RawOrigin::Root, admin.clone()) + verify { + assert_eq!(Admin::::get(), Some(admin)); } - + emit_custom_event { - let caller: T::AccountId = frame_benchmarking::whitelisted_caller(); - let message = vec![1u8; 256]; - let value = 1234567890u128; + // Setup: Initialize first + let admin: T::AccountId = whitelisted_caller(); + let _ = Pallet::::initialize(RawOrigin::Root.into(), admin); - }: _(RawOrigin::Signed(caller.clone()), message.clone(), value) + let caller: T::AccountId = whitelisted_caller(); + let message = vec![1u8; 100]; + let value = 12345u128; + }: _(RawOrigin::Signed(caller.clone()), message, value) verify { - assert_last_event::(Event::DataEmitted { - who: caller, - message: BoundedVec::try_from(message).unwrap(), - value, - }.into()); + // Event was emitted } - - impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); -} -// Helper function to assert last event -fn assert_last_event(generic_event: ::RuntimeEvent) { - frame_system::Pallet::::assert_last_event(generic_event.into()); + impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test); } \ No newline at end of file diff --git a/pallets/signet/src/lib.rs b/pallets/signet/src/lib.rs index f7a128cba1..7c4ddce994 100644 --- a/pallets/signet/src/lib.rs +++ b/pallets/signet/src/lib.rs @@ -1,20 +1,3 @@ -// This file is part of HydraDX-node. - -// Copyright (C) 2020-2024 Intergalactic, Limited (GIB). -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - #![cfg_attr(not(feature = "std"), no_std)] use frame_support::pallet_prelude::*; @@ -29,6 +12,10 @@ mod benchmarks; pub mod weights; pub use weights::WeightInfo; +// For testing +#[cfg(test)] +mod tests; + #[frame_support::pallet] pub mod pallet { use super::*; @@ -38,42 +25,86 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { - /// The overarching event type type RuntimeEvent: From> + IsType<::RuntimeEvent>; - /// Weight information for extrinsics in this pallet + /// Weight information for extrinsics type WeightInfo: WeightInfo; } + // ======================================== + // Storage + // ======================================== + + /// The admin account that controls this pallet + #[pallet::storage] + #[pallet::getter(fn admin)] + pub type Admin = StorageValue<_, T::AccountId>; + + // ======================================== + // Events + // ======================================== + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// Custom data was emitted - /// Fields: [who, message, value] DataEmitted { who: T::AccountId, message: BoundedVec>, value: u128, }, + + /// Pallet has been initialized with an admin + Initialized { + admin: T::AccountId, + }, } + // ======================================== + // Errors + // ======================================== + #[pallet::error] pub enum Error { /// The provided message exceeds the maximum length of 256 bytes MessageTooLong, + /// The pallet has already been initialized + AlreadyInitialized, + /// The pallet has not been initialized yet + NotInitialized, } + // ======================================== + // Extrinsics + // ======================================== + #[pallet::call] impl Pallet { - /// Emit a custom event with provided data - /// - /// Parameters: - /// - `origin`: The transaction origin (must be signed) - /// - `message`: UTF-8 encoded message (max 256 bytes) - /// - `value`: Numeric value to include in the event - /// - /// Emits `DataEmitted` event when successful + + /// Initialize the pallet with an admin account #[pallet::call_index(0)] + #[pallet::weight(::WeightInfo::initialize())] + pub fn initialize( + origin: OriginFor, + admin: T::AccountId, + ) -> DispatchResult { + // Only root (sudo) can initialize + ensure_root(origin)?; + + // Make sure we haven't initialized already + ensure!(Admin::::get().is_none(), Error::::AlreadyInitialized); + + // Store the admin + Admin::::put(&admin); + + // Emit event + Self::deposit_event(Event::Initialized { admin }); + + Ok(()) + } + + /// Emit a custom event with data + #[pallet::call_index(1)] #[pallet::weight(::WeightInfo::emit_custom_event())] pub fn emit_custom_event( origin: OriginFor, @@ -82,6 +113,9 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; + // Check that pallet is initialized + ensure!(Admin::::get().is_some(), Error::::NotInitialized); + let bounded_message = BoundedVec::>::try_from(message) .map_err(|_| Error::::MessageTooLong)?; diff --git a/pallets/signet/src/tests.rs b/pallets/signet/src/tests.rs new file mode 100644 index 0000000000..eeb0f084d7 --- /dev/null +++ b/pallets/signet/src/tests.rs @@ -0,0 +1,179 @@ +use crate::{self as pallet_signet, *}; +use frame_support::{ + assert_noop, assert_ok, + parameter_types, + traits::{ConstU16, ConstU64}, +}; +use frame_system as system; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; + +// Create a mock runtime for testing +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + Signet: pallet_signet, + } +); + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 42; +} + +impl system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; // Using u64 for simple test accounts + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = ConstU16<42>; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; + type RuntimeTask = (); + type SingleBlockMigrations = (); + type MultiBlockMigrator = (); + type PreInherents = (); + type PostInherents = (); + type PostTransactions = (); +} + +impl pallet_signet::Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); // Using () for tests +} + +// Build test environment +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +// ======================================== +// 🧪 TESTS START HERE +// ======================================== + +#[test] +fn test_initialize_works() { + new_test_ext().execute_with(|| { + // Account 1 will be our admin + let admin_account = 1u64; + + // Before initialization, admin should be None + assert_eq!(Signet::admin(), None); + + // Initialize the pallet (must use root origin) + assert_ok!(Signet::initialize( + RuntimeOrigin::root(), + admin_account + )); + + // After initialization, admin should be set + assert_eq!(Signet::admin(), Some(admin_account)); + + // Check that the event was emitted + System::assert_last_event( + Event::Initialized { admin: admin_account }.into() + ); + }); +} + +#[test] +fn test_cannot_initialize_twice() { + new_test_ext().execute_with(|| { + // First initialization should work + assert_ok!(Signet::initialize(RuntimeOrigin::root(), 1)); + + // Second initialization should fail + assert_noop!( + Signet::initialize(RuntimeOrigin::root(), 2), + Error::::AlreadyInitialized + ); + + // Admin should still be the first one + assert_eq!(Signet::admin(), Some(1)); + }); +} + +#[test] +fn test_cannot_use_before_initialization() { + new_test_ext().execute_with(|| { + // Try to emit event before initialization + assert_noop!( + Signet::emit_custom_event( + RuntimeOrigin::signed(1), + b"hello".to_vec(), + 123 + ), + Error::::NotInitialized + ); + }); +} + +#[test] +fn test_emit_event_after_initialization() { + new_test_ext().execute_with(|| { + // Initialize first + assert_ok!(Signet::initialize(RuntimeOrigin::root(), 1)); + + // Now we can emit events + let sender = 2u64; + let message = b"Hello World".to_vec(); + let value = 12345u128; + + assert_ok!(Signet::emit_custom_event( + RuntimeOrigin::signed(sender), + message.clone(), + value + )); + + // Check the event + System::assert_last_event( + Event::DataEmitted { + who: sender, + message: BoundedVec::try_from(message).unwrap(), + value, + }.into() + ); + }); +} + +#[test] +fn test_only_root_can_initialize() { + new_test_ext().execute_with(|| { + // Regular user (not root) tries to initialize + assert_noop!( + Signet::initialize(RuntimeOrigin::signed(1), 1), + sp_runtime::DispatchError::BadOrigin + ); + + // Root can initialize + assert_ok!(Signet::initialize(RuntimeOrigin::root(), 1)); + }); +} \ No newline at end of file diff --git a/pallets/signet/src/weights.rs b/pallets/signet/src/weights.rs index a1b24f8147..5b8713f521 100644 --- a/pallets/signet/src/weights.rs +++ b/pallets/signet/src/weights.rs @@ -1,53 +1,22 @@ -// This file is part of HydraDX-node. - -// Copyright (C) 2020-2024 Intergalactic, Limited (GIB). -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] -#![allow(missing_docs)] -use frame_support::{ - traits::Get, - weights::{constants::RocksDbWeight, Weight}, -}; +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use sp_std::marker::PhantomData; -/// Weight functions needed for pallet_signet +/// Weight functions for this pallet pub trait WeightInfo { + fn initialize() -> Weight; fn emit_custom_event() -> Weight; } -/// Weights for pallet_signet using the HydraDX node and recommended hardware -pub struct HydraWeight(PhantomData); -impl WeightInfo for HydraWeight { - fn emit_custom_event() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 2_000_000 picoseconds. - Weight::from_parts(2_500_000, 0) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } -} - -// For backwards compatibility and tests +/// For tests - just returns simple weights impl WeightInfo for () { + fn initialize() -> Weight { + Weight::from_parts(10_000_000, 0) + } + fn emit_custom_event() -> Weight { - Weight::from_parts(2_500_000, 0) - .saturating_add(RocksDbWeight::get().writes(1_u64)) + Weight::from_parts(5_000_000, 0) } } \ No newline at end of file From bfab24b99b6d9ae2abb0d3464e3fca16c02b8dc2 Mon Sep 17 00:00:00 2001 From: Osman Abdelnasir Date: Mon, 15 Sep 2025 11:24:11 +0400 Subject: [PATCH 17/32] feat: signature deposit --- pallets/signet/src/benchmarks.rs | 17 ++++- pallets/signet/src/lib.rs | 63 ++++++++++++++-- pallets/signet/src/tests.rs | 120 +++++++++++++++++++++++++++++-- pallets/signet/src/weights.rs | 5 ++ 4 files changed, 190 insertions(+), 15 deletions(-) diff --git a/pallets/signet/src/benchmarks.rs b/pallets/signet/src/benchmarks.rs index a9b920dcda..84efca56d9 100644 --- a/pallets/signet/src/benchmarks.rs +++ b/pallets/signet/src/benchmarks.rs @@ -7,15 +7,28 @@ use frame_system::RawOrigin; benchmarks! { initialize { let admin: T::AccountId = whitelisted_caller(); - }: _(RawOrigin::Root, admin.clone()) + let deposit = 1000u128; + }: _(RawOrigin::Root, admin.clone(), deposit) verify { assert_eq!(Admin::::get(), Some(admin)); + assert_eq!(SignatureDeposit::::get(), deposit); + } + + update_deposit { + // Setup: Initialize first + let admin: T::AccountId = whitelisted_caller(); + let _ = Pallet::::initialize(RawOrigin::Root.into(), admin.clone(), 1000u128); + + let new_deposit = 2000u128; + }: _(RawOrigin::Signed(admin), new_deposit) + verify { + assert_eq!(SignatureDeposit::::get(), new_deposit); } emit_custom_event { // Setup: Initialize first let admin: T::AccountId = whitelisted_caller(); - let _ = Pallet::::initialize(RawOrigin::Root.into(), admin); + let _ = Pallet::::initialize(RawOrigin::Root.into(), admin, 1000u128); let caller: T::AccountId = whitelisted_caller(); let message = vec![1u8; 100]; diff --git a/pallets/signet/src/lib.rs b/pallets/signet/src/lib.rs index 7c4ddce994..73a9b79cd9 100644 --- a/pallets/signet/src/lib.rs +++ b/pallets/signet/src/lib.rs @@ -12,7 +12,6 @@ mod benchmarks; pub mod weights; pub use weights::WeightInfo; -// For testing #[cfg(test)] mod tests; @@ -26,8 +25,6 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; - - /// Weight information for extrinsics type WeightInfo: WeightInfo; } @@ -40,6 +37,12 @@ pub mod pallet { #[pallet::getter(fn admin)] pub type Admin = StorageValue<_, T::AccountId>; + // Storage for signature deposit amount + /// The amount required as deposit for signature requests + #[pallet::storage] + #[pallet::getter(fn signature_deposit)] + pub type SignatureDeposit = StorageValue<_, u128, ValueQuery>; + // ======================================== // Events // ======================================== @@ -57,6 +60,14 @@ pub mod pallet { /// Pallet has been initialized with an admin Initialized { admin: T::AccountId, + signature_deposit: u128, // šŸ†• NEW: Added deposit to event + }, + + // šŸ†• NEW: Event for deposit updates + /// Signature deposit amount has been updated + DepositUpdated { + old_deposit: u128, + new_deposit: u128, }, } @@ -72,6 +83,9 @@ pub mod pallet { AlreadyInitialized, /// The pallet has not been initialized yet NotInitialized, + // Error for unauthorized access + /// Unauthorized - caller is not admin + Unauthorized, } // ======================================== @@ -81,12 +95,13 @@ pub mod pallet { #[pallet::call] impl Pallet { - /// Initialize the pallet with an admin account + /// Initialize the pallet with an admin account and initial deposit #[pallet::call_index(0)] #[pallet::weight(::WeightInfo::initialize())] pub fn initialize( origin: OriginFor, admin: T::AccountId, + signature_deposit: u128, ) -> DispatchResult { // Only root (sudo) can initialize ensure_root(origin)?; @@ -97,14 +112,50 @@ pub mod pallet { // Store the admin Admin::::put(&admin); + SignatureDeposit::::put(signature_deposit); + + // Emit event (updated to include deposit) + Self::deposit_event(Event::Initialized { + admin, + signature_deposit, // šŸ†• NEW + }); + + Ok(()) + } + + // Function to update deposit (admin only) + /// Update the signature deposit amount (admin only) + #[pallet::call_index(1)] + #[pallet::weight(::WeightInfo::update_deposit())] + pub fn update_deposit( + origin: OriginFor, + new_deposit: u128, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + // Check that pallet is initialized and get admin + let admin = Admin::::get().ok_or(Error::::NotInitialized)?; + + // Check that caller is admin + ensure!(who == admin, Error::::Unauthorized); + + // Get old deposit for event + let old_deposit = SignatureDeposit::::get(); + + // Update the deposit + SignatureDeposit::::put(new_deposit); + // Emit event - Self::deposit_event(Event::Initialized { admin }); + Self::deposit_event(Event::DepositUpdated { + old_deposit, + new_deposit, + }); Ok(()) } /// Emit a custom event with data - #[pallet::call_index(1)] + #[pallet::call_index(2)] #[pallet::weight(::WeightInfo::emit_custom_event())] pub fn emit_custom_event( origin: OriginFor, diff --git a/pallets/signet/src/tests.rs b/pallets/signet/src/tests.rs index eeb0f084d7..01ba675be1 100644 --- a/pallets/signet/src/tests.rs +++ b/pallets/signet/src/tests.rs @@ -84,6 +84,7 @@ fn test_initialize_works() { new_test_ext().execute_with(|| { // Account 1 will be our admin let admin_account = 1u64; + let deposit = 1000u128; // šŸ†• Added deposit // Before initialization, admin should be None assert_eq!(Signet::admin(), None); @@ -91,15 +92,20 @@ fn test_initialize_works() { // Initialize the pallet (must use root origin) assert_ok!(Signet::initialize( RuntimeOrigin::root(), - admin_account + admin_account, + deposit // šŸ†• Added deposit parameter )); // After initialization, admin should be set assert_eq!(Signet::admin(), Some(admin_account)); + assert_eq!(Signet::signature_deposit(), deposit); // šŸ†• Check deposit too // Check that the event was emitted System::assert_last_event( - Event::Initialized { admin: admin_account }.into() + Event::Initialized { + admin: admin_account, + signature_deposit: deposit // šŸ†• Added to event + }.into() ); }); } @@ -108,11 +114,11 @@ fn test_initialize_works() { fn test_cannot_initialize_twice() { new_test_ext().execute_with(|| { // First initialization should work - assert_ok!(Signet::initialize(RuntimeOrigin::root(), 1)); + assert_ok!(Signet::initialize(RuntimeOrigin::root(), 1, 1000)); // šŸ†• Added deposit // Second initialization should fail assert_noop!( - Signet::initialize(RuntimeOrigin::root(), 2), + Signet::initialize(RuntimeOrigin::root(), 2, 2000), // šŸ†• Added deposit Error::::AlreadyInitialized ); @@ -140,7 +146,7 @@ fn test_cannot_use_before_initialization() { fn test_emit_event_after_initialization() { new_test_ext().execute_with(|| { // Initialize first - assert_ok!(Signet::initialize(RuntimeOrigin::root(), 1)); + assert_ok!(Signet::initialize(RuntimeOrigin::root(), 1, 1000)); // šŸ†• Added deposit // Now we can emit events let sender = 2u64; @@ -169,11 +175,111 @@ fn test_only_root_can_initialize() { new_test_ext().execute_with(|| { // Regular user (not root) tries to initialize assert_noop!( - Signet::initialize(RuntimeOrigin::signed(1), 1), + Signet::initialize(RuntimeOrigin::signed(1), 1, 1000), // šŸ†• Added deposit sp_runtime::DispatchError::BadOrigin ); // Root can initialize - assert_ok!(Signet::initialize(RuntimeOrigin::root(), 1)); + assert_ok!(Signet::initialize(RuntimeOrigin::root(), 1, 1000)); // šŸ†• Added deposit + }); +} + +#[test] +fn test_initialize_sets_deposit() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let initial_deposit = 1000u128; + + // Initialize with deposit + assert_ok!(Signet::initialize( + RuntimeOrigin::root(), + admin, + initial_deposit + )); + + // Check that deposit was set + assert_eq!(Signet::signature_deposit(), initial_deposit); + + // Check event includes deposit + System::assert_last_event( + Event::Initialized { + admin, + signature_deposit: initial_deposit, + }.into() + ); + }); +} + +#[test] +fn test_update_deposit_as_admin() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let initial_deposit = 1000u128; + let new_deposit = 2000u128; + + // Initialize first + assert_ok!(Signet::initialize( + RuntimeOrigin::root(), + admin, + initial_deposit + )); + + // Admin updates deposit + assert_ok!(Signet::update_deposit( + RuntimeOrigin::signed(admin), + new_deposit + )); + + // Check deposit was updated + assert_eq!(Signet::signature_deposit(), new_deposit); + + // Check event + System::assert_last_event( + Event::DepositUpdated { + old_deposit: initial_deposit, + new_deposit, + }.into() + ); + }); +} + +#[test] +fn test_non_admin_cannot_update_deposit() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let non_admin = 2u64; + + // Initialize + assert_ok!(Signet::initialize( + RuntimeOrigin::root(), + admin, + 1000 + )); + + // Non-admin tries to update deposit + assert_noop!( + Signet::update_deposit( + RuntimeOrigin::signed(non_admin), + 2000 + ), + Error::::Unauthorized + ); + + // Deposit should not change + assert_eq!(Signet::signature_deposit(), 1000); + }); +} + +#[test] +fn test_cannot_update_deposit_before_initialization() { + new_test_ext().execute_with(|| { + // Try to update deposit before initialization + assert_noop!( + Signet::update_deposit( + RuntimeOrigin::signed(1), + 1000 + ), + Error::::NotInitialized + ); }); } \ No newline at end of file diff --git a/pallets/signet/src/weights.rs b/pallets/signet/src/weights.rs index 5b8713f521..b331b90978 100644 --- a/pallets/signet/src/weights.rs +++ b/pallets/signet/src/weights.rs @@ -7,6 +7,7 @@ use sp_std::marker::PhantomData; /// Weight functions for this pallet pub trait WeightInfo { fn initialize() -> Weight; + fn update_deposit() -> Weight; // šŸ†• NEW fn emit_custom_event() -> Weight; } @@ -16,6 +17,10 @@ impl WeightInfo for () { Weight::from_parts(10_000_000, 0) } + fn update_deposit() -> Weight { + Weight::from_parts(8_000_000, 0) + } + fn emit_custom_event() -> Weight { Weight::from_parts(5_000_000, 0) } From 70d1e136cf7e08565e8011ad8869b9193777343a Mon Sep 17 00:00:00 2001 From: Osman Abdelnasir Date: Mon, 15 Sep 2025 12:19:23 +0400 Subject: [PATCH 18/32] feat: withdraw funds --- pallets/signet/Cargo.toml | 2 + pallets/signet/src/benchmarks.rs | 29 ++++- pallets/signet/src/lib.rs | 95 ++++++++++++--- pallets/signet/src/tests.rs | 200 ++++++++++++++++++++++++------- pallets/signet/src/weights.rs | 7 +- 5 files changed, 271 insertions(+), 62 deletions(-) diff --git a/pallets/signet/Cargo.toml b/pallets/signet/Cargo.toml index bc04dac9d7..cb359be05b 100644 --- a/pallets/signet/Cargo.toml +++ b/pallets/signet/Cargo.toml @@ -25,6 +25,7 @@ sp-io = { workspace = true, optional = true } [dev-dependencies] sp-io = { workspace = true } sp-core = { workspace = true } +pallet-balances = { workspace = true } [features] default = ["std"] @@ -37,6 +38,7 @@ std = [ "sp-std/std", "sp-io/std", "frame-benchmarking?/std", + "pallet-balances/std" ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", diff --git a/pallets/signet/src/benchmarks.rs b/pallets/signet/src/benchmarks.rs index 84efca56d9..8c315868b0 100644 --- a/pallets/signet/src/benchmarks.rs +++ b/pallets/signet/src/benchmarks.rs @@ -3,11 +3,12 @@ use super::*; use frame_benchmarking::{benchmarks, whitelisted_caller}; use frame_system::RawOrigin; +use frame_support::traits::Currency; benchmarks! { initialize { let admin: T::AccountId = whitelisted_caller(); - let deposit = 1000u128; + let deposit = T::Currency::minimum_balance(); }: _(RawOrigin::Root, admin.clone(), deposit) verify { assert_eq!(Admin::::get(), Some(admin)); @@ -17,18 +18,38 @@ benchmarks! { update_deposit { // Setup: Initialize first let admin: T::AccountId = whitelisted_caller(); - let _ = Pallet::::initialize(RawOrigin::Root.into(), admin.clone(), 1000u128); + let initial_deposit = T::Currency::minimum_balance(); + let _ = Pallet::::initialize(RawOrigin::Root.into(), admin.clone(), initial_deposit); - let new_deposit = 2000u128; + let new_deposit = initial_deposit * 2u32.into(); }: _(RawOrigin::Signed(admin), new_deposit) verify { assert_eq!(SignatureDeposit::::get(), new_deposit); } + // šŸ†• NEW: Benchmark for withdraw_funds + withdraw_funds { + // Setup: Initialize and fund the pallet + let admin: T::AccountId = whitelisted_caller(); + let _ = Pallet::::initialize(RawOrigin::Root.into(), admin.clone(), T::Currency::minimum_balance()); + + // Fund the pallet account + let pallet_account = Pallet::::account_id(); + let amount = T::Currency::minimum_balance() * 100u32.into(); + let _ = T::Currency::deposit_creating(&pallet_account, amount); + + let recipient: T::AccountId = whitelisted_caller(); + let withdraw_amount = T::Currency::minimum_balance() * 50u32.into(); + }: _(RawOrigin::Signed(admin), recipient.clone(), withdraw_amount) + verify { + // Verify funds were transferred + assert!(T::Currency::free_balance(&recipient) >= withdraw_amount); + } + emit_custom_event { // Setup: Initialize first let admin: T::AccountId = whitelisted_caller(); - let _ = Pallet::::initialize(RawOrigin::Root.into(), admin, 1000u128); + let _ = Pallet::::initialize(RawOrigin::Root.into(), admin, T::Currency::minimum_balance()); let caller: T::AccountId = whitelisted_caller(); let message = vec![1u8; 100]; diff --git a/pallets/signet/src/lib.rs b/pallets/signet/src/lib.rs index 73a9b79cd9..c2731cecc0 100644 --- a/pallets/signet/src/lib.rs +++ b/pallets/signet/src/lib.rs @@ -1,11 +1,18 @@ #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::pallet_prelude::*; +use frame_support::{ + pallet_prelude::*, + traits::{Currency, ExistenceRequirement}, + PalletId, +}; use frame_system::pallet_prelude::*; +use sp_runtime::traits::AccountIdConversion; use sp_std::vec::Vec; pub use pallet::*; +type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; + #[cfg(feature = "runtime-benchmarks")] mod benchmarks; @@ -25,6 +32,14 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Currency for handling deposits and fees + type Currency: Currency; + + /// The pallet's unique ID for deriving its account + #[pallet::constant] + type PalletId: Get; + type WeightInfo: WeightInfo; } @@ -37,11 +52,10 @@ pub mod pallet { #[pallet::getter(fn admin)] pub type Admin = StorageValue<_, T::AccountId>; - // Storage for signature deposit amount /// The amount required as deposit for signature requests #[pallet::storage] #[pallet::getter(fn signature_deposit)] - pub type SignatureDeposit = StorageValue<_, u128, ValueQuery>; + pub type SignatureDeposit = StorageValue<_, BalanceOf, ValueQuery>; // ======================================== // Events @@ -60,14 +74,20 @@ pub mod pallet { /// Pallet has been initialized with an admin Initialized { admin: T::AccountId, - signature_deposit: u128, // šŸ†• NEW: Added deposit to event + signature_deposit: BalanceOf, }, - // šŸ†• NEW: Event for deposit updates /// Signature deposit amount has been updated DepositUpdated { - old_deposit: u128, - new_deposit: u128, + old_deposit: BalanceOf, + new_deposit: BalanceOf, + }, + + + /// Funds have been withdrawn from the pallet + FundsWithdrawn { + amount: BalanceOf, + recipient: T::AccountId, }, } @@ -83,9 +103,11 @@ pub mod pallet { AlreadyInitialized, /// The pallet has not been initialized yet NotInitialized, - // Error for unauthorized access /// Unauthorized - caller is not admin Unauthorized, + + /// Insufficient funds for withdrawal + InsufficientFunds, } // ======================================== @@ -101,7 +123,7 @@ pub mod pallet { pub fn initialize( origin: OriginFor, admin: T::AccountId, - signature_deposit: u128, + signature_deposit: BalanceOf, ) -> DispatchResult { // Only root (sudo) can initialize ensure_root(origin)?; @@ -112,24 +134,24 @@ pub mod pallet { // Store the admin Admin::::put(&admin); + // Store the signature deposit SignatureDeposit::::put(signature_deposit); - // Emit event (updated to include deposit) + // Emit event Self::deposit_event(Event::Initialized { admin, - signature_deposit, // šŸ†• NEW + signature_deposit, }); Ok(()) } - // Function to update deposit (admin only) /// Update the signature deposit amount (admin only) #[pallet::call_index(1)] #[pallet::weight(::WeightInfo::update_deposit())] pub fn update_deposit( origin: OriginFor, - new_deposit: u128, + new_deposit: BalanceOf, ) -> DispatchResult { let who = ensure_signed(origin)?; @@ -154,8 +176,46 @@ pub mod pallet { Ok(()) } - /// Emit a custom event with data + /// Withdraw funds from the pallet account (admin only) #[pallet::call_index(2)] + #[pallet::weight(::WeightInfo::withdraw_funds())] + pub fn withdraw_funds( + origin: OriginFor, + recipient: T::AccountId, + amount: BalanceOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + // Check admin authorization + let admin = Admin::::get().ok_or(Error::::NotInitialized)?; + ensure!(who == admin, Error::::Unauthorized); + + // Get pallet account + let pallet_account = Self::account_id(); + + // Check sufficient balance + let pallet_balance = T::Currency::free_balance(&pallet_account); + ensure!(pallet_balance >= amount, Error::::InsufficientFunds); + + // Transfer funds from pallet to recipient + T::Currency::transfer( + &pallet_account, + &recipient, + amount, + ExistenceRequirement::AllowDeath, // Allow pallet account to be emptied + )?; + + // Emit event + Self::deposit_event(Event::FundsWithdrawn { + amount, + recipient, + }); + + Ok(()) + } + + /// Emit a custom event with data + #[pallet::call_index(3)] #[pallet::weight(::WeightInfo::emit_custom_event())] pub fn emit_custom_event( origin: OriginFor, @@ -179,4 +239,11 @@ pub mod pallet { Ok(()) } } + + impl Pallet { + /// Get the pallet's account ID (where funds are stored) + pub fn account_id() -> T::AccountId { + T::PalletId::get().into_account_truncating() + } + } } \ No newline at end of file diff --git a/pallets/signet/src/tests.rs b/pallets/signet/src/tests.rs index 01ba675be1..6ab3e75931 100644 --- a/pallets/signet/src/tests.rs +++ b/pallets/signet/src/tests.rs @@ -2,7 +2,8 @@ use crate::{self as pallet_signet, *}; use frame_support::{ assert_noop, assert_ok, parameter_types, - traits::{ConstU16, ConstU64}, + traits::{ConstU16, ConstU64, Currency as CurrencyTrait}, + PalletId, }; use frame_system as system; use sp_core::H256; @@ -18,6 +19,7 @@ frame_support::construct_runtime!( pub enum Test { System: frame_system, + Balances: pallet_balances, Signet: pallet_signet, } ); @@ -37,14 +39,14 @@ impl system::Config for Test { type Nonce = u64; type Hash = H256; type Hashing = BlakeTwo256; - type AccountId = u64; // Using u64 for simple test accounts + type AccountId = u64; type Lookup = IdentityLookup; type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; - type AccountData = (); + type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); type SystemWeightInfo = (); @@ -59,19 +61,54 @@ impl system::Config for Test { type PostTransactions = (); } +// Balances pallet configuration +parameter_types! { + pub const ExistentialDeposit: u128 = 1; +} + +impl pallet_balances::Config for Test { + type Balance = u128; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = (); + // Removed MaxHolds - not in newer version + type MaxFreezes = (); + type RuntimeHoldReason = (); + type RuntimeFreezeReason = (); +} + +// Pallet ID for account derivation +parameter_types! { + pub const SignetPalletId: PalletId = PalletId(*b"py/signt"); +} + impl pallet_signet::Config for Test { type RuntimeEvent = RuntimeEvent; - type WeightInfo = (); // Using () for tests + type Currency = Balances; + type PalletId = SignetPalletId; + type WeightInfo = (); } -// Build test environment +// Build test environment with initial balances pub fn new_test_ext() -> sp_io::TestExternalities { let t = system::GenesisConfig::::default() .build_storage() .unwrap(); let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); + ext.execute_with(|| { + System::set_block_number(1); + // Fund accounts directly in tests instead of using GenesisConfig + let _ = Balances::deposit_creating(&1, 1_000_000); // Admin has 1M tokens + let _ = Balances::deposit_creating(&2, 1_000_000); // User has 1M tokens + let _ = Balances::deposit_creating(&3, 100); // Poor user has only 100 tokens + }); ext } @@ -82,29 +119,24 @@ pub fn new_test_ext() -> sp_io::TestExternalities { #[test] fn test_initialize_works() { new_test_ext().execute_with(|| { - // Account 1 will be our admin let admin_account = 1u64; - let deposit = 1000u128; // šŸ†• Added deposit + let deposit = 1000u128; // Changed from u128 to u64 - // Before initialization, admin should be None assert_eq!(Signet::admin(), None); - // Initialize the pallet (must use root origin) assert_ok!(Signet::initialize( RuntimeOrigin::root(), admin_account, - deposit // šŸ†• Added deposit parameter + deposit )); - // After initialization, admin should be set assert_eq!(Signet::admin(), Some(admin_account)); - assert_eq!(Signet::signature_deposit(), deposit); // šŸ†• Check deposit too + assert_eq!(Signet::signature_deposit(), deposit); - // Check that the event was emitted System::assert_last_event( Event::Initialized { admin: admin_account, - signature_deposit: deposit // šŸ†• Added to event + signature_deposit: deposit }.into() ); }); @@ -113,16 +145,13 @@ fn test_initialize_works() { #[test] fn test_cannot_initialize_twice() { new_test_ext().execute_with(|| { - // First initialization should work - assert_ok!(Signet::initialize(RuntimeOrigin::root(), 1, 1000)); // šŸ†• Added deposit + assert_ok!(Signet::initialize(RuntimeOrigin::root(), 1, 1000)); - // Second initialization should fail assert_noop!( - Signet::initialize(RuntimeOrigin::root(), 2, 2000), // šŸ†• Added deposit + Signet::initialize(RuntimeOrigin::root(), 2, 2000), Error::::AlreadyInitialized ); - // Admin should still be the first one assert_eq!(Signet::admin(), Some(1)); }); } @@ -130,7 +159,6 @@ fn test_cannot_initialize_twice() { #[test] fn test_cannot_use_before_initialization() { new_test_ext().execute_with(|| { - // Try to emit event before initialization assert_noop!( Signet::emit_custom_event( RuntimeOrigin::signed(1), @@ -145,10 +173,8 @@ fn test_cannot_use_before_initialization() { #[test] fn test_emit_event_after_initialization() { new_test_ext().execute_with(|| { - // Initialize first - assert_ok!(Signet::initialize(RuntimeOrigin::root(), 1, 1000)); // šŸ†• Added deposit + assert_ok!(Signet::initialize(RuntimeOrigin::root(), 1, 1000)); - // Now we can emit events let sender = 2u64; let message = b"Hello World".to_vec(); let value = 12345u128; @@ -159,7 +185,6 @@ fn test_emit_event_after_initialization() { value )); - // Check the event System::assert_last_event( Event::DataEmitted { who: sender, @@ -173,14 +198,12 @@ fn test_emit_event_after_initialization() { #[test] fn test_only_root_can_initialize() { new_test_ext().execute_with(|| { - // Regular user (not root) tries to initialize assert_noop!( - Signet::initialize(RuntimeOrigin::signed(1), 1, 1000), // šŸ†• Added deposit + Signet::initialize(RuntimeOrigin::signed(1), 1, 1000), sp_runtime::DispatchError::BadOrigin ); - // Root can initialize - assert_ok!(Signet::initialize(RuntimeOrigin::root(), 1, 1000)); // šŸ†• Added deposit + assert_ok!(Signet::initialize(RuntimeOrigin::root(), 1, 1000)); }); } @@ -188,19 +211,16 @@ fn test_only_root_can_initialize() { fn test_initialize_sets_deposit() { new_test_ext().execute_with(|| { let admin = 1u64; - let initial_deposit = 1000u128; + let initial_deposit = 1000u128; // Changed from u128 to u64 - // Initialize with deposit assert_ok!(Signet::initialize( RuntimeOrigin::root(), admin, initial_deposit )); - // Check that deposit was set assert_eq!(Signet::signature_deposit(), initial_deposit); - // Check event includes deposit System::assert_last_event( Event::Initialized { admin, @@ -214,26 +234,22 @@ fn test_initialize_sets_deposit() { fn test_update_deposit_as_admin() { new_test_ext().execute_with(|| { let admin = 1u64; - let initial_deposit = 1000u128; - let new_deposit = 2000u128; + let initial_deposit = 1000u128; // Changed from u128 to u64 + let new_deposit = 2000u128; // Changed from u128 to u64 - // Initialize first assert_ok!(Signet::initialize( RuntimeOrigin::root(), admin, initial_deposit )); - // Admin updates deposit assert_ok!(Signet::update_deposit( RuntimeOrigin::signed(admin), new_deposit )); - // Check deposit was updated assert_eq!(Signet::signature_deposit(), new_deposit); - // Check event System::assert_last_event( Event::DepositUpdated { old_deposit: initial_deposit, @@ -249,14 +265,12 @@ fn test_non_admin_cannot_update_deposit() { let admin = 1u64; let non_admin = 2u64; - // Initialize assert_ok!(Signet::initialize( RuntimeOrigin::root(), admin, 1000 )); - // Non-admin tries to update deposit assert_noop!( Signet::update_deposit( RuntimeOrigin::signed(non_admin), @@ -265,7 +279,6 @@ fn test_non_admin_cannot_update_deposit() { Error::::Unauthorized ); - // Deposit should not change assert_eq!(Signet::signature_deposit(), 1000); }); } @@ -273,7 +286,6 @@ fn test_non_admin_cannot_update_deposit() { #[test] fn test_cannot_update_deposit_before_initialization() { new_test_ext().execute_with(|| { - // Try to update deposit before initialization assert_noop!( Signet::update_deposit( RuntimeOrigin::signed(1), @@ -282,4 +294,106 @@ fn test_cannot_update_deposit_before_initialization() { Error::::NotInitialized ); }); +} + +#[test] +fn test_withdraw_funds_as_admin() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let recipient = 2u64; + let amount = 5000u128; // Changed from u128 to u64 + + // Initialize + assert_ok!(Signet::initialize( + RuntimeOrigin::root(), + admin, + 1000 + )); + + // Fund the pallet account (simulate deposits) + let pallet_account = Signet::account_id(); + let _ = Balances::deposit_creating(&pallet_account, 10_000); + + // Check initial balances + let recipient_balance_before = Balances::free_balance(&recipient); + assert_eq!(Balances::free_balance(&pallet_account), 10_000); + + // Admin withdraws funds + assert_ok!(Signet::withdraw_funds( + RuntimeOrigin::signed(admin), + recipient, + amount + )); + + // Check balances after withdrawal + assert_eq!(Balances::free_balance(&pallet_account), 5_000); // 10k - 5k + assert_eq!(Balances::free_balance(&recipient), recipient_balance_before + amount); + + // Check event + System::assert_last_event( + Event::FundsWithdrawn { + amount, + recipient, + }.into() + ); + }); +} + +#[test] +fn test_non_admin_cannot_withdraw() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let non_admin = 2u64; + + // Initialize and fund pallet + assert_ok!(Signet::initialize(RuntimeOrigin::root(), admin, 1000)); + let pallet_account = Signet::account_id(); + let _ = Balances::deposit_creating(&pallet_account, 10_000); + + // Non-admin tries to withdraw + assert_noop!( + Signet::withdraw_funds( + RuntimeOrigin::signed(non_admin), + non_admin, + 5000 + ), + Error::::Unauthorized + ); + }); +} + +#[test] +fn test_cannot_withdraw_more_than_balance() { + new_test_ext().execute_with(|| { + let admin = 1u64; + + // Initialize and fund pallet with 10k + assert_ok!(Signet::initialize(RuntimeOrigin::root(), admin, 1000)); + let pallet_account = Signet::account_id(); + let _ = Balances::deposit_creating(&pallet_account, 10_000); + + // Try to withdraw 20k (more than balance) + assert_noop!( + Signet::withdraw_funds( + RuntimeOrigin::signed(admin), + admin, + 20_000 + ), + Error::::InsufficientFunds + ); + }); +} + +#[test] +fn test_pallet_account_id_is_deterministic() { + new_test_ext().execute_with(|| { + // The pallet account should always be the same + let account1 = Signet::account_id(); + let account2 = Signet::account_id(); + assert_eq!(account1, account2); + + // And it should be different from regular accounts + assert_ne!(account1, 1u64); + assert_ne!(account1, 2u64); + }); } \ No newline at end of file diff --git a/pallets/signet/src/weights.rs b/pallets/signet/src/weights.rs index b331b90978..f18f96d853 100644 --- a/pallets/signet/src/weights.rs +++ b/pallets/signet/src/weights.rs @@ -7,7 +7,8 @@ use sp_std::marker::PhantomData; /// Weight functions for this pallet pub trait WeightInfo { fn initialize() -> Weight; - fn update_deposit() -> Weight; // šŸ†• NEW + fn update_deposit() -> Weight; + fn withdraw_funds() -> Weight; fn emit_custom_event() -> Weight; } @@ -21,6 +22,10 @@ impl WeightInfo for () { Weight::from_parts(8_000_000, 0) } + fn withdraw_funds() -> Weight { + Weight::from_parts(35_000_000, 0) + } + fn emit_custom_event() -> Weight { Weight::from_parts(5_000_000, 0) } From 2ce0620c3d53ad66751129d0424bc77a7e859822 Mon Sep 17 00:00:00 2001 From: Osman Abdelnasir Date: Mon, 15 Sep 2025 12:34:23 +0400 Subject: [PATCH 19/32] feat: sign req --- pallets/signet/src/benchmarks.rs | 1 - pallets/signet/src/lib.rs | 100 +++++++++++++------- pallets/signet/src/tests.rs | 152 +++++++++++++++++++++++++++++++ pallets/signet/src/weights.rs | 9 +- 4 files changed, 225 insertions(+), 37 deletions(-) diff --git a/pallets/signet/src/benchmarks.rs b/pallets/signet/src/benchmarks.rs index 8c315868b0..a7978ed07a 100644 --- a/pallets/signet/src/benchmarks.rs +++ b/pallets/signet/src/benchmarks.rs @@ -27,7 +27,6 @@ benchmarks! { assert_eq!(SignatureDeposit::::get(), new_deposit); } - // šŸ†• NEW: Benchmark for withdraw_funds withdraw_funds { // Setup: Initialize and fund the pallet let admin: T::AccountId = whitelisted_caller(); diff --git a/pallets/signet/src/lib.rs b/pallets/signet/src/lib.rs index c2731cecc0..eafaa72b8c 100644 --- a/pallets/signet/src/lib.rs +++ b/pallets/signet/src/lib.rs @@ -2,15 +2,16 @@ use frame_support::{ pallet_prelude::*, - traits::{Currency, ExistenceRequirement}, - PalletId, + traits::{Currency, ExistenceRequirement}, + PalletId, }; use frame_system::pallet_prelude::*; -use sp_runtime::traits::AccountIdConversion; +use sp_runtime::traits::AccountIdConversion; use sp_std::vec::Vec; pub use pallet::*; +// Type alias for cleaner code type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; #[cfg(feature = "runtime-benchmarks")] @@ -74,21 +75,33 @@ pub mod pallet { /// Pallet has been initialized with an admin Initialized { admin: T::AccountId, - signature_deposit: BalanceOf, + signature_deposit: BalanceOf, }, /// Signature deposit amount has been updated DepositUpdated { - old_deposit: BalanceOf, + old_deposit: BalanceOf, new_deposit: BalanceOf, }, - /// Funds have been withdrawn from the pallet FundsWithdrawn { amount: BalanceOf, recipient: T::AccountId, }, + + // Event for signature requests + /// A signature has been requested + SignatureRequested { + sender: T::AccountId, + payload: [u8; 32], + key_version: u32, + deposit: BalanceOf, + path: Vec, + algo: Vec, + dest: Vec, + params: Vec, + }, } // ======================================== @@ -105,7 +118,6 @@ pub mod pallet { NotInitialized, /// Unauthorized - caller is not admin Unauthorized, - /// Insufficient funds for withdrawal InsufficientFunds, } @@ -125,19 +137,12 @@ pub mod pallet { admin: T::AccountId, signature_deposit: BalanceOf, ) -> DispatchResult { - // Only root (sudo) can initialize ensure_root(origin)?; - - // Make sure we haven't initialized already ensure!(Admin::::get().is_none(), Error::::AlreadyInitialized); - // Store the admin Admin::::put(&admin); - - // Store the signature deposit SignatureDeposit::::put(signature_deposit); - // Emit event Self::deposit_event(Event::Initialized { admin, signature_deposit, @@ -154,20 +159,12 @@ pub mod pallet { new_deposit: BalanceOf, ) -> DispatchResult { let who = ensure_signed(origin)?; - - // Check that pallet is initialized and get admin let admin = Admin::::get().ok_or(Error::::NotInitialized)?; - - // Check that caller is admin ensure!(who == admin, Error::::Unauthorized); - // Get old deposit for event let old_deposit = SignatureDeposit::::get(); - - // Update the deposit SignatureDeposit::::put(new_deposit); - // Emit event Self::deposit_event(Event::DepositUpdated { old_deposit, new_deposit, @@ -185,27 +182,20 @@ pub mod pallet { amount: BalanceOf, ) -> DispatchResult { let who = ensure_signed(origin)?; - - // Check admin authorization let admin = Admin::::get().ok_or(Error::::NotInitialized)?; ensure!(who == admin, Error::::Unauthorized); - // Get pallet account let pallet_account = Self::account_id(); - - // Check sufficient balance let pallet_balance = T::Currency::free_balance(&pallet_account); ensure!(pallet_balance >= amount, Error::::InsufficientFunds); - // Transfer funds from pallet to recipient T::Currency::transfer( &pallet_account, &recipient, amount, - ExistenceRequirement::AllowDeath, // Allow pallet account to be emptied + ExistenceRequirement::AllowDeath, )?; - // Emit event Self::deposit_event(Event::FundsWithdrawn { amount, recipient, @@ -214,8 +204,53 @@ pub mod pallet { Ok(()) } - /// Emit a custom event with data + // Sign function + /// Request a signature for a payload #[pallet::call_index(3)] + #[pallet::weight(::WeightInfo::sign())] + pub fn sign( + origin: OriginFor, + payload: [u8; 32], + key_version: u32, + path: Vec, + algo: Vec, + dest: Vec, + params: Vec, + ) -> DispatchResult { + let requester = ensure_signed(origin)?; + + // Ensure initialized + ensure!(Admin::::get().is_some(), Error::::NotInitialized); + + // Get deposit amount + let deposit = SignatureDeposit::::get(); + + // Transfer deposit from requester to pallet account + let pallet_account = Self::account_id(); + T::Currency::transfer( + &requester, + &pallet_account, + deposit, + ExistenceRequirement::KeepAlive, + )?; + + // Emit event + Self::deposit_event(Event::SignatureRequested { + sender: requester, + payload, + key_version, + deposit, + path, + algo, + dest, + params, + }); + + Ok(()) + } + + /// Emit a custom event with data + #[pallet::call_index(4)] #[pallet::weight(::WeightInfo::emit_custom_event())] pub fn emit_custom_event( origin: OriginFor, @@ -223,8 +258,6 @@ pub mod pallet { value: u128, ) -> DispatchResult { let who = ensure_signed(origin)?; - - // Check that pallet is initialized ensure!(Admin::::get().is_some(), Error::::NotInitialized); let bounded_message = BoundedVec::>::try_from(message) @@ -240,6 +273,7 @@ pub mod pallet { } } + // Helper functions impl Pallet { /// Get the pallet's account ID (where funds are stored) pub fn account_id() -> T::AccountId { diff --git a/pallets/signet/src/tests.rs b/pallets/signet/src/tests.rs index 6ab3e75931..91df20108b 100644 --- a/pallets/signet/src/tests.rs +++ b/pallets/signet/src/tests.rs @@ -396,4 +396,156 @@ fn test_pallet_account_id_is_deterministic() { assert_ne!(account1, 1u64); assert_ne!(account1, 2u64); }); +} + +#[test] +fn test_sign_request_works() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let requester = 2u64; + let deposit = 1000u128; + + // Initialize first + assert_ok!(Signet::initialize( + RuntimeOrigin::root(), + admin, + deposit + )); + + // Check requester balance before + let balance_before = Balances::free_balance(&requester); + + // Create signature request + let payload = [42u8; 32]; + let key_version = 1u32; + let path = b"path".to_vec(); + let algo = b"ecdsa".to_vec(); + let dest = b"callback_contract".to_vec(); + let params = b"{}".to_vec(); + + // Submit signature request + assert_ok!(Signet::sign( + RuntimeOrigin::signed(requester), + payload, + key_version, + path.clone(), + algo.clone(), + dest.clone(), + params.clone() + )); + + // Check that deposit was transferred to pallet + assert_eq!(Balances::free_balance(&requester), balance_before - deposit); + let pallet_account = Signet::account_id(); + assert_eq!(Balances::free_balance(&pallet_account), deposit); + + // Check event was emitted + System::assert_last_event( + Event::SignatureRequested { + sender: requester, + payload, + key_version, + deposit, + path, + algo, + dest, + params, + }.into() + ); + }); +} + +#[test] +fn test_sign_request_insufficient_balance() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let poor_user = 3u64; // Has only 100 tokens + let deposit = 1000u128; // Deposit is 1000 + + // Initialize + assert_ok!(Signet::initialize( + RuntimeOrigin::root(), + admin, + deposit + )); + + // Try to request signature without enough balance + assert_noop!( + Signet::sign( + RuntimeOrigin::signed(poor_user), + [0u8; 32], + 1, + b"path".to_vec(), + b"algo".to_vec(), + b"dest".to_vec(), + b"params".to_vec() + ), + sp_runtime::TokenError::FundsUnavailable + ); + }); +} + +#[test] +fn test_sign_request_before_initialization() { + new_test_ext().execute_with(|| { + // Try to request signature before initialization + assert_noop!( + Signet::sign( + RuntimeOrigin::signed(1), + [0u8; 32], + 1, + b"path".to_vec(), + b"algo".to_vec(), + b"dest".to_vec(), + b"params".to_vec() + ), + Error::::NotInitialized + ); + }); +} + +#[test] +fn test_multiple_sign_requests() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let requester1 = 1u64; + let requester2 = 2u64; + let deposit = 100u128; + + // Initialize with small deposit + assert_ok!(Signet::initialize( + RuntimeOrigin::root(), + admin, + deposit + )); + + let pallet_account = Signet::account_id(); + + // First request + assert_ok!(Signet::sign( + RuntimeOrigin::signed(requester1), + [1u8; 32], + 1, + b"path1".to_vec(), + b"algo".to_vec(), + b"dest".to_vec(), + b"params".to_vec() + )); + + assert_eq!(Balances::free_balance(&pallet_account), deposit); + + // Second request - funds accumulate + assert_ok!(Signet::sign( + RuntimeOrigin::signed(requester2), + [2u8; 32], + 2, + b"path2".to_vec(), + b"algo".to_vec(), + b"dest".to_vec(), + b"params".to_vec() + )); + + // Pallet should have accumulated both deposits + assert_eq!(Balances::free_balance(&pallet_account), deposit * 2); + }); } \ No newline at end of file diff --git a/pallets/signet/src/weights.rs b/pallets/signet/src/weights.rs index f18f96d853..f3091d9e99 100644 --- a/pallets/signet/src/weights.rs +++ b/pallets/signet/src/weights.rs @@ -3,12 +3,11 @@ use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use sp_std::marker::PhantomData; - -/// Weight functions for this pallet pub trait WeightInfo { fn initialize() -> Weight; fn update_deposit() -> Weight; - fn withdraw_funds() -> Weight; + fn withdraw_funds() -> Weight; + fn sign() -> Weight; fn emit_custom_event() -> Weight; } @@ -26,6 +25,10 @@ impl WeightInfo for () { Weight::from_parts(35_000_000, 0) } + fn sign() -> Weight { + Weight::from_parts(45_000_000, 0) // Higher weight for transfer + storage + } + fn emit_custom_event() -> Weight { Weight::from_parts(5_000_000, 0) } From 47fae9892be7aaf687667fdedb7039f75f6f1878 Mon Sep 17 00:00:00 2001 From: Osman Abdelnasir Date: Mon, 15 Sep 2025 12:57:24 +0400 Subject: [PATCH 20/32] feat: add respond, sign_respond, read_respond --- pallets/signet/src/lib.rs | 242 ++++++- pallets/signet/src/tests.rs | 1213 +++++++++++++++++++++------------ pallets/signet/src/weights.rs | 33 +- 3 files changed, 1040 insertions(+), 448 deletions(-) diff --git a/pallets/signet/src/lib.rs b/pallets/signet/src/lib.rs index eafaa72b8c..9567f8266f 100644 --- a/pallets/signet/src/lib.rs +++ b/pallets/signet/src/lib.rs @@ -41,9 +41,46 @@ pub mod pallet { #[pallet::constant] type PalletId: Get; + /// Maximum length for chain ID + #[pallet::constant] + type MaxChainIdLength: Get; + type WeightInfo: WeightInfo; } + // ======================================== + // Types + // ======================================== + + /// Serialization format enum + #[derive(Encode, Decode, TypeInfo, Clone, Copy, Debug, PartialEq, Eq)] + pub enum SerializationFormat { + Borsh = 0, + AbiJson = 1, + } + + /// Affine point for signatures + #[derive(Encode, Decode, TypeInfo, Clone, Debug, PartialEq, Eq)] + pub struct AffinePoint { + pub x: [u8; 32], + pub y: [u8; 32], + } + + /// Signature structure + #[derive(Encode, Decode, TypeInfo, Clone, Debug, PartialEq, Eq)] + pub struct Signature { + pub big_r: AffinePoint, + pub s: [u8; 32], + pub recovery_id: u8, + } + + /// Error response structure + #[derive(Encode, Decode, TypeInfo, Debug, Clone, PartialEq, Eq)] + pub struct ErrorResponse { + pub request_id: [u8; 32], + pub error_message: Vec, + } + // ======================================== // Storage // ======================================== @@ -58,6 +95,11 @@ pub mod pallet { #[pallet::getter(fn signature_deposit)] pub type SignatureDeposit = StorageValue<_, BalanceOf, ValueQuery>; + /// The CAIP-2 chain identifier + #[pallet::storage] + #[pallet::getter(fn chain_id)] + pub type ChainId = StorageValue<_, BoundedVec, ValueQuery>; + // ======================================== // Events // ======================================== @@ -76,6 +118,7 @@ pub mod pallet { Initialized { admin: T::AccountId, signature_deposit: BalanceOf, + chain_id: Vec, }, /// Signature deposit amount has been updated @@ -90,17 +133,56 @@ pub mod pallet { recipient: T::AccountId, }, - // Event for signature requests /// A signature has been requested SignatureRequested { sender: T::AccountId, payload: [u8; 32], key_version: u32, deposit: BalanceOf, + chain_id: Vec, + path: Vec, + algo: Vec, + dest: Vec, + params: Vec, + }, + + /// Sign-respond request event + SignRespondRequested { + sender: T::AccountId, + transaction_data: Vec, + slip44_chain_id: u32, + key_version: u32, + deposit: BalanceOf, path: Vec, algo: Vec, dest: Vec, params: Vec, + explorer_deserialization_format: u8, + explorer_deserialization_schema: Vec, + callback_serialization_format: u8, + callback_serialization_schema: Vec, + }, + + /// Signature response event + SignatureResponded { + request_id: [u8; 32], + responder: T::AccountId, + signature: Signature, + }, + + /// Signature error event + SignatureError { + request_id: [u8; 32], + responder: T::AccountId, + error: Vec, + }, + + /// Read response event + ReadResponded { + request_id: [u8; 32], + responder: T::AccountId, + serialized_output: Vec, + signature: Signature, }, } @@ -120,6 +202,12 @@ pub mod pallet { Unauthorized, /// Insufficient funds for withdrawal InsufficientFunds, + /// Invalid transaction data (empty) + InvalidTransaction, + /// Arrays must have the same length + InvalidInputLength, + /// The chain ID is too long + ChainIdTooLong, } // ======================================== @@ -129,13 +217,14 @@ pub mod pallet { #[pallet::call] impl Pallet { - /// Initialize the pallet with an admin account and initial deposit + /// Initialize the pallet with admin, deposit, and chain ID #[pallet::call_index(0)] #[pallet::weight(::WeightInfo::initialize())] pub fn initialize( origin: OriginFor, admin: T::AccountId, signature_deposit: BalanceOf, + chain_id: Vec, ) -> DispatchResult { ensure_root(origin)?; ensure!(Admin::::get().is_none(), Error::::AlreadyInitialized); @@ -143,9 +232,15 @@ pub mod pallet { Admin::::put(&admin); SignatureDeposit::::put(signature_deposit); + // Convert to BoundedVec for storage + let bounded_chain_id = BoundedVec::::try_from(chain_id.clone()) + .map_err(|_| Error::::ChainIdTooLong)?; + ChainId::::put(bounded_chain_id); + Self::deposit_event(Event::Initialized { admin, signature_deposit, + chain_id, // Event uses regular Vec }); Ok(()) @@ -204,7 +299,6 @@ pub mod pallet { Ok(()) } - // Sign function /// Request a signature for a payload #[pallet::call_index(3)] #[pallet::weight(::WeightInfo::sign())] @@ -234,12 +328,16 @@ pub mod pallet { ExistenceRequirement::KeepAlive, )?; + // Get chain ID for event (convert BoundedVec to Vec) + let chain_id = ChainId::::get().to_vec(); + // Emit event Self::deposit_event(Event::SignatureRequested { sender: requester, payload, key_version, deposit, + chain_id, path, algo, dest, @@ -249,8 +347,144 @@ pub mod pallet { Ok(()) } - /// Emit a custom event with data + /// Request a signature for a serialized transaction #[pallet::call_index(4)] + #[pallet::weight(::WeightInfo::sign_respond())] + pub fn sign_respond( + origin: OriginFor, + serialized_transaction: Vec, + slip44_chain_id: u32, + key_version: u32, + path: Vec, + algo: Vec, + dest: Vec, + params: Vec, + explorer_deserialization_format: SerializationFormat, + explorer_deserialization_schema: Vec, + callback_serialization_format: SerializationFormat, + callback_serialization_schema: Vec, + ) -> DispatchResult { + let requester = ensure_signed(origin)?; + + // Ensure initialized + ensure!(Admin::::get().is_some(), Error::::NotInitialized); + + // Validate transaction data + ensure!(!serialized_transaction.is_empty(), Error::::InvalidTransaction); + + // Get deposit amount + let deposit = SignatureDeposit::::get(); + + // Transfer deposit from requester to pallet account + let pallet_account = Self::account_id(); + T::Currency::transfer( + &requester, + &pallet_account, + deposit, + ExistenceRequirement::KeepAlive, + )?; + + // Emit event + Self::deposit_event(Event::SignRespondRequested { + sender: requester, + transaction_data: serialized_transaction, + slip44_chain_id, + key_version, + deposit, + path, + algo, + dest, + params, + explorer_deserialization_format: explorer_deserialization_format as u8, + explorer_deserialization_schema, + callback_serialization_format: callback_serialization_format as u8, + callback_serialization_schema, + }); + + Ok(()) + } + + /// Respond to signature requests (batch support) + #[pallet::call_index(5)] + #[pallet::weight(::WeightInfo::respond(request_ids.len() as u32))] + pub fn respond( + origin: OriginFor, + request_ids: Vec<[u8; 32]>, + signatures: Vec, + ) -> DispatchResult { + let responder = ensure_signed(origin)?; + + // Validate input lengths + ensure!( + request_ids.len() == signatures.len(), + Error::::InvalidInputLength + ); + + // Emit events for each response (NO PAYMENT!) + for i in 0..request_ids.len() { + Self::deposit_event(Event::SignatureResponded { + request_id: request_ids[i], + responder: responder.clone(), + signature: signatures[i].clone(), + }); + } + + Ok(()) + } + + /// Report signature generation errors (batch support) + #[pallet::call_index(6)] + #[pallet::weight(::WeightInfo::respond_error(errors.len() as u32))] + pub fn respond_error( + origin: OriginFor, + errors: Vec, + ) -> DispatchResult { + let responder = ensure_signed(origin)?; + + // Emit error events (NO PAYMENT!) + for error in errors { + Self::deposit_event(Event::SignatureError { + request_id: error.request_id, + responder: responder.clone(), + error: error.error_message, + }); + } + + Ok(()) + } + + /// Provide a read response with signature + #[pallet::call_index(7)] + #[pallet::weight(::WeightInfo::read_respond())] + pub fn read_respond( + origin: OriginFor, + request_id: [u8; 32], + serialized_output: Vec, + signature: Signature, + ) -> DispatchResult { + let responder = ensure_signed(origin)?; + + // Just emit event (NO VERIFICATION, NO PAYMENT!) + Self::deposit_event(Event::ReadResponded { + request_id, + responder, + serialized_output, + signature, + }); + + Ok(()) + } + + /// Get the current signature deposit amount + #[pallet::call_index(8)] + #[pallet::weight(::WeightInfo::get_signature_deposit())] + pub fn get_signature_deposit(_origin: OriginFor) -> DispatchResultWithPostInfo { + // This is just for RPC queries - the getter handles actual retrieval + Ok(().into()) + } + + /// Emit a custom event with data + #[pallet::call_index(9)] #[pallet::weight(::WeightInfo::emit_custom_event())] pub fn emit_custom_event( origin: OriginFor, diff --git a/pallets/signet/src/tests.rs b/pallets/signet/src/tests.rs index 91df20108b..9d96ef08c5 100644 --- a/pallets/signet/src/tests.rs +++ b/pallets/signet/src/tests.rs @@ -1,115 +1,115 @@ use crate::{self as pallet_signet, *}; +use crate::{AffinePoint, ErrorResponse, SerializationFormat, Signature}; use frame_support::{ - assert_noop, assert_ok, - parameter_types, - traits::{ConstU16, ConstU64, Currency as CurrencyTrait}, - PalletId, + assert_noop, assert_ok, parameter_types, + traits::{ConstU16, ConstU64, Currency as CurrencyTrait}, + PalletId, }; use frame_system as system; use sp_core::H256; use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, }; // Create a mock runtime for testing type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test - { - System: frame_system, - Balances: pallet_balances, - Signet: pallet_signet, - } + pub enum Test + { + System: frame_system, + Balances: pallet_balances, + Signet: pallet_signet, + } ); parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const SS58Prefix: u8 = 42; + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 42; } impl system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = ConstU16<42>; - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; - type RuntimeTask = (); - type SingleBlockMigrations = (); - type MultiBlockMigrator = (); - type PreInherents = (); - type PostInherents = (); - type PostTransactions = (); + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = ConstU16<42>; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; + type RuntimeTask = (); + type SingleBlockMigrations = (); + type MultiBlockMigrator = (); + type PreInherents = (); + type PostInherents = (); + type PostTransactions = (); } // Balances pallet configuration parameter_types! { - pub const ExistentialDeposit: u128 = 1; + pub const ExistentialDeposit: u128 = 1; } impl pallet_balances::Config for Test { - type Balance = u128; - type DustRemoval = (); - type RuntimeEvent = RuntimeEvent; - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type WeightInfo = (); - type MaxLocks = (); - type MaxReserves = (); - type ReserveIdentifier = [u8; 8]; - type FreezeIdentifier = (); - // Removed MaxHolds - not in newer version - type MaxFreezes = (); - type RuntimeHoldReason = (); - type RuntimeFreezeReason = (); + type Balance = u128; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = (); + // Removed MaxHolds - not in newer version + type MaxFreezes = (); + type RuntimeHoldReason = (); + type RuntimeFreezeReason = (); } // Pallet ID for account derivation parameter_types! { - pub const SignetPalletId: PalletId = PalletId(*b"py/signt"); + pub const SignetPalletId: PalletId = PalletId(*b"py/signt"); + pub const MaxChainIdLength: u32 = 128; } impl pallet_signet::Config for Test { - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type PalletId = SignetPalletId; - type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type PalletId = SignetPalletId; + type MaxChainIdLength = MaxChainIdLength; + type WeightInfo = (); } // Build test environment with initial balances pub fn new_test_ext() -> sp_io::TestExternalities { - let t = system::GenesisConfig::::default() - .build_storage() - .unwrap(); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| { - System::set_block_number(1); - // Fund accounts directly in tests instead of using GenesisConfig - let _ = Balances::deposit_creating(&1, 1_000_000); // Admin has 1M tokens - let _ = Balances::deposit_creating(&2, 1_000_000); // User has 1M tokens - let _ = Balances::deposit_creating(&3, 100); // Poor user has only 100 tokens - }); - ext + let t = system::GenesisConfig::::default().build_storage().unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + // Fund accounts directly in tests instead of using GenesisConfig + let _ = Balances::deposit_creating(&1, 1_000_000); // Admin has 1M tokens + let _ = Balances::deposit_creating(&2, 1_000_000); // User has 1M tokens + let _ = Balances::deposit_creating(&3, 100); // Poor user has only 100 tokens + }); + ext } // ======================================== @@ -118,434 +118,761 @@ pub fn new_test_ext() -> sp_io::TestExternalities { #[test] fn test_initialize_works() { - new_test_ext().execute_with(|| { - let admin_account = 1u64; - let deposit = 1000u128; // Changed from u128 to u64 - - assert_eq!(Signet::admin(), None); - - assert_ok!(Signet::initialize( - RuntimeOrigin::root(), - admin_account, - deposit - )); - - assert_eq!(Signet::admin(), Some(admin_account)); - assert_eq!(Signet::signature_deposit(), deposit); - - System::assert_last_event( - Event::Initialized { - admin: admin_account, - signature_deposit: deposit - }.into() - ); - }); + new_test_ext().execute_with(|| { + let admin_account = 1u64; + let deposit = 1000u128; + let chain_id = b"test-chain".to_vec(); + + assert_eq!(Signet::admin(), None); + + assert_ok!(Signet::initialize( + RuntimeOrigin::root(), + admin_account, + deposit, + chain_id.clone() + )); + + assert_eq!(Signet::admin(), Some(admin_account)); + assert_eq!(Signet::signature_deposit(), deposit); + assert_eq!(Signet::chain_id().to_vec(), chain_id.to_vec()); + + System::assert_last_event( + Event::Initialized { + admin: admin_account, + signature_deposit: deposit, + chain_id, + } + .into(), + ); + }); } #[test] fn test_cannot_initialize_twice() { - new_test_ext().execute_with(|| { - assert_ok!(Signet::initialize(RuntimeOrigin::root(), 1, 1000)); - - assert_noop!( - Signet::initialize(RuntimeOrigin::root(), 2, 2000), - Error::::AlreadyInitialized - ); - - assert_eq!(Signet::admin(), Some(1)); - }); + new_test_ext().execute_with(|| { + assert_ok!(Signet::initialize( + RuntimeOrigin::root(), + 1, + 1000, + b"test-chain".to_vec() + )); + + assert_noop!( + Signet::initialize(RuntimeOrigin::root(), 2, 2000, b"test-chain".to_vec()), + Error::::AlreadyInitialized + ); + + assert_eq!(Signet::admin(), Some(1)); + }); } #[test] fn test_cannot_use_before_initialization() { - new_test_ext().execute_with(|| { - assert_noop!( - Signet::emit_custom_event( - RuntimeOrigin::signed(1), - b"hello".to_vec(), - 123 - ), - Error::::NotInitialized - ); - }); + new_test_ext().execute_with(|| { + assert_noop!( + Signet::emit_custom_event(RuntimeOrigin::signed(1), b"hello".to_vec(), 123), + Error::::NotInitialized + ); + }); } #[test] fn test_emit_event_after_initialization() { - new_test_ext().execute_with(|| { - assert_ok!(Signet::initialize(RuntimeOrigin::root(), 1, 1000)); - - let sender = 2u64; - let message = b"Hello World".to_vec(); - let value = 12345u128; - - assert_ok!(Signet::emit_custom_event( - RuntimeOrigin::signed(sender), - message.clone(), - value - )); - - System::assert_last_event( - Event::DataEmitted { - who: sender, - message: BoundedVec::try_from(message).unwrap(), - value, - }.into() - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(Signet::initialize( + RuntimeOrigin::root(), + 1, + 1000, + b"test-chain".to_vec() + )); + + let sender = 2u64; + let message = b"Hello World".to_vec(); + let value = 12345u128; + + assert_ok!(Signet::emit_custom_event( + RuntimeOrigin::signed(sender), + message.clone(), + value + )); + + System::assert_last_event( + Event::DataEmitted { + who: sender, + message: BoundedVec::try_from(message).unwrap(), + value, + } + .into(), + ); + }); } #[test] fn test_only_root_can_initialize() { - new_test_ext().execute_with(|| { - assert_noop!( - Signet::initialize(RuntimeOrigin::signed(1), 1, 1000), - sp_runtime::DispatchError::BadOrigin - ); - - assert_ok!(Signet::initialize(RuntimeOrigin::root(), 1, 1000)); - }); + new_test_ext().execute_with(|| { + assert_noop!( + Signet::initialize(RuntimeOrigin::signed(1), 1, 1000, b"test-chain".to_vec()), + sp_runtime::DispatchError::BadOrigin + ); + + assert_ok!(Signet::initialize( + RuntimeOrigin::root(), + 1, + 1000, + b"test-chain".to_vec() + )); + }); } #[test] fn test_initialize_sets_deposit() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let initial_deposit = 1000u128; // Changed from u128 to u64 - - assert_ok!(Signet::initialize( - RuntimeOrigin::root(), - admin, - initial_deposit - )); - - assert_eq!(Signet::signature_deposit(), initial_deposit); - - System::assert_last_event( - Event::Initialized { - admin, - signature_deposit: initial_deposit, - }.into() - ); - }); + new_test_ext().execute_with(|| { + let admin = 1u64; + let initial_deposit = 1000u128; // Changed from u128 to u64 + + assert_ok!(Signet::initialize( + RuntimeOrigin::root(), + admin, + initial_deposit, + b"test-chain".to_vec() + )); + + assert_eq!(Signet::signature_deposit(), initial_deposit); + + System::assert_last_event( + Event::Initialized { + admin, + signature_deposit: initial_deposit, + chain_id: b"test-chain".to_vec(), // Add this line + } + .into(), + ); + }); } #[test] fn test_update_deposit_as_admin() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let initial_deposit = 1000u128; // Changed from u128 to u64 - let new_deposit = 2000u128; // Changed from u128 to u64 - - assert_ok!(Signet::initialize( - RuntimeOrigin::root(), - admin, - initial_deposit - )); - - assert_ok!(Signet::update_deposit( - RuntimeOrigin::signed(admin), - new_deposit - )); - - assert_eq!(Signet::signature_deposit(), new_deposit); - - System::assert_last_event( - Event::DepositUpdated { - old_deposit: initial_deposit, - new_deposit, - }.into() - ); - }); + new_test_ext().execute_with(|| { + let admin = 1u64; + let initial_deposit = 1000u128; // Changed from u128 to u64 + let new_deposit = 2000u128; // Changed from u128 to u64 + + assert_ok!(Signet::initialize( + RuntimeOrigin::root(), + admin, + initial_deposit, + b"test-chain".to_vec() + )); + + assert_ok!(Signet::update_deposit(RuntimeOrigin::signed(admin), new_deposit)); + + assert_eq!(Signet::signature_deposit(), new_deposit); + + System::assert_last_event( + Event::DepositUpdated { + old_deposit: initial_deposit, + new_deposit, + } + .into(), + ); + }); } #[test] fn test_non_admin_cannot_update_deposit() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let non_admin = 2u64; - - assert_ok!(Signet::initialize( - RuntimeOrigin::root(), - admin, - 1000 - )); - - assert_noop!( - Signet::update_deposit( - RuntimeOrigin::signed(non_admin), - 2000 - ), - Error::::Unauthorized - ); - - assert_eq!(Signet::signature_deposit(), 1000); - }); + new_test_ext().execute_with(|| { + let admin = 1u64; + let non_admin = 2u64; + + assert_ok!(Signet::initialize( + RuntimeOrigin::root(), + admin, + 1000, + b"test-chain".to_vec() + )); + + assert_noop!( + Signet::update_deposit(RuntimeOrigin::signed(non_admin), 2000), + Error::::Unauthorized + ); + + assert_eq!(Signet::signature_deposit(), 1000); + }); } #[test] fn test_cannot_update_deposit_before_initialization() { - new_test_ext().execute_with(|| { - assert_noop!( - Signet::update_deposit( - RuntimeOrigin::signed(1), - 1000 - ), - Error::::NotInitialized - ); - }); + new_test_ext().execute_with(|| { + assert_noop!( + Signet::update_deposit(RuntimeOrigin::signed(1), 1000), + Error::::NotInitialized + ); + }); } #[test] fn test_withdraw_funds_as_admin() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let recipient = 2u64; - let amount = 5000u128; // Changed from u128 to u64 - - // Initialize - assert_ok!(Signet::initialize( - RuntimeOrigin::root(), - admin, - 1000 - )); - - // Fund the pallet account (simulate deposits) - let pallet_account = Signet::account_id(); - let _ = Balances::deposit_creating(&pallet_account, 10_000); - - // Check initial balances - let recipient_balance_before = Balances::free_balance(&recipient); - assert_eq!(Balances::free_balance(&pallet_account), 10_000); - - // Admin withdraws funds - assert_ok!(Signet::withdraw_funds( - RuntimeOrigin::signed(admin), - recipient, - amount - )); - - // Check balances after withdrawal - assert_eq!(Balances::free_balance(&pallet_account), 5_000); // 10k - 5k - assert_eq!(Balances::free_balance(&recipient), recipient_balance_before + amount); - - // Check event - System::assert_last_event( - Event::FundsWithdrawn { - amount, - recipient, - }.into() - ); - }); + new_test_ext().execute_with(|| { + let admin = 1u64; + let recipient = 2u64; + let amount = 5000u128; // Changed from u128 to u64 + + // Initialize + assert_ok!(Signet::initialize( + RuntimeOrigin::root(), + admin, + 1000, + b"test-chain".to_vec() + )); + + // Fund the pallet account (simulate deposits) + let pallet_account = Signet::account_id(); + let _ = Balances::deposit_creating(&pallet_account, 10_000); + + // Check initial balances + let recipient_balance_before = Balances::free_balance(&recipient); + assert_eq!(Balances::free_balance(&pallet_account), 10_000); + + // Admin withdraws funds + assert_ok!(Signet::withdraw_funds(RuntimeOrigin::signed(admin), recipient, amount)); + + // Check balances after withdrawal + assert_eq!(Balances::free_balance(&pallet_account), 5_000); // 10k - 5k + assert_eq!(Balances::free_balance(&recipient), recipient_balance_before + amount); + + // Check event + System::assert_last_event(Event::FundsWithdrawn { amount, recipient }.into()); + }); } #[test] fn test_non_admin_cannot_withdraw() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let non_admin = 2u64; - - // Initialize and fund pallet - assert_ok!(Signet::initialize(RuntimeOrigin::root(), admin, 1000)); - let pallet_account = Signet::account_id(); - let _ = Balances::deposit_creating(&pallet_account, 10_000); - - // Non-admin tries to withdraw - assert_noop!( - Signet::withdraw_funds( - RuntimeOrigin::signed(non_admin), - non_admin, - 5000 - ), - Error::::Unauthorized - ); - }); + new_test_ext().execute_with(|| { + let admin = 1u64; + let non_admin = 2u64; + + // Initialize and fund pallet + assert_ok!(Signet::initialize( + RuntimeOrigin::root(), + admin, + 1000, + b"test-chain".to_vec() + )); + let pallet_account = Signet::account_id(); + let _ = Balances::deposit_creating(&pallet_account, 10_000); + + // Non-admin tries to withdraw + assert_noop!( + Signet::withdraw_funds(RuntimeOrigin::signed(non_admin), non_admin, 5000), + Error::::Unauthorized + ); + }); } #[test] fn test_cannot_withdraw_more_than_balance() { - new_test_ext().execute_with(|| { - let admin = 1u64; - - // Initialize and fund pallet with 10k - assert_ok!(Signet::initialize(RuntimeOrigin::root(), admin, 1000)); - let pallet_account = Signet::account_id(); - let _ = Balances::deposit_creating(&pallet_account, 10_000); - - // Try to withdraw 20k (more than balance) - assert_noop!( - Signet::withdraw_funds( - RuntimeOrigin::signed(admin), - admin, - 20_000 - ), - Error::::InsufficientFunds - ); - }); + new_test_ext().execute_with(|| { + let admin = 1u64; + + // Initialize and fund pallet with 10k + assert_ok!(Signet::initialize( + RuntimeOrigin::root(), + admin, + 1000, + b"test-chain".to_vec() + )); + let pallet_account = Signet::account_id(); + let _ = Balances::deposit_creating(&pallet_account, 10_000); + + // Try to withdraw 20k (more than balance) + assert_noop!( + Signet::withdraw_funds(RuntimeOrigin::signed(admin), admin, 20_000), + Error::::InsufficientFunds + ); + }); } #[test] fn test_pallet_account_id_is_deterministic() { - new_test_ext().execute_with(|| { - // The pallet account should always be the same - let account1 = Signet::account_id(); - let account2 = Signet::account_id(); - assert_eq!(account1, account2); - - // And it should be different from regular accounts - assert_ne!(account1, 1u64); - assert_ne!(account1, 2u64); - }); + new_test_ext().execute_with(|| { + // The pallet account should always be the same + let account1 = Signet::account_id(); + let account2 = Signet::account_id(); + assert_eq!(account1, account2); + + // And it should be different from regular accounts + assert_ne!(account1, 1u64); + assert_ne!(account1, 2u64); + }); } #[test] fn test_sign_request_works() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let requester = 2u64; - let deposit = 1000u128; - - // Initialize first - assert_ok!(Signet::initialize( - RuntimeOrigin::root(), - admin, - deposit - )); - - // Check requester balance before - let balance_before = Balances::free_balance(&requester); - - // Create signature request - let payload = [42u8; 32]; - let key_version = 1u32; - let path = b"path".to_vec(); - let algo = b"ecdsa".to_vec(); - let dest = b"callback_contract".to_vec(); - let params = b"{}".to_vec(); - - // Submit signature request - assert_ok!(Signet::sign( - RuntimeOrigin::signed(requester), - payload, - key_version, - path.clone(), - algo.clone(), - dest.clone(), - params.clone() - )); - - // Check that deposit was transferred to pallet - assert_eq!(Balances::free_balance(&requester), balance_before - deposit); - let pallet_account = Signet::account_id(); - assert_eq!(Balances::free_balance(&pallet_account), deposit); - - // Check event was emitted - System::assert_last_event( - Event::SignatureRequested { - sender: requester, - payload, - key_version, - deposit, - path, - algo, - dest, - params, - }.into() - ); - }); + new_test_ext().execute_with(|| { + let admin = 1u64; + let requester = 2u64; + let deposit = 1000u128; + + // Initialize first + assert_ok!(Signet::initialize( + RuntimeOrigin::root(), + admin, + deposit, + b"test-chain".to_vec() + )); + + // Check requester balance before + let balance_before = Balances::free_balance(&requester); + + // Create signature request + let payload = [42u8; 32]; + let key_version = 1u32; + let path = b"path".to_vec(); + let algo = b"ecdsa".to_vec(); + let dest = b"callback_contract".to_vec(); + let params = b"{}".to_vec(); + + // Submit signature request + assert_ok!(Signet::sign( + RuntimeOrigin::signed(requester), + payload, + key_version, + path.clone(), + algo.clone(), + dest.clone(), + params.clone() + )); + + // Check that deposit was transferred to pallet + assert_eq!(Balances::free_balance(&requester), balance_before - deposit); + let pallet_account = Signet::account_id(); + assert_eq!(Balances::free_balance(&pallet_account), deposit); + + // Check event was emitted + System::assert_last_event( + Event::SignatureRequested { + sender: requester, + payload, + key_version, + deposit, + chain_id: b"test-chain".to_vec(), // Add this line + path, + algo, + dest, + params, + } + .into(), + ); + }); } #[test] fn test_sign_request_insufficient_balance() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let poor_user = 3u64; // Has only 100 tokens - let deposit = 1000u128; // Deposit is 1000 - - // Initialize - assert_ok!(Signet::initialize( - RuntimeOrigin::root(), - admin, - deposit - )); - - // Try to request signature without enough balance - assert_noop!( - Signet::sign( - RuntimeOrigin::signed(poor_user), - [0u8; 32], - 1, - b"path".to_vec(), - b"algo".to_vec(), - b"dest".to_vec(), - b"params".to_vec() - ), - sp_runtime::TokenError::FundsUnavailable - ); - }); + new_test_ext().execute_with(|| { + let admin = 1u64; + let poor_user = 3u64; // Has only 100 tokens + let deposit = 1000u128; // Deposit is 1000 + + // Initialize + assert_ok!(Signet::initialize( + RuntimeOrigin::root(), + admin, + deposit, + b"test-chain".to_vec() + )); + + // Try to request signature without enough balance + assert_noop!( + Signet::sign( + RuntimeOrigin::signed(poor_user), + [0u8; 32], + 1, + b"path".to_vec(), + b"algo".to_vec(), + b"dest".to_vec(), + b"params".to_vec() + ), + sp_runtime::TokenError::FundsUnavailable + ); + }); } #[test] fn test_sign_request_before_initialization() { - new_test_ext().execute_with(|| { - // Try to request signature before initialization - assert_noop!( - Signet::sign( - RuntimeOrigin::signed(1), - [0u8; 32], - 1, - b"path".to_vec(), - b"algo".to_vec(), - b"dest".to_vec(), - b"params".to_vec() - ), - Error::::NotInitialized - ); - }); + new_test_ext().execute_with(|| { + // Try to request signature before initialization + assert_noop!( + Signet::sign( + RuntimeOrigin::signed(1), + [0u8; 32], + 1, + b"path".to_vec(), + b"algo".to_vec(), + b"dest".to_vec(), + b"params".to_vec() + ), + Error::::NotInitialized + ); + }); } #[test] fn test_multiple_sign_requests() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let requester1 = 1u64; - let requester2 = 2u64; - let deposit = 100u128; - - // Initialize with small deposit - assert_ok!(Signet::initialize( - RuntimeOrigin::root(), - admin, - deposit - )); - - let pallet_account = Signet::account_id(); - - // First request - assert_ok!(Signet::sign( - RuntimeOrigin::signed(requester1), - [1u8; 32], - 1, - b"path1".to_vec(), - b"algo".to_vec(), - b"dest".to_vec(), - b"params".to_vec() - )); - - assert_eq!(Balances::free_balance(&pallet_account), deposit); - - // Second request - funds accumulate - assert_ok!(Signet::sign( - RuntimeOrigin::signed(requester2), - [2u8; 32], - 2, - b"path2".to_vec(), - b"algo".to_vec(), - b"dest".to_vec(), - b"params".to_vec() - )); - - // Pallet should have accumulated both deposits - assert_eq!(Balances::free_balance(&pallet_account), deposit * 2); - }); -} \ No newline at end of file + new_test_ext().execute_with(|| { + let admin = 1u64; + let requester1 = 1u64; + let requester2 = 2u64; + let deposit = 100u128; + + // Initialize with small deposit + assert_ok!(Signet::initialize( + RuntimeOrigin::root(), + admin, + deposit, + b"test-chain".to_vec() + )); + + let pallet_account = Signet::account_id(); + + // First request + assert_ok!(Signet::sign( + RuntimeOrigin::signed(requester1), + [1u8; 32], + 1, + b"path1".to_vec(), + b"algo".to_vec(), + b"dest".to_vec(), + b"params".to_vec() + )); + + assert_eq!(Balances::free_balance(&pallet_account), deposit); + + // Second request - funds accumulate + assert_ok!(Signet::sign( + RuntimeOrigin::signed(requester2), + [2u8; 32], + 2, + b"path2".to_vec(), + b"algo".to_vec(), + b"dest".to_vec(), + b"params".to_vec() + )); + + // Pallet should have accumulated both deposits + assert_eq!(Balances::free_balance(&pallet_account), deposit * 2); + }); +} + +fn create_test_signature() -> Signature { + Signature { + big_r: AffinePoint { + x: [1u8; 32], + y: [2u8; 32], + }, + s: [3u8; 32], + recovery_id: 0, + } +} + +#[test] +fn test_sign_respond_works() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let requester = 2u64; + let deposit = 100u128; + + // Initialize + assert_ok!(Signet::initialize( + RuntimeOrigin::root(), + admin, + deposit, + b"test-chain".to_vec() + )); + + // Create transaction data + let tx_data = b"mock_transaction_data".to_vec(); + let slip44_chain_id = 60u32; // Ethereum + + // Check balance before + let balance_before = Balances::free_balance(&requester); + + // Submit sign-respond request + assert_ok!(Signet::sign_respond( + RuntimeOrigin::signed(requester), + tx_data.clone(), + slip44_chain_id, + 1, // key_version + b"path".to_vec(), + b"ecdsa".to_vec(), + b"callback".to_vec(), + b"{}".to_vec(), + SerializationFormat::AbiJson, + b"schema1".to_vec(), + SerializationFormat::Borsh, + b"schema2".to_vec() + )); + + // Check deposit was taken + assert_eq!(Balances::free_balance(&requester), balance_before - deposit); + + // Check event + let events = System::events(); + let event_found = events + .iter() + .any(|e| matches!(&e.event, RuntimeEvent::Signet(Event::SignRespondRequested { .. }))); + assert!(event_found); + }); +} + +#[test] +fn test_sign_respond_empty_transaction_fails() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let requester = 2u64; + + // Initialize + assert_ok!(Signet::initialize( + RuntimeOrigin::root(), + admin, + 100, + b"test-chain".to_vec() + )); + + // Try with empty transaction + assert_noop!( + Signet::sign_respond( + RuntimeOrigin::signed(requester), + vec![], // Empty transaction + 60, + 1, + b"path".to_vec(), + b"algo".to_vec(), + b"dest".to_vec(), + b"params".to_vec(), + SerializationFormat::Borsh, + vec![], + SerializationFormat::Borsh, + vec![] + ), + Error::::InvalidTransaction + ); + }); +} + +#[test] +fn test_respond_single() { + new_test_ext().execute_with(|| { + let responder = 1u64; + let request_id = [99u8; 32]; + let signature = create_test_signature(); + + // No initialization needed for respond + assert_ok!(Signet::respond( + RuntimeOrigin::signed(responder), + vec![request_id], + vec![signature.clone()] + )); + + // Check event + System::assert_last_event( + Event::SignatureResponded { + request_id, + responder, + signature, + } + .into(), + ); + }); +} + +#[test] +fn test_respond_batch() { + new_test_ext().execute_with(|| { + let responder = 1u64; + let request_ids = vec![[1u8; 32], [2u8; 32], [3u8; 32]]; + let signatures = vec![ + create_test_signature(), + create_test_signature(), + create_test_signature(), + ]; + + // Batch respond + assert_ok!(Signet::respond( + RuntimeOrigin::signed(responder), + request_ids.clone(), + signatures.clone() + )); + + // Check that 3 events were emitted + let events = System::events(); + let response_events = events + .iter() + .filter(|e| matches!(&e.event, RuntimeEvent::Signet(Event::SignatureResponded { .. }))) + .count(); + assert_eq!(response_events, 3); + }); +} + +#[test] +fn test_respond_mismatched_arrays_fails() { + new_test_ext().execute_with(|| { + let responder = 1u64; + + // 2 request IDs but 3 signatures + assert_noop!( + Signet::respond( + RuntimeOrigin::signed(responder), + vec![[1u8; 32], [2u8; 32]], + vec![ + create_test_signature(), + create_test_signature(), + create_test_signature(), + ] + ), + Error::::InvalidInputLength + ); + }); +} + +#[test] +fn test_respond_error_single() { + new_test_ext().execute_with(|| { + let responder = 1u64; + let error_response = ErrorResponse { + request_id: [99u8; 32], + error_message: b"Signature generation failed".to_vec(), + }; + + assert_ok!(Signet::respond_error( + RuntimeOrigin::signed(responder), + vec![error_response] + )); + + // Check event + System::assert_last_event( + Event::SignatureError { + request_id: [99u8; 32], + responder, + error: b"Signature generation failed".to_vec(), + } + .into(), + ); + }); +} + +#[test] +fn test_respond_error_batch() { + new_test_ext().execute_with(|| { + let responder = 1u64; + let errors = vec![ + ErrorResponse { + request_id: [1u8; 32], + error_message: b"Error 1".to_vec(), + }, + ErrorResponse { + request_id: [2u8; 32], + error_message: b"Error 2".to_vec(), + }, + ]; + + assert_ok!(Signet::respond_error(RuntimeOrigin::signed(responder), errors)); + + // Check that 2 error events were emitted + let events = System::events(); + let error_events = events + .iter() + .filter(|e| matches!(&e.event, RuntimeEvent::Signet(Event::SignatureError { .. }))) + .count(); + assert_eq!(error_events, 2); + }); +} + +#[test] +fn test_read_respond() { + new_test_ext().execute_with(|| { + let responder = 1u64; + let request_id = [99u8; 32]; + let output = b"read_output_data".to_vec(); + let signature = create_test_signature(); + + assert_ok!(Signet::read_respond( + RuntimeOrigin::signed(responder), + request_id, + output.clone(), + signature.clone() + )); + + // Check event + System::assert_last_event( + Event::ReadResponded { + request_id, + responder, + serialized_output: output, + signature, + } + .into(), + ); + }); +} + +#[test] +fn test_get_signature_deposit() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let deposit = 5000u128; + + // Initialize with a specific deposit + assert_ok!(Signet::initialize( + RuntimeOrigin::root(), + admin, + deposit, + b"test-chain".to_vec() + )); + + // The getter should return the deposit + assert_eq!(Signet::signature_deposit(), deposit); + + // The extrinsic should succeed (it's mainly for RPC) + assert_ok!(Signet::get_signature_deposit(RuntimeOrigin::signed(1))); + }); +} + +// Update test for sign to include chain_id in event +#[test] +fn test_sign_includes_chain_id() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let requester = 2u64; + let chain_id = b"hydradx:polkadot:0".to_vec(); + + // Initialize with specific chain_id + assert_ok!(Signet::initialize(RuntimeOrigin::root(), admin, 100, chain_id.clone())); + + // Submit signature request + assert_ok!(Signet::sign( + RuntimeOrigin::signed(requester), + [42u8; 32], + 1, + b"path".to_vec(), + b"algo".to_vec(), + b"dest".to_vec(), + b"params".to_vec() + )); + + // Check that event includes chain_id + let events = System::events(); + let sign_event = events.iter().find_map(|e| { + if let RuntimeEvent::Signet(Event::SignatureRequested { + chain_id: event_chain_id, + .. + }) = &e.event + { + Some(event_chain_id.clone()) + } else { + None + } + }); + + assert_eq!(sign_event, Some(chain_id)); + }); +} diff --git a/pallets/signet/src/weights.rs b/pallets/signet/src/weights.rs index f3091d9e99..d788e9d080 100644 --- a/pallets/signet/src/weights.rs +++ b/pallets/signet/src/weights.rs @@ -3,11 +3,18 @@ use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use sp_std::marker::PhantomData; + +/// Weight functions for this pallet pub trait WeightInfo { fn initialize() -> Weight; fn update_deposit() -> Weight; fn withdraw_funds() -> Weight; fn sign() -> Weight; + fn sign_respond() -> Weight; + fn respond(r: u32) -> Weight; // r = number of responses + fn respond_error(e: u32) -> Weight; // e = number of errors + fn read_respond() -> Weight; + fn get_signature_deposit() -> Weight; fn emit_custom_event() -> Weight; } @@ -26,7 +33,31 @@ impl WeightInfo for () { } fn sign() -> Weight { - Weight::from_parts(45_000_000, 0) // Higher weight for transfer + storage + Weight::from_parts(45_000_000, 0) + } + + fn sign_respond() -> Weight { + Weight::from_parts(50_000_000, 0) // Slightly more than sign + } + + fn respond(r: u32) -> Weight { + // Base weight + per-response weight + Weight::from_parts(10_000_000, 0) + .saturating_add(Weight::from_parts(1_000_000, 0).saturating_mul(r.into())) + } + + fn respond_error(e: u32) -> Weight { + // Base weight + per-error weight + Weight::from_parts(10_000_000, 0) + .saturating_add(Weight::from_parts(500_000, 0).saturating_mul(e.into())) + } + + fn read_respond() -> Weight { + Weight::from_parts(10_000_000, 0) + } + + fn get_signature_deposit() -> Weight { + Weight::from_parts(5_000_000, 0) } fn emit_custom_event() -> Weight { From e49635a493275b2308790acbae15dbc6291c6b3a Mon Sep 17 00:00:00 2001 From: Osman Abdelnasir Date: Wed, 17 Sep 2025 15:06:05 +0400 Subject: [PATCH 21/32] fix: remove emit event --- Cargo.lock | 1 + pallets/signet/src/benchmarks.rs | 13 ----- pallets/signet/src/lib.rs | 31 ------------ pallets/signet/src/tests.rs | 51 ++++++-------------- pallets/signet/src/weights.rs | 5 -- runtime/hydradx/src/assets.rs | 12 ++++- runtime/hydradx/src/weights/pallet_signet.rs | 39 +++++++++++++-- 7 files changed, 61 insertions(+), 91 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5bba434299..5d06f35a23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10540,6 +10540,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "pallet-balances", "parity-scale-codec", "scale-info", "sp-core", diff --git a/pallets/signet/src/benchmarks.rs b/pallets/signet/src/benchmarks.rs index a7978ed07a..4d8276c6e6 100644 --- a/pallets/signet/src/benchmarks.rs +++ b/pallets/signet/src/benchmarks.rs @@ -45,18 +45,5 @@ benchmarks! { assert!(T::Currency::free_balance(&recipient) >= withdraw_amount); } - emit_custom_event { - // Setup: Initialize first - let admin: T::AccountId = whitelisted_caller(); - let _ = Pallet::::initialize(RawOrigin::Root.into(), admin, T::Currency::minimum_balance()); - - let caller: T::AccountId = whitelisted_caller(); - let message = vec![1u8; 100]; - let value = 12345u128; - }: _(RawOrigin::Signed(caller.clone()), message, value) - verify { - // Event was emitted - } - impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test); } \ No newline at end of file diff --git a/pallets/signet/src/lib.rs b/pallets/signet/src/lib.rs index 9567f8266f..3f7515619f 100644 --- a/pallets/signet/src/lib.rs +++ b/pallets/signet/src/lib.rs @@ -107,12 +107,6 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// Custom data was emitted - DataEmitted { - who: T::AccountId, - message: BoundedVec>, - value: u128, - }, /// Pallet has been initialized with an admin Initialized { @@ -192,8 +186,6 @@ pub mod pallet { #[pallet::error] pub enum Error { - /// The provided message exceeds the maximum length of 256 bytes - MessageTooLong, /// The pallet has already been initialized AlreadyInitialized, /// The pallet has not been initialized yet @@ -482,29 +474,6 @@ pub mod pallet { // This is just for RPC queries - the getter handles actual retrieval Ok(().into()) } - - /// Emit a custom event with data - #[pallet::call_index(9)] - #[pallet::weight(::WeightInfo::emit_custom_event())] - pub fn emit_custom_event( - origin: OriginFor, - message: Vec, - value: u128, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - ensure!(Admin::::get().is_some(), Error::::NotInitialized); - - let bounded_message = BoundedVec::>::try_from(message) - .map_err(|_| Error::::MessageTooLong)?; - - Self::deposit_event(Event::DataEmitted { - who, - message: bounded_message, - value, - }); - - Ok(()) - } } // Helper functions diff --git a/pallets/signet/src/tests.rs b/pallets/signet/src/tests.rs index 9d96ef08c5..c5d12e8778 100644 --- a/pallets/signet/src/tests.rs +++ b/pallets/signet/src/tests.rs @@ -168,43 +168,20 @@ fn test_cannot_initialize_twice() { #[test] fn test_cannot_use_before_initialization() { - new_test_ext().execute_with(|| { - assert_noop!( - Signet::emit_custom_event(RuntimeOrigin::signed(1), b"hello".to_vec(), 123), - Error::::NotInitialized - ); - }); -} - -#[test] -fn test_emit_event_after_initialization() { - new_test_ext().execute_with(|| { - assert_ok!(Signet::initialize( - RuntimeOrigin::root(), - 1, - 1000, - b"test-chain".to_vec() - )); - - let sender = 2u64; - let message = b"Hello World".to_vec(); - let value = 12345u128; - - assert_ok!(Signet::emit_custom_event( - RuntimeOrigin::signed(sender), - message.clone(), - value - )); - - System::assert_last_event( - Event::DataEmitted { - who: sender, - message: BoundedVec::try_from(message).unwrap(), - value, - } - .into(), - ); - }); + new_test_ext().execute_with(|| { + assert_noop!( + Signet::sign( + RuntimeOrigin::signed(1), + [0u8; 32], + 1, + b"path".to_vec(), + b"algo".to_vec(), + b"dest".to_vec(), + b"params".to_vec() + ), + Error::::NotInitialized + ); + }); } #[test] diff --git a/pallets/signet/src/weights.rs b/pallets/signet/src/weights.rs index d788e9d080..3f0847cf15 100644 --- a/pallets/signet/src/weights.rs +++ b/pallets/signet/src/weights.rs @@ -15,7 +15,6 @@ pub trait WeightInfo { fn respond_error(e: u32) -> Weight; // e = number of errors fn read_respond() -> Weight; fn get_signature_deposit() -> Weight; - fn emit_custom_event() -> Weight; } /// For tests - just returns simple weights @@ -59,8 +58,4 @@ impl WeightInfo for () { fn get_signature_deposit() -> Weight { Weight::from_parts(5_000_000, 0) } - - fn emit_custom_event() -> Weight { - Weight::from_parts(5_000_000, 0) - } } \ No newline at end of file diff --git a/runtime/hydradx/src/assets.rs b/runtime/hydradx/src/assets.rs index 0f73fa24c1..de37b7535d 100644 --- a/runtime/hydradx/src/assets.rs +++ b/runtime/hydradx/src/assets.rs @@ -1840,9 +1840,17 @@ impl pallet_build_evm_tx::Config for Runtime { type MaxDataLength = MaxEvmDataLength; } +parameter_types! { + pub const SignetPalletId: PalletId = PalletId(*b"py/signt"); + pub const MaxChainIdLength: u32 = 128; +} + impl pallet_signet::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type WeightInfo = weights::pallet_signet::HydraWeight; + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type PalletId = SignetPalletId; + type MaxChainIdLength = MaxChainIdLength; + type WeightInfo = weights::pallet_signet::HydraWeight; } pub struct ConvertViaOmnipool(PhantomData); diff --git a/runtime/hydradx/src/weights/pallet_signet.rs b/runtime/hydradx/src/weights/pallet_signet.rs index 0618028e69..30994941b6 100644 --- a/runtime/hydradx/src/weights/pallet_signet.rs +++ b/runtime/hydradx/src/weights/pallet_signet.rs @@ -7,8 +7,41 @@ pub use pallet_signet::weights::*; pub struct HydraWeight(PhantomData); impl pallet_signet::weights::WeightInfo for HydraWeight { - fn emit_custom_event() -> Weight { - Weight::from_parts(2_500_000, 0) - .saturating_add(T::DbWeight::get().writes(1_u64)) + fn initialize() -> Weight { + Weight::from_parts(10_000_000, 0) + } + + fn update_deposit() -> Weight { + Weight::from_parts(8_000_000, 0) + } + + fn withdraw_funds() -> Weight { + Weight::from_parts(35_000_000, 0) + } + + fn sign() -> Weight { + Weight::from_parts(45_000_000, 0) + } + + fn sign_respond() -> Weight { + Weight::from_parts(50_000_000, 0) + } + + fn respond(r: u32) -> Weight { + Weight::from_parts(10_000_000, 0) + .saturating_add(Weight::from_parts(1_000_000, 0).saturating_mul(r.into())) + } + + fn respond_error(e: u32) -> Weight { + Weight::from_parts(10_000_000, 0) + .saturating_add(Weight::from_parts(500_000, 0).saturating_mul(e.into())) + } + + fn read_respond() -> Weight { + Weight::from_parts(10_000_000, 0) + } + + fn get_signature_deposit() -> Weight { + Weight::from_parts(5_000_000, 0) } } \ No newline at end of file From 01cdc1d827ed02f7ff399e36b9d6bdaa770aa7cf Mon Sep 17 00:00:00 2001 From: Osman Abdelnasir Date: Fri, 19 Sep 2025 20:23:33 +0400 Subject: [PATCH 22/32] feat: add test client --- pallets/signet/src/lib.rs | 886 +++-- pallets/signet/src/tests.rs | 805 ++-- signet-ts-client-scripts/README.md | 109 + signet-ts-client-scripts/key-derivation.ts | 28 + signet-ts-client-scripts/package.json | 35 + signet-ts-client-scripts/signet-client.ts | 219 ++ signet-ts-client-scripts/signet.test.ts | 126 + .../transaction-builder.ts | 26 + signet-ts-client-scripts/tsconfig.json | 15 + signet-ts-client-scripts/yarn.lock | 3250 +++++++++++++++++ 10 files changed, 4616 insertions(+), 883 deletions(-) create mode 100644 signet-ts-client-scripts/README.md create mode 100644 signet-ts-client-scripts/key-derivation.ts create mode 100644 signet-ts-client-scripts/package.json create mode 100644 signet-ts-client-scripts/signet-client.ts create mode 100644 signet-ts-client-scripts/signet.test.ts create mode 100644 signet-ts-client-scripts/transaction-builder.ts create mode 100644 signet-ts-client-scripts/tsconfig.json create mode 100644 signet-ts-client-scripts/yarn.lock diff --git a/pallets/signet/src/lib.rs b/pallets/signet/src/lib.rs index 3f7515619f..cbc174ad3b 100644 --- a/pallets/signet/src/lib.rs +++ b/pallets/signet/src/lib.rs @@ -1,9 +1,9 @@ #![cfg_attr(not(feature = "std"), no_std)] use frame_support::{ - pallet_prelude::*, - traits::{Currency, ExistenceRequirement}, - PalletId, + pallet_prelude::*, + traits::{Currency, ExistenceRequirement}, + PalletId, }; use frame_system::pallet_prelude::*; use sp_runtime::traits::AccountIdConversion; @@ -25,462 +25,424 @@ mod tests; #[frame_support::pallet] pub mod pallet { - use super::*; - - #[pallet::pallet] - pub struct Pallet(_); - - #[pallet::config] - pub trait Config: frame_system::Config { - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - - /// Currency for handling deposits and fees - type Currency: Currency; - - /// The pallet's unique ID for deriving its account - #[pallet::constant] - type PalletId: Get; - - /// Maximum length for chain ID - #[pallet::constant] - type MaxChainIdLength: Get; - - type WeightInfo: WeightInfo; - } - - // ======================================== - // Types - // ======================================== - - /// Serialization format enum - #[derive(Encode, Decode, TypeInfo, Clone, Copy, Debug, PartialEq, Eq)] - pub enum SerializationFormat { - Borsh = 0, - AbiJson = 1, - } - - /// Affine point for signatures - #[derive(Encode, Decode, TypeInfo, Clone, Debug, PartialEq, Eq)] - pub struct AffinePoint { - pub x: [u8; 32], - pub y: [u8; 32], - } - - /// Signature structure - #[derive(Encode, Decode, TypeInfo, Clone, Debug, PartialEq, Eq)] - pub struct Signature { - pub big_r: AffinePoint, - pub s: [u8; 32], - pub recovery_id: u8, - } - - /// Error response structure - #[derive(Encode, Decode, TypeInfo, Debug, Clone, PartialEq, Eq)] - pub struct ErrorResponse { - pub request_id: [u8; 32], - pub error_message: Vec, - } - - // ======================================== - // Storage - // ======================================== - - /// The admin account that controls this pallet - #[pallet::storage] - #[pallet::getter(fn admin)] - pub type Admin = StorageValue<_, T::AccountId>; - - /// The amount required as deposit for signature requests - #[pallet::storage] - #[pallet::getter(fn signature_deposit)] - pub type SignatureDeposit = StorageValue<_, BalanceOf, ValueQuery>; - - /// The CAIP-2 chain identifier - #[pallet::storage] - #[pallet::getter(fn chain_id)] - pub type ChainId = StorageValue<_, BoundedVec, ValueQuery>; - - // ======================================== - // Events - // ======================================== - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - - /// Pallet has been initialized with an admin - Initialized { - admin: T::AccountId, - signature_deposit: BalanceOf, - chain_id: Vec, - }, - - /// Signature deposit amount has been updated - DepositUpdated { - old_deposit: BalanceOf, - new_deposit: BalanceOf, - }, - - /// Funds have been withdrawn from the pallet - FundsWithdrawn { - amount: BalanceOf, - recipient: T::AccountId, - }, - - /// A signature has been requested - SignatureRequested { - sender: T::AccountId, - payload: [u8; 32], - key_version: u32, - deposit: BalanceOf, - chain_id: Vec, - path: Vec, - algo: Vec, - dest: Vec, - params: Vec, - }, - - /// Sign-respond request event - SignRespondRequested { - sender: T::AccountId, - transaction_data: Vec, - slip44_chain_id: u32, - key_version: u32, - deposit: BalanceOf, - path: Vec, - algo: Vec, - dest: Vec, - params: Vec, - explorer_deserialization_format: u8, - explorer_deserialization_schema: Vec, - callback_serialization_format: u8, - callback_serialization_schema: Vec, - }, - - /// Signature response event - SignatureResponded { - request_id: [u8; 32], - responder: T::AccountId, - signature: Signature, - }, - - /// Signature error event - SignatureError { - request_id: [u8; 32], - responder: T::AccountId, - error: Vec, - }, - - /// Read response event - ReadResponded { - request_id: [u8; 32], - responder: T::AccountId, - serialized_output: Vec, - signature: Signature, - }, - } - - // ======================================== - // Errors - // ======================================== - - #[pallet::error] - pub enum Error { - /// The pallet has already been initialized - AlreadyInitialized, - /// The pallet has not been initialized yet - NotInitialized, - /// Unauthorized - caller is not admin - Unauthorized, - /// Insufficient funds for withdrawal - InsufficientFunds, - /// Invalid transaction data (empty) - InvalidTransaction, - /// Arrays must have the same length - InvalidInputLength, - /// The chain ID is too long - ChainIdTooLong, - } - - // ======================================== - // Extrinsics - // ======================================== - - #[pallet::call] - impl Pallet { - - /// Initialize the pallet with admin, deposit, and chain ID - #[pallet::call_index(0)] - #[pallet::weight(::WeightInfo::initialize())] - pub fn initialize( - origin: OriginFor, - admin: T::AccountId, - signature_deposit: BalanceOf, - chain_id: Vec, - ) -> DispatchResult { - ensure_root(origin)?; - ensure!(Admin::::get().is_none(), Error::::AlreadyInitialized); - - Admin::::put(&admin); - SignatureDeposit::::put(signature_deposit); - - // Convert to BoundedVec for storage - let bounded_chain_id = BoundedVec::::try_from(chain_id.clone()) - .map_err(|_| Error::::ChainIdTooLong)?; - ChainId::::put(bounded_chain_id); - - Self::deposit_event(Event::Initialized { - admin, - signature_deposit, - chain_id, // Event uses regular Vec - }); - - Ok(()) - } - - /// Update the signature deposit amount (admin only) - #[pallet::call_index(1)] - #[pallet::weight(::WeightInfo::update_deposit())] - pub fn update_deposit( - origin: OriginFor, - new_deposit: BalanceOf, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - let admin = Admin::::get().ok_or(Error::::NotInitialized)?; - ensure!(who == admin, Error::::Unauthorized); - - let old_deposit = SignatureDeposit::::get(); - SignatureDeposit::::put(new_deposit); - - Self::deposit_event(Event::DepositUpdated { - old_deposit, - new_deposit, - }); - - Ok(()) - } - - /// Withdraw funds from the pallet account (admin only) - #[pallet::call_index(2)] - #[pallet::weight(::WeightInfo::withdraw_funds())] - pub fn withdraw_funds( - origin: OriginFor, - recipient: T::AccountId, - amount: BalanceOf, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - let admin = Admin::::get().ok_or(Error::::NotInitialized)?; - ensure!(who == admin, Error::::Unauthorized); - - let pallet_account = Self::account_id(); - let pallet_balance = T::Currency::free_balance(&pallet_account); - ensure!(pallet_balance >= amount, Error::::InsufficientFunds); - - T::Currency::transfer( - &pallet_account, - &recipient, - amount, - ExistenceRequirement::AllowDeath, - )?; - - Self::deposit_event(Event::FundsWithdrawn { - amount, - recipient, - }); - - Ok(()) - } - - /// Request a signature for a payload - #[pallet::call_index(3)] - #[pallet::weight(::WeightInfo::sign())] - pub fn sign( - origin: OriginFor, - payload: [u8; 32], - key_version: u32, - path: Vec, - algo: Vec, - dest: Vec, - params: Vec, - ) -> DispatchResult { - let requester = ensure_signed(origin)?; - - // Ensure initialized - ensure!(Admin::::get().is_some(), Error::::NotInitialized); - - // Get deposit amount - let deposit = SignatureDeposit::::get(); - - // Transfer deposit from requester to pallet account - let pallet_account = Self::account_id(); - T::Currency::transfer( - &requester, - &pallet_account, - deposit, - ExistenceRequirement::KeepAlive, - )?; - - // Get chain ID for event (convert BoundedVec to Vec) - let chain_id = ChainId::::get().to_vec(); - - // Emit event - Self::deposit_event(Event::SignatureRequested { - sender: requester, - payload, - key_version, - deposit, - chain_id, - path, - algo, - dest, - params, - }); - - Ok(()) - } - - /// Request a signature for a serialized transaction - #[pallet::call_index(4)] - #[pallet::weight(::WeightInfo::sign_respond())] - pub fn sign_respond( - origin: OriginFor, - serialized_transaction: Vec, - slip44_chain_id: u32, - key_version: u32, - path: Vec, - algo: Vec, - dest: Vec, - params: Vec, - explorer_deserialization_format: SerializationFormat, - explorer_deserialization_schema: Vec, - callback_serialization_format: SerializationFormat, - callback_serialization_schema: Vec, - ) -> DispatchResult { - let requester = ensure_signed(origin)?; - - // Ensure initialized - ensure!(Admin::::get().is_some(), Error::::NotInitialized); - - // Validate transaction data - ensure!(!serialized_transaction.is_empty(), Error::::InvalidTransaction); - - // Get deposit amount - let deposit = SignatureDeposit::::get(); - - // Transfer deposit from requester to pallet account - let pallet_account = Self::account_id(); - T::Currency::transfer( - &requester, - &pallet_account, - deposit, - ExistenceRequirement::KeepAlive, - )?; - - // Emit event - Self::deposit_event(Event::SignRespondRequested { - sender: requester, - transaction_data: serialized_transaction, - slip44_chain_id, - key_version, - deposit, - path, - algo, - dest, - params, - explorer_deserialization_format: explorer_deserialization_format as u8, - explorer_deserialization_schema, - callback_serialization_format: callback_serialization_format as u8, - callback_serialization_schema, - }); - - Ok(()) - } - - /// Respond to signature requests (batch support) - #[pallet::call_index(5)] - #[pallet::weight(::WeightInfo::respond(request_ids.len() as u32))] - pub fn respond( - origin: OriginFor, - request_ids: Vec<[u8; 32]>, - signatures: Vec, - ) -> DispatchResult { - let responder = ensure_signed(origin)?; - - // Validate input lengths - ensure!( - request_ids.len() == signatures.len(), - Error::::InvalidInputLength - ); - - // Emit events for each response (NO PAYMENT!) - for i in 0..request_ids.len() { - Self::deposit_event(Event::SignatureResponded { - request_id: request_ids[i], - responder: responder.clone(), - signature: signatures[i].clone(), - }); - } - - Ok(()) - } - - /// Report signature generation errors (batch support) - #[pallet::call_index(6)] - #[pallet::weight(::WeightInfo::respond_error(errors.len() as u32))] - pub fn respond_error( - origin: OriginFor, - errors: Vec, - ) -> DispatchResult { - let responder = ensure_signed(origin)?; - - // Emit error events (NO PAYMENT!) - for error in errors { - Self::deposit_event(Event::SignatureError { - request_id: error.request_id, - responder: responder.clone(), - error: error.error_message, - }); - } - - Ok(()) - } - - /// Provide a read response with signature - #[pallet::call_index(7)] - #[pallet::weight(::WeightInfo::read_respond())] - pub fn read_respond( - origin: OriginFor, - request_id: [u8; 32], - serialized_output: Vec, - signature: Signature, - ) -> DispatchResult { - let responder = ensure_signed(origin)?; - - // Just emit event (NO VERIFICATION, NO PAYMENT!) - Self::deposit_event(Event::ReadResponded { - request_id, - responder, - serialized_output, - signature, - }); - - Ok(()) - } - - /// Get the current signature deposit amount - #[pallet::call_index(8)] - #[pallet::weight(::WeightInfo::get_signature_deposit())] - pub fn get_signature_deposit(_origin: OriginFor) -> DispatchResultWithPostInfo { - // This is just for RPC queries - the getter handles actual retrieval - Ok(().into()) - } - } - - // Helper functions - impl Pallet { - /// Get the pallet's account ID (where funds are stored) - pub fn account_id() -> T::AccountId { - T::PalletId::get().into_account_truncating() - } - } -} \ No newline at end of file + use super::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Currency for handling deposits and fees + type Currency: Currency; + + /// The pallet's unique ID for deriving its account + #[pallet::constant] + type PalletId: Get; + + /// Maximum length for chain ID + #[pallet::constant] + type MaxChainIdLength: Get; + + type WeightInfo: WeightInfo; + } + + // ======================================== + // Types + // ======================================== + + /// Serialization format enum + #[derive(Encode, Decode, TypeInfo, Clone, Copy, Debug, PartialEq, Eq)] + pub enum SerializationFormat { + Borsh = 0, + AbiJson = 1, + } + + /// Affine point for signatures + #[derive(Encode, Decode, TypeInfo, Clone, Debug, PartialEq, Eq)] + pub struct AffinePoint { + pub x: [u8; 32], + pub y: [u8; 32], + } + + /// Signature structure + #[derive(Encode, Decode, TypeInfo, Clone, Debug, PartialEq, Eq)] + pub struct Signature { + pub big_r: AffinePoint, + pub s: [u8; 32], + pub recovery_id: u8, + } + + /// Error response structure + #[derive(Encode, Decode, TypeInfo, Debug, Clone, PartialEq, Eq)] + pub struct ErrorResponse { + pub request_id: [u8; 32], + pub error_message: Vec, + } + + // ======================================== + // Storage + // ======================================== + + /// The admin account that controls this pallet + #[pallet::storage] + #[pallet::getter(fn admin)] + pub type Admin = StorageValue<_, T::AccountId>; + + /// The amount required as deposit for signature requests + #[pallet::storage] + #[pallet::getter(fn signature_deposit)] + pub type SignatureDeposit = StorageValue<_, BalanceOf, ValueQuery>; + + /// The CAIP-2 chain identifier + #[pallet::storage] + #[pallet::getter(fn chain_id)] + pub type ChainId = StorageValue<_, BoundedVec, ValueQuery>; + + // ======================================== + // Events + // ======================================== + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Pallet has been initialized with an admin + Initialized { + admin: T::AccountId, + signature_deposit: BalanceOf, + chain_id: Vec, + }, + + /// Signature deposit amount has been updated + DepositUpdated { + old_deposit: BalanceOf, + new_deposit: BalanceOf, + }, + + /// Funds have been withdrawn from the pallet + FundsWithdrawn { + amount: BalanceOf, + recipient: T::AccountId, + }, + + /// A signature has been requested + SignatureRequested { + sender: T::AccountId, + payload: [u8; 32], + key_version: u32, + deposit: BalanceOf, + chain_id: Vec, + path: Vec, + algo: Vec, + dest: Vec, + params: Vec, + }, + + /// Sign-respond request event + SignRespondRequested { + sender: T::AccountId, + transaction_data: Vec, + slip44_chain_id: u32, + key_version: u32, + deposit: BalanceOf, + path: Vec, + algo: Vec, + dest: Vec, + params: Vec, + explorer_deserialization_format: u8, + explorer_deserialization_schema: Vec, + callback_serialization_format: u8, + callback_serialization_schema: Vec, + }, + + /// Signature response event + SignatureResponded { + request_id: [u8; 32], + responder: T::AccountId, + signature: Signature, + }, + + /// Signature error event + SignatureError { + request_id: [u8; 32], + responder: T::AccountId, + error: Vec, + }, + + /// Read response event + ReadResponded { + request_id: [u8; 32], + responder: T::AccountId, + serialized_output: Vec, + signature: Signature, + }, + } + + // ======================================== + // Errors + // ======================================== + + #[pallet::error] + pub enum Error { + /// The pallet has already been initialized + AlreadyInitialized, + /// The pallet has not been initialized yet + NotInitialized, + /// Unauthorized - caller is not admin + Unauthorized, + /// Insufficient funds for withdrawal + InsufficientFunds, + /// Invalid transaction data (empty) + InvalidTransaction, + /// Arrays must have the same length + InvalidInputLength, + /// The chain ID is too long + ChainIdTooLong, + } + + // ======================================== + // Extrinsics + // ======================================== + + #[pallet::call] + impl Pallet { + /// Initialize the pallet with admin, deposit, and chain ID + #[pallet::call_index(0)] + #[pallet::weight(::WeightInfo::initialize())] + pub fn initialize( + origin: OriginFor, + admin: T::AccountId, + signature_deposit: BalanceOf, + chain_id: Vec, + ) -> DispatchResult { + let _initializer = ensure_signed(origin)?; + ensure!(Admin::::get().is_none(), Error::::AlreadyInitialized); + + Admin::::put(&admin); + SignatureDeposit::::put(signature_deposit); + + let bounded_chain_id = BoundedVec::::try_from(chain_id.clone()) + .map_err(|_| Error::::ChainIdTooLong)?; + ChainId::::put(bounded_chain_id); + + Self::deposit_event(Event::Initialized { + admin, + signature_deposit, + chain_id, + }); + + Ok(()) + } + + /// Update the signature deposit amount (admin only) + #[pallet::call_index(1)] + #[pallet::weight(::WeightInfo::update_deposit())] + pub fn update_deposit(origin: OriginFor, new_deposit: BalanceOf) -> DispatchResult { + let who = ensure_signed(origin)?; + let admin = Admin::::get().ok_or(Error::::NotInitialized)?; + ensure!(who == admin, Error::::Unauthorized); + + let old_deposit = SignatureDeposit::::get(); + SignatureDeposit::::put(new_deposit); + + Self::deposit_event(Event::DepositUpdated { + old_deposit, + new_deposit, + }); + + Ok(()) + } + + /// Withdraw funds from the pallet account (admin only) + #[pallet::call_index(2)] + #[pallet::weight(::WeightInfo::withdraw_funds())] + pub fn withdraw_funds(origin: OriginFor, recipient: T::AccountId, amount: BalanceOf) -> DispatchResult { + let who = ensure_signed(origin)?; + let admin = Admin::::get().ok_or(Error::::NotInitialized)?; + ensure!(who == admin, Error::::Unauthorized); + + let pallet_account = Self::account_id(); + let pallet_balance = T::Currency::free_balance(&pallet_account); + ensure!(pallet_balance >= amount, Error::::InsufficientFunds); + + T::Currency::transfer(&pallet_account, &recipient, amount, ExistenceRequirement::AllowDeath)?; + + Self::deposit_event(Event::FundsWithdrawn { amount, recipient }); + + Ok(()) + } + + /// Request a signature for a payload + #[pallet::call_index(3)] + #[pallet::weight(::WeightInfo::sign())] + pub fn sign( + origin: OriginFor, + payload: [u8; 32], + key_version: u32, + path: Vec, + algo: Vec, + dest: Vec, + params: Vec, + ) -> DispatchResult { + let requester = ensure_signed(origin)?; + + // Ensure initialized + ensure!(Admin::::get().is_some(), Error::::NotInitialized); + + // Get deposit amount + let deposit = SignatureDeposit::::get(); + + // Transfer deposit from requester to pallet account + let pallet_account = Self::account_id(); + T::Currency::transfer(&requester, &pallet_account, deposit, ExistenceRequirement::KeepAlive)?; + + // Get chain ID for event (convert BoundedVec to Vec) + let chain_id = ChainId::::get().to_vec(); + + // Emit event + Self::deposit_event(Event::SignatureRequested { + sender: requester, + payload, + key_version, + deposit, + chain_id, + path, + algo, + dest, + params, + }); + + Ok(()) + } + + /// Request a signature for a serialized transaction + #[pallet::call_index(4)] + #[pallet::weight(::WeightInfo::sign_respond())] + pub fn sign_respond( + origin: OriginFor, + serialized_transaction: Vec, + slip44_chain_id: u32, + key_version: u32, + path: Vec, + algo: Vec, + dest: Vec, + params: Vec, + explorer_deserialization_format: SerializationFormat, + explorer_deserialization_schema: Vec, + callback_serialization_format: SerializationFormat, + callback_serialization_schema: Vec, + ) -> DispatchResult { + let requester = ensure_signed(origin)?; + + // Ensure initialized + ensure!(Admin::::get().is_some(), Error::::NotInitialized); + + // Validate transaction data + ensure!(!serialized_transaction.is_empty(), Error::::InvalidTransaction); + + // Get deposit amount + let deposit = SignatureDeposit::::get(); + + // Transfer deposit from requester to pallet account + let pallet_account = Self::account_id(); + T::Currency::transfer(&requester, &pallet_account, deposit, ExistenceRequirement::KeepAlive)?; + + // Emit event + Self::deposit_event(Event::SignRespondRequested { + sender: requester, + transaction_data: serialized_transaction, + slip44_chain_id, + key_version, + deposit, + path, + algo, + dest, + params, + explorer_deserialization_format: explorer_deserialization_format as u8, + explorer_deserialization_schema, + callback_serialization_format: callback_serialization_format as u8, + callback_serialization_schema, + }); + + Ok(()) + } + + /// Respond to signature requests (batch support) + #[pallet::call_index(5)] + #[pallet::weight(::WeightInfo::respond(request_ids.len() as u32))] + pub fn respond(origin: OriginFor, request_ids: Vec<[u8; 32]>, signatures: Vec) -> DispatchResult { + let responder = ensure_signed(origin)?; + + // Validate input lengths + ensure!(request_ids.len() == signatures.len(), Error::::InvalidInputLength); + + // Emit events for each response + for i in 0..request_ids.len() { + Self::deposit_event(Event::SignatureResponded { + request_id: request_ids[i], + responder: responder.clone(), + signature: signatures[i].clone(), + }); + } + + Ok(()) + } + + /// Report signature generation errors (batch support) + #[pallet::call_index(6)] + #[pallet::weight(::WeightInfo::respond_error(errors.len() as u32))] + pub fn respond_error(origin: OriginFor, errors: Vec) -> DispatchResult { + let responder = ensure_signed(origin)?; + + // Emit error events + for error in errors { + Self::deposit_event(Event::SignatureError { + request_id: error.request_id, + responder: responder.clone(), + error: error.error_message, + }); + } + + Ok(()) + } + + /// Provide a read response with signature + #[pallet::call_index(7)] + #[pallet::weight(::WeightInfo::read_respond())] + pub fn read_respond( + origin: OriginFor, + request_id: [u8; 32], + serialized_output: Vec, + signature: Signature, + ) -> DispatchResult { + let responder = ensure_signed(origin)?; + + // Just emit event + Self::deposit_event(Event::ReadResponded { + request_id, + responder, + serialized_output, + signature, + }); + + Ok(()) + } + + /// Get the current signature deposit amount + #[pallet::call_index(8)] + #[pallet::weight(::WeightInfo::get_signature_deposit())] + pub fn get_signature_deposit(_origin: OriginFor) -> DispatchResultWithPostInfo { + // This is just for RPC queries - the getter handles actual retrieval + Ok(().into()) + } + } + + // Helper functions + impl Pallet { + /// Get the pallet's account ID (where funds are stored) + pub fn account_id() -> T::AccountId { + T::PalletId::get().into_account_truncating() + } + } +} diff --git a/pallets/signet/src/tests.rs b/pallets/signet/src/tests.rs index c5d12e8778..e0622f0b6f 100644 --- a/pallets/signet/src/tests.rs +++ b/pallets/signet/src/tests.rs @@ -12,7 +12,6 @@ use sp_runtime::{ BuildStorage, }; -// Create a mock runtime for testing type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( @@ -61,7 +60,6 @@ impl system::Config for Test { type PostTransactions = (); } -// Balances pallet configuration parameter_types! { pub const ExistentialDeposit: u128 = 1; } @@ -77,13 +75,11 @@ impl pallet_balances::Config for Test { type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type FreezeIdentifier = (); - // Removed MaxHolds - not in newer version type MaxFreezes = (); type RuntimeHoldReason = (); type RuntimeFreezeReason = (); } -// Pallet ID for account derivation parameter_types! { pub const SignetPalletId: PalletId = PalletId(*b"py/signt"); pub const MaxChainIdLength: u32 = 128; @@ -97,17 +93,15 @@ impl pallet_signet::Config for Test { type WeightInfo = (); } -// Build test environment with initial balances pub fn new_test_ext() -> sp_io::TestExternalities { let t = system::GenesisConfig::::default().build_storage().unwrap(); let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| { System::set_block_number(1); - // Fund accounts directly in tests instead of using GenesisConfig - let _ = Balances::deposit_creating(&1, 1_000_000); // Admin has 1M tokens - let _ = Balances::deposit_creating(&2, 1_000_000); // User has 1M tokens - let _ = Balances::deposit_creating(&3, 100); // Poor user has only 100 tokens + let _ = Balances::deposit_creating(&1, 1_000_000); + let _ = Balances::deposit_creating(&2, 1_000_000); + let _ = Balances::deposit_creating(&3, 100); }); ext } @@ -126,7 +120,7 @@ fn test_initialize_works() { assert_eq!(Signet::admin(), None); assert_ok!(Signet::initialize( - RuntimeOrigin::root(), + RuntimeOrigin::signed(1), admin_account, deposit, chain_id.clone() @@ -151,130 +145,143 @@ fn test_initialize_works() { fn test_cannot_initialize_twice() { new_test_ext().execute_with(|| { assert_ok!(Signet::initialize( - RuntimeOrigin::root(), + RuntimeOrigin::signed(1), 1, 1000, b"test-chain".to_vec() )); assert_noop!( - Signet::initialize(RuntimeOrigin::root(), 2, 2000, b"test-chain".to_vec()), + Signet::initialize(RuntimeOrigin::signed(2), 2, 2000, b"test-chain".to_vec()), Error::::AlreadyInitialized ); - - assert_eq!(Signet::admin(), Some(1)); }); } #[test] fn test_cannot_use_before_initialization() { - new_test_ext().execute_with(|| { - assert_noop!( - Signet::sign( - RuntimeOrigin::signed(1), - [0u8; 32], - 1, - b"path".to_vec(), - b"algo".to_vec(), - b"dest".to_vec(), - b"params".to_vec() - ), - Error::::NotInitialized - ); - }); -} - -#[test] -fn test_only_root_can_initialize() { new_test_ext().execute_with(|| { assert_noop!( - Signet::initialize(RuntimeOrigin::signed(1), 1, 1000, b"test-chain".to_vec()), - sp_runtime::DispatchError::BadOrigin + Signet::sign( + RuntimeOrigin::signed(1), + [0u8; 32], + 1, + b"path".to_vec(), + b"algo".to_vec(), + b"dest".to_vec(), + b"params".to_vec() + ), + Error::::NotInitialized ); - - assert_ok!(Signet::initialize( - RuntimeOrigin::root(), - 1, - 1000, - b"test-chain".to_vec() - )); }); } #[test] -fn test_initialize_sets_deposit() { +fn test_any_signed_can_initialize_once() { new_test_ext().execute_with(|| { - let admin = 1u64; - let initial_deposit = 1000u128; // Changed from u128 to u64 - assert_ok!(Signet::initialize( - RuntimeOrigin::root(), - admin, - initial_deposit, + RuntimeOrigin::signed(2), + 1, + 1000, b"test-chain".to_vec() )); - assert_eq!(Signet::signature_deposit(), initial_deposit); + assert_eq!(Signet::admin(), Some(1)); + assert_eq!(Signet::signature_deposit(), 1000); - System::assert_last_event( - Event::Initialized { - admin, - signature_deposit: initial_deposit, - chain_id: b"test-chain".to_vec(), // Add this line - } - .into(), + assert_noop!( + Signet::initialize( + RuntimeOrigin::signed(1), + 3, + 2000, + b"other-chain".to_vec() + ), + Error::::AlreadyInitialized ); + + assert_noop!( + Signet::initialize(RuntimeOrigin::signed(3), 3, 2000, b"other-chain".to_vec()), + Error::::AlreadyInitialized + ); + + assert_eq!(Signet::admin(), Some(1)); + assert_eq!(Signet::signature_deposit(), 1000); }); } #[test] -fn test_update_deposit_as_admin() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let initial_deposit = 1000u128; // Changed from u128 to u64 - let new_deposit = 2000u128; // Changed from u128 to u64 - - assert_ok!(Signet::initialize( - RuntimeOrigin::root(), - admin, - initial_deposit, - b"test-chain".to_vec() - )); - - assert_ok!(Signet::update_deposit(RuntimeOrigin::signed(admin), new_deposit)); - - assert_eq!(Signet::signature_deposit(), new_deposit); +fn test_initialize_sets_deposit() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let initial_deposit = 1000u128; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + initial_deposit, + b"test-chain".to_vec() + )); + + assert_eq!(Signet::signature_deposit(), initial_deposit); + + System::assert_last_event( + Event::Initialized { + admin, + signature_deposit: initial_deposit, + chain_id: b"test-chain".to_vec(), + } + .into(), + ); + }); +} - System::assert_last_event( - Event::DepositUpdated { - old_deposit: initial_deposit, - new_deposit, - } - .into(), - ); - }); +#[test] +fn test_update_deposit_as_admin() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let initial_deposit = 1000u128; + let new_deposit = 2000u128; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + initial_deposit, + b"test-chain".to_vec() + )); + + assert_ok!(Signet::update_deposit(RuntimeOrigin::signed(admin), new_deposit)); + assert_eq!(Signet::signature_deposit(), new_deposit); + + System::assert_last_event( + Event::DepositUpdated { + old_deposit: initial_deposit, + new_deposit, + } + .into(), + ); + }); } #[test] fn test_non_admin_cannot_update_deposit() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let non_admin = 2u64; + new_test_ext().execute_with(|| { + let admin = 1u64; + let non_admin = 2u64; - assert_ok!(Signet::initialize( - RuntimeOrigin::root(), - admin, - 1000, - b"test-chain".to_vec() - )); + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + 1000, + b"test-chain".to_vec() + )); - assert_noop!( - Signet::update_deposit(RuntimeOrigin::signed(non_admin), 2000), - Error::::Unauthorized - ); + assert_noop!( + Signet::update_deposit(RuntimeOrigin::signed(non_admin), 2000), + Error::::Unauthorized + ); - assert_eq!(Signet::signature_deposit(), 1000); - }); + assert_eq!(Signet::signature_deposit(), 1000); + }); } #[test] @@ -289,95 +296,85 @@ fn test_cannot_update_deposit_before_initialization() { #[test] fn test_withdraw_funds_as_admin() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let recipient = 2u64; - let amount = 5000u128; // Changed from u128 to u64 + new_test_ext().execute_with(|| { + let admin = 1u64; + let recipient = 2u64; + let amount = 5000u128; - // Initialize - assert_ok!(Signet::initialize( - RuntimeOrigin::root(), - admin, - 1000, - b"test-chain".to_vec() - )); + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + 1000, + b"test-chain".to_vec() + )); - // Fund the pallet account (simulate deposits) - let pallet_account = Signet::account_id(); - let _ = Balances::deposit_creating(&pallet_account, 10_000); + let pallet_account = Signet::account_id(); + let _ = Balances::deposit_creating(&pallet_account, 10_000); - // Check initial balances - let recipient_balance_before = Balances::free_balance(&recipient); - assert_eq!(Balances::free_balance(&pallet_account), 10_000); + let recipient_balance_before = Balances::free_balance(&recipient); + assert_eq!(Balances::free_balance(&pallet_account), 10_000); - // Admin withdraws funds - assert_ok!(Signet::withdraw_funds(RuntimeOrigin::signed(admin), recipient, amount)); + assert_ok!(Signet::withdraw_funds(RuntimeOrigin::signed(admin), recipient, amount)); - // Check balances after withdrawal - assert_eq!(Balances::free_balance(&pallet_account), 5_000); // 10k - 5k - assert_eq!(Balances::free_balance(&recipient), recipient_balance_before + amount); + assert_eq!(Balances::free_balance(&pallet_account), 5_000); + assert_eq!(Balances::free_balance(&recipient), recipient_balance_before + amount); - // Check event - System::assert_last_event(Event::FundsWithdrawn { amount, recipient }.into()); - }); + System::assert_last_event(Event::FundsWithdrawn { amount, recipient }.into()); + }); } #[test] fn test_non_admin_cannot_withdraw() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let non_admin = 2u64; - - // Initialize and fund pallet - assert_ok!(Signet::initialize( - RuntimeOrigin::root(), - admin, - 1000, - b"test-chain".to_vec() - )); - let pallet_account = Signet::account_id(); - let _ = Balances::deposit_creating(&pallet_account, 10_000); + new_test_ext().execute_with(|| { + let admin = 1u64; + let non_admin = 2u64; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + 1000, + b"test-chain".to_vec() + )); + + let pallet_account = Signet::account_id(); + let _ = Balances::deposit_creating(&pallet_account, 10_000); - // Non-admin tries to withdraw - assert_noop!( - Signet::withdraw_funds(RuntimeOrigin::signed(non_admin), non_admin, 5000), - Error::::Unauthorized - ); - }); + assert_noop!( + Signet::withdraw_funds(RuntimeOrigin::signed(non_admin), non_admin, 5000), + Error::::Unauthorized + ); + }); } #[test] fn test_cannot_withdraw_more_than_balance() { - new_test_ext().execute_with(|| { - let admin = 1u64; - - // Initialize and fund pallet with 10k - assert_ok!(Signet::initialize( - RuntimeOrigin::root(), - admin, - 1000, - b"test-chain".to_vec() - )); - let pallet_account = Signet::account_id(); - let _ = Balances::deposit_creating(&pallet_account, 10_000); + new_test_ext().execute_with(|| { + let admin = 1u64; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + 1000, + b"test-chain".to_vec() + )); + + let pallet_account = Signet::account_id(); + let _ = Balances::deposit_creating(&pallet_account, 10_000); - // Try to withdraw 20k (more than balance) - assert_noop!( - Signet::withdraw_funds(RuntimeOrigin::signed(admin), admin, 20_000), - Error::::InsufficientFunds - ); - }); + assert_noop!( + Signet::withdraw_funds(RuntimeOrigin::signed(admin), admin, 20_000), + Error::::InsufficientFunds + ); + }); } #[test] fn test_pallet_account_id_is_deterministic() { new_test_ext().execute_with(|| { - // The pallet account should always be the same let account1 = Signet::account_id(); let account2 = Signet::account_id(); assert_eq!(account1, account2); - // And it should be different from regular accounts assert_ne!(account1, 1u64); assert_ne!(account1, 2u64); }); @@ -385,99 +382,89 @@ fn test_pallet_account_id_is_deterministic() { #[test] fn test_sign_request_works() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let requester = 2u64; - let deposit = 1000u128; - - // Initialize first - assert_ok!(Signet::initialize( - RuntimeOrigin::root(), - admin, - deposit, - b"test-chain".to_vec() - )); - - // Check requester balance before - let balance_before = Balances::free_balance(&requester); - - // Create signature request - let payload = [42u8; 32]; - let key_version = 1u32; - let path = b"path".to_vec(); - let algo = b"ecdsa".to_vec(); - let dest = b"callback_contract".to_vec(); - let params = b"{}".to_vec(); - - // Submit signature request - assert_ok!(Signet::sign( - RuntimeOrigin::signed(requester), - payload, - key_version, - path.clone(), - algo.clone(), - dest.clone(), - params.clone() - )); - - // Check that deposit was transferred to pallet - assert_eq!(Balances::free_balance(&requester), balance_before - deposit); - let pallet_account = Signet::account_id(); - assert_eq!(Balances::free_balance(&pallet_account), deposit); - - // Check event was emitted - System::assert_last_event( - Event::SignatureRequested { - sender: requester, - payload, - key_version, - deposit, - chain_id: b"test-chain".to_vec(), // Add this line - path, - algo, - dest, - params, - } - .into(), - ); - }); + new_test_ext().execute_with(|| { + let admin = 1u64; + let requester = 2u64; + let deposit = 1000u128; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + deposit, + b"test-chain".to_vec() + )); + + let balance_before = Balances::free_balance(&requester); + let payload = [42u8; 32]; + let key_version = 1u32; + let path = b"path".to_vec(); + let algo = b"ecdsa".to_vec(); + let dest = b"callback_contract".to_vec(); + let params = b"{}".to_vec(); + + assert_ok!(Signet::sign( + RuntimeOrigin::signed(requester), + payload, + key_version, + path.clone(), + algo.clone(), + dest.clone(), + params.clone() + )); + + assert_eq!(Balances::free_balance(&requester), balance_before - deposit); + let pallet_account = Signet::account_id(); + assert_eq!(Balances::free_balance(&pallet_account), deposit); + + System::assert_last_event( + Event::SignatureRequested { + sender: requester, + payload, + key_version, + deposit, + chain_id: b"test-chain".to_vec(), + path, + algo, + dest, + params, + } + .into(), + ); + }); } #[test] fn test_sign_request_insufficient_balance() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let poor_user = 3u64; // Has only 100 tokens - let deposit = 1000u128; // Deposit is 1000 + new_test_ext().execute_with(|| { + let admin = 1u64; + let poor_user = 3u64; + let deposit = 1000u128; - // Initialize - assert_ok!(Signet::initialize( - RuntimeOrigin::root(), - admin, - deposit, - b"test-chain".to_vec() - )); + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + deposit, + b"test-chain".to_vec() + )); - // Try to request signature without enough balance - assert_noop!( - Signet::sign( - RuntimeOrigin::signed(poor_user), - [0u8; 32], - 1, - b"path".to_vec(), - b"algo".to_vec(), - b"dest".to_vec(), - b"params".to_vec() - ), - sp_runtime::TokenError::FundsUnavailable - ); - }); + assert_noop!( + Signet::sign( + RuntimeOrigin::signed(poor_user), + [0u8; 32], + 1, + b"path".to_vec(), + b"algo".to_vec(), + b"dest".to_vec(), + b"params".to_vec() + ), + sp_runtime::TokenError::FundsUnavailable + ); + }); } #[test] fn test_sign_request_before_initialization() { new_test_ext().execute_with(|| { - // Try to request signature before initialization assert_noop!( Signet::sign( RuntimeOrigin::signed(1), @@ -495,49 +482,45 @@ fn test_sign_request_before_initialization() { #[test] fn test_multiple_sign_requests() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let requester1 = 1u64; - let requester2 = 2u64; - let deposit = 100u128; - - // Initialize with small deposit - assert_ok!(Signet::initialize( - RuntimeOrigin::root(), - admin, - deposit, - b"test-chain".to_vec() - )); - - let pallet_account = Signet::account_id(); - - // First request - assert_ok!(Signet::sign( - RuntimeOrigin::signed(requester1), - [1u8; 32], - 1, - b"path1".to_vec(), - b"algo".to_vec(), - b"dest".to_vec(), - b"params".to_vec() - )); - - assert_eq!(Balances::free_balance(&pallet_account), deposit); - - // Second request - funds accumulate - assert_ok!(Signet::sign( - RuntimeOrigin::signed(requester2), - [2u8; 32], - 2, - b"path2".to_vec(), - b"algo".to_vec(), - b"dest".to_vec(), - b"params".to_vec() - )); - - // Pallet should have accumulated both deposits - assert_eq!(Balances::free_balance(&pallet_account), deposit * 2); - }); + new_test_ext().execute_with(|| { + let admin = 1u64; + let requester1 = 1u64; + let requester2 = 2u64; + let deposit = 100u128; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + deposit, + b"test-chain".to_vec() + )); + + let pallet_account = Signet::account_id(); + + assert_ok!(Signet::sign( + RuntimeOrigin::signed(requester1), + [1u8; 32], + 1, + b"path1".to_vec(), + b"algo".to_vec(), + b"dest".to_vec(), + b"params".to_vec() + )); + + assert_eq!(Balances::free_balance(&pallet_account), deposit); + + assert_ok!(Signet::sign( + RuntimeOrigin::signed(requester2), + [2u8; 32], + 2, + b"path2".to_vec(), + b"algo".to_vec(), + b"dest".to_vec(), + b"params".to_vec() + )); + + assert_eq!(Balances::free_balance(&pallet_account), deposit * 2); + }); } fn create_test_signature() -> Signature { @@ -553,87 +536,78 @@ fn create_test_signature() -> Signature { #[test] fn test_sign_respond_works() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let requester = 2u64; - let deposit = 100u128; - - // Initialize - assert_ok!(Signet::initialize( - RuntimeOrigin::root(), - admin, - deposit, - b"test-chain".to_vec() - )); - - // Create transaction data - let tx_data = b"mock_transaction_data".to_vec(); - let slip44_chain_id = 60u32; // Ethereum - - // Check balance before - let balance_before = Balances::free_balance(&requester); - - // Submit sign-respond request - assert_ok!(Signet::sign_respond( - RuntimeOrigin::signed(requester), - tx_data.clone(), - slip44_chain_id, - 1, // key_version - b"path".to_vec(), - b"ecdsa".to_vec(), - b"callback".to_vec(), - b"{}".to_vec(), - SerializationFormat::AbiJson, - b"schema1".to_vec(), - SerializationFormat::Borsh, - b"schema2".to_vec() - )); - - // Check deposit was taken - assert_eq!(Balances::free_balance(&requester), balance_before - deposit); - - // Check event - let events = System::events(); - let event_found = events - .iter() - .any(|e| matches!(&e.event, RuntimeEvent::Signet(Event::SignRespondRequested { .. }))); - assert!(event_found); - }); + new_test_ext().execute_with(|| { + let admin = 1u64; + let requester = 2u64; + let deposit = 100u128; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + deposit, + b"test-chain".to_vec() + )); + + let tx_data = b"mock_transaction_data".to_vec(); + let slip44_chain_id = 60u32; + let balance_before = Balances::free_balance(&requester); + + assert_ok!(Signet::sign_respond( + RuntimeOrigin::signed(requester), + tx_data.clone(), + slip44_chain_id, + 1, + b"path".to_vec(), + b"ecdsa".to_vec(), + b"callback".to_vec(), + b"{}".to_vec(), + SerializationFormat::AbiJson, + b"schema1".to_vec(), + SerializationFormat::Borsh, + b"schema2".to_vec() + )); + + assert_eq!(Balances::free_balance(&requester), balance_before - deposit); + + let events = System::events(); + let event_found = events + .iter() + .any(|e| matches!(&e.event, RuntimeEvent::Signet(Event::SignRespondRequested { .. }))); + assert!(event_found); + }); } #[test] fn test_sign_respond_empty_transaction_fails() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let requester = 2u64; + new_test_ext().execute_with(|| { + let admin = 1u64; + let requester = 2u64; - // Initialize - assert_ok!(Signet::initialize( - RuntimeOrigin::root(), - admin, - 100, - b"test-chain".to_vec() - )); + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + 100, + b"test-chain".to_vec() + )); - // Try with empty transaction - assert_noop!( - Signet::sign_respond( - RuntimeOrigin::signed(requester), - vec![], // Empty transaction - 60, - 1, - b"path".to_vec(), - b"algo".to_vec(), - b"dest".to_vec(), - b"params".to_vec(), - SerializationFormat::Borsh, - vec![], - SerializationFormat::Borsh, - vec![] - ), - Error::::InvalidTransaction - ); - }); + assert_noop!( + Signet::sign_respond( + RuntimeOrigin::signed(requester), + vec![], + 60, + 1, + b"path".to_vec(), + b"algo".to_vec(), + b"dest".to_vec(), + b"params".to_vec(), + SerializationFormat::Borsh, + vec![], + SerializationFormat::Borsh, + vec![] + ), + Error::::InvalidTransaction + ); + }); } #[test] @@ -643,14 +617,12 @@ fn test_respond_single() { let request_id = [99u8; 32]; let signature = create_test_signature(); - // No initialization needed for respond assert_ok!(Signet::respond( RuntimeOrigin::signed(responder), vec![request_id], vec![signature.clone()] )); - // Check event System::assert_last_event( Event::SignatureResponded { request_id, @@ -673,14 +645,12 @@ fn test_respond_batch() { create_test_signature(), ]; - // Batch respond assert_ok!(Signet::respond( RuntimeOrigin::signed(responder), request_ids.clone(), signatures.clone() )); - // Check that 3 events were emitted let events = System::events(); let response_events = events .iter() @@ -695,7 +665,6 @@ fn test_respond_mismatched_arrays_fails() { new_test_ext().execute_with(|| { let responder = 1u64; - // 2 request IDs but 3 signatures assert_noop!( Signet::respond( RuntimeOrigin::signed(responder), @@ -725,7 +694,6 @@ fn test_respond_error_single() { vec![error_response] )); - // Check event System::assert_last_event( Event::SignatureError { request_id: [99u8; 32], @@ -754,7 +722,6 @@ fn test_respond_error_batch() { assert_ok!(Signet::respond_error(RuntimeOrigin::signed(responder), errors)); - // Check that 2 error events were emitted let events = System::events(); let error_events = events .iter() @@ -779,7 +746,6 @@ fn test_read_respond() { signature.clone() )); - // Check event System::assert_last_event( Event::ReadResponded { request_id, @@ -794,62 +760,59 @@ fn test_read_respond() { #[test] fn test_get_signature_deposit() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let deposit = 5000u128; - - // Initialize with a specific deposit - assert_ok!(Signet::initialize( - RuntimeOrigin::root(), - admin, - deposit, - b"test-chain".to_vec() - )); - - // The getter should return the deposit - assert_eq!(Signet::signature_deposit(), deposit); - - // The extrinsic should succeed (it's mainly for RPC) - assert_ok!(Signet::get_signature_deposit(RuntimeOrigin::signed(1))); - }); + new_test_ext().execute_with(|| { + let admin = 1u64; + let deposit = 5000u128; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + deposit, + b"test-chain".to_vec() + )); + + assert_eq!(Signet::signature_deposit(), deposit); + assert_ok!(Signet::get_signature_deposit(RuntimeOrigin::signed(1))); + }); } -// Update test for sign to include chain_id in event #[test] fn test_sign_includes_chain_id() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let requester = 2u64; - let chain_id = b"hydradx:polkadot:0".to_vec(); - - // Initialize with specific chain_id - assert_ok!(Signet::initialize(RuntimeOrigin::root(), admin, 100, chain_id.clone())); - - // Submit signature request - assert_ok!(Signet::sign( - RuntimeOrigin::signed(requester), - [42u8; 32], - 1, - b"path".to_vec(), - b"algo".to_vec(), - b"dest".to_vec(), - b"params".to_vec() - )); - - // Check that event includes chain_id - let events = System::events(); - let sign_event = events.iter().find_map(|e| { - if let RuntimeEvent::Signet(Event::SignatureRequested { - chain_id: event_chain_id, - .. - }) = &e.event - { - Some(event_chain_id.clone()) - } else { - None - } - }); - - assert_eq!(sign_event, Some(chain_id)); - }); + new_test_ext().execute_with(|| { + let admin = 1u64; + let requester = 2u64; + let chain_id = b"hydradx:polkadot:0".to_vec(); + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + 100, + chain_id.clone() + )); + + assert_ok!(Signet::sign( + RuntimeOrigin::signed(requester), + [42u8; 32], + 1, + b"path".to_vec(), + b"algo".to_vec(), + b"dest".to_vec(), + b"params".to_vec() + )); + + let events = System::events(); + let sign_event = events.iter().find_map(|e| { + if let RuntimeEvent::Signet(Event::SignatureRequested { + chain_id: event_chain_id, + .. + }) = &e.event + { + Some(event_chain_id.clone()) + } else { + None + } + }); + + assert_eq!(sign_event, Some(chain_id)); + }); } diff --git a/signet-ts-client-scripts/README.md b/signet-ts-client-scripts/README.md new file mode 100644 index 0000000000..965ee12d47 --- /dev/null +++ b/signet-ts-client-scripts/README.md @@ -0,0 +1,109 @@ +# Signet Substrate Client + +Test client for the Signet pallet on Substrate/Polkadot. Validates signature generation and verification for both simple payloads and EIP-1559 transactions. + +## Prerequisites + +- Node.js v16+ and npm/yarn +- Running Substrate node with Signet pallet deployed (port 8000) +- Access to the Signet signature server + +## Setup + +### 1. Start the Signature Server + +Clone and run the signature server that responds to Substrate signature requests: + +```bash +# Clone the server repository +git clone https://github.com/sig-net/chain-signatures-solana.git +cd chain-signatures-solana/clients/response-server + +# Install dependencies +yarn install + +# Configure environment variables +cat > .env << EOF +# Substrate Configuration +SUBSTRATE_WS_URL=ws://localhost:8000 +SUBSTRATE_SIGNER_SEED=//Alice + +# Signing Keys (must match ROOT_PUBLIC_KEY in tests) +PRIVATE_KEY_TESTNET=0x... # Your private key for signing + +# Optional: Ethereum RPC for transaction monitoring +INFURA_API_KEY=your_infura_key_here +EOF + +# Start the server +yarn start +``` + +The server will connect to your Substrate node and automatically respond to signature requests. + +### 2. Install Test Client Dependencies + +```bash +yarn install +``` + +### 3. Ensure Substrate Node is Running + +The tests expect a Substrate node with the Signet pallet at `ws://localhost:8000`. If using Chopsticks: + +```bash +npx @acala-network/chopsticks@latest --config=hydradx \ + --wasm-override ./path/to/hydradx_runtime.wasm \ + --db=:memory: +``` + +## Running Tests + +```bash +# Run all tests +yarn test + +# Run with watch mode +yarn test:watch +``` + +## Test Coverage + +The test suite validates: +- **Simple Signatures**: Request and verify ECDSA signatures for 32-byte payloads +- **Transaction Signatures**: Sign and verify EIP-1559 Ethereum transactions +- **Key Derivation**: Verify derived keys match between client and server +- **Address Recovery**: Ensure signature recovery produces expected addresses + +## Expected Output + +``` +PASS ./signet.test.ts + Signet Pallet Integration + Sign + āœ“ should request and verify a signature + āœ… Signature received from: 14E5nqKAp3oAJcmzgZhUD2RcptBeUBScxKHgJKU4HPNcKVf3 + Recovered: 0xF4a62e4f48e8e71170BA758b5bAf90646db61301 + Expected: 0xf4a62e4f48e8e71170ba758b5baf90646db61301 + āœ… Signature verification PASSED + SignRespond + āœ“ should request and verify a transaction signature + āœ… Transaction signature received from: 14E5nqKAp3oAJcmzgZhUD2RcptBeUBScxKHgJKU4HPNcKVf3 + Recovered: 0xF4a62e4f48e8e71170BA758b5bAf90646db61301 + Expected: 0xf4a62e4f48e8e71170ba758b5baf90646db61301 + āœ… Transaction signature verification PASSED +``` + +## Configuration + +The root public key used for derivation is hardcoded in the tests. Ensure the server's `PRIVATE_KEY_TESTNET` corresponds to: + +```typescript +const ROOT_PUBLIC_KEY = "0x044eef776e4f257d68983e45b340c2e9546c5df95447900b6aadfec68fb46fdee257e26b8ba383ddba9914b33c60e869265f859566fff4baef283c54d821ca3b64"; +``` + +## Troubleshooting + +- **Timeout errors**: Ensure the signature server is running and connected to the same Substrate node +- **Address mismatch**: Verify the server's private key matches the client's expected public key +- **Transaction errors**: Check that the Signet pallet is initialized (tests handle this automatically) \ No newline at end of file diff --git a/signet-ts-client-scripts/key-derivation.ts b/signet-ts-client-scripts/key-derivation.ts new file mode 100644 index 0000000000..5cb2682ba0 --- /dev/null +++ b/signet-ts-client-scripts/key-derivation.ts @@ -0,0 +1,28 @@ +import { ethers } from "ethers"; +import { ec as EC } from "elliptic"; + +export class KeyDerivation { + private static readonly EPSILON_PREFIX = "sig.network v1.0.0 epsilon derivation"; + + static derivePublicKey( + rootPublicKey: string, + predecessorId: string, + path: string, + chainId: string + ): string { + const ec = new EC("secp256k1"); + const uncompressedRoot = rootPublicKey.slice(2); + + const derivationPath = `${this.EPSILON_PREFIX},${chainId},${predecessorId},${path}`; + const hash = ethers.keccak256(ethers.toUtf8Bytes(derivationPath)); + const scalarHex = hash.slice(2); + + const x = uncompressedRoot.substring(2, 66); + const y = uncompressedRoot.substring(66); + const oldPoint = ec.curve.point(x, y); + const scalarTimesG = ec.g.mul(scalarHex); + const newPoint = oldPoint.add(scalarTimesG); + + return `04${newPoint.getX().toString("hex").padStart(64, "0")}${newPoint.getY().toString("hex").padStart(64, "0")}`; + } +} \ No newline at end of file diff --git a/signet-ts-client-scripts/package.json b/signet-ts-client-scripts/package.json new file mode 100644 index 0000000000..a3ddea57bd --- /dev/null +++ b/signet-ts-client-scripts/package.json @@ -0,0 +1,35 @@ +{ + "name": "hydration-signet-interaction", + "version": "1.0.0", + "description": "TypeScript client for Signet pallet interaction", + "main": "emit-event.ts", + "scripts": { + "test": "jest", + "test:watch": "jest --watch" + }, + "jest": { + "preset": "ts-jest", + "testEnvironment": "node", + "testMatch": [ + "**/*.test.ts" + ] + }, + "dependencies": { + "@polkadot/api": "^11.2.1", + "@polkadot/keyring": "^12.6.2", + "@polkadot/util": "^12.6.2", + "elliptic": "^6.6.1", + "ethers": "^6.15.0", + "ts-node": "^10.9.2", + "typescript": "^5.3.3", + "viem": "^2.37.6" + }, + "devDependencies": { + "@types/elliptic": "^6.4.18", + "@types/jest": "^30.0.0", + "@types/mocha": "^10.0.10", + "@types/node": "^20.11.5", + "jest": "^30.1.3", + "ts-jest": "^29.4.4" + } +} diff --git a/signet-ts-client-scripts/signet-client.ts b/signet-ts-client-scripts/signet-client.ts new file mode 100644 index 0000000000..a946ea9dbe --- /dev/null +++ b/signet-ts-client-scripts/signet-client.ts @@ -0,0 +1,219 @@ +import { ApiPromise } from "@polkadot/api"; +import { EventRecord } from "@polkadot/types/interfaces"; +import { Vec } from "@polkadot/types"; +import { u8aToHex } from "@polkadot/util"; +import { ISubmittableResult } from "@polkadot/types/types"; +import { ethers } from "ethers"; +import { keccak256, recoverAddress } from "viem"; + +export class SignetClient { + constructor(private api: ApiPromise, private signer: any) {} + + async ensureInitialized(chainId: string): Promise { + const admin = await this.api.query.signet.admin(); + + if (admin.isEmpty) { + const chainIdBytes = Array.from(new TextEncoder().encode(chainId)); + const tx = this.api.tx.signet.initialize( + this.signer.address, + 1000000000000, + chainIdBytes + ); + await tx.signAndSend(this.signer); + await new Promise((resolve) => setTimeout(resolve, 5000)); + } + } + + async requestSignature(payload: Uint8Array, params: any): Promise { + const tx = this.api.tx.signet.sign( + Array.from(payload), + params.keyVersion, + params.path, + params.algo, + params.dest, + params.params + ); + + await new Promise((resolve, reject) => { + tx.signAndSend(this.signer, (result: ISubmittableResult) => { + const { status, dispatchError } = result; + if (dispatchError) { + reject(dispatchError); + } else if (status.isInBlock) { + resolve(); + } + }).catch(reject); + }); + } + + async requestTransactionSignature( + serializedTx: number[], + params: any + ): Promise { + const tx = this.api.tx.signet.signRespond( + serializedTx, + params.slip44ChainId, + params.keyVersion, + params.path, + params.algo || "", + params.dest || "", + params.params || "", + params.schemas.explorer.format, + Array.from(new TextEncoder().encode(params.schemas.explorer.schema)), + params.schemas.callback.format, + Array.from(new TextEncoder().encode(params.schemas.callback.schema)) + ); + + await tx.signAndSend(this.signer); + } + + async waitForSignature(requestId: string, timeout: number): Promise { + return new Promise((resolve) => { + let unsubscribe: any; + const timer = setTimeout(() => { + if (unsubscribe) unsubscribe(); + resolve(null); + }, timeout); + + this.api.query.system + .events((events: Vec) => { + events.forEach((record: EventRecord) => { + const { event } = record; + if ( + event.section === "signet" && + event.method === "SignatureResponded" + ) { + const [reqId, responder, signature] = event.data as any; + if (u8aToHex(reqId.toU8a()) === requestId) { + clearTimeout(timer); + if (unsubscribe) unsubscribe(); + resolve({ + responder: responder.toString(), + signature: signature.toJSON(), + }); + } + } + }); + }) + .then((unsub: any) => { + unsubscribe = unsub; + }); + }); + } + + calculateRequestId( + sender: string, + payload: Uint8Array, + params: any, + chainId: string + ): string { + const payloadHex = "0x" + Buffer.from(payload).toString("hex"); + const encoded = ethers.AbiCoder.defaultAbiCoder().encode( + [ + "string", + "bytes", + "string", + "uint32", + "string", + "string", + "string", + "string", + ], + [ + sender, + payloadHex, + params.path, + params.keyVersion, + chainId, + params.algo, + params.dest, + params.params, + ] + ); + return ethers.keccak256(encoded); + } + + calculateSignRespondRequestId( + sender: string, + txData: number[], + params: any + ): string { + const txHex = "0x" + Buffer.from(txData).toString("hex"); + const encoded = ethers.solidityPacked( + [ + "string", + "bytes", + "uint32", + "uint32", + "string", + "string", + "string", + "string", + ], + [ + sender, + txHex, + params.slip44ChainId, + params.keyVersion, + params.path, + params.algo || "", + params.dest || "", + params.params || "", + ] + ); + return ethers.keccak256(encoded); + } + + async verifySignature( + payload: Uint8Array, + signature: any, + derivedPublicKey: string + ): Promise { + const r = signature.bigR.x.startsWith("0x") + ? signature.bigR.x + : `0x${signature.bigR.x}`; + const s = signature.s.startsWith("0x") ? signature.s : `0x${signature.s}`; + const v = BigInt(signature.recoveryId + 27); + + const recoveredAddress = await recoverAddress({ + hash: payload as any, + signature: { r, s, v }, + }); + + const expectedAddress = + "0x" + + keccak256(Buffer.from(derivedPublicKey.slice(2), "hex")).slice(-40); + + console.log(" Recovered:", recoveredAddress); + console.log(" Expected: ", expectedAddress); + + return recoveredAddress.toLowerCase() === expectedAddress.toLowerCase(); + } + + async verifyTransactionSignature( + tx: ethers.Transaction, + signature: any, + derivedPublicKey: string + ): Promise { + const msgHash = ethers.keccak256(tx.unsignedSerialized); + const r = signature.bigR.x.startsWith("0x") + ? signature.bigR.x + : `0x${signature.bigR.x}`; + const s = signature.s.startsWith("0x") ? signature.s : `0x${signature.s}`; + const v = BigInt(signature.recoveryId + 27); + + const recoveredAddress = await recoverAddress({ + hash: msgHash as `0x${string}`, + signature: { r, s, v } as any, + }); + + const expectedAddress = + "0x" + + keccak256(Buffer.from(derivedPublicKey.slice(2), "hex")).slice(-40); + + console.log(" Recovered:", recoveredAddress); + console.log(" Expected: ", expectedAddress); + + return recoveredAddress.toLowerCase() === expectedAddress.toLowerCase(); + } +} diff --git a/signet-ts-client-scripts/signet.test.ts b/signet-ts-client-scripts/signet.test.ts new file mode 100644 index 0000000000..004f2e1b6a --- /dev/null +++ b/signet-ts-client-scripts/signet.test.ts @@ -0,0 +1,126 @@ +import { ApiPromise, WsProvider, Keyring } from "@polkadot/api"; +import { waitReady } from "@polkadot/wasm-crypto"; +import { u8aToHex } from "@polkadot/util"; +import { encodeAddress } from "@polkadot/keyring"; +import { ethers } from "ethers"; +import * as crypto from "crypto"; +import { SignetClient } from "./signet-client"; +import { KeyDerivation } from "./key-derivation"; +import { TransactionBuilder } from "./transaction-builder"; + +describe("Signet Pallet Integration", () => { + let api: ApiPromise; + let client: SignetClient; + let alice: any; + let alicePolkadotAddress: string; + + const ROOT_PUBLIC_KEY = "0x044eef776e4f257d68983e45b340c2e9546c5df95447900b6aadfec68fb46fdee257e26b8ba383ddba9914b33c60e869265f859566fff4baef283c54d821ca3b64"; + const CHAIN_ID = "polkadot:2034"; + + beforeAll(async () => { + await waitReady(); + + api = await ApiPromise.create({ + provider: new WsProvider("ws://127.0.0.1:8000"), + types: { + AffinePoint: { x: "[u8; 32]", y: "[u8; 32]" }, + Signature: { big_r: "AffinePoint", s: "[u8; 32]", recovery_id: "u8" } + } + }); + + const keyring = new Keyring({ type: "sr25519" }); + alice = keyring.addFromUri("//Alice"); + alicePolkadotAddress = encodeAddress(alice.publicKey, 0); + + client = new SignetClient(api, alice); + await client.ensureInitialized(CHAIN_ID); + }); + + afterAll(async () => { + await api.disconnect(); + }); + + describe("Sign", () => { + it("should request and verify a signature", async () => { + const payload = crypto.randomBytes(32); + const params = { + keyVersion: 1, + path: "testPath", + algo: "ecdsa", + dest: "", + params: "{}" + }; + + const requestId = client.calculateRequestId(alicePolkadotAddress, payload, params, CHAIN_ID); + const derivedKey = KeyDerivation.derivePublicKey(ROOT_PUBLIC_KEY, alicePolkadotAddress, params.path, CHAIN_ID); + + await client.requestSignature(payload, params); + + const signature = await client.waitForSignature(requestId, 30000); + expect(signature).toBeDefined(); + expect(signature.responder).toBeTruthy(); + + console.log("\n āœ… Signature received from:", signature.responder); + + const isValid = await client.verifySignature(payload, signature.signature, derivedKey); + expect(isValid).toBe(true); + + console.log(" āœ… Signature verification PASSED"); + }); + }); + + describe("SignRespond", () => { + it("should request and verify a transaction signature", async () => { + const tx = TransactionBuilder.buildEIP1559({ + chainId: 11155111, + nonce: 0, + maxPriorityFeePerGas: BigInt("2000000000"), + maxFeePerGas: BigInt("30000000000"), + gasLimit: 10000, + to: "0x0000000000000000000000000000000000000000", + value: BigInt(0), + data: "0x", + accessList: [] + }); + + const params = { + slip44ChainId: 60, + keyVersion: 0, + path: "testPath", + schemas: { + explorer: { format: 0, schema: "{}" }, + callback: { format: 0, schema: "{}" } + } + }; + + const requestId = client.calculateSignRespondRequestId( + alicePolkadotAddress, + tx.serialized, + params + ); + + const derivedKey = KeyDerivation.derivePublicKey( + ROOT_PUBLIC_KEY, + alicePolkadotAddress, + params.path, + CHAIN_ID + ); + + await client.requestTransactionSignature(tx.serialized, params); + + const signature = await client.waitForSignature(requestId, 30000); + expect(signature).toBeDefined(); + + console.log("\n āœ… Transaction signature received from:", signature.responder); + + const isValid = await client.verifyTransactionSignature( + tx.transaction, + signature.signature, + derivedKey + ); + expect(isValid).toBe(true); + + console.log(" āœ… Transaction signature verification PASSED"); + }); + }); +}); \ No newline at end of file diff --git a/signet-ts-client-scripts/transaction-builder.ts b/signet-ts-client-scripts/transaction-builder.ts new file mode 100644 index 0000000000..353d9322b3 --- /dev/null +++ b/signet-ts-client-scripts/transaction-builder.ts @@ -0,0 +1,26 @@ +// transaction-builder.ts +import { ethers } from "ethers"; + +export class TransactionBuilder { + static buildEIP1559(params: { + chainId: number; + nonce: number; + maxPriorityFeePerGas: bigint; + maxFeePerGas: bigint; + gasLimit: number; + to: string; + value: bigint; + data: string; + accessList: any[]; + }): { transaction: ethers.Transaction; serialized: number[] } { + const transaction = ethers.Transaction.from({ + type: 2, + ...params + }); + + return { + transaction, + serialized: Array.from(ethers.getBytes(transaction.unsignedSerialized)) + }; + } +} \ No newline at end of file diff --git a/signet-ts-client-scripts/tsconfig.json b/signet-ts-client-scripts/tsconfig.json new file mode 100644 index 0000000000..5e71b205bf --- /dev/null +++ b/signet-ts-client-scripts/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "types": ["jest", "node"] + }, + "include": ["**/*.ts"], + "exclude": ["node_modules"] +} \ No newline at end of file diff --git a/signet-ts-client-scripts/yarn.lock b/signet-ts-client-scripts/yarn.lock new file mode 100644 index 0000000000..be8eed5d72 --- /dev/null +++ b/signet-ts-client-scripts/yarn.lock @@ -0,0 +1,3250 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@adraffy/ens-normalize@1.10.1": + version "1.10.1" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz#63430d04bd8c5e74f8d7d049338f1cd9d4f02069" + integrity sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw== + +"@adraffy/ens-normalize@^1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz#42cc67c5baa407ac25059fcd7d405cc5ecdb0c33" + integrity sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg== + +"@babel/code-frame@^7.0.0", "@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" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + dependencies: + "@babel/helper-validator-identifier" "^7.27.1" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/compat-data@^7.27.2": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.4.tgz#96fdf1af1b8859c8474ab39c295312bfb7c24b04" + integrity sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw== + +"@babel/core@^7.23.9", "@babel/core@^7.27.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.4.tgz#12a550b8794452df4c8b084f95003bce1742d496" + integrity sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-module-transforms" "^7.28.3" + "@babel/helpers" "^7.28.4" + "@babel/parser" "^7.28.4" + "@babel/template" "^7.27.2" + "@babel/traverse" "^7.28.4" + "@babel/types" "^7.28.4" + "@jridgewell/remapping" "^2.3.5" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.27.5", "@babel/generator@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.3.tgz#9626c1741c650cbac39121694a0f2d7451b8ef3e" + integrity sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw== + dependencies: + "@babel/parser" "^7.28.3" + "@babel/types" "^7.28.2" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + +"@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" + integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== + dependencies: + "@babel/compat-data" "^7.27.2" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@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" + 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" + integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@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" + integrity sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@babel/traverse" "^7.28.3" + +"@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" + 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" + 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" + 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" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helpers@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.4.tgz#fe07274742e95bdf7cf1443593eeb8926ab63827" + integrity sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w== + dependencies: + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.4" + +"@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.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.4.tgz#da25d4643532890932cc03f7705fe19637e03fa8" + integrity sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg== + dependencies: + "@babel/types" "^7.28.4" + +"@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" + 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" + 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" + 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" + 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" + 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" + 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" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c" + 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" + 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" + 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" + 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" + 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" + 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" + 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" + 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" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz#5147d29066a793450f220c63fa3a9431b7e6dd18" + integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/template@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" + integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" + +"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.4.tgz#8d456101b96ab175d487249f60680221692b958b" + integrity sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.4" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.4" + 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.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.4.tgz#0a4e618f4c60a7cd6c11cb2d48060e4dbe38ac3a" + integrity sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + +"@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" + 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" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@emnapi/core@^1.4.3": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.5.0.tgz#85cd84537ec989cebb2343606a1ee663ce4edaf0" + integrity sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg== + dependencies: + "@emnapi/wasi-threads" "1.1.0" + tslib "^2.4.0" + +"@emnapi/runtime@^1.4.3": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.5.0.tgz#9aebfcb9b17195dce3ab53c86787a6b7d058db73" + integrity sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ== + dependencies: + tslib "^2.4.0" + +"@emnapi/wasi-threads@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz#60b2102fddc9ccb78607e4a3cf8403ea69be41bf" + integrity sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ== + dependencies: + tslib "^2.4.0" + +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + +"@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" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@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" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@30.1.2": + version "30.1.2" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-30.1.2.tgz#3d32b966454d57874520b27647129228a654c995" + integrity sha512-BGMAxj8VRmoD0MoA/jo9alMXSRoqW8KPeqOfEo1ncxnRLatTBCpRoOwlwlEMdudp68Q6WSGwYrrLtTGOh8fLzw== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + chalk "^4.1.2" + jest-message-util "30.1.0" + jest-util "30.0.5" + slash "^3.0.0" + +"@jest/core@30.1.3": + version "30.1.3" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-30.1.3.tgz#c097dcead36ac6ccee2825a35078163465f8b79d" + integrity sha512-LIQz7NEDDO1+eyOA2ZmkiAyYvZuo6s1UxD/e2IHldR6D7UYogVq3arTmli07MkENLq6/3JEQjp0mA8rrHHJ8KQ== + dependencies: + "@jest/console" "30.1.2" + "@jest/pattern" "30.0.1" + "@jest/reporters" "30.1.3" + "@jest/test-result" "30.1.3" + "@jest/transform" "30.1.2" + "@jest/types" "30.0.5" + "@types/node" "*" + ansi-escapes "^4.3.2" + chalk "^4.1.2" + ci-info "^4.2.0" + exit-x "^0.2.2" + graceful-fs "^4.2.11" + jest-changed-files "30.0.5" + jest-config "30.1.3" + jest-haste-map "30.1.0" + jest-message-util "30.1.0" + jest-regex-util "30.0.1" + jest-resolve "30.1.3" + jest-resolve-dependencies "30.1.3" + jest-runner "30.1.3" + jest-runtime "30.1.3" + jest-snapshot "30.1.2" + jest-util "30.0.5" + jest-validate "30.1.0" + jest-watcher "30.1.3" + micromatch "^4.0.8" + pretty-format "30.0.5" + slash "^3.0.0" + +"@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" + integrity sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw== + +"@jest/environment@30.1.2": + version "30.1.2" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-30.1.2.tgz#f1bd73a7571f96104a3ff2007747c2ce12b5c038" + integrity sha512-N8t1Ytw4/mr9uN28OnVf0SYE2dGhaIxOVYcwsf9IInBKjvofAjbFRvedvBBlyTYk2knbJTiEjEJ2PyyDIBnd9w== + dependencies: + "@jest/fake-timers" "30.1.2" + "@jest/types" "30.0.5" + "@types/node" "*" + jest-mock "30.0.5" + +"@jest/expect-utils@30.1.2": + version "30.1.2" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.1.2.tgz#88ea18040f707c9fadb6fd9e77568cae5266cee8" + integrity sha512-HXy1qT/bfdjCv7iC336ExbqqYtZvljrV8odNdso7dWK9bSeHtLlvwWWC3YSybSPL03Gg5rug6WLCZAZFH72m0A== + dependencies: + "@jest/get-type" "30.1.0" + +"@jest/expect@30.1.2": + version "30.1.2" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-30.1.2.tgz#35283e8bd083aab6cc26d4d30aeeacb5e7190a0f" + integrity sha512-tyaIExOwQRCxPCGNC05lIjWJztDwk2gPDNSDGg1zitXJJ8dC3++G/CRjE5mb2wQsf89+lsgAgqxxNpDLiCViTA== + dependencies: + expect "30.1.2" + jest-snapshot "30.1.2" + +"@jest/fake-timers@30.1.2": + version "30.1.2" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-30.1.2.tgz#cb0df6995034d50c6973ffd3ffdaa1353a816c41" + integrity sha512-Beljfv9AYkr9K+ETX9tvV61rJTY706BhBUtiaepQHeEGfe0DbpvUA5Z3fomwc5Xkhns6NWrcFDZn+72fLieUnA== + dependencies: + "@jest/types" "30.0.5" + "@sinonjs/fake-timers" "^13.0.0" + "@types/node" "*" + jest-message-util "30.1.0" + jest-mock "30.0.5" + jest-util "30.0.5" + +"@jest/get-type@30.1.0": + version "30.1.0" + resolved "https://registry.yarnpkg.com/@jest/get-type/-/get-type-30.1.0.tgz#4fcb4dc2ebcf0811be1c04fd1cb79c2dba431cbc" + integrity sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA== + +"@jest/globals@30.1.2": + version "30.1.2" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-30.1.2.tgz#821cad7d8ef3dc145979088bb0bfbc1f81a5d8ce" + integrity sha512-teNTPZ8yZe3ahbYnvnVRDeOjr+3pu2uiAtNtrEsiMjVPPj+cXd5E/fr8BL7v/T7F31vYdEHrI5cC/2OoO/vM9A== + dependencies: + "@jest/environment" "30.1.2" + "@jest/expect" "30.1.2" + "@jest/types" "30.0.5" + jest-mock "30.0.5" + +"@jest/pattern@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/pattern/-/pattern-30.0.1.tgz#d5304147f49a052900b4b853dedb111d080e199f" + integrity sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA== + dependencies: + "@types/node" "*" + jest-regex-util "30.0.1" + +"@jest/reporters@30.1.3": + version "30.1.3" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-30.1.3.tgz#015b5838b3edf60f6e995186cd805b7fcbac86b3" + integrity sha512-VWEQmJWfXMOrzdFEOyGjUEOuVXllgZsoPtEHZzfdNz18RmzJ5nlR6kp8hDdY8dDS1yGOXAY7DHT+AOHIPSBV0w== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "30.1.2" + "@jest/test-result" "30.1.3" + "@jest/transform" "30.1.2" + "@jest/types" "30.0.5" + "@jridgewell/trace-mapping" "^0.3.25" + "@types/node" "*" + chalk "^4.1.2" + collect-v8-coverage "^1.0.2" + exit-x "^0.2.2" + glob "^10.3.10" + graceful-fs "^4.2.11" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^5.0.0" + istanbul-reports "^3.1.3" + jest-message-util "30.1.0" + jest-util "30.0.5" + jest-worker "30.1.0" + slash "^3.0.0" + string-length "^4.0.2" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-30.0.5.tgz#7bdf69fc5a368a5abdb49fd91036c55225846473" + integrity sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA== + dependencies: + "@sinclair/typebox" "^0.34.0" + +"@jest/snapshot-utils@30.1.2": + version "30.1.2" + resolved "https://registry.yarnpkg.com/@jest/snapshot-utils/-/snapshot-utils-30.1.2.tgz#320500eba29a25c33e9ec968154e521873624309" + integrity sha512-vHoMTpimcPSR7OxS2S0V1Cpg8eKDRxucHjoWl5u4RQcnxqQrV3avETiFpl8etn4dqxEGarBeHbIBety/f8mLXw== + dependencies: + "@jest/types" "30.0.5" + chalk "^4.1.2" + graceful-fs "^4.2.11" + natural-compare "^1.4.0" + +"@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" + integrity sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg== + dependencies: + "@jridgewell/trace-mapping" "^0.3.25" + callsites "^3.1.0" + graceful-fs "^4.2.11" + +"@jest/test-result@30.1.3": + version "30.1.3" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-30.1.3.tgz#69fe7ff93da8c0c47bae245727e0ce23571d058e" + integrity sha512-P9IV8T24D43cNRANPPokn7tZh0FAFnYS2HIfi5vK18CjRkTDR9Y3e1BoEcAJnl4ghZZF4Ecda4M/k41QkvurEQ== + dependencies: + "@jest/console" "30.1.2" + "@jest/types" "30.0.5" + "@types/istanbul-lib-coverage" "^2.0.6" + collect-v8-coverage "^1.0.2" + +"@jest/test-sequencer@30.1.3": + version "30.1.3" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-30.1.3.tgz#df64038d46150e704ed07c5fee4626609f518089" + integrity sha512-82J+hzC0qeQIiiZDThh+YUadvshdBswi5nuyXlEmXzrhw5ZQSRHeQ5LpVMD/xc8B3wPePvs6VMzHnntxL+4E3w== + dependencies: + "@jest/test-result" "30.1.3" + graceful-fs "^4.2.11" + jest-haste-map "30.1.0" + slash "^3.0.0" + +"@jest/transform@30.1.2": + version "30.1.2" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-30.1.2.tgz#42624a9c89f2427cd413b989aaf9f6aeb58cae56" + integrity sha512-UYYFGifSgfjujf1Cbd3iU/IQoSd6uwsj8XHj5DSDf5ERDcWMdJOPTkHWXj4U+Z/uMagyOQZ6Vne8C4nRIrCxqA== + dependencies: + "@babel/core" "^7.27.4" + "@jest/types" "30.0.5" + "@jridgewell/trace-mapping" "^0.3.25" + babel-plugin-istanbul "^7.0.0" + chalk "^4.1.2" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.11" + jest-haste-map "30.1.0" + jest-regex-util "30.0.1" + jest-util "30.0.5" + micromatch "^4.0.8" + pirates "^4.0.7" + slash "^3.0.0" + write-file-atomic "^5.0.1" + +"@jest/types@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.0.5.tgz#29a33a4c036e3904f1cfd94f6fe77f89d2e1cc05" + integrity sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ== + dependencies: + "@jest/pattern" "30.0.1" + "@jest/schemas" "30.0.5" + "@types/istanbul-lib-coverage" "^2.0.6" + "@types/istanbul-reports" "^3.0.4" + "@types/node" "*" + "@types/yargs" "^17.0.33" + chalk "^4.1.2" + +"@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" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@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" + 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" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@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" + 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.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@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/ciphers@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-1.3.0.tgz#f64b8ff886c240e644e5573c097f86e5b43676dc" + integrity sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw== + +"@noble/curves@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" + integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== + dependencies: + "@noble/hashes" "1.3.2" + +"@noble/curves@1.9.1": + version "1.9.1" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.9.1.tgz#9654a0bc6c13420ae252ddcf975eaf0f58f0a35c" + integrity sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA== + dependencies: + "@noble/hashes" "1.8.0" + +"@noble/curves@^1.3.0", "@noble/curves@~1.9.0": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.9.7.tgz#79d04b4758a43e4bca2cbdc62e7771352fa6b951" + integrity sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw== + dependencies: + "@noble/hashes" "1.8.0" + +"@noble/hashes@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== + +"@noble/hashes@1.8.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.3.3", "@noble/hashes@^1.8.0", "@noble/hashes@~1.8.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" + 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" + 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" + integrity sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA== + +"@polkadot-api/json-rpc-provider-proxy@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@polkadot-api/json-rpc-provider-proxy/-/json-rpc-provider-proxy-0.0.1.tgz#bb5c943642cdf0ec7bc48c0a2647558b9fcd7bdb" + integrity sha512-gmVDUP8LpCH0BXewbzqXF2sdHddq1H1q+XrAW2of+KZj4woQkIGBRGTJHeBEVHe30EB+UejR1N2dT4PO/RvDdg== + +"@polkadot-api/json-rpc-provider@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@polkadot-api/json-rpc-provider/-/json-rpc-provider-0.0.1.tgz#333645d40ccd9bccfd1f32503f17e4e63e76e297" + integrity sha512-/SMC/l7foRjpykLTUTacIH05H3mr9ip8b5xxfwXlVezXrNVLp3Cv0GX6uItkKd+ZjzVPf3PFrDF2B2/HLSNESA== + +"@polkadot-api/metadata-builders@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@polkadot-api/metadata-builders/-/metadata-builders-0.0.1.tgz#a76b48febef9ea72be8273d889e2677101045a05" + integrity sha512-GCI78BHDzXAF/L2pZD6Aod/yl82adqQ7ftNmKg51ixRL02JpWUA+SpUKTJE5MY1p8kiJJIo09P2um24SiJHxNA== + dependencies: + "@polkadot-api/substrate-bindings" "0.0.1" + "@polkadot-api/utils" "0.0.1" + +"@polkadot-api/observable-client@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@polkadot-api/observable-client/-/observable-client-0.1.0.tgz#472045ea06a2bc4bccdc2db5c063eadcbf6f5351" + integrity sha512-GBCGDRztKorTLna/unjl/9SWZcRmvV58o9jwU2Y038VuPXZcr01jcw/1O3x+yeAuwyGzbucI/mLTDa1QoEml3A== + dependencies: + "@polkadot-api/metadata-builders" "0.0.1" + "@polkadot-api/substrate-bindings" "0.0.1" + "@polkadot-api/substrate-client" "0.0.1" + "@polkadot-api/utils" "0.0.1" + +"@polkadot-api/substrate-bindings@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@polkadot-api/substrate-bindings/-/substrate-bindings-0.0.1.tgz#c4b7f4d6c3672d2c15cbc6c02964f014b73cbb0b" + integrity sha512-bAe7a5bOPnuFVmpv7y4BBMRpNTnMmE0jtTqRUw/+D8ZlEHNVEJQGr4wu3QQCl7k1GnSV1wfv3mzIbYjErEBocg== + dependencies: + "@noble/hashes" "^1.3.1" + "@polkadot-api/utils" "0.0.1" + "@scure/base" "^1.1.1" + scale-ts "^1.6.0" + +"@polkadot-api/substrate-client@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@polkadot-api/substrate-client/-/substrate-client-0.0.1.tgz#0e8010a0abe2fb47d6fa7ab94e45e1d0de083314" + integrity sha512-9Bg9SGc3AwE+wXONQoW8GC00N3v6lCZLW74HQzqB6ROdcm5VAHM4CB/xRzWSUF9CXL78ugiwtHx3wBcpx4H4Wg== + +"@polkadot-api/utils@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@polkadot-api/utils/-/utils-0.0.1.tgz#908b22becac705149d7cc946532143d0fb003bfc" + integrity sha512-3j+pRmlF9SgiYDabSdZsBSsN5XHbpXOAce1lWj56IEEaFZVjsiCaxDOA7C9nCcgfVXuvnbxqqEGQvnY+QfBAUw== + +"@polkadot/api-augment@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/api-augment/-/api-augment-11.3.1.tgz#c7deeac438b017613e244c25505216a9d4c3977e" + integrity sha512-Yj+6rb6h0WwY3yJ+UGhjGW+tyMRFUMsKQuGw+eFsXdjiNU9UoXsAqA2dG7Q1F+oeX/g+y2gLGBezNoCwbl6HfA== + dependencies: + "@polkadot/api-base" "11.3.1" + "@polkadot/rpc-augment" "11.3.1" + "@polkadot/types" "11.3.1" + "@polkadot/types-augment" "11.3.1" + "@polkadot/types-codec" "11.3.1" + "@polkadot/util" "^12.6.2" + tslib "^2.6.2" + +"@polkadot/api-base@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/api-base/-/api-base-11.3.1.tgz#6c74c88d4a4b3d22344bb8715a135493f5a3dd33" + integrity sha512-b8UkNL00NN7+3QaLCwL5cKg+7YchHoKCAhwKusWHNBZkkO6Oo2BWilu0dZkPJOyqV9P389Kbd9+oH+SKs9u2VQ== + dependencies: + "@polkadot/rpc-core" "11.3.1" + "@polkadot/types" "11.3.1" + "@polkadot/util" "^12.6.2" + rxjs "^7.8.1" + tslib "^2.6.2" + +"@polkadot/api-derive@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/api-derive/-/api-derive-11.3.1.tgz#3617655b6dab56d5beb8efbf7383ab457370df35" + integrity sha512-9dopzrh4cRuft1nANmBvMY/hEhFDu0VICMTOGxQLOl8NMfcOFPTLAN0JhSBUoicGZhV+c4vpv01NBx/7/IL1HA== + dependencies: + "@polkadot/api" "11.3.1" + "@polkadot/api-augment" "11.3.1" + "@polkadot/api-base" "11.3.1" + "@polkadot/rpc-core" "11.3.1" + "@polkadot/types" "11.3.1" + "@polkadot/types-codec" "11.3.1" + "@polkadot/util" "^12.6.2" + "@polkadot/util-crypto" "^12.6.2" + rxjs "^7.8.1" + tslib "^2.6.2" + +"@polkadot/api@11.3.1", "@polkadot/api@^11.2.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-11.3.1.tgz#6092aea8147ea03873b3f383cceae0390a67f71d" + integrity sha512-q4kFIIHTLvKxM24b0Eo8hJevsPMme+aITJGrDML9BgdZYTRN14+cu5nXiCsQvaEamdyYj+uCXWe2OV9X7pPxsA== + dependencies: + "@polkadot/api-augment" "11.3.1" + "@polkadot/api-base" "11.3.1" + "@polkadot/api-derive" "11.3.1" + "@polkadot/keyring" "^12.6.2" + "@polkadot/rpc-augment" "11.3.1" + "@polkadot/rpc-core" "11.3.1" + "@polkadot/rpc-provider" "11.3.1" + "@polkadot/types" "11.3.1" + "@polkadot/types-augment" "11.3.1" + "@polkadot/types-codec" "11.3.1" + "@polkadot/types-create" "11.3.1" + "@polkadot/types-known" "11.3.1" + "@polkadot/util" "^12.6.2" + "@polkadot/util-crypto" "^12.6.2" + eventemitter3 "^5.0.1" + rxjs "^7.8.1" + tslib "^2.6.2" + +"@polkadot/keyring@^12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/keyring/-/keyring-12.6.2.tgz#6067e6294fee23728b008ac116e7e9db05cecb9b" + integrity sha512-O3Q7GVmRYm8q7HuB3S0+Yf/q/EB2egKRRU3fv9b3B7V+A52tKzA+vIwEmNVaD1g5FKW9oB97rmpggs0zaKFqHw== + dependencies: + "@polkadot/util" "12.6.2" + "@polkadot/util-crypto" "12.6.2" + tslib "^2.6.2" + +"@polkadot/networks@12.6.2", "@polkadot/networks@^12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/networks/-/networks-12.6.2.tgz#791779fee1d86cc5b6cd371858eea9b7c3f8720d" + integrity sha512-1oWtZm1IvPWqvMrldVH6NI2gBoCndl5GEwx7lAuQWGr7eNL+6Bdc5K3Z9T0MzFvDGoi2/CBqjX9dRKo39pDC/w== + dependencies: + "@polkadot/util" "12.6.2" + "@substrate/ss58-registry" "^1.44.0" + tslib "^2.6.2" + +"@polkadot/rpc-augment@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-augment/-/rpc-augment-11.3.1.tgz#b589ef5b7ab578cf274077604543071ce9889301" + integrity sha512-2PaDcKNju4QYQpxwVkWbRU3M0t340nMX9cMo+8awgvgL1LliV/fUDZueMKLuSS910JJMTPQ7y2pK4eQgMt08gQ== + dependencies: + "@polkadot/rpc-core" "11.3.1" + "@polkadot/types" "11.3.1" + "@polkadot/types-codec" "11.3.1" + "@polkadot/util" "^12.6.2" + tslib "^2.6.2" + +"@polkadot/rpc-core@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-core/-/rpc-core-11.3.1.tgz#c23e23ee5c403c4edb207d603ae4dc16e69bc710" + integrity sha512-KKNepsDd/mpmXcA6v/h14eFFPEzLGd7nrvx2UUXUxoZ0Fq2MH1hplP3s93k1oduNY/vOXJR2K9S4dKManA6GVQ== + dependencies: + "@polkadot/rpc-augment" "11.3.1" + "@polkadot/rpc-provider" "11.3.1" + "@polkadot/types" "11.3.1" + "@polkadot/util" "^12.6.2" + rxjs "^7.8.1" + tslib "^2.6.2" + +"@polkadot/rpc-provider@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-provider/-/rpc-provider-11.3.1.tgz#1d1289bf42d065b5f04f9baa46ef90d96940819e" + integrity sha512-pqERChoHo45hd3WAgW8UuzarRF+G/o/eXEbl0PXLubiayw4X4qCmIzmtntUcKYgxGNcYGZaG87ZU8OjN97m6UA== + dependencies: + "@polkadot/keyring" "^12.6.2" + "@polkadot/types" "11.3.1" + "@polkadot/types-support" "11.3.1" + "@polkadot/util" "^12.6.2" + "@polkadot/util-crypto" "^12.6.2" + "@polkadot/x-fetch" "^12.6.2" + "@polkadot/x-global" "^12.6.2" + "@polkadot/x-ws" "^12.6.2" + eventemitter3 "^5.0.1" + mock-socket "^9.3.1" + nock "^13.5.0" + tslib "^2.6.2" + optionalDependencies: + "@substrate/connect" "0.8.10" + +"@polkadot/types-augment@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-augment/-/types-augment-11.3.1.tgz#1f7f553f0ca6eb8fbc0306901edc045fe18729e1" + integrity sha512-eR3HVpvUmB3v7q2jTWVmVfAVfb1/kuNn7ij94Zqadg/fuUq0pKqIOKwkUj3OxRM3A/5BnW3MbgparjKD3r+fyw== + dependencies: + "@polkadot/types" "11.3.1" + "@polkadot/types-codec" "11.3.1" + "@polkadot/util" "^12.6.2" + tslib "^2.6.2" + +"@polkadot/types-codec@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-codec/-/types-codec-11.3.1.tgz#2767cf482cd49afdd5dce9701615f68ec59cec5e" + integrity sha512-i7IiiuuL+Z/jFoKTA9xeh4wGQnhnNNjMT0+1ohvlOvnFsoKZKFQQOaDPPntGJVL1JDCV+KjkN2uQKZSeW8tguQ== + dependencies: + "@polkadot/util" "^12.6.2" + "@polkadot/x-bigint" "^12.6.2" + tslib "^2.6.2" + +"@polkadot/types-create@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-create/-/types-create-11.3.1.tgz#3ac2c8283f61555f9e572ca30e3485b95a0a54e2" + integrity sha512-pBXtpz5FehcRJ6j5MzFUIUN8ZWM7z6HbqK1GxBmYbJVRElcGcOg7a/rL2pQVphU0Rx1E8bSO4thzGf4wUxSX7w== + dependencies: + "@polkadot/types-codec" "11.3.1" + "@polkadot/util" "^12.6.2" + tslib "^2.6.2" + +"@polkadot/types-known@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-known/-/types-known-11.3.1.tgz#fc34ed29ac2474db6b66805a15d12008226346bb" + integrity sha512-3BIof7u6tn9bk3ZCIxA07iNoQ3uj4+vn3DTOjCKECozkRlt6V+kWRvqh16Hc0SHMg/QjcMb2fIu/WZhka1McUQ== + dependencies: + "@polkadot/networks" "^12.6.2" + "@polkadot/types" "11.3.1" + "@polkadot/types-codec" "11.3.1" + "@polkadot/types-create" "11.3.1" + "@polkadot/util" "^12.6.2" + tslib "^2.6.2" + +"@polkadot/types-support@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-support/-/types-support-11.3.1.tgz#dee02a67784baa13177fe9957f5d8d62e8a7e570" + integrity sha512-jTFz1GKyF7nI29yIOq4v0NiWTOf5yX4HahJNeFD8TcxoLhF+6tH/XXqrUXJEfbaTlSrRWiW1LZYlb+snctqKHA== + dependencies: + "@polkadot/util" "^12.6.2" + tslib "^2.6.2" + +"@polkadot/types@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/types/-/types-11.3.1.tgz#bab61b701218158099e3f548d20efc27cbf1287f" + integrity sha512-5c7uRFXQTT11Awi6T0yFIdAfD6xGDAOz06Kp7M5S9OGNZY28wSPk5x6BYfNphWPaIBmHHewYJB5qmnrdYQAWKQ== + dependencies: + "@polkadot/keyring" "^12.6.2" + "@polkadot/types-augment" "11.3.1" + "@polkadot/types-codec" "11.3.1" + "@polkadot/types-create" "11.3.1" + "@polkadot/util" "^12.6.2" + "@polkadot/util-crypto" "^12.6.2" + rxjs "^7.8.1" + tslib "^2.6.2" + +"@polkadot/util-crypto@12.6.2", "@polkadot/util-crypto@^12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/util-crypto/-/util-crypto-12.6.2.tgz#d2d51010e8e8ca88951b7d864add797dad18bbfc" + integrity sha512-FEWI/dJ7wDMNN1WOzZAjQoIcCP/3vz3wvAp5QQm+lOrzOLj0iDmaIGIcBkz8HVm3ErfSe/uKP0KS4jgV/ib+Mg== + dependencies: + "@noble/curves" "^1.3.0" + "@noble/hashes" "^1.3.3" + "@polkadot/networks" "12.6.2" + "@polkadot/util" "12.6.2" + "@polkadot/wasm-crypto" "^7.3.2" + "@polkadot/wasm-util" "^7.3.2" + "@polkadot/x-bigint" "12.6.2" + "@polkadot/x-randomvalues" "12.6.2" + "@scure/base" "^1.1.5" + tslib "^2.6.2" + +"@polkadot/util@12.6.2", "@polkadot/util@^12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/util/-/util-12.6.2.tgz#9396eff491221e1f0fd28feac55fc16ecd61a8dc" + integrity sha512-l8TubR7CLEY47240uki0TQzFvtnxFIO7uI/0GoWzpYD/O62EIAMRsuY01N4DuwgKq2ZWD59WhzsLYmA5K6ksdw== + dependencies: + "@polkadot/x-bigint" "12.6.2" + "@polkadot/x-global" "12.6.2" + "@polkadot/x-textdecoder" "12.6.2" + "@polkadot/x-textencoder" "12.6.2" + "@types/bn.js" "^5.1.5" + bn.js "^5.2.1" + tslib "^2.6.2" + +"@polkadot/wasm-bridge@7.5.1": + version "7.5.1" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-bridge/-/wasm-bridge-7.5.1.tgz#f738858213a8a599ae8bf6a6c179b325dcf091f4" + integrity sha512-E+N3CSnX3YaXpAmfIQ+4bTyiAqJQKvVcMaXjkuL8Tp2zYffClWLG5e+RY15Uh+EWfUl9If4y6cLZi3D5NcpAGQ== + dependencies: + "@polkadot/wasm-util" "7.5.1" + tslib "^2.7.0" + +"@polkadot/wasm-crypto-asmjs@7.5.1": + version "7.5.1" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-7.5.1.tgz#87e07aa340249d5c978cd03eb58b395563066a4c" + integrity sha512-jAg7Uusk+xeHQ+QHEH4c/N3b1kEGBqZb51cWe+yM61kKpQwVGZhNdlWetW6U23t/BMyZArIWMsZqmK/Ij0PHog== + dependencies: + tslib "^2.7.0" + +"@polkadot/wasm-crypto-init@7.5.1": + version "7.5.1" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto-init/-/wasm-crypto-init-7.5.1.tgz#0434850b7f05619ff312d5cbfd33629a54f9b31a" + integrity sha512-Obu4ZEo5jYO6sN31eqCNOXo88rPVkP9TrUOyynuFCnXnXr8V/HlmY/YkAd9F87chZnkTJRlzak17kIWr+i7w3A== + dependencies: + "@polkadot/wasm-bridge" "7.5.1" + "@polkadot/wasm-crypto-asmjs" "7.5.1" + "@polkadot/wasm-crypto-wasm" "7.5.1" + "@polkadot/wasm-util" "7.5.1" + tslib "^2.7.0" + +"@polkadot/wasm-crypto-wasm@7.5.1": + version "7.5.1" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-7.5.1.tgz#b3996007875db6945d29f94f4d4719fce2b3bb8f" + integrity sha512-S2yQSGbOGTcaV6UdipFVyEGanJvG6uD6Tg7XubxpiGbNAblsyYKeFcxyH1qCosk/4qf+GIUwlOL4ydhosZflqg== + dependencies: + "@polkadot/wasm-util" "7.5.1" + tslib "^2.7.0" + +"@polkadot/wasm-crypto@^7.3.2": + version "7.5.1" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto/-/wasm-crypto-7.5.1.tgz#324ebf9a86a30fd19bf4b02a6582367bdddb62c9" + integrity sha512-acjt4VJ3w19v7b/SIPsV/5k9s6JsragHKPnwoZ0KTfBvAFXwzz80jUzVGxA06SKHacfCUe7vBRlz7M5oRby1Pw== + dependencies: + "@polkadot/wasm-bridge" "7.5.1" + "@polkadot/wasm-crypto-asmjs" "7.5.1" + "@polkadot/wasm-crypto-init" "7.5.1" + "@polkadot/wasm-crypto-wasm" "7.5.1" + "@polkadot/wasm-util" "7.5.1" + tslib "^2.7.0" + +"@polkadot/wasm-util@7.5.1", "@polkadot/wasm-util@^7.3.2": + version "7.5.1" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-util/-/wasm-util-7.5.1.tgz#4568a9bf8d02d2d68fc139f331719865300e5233" + integrity sha512-sbvu71isFhPXpvMVX+EkRnUg/+54Tx7Sf9BEMqxxoPj7cG1I/MKeDEwbQz6MaU4gm7xJqvEWCAemLFcXfHQ/2A== + dependencies: + tslib "^2.7.0" + +"@polkadot/x-bigint@12.6.2", "@polkadot/x-bigint@^12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-bigint/-/x-bigint-12.6.2.tgz#59b7a615f205ae65e1ac67194aefde94d3344580" + integrity sha512-HSIk60uFPX4GOFZSnIF7VYJz7WZA7tpFJsne7SzxOooRwMTWEtw3fUpFy5cYYOeLh17/kHH1Y7SVcuxzVLc74Q== + dependencies: + "@polkadot/x-global" "12.6.2" + tslib "^2.6.2" + +"@polkadot/x-fetch@^12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-fetch/-/x-fetch-12.6.2.tgz#b1bca028db90263bafbad2636c18d838d842d439" + integrity sha512-8wM/Z9JJPWN1pzSpU7XxTI1ldj/AfC8hKioBlUahZ8gUiJaOF7K9XEFCrCDLis/A1BoOu7Ne6WMx/vsJJIbDWw== + dependencies: + "@polkadot/x-global" "12.6.2" + node-fetch "^3.3.2" + tslib "^2.6.2" + +"@polkadot/x-global@12.6.2", "@polkadot/x-global@^12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-global/-/x-global-12.6.2.tgz#31d4de1c3d4c44e4be3219555a6d91091decc4ec" + integrity sha512-a8d6m+PW98jmsYDtAWp88qS4dl8DyqUBsd0S+WgyfSMtpEXu6v9nXDgPZgwF5xdDvXhm+P0ZfVkVTnIGrScb5g== + dependencies: + tslib "^2.6.2" + +"@polkadot/x-randomvalues@12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-randomvalues/-/x-randomvalues-12.6.2.tgz#13fe3619368b8bf5cb73781554859b5ff9d900a2" + integrity sha512-Vr8uG7rH2IcNJwtyf5ebdODMcr0XjoCpUbI91Zv6AlKVYOGKZlKLYJHIwpTaKKB+7KPWyQrk4Mlym/rS7v9feg== + dependencies: + "@polkadot/x-global" "12.6.2" + tslib "^2.6.2" + +"@polkadot/x-textdecoder@12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-textdecoder/-/x-textdecoder-12.6.2.tgz#b86da0f8e8178f1ca31a7158257e92aea90b10e4" + integrity sha512-M1Bir7tYvNappfpFWXOJcnxUhBUFWkUFIdJSyH0zs5LmFtFdbKAeiDXxSp2Swp5ddOZdZgPac294/o2TnQKN1w== + dependencies: + "@polkadot/x-global" "12.6.2" + tslib "^2.6.2" + +"@polkadot/x-textencoder@12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-textencoder/-/x-textencoder-12.6.2.tgz#81d23bd904a2c36137a395c865c5fefa21abfb44" + integrity sha512-4N+3UVCpI489tUJ6cv3uf0PjOHvgGp9Dl+SZRLgFGt9mvxnvpW/7+XBADRMtlG4xi5gaRK7bgl5bmY6OMDsNdw== + dependencies: + "@polkadot/x-global" "12.6.2" + tslib "^2.6.2" + +"@polkadot/x-ws@^12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-ws/-/x-ws-12.6.2.tgz#b99094d8e53a03be1de903d13ba59adaaabc767a" + integrity sha512-cGZWo7K5eRRQCRl2LrcyCYsrc3lRbTlixZh3AzgU8uX4wASVGRlNWi/Hf4TtHNe1ExCDmxabJzdIsABIfrr7xw== + dependencies: + "@polkadot/x-global" "12.6.2" + tslib "^2.6.2" + ws "^8.15.1" + +"@scure/base@^1.1.1", "@scure/base@^1.1.5", "@scure/base@~1.2.5": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.2.6.tgz#ca917184b8231394dd8847509c67a0be522e59f6" + integrity sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg== + +"@scure/bip32@1.7.0", "@scure/bip32@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.7.0.tgz#b8683bab172369f988f1589640e53c4606984219" + integrity sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw== + dependencies: + "@noble/curves" "~1.9.0" + "@noble/hashes" "~1.8.0" + "@scure/base" "~1.2.5" + +"@scure/bip39@1.6.0", "@scure/bip39@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.6.0.tgz#475970ace440d7be87a6086cbee77cb8f1a684f9" + integrity sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A== + dependencies: + "@noble/hashes" "~1.8.0" + "@scure/base" "~1.2.5" + +"@sinclair/typebox@^0.34.0": + version "0.34.41" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.41.tgz#aa51a6c1946df2c5a11494a2cdb9318e026db16c" + integrity sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g== + +"@sinonjs/commons@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@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" + integrity sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw== + dependencies: + "@sinonjs/commons" "^3.0.1" + +"@substrate/connect-extension-protocol@^2.0.0": + version "2.2.2" + resolved "https://registry.yarnpkg.com/@substrate/connect-extension-protocol/-/connect-extension-protocol-2.2.2.tgz#2cf8f2eaf1879308d307a1a08df83cd5db918fe0" + integrity sha512-t66jwrXA0s5Goq82ZtjagLNd7DPGCNjHeehRlE/gcJmJ+G56C0W+2plqOMRicJ8XGR1/YFnUSEqUFiSNbjGrAA== + +"@substrate/connect-known-chains@^1.1.4": + version "1.10.3" + resolved "https://registry.yarnpkg.com/@substrate/connect-known-chains/-/connect-known-chains-1.10.3.tgz#71a89864f13626c412fa0a9d0ffc4f6ca39fdcec" + integrity sha512-OJEZO1Pagtb6bNE3wCikc2wrmvEU5x7GxFFLqqbz1AJYYxSlrPCGu4N2og5YTExo4IcloNMQYFRkBGue0BKZ4w== + +"@substrate/connect@0.8.10": + version "0.8.10" + resolved "https://registry.yarnpkg.com/@substrate/connect/-/connect-0.8.10.tgz#810b6589f848828aa840c731a1f36b84fe0e5956" + integrity sha512-DIyQ13DDlXqVFnLV+S6/JDgiGowVRRrh18kahieJxhgvzcWicw5eLc6jpfQ0moVVLBYkO7rctB5Wreldwpva8w== + dependencies: + "@substrate/connect-extension-protocol" "^2.0.0" + "@substrate/connect-known-chains" "^1.1.4" + "@substrate/light-client-extension-helpers" "^0.0.6" + smoldot "2.0.22" + +"@substrate/light-client-extension-helpers@^0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@substrate/light-client-extension-helpers/-/light-client-extension-helpers-0.0.6.tgz#bec1c7997241226db50b44ad85a992b4348d21c3" + integrity sha512-girltEuxQ1BvkJWmc8JJlk4ZxnlGXc/wkLcNguhY+UoDEMBK0LsdtfzQKIfrIehi4QdeSBlFEFBoI4RqPmsZzA== + dependencies: + "@polkadot-api/json-rpc-provider" "0.0.1" + "@polkadot-api/json-rpc-provider-proxy" "0.0.1" + "@polkadot-api/observable-client" "0.1.0" + "@polkadot-api/substrate-client" "0.0.1" + "@substrate/connect-extension-protocol" "^2.0.0" + "@substrate/connect-known-chains" "^1.1.4" + rxjs "^7.8.1" + +"@substrate/ss58-registry@^1.44.0": + version "1.51.0" + resolved "https://registry.yarnpkg.com/@substrate/ss58-registry/-/ss58-registry-1.51.0.tgz#39e0341eb4069c2d3e684b93f0d8cb0bec572383" + integrity sha512-TWDurLiPxndFgKjVavCniytBIw+t4ViOi7TYp9h/D0NMmkEc9klFTo+827eyEJ0lELpqO207Ey7uGxUa+BS1jQ== + +"@tsconfig/node10@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + 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" + 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" + 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" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@tybys/wasm-util@^0.10.0": + version "0.10.1" + resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz#ecddd3205cf1e2d5274649ff0eedd2991ed7f414" + integrity sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg== + dependencies: + tslib "^2.4.0" + +"@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" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" + 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" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74" + integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== + dependencies: + "@babel/types" "^7.28.2" + +"@types/bn.js@*", "@types/bn.js@^5.1.5": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.2.0.tgz#4349b9710e98f9ab3cdc50f1c5e4dcbd8ef29c80" + integrity sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q== + dependencies: + "@types/node" "*" + +"@types/elliptic@^6.4.18": + version "6.4.18" + resolved "https://registry.yarnpkg.com/@types/elliptic/-/elliptic-6.4.18.tgz#bc96e26e1ccccbabe8b6f0e409c85898635482e1" + integrity sha512-UseG6H5vjRiNpQvrhy4VF/JXdA3V/Fp5amvveaL+fs28BZ6xIKJBPnUPRlEaZpysD9MbpfaLi8lbl7PGUAkpWw== + dependencies: + "@types/bn.js" "*" + +"@types/istanbul-lib-coverage@*", "@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" + 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" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@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" + 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" + integrity sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA== + dependencies: + expect "^30.0.0" + pretty-format "^30.0.0" + +"@types/mocha@^10.0.10": + version "10.0.10" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.10.tgz#91f62905e8d23cbd66225312f239454a23bebfa0" + integrity sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q== + +"@types/node@*": + version "24.4.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-24.4.0.tgz#4ca9168c016a55ab15b7765ad1674ab807489600" + integrity sha512-gUuVEAK4/u6F9wRLznPUU4WGUacSEBDPoC2TrBkw3GAnOLHBL45QdfHOXp1kJ4ypBGLxTOB+t7NJLpKoC3gznQ== + dependencies: + undici-types "~7.11.0" + +"@types/node@22.7.5": + version "22.7.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.5.tgz#cfde981727a7ab3611a481510b473ae54442b92b" + integrity sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ== + dependencies: + undici-types "~6.19.2" + +"@types/node@^20.11.5": + version "20.19.17" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.17.tgz#41b52697373aef8a43b3b92f33b43f329b2d674b" + integrity sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ== + dependencies: + undici-types "~6.21.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" + 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" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.33": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + 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" + 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" + 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" + 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== + +abitype@1.1.0, abitype@^1.0.9: + version "1.1.0" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.1.0.tgz#510c5b3f92901877977af5e864841f443bf55406" + integrity sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A== + +acorn-walk@^8.1.1: + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + 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" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== + +aes-js@4.0.0-beta.5: + version "4.0.0-beta.5" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-4.0.0-beta.5.tgz#8d2452c52adedebc3a3e28465d858c11ca315873" + integrity sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q== + +ansi-escapes@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + 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" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.2.2" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.2.2.tgz#60216eea464d864597ce2832000738a0589650c1" + integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== + +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" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +ansi-styles@^6.1.0: + version "6.2.3" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.3.tgz#c044d5dcc521a076413472597a1acb1f103c4041" + integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== + +anymatch@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +babel-jest@30.1.2: + version "30.1.2" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-30.1.2.tgz#decd53b3a0cafca49443f93fb7a2c0fba55510da" + integrity sha512-IQCus1rt9kaSh7PQxLYRY5NmkNrNlU2TpabzwV7T2jljnpdHOcmnYYv8QmE04Li4S3a2Lj8/yXyET5pBarPr6g== + dependencies: + "@jest/transform" "30.1.2" + "@types/babel__core" "^7.20.5" + babel-plugin-istanbul "^7.0.0" + babel-preset-jest "30.0.1" + chalk "^4.1.2" + graceful-fs "^4.2.11" + slash "^3.0.0" + +babel-plugin-istanbul@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz#d8b518c8ea199364cf84ccc82de89740236daf92" + integrity sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-instrument "^6.0.2" + test-exclude "^6.0.0" + +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" + 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: + version "1.2.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz#20730d6cdc7dda5d89401cab10ac6a32067acde6" + integrity sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + +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" + integrity sha512-+YHejD5iTWI46cZmcc/YtX4gaKBtdqCHCVfuVinizVpbmyjO3zYmeuyFdfA8duRqQZfgCAMlsfmkVbJ+e2MAJw== + dependencies: + babel-plugin-jest-hoist "30.0.1" + babel-preset-current-node-syntax "^1.1.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +baseline-browser-mapping@^2.8.3: + version "2.8.6" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.6.tgz#c37dea4291ed8d01682f85661dbe87967028642e" + integrity sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw== + +bn.js@^4.11.9: + version "4.12.2" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.2.tgz#3d8fed6796c24e177737f7cc5172ee04ef39ec99" + integrity sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw== + +bn.js@^5.2.1: + version "5.2.2" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.2.tgz#82c09f9ebbb17107cd72cb7fd39bd1f9d0aaa566" + 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" + integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" + 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" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +brorand@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== + +browserslist@^4.24.0: + version "4.26.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.26.2.tgz#7db3b3577ec97f1140a52db4936654911078cef3" + integrity sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A== + dependencies: + baseline-browser-mapping "^2.8.3" + caniuse-lite "^1.0.30001741" + electron-to-chromium "^1.5.218" + node-releases "^2.0.21" + update-browserslist-db "^1.1.3" + +bs-logger@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + 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" + 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" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +callsites@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + 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" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001741: + version "1.0.30001743" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz#50ff91a991220a1ee2df5af00650dd5c308ea7cd" + integrity sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw== + +chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +ci-info@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.3.0.tgz#c39b1013f8fdbd28cd78e62318357d02da160cd7" + integrity sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ== + +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" + integrity sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +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" + 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" + 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" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + 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" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + 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" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +data-uri-to-buffer@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" + integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +dedent@^1.6.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.7.0.tgz#c1f9445335f0175a96587be245a282ff451446ca" + integrity sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ== + +deepmerge@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +detect-newline@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +electron-to-chromium@^1.5.218: + version "1.5.222" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.222.tgz#965c93783ad989116b74593ae3068b9466fdb237" + integrity sha512-gA7psSwSwQRE60CEoLz6JBCQPIxNeuzB2nL8vE03GK/OHxlvykbLyeiumQy1iH5C2f3YbRAZpGCMT12a/9ih9w== + +elliptic@^6.6.1: + version "6.6.1" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.6.1.tgz#3b8ffb02670bf69e382c7f65bf524c97c5405c06" + integrity sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + 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" + 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" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +error-ex@^1.3.1: + version "1.3.4" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.4.tgz#b3a8d8bb6f92eecc1629e3e27d3c8607a8a32414" + integrity sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ== + dependencies: + is-arrayish "^0.2.1" + +escalade@^3.1.1, escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +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" + 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" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +ethers@^6.15.0: + version "6.15.0" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.15.0.tgz#2980f2a3baf0509749b7e21f8692fa8a8349c0e3" + integrity sha512-Kf/3ZW54L4UT0pZtsY/rf+EkBU7Qi5nnhonjUb8yTXcxH3cdcWrV2cRyk0Xk/4jK6OoHhxxZHriyhje20If2hQ== + dependencies: + "@adraffy/ens-normalize" "1.10.1" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@types/node" "22.7.5" + aes-js "4.0.0-beta.5" + tslib "2.7.0" + ws "8.17.1" + +eventemitter3@5.0.1, eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + +execa@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit-x@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/exit-x/-/exit-x-0.2.2.tgz#1f9052de3b8d99a696b10dad5bced9bdd5c3aa64" + integrity sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ== + +expect@30.1.2, expect@^30.0.0: + version "30.1.2" + resolved "https://registry.yarnpkg.com/expect/-/expect-30.1.2.tgz#094909c2443f76b9e208fafac4a315aaaf924580" + integrity sha512-xvHszRavo28ejws8FpemjhwswGj4w/BetHIL8cU49u4sGyXDw2+p3YbeDbj6xzlxi6kWTjIRSTJ+9sNXPnF0Zg== + dependencies: + "@jest/expect-utils" "30.1.2" + "@jest/get-type" "30.1.0" + jest-matcher-utils "30.1.2" + jest-message-util "30.1.0" + jest-mock "30.0.5" + jest-util "30.0.5" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fb-watchman@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" + integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + 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" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +foreground-child@^3.1.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" + integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== + dependencies: + cross-spawn "^7.0.6" + signal-exit "^4.0.1" + +formdata-polyfill@^4.0.10: + version "4.0.10" + resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + dependencies: + fetch-blob "^3.1.2" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + 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== + +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" + 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" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +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" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + 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" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + +glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + 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" + +graceful-fs@^4.2.11: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +handlebars@^4.7.8: + version "4.7.8" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" + integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.2" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +hmac-drbg@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + 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" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +import-local@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +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" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +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" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isows@1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.7.tgz#1c06400b7eed216fbba3bcbd68f12490fc342915" + integrity sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg== + +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" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +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" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +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" + 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@^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" + integrity sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A== + dependencies: + "@jridgewell/trace-mapping" "^0.3.23" + debug "^4.1.1" + istanbul-lib-coverage "^3.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" + integrity sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.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" + integrity sha512-bGl2Ntdx0eAwXuGpdLdVYVr5YQHnSZlQ0y9HVDu565lCUAe9sj6JOtBbMmBBikGIegne9piDDIOeiLVoqTkz4A== + dependencies: + execa "^5.1.1" + jest-util "30.0.5" + p-limit "^3.1.0" + +jest-circus@30.1.3: + version "30.1.3" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-30.1.3.tgz#7ee0089f22b2b3e72ab04aee8e037c364a6d73d1" + integrity sha512-Yf3dnhRON2GJT4RYzM89t/EXIWNxKTpWTL9BfF3+geFetWP4XSvJjiU1vrWplOiUkmq8cHLiwuhz+XuUp9DscA== + dependencies: + "@jest/environment" "30.1.2" + "@jest/expect" "30.1.2" + "@jest/test-result" "30.1.3" + "@jest/types" "30.0.5" + "@types/node" "*" + chalk "^4.1.2" + co "^4.6.0" + dedent "^1.6.0" + is-generator-fn "^2.1.0" + jest-each "30.1.0" + jest-matcher-utils "30.1.2" + jest-message-util "30.1.0" + jest-runtime "30.1.3" + jest-snapshot "30.1.2" + jest-util "30.0.5" + p-limit "^3.1.0" + pretty-format "30.0.5" + pure-rand "^7.0.0" + slash "^3.0.0" + stack-utils "^2.0.6" + +jest-cli@30.1.3: + version "30.1.3" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-30.1.3.tgz#3fb8dea88886379eb95a08f954bfc2ed17a9be4f" + integrity sha512-G8E2Ol3OKch1DEeIBl41NP7OiC6LBhfg25Btv+idcusmoUSpqUkbrneMqbW9lVpI/rCKb/uETidb7DNteheuAQ== + dependencies: + "@jest/core" "30.1.3" + "@jest/test-result" "30.1.3" + "@jest/types" "30.0.5" + chalk "^4.1.2" + exit-x "^0.2.2" + import-local "^3.2.0" + jest-config "30.1.3" + jest-util "30.0.5" + jest-validate "30.1.0" + yargs "^17.7.2" + +jest-config@30.1.3: + version "30.1.3" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-30.1.3.tgz#10bcf4cd979119bfac6a130fb79d837057ce33d4" + integrity sha512-M/f7gqdQEPgZNA181Myz+GXCe8jXcJsGjCMXUzRj22FIXsZOyHNte84e0exntOvdPaeh9tA0w+B8qlP2fAezfw== + dependencies: + "@babel/core" "^7.27.4" + "@jest/get-type" "30.1.0" + "@jest/pattern" "30.0.1" + "@jest/test-sequencer" "30.1.3" + "@jest/types" "30.0.5" + babel-jest "30.1.2" + chalk "^4.1.2" + ci-info "^4.2.0" + deepmerge "^4.3.1" + glob "^10.3.10" + graceful-fs "^4.2.11" + jest-circus "30.1.3" + jest-docblock "30.0.1" + jest-environment-node "30.1.2" + jest-regex-util "30.0.1" + jest-resolve "30.1.3" + jest-runner "30.1.3" + jest-util "30.0.5" + jest-validate "30.1.0" + micromatch "^4.0.8" + parse-json "^5.2.0" + pretty-format "30.0.5" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@30.1.2: + version "30.1.2" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.1.2.tgz#8ff4217e5b63fef49a5b37462999d8f5299a4eb4" + integrity sha512-4+prq+9J61mOVXCa4Qp8ZjavdxzrWQXrI80GNxP8f4tkI2syPuPrJgdRPZRrfUTRvIoUwcmNLbqEJy9W800+NQ== + dependencies: + "@jest/diff-sequences" "30.0.1" + "@jest/get-type" "30.1.0" + chalk "^4.1.2" + pretty-format "30.0.5" + +jest-docblock@30.0.1: + version "30.0.1" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-30.0.1.tgz#545ff59f2fa88996bd470dba7d3798a8421180b1" + integrity sha512-/vF78qn3DYphAaIc3jy4gA7XSAz167n9Bm/wn/1XhTLW7tTBIzXtCJpb/vcmc73NIIeeohCbdL94JasyXUZsGA== + dependencies: + detect-newline "^3.1.0" + +jest-each@30.1.0: + version "30.1.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-30.1.0.tgz#228756d5ea9e4dcb462fc2e90a44ec27dd482d23" + integrity sha512-A+9FKzxPluqogNahpCv04UJvcZ9B3HamqpDNWNKDjtxVRYB8xbZLFuCr8JAJFpNp83CA0anGQFlpQna9Me+/tQ== + dependencies: + "@jest/get-type" "30.1.0" + "@jest/types" "30.0.5" + chalk "^4.1.2" + jest-util "30.0.5" + pretty-format "30.0.5" + +jest-environment-node@30.1.2: + version "30.1.2" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-30.1.2.tgz#ae2f20442f8abc3c6b20120dc789fa38faff568f" + integrity sha512-w8qBiXtqGWJ9xpJIA98M0EIoq079GOQRQUyse5qg1plShUCQ0Ek1VTTcczqKrn3f24TFAgFtT+4q3aOXvjbsuA== + dependencies: + "@jest/environment" "30.1.2" + "@jest/fake-timers" "30.1.2" + "@jest/types" "30.0.5" + "@types/node" "*" + jest-mock "30.0.5" + jest-util "30.0.5" + jest-validate "30.1.0" + +jest-haste-map@30.1.0: + version "30.1.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-30.1.0.tgz#e54d84e07fac15ea3a98903b735048e36d7d2ed3" + integrity sha512-JLeM84kNjpRkggcGpQLsV7B8W4LNUWz7oDNVnY1Vjj22b5/fAb3kk3htiD+4Na8bmJmjJR7rBtS2Rmq/NEcADg== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + anymatch "^3.1.3" + fb-watchman "^2.0.2" + graceful-fs "^4.2.11" + jest-regex-util "30.0.1" + jest-util "30.0.5" + jest-worker "30.1.0" + micromatch "^4.0.8" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.3" + +jest-leak-detector@30.1.0: + version "30.1.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-30.1.0.tgz#8b86e7c5f1e3e4f2a32d930ec769103ad0985874" + integrity sha512-AoFvJzwxK+4KohH60vRuHaqXfWmeBATFZpzpmzNmYTtmRMiyGPVhkXpBqxUQunw+dQB48bDf4NpUs6ivVbRv1g== + dependencies: + "@jest/get-type" "30.1.0" + pretty-format "30.0.5" + +jest-matcher-utils@30.1.2: + version "30.1.2" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.1.2.tgz#3f1b63949f740025aff740c6c6a1b653ae370fbb" + integrity sha512-7ai16hy4rSbDjvPTuUhuV8nyPBd6EX34HkBsBcBX2lENCuAQ0qKCPb/+lt8OSWUa9WWmGYLy41PrEzkwRwoGZQ== + dependencies: + "@jest/get-type" "30.1.0" + chalk "^4.1.2" + jest-diff "30.1.2" + pretty-format "30.0.5" + +jest-message-util@30.1.0: + version "30.1.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.1.0.tgz#653a9bb1a33306eddf13455ce0666ba621b767c4" + integrity sha512-HizKDGG98cYkWmaLUHChq4iN+oCENohQLb7Z5guBPumYs+/etonmNFlg1Ps6yN9LTPyZn+M+b/9BbnHx3WTMDg== + dependencies: + "@babel/code-frame" "^7.27.1" + "@jest/types" "30.0.5" + "@types/stack-utils" "^2.0.3" + chalk "^4.1.2" + graceful-fs "^4.2.11" + micromatch "^4.0.8" + pretty-format "30.0.5" + slash "^3.0.0" + stack-utils "^2.0.6" + +jest-mock@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.0.5.tgz#ef437e89212560dd395198115550085038570bdd" + 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: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +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" + integrity sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA== + +jest-resolve-dependencies@30.1.3: + version "30.1.3" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-30.1.3.tgz#04bbe95c9f4af51046dde940698d7121b49d0167" + integrity sha512-DNfq3WGmuRyHRHfEet+Zm3QOmVFtIarUOQHHryKPc0YL9ROfgWZxl4+aZq/VAzok2SS3gZdniP+dO4zgo59hBg== + dependencies: + jest-regex-util "30.0.1" + jest-snapshot "30.1.2" + +jest-resolve@30.1.3: + version "30.1.3" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-30.1.3.tgz#cc1019b28374ca7bcf7e58d57a4300449f390ec5" + integrity sha512-DI4PtTqzw9GwELFS41sdMK32Ajp3XZQ8iygeDMWkxlRhm7uUTOFSZFVZABFuxr0jvspn8MAYy54NxZCsuCTSOw== + dependencies: + chalk "^4.1.2" + graceful-fs "^4.2.11" + jest-haste-map "30.1.0" + jest-pnp-resolver "^1.2.3" + jest-util "30.0.5" + jest-validate "30.1.0" + slash "^3.0.0" + unrs-resolver "^1.7.11" + +jest-runner@30.1.3: + version "30.1.3" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-30.1.3.tgz#3253a0faab8f404aa9e0010911e8acbaf220865b" + integrity sha512-dd1ORcxQraW44Uz029TtXj85W11yvLpDuIzNOlofrC8GN+SgDlgY4BvyxJiVeuabA1t6idjNbX59jLd2oplOGQ== + dependencies: + "@jest/console" "30.1.2" + "@jest/environment" "30.1.2" + "@jest/test-result" "30.1.3" + "@jest/transform" "30.1.2" + "@jest/types" "30.0.5" + "@types/node" "*" + chalk "^4.1.2" + emittery "^0.13.1" + exit-x "^0.2.2" + graceful-fs "^4.2.11" + jest-docblock "30.0.1" + jest-environment-node "30.1.2" + jest-haste-map "30.1.0" + jest-leak-detector "30.1.0" + jest-message-util "30.1.0" + jest-resolve "30.1.3" + jest-runtime "30.1.3" + jest-util "30.0.5" + jest-watcher "30.1.3" + jest-worker "30.1.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@30.1.3: + version "30.1.3" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-30.1.3.tgz#bca7cb48d53c5b5ae21399e7a65e21271f500004" + integrity sha512-WS8xgjuNSphdIGnleQcJ3AKE4tBKOVP+tKhCD0u+Tb2sBmsU8DxfbBpZX7//+XOz81zVs4eFpJQwBNji2Y07DA== + dependencies: + "@jest/environment" "30.1.2" + "@jest/fake-timers" "30.1.2" + "@jest/globals" "30.1.2" + "@jest/source-map" "30.0.1" + "@jest/test-result" "30.1.3" + "@jest/transform" "30.1.2" + "@jest/types" "30.0.5" + "@types/node" "*" + chalk "^4.1.2" + cjs-module-lexer "^2.1.0" + collect-v8-coverage "^1.0.2" + glob "^10.3.10" + graceful-fs "^4.2.11" + jest-haste-map "30.1.0" + jest-message-util "30.1.0" + jest-mock "30.0.5" + jest-regex-util "30.0.1" + jest-resolve "30.1.3" + jest-snapshot "30.1.2" + jest-util "30.0.5" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@30.1.2: + version "30.1.2" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-30.1.2.tgz#4001a94d8394bb077a1c96246f0107c81aba4f12" + integrity sha512-4q4+6+1c8B6Cy5pGgFvjDy/Pa6VYRiGu0yQafKkJ9u6wQx4G5PqI2QR6nxTl43yy7IWsINwz6oT4o6tD12a8Dg== + dependencies: + "@babel/core" "^7.27.4" + "@babel/generator" "^7.27.5" + "@babel/plugin-syntax-jsx" "^7.27.1" + "@babel/plugin-syntax-typescript" "^7.27.1" + "@babel/types" "^7.27.3" + "@jest/expect-utils" "30.1.2" + "@jest/get-type" "30.1.0" + "@jest/snapshot-utils" "30.1.2" + "@jest/transform" "30.1.2" + "@jest/types" "30.0.5" + babel-preset-current-node-syntax "^1.1.0" + chalk "^4.1.2" + expect "30.1.2" + graceful-fs "^4.2.11" + jest-diff "30.1.2" + jest-matcher-utils "30.1.2" + jest-message-util "30.1.0" + jest-util "30.0.5" + pretty-format "30.0.5" + semver "^7.7.2" + synckit "^0.11.8" + +jest-util@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.0.5.tgz#035d380c660ad5f1748dff71c4105338e05f8669" + integrity sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + chalk "^4.1.2" + ci-info "^4.2.0" + graceful-fs "^4.2.11" + picomatch "^4.0.2" + +jest-validate@30.1.0: + version "30.1.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-30.1.0.tgz#585aae6c9ee1ac138dbacbece8a7838ca7773e60" + integrity sha512-7P3ZlCFW/vhfQ8pE7zW6Oi4EzvuB4sgR72Q1INfW9m0FGo0GADYlPwIkf4CyPq7wq85g+kPMtPOHNAdWHeBOaA== + dependencies: + "@jest/get-type" "30.1.0" + "@jest/types" "30.0.5" + camelcase "^6.3.0" + chalk "^4.1.2" + leven "^3.1.0" + pretty-format "30.0.5" + +jest-watcher@30.1.3: + version "30.1.3" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-30.1.3.tgz#2f381da5c2c76a46c46ba2108e6607c585421dc0" + integrity sha512-6jQUZCP1BTL2gvG9E4YF06Ytq4yMb4If6YoQGRR6PpjtqOXSP3sKe2kqwB6SQ+H9DezOfZaSLnmka1NtGm3fCQ== + dependencies: + "@jest/test-result" "30.1.3" + "@jest/types" "30.0.5" + "@types/node" "*" + ansi-escapes "^4.3.2" + chalk "^4.1.2" + emittery "^0.13.1" + jest-util "30.0.5" + string-length "^4.0.2" + +jest-worker@30.1.0: + version "30.1.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-30.1.0.tgz#a89c36772be449d4bdb60697fb695a1673b12ac2" + integrity sha512-uvWcSjlwAAgIu133Tt77A05H7RIk3Ho8tZL50bQM2AkvLdluw9NG48lRCl3Dt+MOH719n/0nnb5YxUwcuJiKRA== + dependencies: + "@types/node" "*" + "@ungap/structured-clone" "^1.3.0" + jest-util "30.0.5" + merge-stream "^2.0.0" + supports-color "^8.1.1" + +jest@^30.1.3: + version "30.1.3" + resolved "https://registry.yarnpkg.com/jest/-/jest-30.1.3.tgz#c962290f65c32d44a0624f785b2d780835525a23" + integrity sha512-Ry+p2+NLk6u8Agh5yVqELfUJvRfV51hhVBRIB5yZPY7mU0DGBmOuFG5GebZbMbm86cdQNK0fhJuDX8/1YorISQ== + dependencies: + "@jest/core" "30.1.3" + "@jest/types" "30.0.5" + import-local "^3.2.0" + jest-cli "30.1.3" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + 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" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + 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" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-stringify-safe@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + 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" + 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" + 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" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + 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" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + 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" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== + +minimatch@^3.0.4, minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + 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" + 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" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + +mock-socket@^9.3.1: + version "9.3.1" + resolved "https://registry.yarnpkg.com/mock-socket/-/mock-socket-9.3.1.tgz#24fb00c2f573c84812aa4a24181bb025de80cc8e" + integrity sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw== + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +napi-postinstall@^0.3.0: + version "0.3.3" + resolved "https://registry.yarnpkg.com/napi-postinstall/-/napi-postinstall-0.3.3.tgz#93d045c6b576803ead126711d3093995198c6eb9" + 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" + 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" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +nock@^13.5.0: + version "13.5.6" + resolved "https://registry.yarnpkg.com/nock/-/nock-13.5.6.tgz#5e693ec2300bbf603b61dae6df0225673e6c4997" + integrity sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ== + dependencies: + debug "^4.1.0" + json-stringify-safe "^5.0.1" + propagate "^2.0.0" + +node-domexception@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-fetch@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b" + integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.21: + version "2.0.21" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.21.tgz#f59b018bc0048044be2d4c4c04e4c8b18160894c" + integrity sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw== + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + 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" + 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" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +ox@0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/ox/-/ox-0.9.3.tgz#92cc1008dcd913e919364fd4175c860b3eeb18db" + integrity sha512-KzyJP+fPV4uhuuqrTZyok4DC7vFzi7HLUFiUNEmpbyh59htKWkOC98IONC1zgXJPbHAhQgqs6B0Z6StCGhmQvg== + dependencies: + "@adraffy/ens-normalize" "^1.11.0" + "@noble/ciphers" "^1.3.0" + "@noble/curves" "1.9.1" + "@noble/hashes" "^1.8.0" + "@scure/bip32" "^1.7.0" + "@scure/bip39" "^1.6.0" + abitype "^1.0.9" + eventemitter3 "5.0.1" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + 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" + 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" + 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" + 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" + 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" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + 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" + 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" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +picomatch@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" + integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== + +pirates@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" + 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" + 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: + version "30.0.5" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.0.5.tgz#e001649d472800396c1209684483e18a4d250360" + integrity sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw== + dependencies: + "@jest/schemas" "30.0.5" + ansi-styles "^5.2.0" + react-is "^18.3.1" + +propagate@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" + integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== + +pure-rand@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-7.0.1.tgz#6f53a5a9e3e4a47445822af96821ca509ed37566" + integrity sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ== + +react-is@^18.3.1: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + 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" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +rxjs@^7.8.1: + version "7.8.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" + integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== + dependencies: + tslib "^2.1.0" + +scale-ts@^1.6.0: + version "1.6.1" + resolved "https://registry.yarnpkg.com/scale-ts/-/scale-ts-1.6.1.tgz#45151e156d6c04792223c39d8e7484ce926445f2" + integrity sha512-PBMc2AWc6wSEqJYBDPcyCLUj9/tMKnLX70jLOSndMtcUoLQucP/DM0vnQo1wJAYjTrQiq8iG9rD0q6wFzgjH7g== + +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.5.3, semver@^7.5.4, semver@^7.7.2: + version "7.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" + 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" + 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" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^3.0.3: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + 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" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +smoldot@2.0.22: + version "2.0.22" + resolved "https://registry.yarnpkg.com/smoldot/-/smoldot-2.0.22.tgz#1e924d2011a31c57416e79a2b97a460f462a31c7" + integrity sha512-B50vRgTY6v3baYH6uCgL15tfaag5tcS2o/P5q1OiXcKGv1axZDfz2dzzMuIkVpyMR2ug11F6EAtQlmYBQd292g== + dependencies: + ws "^8.8.1" + +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" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +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" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +string-length@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +"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" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +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" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +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" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +"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" + 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" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.1.2" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.2.tgz#132875abde678c7ea8d691533f2e7e22bb744dba" + integrity sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA== + 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" + 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" + 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" + 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" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + 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" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +synckit@^0.11.8: + version "0.11.11" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.11.tgz#c0b619cf258a97faa209155d9cd1699b5c998cb0" + integrity sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw== + dependencies: + "@pkgr/core" "^0.2.9" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + 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" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +ts-jest@^29.4.4: + version "29.4.4" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.4.4.tgz#fc6fefe28652ed81b8e1381ef8391901d9f81417" + integrity sha512-ccVcRABct5ZELCT5U0+DZwkXMCcOCLi2doHRrKy1nK/s7J7bch6TzJMsrY09WxgUUIP/ITfmcDS8D2yl63rnXw== + dependencies: + bs-logger "^0.2.6" + fast-json-stable-stringify "^2.1.0" + handlebars "^4.7.8" + json5 "^2.2.3" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.7.2" + type-fest "^4.41.0" + yargs-parser "^21.1.1" + +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +tslib@2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== + +tslib@^2.1.0, tslib@^2.4.0, tslib@^2.6.2, tslib@^2.7.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + 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" + 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" + integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== + +typescript@^5.3.3: + version "5.9.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6" + 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" + integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== + +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +undici-types@~7.11.0: + version "7.11.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.11.0.tgz#075798115d0bbc4e4fc7c173f38727ca66bfb592" + integrity sha512-kt1ZriHTi7MU+Z/r9DOdAI3ONdaR3M3csEaRc6ewa4f4dTvX4cQCbJ4NkEn0ohE4hHtq85+PhPSTY+pO/1PwgA== + +unrs-resolver@^1.7.11: + version "1.11.1" + resolved "https://registry.yarnpkg.com/unrs-resolver/-/unrs-resolver-1.11.1.tgz#be9cd8686c99ef53ecb96df2a473c64d304048a9" + integrity sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg== + dependencies: + napi-postinstall "^0.3.0" + optionalDependencies: + "@unrs/resolver-binding-android-arm-eabi" "1.11.1" + "@unrs/resolver-binding-android-arm64" "1.11.1" + "@unrs/resolver-binding-darwin-arm64" "1.11.1" + "@unrs/resolver-binding-darwin-x64" "1.11.1" + "@unrs/resolver-binding-freebsd-x64" "1.11.1" + "@unrs/resolver-binding-linux-arm-gnueabihf" "1.11.1" + "@unrs/resolver-binding-linux-arm-musleabihf" "1.11.1" + "@unrs/resolver-binding-linux-arm64-gnu" "1.11.1" + "@unrs/resolver-binding-linux-arm64-musl" "1.11.1" + "@unrs/resolver-binding-linux-ppc64-gnu" "1.11.1" + "@unrs/resolver-binding-linux-riscv64-gnu" "1.11.1" + "@unrs/resolver-binding-linux-riscv64-musl" "1.11.1" + "@unrs/resolver-binding-linux-s390x-gnu" "1.11.1" + "@unrs/resolver-binding-linux-x64-gnu" "1.11.1" + "@unrs/resolver-binding-linux-x64-musl" "1.11.1" + "@unrs/resolver-binding-wasm32-wasi" "1.11.1" + "@unrs/resolver-binding-win32-arm64-msvc" "1.11.1" + "@unrs/resolver-binding-win32-ia32-msvc" "1.11.1" + "@unrs/resolver-binding-win32-x64-msvc" "1.11.1" + +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" + integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +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" + 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" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +viem@^2.37.6: + version "2.37.6" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.37.6.tgz#3b05586555bd4b2c1b7351ed148f9fa98df72027" + integrity sha512-b+1IozQ8TciVQNdQUkOH5xtFR0z7ZxR8pyloENi/a+RA408lv4LoX12ofwoiT3ip0VRhO5ni1em//X0jn/eW0g== + dependencies: + "@noble/curves" "1.9.1" + "@noble/hashes" "1.8.0" + "@scure/bip32" "1.7.0" + "@scure/bip39" "1.6.0" + abitype "1.1.0" + isows "1.0.7" + ox "0.9.3" + ws "8.18.3" + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +web-streams-polyfill@^3.0.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b" + integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + 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" + 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" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.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" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +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" + integrity sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^4.0.1" + +ws@8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== + +ws@8.18.3, ws@^8.15.1, ws@^8.8.1: + version "8.18.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472" + 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" + 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" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + 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" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From 436bc8054a0e10f90e6ba719c4aaccfabe35ddd0 Mon Sep 17 00:00:00 2001 From: Osman Abdelnasir Date: Mon, 22 Sep 2025 18:48:43 +0400 Subject: [PATCH 23/32] feat: cleanup --- Cargo.toml | 1 + pallets/signet/src/benchmarks.rs | 78 ++++++++++---------- pallets/signet/src/lib.rs | 8 -- pallets/signet/src/tests.rs | 18 ----- pallets/signet/src/weights.rs | 5 -- runtime/hydradx/Cargo.toml | 2 +- runtime/hydradx/src/weights/pallet_signet.rs | 4 - 7 files changed, 41 insertions(+), 75 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 777d5805ff..30a05df89e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -161,6 +161,7 @@ liquidation-worker-support = { path = "liquidation-worker-support", default-feat pallet-hsm = { path = "pallets/hsm", default-features = false } pallet-build-evm-tx = { path = "pallets/build-evm-tx", default-features = false } pallet-parameters = { path = "pallets/parameters", default-features = false } +pallet-signet = { path = "pallets/signet", default-features = false } hydra-dx-build-script-utils = { path = "utils/build-script-utils", default-features = false } scraper = { path = "scraper", default-features = false } diff --git a/pallets/signet/src/benchmarks.rs b/pallets/signet/src/benchmarks.rs index 4d8276c6e6..a0f840de2f 100644 --- a/pallets/signet/src/benchmarks.rs +++ b/pallets/signet/src/benchmarks.rs @@ -2,48 +2,48 @@ use super::*; use frame_benchmarking::{benchmarks, whitelisted_caller}; -use frame_system::RawOrigin; use frame_support::traits::Currency; +use frame_system::RawOrigin; benchmarks! { - initialize { - let admin: T::AccountId = whitelisted_caller(); - let deposit = T::Currency::minimum_balance(); - }: _(RawOrigin::Root, admin.clone(), deposit) - verify { - assert_eq!(Admin::::get(), Some(admin)); - assert_eq!(SignatureDeposit::::get(), deposit); - } + initialize { + let admin: T::AccountId = whitelisted_caller(); + let deposit = T::Currency::minimum_balance(); + let chain_id = b"test-chain".to_vec(); + }: _(RawOrigin::Root, admin.clone(), deposit, chain_id) + verify { + assert_eq!(Admin::::get(), Some(admin)); + assert_eq!(SignatureDeposit::::get(), deposit); + } + + update_deposit { + let admin: T::AccountId = whitelisted_caller(); + let initial_deposit = T::Currency::minimum_balance(); + let chain_id = b"test-chain".to_vec(); + let _ = Pallet::::initialize(RawOrigin::Root.into(), admin.clone(), initial_deposit, chain_id); + let new_deposit = T::Currency::minimum_balance() * 2u32.into(); + }: _(RawOrigin::Signed(admin), new_deposit) + verify { + assert_eq!(SignatureDeposit::::get(), new_deposit); + } + + withdraw_funds { + let admin: T::AccountId = whitelisted_caller(); + let chain_id = b"test-chain".to_vec(); + let _ = Pallet::::initialize(RawOrigin::Root.into(), admin.clone(), T::Currency::minimum_balance(), chain_id); - update_deposit { - // Setup: Initialize first - let admin: T::AccountId = whitelisted_caller(); - let initial_deposit = T::Currency::minimum_balance(); - let _ = Pallet::::initialize(RawOrigin::Root.into(), admin.clone(), initial_deposit); - - let new_deposit = initial_deposit * 2u32.into(); - }: _(RawOrigin::Signed(admin), new_deposit) - verify { - assert_eq!(SignatureDeposit::::get(), new_deposit); - } + // Fund the pallet account + let pallet_account = Pallet::::account_id(); + let amount = T::Currency::minimum_balance() * 100u32.into(); + let _ = T::Currency::deposit_creating(&pallet_account, amount); - withdraw_funds { - // Setup: Initialize and fund the pallet - let admin: T::AccountId = whitelisted_caller(); - let _ = Pallet::::initialize(RawOrigin::Root.into(), admin.clone(), T::Currency::minimum_balance()); - - // Fund the pallet account - let pallet_account = Pallet::::account_id(); - let amount = T::Currency::minimum_balance() * 100u32.into(); - let _ = T::Currency::deposit_creating(&pallet_account, amount); - - let recipient: T::AccountId = whitelisted_caller(); - let withdraw_amount = T::Currency::minimum_balance() * 50u32.into(); - }: _(RawOrigin::Signed(admin), recipient.clone(), withdraw_amount) - verify { - // Verify funds were transferred - assert!(T::Currency::free_balance(&recipient) >= withdraw_amount); - } + let recipient: T::AccountId = whitelisted_caller(); + let withdraw_amount = T::Currency::minimum_balance() * 50u32.into(); + }: _(RawOrigin::Signed(admin), recipient.clone(), withdraw_amount) + verify { + // Verify funds were transferred + assert!(T::Currency::free_balance(&recipient) >= withdraw_amount); + } - impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test); -} \ No newline at end of file + impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test); +} diff --git a/pallets/signet/src/lib.rs b/pallets/signet/src/lib.rs index cbc174ad3b..97325b1938 100644 --- a/pallets/signet/src/lib.rs +++ b/pallets/signet/src/lib.rs @@ -428,14 +428,6 @@ pub mod pallet { Ok(()) } - - /// Get the current signature deposit amount - #[pallet::call_index(8)] - #[pallet::weight(::WeightInfo::get_signature_deposit())] - pub fn get_signature_deposit(_origin: OriginFor) -> DispatchResultWithPostInfo { - // This is just for RPC queries - the getter handles actual retrieval - Ok(().into()) - } } // Helper functions diff --git a/pallets/signet/src/tests.rs b/pallets/signet/src/tests.rs index e0622f0b6f..636b673aba 100644 --- a/pallets/signet/src/tests.rs +++ b/pallets/signet/src/tests.rs @@ -758,24 +758,6 @@ fn test_read_respond() { }); } -#[test] -fn test_get_signature_deposit() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let deposit = 5000u128; - - assert_ok!(Signet::initialize( - RuntimeOrigin::signed(1), - admin, - deposit, - b"test-chain".to_vec() - )); - - assert_eq!(Signet::signature_deposit(), deposit); - assert_ok!(Signet::get_signature_deposit(RuntimeOrigin::signed(1))); - }); -} - #[test] fn test_sign_includes_chain_id() { new_test_ext().execute_with(|| { diff --git a/pallets/signet/src/weights.rs b/pallets/signet/src/weights.rs index 3f0847cf15..979df9ab01 100644 --- a/pallets/signet/src/weights.rs +++ b/pallets/signet/src/weights.rs @@ -14,7 +14,6 @@ pub trait WeightInfo { fn respond(r: u32) -> Weight; // r = number of responses fn respond_error(e: u32) -> Weight; // e = number of errors fn read_respond() -> Weight; - fn get_signature_deposit() -> Weight; } /// For tests - just returns simple weights @@ -54,8 +53,4 @@ impl WeightInfo for () { fn read_respond() -> Weight { Weight::from_parts(10_000_000, 0) } - - fn get_signature_deposit() -> Weight { - Weight::from_parts(5_000_000, 0) - } } \ No newline at end of file diff --git a/runtime/hydradx/Cargo.toml b/runtime/hydradx/Cargo.toml index b14c50922f..ff4bcbbb8d 100644 --- a/runtime/hydradx/Cargo.toml +++ b/runtime/hydradx/Cargo.toml @@ -92,7 +92,7 @@ pallet-uniques = { workspace = true } pallet-whitelist = { workspace = true } pallet-message-queue = { workspace = true } pallet-state-trie-migration = { workspace = true } -pallet-signet = { path = "../../pallets/signet", default-features = false } +pallet-signet = { workspace = true } # ORML dependencies orml-tokens = { workspace = true } diff --git a/runtime/hydradx/src/weights/pallet_signet.rs b/runtime/hydradx/src/weights/pallet_signet.rs index 30994941b6..ab8f98c00e 100644 --- a/runtime/hydradx/src/weights/pallet_signet.rs +++ b/runtime/hydradx/src/weights/pallet_signet.rs @@ -40,8 +40,4 @@ impl pallet_signet::weights::WeightInfo for HydraWeight fn read_respond() -> Weight { Weight::from_parts(10_000_000, 0) } - - fn get_signature_deposit() -> Weight { - Weight::from_parts(5_000_000, 0) - } } \ No newline at end of file From 1c46ef5c3665fd8c968b622849b59a738daeca83 Mon Sep 17 00:00:00 2001 From: Osman Abdelnasir Date: Mon, 29 Sep 2025 12:29:00 +0400 Subject: [PATCH 24/32] feat: cross pallet test --- pallets/signet/src/tests.rs | 781 ++++++++++++++++++++---------------- 1 file changed, 439 insertions(+), 342 deletions(-) diff --git a/pallets/signet/src/tests.rs b/pallets/signet/src/tests.rs index 636b673aba..91d8a4d0ae 100644 --- a/pallets/signet/src/tests.rs +++ b/pallets/signet/src/tests.rs @@ -7,11 +7,56 @@ use frame_support::{ }; use frame_system as system; use sp_core::H256; +use sp_runtime::traits::AccountIdConversion; use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, BuildStorage, }; +#[frame_support::pallet] +pub mod pallet_mock_caller { + use frame_support::{pallet_prelude::*, PalletId}; + use frame_system::pallet_prelude::*; + use sp_runtime::traits::AccountIdConversion; + use crate::{self as pallet_signet}; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_signet::Config { + #[pallet::constant] + type PalletId: Get; + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(Weight::from_parts(10_000, 0))] + pub fn call_signet(origin: OriginFor) -> DispatchResult { + // This pallet will call signet with ITS OWN account as the sender + let _who = ensure_signed(origin)?; + + // Get this pallet's derived account (use fully-qualified syntax) + let pallet_account: T::AccountId = + ::PalletId::get().into_account_truncating(); + + // Call signet from this pallet's account + pallet_signet::Pallet::::sign( + frame_system::RawOrigin::Signed(pallet_account).into(), + [99u8; 32], + 1, + b"from_pallet".to_vec(), + b"ecdsa".to_vec(), + b"".to_vec(), + b"{}".to_vec(), + )?; + + Ok(()) + } + } +} + type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( @@ -20,6 +65,7 @@ frame_support::construct_runtime!( System: frame_system, Balances: pallet_balances, Signet: pallet_signet, + MockCaller: pallet_mock_caller, } ); @@ -93,6 +139,14 @@ impl pallet_signet::Config for Test { type WeightInfo = (); } +parameter_types! { + pub const MockCallerPalletId: PalletId = PalletId(*b"py/mockc"); +} + +impl pallet_mock_caller::Config for Test { + type PalletId = MockCallerPalletId; +} + pub fn new_test_ext() -> sp_io::TestExternalities { let t = system::GenesisConfig::::default().build_storage().unwrap(); @@ -180,8 +234,8 @@ fn test_cannot_use_before_initialization() { fn test_any_signed_can_initialize_once() { new_test_ext().execute_with(|| { assert_ok!(Signet::initialize( - RuntimeOrigin::signed(2), - 1, + RuntimeOrigin::signed(2), + 1, 1000, b"test-chain".to_vec() )); @@ -190,12 +244,7 @@ fn test_any_signed_can_initialize_once() { assert_eq!(Signet::signature_deposit(), 1000); assert_noop!( - Signet::initialize( - RuntimeOrigin::signed(1), - 3, - 2000, - b"other-chain".to_vec() - ), + Signet::initialize(RuntimeOrigin::signed(1), 3, 2000, b"other-chain".to_vec()), Error::::AlreadyInitialized ); @@ -211,77 +260,77 @@ fn test_any_signed_can_initialize_once() { #[test] fn test_initialize_sets_deposit() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let initial_deposit = 1000u128; - - assert_ok!(Signet::initialize( - RuntimeOrigin::signed(1), - admin, - initial_deposit, - b"test-chain".to_vec() - )); - - assert_eq!(Signet::signature_deposit(), initial_deposit); - - System::assert_last_event( - Event::Initialized { - admin, - signature_deposit: initial_deposit, - chain_id: b"test-chain".to_vec(), - } - .into(), - ); - }); + new_test_ext().execute_with(|| { + let admin = 1u64; + let initial_deposit = 1000u128; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + initial_deposit, + b"test-chain".to_vec() + )); + + assert_eq!(Signet::signature_deposit(), initial_deposit); + + System::assert_last_event( + Event::Initialized { + admin, + signature_deposit: initial_deposit, + chain_id: b"test-chain".to_vec(), + } + .into(), + ); + }); } #[test] fn test_update_deposit_as_admin() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let initial_deposit = 1000u128; - let new_deposit = 2000u128; - - assert_ok!(Signet::initialize( - RuntimeOrigin::signed(1), - admin, - initial_deposit, - b"test-chain".to_vec() - )); - - assert_ok!(Signet::update_deposit(RuntimeOrigin::signed(admin), new_deposit)); - assert_eq!(Signet::signature_deposit(), new_deposit); - - System::assert_last_event( - Event::DepositUpdated { - old_deposit: initial_deposit, - new_deposit, - } - .into(), - ); - }); + new_test_ext().execute_with(|| { + let admin = 1u64; + let initial_deposit = 1000u128; + let new_deposit = 2000u128; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + initial_deposit, + b"test-chain".to_vec() + )); + + assert_ok!(Signet::update_deposit(RuntimeOrigin::signed(admin), new_deposit)); + assert_eq!(Signet::signature_deposit(), new_deposit); + + System::assert_last_event( + Event::DepositUpdated { + old_deposit: initial_deposit, + new_deposit, + } + .into(), + ); + }); } #[test] fn test_non_admin_cannot_update_deposit() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let non_admin = 2u64; + new_test_ext().execute_with(|| { + let admin = 1u64; + let non_admin = 2u64; - assert_ok!(Signet::initialize( - RuntimeOrigin::signed(1), - admin, - 1000, - b"test-chain".to_vec() - )); + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + 1000, + b"test-chain".to_vec() + )); - assert_noop!( - Signet::update_deposit(RuntimeOrigin::signed(non_admin), 2000), - Error::::Unauthorized - ); + assert_noop!( + Signet::update_deposit(RuntimeOrigin::signed(non_admin), 2000), + Error::::Unauthorized + ); - assert_eq!(Signet::signature_deposit(), 1000); - }); + assert_eq!(Signet::signature_deposit(), 1000); + }); } #[test] @@ -296,76 +345,76 @@ fn test_cannot_update_deposit_before_initialization() { #[test] fn test_withdraw_funds_as_admin() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let recipient = 2u64; - let amount = 5000u128; + new_test_ext().execute_with(|| { + let admin = 1u64; + let recipient = 2u64; + let amount = 5000u128; - assert_ok!(Signet::initialize( - RuntimeOrigin::signed(1), - admin, - 1000, - b"test-chain".to_vec() - )); + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + 1000, + b"test-chain".to_vec() + )); - let pallet_account = Signet::account_id(); - let _ = Balances::deposit_creating(&pallet_account, 10_000); + let pallet_account = Signet::account_id(); + let _ = Balances::deposit_creating(&pallet_account, 10_000); - let recipient_balance_before = Balances::free_balance(&recipient); - assert_eq!(Balances::free_balance(&pallet_account), 10_000); + let recipient_balance_before = Balances::free_balance(&recipient); + assert_eq!(Balances::free_balance(&pallet_account), 10_000); - assert_ok!(Signet::withdraw_funds(RuntimeOrigin::signed(admin), recipient, amount)); + assert_ok!(Signet::withdraw_funds(RuntimeOrigin::signed(admin), recipient, amount)); - assert_eq!(Balances::free_balance(&pallet_account), 5_000); - assert_eq!(Balances::free_balance(&recipient), recipient_balance_before + amount); + assert_eq!(Balances::free_balance(&pallet_account), 5_000); + assert_eq!(Balances::free_balance(&recipient), recipient_balance_before + amount); - System::assert_last_event(Event::FundsWithdrawn { amount, recipient }.into()); - }); + System::assert_last_event(Event::FundsWithdrawn { amount, recipient }.into()); + }); } #[test] fn test_non_admin_cannot_withdraw() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let non_admin = 2u64; - - assert_ok!(Signet::initialize( - RuntimeOrigin::signed(1), - admin, - 1000, - b"test-chain".to_vec() - )); - - let pallet_account = Signet::account_id(); - let _ = Balances::deposit_creating(&pallet_account, 10_000); - - assert_noop!( - Signet::withdraw_funds(RuntimeOrigin::signed(non_admin), non_admin, 5000), - Error::::Unauthorized - ); - }); + new_test_ext().execute_with(|| { + let admin = 1u64; + let non_admin = 2u64; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + 1000, + b"test-chain".to_vec() + )); + + let pallet_account = Signet::account_id(); + let _ = Balances::deposit_creating(&pallet_account, 10_000); + + assert_noop!( + Signet::withdraw_funds(RuntimeOrigin::signed(non_admin), non_admin, 5000), + Error::::Unauthorized + ); + }); } #[test] fn test_cannot_withdraw_more_than_balance() { - new_test_ext().execute_with(|| { - let admin = 1u64; - - assert_ok!(Signet::initialize( - RuntimeOrigin::signed(1), - admin, - 1000, - b"test-chain".to_vec() - )); - - let pallet_account = Signet::account_id(); - let _ = Balances::deposit_creating(&pallet_account, 10_000); - - assert_noop!( - Signet::withdraw_funds(RuntimeOrigin::signed(admin), admin, 20_000), - Error::::InsufficientFunds - ); - }); + new_test_ext().execute_with(|| { + let admin = 1u64; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + 1000, + b"test-chain".to_vec() + )); + + let pallet_account = Signet::account_id(); + let _ = Balances::deposit_creating(&pallet_account, 10_000); + + assert_noop!( + Signet::withdraw_funds(RuntimeOrigin::signed(admin), admin, 20_000), + Error::::InsufficientFunds + ); + }); } #[test] @@ -382,84 +431,84 @@ fn test_pallet_account_id_is_deterministic() { #[test] fn test_sign_request_works() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let requester = 2u64; - let deposit = 1000u128; - - assert_ok!(Signet::initialize( - RuntimeOrigin::signed(1), - admin, - deposit, - b"test-chain".to_vec() - )); - - let balance_before = Balances::free_balance(&requester); - let payload = [42u8; 32]; - let key_version = 1u32; - let path = b"path".to_vec(); - let algo = b"ecdsa".to_vec(); - let dest = b"callback_contract".to_vec(); - let params = b"{}".to_vec(); - - assert_ok!(Signet::sign( - RuntimeOrigin::signed(requester), - payload, - key_version, - path.clone(), - algo.clone(), - dest.clone(), - params.clone() - )); - - assert_eq!(Balances::free_balance(&requester), balance_before - deposit); - let pallet_account = Signet::account_id(); - assert_eq!(Balances::free_balance(&pallet_account), deposit); - - System::assert_last_event( - Event::SignatureRequested { - sender: requester, - payload, - key_version, - deposit, - chain_id: b"test-chain".to_vec(), - path, - algo, - dest, - params, - } - .into(), - ); - }); + new_test_ext().execute_with(|| { + let admin = 1u64; + let requester = 2u64; + let deposit = 1000u128; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + deposit, + b"test-chain".to_vec() + )); + + let balance_before = Balances::free_balance(&requester); + let payload = [42u8; 32]; + let key_version = 1u32; + let path = b"path".to_vec(); + let algo = b"ecdsa".to_vec(); + let dest = b"callback_contract".to_vec(); + let params = b"{}".to_vec(); + + assert_ok!(Signet::sign( + RuntimeOrigin::signed(requester), + payload, + key_version, + path.clone(), + algo.clone(), + dest.clone(), + params.clone() + )); + + assert_eq!(Balances::free_balance(&requester), balance_before - deposit); + let pallet_account = Signet::account_id(); + assert_eq!(Balances::free_balance(&pallet_account), deposit); + + System::assert_last_event( + Event::SignatureRequested { + sender: requester, + payload, + key_version, + deposit, + chain_id: b"test-chain".to_vec(), + path, + algo, + dest, + params, + } + .into(), + ); + }); } #[test] fn test_sign_request_insufficient_balance() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let poor_user = 3u64; - let deposit = 1000u128; - - assert_ok!(Signet::initialize( - RuntimeOrigin::signed(1), - admin, - deposit, - b"test-chain".to_vec() - )); - - assert_noop!( - Signet::sign( - RuntimeOrigin::signed(poor_user), - [0u8; 32], - 1, - b"path".to_vec(), - b"algo".to_vec(), - b"dest".to_vec(), - b"params".to_vec() - ), - sp_runtime::TokenError::FundsUnavailable - ); - }); + new_test_ext().execute_with(|| { + let admin = 1u64; + let poor_user = 3u64; + let deposit = 1000u128; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + deposit, + b"test-chain".to_vec() + )); + + assert_noop!( + Signet::sign( + RuntimeOrigin::signed(poor_user), + [0u8; 32], + 1, + b"path".to_vec(), + b"algo".to_vec(), + b"dest".to_vec(), + b"params".to_vec() + ), + sp_runtime::TokenError::FundsUnavailable + ); + }); } #[test] @@ -482,45 +531,45 @@ fn test_sign_request_before_initialization() { #[test] fn test_multiple_sign_requests() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let requester1 = 1u64; - let requester2 = 2u64; - let deposit = 100u128; - - assert_ok!(Signet::initialize( - RuntimeOrigin::signed(1), - admin, - deposit, - b"test-chain".to_vec() - )); - - let pallet_account = Signet::account_id(); - - assert_ok!(Signet::sign( - RuntimeOrigin::signed(requester1), - [1u8; 32], - 1, - b"path1".to_vec(), - b"algo".to_vec(), - b"dest".to_vec(), - b"params".to_vec() - )); - - assert_eq!(Balances::free_balance(&pallet_account), deposit); - - assert_ok!(Signet::sign( - RuntimeOrigin::signed(requester2), - [2u8; 32], - 2, - b"path2".to_vec(), - b"algo".to_vec(), - b"dest".to_vec(), - b"params".to_vec() - )); - - assert_eq!(Balances::free_balance(&pallet_account), deposit * 2); - }); + new_test_ext().execute_with(|| { + let admin = 1u64; + let requester1 = 1u64; + let requester2 = 2u64; + let deposit = 100u128; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + deposit, + b"test-chain".to_vec() + )); + + let pallet_account = Signet::account_id(); + + assert_ok!(Signet::sign( + RuntimeOrigin::signed(requester1), + [1u8; 32], + 1, + b"path1".to_vec(), + b"algo".to_vec(), + b"dest".to_vec(), + b"params".to_vec() + )); + + assert_eq!(Balances::free_balance(&pallet_account), deposit); + + assert_ok!(Signet::sign( + RuntimeOrigin::signed(requester2), + [2u8; 32], + 2, + b"path2".to_vec(), + b"algo".to_vec(), + b"dest".to_vec(), + b"params".to_vec() + )); + + assert_eq!(Balances::free_balance(&pallet_account), deposit * 2); + }); } fn create_test_signature() -> Signature { @@ -536,78 +585,78 @@ fn create_test_signature() -> Signature { #[test] fn test_sign_respond_works() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let requester = 2u64; - let deposit = 100u128; - - assert_ok!(Signet::initialize( - RuntimeOrigin::signed(1), - admin, - deposit, - b"test-chain".to_vec() - )); - - let tx_data = b"mock_transaction_data".to_vec(); - let slip44_chain_id = 60u32; - let balance_before = Balances::free_balance(&requester); - - assert_ok!(Signet::sign_respond( - RuntimeOrigin::signed(requester), - tx_data.clone(), - slip44_chain_id, - 1, - b"path".to_vec(), - b"ecdsa".to_vec(), - b"callback".to_vec(), - b"{}".to_vec(), - SerializationFormat::AbiJson, - b"schema1".to_vec(), - SerializationFormat::Borsh, - b"schema2".to_vec() - )); - - assert_eq!(Balances::free_balance(&requester), balance_before - deposit); - - let events = System::events(); - let event_found = events - .iter() - .any(|e| matches!(&e.event, RuntimeEvent::Signet(Event::SignRespondRequested { .. }))); - assert!(event_found); - }); + new_test_ext().execute_with(|| { + let admin = 1u64; + let requester = 2u64; + let deposit = 100u128; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + deposit, + b"test-chain".to_vec() + )); + + let tx_data = b"mock_transaction_data".to_vec(); + let slip44_chain_id = 60u32; + let balance_before = Balances::free_balance(&requester); + + assert_ok!(Signet::sign_respond( + RuntimeOrigin::signed(requester), + tx_data.clone(), + slip44_chain_id, + 1, + b"path".to_vec(), + b"ecdsa".to_vec(), + b"callback".to_vec(), + b"{}".to_vec(), + SerializationFormat::AbiJson, + b"schema1".to_vec(), + SerializationFormat::Borsh, + b"schema2".to_vec() + )); + + assert_eq!(Balances::free_balance(&requester), balance_before - deposit); + + let events = System::events(); + let event_found = events + .iter() + .any(|e| matches!(&e.event, RuntimeEvent::Signet(Event::SignRespondRequested { .. }))); + assert!(event_found); + }); } #[test] fn test_sign_respond_empty_transaction_fails() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let requester = 2u64; - - assert_ok!(Signet::initialize( - RuntimeOrigin::signed(1), - admin, - 100, - b"test-chain".to_vec() - )); - - assert_noop!( - Signet::sign_respond( - RuntimeOrigin::signed(requester), - vec![], - 60, - 1, - b"path".to_vec(), - b"algo".to_vec(), - b"dest".to_vec(), - b"params".to_vec(), - SerializationFormat::Borsh, - vec![], - SerializationFormat::Borsh, - vec![] - ), - Error::::InvalidTransaction - ); - }); + new_test_ext().execute_with(|| { + let admin = 1u64; + let requester = 2u64; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + 100, + b"test-chain".to_vec() + )); + + assert_noop!( + Signet::sign_respond( + RuntimeOrigin::signed(requester), + vec![], + 60, + 1, + b"path".to_vec(), + b"algo".to_vec(), + b"dest".to_vec(), + b"params".to_vec(), + SerializationFormat::Borsh, + vec![], + SerializationFormat::Borsh, + vec![] + ), + Error::::InvalidTransaction + ); + }); } #[test] @@ -760,41 +809,89 @@ fn test_read_respond() { #[test] fn test_sign_includes_chain_id() { - new_test_ext().execute_with(|| { - let admin = 1u64; - let requester = 2u64; - let chain_id = b"hydradx:polkadot:0".to_vec(); - - assert_ok!(Signet::initialize( - RuntimeOrigin::signed(1), - admin, - 100, - chain_id.clone() - )); - - assert_ok!(Signet::sign( - RuntimeOrigin::signed(requester), - [42u8; 32], - 1, - b"path".to_vec(), - b"algo".to_vec(), - b"dest".to_vec(), - b"params".to_vec() - )); - - let events = System::events(); - let sign_event = events.iter().find_map(|e| { - if let RuntimeEvent::Signet(Event::SignatureRequested { - chain_id: event_chain_id, - .. - }) = &e.event - { - Some(event_chain_id.clone()) - } else { - None - } - }); - - assert_eq!(sign_event, Some(chain_id)); - }); + new_test_ext().execute_with(|| { + let admin = 1u64; + let requester = 2u64; + let chain_id = b"hydradx:polkadot:0".to_vec(); + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + 100, + chain_id.clone() + )); + + assert_ok!(Signet::sign( + RuntimeOrigin::signed(requester), + [42u8; 32], + 1, + b"path".to_vec(), + b"algo".to_vec(), + b"dest".to_vec(), + b"params".to_vec() + )); + + let events = System::events(); + let sign_event = events.iter().find_map(|e| { + if let RuntimeEvent::Signet(Event::SignatureRequested { + chain_id: event_chain_id, + .. + }) = &e.event + { + Some(event_chain_id.clone()) + } else { + None + } + }); + + assert_eq!(sign_event, Some(chain_id)); + }); +} + +#[test] +fn test_cross_pallet_execution() { + new_test_ext().execute_with(|| { + // Initialize signet first + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + 1, + 100, + b"test-chain".to_vec() + )); + + // Fund the MockCaller pallet's account + let mock_pallet_account: u64 = MockCallerPalletId::get().into_account_truncating(); + let _ = Balances::deposit_creating(&mock_pallet_account, 10_000); + + // User calls MockCaller, which then calls Signet + assert_ok!(MockCaller::call_signet(RuntimeOrigin::signed(2))); + + // Check the event - the sender should be the PALLET's account + System::assert_last_event( + Event::SignatureRequested { + sender: mock_pallet_account, + payload: [99u8; 32], + key_version: 1, + deposit: 100, + chain_id: b"test-chain".to_vec(), + path: b"from_pallet".to_vec(), + algo: b"ecdsa".to_vec(), + dest: b"".to_vec(), + params: b"{}".to_vec(), + } + .into(), + ); + + // Verify the deposit was taken from the pallet's account + assert_eq!(Balances::free_balance(&mock_pallet_account), 10_000 - 100); + + println!("āœ… Cross-pallet test passed!"); + println!(" User 2 called MockCaller"); + println!(" MockCaller called Signet"); + println!( + " Signet saw sender as: {:?} (the pallet account)", + mock_pallet_account + ); + println!(" NOT as: 2 (the original user)"); + }); } From 7897e4332bd44af762de6aaeb03db08619e59dfd Mon Sep 17 00:00:00 2001 From: Osman Abdelnasir Date: Mon, 6 Oct 2025 23:29:51 +0400 Subject: [PATCH 25/32] fix: use bounded vec --- pallets/signet/Cargo.toml | 2 +- pallets/signet/src/lib.rs | 86 ++++--- pallets/signet/src/tests.rs | 219 ++++++++++-------- signet-ts-client-scripts/key-derivation.ts | 12 +- signet-ts-client-scripts/signet-client.ts | 4 +- signet-ts-client-scripts/signet.test.ts | 120 +++++++--- .../transaction-builder.ts | 8 +- 7 files changed, 280 insertions(+), 171 deletions(-) diff --git a/pallets/signet/Cargo.toml b/pallets/signet/Cargo.toml index cb359be05b..3fe90bfe27 100644 --- a/pallets/signet/Cargo.toml +++ b/pallets/signet/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "pallet-signet" version = "1.0.0" -authors = ["GalacticCouncil"] +authors = ["Signet"] edition = "2021" license = "Apache-2.0" repository = "https://github.com/galacticcouncil/hydradx-node" diff --git a/pallets/signet/src/lib.rs b/pallets/signet/src/lib.rs index 97325b1938..d336832665 100644 --- a/pallets/signet/src/lib.rs +++ b/pallets/signet/src/lib.rs @@ -14,6 +14,23 @@ pub use pallet::*; // Type alias for cleaner code type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; +/// Maximum length for paths, algorithms, destinations, and parameters +const MAX_PATH_LENGTH: u32 = 256; +const MAX_ALGO_LENGTH: u32 = 32; +const MAX_DEST_LENGTH: u32 = 64; +const MAX_PARAMS_LENGTH: u32 = 1024; + +/// Maximum length for transaction data and serialized outputs +const MAX_TRANSACTION_LENGTH: u32 = 65536; // 64 KB +const MAX_SERIALIZED_OUTPUT_LENGTH: u32 = 65536; // 64 KB + +/// Maximum length for schemas and error messages +const MAX_SCHEMA_LENGTH: u32 = 4096; // 4 KB +const MAX_ERROR_MESSAGE_LENGTH: u32 = 1024; + +/// Maximum batch sizes +const MAX_BATCH_SIZE: u32 = 100; + #[cfg(feature = "runtime-benchmarks")] mod benchmarks; @@ -78,7 +95,7 @@ pub mod pallet { #[derive(Encode, Decode, TypeInfo, Debug, Clone, PartialEq, Eq)] pub struct ErrorResponse { pub request_id: [u8; 32], - pub error_message: Vec, + pub error_message: BoundedVec>, } // ======================================== @@ -214,7 +231,7 @@ pub mod pallet { origin: OriginFor, admin: T::AccountId, signature_deposit: BalanceOf, - chain_id: Vec, + chain_id: BoundedVec, ) -> DispatchResult { let _initializer = ensure_signed(origin)?; ensure!(Admin::::get().is_none(), Error::::AlreadyInitialized); @@ -229,7 +246,7 @@ pub mod pallet { Self::deposit_event(Event::Initialized { admin, signature_deposit, - chain_id, + chain_id: chain_id.to_vec(), }); Ok(()) @@ -280,10 +297,10 @@ pub mod pallet { origin: OriginFor, payload: [u8; 32], key_version: u32, - path: Vec, - algo: Vec, - dest: Vec, - params: Vec, + path: BoundedVec>, + algo: BoundedVec>, + dest: BoundedVec>, + params: BoundedVec>, ) -> DispatchResult { let requester = ensure_signed(origin)?; @@ -307,10 +324,10 @@ pub mod pallet { key_version, deposit, chain_id, - path, - algo, - dest, - params, + path: path.to_vec(), + algo: algo.to_vec(), + dest: dest.to_vec(), + params: params.to_vec(), }); Ok(()) @@ -321,17 +338,17 @@ pub mod pallet { #[pallet::weight(::WeightInfo::sign_respond())] pub fn sign_respond( origin: OriginFor, - serialized_transaction: Vec, + serialized_transaction: BoundedVec>, slip44_chain_id: u32, key_version: u32, - path: Vec, - algo: Vec, - dest: Vec, - params: Vec, + path: BoundedVec>, + algo: BoundedVec>, + dest: BoundedVec>, + params: BoundedVec>, explorer_deserialization_format: SerializationFormat, - explorer_deserialization_schema: Vec, + explorer_deserialization_schema: BoundedVec>, callback_serialization_format: SerializationFormat, - callback_serialization_schema: Vec, + callback_serialization_schema: BoundedVec>, ) -> DispatchResult { let requester = ensure_signed(origin)?; @@ -351,18 +368,18 @@ pub mod pallet { // Emit event Self::deposit_event(Event::SignRespondRequested { sender: requester, - transaction_data: serialized_transaction, + transaction_data: serialized_transaction.to_vec(), slip44_chain_id, key_version, deposit, - path, - algo, - dest, - params, + path: path.to_vec(), + algo: algo.to_vec(), + dest: dest.to_vec(), + params: params.to_vec(), explorer_deserialization_format: explorer_deserialization_format as u8, - explorer_deserialization_schema, + explorer_deserialization_schema: explorer_deserialization_schema.to_vec(), callback_serialization_format: callback_serialization_format as u8, - callback_serialization_schema, + callback_serialization_schema: callback_serialization_schema.to_vec(), }); Ok(()) @@ -371,7 +388,11 @@ pub mod pallet { /// Respond to signature requests (batch support) #[pallet::call_index(5)] #[pallet::weight(::WeightInfo::respond(request_ids.len() as u32))] - pub fn respond(origin: OriginFor, request_ids: Vec<[u8; 32]>, signatures: Vec) -> DispatchResult { + pub fn respond( + origin: OriginFor, + request_ids: BoundedVec<[u8; 32], ConstU32>, + signatures: BoundedVec>, + ) -> DispatchResult { let responder = ensure_signed(origin)?; // Validate input lengths @@ -392,7 +413,10 @@ pub mod pallet { /// Report signature generation errors (batch support) #[pallet::call_index(6)] #[pallet::weight(::WeightInfo::respond_error(errors.len() as u32))] - pub fn respond_error(origin: OriginFor, errors: Vec) -> DispatchResult { + pub fn respond_error( + origin: OriginFor, + errors: BoundedVec>, + ) -> DispatchResult { let responder = ensure_signed(origin)?; // Emit error events @@ -400,7 +424,7 @@ pub mod pallet { Self::deposit_event(Event::SignatureError { request_id: error.request_id, responder: responder.clone(), - error: error.error_message, + error: error.error_message.to_vec(), }); } @@ -413,16 +437,16 @@ pub mod pallet { pub fn read_respond( origin: OriginFor, request_id: [u8; 32], - serialized_output: Vec, + serialized_output: BoundedVec>, signature: Signature, ) -> DispatchResult { let responder = ensure_signed(origin)?; - // Just emit event + // Just emit event Self::deposit_event(Event::ReadResponded { request_id, responder, - serialized_output, + serialized_output: serialized_output.to_vec(), signature, }); diff --git a/pallets/signet/src/tests.rs b/pallets/signet/src/tests.rs index 91d8a4d0ae..1d37305df5 100644 --- a/pallets/signet/src/tests.rs +++ b/pallets/signet/src/tests.rs @@ -12,13 +12,33 @@ use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, BuildStorage, }; +use sp_std::vec::Vec; +fn bounded_u8(v: Vec) -> BoundedVec> { + BoundedVec::try_from(v).unwrap() +} + +fn bounded_array(v: Vec<[u8; 32]>) -> BoundedVec<[u8; 32], ConstU32> { + BoundedVec::try_from(v).unwrap() +} + +fn bounded_sig(v: Vec) -> BoundedVec> { + BoundedVec::try_from(v).unwrap() +} + +fn bounded_err(v: Vec) -> BoundedVec> { + BoundedVec::try_from(v).unwrap() +} + +fn bounded_chain_id(v: Vec) -> BoundedVec { + BoundedVec::try_from(v).unwrap() +} #[frame_support::pallet] pub mod pallet_mock_caller { + use crate::{self as pallet_signet, tests::bounded_u8}; use frame_support::{pallet_prelude::*, PalletId}; use frame_system::pallet_prelude::*; - use sp_runtime::traits::AccountIdConversion; - use crate::{self as pallet_signet}; + use sp_runtime::traits::AccountIdConversion; #[pallet::pallet] pub struct Pallet(_); @@ -38,18 +58,17 @@ pub mod pallet_mock_caller { let _who = ensure_signed(origin)?; // Get this pallet's derived account (use fully-qualified syntax) - let pallet_account: T::AccountId = - ::PalletId::get().into_account_truncating(); + let pallet_account: T::AccountId = ::PalletId::get().into_account_truncating(); // Call signet from this pallet's account pallet_signet::Pallet::::sign( frame_system::RawOrigin::Signed(pallet_account).into(), [99u8; 32], 1, - b"from_pallet".to_vec(), - b"ecdsa".to_vec(), - b"".to_vec(), - b"{}".to_vec(), + bounded_u8::<256>(b"from_pallet".to_vec()), + bounded_u8::<32>(b"ecdsa".to_vec()), + bounded_u8::<64>(b"".to_vec()), + bounded_u8::<1024>(b"{}".to_vec()), )?; Ok(()) @@ -169,7 +188,7 @@ fn test_initialize_works() { new_test_ext().execute_with(|| { let admin_account = 1u64; let deposit = 1000u128; - let chain_id = b"test-chain".to_vec(); + let chain_id = bounded_chain_id(b"test-chain".to_vec()); assert_eq!(Signet::admin(), None); @@ -188,7 +207,7 @@ fn test_initialize_works() { Event::Initialized { admin: admin_account, signature_deposit: deposit, - chain_id, + chain_id: chain_id.to_vec(), } .into(), ); @@ -202,11 +221,16 @@ fn test_cannot_initialize_twice() { RuntimeOrigin::signed(1), 1, 1000, - b"test-chain".to_vec() + bounded_chain_id(b"test-chain".to_vec()) )); assert_noop!( - Signet::initialize(RuntimeOrigin::signed(2), 2, 2000, b"test-chain".to_vec()), + Signet::initialize( + RuntimeOrigin::signed(2), + 2, + 2000, + bounded_chain_id(b"test-chain".to_vec()) + ), Error::::AlreadyInitialized ); }); @@ -220,10 +244,10 @@ fn test_cannot_use_before_initialization() { RuntimeOrigin::signed(1), [0u8; 32], 1, - b"path".to_vec(), - b"algo".to_vec(), - b"dest".to_vec(), - b"params".to_vec() + bounded_u8::<256>(b"path".to_vec()), + bounded_u8::<32>(b"algo".to_vec()), + bounded_u8::<64>(b"dest".to_vec()), + bounded_u8::<1024>(b"params".to_vec()) ), Error::::NotInitialized ); @@ -237,19 +261,29 @@ fn test_any_signed_can_initialize_once() { RuntimeOrigin::signed(2), 1, 1000, - b"test-chain".to_vec() + bounded_chain_id(b"test-chain".to_vec()) )); assert_eq!(Signet::admin(), Some(1)); assert_eq!(Signet::signature_deposit(), 1000); assert_noop!( - Signet::initialize(RuntimeOrigin::signed(1), 3, 2000, b"other-chain".to_vec()), + Signet::initialize( + RuntimeOrigin::signed(1), + 3, + 2000, + bounded_chain_id(b"test-chain".to_vec()) + ), Error::::AlreadyInitialized ); assert_noop!( - Signet::initialize(RuntimeOrigin::signed(3), 3, 2000, b"other-chain".to_vec()), + Signet::initialize( + RuntimeOrigin::signed(3), + 3, + 2000, + bounded_chain_id(b"test-chain".to_vec()) + ), Error::::AlreadyInitialized ); @@ -268,7 +302,7 @@ fn test_initialize_sets_deposit() { RuntimeOrigin::signed(1), admin, initial_deposit, - b"test-chain".to_vec() + bounded_chain_id(b"test-chain".to_vec()) )); assert_eq!(Signet::signature_deposit(), initial_deposit); @@ -277,7 +311,7 @@ fn test_initialize_sets_deposit() { Event::Initialized { admin, signature_deposit: initial_deposit, - chain_id: b"test-chain".to_vec(), + chain_id: bounded_chain_id(b"test-chain".to_vec()).to_vec(), } .into(), ); @@ -295,7 +329,7 @@ fn test_update_deposit_as_admin() { RuntimeOrigin::signed(1), admin, initial_deposit, - b"test-chain".to_vec() + bounded_chain_id(b"test-chain".to_vec()) )); assert_ok!(Signet::update_deposit(RuntimeOrigin::signed(admin), new_deposit)); @@ -321,7 +355,7 @@ fn test_non_admin_cannot_update_deposit() { RuntimeOrigin::signed(1), admin, 1000, - b"test-chain".to_vec() + bounded_chain_id(b"test-chain".to_vec()) )); assert_noop!( @@ -354,7 +388,7 @@ fn test_withdraw_funds_as_admin() { RuntimeOrigin::signed(1), admin, 1000, - b"test-chain".to_vec() + bounded_chain_id(b"test-chain".to_vec()) )); let pallet_account = Signet::account_id(); @@ -382,7 +416,7 @@ fn test_non_admin_cannot_withdraw() { RuntimeOrigin::signed(1), admin, 1000, - b"test-chain".to_vec() + bounded_chain_id(b"test-chain".to_vec()) )); let pallet_account = Signet::account_id(); @@ -404,7 +438,7 @@ fn test_cannot_withdraw_more_than_balance() { RuntimeOrigin::signed(1), admin, 1000, - b"test-chain".to_vec() + bounded_chain_id(b"test-chain".to_vec()) )); let pallet_account = Signet::account_id(); @@ -440,16 +474,16 @@ fn test_sign_request_works() { RuntimeOrigin::signed(1), admin, deposit, - b"test-chain".to_vec() + bounded_chain_id(b"test-chain".to_vec()) )); let balance_before = Balances::free_balance(&requester); let payload = [42u8; 32]; let key_version = 1u32; - let path = b"path".to_vec(); - let algo = b"ecdsa".to_vec(); - let dest = b"callback_contract".to_vec(); - let params = b"{}".to_vec(); + let path = bounded_u8::<256>(b"path".to_vec()); + let algo = bounded_u8::<32>(b"ecdsa".to_vec()); + let dest = bounded_u8::<64>(b"callback_contract".to_vec()); + let params = bounded_u8::<1024>(b"{}".to_vec()); assert_ok!(Signet::sign( RuntimeOrigin::signed(requester), @@ -471,11 +505,11 @@ fn test_sign_request_works() { payload, key_version, deposit, - chain_id: b"test-chain".to_vec(), - path, - algo, - dest, - params, + chain_id: bounded_chain_id(b"test-chain".to_vec()).to_vec(), + path: path.to_vec(), + algo: algo.to_vec(), + dest: dest.to_vec(), + params: params.to_vec(), } .into(), ); @@ -493,7 +527,7 @@ fn test_sign_request_insufficient_balance() { RuntimeOrigin::signed(1), admin, deposit, - b"test-chain".to_vec() + bounded_chain_id(b"test-chain".to_vec()) )); assert_noop!( @@ -501,10 +535,10 @@ fn test_sign_request_insufficient_balance() { RuntimeOrigin::signed(poor_user), [0u8; 32], 1, - b"path".to_vec(), - b"algo".to_vec(), - b"dest".to_vec(), - b"params".to_vec() + bounded_u8::<256>(b"path".to_vec()), + bounded_u8::<32>(b"algo".to_vec()), + bounded_u8::<64>(b"dest".to_vec()), + bounded_u8::<1024>(b"params".to_vec()) ), sp_runtime::TokenError::FundsUnavailable ); @@ -519,10 +553,10 @@ fn test_sign_request_before_initialization() { RuntimeOrigin::signed(1), [0u8; 32], 1, - b"path".to_vec(), - b"algo".to_vec(), - b"dest".to_vec(), - b"params".to_vec() + bounded_u8::<256>(b"path".to_vec()), + bounded_u8::<32>(b"algo".to_vec()), + bounded_u8::<64>(b"dest".to_vec()), + bounded_u8::<1024>(b"params".to_vec()) ), Error::::NotInitialized ); @@ -541,7 +575,7 @@ fn test_multiple_sign_requests() { RuntimeOrigin::signed(1), admin, deposit, - b"test-chain".to_vec() + bounded_chain_id(b"test-chain".to_vec()) )); let pallet_account = Signet::account_id(); @@ -550,10 +584,10 @@ fn test_multiple_sign_requests() { RuntimeOrigin::signed(requester1), [1u8; 32], 1, - b"path1".to_vec(), - b"algo".to_vec(), - b"dest".to_vec(), - b"params".to_vec() + bounded_u8::<256>(b"path1".to_vec()), + bounded_u8::<32>(b"algo".to_vec()), + bounded_u8::<64>(b"dest".to_vec()), + bounded_u8::<1024>(b"params".to_vec()) )); assert_eq!(Balances::free_balance(&pallet_account), deposit); @@ -562,10 +596,10 @@ fn test_multiple_sign_requests() { RuntimeOrigin::signed(requester2), [2u8; 32], 2, - b"path2".to_vec(), - b"algo".to_vec(), - b"dest".to_vec(), - b"params".to_vec() + bounded_u8::<256>(b"path2".to_vec()), + bounded_u8::<32>(b"algo".to_vec()), + bounded_u8::<64>(b"dest".to_vec()), + bounded_u8::<1024>(b"params".to_vec()) )); assert_eq!(Balances::free_balance(&pallet_account), deposit * 2); @@ -594,7 +628,7 @@ fn test_sign_respond_works() { RuntimeOrigin::signed(1), admin, deposit, - b"test-chain".to_vec() + bounded_chain_id(b"test-chain".to_vec()) )); let tx_data = b"mock_transaction_data".to_vec(); @@ -603,17 +637,17 @@ fn test_sign_respond_works() { assert_ok!(Signet::sign_respond( RuntimeOrigin::signed(requester), - tx_data.clone(), + bounded_u8::<65536>(tx_data.clone()), slip44_chain_id, 1, - b"path".to_vec(), - b"ecdsa".to_vec(), - b"callback".to_vec(), - b"{}".to_vec(), + bounded_u8::<256>(b"path".to_vec()), + bounded_u8::<32>(b"ecdsa".to_vec()), + bounded_u8::<64>(b"callback".to_vec()), + bounded_u8::<1024>(b"{}".to_vec()), SerializationFormat::AbiJson, - b"schema1".to_vec(), + bounded_u8::<4096>(b"schema1".to_vec()), SerializationFormat::Borsh, - b"schema2".to_vec() + bounded_u8::<4096>(b"schema2".to_vec()) )); assert_eq!(Balances::free_balance(&requester), balance_before - deposit); @@ -636,23 +670,23 @@ fn test_sign_respond_empty_transaction_fails() { RuntimeOrigin::signed(1), admin, 100, - b"test-chain".to_vec() + bounded_chain_id(b"test-chain".to_vec()) )); assert_noop!( Signet::sign_respond( RuntimeOrigin::signed(requester), - vec![], + bounded_u8::<65536>(vec![]), 60, 1, - b"path".to_vec(), - b"algo".to_vec(), - b"dest".to_vec(), - b"params".to_vec(), + bounded_u8::<256>(b"path".to_vec()), + bounded_u8::<32>(b"algo".to_vec()), + bounded_u8::<64>(b"dest".to_vec()), + bounded_u8::<1024>(b"params".to_vec()), SerializationFormat::Borsh, - vec![], + bounded_u8::<4096>(vec![]), SerializationFormat::Borsh, - vec![] + bounded_u8::<4096>(vec![]) ), Error::::InvalidTransaction ); @@ -668,8 +702,8 @@ fn test_respond_single() { assert_ok!(Signet::respond( RuntimeOrigin::signed(responder), - vec![request_id], - vec![signature.clone()] + bounded_array::<100>(vec![request_id]), + bounded_sig::<100>(vec![signature.clone()]) )); System::assert_last_event( @@ -696,8 +730,8 @@ fn test_respond_batch() { assert_ok!(Signet::respond( RuntimeOrigin::signed(responder), - request_ids.clone(), - signatures.clone() + bounded_array::<100>(request_ids.clone()), + bounded_sig::<100>(signatures.clone()) )); let events = System::events(); @@ -717,12 +751,12 @@ fn test_respond_mismatched_arrays_fails() { assert_noop!( Signet::respond( RuntimeOrigin::signed(responder), - vec![[1u8; 32], [2u8; 32]], - vec![ + bounded_array::<100>(vec![[1u8; 32], [2u8; 32]]), + bounded_sig::<100>(vec![ create_test_signature(), create_test_signature(), create_test_signature(), - ] + ]) ), Error::::InvalidInputLength ); @@ -735,12 +769,12 @@ fn test_respond_error_single() { let responder = 1u64; let error_response = ErrorResponse { request_id: [99u8; 32], - error_message: b"Signature generation failed".to_vec(), + error_message: bounded_u8::<1024>(b"Signature generation failed".to_vec()), }; assert_ok!(Signet::respond_error( RuntimeOrigin::signed(responder), - vec![error_response] + bounded_err::<100>(vec![error_response]) )); System::assert_last_event( @@ -761,15 +795,18 @@ fn test_respond_error_batch() { let errors = vec![ ErrorResponse { request_id: [1u8; 32], - error_message: b"Error 1".to_vec(), + error_message: bounded_u8::<1024>(b"Error 1".to_vec()), }, ErrorResponse { request_id: [2u8; 32], - error_message: b"Error 2".to_vec(), + error_message: bounded_u8::<1024>(b"Error 2".to_vec()), }, ]; - assert_ok!(Signet::respond_error(RuntimeOrigin::signed(responder), errors)); + assert_ok!(Signet::respond_error( + RuntimeOrigin::signed(responder), + bounded_err::<100>(errors) + )); let events = System::events(); let error_events = events @@ -791,7 +828,7 @@ fn test_read_respond() { assert_ok!(Signet::read_respond( RuntimeOrigin::signed(responder), request_id, - output.clone(), + bounded_u8::<65536>(output.clone()), signature.clone() )); @@ -812,7 +849,7 @@ fn test_sign_includes_chain_id() { new_test_ext().execute_with(|| { let admin = 1u64; let requester = 2u64; - let chain_id = b"hydradx:polkadot:0".to_vec(); + let chain_id = bounded_chain_id(b"hydradx:polkadot:0".to_vec()); assert_ok!(Signet::initialize( RuntimeOrigin::signed(1), @@ -825,10 +862,10 @@ fn test_sign_includes_chain_id() { RuntimeOrigin::signed(requester), [42u8; 32], 1, - b"path".to_vec(), - b"algo".to_vec(), - b"dest".to_vec(), - b"params".to_vec() + bounded_u8::<256>(b"path".to_vec()), + bounded_u8::<32>(b"algo".to_vec()), + bounded_u8::<64>(b"dest".to_vec()), + bounded_u8::<1024>(b"params".to_vec()) )); let events = System::events(); @@ -844,7 +881,7 @@ fn test_sign_includes_chain_id() { } }); - assert_eq!(sign_event, Some(chain_id)); + assert_eq!(sign_event, Some(chain_id.to_vec())); }); } @@ -856,7 +893,7 @@ fn test_cross_pallet_execution() { RuntimeOrigin::signed(1), 1, 100, - b"test-chain".to_vec() + bounded_chain_id(b"test-chain".to_vec()) )); // Fund the MockCaller pallet's account @@ -873,7 +910,7 @@ fn test_cross_pallet_execution() { payload: [99u8; 32], key_version: 1, deposit: 100, - chain_id: b"test-chain".to_vec(), + chain_id: bounded_chain_id(b"test-chain".to_vec()).to_vec(), path: b"from_pallet".to_vec(), algo: b"ecdsa".to_vec(), dest: b"".to_vec(), diff --git a/signet-ts-client-scripts/key-derivation.ts b/signet-ts-client-scripts/key-derivation.ts index 5cb2682ba0..3ef2d1e371 100644 --- a/signet-ts-client-scripts/key-derivation.ts +++ b/signet-ts-client-scripts/key-derivation.ts @@ -11,18 +11,20 @@ export class KeyDerivation { chainId: string ): string { const ec = new EC("secp256k1"); - const uncompressedRoot = rootPublicKey.slice(2); + + const uncompressedRoot = rootPublicKey.slice(4); const derivationPath = `${this.EPSILON_PREFIX},${chainId},${predecessorId},${path}`; const hash = ethers.keccak256(ethers.toUtf8Bytes(derivationPath)); const scalarHex = hash.slice(2); - - const x = uncompressedRoot.substring(2, 66); - const y = uncompressedRoot.substring(66); + + const x = uncompressedRoot.substring(0, 64); + const y = uncompressedRoot.substring(64); + const oldPoint = ec.curve.point(x, y); const scalarTimesG = ec.g.mul(scalarHex); const newPoint = oldPoint.add(scalarTimesG); - return `04${newPoint.getX().toString("hex").padStart(64, "0")}${newPoint.getY().toString("hex").padStart(64, "0")}`; + return `0x04${newPoint.getX().toString(16).padStart(64, "0")}${newPoint.getY().toString(16).padStart(64, "0")}`; } } \ No newline at end of file diff --git a/signet-ts-client-scripts/signet-client.ts b/signet-ts-client-scripts/signet-client.ts index a946ea9dbe..a67d2c5bcf 100644 --- a/signet-ts-client-scripts/signet-client.ts +++ b/signet-ts-client-scripts/signet-client.ts @@ -182,7 +182,7 @@ export class SignetClient { const expectedAddress = "0x" + - keccak256(Buffer.from(derivedPublicKey.slice(2), "hex")).slice(-40); + keccak256(Buffer.from(derivedPublicKey.slice(4), "hex")).slice(-40); console.log(" Recovered:", recoveredAddress); console.log(" Expected: ", expectedAddress); @@ -209,7 +209,7 @@ export class SignetClient { const expectedAddress = "0x" + - keccak256(Buffer.from(derivedPublicKey.slice(2), "hex")).slice(-40); + keccak256(Buffer.from(derivedPublicKey.slice(4), "hex")).slice(-40); console.log(" Recovered:", recoveredAddress); console.log(" Expected: ", expectedAddress); diff --git a/signet-ts-client-scripts/signet.test.ts b/signet-ts-client-scripts/signet.test.ts index 004f2e1b6a..c77eb70ded 100644 --- a/signet-ts-client-scripts/signet.test.ts +++ b/signet-ts-client-scripts/signet.test.ts @@ -1,8 +1,7 @@ import { ApiPromise, WsProvider, Keyring } from "@polkadot/api"; +import { ISubmittableResult } from "@polkadot/types/types"; import { waitReady } from "@polkadot/wasm-crypto"; -import { u8aToHex } from "@polkadot/util"; import { encodeAddress } from "@polkadot/keyring"; -import { ethers } from "ethers"; import * as crypto from "crypto"; import { SignetClient } from "./signet-client"; import { KeyDerivation } from "./key-derivation"; @@ -13,28 +12,51 @@ describe("Signet Pallet Integration", () => { let client: SignetClient; let alice: any; let alicePolkadotAddress: string; - - const ROOT_PUBLIC_KEY = "0x044eef776e4f257d68983e45b340c2e9546c5df95447900b6aadfec68fb46fdee257e26b8ba383ddba9914b33c60e869265f859566fff4baef283c54d821ca3b64"; + + const ROOT_PUBLIC_KEY = + "0x044eef776e4f257d68983e45b340c2e9546c5df95447900b6aadfec68fb46fdee257e26b8ba383ddba9914b33c60e869265f859566fff4baef283c54d821ca3b64"; const CHAIN_ID = "polkadot:2034"; beforeAll(async () => { await waitReady(); - + api = await ApiPromise.create({ provider: new WsProvider("ws://127.0.0.1:8000"), types: { AffinePoint: { x: "[u8; 32]", y: "[u8; 32]" }, - Signature: { big_r: "AffinePoint", s: "[u8; 32]", recovery_id: "u8" } - } + Signature: { big_r: "AffinePoint", s: "[u8; 32]", recovery_id: "u8" }, + }, }); const keyring = new Keyring({ type: "sr25519" }); alice = keyring.addFromUri("//Alice"); alicePolkadotAddress = encodeAddress(alice.publicKey, 0); - + + const bob = keyring.addFromUri("//Bob"); + const { data: bobBalance } = (await api.query.system.account( + bob.address + )) as any; + + if (bobBalance.free.toBigInt() < 1000000000000n) { + console.log("Funding Bob's account for server responses..."); + + await new Promise((resolve, reject) => { + api.tx.balances + .transferKeepAlive(bob.address, 100000000000000n) + .signAndSend(alice, (result: ISubmittableResult) => { + if (result.dispatchError) { + reject(result.dispatchError); + } else if (result.status.isFinalized) { + console.log("Bob's account funded!"); + resolve(result.status.asFinalized); + } + }); + }); + } + client = new SignetClient(api, alice); await client.ensureInitialized(CHAIN_ID); - }); + }, 60000); afterAll(async () => { await api.disconnect(); @@ -48,25 +70,39 @@ describe("Signet Pallet Integration", () => { path: "testPath", algo: "ecdsa", dest: "", - params: "{}" + params: "{}", }; - const requestId = client.calculateRequestId(alicePolkadotAddress, payload, params, CHAIN_ID); - const derivedKey = KeyDerivation.derivePublicKey(ROOT_PUBLIC_KEY, alicePolkadotAddress, params.path, CHAIN_ID); - + const requestId = client.calculateRequestId( + alicePolkadotAddress, + payload, + params, + CHAIN_ID + ); + const derivedKey = KeyDerivation.derivePublicKey( + ROOT_PUBLIC_KEY, + alicePolkadotAddress, + params.path, + CHAIN_ID + ); + await client.requestSignature(payload, params); - + const signature = await client.waitForSignature(requestId, 30000); expect(signature).toBeDefined(); expect(signature.responder).toBeTruthy(); - + console.log("\n āœ… Signature received from:", signature.responder); - - const isValid = await client.verifySignature(payload, signature.signature, derivedKey); + + const isValid = await client.verifySignature( + payload, + signature.signature, + derivedKey + ); expect(isValid).toBe(true); - + console.log(" āœ… Signature verification PASSED"); - }); + }, 40000); }); describe("SignRespond", () => { @@ -80,47 +116,55 @@ describe("Signet Pallet Integration", () => { to: "0x0000000000000000000000000000000000000000", value: BigInt(0), data: "0x", - accessList: [] + accessList: [], }); const params = { slip44ChainId: 60, keyVersion: 0, path: "testPath", + algo: "", + dest: "", + params: "", schemas: { explorer: { format: 0, schema: "{}" }, - callback: { format: 0, schema: "{}" } - } + callback: { format: 0, schema: "{}" }, + }, }; const requestId = client.calculateSignRespondRequestId( - alicePolkadotAddress, - tx.serialized, + alicePolkadotAddress, + tx.serialized, params ); - + + console.log("Test calculated request ID:", requestId); + const derivedKey = KeyDerivation.derivePublicKey( - ROOT_PUBLIC_KEY, - alicePolkadotAddress, - params.path, + ROOT_PUBLIC_KEY, + alicePolkadotAddress, + params.path, CHAIN_ID ); - + await client.requestTransactionSignature(tx.serialized, params); - + const signature = await client.waitForSignature(requestId, 30000); expect(signature).toBeDefined(); - - console.log("\n āœ… Transaction signature received from:", signature.responder); - + + console.log( + "\n āœ… Transaction signature received from:", + signature.responder + ); + const isValid = await client.verifyTransactionSignature( - tx.transaction, - signature.signature, + tx.transaction, + signature.signature, derivedKey ); expect(isValid).toBe(true); - + console.log(" āœ… Transaction signature verification PASSED"); - }); + }, 40000); }); -}); \ No newline at end of file +}); diff --git a/signet-ts-client-scripts/transaction-builder.ts b/signet-ts-client-scripts/transaction-builder.ts index 353d9322b3..478c9d9f45 100644 --- a/signet-ts-client-scripts/transaction-builder.ts +++ b/signet-ts-client-scripts/transaction-builder.ts @@ -1,4 +1,3 @@ -// transaction-builder.ts import { ethers } from "ethers"; export class TransactionBuilder { @@ -12,15 +11,18 @@ export class TransactionBuilder { value: bigint; data: string; accessList: any[]; - }): { transaction: ethers.Transaction; serialized: number[] } { + }): { transaction: ethers.Transaction; serialized: number[]; unwrapped: number[] } { const transaction = ethers.Transaction.from({ type: 2, ...params }); + const fullSerialized = Array.from(ethers.getBytes(transaction.unsignedSerialized)); + return { transaction, - serialized: Array.from(ethers.getBytes(transaction.unsignedSerialized)) + serialized: fullSerialized, + unwrapped: fullSerialized.slice(1) }; } } \ No newline at end of file From b58b22e45d13ccf6b5b8e67634a2464f8928e624 Mon Sep 17 00:00:00 2001 From: Osman Abdelnasir Date: Tue, 7 Oct 2025 01:17:01 +0400 Subject: [PATCH 26/32] feat: erc 20 vault and tests --- Cargo.lock | 809 ++++++++++++++++---- Cargo.toml | 3 + pallets/erc20-vault/Cargo.toml | 73 ++ pallets/erc20-vault/src/lib.rs | 464 +++++++++++ pallets/erc20-vault/src/tests.rs | 750 ++++++++++++++++++ runtime/hydradx/Cargo.toml | 4 + runtime/hydradx/src/assets.rs | 9 + runtime/hydradx/src/lib.rs | 2 + signet-ts-client-scripts/README.md | 102 ++- signet-ts-client-scripts/erc20vault.test.ts | 483 ++++++++++++ 10 files changed, 2520 insertions(+), 179 deletions(-) create mode 100644 pallets/erc20-vault/Cargo.toml create mode 100644 pallets/erc20-vault/src/lib.rs create mode 100644 pallets/erc20-vault/src/tests.rs create mode 100644 signet-ts-client-scripts/erc20vault.test.ts diff --git a/Cargo.lock b/Cargo.lock index 5d06f35a23..13284cab62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -91,6 +91,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "ahash" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.11", + "once_cell", + "version_check", +] + [[package]] name = "ahash" version = "0.8.12" @@ -98,7 +109,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.2.11", "once_cell", "version_check", "zerocopy", @@ -119,6 +130,122 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +[[package]] +name = "alloy-json-abi" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc05b04ac331a9f07e3a4036ef7926e49a8bf84a99a1ccfc7e2ab55a5fcbb372" +dependencies = [ + "alloy-primitives", + "alloy-sol-type-parser", + "serde", +] + +[[package]] +name = "alloy-primitives" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccb3ead547f4532bc8af961649942f0b9c16ee9226e26caa3f38420651cc0bf4" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "k256", + "keccak-asm", + "proptest", + "rand 0.8.5", + "ruint", + "serde", + "tiny-keccak", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f70d83b765fdc080dbcd4f4db70d8d23fe4761f2f02ebfa9146b833900634b4" +dependencies = [ + "arrayvec 0.7.4", + "bytes", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b40397ddcdcc266f59f959770f601ce1280e699a91fc1862f29cef91707cd09" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "867a5469d61480fea08c7333ffeca52d5b621f5ca2e44f271b117ec1fc9a0525" +dependencies = [ + "alloy-json-abi", + "alloy-sol-macro-input", + "const-hex", + "heck 0.5.0", + "indexmap 2.7.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.106", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e482dc33a32b6fadbc0f599adea520bd3aaa585c141a80b404d0a3e3fa72528" +dependencies = [ + "alloy-json-abi", + "const-hex", + "dunce", + "heck 0.5.0", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.106", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-type-parser" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbcba3ca07cf7975f15d871b721fb18031eec8bce51103907f6dcce00b255d98" +dependencies = [ + "serde", + "winnow 0.6.20", +] + +[[package]] +name = "alloy-sol-types" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a49042c6d3b66a9fe6b2b5a8bf0d39fc2ae1ee0310a2a26ffedd79fb097878dd" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-macro", + "const-hex", + "serde", +] + [[package]] name = "always-assert" version = "0.1.3" @@ -231,8 +358,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb00293ba84f51ce3bd026bd0de55899c4e68f0a39a5728cebae3a73ffdc0a4f" dependencies = [ "ark-ec", - "ark-ff", - "ark-std", + "ark-ff 0.4.2", + "ark-std 0.4.0", ] [[package]] @@ -242,9 +369,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" dependencies = [ "ark-ec", - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", ] [[package]] @@ -253,10 +380,10 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" dependencies = [ - "ark-ff", + "ark-ff 0.4.2", "ark-poly", - "ark-serialize", - "ark-std", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "hashbrown 0.13.2", "itertools 0.10.5", @@ -333,26 +460,54 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + [[package]] name = "ark-ff" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" dependencies = [ - "ark-ff-asm", - "ark-ff-macros", - "ark-serialize", - "ark-std", + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "digest 0.10.7", "itertools 0.10.5", "num-bigint", "num-traits", "paste", - "rustc_version", + "rustc_version 0.4.0", "zeroize", ] +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "ark-ff-asm" version = "0.4.2" @@ -363,6 +518,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint", + "num-traits", + "quote", + "syn 1.0.109", +] + [[package]] name = "ark-ff-macros" version = "0.4.2" @@ -395,9 +562,9 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" dependencies = [ - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "hashbrown 0.13.2", ] @@ -426,6 +593,16 @@ dependencies = [ "digest 0.9.0", ] +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + [[package]] name = "ark-serialize" version = "0.4.2" @@ -433,7 +610,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ "ark-serialize-derive", - "ark-std", + "ark-std 0.4.0", "digest 0.10.7", "num-bigint", ] @@ -449,6 +626,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + [[package]] name = "ark-std" version = "0.4.0" @@ -456,7 +643,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ "num-traits", - "rand", + "rand 0.8.5", ] [[package]] @@ -965,18 +1152,18 @@ dependencies = [ [[package]] name = "bit-set" -version = "0.5.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.6.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitcoin-internals" @@ -1125,6 +1312,30 @@ dependencies = [ "tracing", ] +[[package]] +name = "borsh" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" +dependencies = [ + "borsh-derive", + "cfg_aliases 0.2.1", + "hashbrown 0.12.3", +] + +[[package]] +name = "borsh-derive" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" +dependencies = [ + "once_cell", + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "bounded-collections" version = "0.2.0" @@ -1478,6 +1689,9 @@ name = "bytes" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +dependencies = [ + "serde", +] [[package]] name = "bzip2-sys" @@ -1602,6 +1816,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chacha" version = "0.3.0" @@ -1783,7 +2003,7 @@ checksum = "71367d3385c716342014ad17e3d19f7788ae514885a1f4c24f500260fb365e1a" dependencies = [ "libc", "once_cell", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -1873,6 +2093,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "const-hex" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6407bff74dea37e0fa3dc1c1c974e5d46405f0c987bf9997a0762adce71eda6" +dependencies = [ + "cfg-if", + "cpufeatures", + "proptest", + "serde_core", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -1894,7 +2126,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom", + "getrandom 0.2.11", "once_cell", "tiny-keccak", ] @@ -2195,7 +2427,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array 0.14.7", - "rand_core", + "rand_core 0.6.4", "subtle 2.6.1", "zeroize", ] @@ -2207,7 +2439,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array 0.14.7", - "rand_core", + "rand_core 0.6.4", "typenum", ] @@ -2456,7 +2688,7 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-overseer", "polkadot-primitives", - "rand", + "rand 0.8.5", "sc-client-api", "sc-consensus", "sp-api", @@ -2853,7 +3085,7 @@ dependencies = [ "parity-scale-codec", "pin-project", "polkadot-overseer", - "rand", + "rand 0.8.5", "sc-client-api", "sc-rpc-api", "sc-service", @@ -2901,7 +3133,7 @@ dependencies = [ "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", - "rustc_version", + "rustc_version 0.4.0", "subtle 2.6.1", "zeroize", ] @@ -2925,7 +3157,7 @@ checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" dependencies = [ "byteorder", "digest 0.9.0", - "rand_core", + "rand_core 0.6.4", "subtle-ng", "zeroize", ] @@ -3091,7 +3323,7 @@ dependencies = [ "convert_case 0.4.0", "proc-macro2", "quote", - "rustc_version", + "rustc_version 0.4.0", "syn 2.0.106", ] @@ -3235,6 +3467,12 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clonable" version = "0.9.0" @@ -3295,7 +3533,7 @@ checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", - "rand_core", + "rand_core 0.6.4", "serde", "sha2 0.10.8", "subtle 2.6.1", @@ -3312,7 +3550,7 @@ dependencies = [ "ed25519", "hashbrown 0.14.3", "hex", - "rand_core", + "rand_core 0.6.4", "sha2 0.10.8", "zeroize", ] @@ -3336,7 +3574,7 @@ dependencies = [ "generic-array 0.14.7", "group", "pkcs8", - "rand_core", + "rand_core 0.6.4", "sec1", "serdect", "subtle 2.6.1", @@ -3669,6 +3907,28 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec 0.7.4", + "auto_impl", + "bytes", +] + +[[package]] +name = "fastrlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" +dependencies = [ + "arrayvec 0.7.4", + "auto_impl", + "bytes", +] + [[package]] name = "fatality" version = "0.1.1" @@ -3786,7 +4046,7 @@ dependencies = [ "pallet-evm", "parity-scale-codec", "prometheus", - "rand", + "rand 0.8.5", "rlp", "sc-client-api", "sc-consensus-aura", @@ -3866,7 +4126,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ - "rand_core", + "rand_core 0.6.4", "subtle 2.6.1", ] @@ -3944,7 +4204,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ "byteorder", - "rand", + "rand 0.8.5", "rustc-hex", "static_assertions", ] @@ -4174,7 +4434,7 @@ dependencies = [ "linked-hash-map", "log", "parity-scale-codec", - "rand", + "rand 0.8.5", "rand_pcg", "sc-block-builder", "sc-chain-spec", @@ -4676,7 +4936,19 @@ checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.7+wasi-0.2.4", ] [[package]] @@ -4685,8 +4957,8 @@ version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea1015b5a70616b688dc230cfe50c8af89d972cb132d5a622814d29773b10b9" dependencies = [ - "rand", - "rand_core", + "rand 0.8.5", + "rand_core 0.6.4", ] [[package]] @@ -4751,7 +5023,7 @@ dependencies = [ "parking_lot 0.12.3", "portable-atomic", "quanta", - "rand", + "rand 0.8.5", "smallvec", "spinning_top", ] @@ -4763,7 +5035,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core", + "rand_core 0.6.4", "subtle 2.6.1", ] @@ -4855,7 +5127,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.12", + "ahash 0.8.6", ] [[package]] @@ -4864,7 +5136,7 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ - "ahash 0.8.12", + "ahash 0.8.6", "allocator-api2", "serde", ] @@ -5077,7 +5349,7 @@ dependencies = [ "parity-scale-codec", "primitive-types 0.12.2", "proptest", - "rand", + "rand 0.8.5", "rand_xoshiro", "rug", "scale-info", @@ -5309,6 +5581,7 @@ dependencies = [ "pallet-dynamic-fees", "pallet-elections-phragmen", "pallet-ema-oracle", + "pallet-erc20-vault", "pallet-ethereum", "pallet-evm", "pallet-evm-accounts", @@ -5609,7 +5882,7 @@ dependencies = [ "http 0.2.11", "hyper 0.14.31", "log", - "rand", + "rand 0.8.5", "tokio", "url", "xmltree", @@ -5982,7 +6255,7 @@ dependencies = [ "jsonrpsee-types", "parking_lot 0.12.3", "pin-project", - "rand", + "rand 0.8.5", "rustc-hash 2.1.0", "serde", "serde_json", @@ -6105,6 +6378,16 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "keccak-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + [[package]] name = "keystream" version = "1.0.0" @@ -6202,7 +6485,7 @@ dependencies = [ "either", "futures", "futures-timer", - "getrandom", + "getrandom 0.2.11", "instant", "libp2p-allow-block-list", "libp2p-connection-limits", @@ -6273,7 +6556,7 @@ dependencies = [ "parking_lot 0.12.3", "pin-project", "quick-protobuf", - "rand", + "rand 0.8.5", "rw-stream-sink", "smallvec", "thiserror 1.0.63", @@ -6331,7 +6614,7 @@ dependencies = [ "hkdf", "multihash 0.19.1", "quick-protobuf", - "rand", + "rand 0.8.5", "sha2 0.10.8", "thiserror 1.0.63", "tracing", @@ -6358,7 +6641,7 @@ dependencies = [ "log", "quick-protobuf", "quick-protobuf-codec", - "rand", + "rand 0.8.5", "sha2 0.10.8", "smallvec", "thiserror 1.0.63", @@ -6380,7 +6663,7 @@ dependencies = [ "libp2p-identity", "libp2p-swarm", "log", - "rand", + "rand 0.8.5", "smallvec", "socket2 0.5.8", "tokio", @@ -6421,7 +6704,7 @@ dependencies = [ "multihash 0.19.1", "once_cell", "quick-protobuf", - "rand", + "rand 0.8.5", "sha2 0.10.8", "snow", "static_assertions", @@ -6444,7 +6727,7 @@ dependencies = [ "libp2p-identity", "libp2p-swarm", "log", - "rand", + "rand 0.8.5", "void", ] @@ -6464,7 +6747,7 @@ dependencies = [ "log", "parking_lot 0.12.3", "quinn 0.10.2", - "rand", + "rand 0.8.5", "ring 0.16.20", "rustls 0.21.10", "socket2 0.5.8", @@ -6485,7 +6768,7 @@ dependencies = [ "libp2p-identity", "libp2p-swarm", "log", - "rand", + "rand 0.8.5", "smallvec", "void", ] @@ -6507,7 +6790,7 @@ dependencies = [ "log", "multistream-select", "once_cell", - "rand", + "rand 0.8.5", "smallvec", "tokio", "void", @@ -6665,7 +6948,7 @@ dependencies = [ "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", - "rand", + "rand 0.8.5", "serde", "sha2 0.9.9", "typenum", @@ -6822,7 +7105,7 @@ dependencies = [ "prost 0.12.6", "prost-build 0.11.9", "quinn 0.9.4", - "rand", + "rand 0.8.5", "rcgen", "ring 0.16.20", "rustls 0.20.9", @@ -7112,7 +7395,7 @@ checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" dependencies = [ "byteorder", "keccak", - "rand_core", + "rand_core 0.6.4", "zeroize", ] @@ -7123,7 +7406,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69672161530e8aeca1d1400fbf3f1a1747ff60ea604265a4e906c2442df20532" dependencies = [ "futures", - "rand", + "rand 0.8.5", "thrift", ] @@ -7149,7 +7432,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -7170,8 +7453,8 @@ dependencies = [ "lioness", "log", "parking_lot 0.12.3", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "rand_distr", "subtle 2.6.1", "thiserror 1.0.63", @@ -7454,7 +7737,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bddcd3bf5144b6392de80e04c347cd7fab2508f6df16a85fc496ecd5cec39bc" dependencies = [ - "rand", + "rand 0.8.5", ] [[package]] @@ -7560,7 +7843,7 @@ checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ "bitflags 2.6.0", "cfg-if", - "cfg_aliases", + "cfg_aliases 0.1.1", "libc", ] @@ -8681,7 +8964,7 @@ dependencies = [ "pallet-balances", "pallet-session", "parity-scale-codec", - "rand", + "rand 0.8.5", "scale-info", "sp-runtime", "sp-staking", @@ -8899,7 +9182,7 @@ dependencies = [ "pretty_assertions", "primitive-types 0.12.2", "primitives", - "rand", + "rand 0.8.5", "scale-info", "smallvec", "sp-api", @@ -9092,7 +9375,7 @@ dependencies = [ "log", "pallet-election-provider-support-benchmarking", "parity-scale-codec", - "rand", + "rand 0.8.5", "scale-info", "sp-arithmetic", "sp-core", @@ -9160,6 +9443,31 @@ dependencies = [ "test-utils", ] +[[package]] +name = "pallet-erc20-vault" +version = "1.0.0" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "borsh", + "frame-benchmarking", + "frame-support", + "frame-system", + "hex", + "pallet-balances", + "pallet-build-evm-tx", + "pallet-signet", + "parity-scale-codec", + "scale-info", + "secp256k1 0.27.0", + "serde", + "serde_json", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-ethereum" version = "4.0.0-dev" @@ -9674,7 +9982,7 @@ dependencies = [ "pretty_assertions", "primitives", "proptest", - "rand", + "rand 0.8.5", "scale-info", "sp-arithmetic", "sp-core", @@ -10528,7 +10836,7 @@ dependencies = [ "pallet-session", "pallet-staking 38.0.0", "parity-scale-codec", - "rand", + "rand 0.8.5", "sp-runtime", "sp-session", ] @@ -10559,7 +10867,7 @@ dependencies = [ "frame-system", "log", "parity-scale-codec", - "rand_chacha", + "rand_chacha 0.3.1", "scale-info", "sp-arithmetic", "sp-io", @@ -10634,7 +10942,7 @@ dependencies = [ "pallet-authorship", "pallet-session", "parity-scale-codec", - "rand_chacha", + "rand_chacha 0.3.1", "scale-info", "serde", "sp-application-crypto", @@ -11177,8 +11485,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e69bf016dc406eff7d53a7d3f7cf1c2e72c82b9088aac1118591e36dd2cd3e9" dependencies = [ "bitcoin_hashes 0.13.0", - "rand", - "rand_core", + "rand 0.8.5", + "rand_core 0.6.4", "serde", "unicode-normalization", ] @@ -11204,7 +11512,7 @@ dependencies = [ "lz4", "memmap2 0.5.10", "parking_lot 0.12.3", - "rand", + "rand 0.8.5", "siphasher", "snap", "winapi", @@ -11339,7 +11647,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle 2.6.1", ] @@ -11544,7 +11852,7 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-util", "polkadot-primitives", - "rand", + "rand 0.8.5", "tracing-gum", ] @@ -11560,7 +11868,7 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-util", "polkadot-primitives", - "rand", + "rand 0.8.5", "tracing-gum", ] @@ -11579,7 +11887,7 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-util", "polkadot-primitives", - "rand", + "rand 0.8.5", "sc-network", "schnellru", "sp-core", @@ -11603,7 +11911,7 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-util", "polkadot-primitives", - "rand", + "rand 0.8.5", "sc-network", "schnellru", "thiserror 1.0.63", @@ -11733,8 +12041,8 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-util", "polkadot-primitives", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "sc-network", "sc-network-common", "sp-application-crypto", @@ -11804,9 +12112,9 @@ dependencies = [ "polkadot-node-subsystem-util", "polkadot-overseer", "polkadot-primitives", - "rand", - "rand_chacha", - "rand_core", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", "sc-keystore", "schnellru", "schnorrkel 0.11.4", @@ -12016,7 +12324,7 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-parachain-primitives", "polkadot-primitives", - "rand", + "rand 0.8.5", "slotmap", "sp-core", "tempfile", @@ -12136,7 +12444,7 @@ dependencies = [ "polkadot-node-jaeger", "polkadot-node-primitives", "polkadot-primitives", - "rand", + "rand 0.8.5", "sc-authority-discovery", "sc-network", "sc-network-types", @@ -12238,7 +12546,7 @@ dependencies = [ "polkadot-overseer", "polkadot-primitives", "prioritized-metered-channel", - "rand", + "rand 0.8.5", "sc-client-api", "schnellru", "sp-application-crypto", @@ -12507,8 +12815,8 @@ dependencies = [ "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-metrics", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "scale-info", "serde", "sp-api", @@ -13463,17 +13771,17 @@ dependencies = [ [[package]] name = "proptest" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" dependencies = [ "bit-set", "bit-vec", "bitflags 2.6.0", "lazy_static", "num-traits", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "rand_xorshift", "regex-syntax 0.8.2", "rusty-fork", @@ -13607,7 +13915,7 @@ dependencies = [ "libc", "once_cell", "raw-cpuid", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "web-sys", "winapi", ] @@ -13683,7 +13991,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94b0b33c13a79f669c85defaf4c275dc86a0c0372807d0ca3d78e0bb87274863" dependencies = [ "bytes", - "rand", + "rand 0.8.5", "ring 0.16.20", "rustc-hash 1.1.0", "rustls 0.20.9", @@ -13701,7 +14009,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "141bf7dfde2fbc246bfd3fe12f2455aa24b0fbd9af535d8c86c7bd1381ff2b1a" dependencies = [ "bytes", - "rand", + "rand 0.8.5", "ring 0.16.20", "rustc-hash 1.1.0", "rustls 0.21.10", @@ -13746,6 +14054,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "radium" version = "0.7.0" @@ -13759,8 +14073,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -13770,7 +14094,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -13779,7 +14113,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.11", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", ] [[package]] @@ -13789,7 +14132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" dependencies = [ "num-traits", - "rand", + "rand 0.8.5", ] [[package]] @@ -13798,7 +14141,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" dependencies = [ - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -13807,7 +14150,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -13816,7 +14159,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" dependencies = [ - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -13890,7 +14233,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ - "getrandom", + "getrandom 0.2.11", "libredox", "thiserror 1.0.63", ] @@ -14038,7 +14381,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" dependencies = [ "cc", - "getrandom", + "getrandom 0.2.11", "libc", "spin 0.9.8", "untrusted 0.9.0", @@ -14257,6 +14600,39 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ruint" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecb38f82477f20c5c3d62ef52d7c4e536e38ea9b73fb570a20c5cae0e14bcf6" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "bytes", + "fastrlp 0.3.1", + "fastrlp 0.4.0", + "num-bigint", + "num-integer", + "num-traits", + "parity-scale-codec", + "primitive-types 0.12.2", + "proptest", + "rand 0.8.5", + "rand 0.9.2", + "rlp", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + [[package]] name = "runtime-integration-tests" version = "1.57.2" @@ -14435,6 +14811,15 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -14730,7 +15115,7 @@ dependencies = [ "parity-scale-codec", "prost 0.12.6", "prost-build 0.12.6", - "rand", + "rand 0.8.5", "sc-client-api", "sc-network", "sc-network-types", @@ -14835,7 +15220,7 @@ dependencies = [ "names", "parity-bip39", "parity-scale-codec", - "rand", + "rand 0.8.5", "regex", "rpassword", "sc-client-api", @@ -15098,7 +15483,7 @@ name = "sc-consensus-grandpa" version = "0.30.0" source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409-patch6#1ed662757b03c4f17dc2429c1df84e975d98513a" dependencies = [ - "ahash 0.8.12", + "ahash 0.8.6", "array-bytes", "async-trait", "dyn-clone", @@ -15109,7 +15494,7 @@ dependencies = [ "log", "parity-scale-codec", "parking_lot 0.12.3", - "rand", + "rand 0.8.5", "sc-block-builder", "sc-chain-spec", "sc-client-api", @@ -15333,7 +15718,7 @@ dependencies = [ "pin-project", "prost 0.12.6", "prost-build 0.12.6", - "rand", + "rand 0.8.5", "sc-client-api", "sc-network-common", "sc-network-types", @@ -15379,7 +15764,7 @@ name = "sc-network-gossip" version = "0.45.0" source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409-patch6#1ed662757b03c4f17dc2429c1df84e975d98513a" dependencies = [ - "ahash 0.8.12", + "ahash 0.8.6", "futures", "futures-timer", "log", @@ -15482,7 +15867,7 @@ dependencies = [ "log", "multiaddr 0.18.2", "multihash 0.19.1", - "rand", + "rand 0.8.5", "thiserror", "zeroize", ] @@ -15504,7 +15889,7 @@ dependencies = [ "once_cell", "parity-scale-codec", "parking_lot 0.12.3", - "rand", + "rand 0.8.5", "sc-client-api", "sc-network", "sc-network-common", @@ -15619,7 +16004,7 @@ dependencies = [ "log", "parity-scale-codec", "parking_lot 0.12.3", - "rand", + "rand 0.8.5", "sc-chain-spec", "sc-client-api", "sc-rpc", @@ -15653,7 +16038,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "pin-project", - "rand", + "rand 0.8.5", "sc-chain-spec", "sc-client-api", "sc-client-db", @@ -15754,7 +16139,7 @@ dependencies = [ "futures", "libc", "log", - "rand", + "rand 0.8.5", "rand_pcg", "regex", "sc-telemetry", @@ -15777,7 +16162,7 @@ dependencies = [ "log", "parking_lot 0.12.3", "pin-project", - "rand", + "rand 0.8.5", "sc-network", "sc-utils", "serde", @@ -15954,7 +16339,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9a8ef13a93c54d20580de1e5c413e624e53121d42fc7e2c11d10ef7f8b02367" dependencies = [ - "ahash 0.8.12", + "ahash 0.8.6", "cfg-if", "hashbrown 0.13.2", ] @@ -15969,7 +16354,7 @@ dependencies = [ "arrayvec 0.7.4", "curve25519-dalek-ng", "merlin", - "rand_core", + "rand_core 0.6.4", "sha2 0.9.9", "subtle-ng", "zeroize", @@ -15987,7 +16372,7 @@ dependencies = [ "curve25519-dalek", "getrandom_or_panic", "merlin", - "rand_core", + "rand_core 0.6.4", "serde_bytes", "sha2 0.10.8", "subtle 2.6.1", @@ -16056,7 +16441,7 @@ dependencies = [ "crc", "fxhash", "log", - "rand", + "rand 0.8.5", "slab", "thiserror 1.0.63", ] @@ -16085,13 +16470,31 @@ dependencies = [ "libc", ] +[[package]] +name = "secp256k1" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" +dependencies = [ + "secp256k1-sys 0.8.2", +] + [[package]] name = "secp256k1" version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" dependencies = [ - "secp256k1-sys", + "secp256k1-sys 0.9.2", +] + +[[package]] +name = "secp256k1-sys" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4473013577ec77b4ee3668179ef1186df3146e2cf2d927bd200974c6fe60fd99" +dependencies = [ + "cc", ] [[package]] @@ -16142,7 +16545,16 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537" dependencies = [ - "semver-parser", + "semver-parser 0.7.0", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser 0.10.3", ] [[package]] @@ -16160,6 +16572,15 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "semver-parser" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" +dependencies = [ + "pest", +] + [[package]] name = "send_wrapper" version = "0.6.0" @@ -16168,10 +16589,11 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] @@ -16203,11 +16625,20 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -16325,6 +16756,16 @@ dependencies = [ "keccak", ] +[[package]] +name = "sha3-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +dependencies = [ + "cc", + "cfg-if", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -16356,7 +16797,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest 0.10.7", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -16519,8 +16960,8 @@ dependencies = [ "pbkdf2", "pin-project", "poly1305", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "ruzstd", "schnorrkel 0.10.2", "serde", @@ -16562,8 +17003,8 @@ dependencies = [ "no-std-net", "parking_lot 0.12.3", "pin-project", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "serde", "serde_json", "siphasher", @@ -16589,9 +17030,9 @@ dependencies = [ "blake2 0.10.6", "chacha20poly1305", "curve25519-dalek", - "rand_core", + "rand_core 0.6.4", "ring 0.17.7", - "rustc_version", + "rustc_version 0.4.0", "sha2 0.10.8", "subtle 2.6.1", ] @@ -16938,7 +17379,7 @@ dependencies = [ "futures", "httparse", "log", - "rand", + "rand 0.8.5", "sha-1 0.9.8", ] @@ -16954,7 +17395,7 @@ dependencies = [ "http 1.2.0", "httparse", "log", - "rand", + "rand 0.8.5", "sha1", ] @@ -17198,10 +17639,10 @@ dependencies = [ "parking_lot 0.12.3", "paste", "primitive-types 0.12.2", - "rand", + "rand 0.8.5", "scale-info", "schnorrkel 0.11.4", - "secp256k1", + "secp256k1 0.28.2", "secrecy", "serde", "sp-crypto-hashing", @@ -17336,7 +17777,7 @@ dependencies = [ "parity-scale-codec", "polkavm-derive 0.9.1", "rustversion", - "secp256k1", + "secp256k1 0.28.2", "sp-core", "sp-crypto-hashing", "sp-externalities", @@ -17473,7 +17914,7 @@ dependencies = [ "num-traits", "parity-scale-codec", "paste", - "rand", + "rand 0.8.5", "scale-info", "serde", "simple-mermaid", @@ -17554,7 +17995,7 @@ dependencies = [ "log", "parity-scale-codec", "parking_lot 0.12.3", - "rand", + "rand 0.8.5", "smallvec", "sp-core", "sp-externalities", @@ -17575,7 +18016,7 @@ dependencies = [ "ed25519-dalek", "hkdf", "parity-scale-codec", - "rand", + "rand 0.8.5", "scale-info", "sha2 0.10.8", "sp-api", @@ -17657,14 +18098,14 @@ name = "sp-trie" version = "37.0.0" source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409-patch6#1ed662757b03c4f17dc2429c1df84e975d98513a" dependencies = [ - "ahash 0.8.12", + "ahash 0.8.6", "hash-db", "lazy_static", "memory-db", "nohash-hasher", "parity-scale-codec", "parking_lot 0.12.3", - "rand", + "rand 0.8.5", "scale-info", "schnellru", "sp-core", @@ -17920,7 +18361,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a2a1c578e98c1c16fc3b8ec1328f7659a500737d7a0c6d625e73e830ff9c1f6" dependencies = [ "bitflags 1.3.2", - "cfg_aliases", + "cfg_aliases 0.1.1", "libc", "parking_lot 0.11.2", "parking_lot_core 0.8.6", @@ -17934,7 +18375,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70a2595fc3aa78f2d0e45dd425b22282dd863273761cc77780914b2cf3003acf" dependencies = [ - "cfg_aliases", + "cfg_aliases 0.1.1", "memchr", "proc-macro2", "quote", @@ -18062,7 +18503,7 @@ dependencies = [ "byteorder", "crunchy", "lazy_static", - "rand", + "rand 0.8.5", "rustc-hex", ] @@ -18219,6 +18660,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn-solidity" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c837dc8852cb7074e46b444afb81783140dab12c58867b49fb3898fbafedf7ea" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "synstructure" version = "0.12.6" @@ -18596,7 +19049,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f" dependencies = [ "pin-project", - "rand", + "rand 0.8.5", "tokio", ] @@ -18903,7 +19356,7 @@ dependencies = [ "idna 0.2.3", "ipnet", "lazy_static", - "rand", + "rand 0.8.5", "smallvec", "socket2 0.4.10", "thiserror 1.0.63", @@ -18929,7 +19382,7 @@ dependencies = [ "idna 0.4.0", "ipnet", "once_cell", - "rand", + "rand 0.8.5", "smallvec", "thiserror 1.0.63", "tinyvec", @@ -18950,7 +19403,7 @@ dependencies = [ "lru-cache", "once_cell", "parking_lot 0.12.3", - "rand", + "rand 0.8.5", "resolv-conf", "smallvec", "thiserror 1.0.63", @@ -18998,7 +19451,7 @@ dependencies = [ "http 0.2.11", "httparse", "log", - "rand", + "rand 0.8.5", "rustls 0.21.10", "sha1", "thiserror 1.0.63", @@ -19020,7 +19473,7 @@ checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if", "digest 0.10.7", - "rand", + "rand 0.8.5", "static_assertions", ] @@ -19205,15 +19658,15 @@ dependencies = [ "ark-bls12-377", "ark-bls12-381", "ark-ec", - "ark-ff", - "ark-serialize", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", "ark-serialize-derive", "arrayref", "constcat", "digest 0.10.7", - "rand", - "rand_chacha", - "rand_core", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", "sha2 0.10.8", "sha3", "thiserror 1.0.63", @@ -19260,6 +19713,24 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.89" @@ -19655,7 +20126,7 @@ dependencies = [ "memfd", "memoffset 0.8.0", "paste", - "rand", + "rand 0.8.5", "rustix 0.36.17", "wasmtime-asm-macros", "wasmtime-environ", @@ -20169,6 +20640,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + [[package]] name = "wyz" version = "0.5.1" @@ -20185,7 +20662,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" dependencies = [ "curve25519-dalek", - "rand_core", + "rand_core 0.6.4", "serde", "zeroize", ] @@ -20332,7 +20809,7 @@ dependencies = [ "nohash-hasher", "parking_lot 0.12.3", "pin-project", - "rand", + "rand 0.8.5", "static_assertions", ] diff --git a/Cargo.toml b/Cargo.toml index 30a05df89e..1b63243d48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ members = [ 'pallets/hsm', 'pallets/build-evm-tx', "pallets/signet", + 'pallets/erc20-vault', ] resolver = "2" @@ -60,6 +61,7 @@ codec = { package = "parity-scale-codec", version = "3.6.12", default-features = scale-info = { version = "2.11.1", default-features = false, features = ["derive"] } serde = { version = "1.0.209", default-features = false } primitive-types = { version = "0.12.2", default-features = false } +borsh = { version = "1.5.7", default-features = false, features = ["derive"] } affix = "0.1.2" alloy-primitives = { version = "0.7", default-features = false } @@ -162,6 +164,7 @@ pallet-hsm = { path = "pallets/hsm", default-features = false } pallet-build-evm-tx = { path = "pallets/build-evm-tx", default-features = false } pallet-parameters = { path = "pallets/parameters", default-features = false } pallet-signet = { path = "pallets/signet", default-features = false } +pallet-erc20-vault = { path = "pallets/erc20-vault", default-features = false } hydra-dx-build-script-utils = { path = "utils/build-script-utils", default-features = false } scraper = { path = "scraper", default-features = false } diff --git a/pallets/erc20-vault/Cargo.toml b/pallets/erc20-vault/Cargo.toml new file mode 100644 index 0000000000..36a5ff116c --- /dev/null +++ b/pallets/erc20-vault/Cargo.toml @@ -0,0 +1,73 @@ +[package] +name = "pallet-erc20-vault" +version = "1.0.0" +authors = ["Signet"] +edition = "2021" +license = "Apache-2.0" +description = "ERC20 vault pallet for cross-chain token deposits via MPC" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true } +scale-info = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +hex = { version = "0.4", default-features = false, features = ["alloc"] } + +# Ethereum ABI encoding +alloy-sol-types = { version = "=0.7.6", default-features = false, features = ["json"] } +alloy-primitives = { version = "=0.7.7", default-features = false } + +serde = { version = "1.0", default-features = false, features = ["derive"] } +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } + +pallet-build-evm-tx = { path = "../build-evm-tx", default-features = false } +pallet-signet = { path = "../signet", default-features = false } + +frame-benchmarking = { workspace = true, optional = true } + +[dev-dependencies] +pallet-balances = { workspace = true, features = ["std"] } +sp-io = { workspace = true, features = ["std"] } +sp-core = { workspace = true, features = ["std"] } +sp-runtime = { workspace = true, features = ["std"] } +secp256k1 = { version = "0.27", features = ["recovery", "global-context"] } + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "frame-support/std", + "frame-system/std", + "sp-runtime/std", + "sp-std/std", + "sp-core/std", + "sp-io/std", + "frame-benchmarking?/std", + "alloy-sol-types/std", + "alloy-primitives/std", + "serde/std", + "serde_json/std", + "pallet-build-evm-tx/std", + "pallet-signet/std", + "hex/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] + +[target.'cfg(not(feature = "std"))'.dependencies] +borsh = { version = "1.5", default-features = false, features = ["derive", "hashbrown"] } + +[target.'cfg(feature = "std")'.dependencies] +borsh = { version = "1.5", default-features = false, features = ["derive", "std"] } \ No newline at end of file diff --git a/pallets/erc20-vault/src/lib.rs b/pallets/erc20-vault/src/lib.rs new file mode 100644 index 0000000000..0041792756 --- /dev/null +++ b/pallets/erc20-vault/src/lib.rs @@ -0,0 +1,464 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; +use alloc::{format, string::String, vec}; + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::pallet_prelude::*; +use frame_support::traits::Currency; +use frame_support::PalletId; +use frame_support::{dispatch::DispatchResult, pallet_prelude::Weight, BoundedVec}; +use frame_system::pallet_prelude::*; +use sp_core::H160; +use sp_runtime::traits::{AccountIdConversion, Saturating, Zero}; +use sp_std::vec::Vec; + +const MAX_SERIALIZED_OUTPUT_LENGTH: u32 = 65536; + +#[cfg(test)] +mod tests; + +pub use pallet::*; + +pub const SEPOLIA_VAULT_ADDRESS: [u8; 20] = [ + 0x00, 0xA4, 0x0C, 0x26, 0x61, 0x29, 0x3d, 0x51, 0x34, 0xE5, 0x3D, 0xa5, 0x29, 0x51, 0xA3, 0xF7, 0x76, 0x78, 0x36, + 0xEf, +]; + +// ERC20 ABI definition (exactly like Solana) +use alloy_primitives::{Address, U256}; +use alloy_sol_types::{sol, SolCall}; + +sol! { + #[sol(abi)] + interface IERC20 { + function transfer(address to, uint256 amount) external returns (bool); + } +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use sp_io::hashing; + + // ========================= Configuration ========================= + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_build_evm_tx::Config + pallet_signet::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type VaultPalletId: Get; + } + + #[pallet::pallet] + pub struct Pallet(_); + + // ========================= Storage ========================= + + /// Global vault configuration + #[pallet::storage] + #[pallet::getter(fn vault_config)] + pub type VaultConfig = StorageValue<_, VaultConfigData, OptionQuery>; + + /// Pending deposits awaiting signature + #[pallet::storage] + #[pallet::getter(fn pending_deposits)] + pub type PendingDeposits = StorageMap< + _, + Blake2_128Concat, + [u8; 32], // request_id + PendingDepositData, + OptionQuery, + >; + + /// User ERC20 balances + #[pallet::storage] + #[pallet::getter(fn user_balances)] + pub type UserBalances = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Blake2_128Concat, + [u8; 20], // ERC20 address + u128, + ValueQuery, + >; + + // ========================= Types ========================= + + #[derive(Encode, Decode, TypeInfo, Clone, Debug, PartialEq, MaxEncodedLen)] + pub struct VaultConfigData { + pub mpc_root_signer_address: [u8; 20], + } + + #[derive(Encode, Decode, TypeInfo, Clone, Debug, MaxEncodedLen)] + pub struct PendingDepositData { + pub requester: AccountId, + pub amount: u128, + pub erc20_address: [u8; 20], + pub path: BoundedVec>, + } + + #[derive(Encode, Decode, TypeInfo, Clone, Debug, PartialEq)] + pub struct EvmTransactionParams { + pub value: u128, // ETH value (0 for ERC20) + pub gas_limit: u64, + pub max_fee_per_gas: u128, + pub max_priority_fee_per_gas: u128, + pub nonce: u64, + pub chain_id: u64, + } + + // ========================= Events ========================= + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Vault initialized with MPC configuration + VaultInitialized { + mpc_address: [u8; 20], + initialized_by: T::AccountId, + }, + + /// ERC20 deposit requested + DepositRequested { + request_id: [u8; 32], + requester: T::AccountId, + erc20_address: [u8; 20], + amount: u128, + }, + + /// ERC20 deposit successfully claimed + DepositClaimed { + request_id: [u8; 32], + claimer: T::AccountId, + erc20_address: [u8; 20], + amount: u128, + }, + } + + // ========================= Errors ========================= + + #[pallet::error] + pub enum Error { + NotInitialized, + AlreadyInitialized, + InvalidRequestId, + DepositNotFound, + UnauthorizedClaimer, + InvalidSignature, + InvalidSigner, + InvalidOutput, + TransferFailed, + Overflow, + InvalidAbi, + SerializationError, + PathTooLong, + } + + // ========================= Hooks ========================= + + #[pallet::hooks] + impl Hooks> for Pallet {} + + // ========================= Extrinsics ========================= + + #[pallet::call] + impl Pallet { + /// Initialize the vault with MPC signer address + /// Can be called by anyone, but only once + #[pallet::call_index(0)] + #[pallet::weight(Weight::from_parts(10_000, 0))] + pub fn initialize(origin: OriginFor, mpc_root_signer_address: [u8; 20]) -> DispatchResult { + let initializer = ensure_signed(origin)?; + + ensure!(VaultConfig::::get().is_none(), Error::::AlreadyInitialized); + + VaultConfig::::put(VaultConfigData { + mpc_root_signer_address, + }); + + Self::deposit_event(Event::VaultInitialized { + mpc_address: mpc_root_signer_address, + initialized_by: initializer, + }); + + Ok(()) + } + + /// Request to deposit ERC20 tokens + #[pallet::call_index(1)] + #[pallet::weight(Weight::from_parts(100_000, 0))] + pub fn deposit_erc20( + origin: OriginFor, + request_id: [u8; 32], + erc20_address: [u8; 20], + amount: u128, + tx_params: EvmTransactionParams, + ) -> DispatchResult { + let requester = ensure_signed(origin)?; + + // Ensure vault is initialized + ensure!(VaultConfig::::get().is_some(), Error::::NotInitialized); + + // Ensure no duplicate request + ensure!( + PendingDeposits::::get(&request_id).is_none(), + Error::::InvalidRequestId + ); + + // Get the signet deposit and pallet account + let signet_deposit = pallet_signet::Pallet::::signature_deposit(); + let pallet_account = Self::account_id(); + let existential_deposit = ::Currency::minimum_balance(); + + // Check if pallet account exists, if not, create it with existential deposit + if ::Currency::total_balance(&pallet_account).is_zero() { + // First time - need to create the account with ED + signet deposit + let total_needed = existential_deposit.saturating_add(signet_deposit); + let imbalance = ::Currency::withdraw( + &requester, + total_needed, + frame_support::traits::WithdrawReasons::TRANSFER, + frame_support::traits::ExistenceRequirement::AllowDeath, + )?; + ::Currency::resolve_creating(&pallet_account, imbalance); + } else { + // Account exists, just add the signet deposit + ::Currency::transfer( + &requester, + &pallet_account, + signet_deposit, + frame_support::traits::ExistenceRequirement::AllowDeath, + )?; + } + + // Use requester account as path (like Solana's requester.to_string()) + let path = { + let encoded = requester.encode(); + format!("0x{}", hex::encode(encoded)).into_bytes() + }; + + let recipient = Address::from_slice(&SEPOLIA_VAULT_ADDRESS); + let call = IERC20::transferCall { + to: recipient, + amount: U256::from(amount), + }; + + // Build EVM transaction + let rlp_encoded = pallet_build_evm_tx::Pallet::::build_evm_tx( + frame_system::RawOrigin::Signed(requester.clone()).into(), + Some(H160::from(erc20_address)), + tx_params.value, + call.abi_encode(), + tx_params.nonce, + tx_params.gas_limit, + tx_params.max_fee_per_gas, + tx_params.max_priority_fee_per_gas, + vec![], // Empty access list + tx_params.chain_id, + )?; + + // Generate and verify request ID + let computed_request_id = Self::generate_request_id( + &Self::account_id(), + &rlp_encoded, + 60, + 0, + &path, + b"ecdsa", + b"ethereum", + b"", + ); + + ensure!(computed_request_id == request_id, Error::::InvalidRequestId); + + // Store pending deposit + PendingDeposits::::insert( + &request_id, + PendingDepositData { + requester: requester.clone(), + amount, + erc20_address, + path: path.clone().try_into().map_err(|_| Error::::PathTooLong)?, + }, + ); + + // Create schemas for the response + let functions = IERC20::abi::functions(); + let transfer_func = functions + .get("transfer") + .and_then(|funcs| funcs.first()) + .ok_or(Error::::InvalidAbi)?; + + let explorer_schema = + serde_json::to_vec(&transfer_func.outputs).map_err(|_| Error::::SerializationError)?; + + let callback_schema = + serde_json::to_vec(&serde_json::json!("bool")).map_err(|_| Error::::SerializationError)?; + + pallet_signet::Pallet::::sign_respond( + frame_system::RawOrigin::Signed(Self::account_id()).into(), + BoundedVec::try_from(rlp_encoded).map_err(|_| Error::::SerializationError)?, + 60, + 0, + BoundedVec::try_from(path).map_err(|_| Error::::PathTooLong)?, + BoundedVec::try_from(b"ecdsa".to_vec()).map_err(|_| Error::::SerializationError)?, + BoundedVec::try_from(b"ethereum".to_vec()).map_err(|_| Error::::SerializationError)?, + BoundedVec::try_from(vec![]).map_err(|_| Error::::SerializationError)?, + pallet_signet::SerializationFormat::AbiJson, + BoundedVec::try_from(explorer_schema).map_err(|_| Error::::SerializationError)?, + pallet_signet::SerializationFormat::Borsh, + BoundedVec::try_from(callback_schema).map_err(|_| Error::::SerializationError)?, + )?; + + Self::deposit_event(Event::DepositRequested { + request_id, + requester, + erc20_address, + amount, + }); + + Ok(()) + } + + /// Claim deposited ERC20 tokens after signature verification + #[pallet::call_index(2)] + #[pallet::weight(Weight::from_parts(50_000, 0))] + pub fn claim_erc20( + origin: OriginFor, + request_id: [u8; 32], + serialized_output: BoundedVec>, + signature: pallet_signet::Signature, + ) -> DispatchResult { + let claimer = ensure_signed(origin)?; + + // Get pending deposit + let pending = PendingDeposits::::get(&request_id).ok_or(Error::::DepositNotFound)?; + + // Verify claimer is the original requester + ensure!(pending.requester == claimer, Error::::UnauthorizedClaimer); + + // Get vault config + let config = VaultConfig::::get().ok_or(Error::::NotInitialized)?; + + // Verify signature + let message_hash = Self::hash_message(&request_id, &serialized_output); + Self::verify_signature_from_address(&message_hash, &signature, &config.mpc_root_signer_address)?; + + // Check for error magic prefix (matching Solana) + const ERROR_PREFIX: [u8; 4] = [0xDE, 0xAD, 0xBE, 0xEF]; + + let success = if serialized_output.len() >= 4 && &serialized_output[..4] == ERROR_PREFIX { + false // Error response + } else { + // Decode boolean (Borsh serialized) + use borsh::BorshDeserialize; + bool::try_from_slice(&serialized_output).map_err(|_| Error::::InvalidOutput)? + }; + + ensure!(success, Error::::TransferFailed); + + // Update user balance + UserBalances::::mutate(&claimer, &pending.erc20_address, |balance| -> DispatchResult { + *balance = balance.checked_add(pending.amount).ok_or(Error::::Overflow)?; + Ok(()) + })?; + + // Clean up storage + PendingDeposits::::remove(&request_id); + + Self::deposit_event(Event::DepositClaimed { + request_id, + claimer, + erc20_address: pending.erc20_address, + amount: pending.amount, + }); + + Ok(()) + } + } + + // ========================= Helper Functions ========================= + + impl Pallet { + fn generate_request_id( + sender: &T::AccountId, + transaction_data: &[u8], + slip44_chain_id: u32, + key_version: u32, + path: &[u8], + algo: &[u8], + dest: &[u8], + params: &[u8], + ) -> [u8; 32] { + use alloy_sol_types::SolValue; + use sp_core::crypto::Ss58Codec; + + // Convert AccountId to bytes and pad to 32 bytes + let encoded = sender.encode(); + let mut account_bytes = [0u8; 32]; + let len = encoded.len().min(32); + account_bytes[..len].copy_from_slice(&encoded[..len]); + + // Convert to AccountId32 and then to SS58 string + let account_id32 = sp_runtime::AccountId32::from(account_bytes); + let sender_ss58 = account_id32.to_ss58check_with_version(sp_core::crypto::Ss58AddressFormat::custom(0)); + + let encoded = ( + sender_ss58.as_str(), + transaction_data, + slip44_chain_id, + key_version, + core::str::from_utf8(path).unwrap_or(""), + core::str::from_utf8(algo).unwrap_or(""), + core::str::from_utf8(dest).unwrap_or(""), + core::str::from_utf8(params).unwrap_or(""), + ) + .abi_encode_packed(); + + sp_io::hashing::keccak_256(&encoded) + } + + /// Hash message for signature verification + fn hash_message(request_id: &[u8; 32], output: &[u8]) -> [u8; 32] { + let mut data = Vec::with_capacity(32 + output.len()); + data.extend_from_slice(request_id); + data.extend_from_slice(output); + hashing::keccak_256(&data) + } + + /// Verify signature by recovering address + fn verify_signature_from_address( + message_hash: &[u8; 32], + signature: &pallet_signet::Signature, + expected_address: &[u8; 20], + ) -> DispatchResult { + // Validate recovery ID + ensure!(signature.recovery_id < 4, Error::::InvalidSignature); + + // Prepare signature bytes (r || s || v) + let mut sig_bytes = [0u8; 65]; + sig_bytes[..32].copy_from_slice(&signature.big_r.x); + sig_bytes[32..64].copy_from_slice(&signature.s); + sig_bytes[64] = signature.recovery_id; + + // Recover public key (returns 64 bytes without 0x04 prefix) + let pubkey = sp_io::crypto::secp256k1_ecdsa_recover(&sig_bytes, message_hash) + .map_err(|_| Error::::InvalidSignature)?; + + // Hash all 64 bytes (no prefix to skip) + let pubkey_hash = hashing::keccak_256(&pubkey); + let recovered_address = &pubkey_hash[12..]; // Last 20 bytes + + ensure!(recovered_address == expected_address, Error::::InvalidSigner); + + Ok(()) + } + } + + impl Pallet { + pub fn account_id() -> T::AccountId { + T::VaultPalletId::get().into_account_truncating() + } + } +} diff --git a/pallets/erc20-vault/src/tests.rs b/pallets/erc20-vault/src/tests.rs new file mode 100644 index 0000000000..7c5b3e7dda --- /dev/null +++ b/pallets/erc20-vault/src/tests.rs @@ -0,0 +1,750 @@ +use crate::{self as pallet_erc20_vault, *}; +use alloy_primitives::{Address, U256}; +use alloy_sol_types::SolCall; +use codec::Encode; +use frame_support::{assert_noop, assert_ok, parameter_types, traits::Currency as CurrencyTrait, PalletId}; +use frame_system as system; +use secp256k1::{Message, PublicKey, Secp256k1, SecretKey}; +use sp_core::H256; +use sp_io::hashing::keccak_256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; +extern crate alloc; + +// Test secret key for signing +fn get_test_secret_key() -> SecretKey { + SecretKey::from_slice(&[42u8; 32]).expect("Valid secret key") +} + +fn bounded_u8(v: Vec) -> BoundedVec> { + BoundedVec::try_from(v).unwrap() +} + +fn bounded_chain_id(v: Vec) -> BoundedVec { + BoundedVec::try_from(v).unwrap() +} + +// Get public key from secret key +fn get_test_public_key() -> PublicKey { + let secp = Secp256k1::new(); + let secret_key = get_test_secret_key(); + PublicKey::from_secret_key(&secp, &secret_key) +} + +fn public_key_to_eth_address(public_key: &PublicKey) -> [u8; 20] { + // Get uncompressed public key (65 bytes: 0x04 + x + y) + let uncompressed = public_key.serialize_uncompressed(); + // Skip the 0x04 prefix byte and hash the remaining 64 bytes + let hash = keccak_256(&uncompressed[1..]); + // Take the last 20 bytes as Ethereum address + let mut address = [0u8; 20]; + address.copy_from_slice(&hash[12..]); + address +} + +// Create a valid signature for testing using secp256k1 directly +fn create_valid_signature(message_hash: &[u8; 32]) -> pallet_signet::Signature { + let secp = Secp256k1::new(); + let secret_key = get_test_secret_key(); + let message = Message::from_slice(message_hash).expect("Valid message hash"); + + // Sign without hashing (message is already hashed) + let sig = secp.sign_ecdsa_recoverable(&message, &secret_key); + let (recovery_id, sig_bytes) = sig.serialize_compact(); + + // Extract r and s + let mut r = [0u8; 32]; + let mut s = [0u8; 32]; + r.copy_from_slice(&sig_bytes[0..32]); + s.copy_from_slice(&sig_bytes[32..64]); + + pallet_signet::Signature { + big_r: pallet_signet::AffinePoint { + x: r, + y: [0u8; 32], // y-coordinate not used in recovery + }, + s, + recovery_id: recovery_id.to_i32() as u8, + } +} + +// Mock runtime construction +frame_support::construct_runtime!( + pub enum Test { + System: frame_system, + Balances: pallet_balances, + Signet: pallet_signet, + BuildEvmTx: pallet_build_evm_tx, + Erc20Vault: pallet_erc20_vault, + } +); + +// System config +parameter_types! { + pub const BlockHashCount: u64 = 250; +} + +impl system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Block = frame_system::mocking::MockBlock; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; + type RuntimeTask = (); + type SingleBlockMigrations = (); + type MultiBlockMigrator = (); + type PreInherents = (); + type PostInherents = (); + type PostTransactions = (); +} + +// Balances config +parameter_types! { + pub const ExistentialDeposit: u128 = 1; +} + +impl pallet_balances::Config for Test { + type Balance = u128; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type RuntimeFreezeReason = (); +} + +// Signet config +parameter_types! { + pub const SignetPalletId: PalletId = PalletId(*b"py/signt"); + pub const MaxChainIdLength: u32 = 128; +} + +impl pallet_signet::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type PalletId = SignetPalletId; + type MaxChainIdLength = MaxChainIdLength; + type WeightInfo = (); +} + +// Build EVM TX mock config +parameter_types! { + pub const MaxDataLength: u32 = 1024; +} + +impl pallet_build_evm_tx::Config for Test { + type MaxDataLength = MaxDataLength; +} + +parameter_types! { + pub const Erc20VaultPalletId: PalletId = PalletId(*b"py/erc20"); +} + +// ERC20 Vault config +impl pallet_erc20_vault::Config for Test { + type RuntimeEvent = RuntimeEvent; + type VaultPalletId = Erc20VaultPalletId; +} + +// Helper to build test externalities +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = system::GenesisConfig::::default().build_storage().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + // Fund test accounts using Currency trait + let _ = >::deposit_creating(&1, 1_000_000); + let _ = >::deposit_creating(&2, 1_000_000); + let _ = >::deposit_creating(&3, 100); + // Initialize signet pallet for tests that need it + let _ = pallet_signet::Pallet::::initialize( + RuntimeOrigin::signed(1), + 1, // admin + 100, // deposit + bounded_chain_id(b"test-chain".to_vec()), + ); + }); + ext +} + +// Helper to create test signature (dummy, for invalid signature tests) +fn create_test_signature() -> pallet_signet::Signature { + pallet_signet::Signature { + big_r: pallet_signet::AffinePoint { + x: [1u8; 32], + y: [2u8; 32], + }, + s: [3u8; 32], + recovery_id: 0, + } +} + +// Helper to create valid EVM transaction params +fn create_test_tx_params() -> EvmTransactionParams { + EvmTransactionParams { + value: 0, + gas_limit: 100_000, + max_fee_per_gas: 30_000_000_000, + max_priority_fee_per_gas: 1_000_000_000, + nonce: 0, + chain_id: 1, + } +} + +// Helper to create test addresses +fn create_test_erc20_address() -> [u8; 20] { + [1u8; 20] // Mock USDC address +} + +fn create_test_mpc_address() -> [u8; 20] { + // Use the address derived from our test public key + let public_key = get_test_public_key(); + public_key_to_eth_address(&public_key) +} + +fn compute_request_id( + requester: u64, + erc20_address: [u8; 20], + amount: u128, + tx_params: &EvmTransactionParams, +) -> [u8; 32] { + use alloy_sol_types::SolValue; + use sp_core::crypto::Ss58Codec; + + let recipient = Address::from_slice(&crate::SEPOLIA_VAULT_ADDRESS); + let call = crate::IERC20::transferCall { + to: recipient, + amount: U256::from(amount), + }; + + let rlp_encoded = pallet_build_evm_tx::Pallet::::build_evm_tx( + frame_system::RawOrigin::Signed(requester).into(), + Some(sp_core::H160::from(erc20_address)), + tx_params.value, + call.abi_encode(), + tx_params.nonce, + tx_params.gas_limit, + tx_params.max_fee_per_gas, + tx_params.max_priority_fee_per_gas, + vec![], + tx_params.chain_id, + ) + .expect("build_evm_tx should succeed"); + + // CHANGE THIS PART - use PALLET account, not requester + let pallet_account = Erc20Vault::account_id(); // <-- Changed from requester + + let encoded = pallet_account.encode(); // <-- Changed from requester.encode() + let mut account_bytes = [0u8; 32]; + let len = encoded.len().min(32); + account_bytes[..len].copy_from_slice(&encoded[..len]); + + let account_id32 = sp_runtime::AccountId32::from(account_bytes); + let sender_ss58 = account_id32.to_ss58check_with_version(sp_core::crypto::Ss58AddressFormat::custom(0)); + + // Path still uses requester (this is correct) + let path = format!("0x{}", hex::encode(requester.encode())); + + let encoded = ( + sender_ss58.as_str(), + rlp_encoded.as_slice(), + 60u32, + 0u32, + path.as_str(), + "ecdsa", + "ethereum", + "", + ) + .abi_encode_packed(); + + keccak_256(&encoded) +} + +// ======================================== +// INITIALIZATION TESTS +// ======================================== + +#[test] +fn test_initialize_works() { + new_test_ext().execute_with(|| { + let initializer = 2u64; + let mpc_address = create_test_mpc_address(); + + // Vault should not be initialized yet + assert_eq!(Erc20Vault::vault_config(), None); + + // Anyone can initialize + assert_ok!(Erc20Vault::initialize(RuntimeOrigin::signed(initializer), mpc_address)); + + // Check storage + assert_eq!( + Erc20Vault::vault_config(), + Some(VaultConfigData { + mpc_root_signer_address: mpc_address + }) + ); + + // Check event + System::assert_last_event( + Event::VaultInitialized { + mpc_address, + initialized_by: initializer, + } + .into(), + ); + }); +} + +#[test] +fn test_cannot_initialize_twice() { + new_test_ext().execute_with(|| { + let mpc_address = create_test_mpc_address(); + + // First initialization succeeds + assert_ok!(Erc20Vault::initialize(RuntimeOrigin::signed(1), mpc_address)); + + // Second initialization fails + assert_noop!( + Erc20Vault::initialize( + RuntimeOrigin::signed(2), + [4u8; 20] // Different address + ), + Error::::AlreadyInitialized + ); + + // Config remains unchanged + assert_eq!( + Erc20Vault::vault_config(), + Some(VaultConfigData { + mpc_root_signer_address: mpc_address + }) + ); + }); +} + +#[test] +fn test_any_account_can_initialize() { + new_test_ext().execute_with(|| { + let random_account = 3u64; + let mpc_address = create_test_mpc_address(); + + assert_ok!(Erc20Vault::initialize( + RuntimeOrigin::signed(random_account), + mpc_address + )); + + System::assert_last_event( + Event::VaultInitialized { + mpc_address, + initialized_by: random_account, + } + .into(), + ); + }); +} + +// ======================================== +// DEPOSIT TESTS +// ======================================== + +#[test] +fn test_deposit_erc20_fails_without_initialization() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + System::reset_events(); + + let requester = 1u64; + let request_id = [1u8; 32]; + + assert_noop!( + Erc20Vault::deposit_erc20( + RuntimeOrigin::signed(requester), + request_id, + create_test_erc20_address(), + 1_000_000u128, + create_test_tx_params(), + ), + Error::::NotInitialized + ); + }); +} + +#[test] +fn test_deposit_erc20_success() { + new_test_ext().execute_with(|| { + // Initialize vault + assert_ok!(Erc20Vault::initialize( + RuntimeOrigin::signed(1), + create_test_mpc_address() + )); + + let requester = 2u64; + let erc20_address = create_test_erc20_address(); + let amount = 1_000_000u128; + let tx_params = create_test_tx_params(); + + // Compute the correct request ID + let request_id = compute_request_id(requester, erc20_address, amount, &tx_params); + let balance_before = >::free_balance(&requester); + + assert_ok!(Erc20Vault::deposit_erc20( + RuntimeOrigin::signed(requester), + request_id, + erc20_address, + amount, + tx_params, + )); + + // Check pending deposit was stored + let pending = Erc20Vault::pending_deposits(&request_id); + assert!(pending.is_some()); + let pending = pending.unwrap(); + assert_eq!(pending.requester, requester); + assert_eq!(pending.amount, amount); + assert_eq!(pending.erc20_address, erc20_address); + + // Check deposit event was emitted + let events = System::events(); + assert!(events.iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::Erc20Vault(Event::DepositRequested { + request_id: rid, + requester: req, + erc20_address: erc20, + amount: amt, + }) if rid == &request_id + && req == &requester + && erc20 == &erc20_address + && amt == &amount + ) + })); + + // Check signet event was emitted + assert!(events.iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::Signet(pallet_signet::Event::SignRespondRequested { .. }) + ) + })); + + // Check deposit was taken (signet deposit) + assert_eq!( + >::free_balance(&requester), + balance_before - 101 // 100 for signet + 1 for ED + ); + }); +} + +#[test] +fn test_deposit_with_invalid_request_id_fails() { + new_test_ext().execute_with(|| { + assert_ok!(Erc20Vault::initialize( + RuntimeOrigin::signed(1), + create_test_mpc_address() + )); + + let requester = 2u64; + let wrong_request_id = [99u8; 32]; + + assert_noop!( + Erc20Vault::deposit_erc20( + RuntimeOrigin::signed(requester), + wrong_request_id, + create_test_erc20_address(), + 1_000_000u128, + create_test_tx_params(), + ), + Error::::InvalidRequestId + ); + + assert!(Erc20Vault::pending_deposits(&wrong_request_id).is_none()); + }); +} + +#[test] +fn test_deposit_duplicate_request_id_fails() { + new_test_ext().execute_with(|| { + assert_ok!(Erc20Vault::initialize( + RuntimeOrigin::signed(1), + create_test_mpc_address() + )); + + let requester = 2u64; + let erc20_address = create_test_erc20_address(); + let amount = 1_000_000u128; + let tx_params = create_test_tx_params(); + + // Compute correct request ID + let request_id = compute_request_id(requester, erc20_address, amount, &tx_params); + + // First deposit succeeds + assert_ok!(Erc20Vault::deposit_erc20( + RuntimeOrigin::signed(requester), + request_id, + erc20_address, + amount, + tx_params.clone(), + )); + + // Second deposit with same request ID fails + assert_noop!( + Erc20Vault::deposit_erc20( + RuntimeOrigin::signed(requester), + request_id, + erc20_address, + 2_000_000u128, // Different amount + tx_params, + ), + Error::::InvalidRequestId + ); + }); +} + +// ======================================== +// CLAIM TESTS +// ======================================== + +#[test] +fn test_claim_nonexistent_deposit_fails() { + new_test_ext().execute_with(|| { + assert_ok!(Erc20Vault::initialize( + RuntimeOrigin::signed(1), + create_test_mpc_address() + )); + + let claimer = 2u64; + let request_id = [99u8; 32]; + + assert_noop!( + Erc20Vault::claim_erc20( + RuntimeOrigin::signed(claimer), + request_id, + bounded_u8::<65536>(vec![1u8]), + create_test_signature(), + ), + Error::::DepositNotFound + ); + }); +} + +#[test] +fn test_claim_by_non_requester_fails() { + new_test_ext().execute_with(|| { + assert_ok!(Erc20Vault::initialize( + RuntimeOrigin::signed(1), + create_test_mpc_address() + )); + + let requester = 2u64; + let wrong_claimer = 1u64; + let erc20_address = create_test_erc20_address(); + let amount = 1_000_000u128; + let tx_params = create_test_tx_params(); + + // Compute correct request ID + let request_id = compute_request_id(requester, erc20_address, amount, &tx_params); + + // Create deposit + assert_ok!(Erc20Vault::deposit_erc20( + RuntimeOrigin::signed(requester), + request_id, + erc20_address, + amount, + tx_params, + )); + + // Try to claim with different account + assert_noop!( + Erc20Vault::claim_erc20( + RuntimeOrigin::signed(wrong_claimer), + request_id, + bounded_u8::<65536>(vec![1u8]), + create_test_signature(), + ), + Error::::UnauthorizedClaimer + ); + }); +} + +#[test] +fn test_claim_with_invalid_signature_fails() { + new_test_ext().execute_with(|| { + assert_ok!(Erc20Vault::initialize( + RuntimeOrigin::signed(1), + create_test_mpc_address() + )); + + let requester = 2u64; + let erc20_address = create_test_erc20_address(); + let amount = 1_000_000u128; + let tx_params = create_test_tx_params(); + + // Compute correct request ID + let request_id = compute_request_id(requester, erc20_address, amount, &tx_params); + + assert_ok!(Erc20Vault::deposit_erc20( + RuntimeOrigin::signed(requester), + request_id, + erc20_address, + amount, + tx_params, + )); + + // Create invalid signature + let mut bad_signature = create_test_signature(); + bad_signature.recovery_id = 5; + + assert_noop!( + Erc20Vault::claim_erc20( + RuntimeOrigin::signed(requester), + request_id, + bounded_u8::<65536>(vec![1u8]), + bad_signature, + ), + Error::::InvalidSignature + ); + }); +} + +#[test] +fn test_claim_with_error_response_fails() { + new_test_ext().execute_with(|| { + let mpc_address = create_test_mpc_address(); + assert_ok!(Erc20Vault::initialize(RuntimeOrigin::signed(1), mpc_address)); + + let requester = 2u64; + let erc20_address = create_test_erc20_address(); + let amount = 1_000_000u128; + let tx_params = create_test_tx_params(); + + // Compute correct request ID + let request_id = compute_request_id(requester, erc20_address, amount, &tx_params); + + assert_ok!(Erc20Vault::deposit_erc20( + RuntimeOrigin::signed(requester), + request_id, + erc20_address, + amount, + tx_params, + )); + + // Error response with magic prefix + let error_output = vec![0xDE, 0xAD, 0xBE, 0xEF, 1, 2, 3]; + + // Create valid signature for the error response + let message_hash = { + let mut data = Vec::with_capacity(32 + error_output.len()); + data.extend_from_slice(&request_id); + data.extend_from_slice(&error_output); + keccak_256(&data) + }; + + let valid_signature = create_valid_signature(&message_hash); + + // Should fail with TransferFailed because error prefix is detected + assert_noop!( + Erc20Vault::claim_erc20( + RuntimeOrigin::signed(requester), + request_id, + bounded_u8::<65536>(error_output), + valid_signature, + ), + Error::::TransferFailed + ); + + // Balance should not change + assert_eq!(Erc20Vault::user_balances(requester, erc20_address), 0); + }); +} + +#[test] +fn test_claim_successful_with_valid_signature() { + new_test_ext().execute_with(|| { + let mpc_address = create_test_mpc_address(); + assert_ok!(Erc20Vault::initialize(RuntimeOrigin::signed(1), mpc_address)); + + let requester = 2u64; + let erc20_address = create_test_erc20_address(); + let amount = 1_000_000u128; + let tx_params = create_test_tx_params(); + + // Compute correct request ID + let request_id = compute_request_id(requester, erc20_address, amount, &tx_params); + + assert_ok!(Erc20Vault::deposit_erc20( + RuntimeOrigin::signed(requester), + request_id, + erc20_address, + amount, + tx_params, + )); + + // Success response: Borsh-encoded true (1u8) + let success_output = vec![1u8]; + + // Create valid signature + let message_hash = { + let mut data = Vec::with_capacity(32 + success_output.len()); + data.extend_from_slice(&request_id); + data.extend_from_slice(&success_output); + keccak_256(&data) + }; + + let valid_signature = create_valid_signature(&message_hash); + + // Initial balance should be 0 + assert_eq!(Erc20Vault::user_balances(requester, erc20_address), 0); + + // Claim should succeed + assert_ok!(Erc20Vault::claim_erc20( + RuntimeOrigin::signed(requester), + request_id, + bounded_u8::<65536>(success_output), + valid_signature, + ),); + + // Balance should be updated + assert_eq!(Erc20Vault::user_balances(requester, erc20_address), amount); + + // Pending deposit should be removed + assert!(Erc20Vault::pending_deposits(&request_id).is_none()); + + // Check event was emitted + System::assert_has_event( + Event::DepositClaimed { + request_id, + claimer: requester, + erc20_address, + amount, + } + .into(), + ); + }); +} diff --git a/runtime/hydradx/Cargo.toml b/runtime/hydradx/Cargo.toml index ff4bcbbb8d..23035021c3 100644 --- a/runtime/hydradx/Cargo.toml +++ b/runtime/hydradx/Cargo.toml @@ -93,6 +93,7 @@ pallet-whitelist = { workspace = true } pallet-message-queue = { workspace = true } pallet-state-trie-migration = { workspace = true } pallet-signet = { workspace = true } +pallet-erc20-vault = { workspace = true } # ORML dependencies orml-tokens = { workspace = true } @@ -260,6 +261,7 @@ runtime-benchmarks = [ "ismp-parachain/runtime-benchmarks", "pallet-token-gateway/runtime-benchmarks", "pallet-signet/runtime-benchmarks", + "pallet-erc20-vault/runtime-benchmarks", ] std = [ "codec/std", @@ -396,6 +398,7 @@ std = [ "ismp-parachain/std", "ismp-parachain-runtime-api/std", "pallet-signet/std", + "pallet-erc20-vault/std", ] try-runtime = [ "frame-try-runtime", @@ -476,6 +479,7 @@ try-runtime = [ "pallet-broadcast/try-runtime", "pallet-dispatcher/try-runtime", "pallet-signet/try-runtime", + "pallet-erc20-vault/try-runtime", ] metadata-hash = [ diff --git a/runtime/hydradx/src/assets.rs b/runtime/hydradx/src/assets.rs index de37b7535d..c4aacf8975 100644 --- a/runtime/hydradx/src/assets.rs +++ b/runtime/hydradx/src/assets.rs @@ -1853,6 +1853,15 @@ impl pallet_signet::Config for Runtime { type WeightInfo = weights::pallet_signet::HydraWeight; } +parameter_types! { + pub const Erc20VaultPalletId: PalletId = PalletId(*b"py/erc20"); +} + +impl pallet_erc20_vault::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type VaultPalletId = Erc20VaultPalletId; +} + pub struct ConvertViaOmnipool(PhantomData); impl Convert for ConvertViaOmnipool where diff --git a/runtime/hydradx/src/lib.rs b/runtime/hydradx/src/lib.rs index 4992ba3925..c474496517 100644 --- a/runtime/hydradx/src/lib.rs +++ b/runtime/hydradx/src/lib.rs @@ -194,6 +194,7 @@ construct_runtime!( Parameters: pallet_parameters = 83, BuildEvmTx: pallet_build_evm_tx = 86, Signet: pallet_signet = 84, + Erc20Vault: pallet_erc20_vault = 85, // ORML related modules Tokens: orml_tokens = 77, @@ -348,6 +349,7 @@ mod benches { [ismp_parachain, IsmpParachain] [pallet_token_gateway, TokenGateway] [pallet_signet, Signet] + [pallet_erc20_vault, Erc20Vault] ); } diff --git a/signet-ts-client-scripts/README.md b/signet-ts-client-scripts/README.md index 965ee12d47..fbcb29d932 100644 --- a/signet-ts-client-scripts/README.md +++ b/signet-ts-client-scripts/README.md @@ -1,12 +1,13 @@ # Signet Substrate Client -Test client for the Signet pallet on Substrate/Polkadot. Validates signature generation and verification for both simple payloads and EIP-1559 transactions. +Test client for the Signet pallet on Substrate/Polkadot. Validates signature generation and verification for both simple payloads, EIP-1559 transactions, and ERC20 vault deposits. ## Prerequisites - Node.js v16+ and npm/yarn - Running Substrate node with Signet pallet deployed (port 8000) - Access to the Signet signature server +- For ERC20 vault tests: Funded Ethereum Sepolia account with ETH and USDC ## Setup @@ -26,20 +27,23 @@ yarn install cat > .env << EOF # Substrate Configuration SUBSTRATE_WS_URL=ws://localhost:8000 -SUBSTRATE_SIGNER_SEED=//Alice +SUBSTRATE_SIGNER_SEED=//Bob # Signing Keys (must match ROOT_PUBLIC_KEY in tests) PRIVATE_KEY_TESTNET=0x... # Your private key for signing -# Optional: Ethereum RPC for transaction monitoring -INFURA_API_KEY=your_infura_key_here +# Ethereum Configuration (for vault monitoring) +SEPOLIA_RPC=https://sepolia.infura.io/v3/your_infura_key_here EOF # Start the server yarn start ``` -The server will connect to your Substrate node and automatically respond to signature requests. +The server will: +- Connect to your Substrate node +- Automatically respond to signature requests +- Monitor Ethereum transactions and report results back to Substrate ### 2. Install Test Client Dependencies @@ -53,47 +57,114 @@ The tests expect a Substrate node with the Signet pallet at `ws://localhost:8000 ```bash npx @acala-network/chopsticks@latest --config=hydradx \ - --wasm-override ./path/to/hydradx_runtime.wasm \ + --wasm-override ./target/release/wbuild/hydradx-runtime/hydradx_runtime.compact.compressed.wasm \ --db=:memory: ``` +### 4. Fund Ethereum Account for Vault Tests + +The ERC20 vault test requires a funded account on Sepolia. The test derives an Ethereum address from your Substrate account and expects it to have: + +- At least 0.001 ETH for gas +- At least 0.01 USDC (testnet) at address `0xbe72E441BF55620febc26715db68d3494213D8Cb` + +The derived address is deterministic based on your Substrate account. Run the test once to see the address, then fund it on Sepolia + ## Running Tests ```bash # Run all tests yarn test +# Run specific test suite +yarn test signet.test.ts +yarn test erc20vault.test.ts + # Run with watch mode yarn test:watch ``` ## Test Coverage -The test suite validates: +### Basic Signature Tests (`signet.test.ts`) - **Simple Signatures**: Request and verify ECDSA signatures for 32-byte payloads - **Transaction Signatures**: Sign and verify EIP-1559 Ethereum transactions - **Key Derivation**: Verify derived keys match between client and server - **Address Recovery**: Ensure signature recovery produces expected addresses +### ERC20 Vault Integration (`erc20vault.test.ts`) +- **Vault Initialization**: Initialize vault with MPC signer address +- **Deposit Flow**: + - Request MPC signature for ERC20 transfer transaction + - Verify signature and broadcast to Sepolia + - Wait for transaction confirmation +- **Result Monitoring**: MPC server observes transaction result on Ethereum +- **Claim Flow**: + - Receive transaction output from MPC + - Verify MPC signature on result + - Claim deposited tokens in Substrate vault +- **Multi-token Support**: Vault supports any ERC20 token (decimal-agnostic) + ## Expected Output +### Basic Signature Test ``` -PASS ./signet.test.ts +PASS ./signet.test.ts Signet Pallet Integration Sign āœ“ should request and verify a signature āœ… Signature received from: 14E5nqKAp3oAJcmzgZhUD2RcptBeUBScxKHgJKU4HPNcKVf3 - Recovered: 0xF4a62e4f48e8e71170BA758b5bAf90646db61301 - Expected: 0xf4a62e4f48e8e71170ba758b5baf90646db61301 + Recovered: 0xF4a62e4f48e8e71170BA758b5bAf90646db61301 + Expected: 0xf4a62e4f48e8e71170ba758b5baf90646db61301 āœ… Signature verification PASSED + SignRespond āœ“ should request and verify a transaction signature āœ… Transaction signature received from: 14E5nqKAp3oAJcmzgZhUD2RcptBeUBScxKHgJKU4HPNcKVf3 - Recovered: 0xF4a62e4f48e8e71170BA758b5bAf90646db61301 - Expected: 0xf4a62e4f48e8e71170ba758b5baf90646db61301 āœ… Transaction signature verification PASSED ``` +### ERC20 Vault Test +``` +PASS ./erc20vault.test.ts (47.902 s) + ERC20 Vault Integration + āœ“ should complete full deposit and claim flow (43924 ms) + + šŸ”‘ Derived Ethereum Address: 0xde36cd568b21c9e5b19ab6ecf01f9e5024398913 + šŸ’° Balances for 0xde36cd568b21c9e5b19ab6ecf01f9e5024398913: + ETH: 0.00999946 + USDC: 0.06 + + Initializing vault with MPC address: 0x00a40c2661293d5134e53da52951a3f7767836ef + āœ… Vault initialized + + šŸ“Š Current nonce for 0xde36cd568b21c9e5b19ab6ecf01f9e5024398913: 18 + šŸ“‹ Request ID: 0x15dc855a7fb93a3e694d5a93b9a40a2a141c7af0bbfc6afdc20d8c80ce4124f7 + šŸš€ Submitting deposit_erc20 transaction... + ā³ Waiting for MPC signature... + + āœ… Received signature from: 14E5nqKAp3oAJcmzgZhUD2RcptBeUBScxKHgJKU4HPNcKVf3 + šŸ” Signature verification: + Expected address: 0xde36cd568b21c9e5b19ab6ecf01f9e5024398913 + Recovered address: 0xDE36CD568B21C9E5B19AB6ECF01F9e5024398913 + Match: true + + šŸ“Š Fresh nonce check: 18 + šŸ“” Broadcasting transaction to Sepolia... + Tx Hash: 0xead2b6a3de9fdd90d04da5f329c5ada25060b4f0e48ebc65aa1c1c601696f974 + āœ… Transaction confirmed in block 9357487 + + ā³ Waiting for MPC to read transaction result... + āœ… Received read response + āœ… Claim transaction confirmed + āœ… Balance increased by: 0.01 USDC + Total balance: 0.04 USDC + +Test Suites: 1 passed, 1 total +Tests: 1 passed, 1 total +Time: 48.056 s +``` + ## Configuration The root public key used for derivation is hardcoded in the tests. Ensure the server's `PRIVATE_KEY_TESTNET` corresponds to: @@ -106,4 +177,9 @@ const ROOT_PUBLIC_KEY = "0x044eef776e4f257d68983e45b340c2e9546c5df95447900b6aadf - **Timeout errors**: Ensure the signature server is running and connected to the same Substrate node - **Address mismatch**: Verify the server's private key matches the client's expected public key -- **Transaction errors**: Check that the Signet pallet is initialized (tests handle this automatically) \ No newline at end of file +- **Transaction errors**: Check that the Signet pallet is initialized (tests handle this automatically) +- **Vault test failures**: + - Ensure your derived Ethereum address is funded with ETH and USDC on Sepolia + - Verify the MPC server has Ethereum monitoring enabled with `SEPOLIA_RPC` configured + - Check that the vault's MPC address matches the server's signing key +- **InvalidSigner errors**: The output bytes from Substrate events include SCALE encoding that must be stripped before verification \ No newline at end of file diff --git a/signet-ts-client-scripts/erc20vault.test.ts b/signet-ts-client-scripts/erc20vault.test.ts new file mode 100644 index 0000000000..0b2f9cde5f --- /dev/null +++ b/signet-ts-client-scripts/erc20vault.test.ts @@ -0,0 +1,483 @@ +import { ApiPromise, WsProvider, Keyring } from "@polkadot/api"; +import { ISubmittableResult } from "@polkadot/types/types"; +import { waitReady } from "@polkadot/wasm-crypto"; +import { u8aToHex } from "@polkadot/util"; +import { encodeAddress } from "@polkadot/keyring"; +import { ethers } from "ethers"; +import { SignetClient } from "./signet-client"; +import { KeyDerivation } from "./key-derivation"; + +const ROOT_PUBLIC_KEY = + "0x044eef776e4f257d68983e45b340c2e9546c5df95447900b6aadfec68fb46fdee257e26b8ba383ddba9914b33c60e869265f859566fff4baef283c54d821ca3b64"; +const CHAIN_ID = "polkadot:2034"; +const USDC_SEPOLIA = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"; +const SEPOLIA_RPC = + process.env.SEPOLIA_RPC || + "https://sepolia.infura.io/v3/6df51ccaa17f4e078325b5050da5a2dd"; + +function getPalletAccountId(): Uint8Array { + const palletId = new TextEncoder().encode("py/erc20"); + // Substrate's into_account_truncating: "modl" + pallet_id + padding + const modl = new TextEncoder().encode("modl"); + const data = new Uint8Array(32); + data.set(modl, 0); + data.set(palletId, 4); + return data; +} + +describe("ERC20 Vault Integration", () => { + let api: ApiPromise; + let alice: any; + let signetClient: SignetClient; + let sepoliaProvider: ethers.JsonRpcProvider; + let derivedEthAddress: string; + let derivedPubKey: string; + + beforeAll(async () => { + await waitReady(); + + api = await ApiPromise.create({ + provider: new WsProvider("ws://127.0.0.1:8000"), + types: { + AffinePoint: { x: "[u8; 32]", y: "[u8; 32]" }, + Signature: { big_r: "AffinePoint", s: "[u8; 32]", recovery_id: "u8" }, + }, + }); + + const keyring = new Keyring({ type: "sr25519" }); + alice = keyring.addFromUri("//Alice"); + const bob = keyring.addFromUri("//Bob"); + + const { data: bobBalance } = (await api.query.system.account( + bob.address + )) as any; + + if (bobBalance.free.toBigInt() < 1000000000000n) { + console.log("Funding Bob's account for server responses..."); + + await new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(new Error("Bob funding timeout")); + }, 30000); + + api.tx.balances + .transferKeepAlive(bob.address, 100000000000000n) + .signAndSend(alice, (result: ISubmittableResult) => { + if (result.dispatchError) { + clearTimeout(timeout); + reject(result.dispatchError); + } else if (result.status.isInBlock) { + // Changed from isFinalized + clearTimeout(timeout); + console.log("Bob's account funded!"); + resolve(result.status.asInBlock); + } + }); + }); + } + + signetClient = new SignetClient(api, alice); + sepoliaProvider = new ethers.JsonRpcProvider(SEPOLIA_RPC); + + await signetClient.ensureInitialized(CHAIN_ID); + + const palletAccountId = getPalletAccountId(); + const palletSS58 = encodeAddress(palletAccountId, 0); + + const aliceAccountId = keyring.decodeAddress(alice.address); + const aliceHexPath = "0x" + u8aToHex(aliceAccountId).slice(2); + + // Derive using PALLET account but ALICE's path + derivedPubKey = KeyDerivation.derivePublicKey( + ROOT_PUBLIC_KEY, + palletSS58, + aliceHexPath, + CHAIN_ID + ); + + derivedEthAddress = ethAddressFromPubKey(derivedPubKey); + + console.log(`\nšŸ”‘ Derived Ethereum Address: ${derivedEthAddress}`); + await checkFunding(); + }, 30000); + + afterAll(async () => { + if (api) { + await api.disconnect(); + } + }); + + async function checkFunding() { + const ethBalance = await sepoliaProvider.getBalance(derivedEthAddress); + + // Check USDC balance + const usdcContract = new ethers.Contract( + USDC_SEPOLIA, + ["function balanceOf(address) view returns (uint256)"], + sepoliaProvider + ); + const usdcBalance = await usdcContract.balanceOf(derivedEthAddress); + + // Estimate gas needed (conservative estimate) + const feeData = await sepoliaProvider.getFeeData(); + const gasLimit = 100000n; + const estimatedGas = (feeData.maxFeePerGas || 30000000000n) * gasLimit; + + console.log(`šŸ’° Balances for ${derivedEthAddress}:`); + console.log(` ETH: ${ethers.formatEther(ethBalance)}`); + console.log(` USDC: ${ethers.formatUnits(usdcBalance, 6)}`); + console.log( + ` Estimated gas needed: ${ethers.formatEther(estimatedGas)} ETH\n` + ); + + const minUSDC = ethers.parseUnits("0.01", 6); + + if (ethBalance < estimatedGas) { + throw new Error( + `āŒ Insufficient ETH at ${derivedEthAddress}\n` + + ` Need: ${ethers.formatEther(estimatedGas)} ETH\n` + + ` Have: ${ethers.formatEther(ethBalance)} ETH\n` + + ` Please fund this address with ETH for gas` + ); + } + + if (usdcBalance < minUSDC) { + throw new Error( + `āŒ Insufficient USDC at ${derivedEthAddress}\n` + + ` Need: 0.01 USDC\n` + + ` Have: ${ethers.formatUnits(usdcBalance, 6)} USDC\n` + + ` Please fund this address with USDC` + ); + } + } + + it("should complete full deposit and claim flow", async () => { + const mpcEthAddress = ethAddressFromPubKey(ROOT_PUBLIC_KEY); + console.log("Initializing vault with MPC address:", mpcEthAddress); + const mpcAddressBytes = Array.from(ethers.getBytes(mpcEthAddress)); + + const initTx = api.tx.erc20Vault.initialize(mpcAddressBytes); + await initTx.signAndSend(alice); + await sleep(6000); + + console.log("āœ… Vault initialized\n"); + + const amount = ethers.parseUnits("0.01", 6); + const feeData = await sepoliaProvider.getFeeData(); + const currentNonce = await sepoliaProvider.getTransactionCount( + derivedEthAddress, + "pending" + ); + + console.log(`šŸ“Š Current nonce for ${derivedEthAddress}: ${currentNonce}`); + + const txParams = { + value: 0, + gasLimit: 100000, + maxFeePerGas: Number(feeData.maxFeePerGas || 30000000000n), + maxPriorityFeePerGas: Number(feeData.maxPriorityFeePerGas || 2000000000n), + nonce: currentNonce, + chainId: 11155111, + }; + + const keyring = new Keyring({ type: "sr25519" }); + const palletAccountId = getPalletAccountId(); + const palletSS58 = encodeAddress(palletAccountId, 0); + const aliceAccountId = keyring.decodeAddress(alice.address); + const aliceHexPath = "0x" + u8aToHex(aliceAccountId).slice(2); + + // Build transaction to get request ID + const iface = new ethers.Interface([ + "function transfer(address to, uint256 amount) returns (bool)", + ]); + const data = iface.encodeFunctionData("transfer", [ + "0x00A40C2661293d5134E53Da52951A3F7767836Ef", + amount, + ]); + + const tx = ethers.Transaction.from({ + type: 2, + chainId: txParams.chainId, + nonce: txParams.nonce, + maxPriorityFeePerGas: txParams.maxPriorityFeePerGas, + maxFeePerGas: txParams.maxFeePerGas, + gasLimit: txParams.gasLimit, + to: USDC_SEPOLIA, + value: 0, + data: data, + }); + + const requestId = signetClient.calculateSignRespondRequestId( + palletSS58, + Array.from(ethers.getBytes(tx.unsignedSerialized)), + { + slip44ChainId: 60, + keyVersion: 0, + path: aliceHexPath, + algo: "ecdsa", + dest: "ethereum", + params: "", + } + ); + + console.log(`šŸ“‹ Request ID: ${ethers.hexlify(requestId)}\n`); + + // Convert requestId to bytes if it's a hex string + const requestIdBytes = + typeof requestId === "string" ? ethers.getBytes(requestId) : requestId; + + const depositTx = api.tx.erc20Vault.depositErc20( + Array.from(requestIdBytes), // Use the bytes version + Array.from(ethers.getBytes(USDC_SEPOLIA)), + amount.toString(), + txParams + ); + + console.log("šŸš€ Submitting deposit_erc20 transaction..."); + + await new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject( + new Error( + "Deposit transaction timeout - Chopsticks may have flaked, retry test" + ) + ); + }, 15000); + + depositTx.signAndSend(alice, (result: ISubmittableResult) => { + if (result.dispatchError) { + clearTimeout(timeout); + if (result.dispatchError.isModule) { + const decoded = api.registry.findMetaError( + result.dispatchError.asModule + ); + reject(new Error(`${decoded.section}.${decoded.name}`)); + } else { + reject(new Error(result.dispatchError.toString())); + } + } else if (result.status.isInBlock) { + clearTimeout(timeout); + resolve(); + } + }); + }); + + console.log("ā³ Waiting for MPC signature..."); + + const signature = await signetClient.waitForSignature( + ethers.hexlify(requestId), + 120000 + ); + + if (!signature) { + throw new Error("āŒ Timeout waiting for MPC signature"); + } + + console.log(`āœ… Received signature from: ${signature.responder}\n`); + + // Verify signature by recovering address + const signedTx = constructSignedTransaction( + tx.unsignedSerialized, + signature.signature + ); + const recoveredTx = ethers.Transaction.from(signedTx); + const recoveredAddress = recoveredTx.from; + + console.log(`šŸ” Signature verification:`); + console.log(` Expected address: ${derivedEthAddress}`); + console.log(` Recovered address: ${recoveredAddress}`); + console.log( + ` Match: ${ + recoveredAddress?.toLowerCase() === derivedEthAddress.toLowerCase() + }` + ); + + if (recoveredAddress?.toLowerCase() !== derivedEthAddress.toLowerCase()) { + throw new Error( + `āŒ Signature verification failed!\n` + + ` Expected: ${derivedEthAddress}\n` + + ` Recovered: ${recoveredAddress}\n` + + ` This means the MPC signed with the wrong key or recovery ID is incorrect.` + ); + } + + // Get fresh nonce before broadcasting + const freshNonce = await sepoliaProvider.getTransactionCount( + derivedEthAddress, + "pending" + ); + console.log(`šŸ“Š Fresh nonce check: ${freshNonce}`); + + if (freshNonce !== txParams.nonce) { + throw new Error( + `āŒ Nonce mismatch! Expected ${txParams.nonce}, but network shows ${freshNonce}.\n` + + ` A transaction may have already been sent from this address.` + ); + } + + console.log("šŸ“” Broadcasting transaction to Sepolia..."); + const txResponse = await sepoliaProvider.broadcastTransaction(signedTx); + console.log(` Tx Hash: ${txResponse.hash}`); + + const receipt = await txResponse.wait(); + console.log(`āœ… Transaction confirmed in block ${receipt?.blockNumber}\n`); + + console.log("ā³ Waiting for MPC to read transaction result..."); + const readResponse = await waitForReadResponse( + api, + ethers.hexlify(requestId), + 120000 + ); + + if (!readResponse) { + throw new Error("āŒ Timeout waiting for read response"); + } + + console.log("āœ… Received read response\n"); + + console.log("\nšŸ” Claim Debug:"); + console.log(" Request ID:", ethers.hexlify(requestIdBytes)); + console.log( + " Output (hex):", + Buffer.from(readResponse.output).toString("hex") + ); + + // Strip SCALE compact prefix from output + let outputBytes = new Uint8Array(readResponse.output); + if (outputBytes.length > 0) { + const mode = outputBytes[0] & 0b11; + if (mode === 0) { + outputBytes = outputBytes.slice(1); // Remove 1-byte SCALE prefix + } else if (mode === 1) { + outputBytes = outputBytes.slice(2); // Remove 2-byte SCALE prefix + } else if (mode === 2) { + outputBytes = outputBytes.slice(4); // Remove 4-byte SCALE prefix + } + } + + console.log( + " Stripped output (hex):", + Buffer.from(outputBytes).toString("hex") + ); + + const balanceBefore = await api.query.erc20Vault.userBalances( + alice.address, + Array.from(ethers.getBytes(USDC_SEPOLIA)) + ); + + const claimTx = api.tx.erc20Vault.claimErc20( + Array.from(requestIdBytes), + Array.from(outputBytes), + readResponse.signature + ); + + await new Promise((resolve, reject) => { + claimTx.signAndSend(alice, (result: ISubmittableResult) => { + if (result.dispatchError) { + if (result.dispatchError.isModule) { + const decoded = api.registry.findMetaError( + result.dispatchError.asModule + ); + reject( + new Error(`Claim failed: ${decoded.section}.${decoded.name}`) + ); + } else { + reject(new Error(result.dispatchError.toString())); + } + } else if (result.status.isInBlock) { + resolve(); + } + }); + }); + + console.log("āœ… Claim transaction confirmed\n"); + + const balanceAfter = await api.query.erc20Vault.userBalances( + alice.address, + Array.from(ethers.getBytes(USDC_SEPOLIA)) + ); + + const balanceIncrease = + BigInt(balanceAfter.toString()) - BigInt(balanceBefore.toString()); + + expect(balanceIncrease.toString()).toBe(amount.toString()); + console.log( + `āœ… Balance increased by: ${ethers.formatUnits( + balanceIncrease.toString(), + 6 + )} USDC` + ); + console.log( + ` Total balance: ${ethers.formatUnits( + balanceAfter.toString(), + 6 + )} USDC\n` + ); + }, 180000); + + function constructSignedTransaction( + unsignedSerialized: string, + signature: any + ): string { + const tx = ethers.Transaction.from(unsignedSerialized); + + const rHex = ethers.hexlify(signature.bigR.x); + const sHex = ethers.hexlify(signature.s); + + tx.signature = { + r: rHex, + s: sHex, + v: signature.recoveryId, + }; + + return tx.serialized; + } + + async function waitForReadResponse( + api: ApiPromise, + requestId: string, + timeout: number + ): Promise { + return new Promise((resolve) => { + let unsubscribe: any; + const timer = setTimeout(() => { + if (unsubscribe) unsubscribe(); + resolve(null); + }, timeout); + + api.query.system + .events((events: any) => { + events.forEach((record: any) => { + const { event } = record; + if ( + event.section === "signet" && + event.method === "ReadResponded" + ) { + const [reqId, responder, output, signature] = event.data; + if (ethers.hexlify(reqId.toU8a()) === requestId) { + clearTimeout(timer); + if (unsubscribe) unsubscribe(); + resolve({ + responder: responder.toString(), + output: Array.from(output.toU8a()), + signature: signature.toJSON(), + }); + } + } + }); + }) + .then((unsub: any) => { + unsubscribe = unsub; + }); + }); + } + + function ethAddressFromPubKey(pubKey: string): string { + const hash = ethers.keccak256("0x" + pubKey.slice(4)); + return "0x" + hash.slice(-40); + } + + function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } +}); From 67b19a0591857b6bf7d530f86e14a2da2cf636ba Mon Sep 17 00:00:00 2001 From: Osman Abdelnasir Date: Wed, 8 Oct 2025 08:52:12 +0400 Subject: [PATCH 27/32] fix: reduce flakiness --- pallets/erc20-vault/src/lib.rs | 69 ++---- pallets/erc20-vault/src/tests.rs | 12 +- signet-ts-client-scripts/erc20vault.test.ts | 244 +++++++++++++------- 3 files changed, 189 insertions(+), 136 deletions(-) diff --git a/pallets/erc20-vault/src/lib.rs b/pallets/erc20-vault/src/lib.rs index 0041792756..a9fdaf4ccb 100644 --- a/pallets/erc20-vault/src/lib.rs +++ b/pallets/erc20-vault/src/lib.rs @@ -25,7 +25,7 @@ pub const SEPOLIA_VAULT_ADDRESS: [u8; 20] = [ 0xEf, ]; -// ERC20 ABI definition (exactly like Solana) +// ERC20 ABI definition use alloy_primitives::{Address, U256}; use alloy_sol_types::{sol, SolCall}; @@ -100,7 +100,7 @@ pub mod pallet { #[derive(Encode, Decode, TypeInfo, Clone, Debug, PartialEq)] pub struct EvmTransactionParams { - pub value: u128, // ETH value (0 for ERC20) + pub value: u128, pub gas_limit: u64, pub max_fee_per_gas: u128, pub max_priority_fee_per_gas: u128, @@ -113,21 +113,16 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// Vault initialized with MPC configuration VaultInitialized { mpc_address: [u8; 20], initialized_by: T::AccountId, }, - - /// ERC20 deposit requested DepositRequested { request_id: [u8; 32], requester: T::AccountId, erc20_address: [u8; 20], amount: u128, }, - - /// ERC20 deposit successfully claimed DepositClaimed { request_id: [u8; 32], claimer: T::AccountId, @@ -153,6 +148,7 @@ pub mod pallet { InvalidAbi, SerializationError, PathTooLong, + PalletAccountNotFunded, } // ========================= Hooks ========================= @@ -165,12 +161,10 @@ pub mod pallet { #[pallet::call] impl Pallet { /// Initialize the vault with MPC signer address - /// Can be called by anyone, but only once #[pallet::call_index(0)] #[pallet::weight(Weight::from_parts(10_000, 0))] pub fn initialize(origin: OriginFor, mpc_root_signer_address: [u8; 20]) -> DispatchResult { let initializer = ensure_signed(origin)?; - ensure!(VaultConfig::::get().is_none(), Error::::AlreadyInitialized); VaultConfig::::put(VaultConfigData { @@ -186,6 +180,7 @@ pub mod pallet { } /// Request to deposit ERC20 tokens + /// Note: The pallet account must be funded before calling this #[pallet::call_index(1)] #[pallet::weight(Weight::from_parts(100_000, 0))] pub fn deposit_erc20( @@ -206,33 +201,26 @@ pub mod pallet { Error::::InvalidRequestId ); - // Get the signet deposit and pallet account + // Get signet deposit amount and pallet account let signet_deposit = pallet_signet::Pallet::::signature_deposit(); let pallet_account = Self::account_id(); let existential_deposit = ::Currency::minimum_balance(); - // Check if pallet account exists, if not, create it with existential deposit - if ::Currency::total_balance(&pallet_account).is_zero() { - // First time - need to create the account with ED + signet deposit - let total_needed = existential_deposit.saturating_add(signet_deposit); - let imbalance = ::Currency::withdraw( - &requester, - total_needed, - frame_support::traits::WithdrawReasons::TRANSFER, - frame_support::traits::ExistenceRequirement::AllowDeath, - )?; - ::Currency::resolve_creating(&pallet_account, imbalance); - } else { - // Account exists, just add the signet deposit - ::Currency::transfer( - &requester, - &pallet_account, - signet_deposit, - frame_support::traits::ExistenceRequirement::AllowDeath, - )?; - } - - // Use requester account as path (like Solana's requester.to_string()) + // Ensure pallet account has sufficient balance + // It needs at least ED + signet_deposit to transfer signet_deposit while staying alive + let pallet_balance = ::Currency::free_balance(&pallet_account); + let required_balance = existential_deposit.saturating_add(signet_deposit); + ensure!(pallet_balance >= required_balance, Error::::PalletAccountNotFunded); + + // Transfer signet deposit from requester to pallet account + ::Currency::transfer( + &requester, + &pallet_account, + signet_deposit, + frame_support::traits::ExistenceRequirement::AllowDeath, + )?; + + // Use requester account as path let path = { let encoded = requester.encode(); format!("0x{}", hex::encode(encoded)).into_bytes() @@ -254,7 +242,7 @@ pub mod pallet { tx_params.gas_limit, tx_params.max_fee_per_gas, tx_params.max_priority_fee_per_gas, - vec![], // Empty access list + vec![], tx_params.chain_id, )?; @@ -296,6 +284,7 @@ pub mod pallet { let callback_schema = serde_json::to_vec(&serde_json::json!("bool")).map_err(|_| Error::::SerializationError)?; + // Call sign_respond from the pallet account pallet_signet::Pallet::::sign_respond( frame_system::RawOrigin::Signed(Self::account_id()).into(), BoundedVec::try_from(rlp_encoded).map_err(|_| Error::::SerializationError)?, @@ -345,11 +334,11 @@ pub mod pallet { let message_hash = Self::hash_message(&request_id, &serialized_output); Self::verify_signature_from_address(&message_hash, &signature, &config.mpc_root_signer_address)?; - // Check for error magic prefix (matching Solana) + // Check for error magic prefix const ERROR_PREFIX: [u8; 4] = [0xDE, 0xAD, 0xBE, 0xEF]; let success = if serialized_output.len() >= 4 && &serialized_output[..4] == ERROR_PREFIX { - false // Error response + false } else { // Decode boolean (Borsh serialized) use borsh::BorshDeserialize; @@ -394,13 +383,11 @@ pub mod pallet { use alloy_sol_types::SolValue; use sp_core::crypto::Ss58Codec; - // Convert AccountId to bytes and pad to 32 bytes let encoded = sender.encode(); let mut account_bytes = [0u8; 32]; let len = encoded.len().min(32); account_bytes[..len].copy_from_slice(&encoded[..len]); - // Convert to AccountId32 and then to SS58 string let account_id32 = sp_runtime::AccountId32::from(account_bytes); let sender_ss58 = account_id32.to_ss58check_with_version(sp_core::crypto::Ss58AddressFormat::custom(0)); @@ -419,7 +406,6 @@ pub mod pallet { sp_io::hashing::keccak_256(&encoded) } - /// Hash message for signature verification fn hash_message(request_id: &[u8; 32], output: &[u8]) -> [u8; 32] { let mut data = Vec::with_capacity(32 + output.len()); data.extend_from_slice(request_id); @@ -427,28 +413,23 @@ pub mod pallet { hashing::keccak_256(&data) } - /// Verify signature by recovering address fn verify_signature_from_address( message_hash: &[u8; 32], signature: &pallet_signet::Signature, expected_address: &[u8; 20], ) -> DispatchResult { - // Validate recovery ID ensure!(signature.recovery_id < 4, Error::::InvalidSignature); - // Prepare signature bytes (r || s || v) let mut sig_bytes = [0u8; 65]; sig_bytes[..32].copy_from_slice(&signature.big_r.x); sig_bytes[32..64].copy_from_slice(&signature.s); sig_bytes[64] = signature.recovery_id; - // Recover public key (returns 64 bytes without 0x04 prefix) let pubkey = sp_io::crypto::secp256k1_ecdsa_recover(&sig_bytes, message_hash) .map_err(|_| Error::::InvalidSignature)?; - // Hash all 64 bytes (no prefix to skip) let pubkey_hash = hashing::keccak_256(&pubkey); - let recovered_address = &pubkey_hash[12..]; // Last 20 bytes + let recovered_address = &pubkey_hash[12..]; ensure!(recovered_address == expected_address, Error::::InvalidSigner); diff --git a/pallets/erc20-vault/src/tests.rs b/pallets/erc20-vault/src/tests.rs index 7c5b3e7dda..68e9c6c2e7 100644 --- a/pallets/erc20-vault/src/tests.rs +++ b/pallets/erc20-vault/src/tests.rs @@ -189,6 +189,8 @@ pub fn new_test_ext() -> sp_io::TestExternalities { 100, // deposit bounded_chain_id(b"test-chain".to_vec()), ); + let pallet_account = Erc20Vault::account_id(); + let _ = >::deposit_creating(&pallet_account, 10_000); }); ext } @@ -257,10 +259,10 @@ fn compute_request_id( ) .expect("build_evm_tx should succeed"); - // CHANGE THIS PART - use PALLET account, not requester - let pallet_account = Erc20Vault::account_id(); // <-- Changed from requester + // Use PALLET account as sender (not requester) + let pallet_account = Erc20Vault::account_id(); - let encoded = pallet_account.encode(); // <-- Changed from requester.encode() + let encoded = pallet_account.encode(); let mut account_bytes = [0u8; 32]; let len = encoded.len().min(32); account_bytes[..len].copy_from_slice(&encoded[..len]); @@ -268,7 +270,7 @@ fn compute_request_id( let account_id32 = sp_runtime::AccountId32::from(account_bytes); let sender_ss58 = account_id32.to_ss58check_with_version(sp_core::crypto::Ss58AddressFormat::custom(0)); - // Path still uses requester (this is correct) + // Path uses requester (this is correct) let path = format!("0x{}", hex::encode(requester.encode())); let encoded = ( @@ -457,7 +459,7 @@ fn test_deposit_erc20_success() { // Check deposit was taken (signet deposit) assert_eq!( >::free_balance(&requester), - balance_before - 101 // 100 for signet + 1 for ED + balance_before - 100 // 100 for signet deposit ); }); } diff --git a/signet-ts-client-scripts/erc20vault.test.ts b/signet-ts-client-scripts/erc20vault.test.ts index 0b2f9cde5f..f5c169e1a8 100644 --- a/signet-ts-client-scripts/erc20vault.test.ts +++ b/signet-ts-client-scripts/erc20vault.test.ts @@ -17,7 +17,6 @@ const SEPOLIA_RPC = function getPalletAccountId(): Uint8Array { const palletId = new TextEncoder().encode("py/erc20"); - // Substrate's into_account_truncating: "modl" + pallet_id + padding const modl = new TextEncoder().encode("modl"); const data = new Uint8Array(32); data.set(modl, 0); @@ -25,6 +24,98 @@ function getPalletAccountId(): Uint8Array { return data; } +async function submitWithRetry( + tx: any, + signer: any, + api: ApiPromise, + label: string, + maxRetries: number = 1, + timeoutMs: number = 60000 // 60 seconds +): Promise<{ events: any[] }> { + let attempt = 0; + + while (attempt <= maxRetries) { + try { + console.log(`${label} - Attempt ${attempt + 1}/${maxRetries + 1}`); + + const result = await new Promise<{ events: any[] }>((resolve, reject) => { + let unsubscribe: any; + + const timer = setTimeout(() => { + if (unsubscribe) unsubscribe(); + console.log(`ā±ļø ${label} timed out after ${timeoutMs}ms`); + reject(new Error("TIMEOUT")); + }, timeoutMs); + + tx.signAndSend(signer, (result: ISubmittableResult) => { + const { status, events, dispatchError } = result; + + if (status.isInBlock) { + clearTimeout(timer); + if (unsubscribe) unsubscribe(); + + console.log( + `āœ… ${label} included in block ${status.asInBlock.toHex()}` + ); + + // Check for dispatch errors + if (dispatchError) { + if (dispatchError.isModule) { + const decoded = api.registry.findMetaError( + dispatchError.asModule + ); + reject( + new Error( + `${decoded.section}.${decoded.name}: ${decoded.docs.join( + " " + )}` + ) + ); + } else { + reject(new Error(dispatchError.toString())); + } + return; + } + + resolve({ events: Array.from(events) }); + } else if (status.isInvalid) { + clearTimeout(timer); + if (unsubscribe) unsubscribe(); + console.log(`āš ļø ${label} marked as Invalid`); + reject(new Error("INVALID_TX")); + } else if (status.isDropped) { + clearTimeout(timer); + if (unsubscribe) unsubscribe(); + reject(new Error(`${label} dropped`)); + } + }) + .then((unsub: any) => { + unsubscribe = unsub; + }) + .catch((error: any) => { + clearTimeout(timer); + reject(error); + }); + }); + + return result; + } catch (error: any) { + if ( + (error.message === "INVALID_TX" || error.message === "TIMEOUT") && + attempt < maxRetries + ) { + console.log(`šŸ”„ Retrying ${label}...`); + attempt++; + await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait 2s before retry + continue; + } + throw error; + } + } + + throw new Error(`${label} failed after ${maxRetries + 1} attempts`); +} + describe("ERC20 Vault Integration", () => { let api: ApiPromise; let alice: any; @@ -55,25 +146,30 @@ describe("ERC20 Vault Integration", () => { if (bobBalance.free.toBigInt() < 1000000000000n) { console.log("Funding Bob's account for server responses..."); - await new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - reject(new Error("Bob funding timeout")); - }, 30000); - - api.tx.balances - .transferKeepAlive(bob.address, 100000000000000n) - .signAndSend(alice, (result: ISubmittableResult) => { - if (result.dispatchError) { - clearTimeout(timeout); - reject(result.dispatchError); - } else if (result.status.isInBlock) { - // Changed from isFinalized - clearTimeout(timeout); - console.log("Bob's account funded!"); - resolve(result.status.asInBlock); - } - }); - }); + const bobFundTx = api.tx.balances.transferKeepAlive( + bob.address, + 100000000000000n + ); + await submitWithRetry(bobFundTx, alice, api, "Fund Bob account"); + } + + const palletAccountId = getPalletAccountId(); + const palletSS58 = encodeAddress(palletAccountId, 0); + + const { data: palletBalance } = (await api.query.system.account( + palletSS58 + )) as any; + + const fundingAmount = 10000000000000n; + + if (palletBalance.free.toBigInt() < fundingAmount) { + console.log(`Funding ERC20 vault pallet account ${palletSS58}...`); + + const fundTx = api.tx.balances.transferKeepAlive( + palletSS58, + fundingAmount + ); + await submitWithRetry(fundTx, alice, api, "Fund pallet account"); } signetClient = new SignetClient(api, alice); @@ -81,13 +177,9 @@ describe("ERC20 Vault Integration", () => { await signetClient.ensureInitialized(CHAIN_ID); - const palletAccountId = getPalletAccountId(); - const palletSS58 = encodeAddress(palletAccountId, 0); - const aliceAccountId = keyring.decodeAddress(alice.address); const aliceHexPath = "0x" + u8aToHex(aliceAccountId).slice(2); - // Derive using PALLET account but ALICE's path derivedPubKey = KeyDerivation.derivePublicKey( ROOT_PUBLIC_KEY, palletSS58, @@ -99,7 +191,7 @@ describe("ERC20 Vault Integration", () => { console.log(`\nšŸ”‘ Derived Ethereum Address: ${derivedEthAddress}`); await checkFunding(); - }, 30000); + }, 120000); afterAll(async () => { if (api) { @@ -110,7 +202,6 @@ describe("ERC20 Vault Integration", () => { async function checkFunding() { const ethBalance = await sepoliaProvider.getBalance(derivedEthAddress); - // Check USDC balance const usdcContract = new ethers.Contract( USDC_SEPOLIA, ["function balanceOf(address) view returns (uint256)"], @@ -118,7 +209,6 @@ describe("ERC20 Vault Integration", () => { ); const usdcBalance = await usdcContract.balanceOf(derivedEthAddress); - // Estimate gas needed (conservative estimate) const feeData = await sepoliaProvider.getFeeData(); const gasLimit = 100000n; const estimatedGas = (feeData.maxFeePerGas || 30000000000n) * gasLimit; @@ -153,14 +243,20 @@ describe("ERC20 Vault Integration", () => { it("should complete full deposit and claim flow", async () => { const mpcEthAddress = ethAddressFromPubKey(ROOT_PUBLIC_KEY); - console.log("Initializing vault with MPC address:", mpcEthAddress); + console.log("Checking vault initialization..."); const mpcAddressBytes = Array.from(ethers.getBytes(mpcEthAddress)); - const initTx = api.tx.erc20Vault.initialize(mpcAddressBytes); - await initTx.signAndSend(alice); - await sleep(6000); + const existingConfig = await api.query.erc20Vault.vaultConfig(); + const configJson = existingConfig.toJSON(); - console.log("āœ… Vault initialized\n"); + if (configJson !== null) { + console.log("āš ļø Vault already initialized, skipping initialization"); + console.log(" Existing config:", existingConfig.toHuman()); + } else { + console.log("Initializing vault with MPC address:", mpcEthAddress); + const initTx = api.tx.erc20Vault.initialize(mpcAddressBytes); + await submitWithRetry(initTx, alice, api, "Initialize vault"); + } const amount = ethers.parseUnits("0.01", 6); const feeData = await sepoliaProvider.getFeeData(); @@ -222,45 +318,41 @@ describe("ERC20 Vault Integration", () => { console.log(`šŸ“‹ Request ID: ${ethers.hexlify(requestId)}\n`); - // Convert requestId to bytes if it's a hex string const requestIdBytes = typeof requestId === "string" ? ethers.getBytes(requestId) : requestId; const depositTx = api.tx.erc20Vault.depositErc20( - Array.from(requestIdBytes), // Use the bytes version + Array.from(requestIdBytes), Array.from(ethers.getBytes(USDC_SEPOLIA)), amount.toString(), txParams ); console.log("šŸš€ Submitting deposit_erc20 transaction..."); + const depositResult = await submitWithRetry( + depositTx, + alice, + api, + "Deposit ERC20" + ); - await new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - reject( - new Error( - "Deposit transaction timeout - Chopsticks may have flaked, retry test" - ) - ); - }, 15000); - - depositTx.signAndSend(alice, (result: ISubmittableResult) => { - if (result.dispatchError) { - clearTimeout(timeout); - if (result.dispatchError.isModule) { - const decoded = api.registry.findMetaError( - result.dispatchError.asModule - ); - reject(new Error(`${decoded.section}.${decoded.name}`)); - } else { - reject(new Error(result.dispatchError.toString())); - } - } else if (result.status.isInBlock) { - clearTimeout(timeout); - resolve(); - } - }); - }); + const signetEvents = depositResult.events.filter( + (record: any) => + record.event.section === "signet" && + record.event.method === "SignRespondRequested" + ); + + console.log( + `šŸ“Š Found ${signetEvents.length} SignRespondRequested event(s)` + ); + + if (signetEvents.length > 0) { + console.log( + "āœ… SignRespondRequested event emitted - MPC should pick it up!" + ); + } else { + console.log("āš ļø No SignRespondRequested event found!"); + } console.log("ā³ Waiting for MPC signature..."); @@ -347,11 +439,11 @@ describe("ERC20 Vault Integration", () => { if (outputBytes.length > 0) { const mode = outputBytes[0] & 0b11; if (mode === 0) { - outputBytes = outputBytes.slice(1); // Remove 1-byte SCALE prefix + outputBytes = outputBytes.slice(1); } else if (mode === 1) { - outputBytes = outputBytes.slice(2); // Remove 2-byte SCALE prefix + outputBytes = outputBytes.slice(2); } else if (mode === 2) { - outputBytes = outputBytes.slice(4); // Remove 4-byte SCALE prefix + outputBytes = outputBytes.slice(4); } } @@ -371,26 +463,8 @@ describe("ERC20 Vault Integration", () => { readResponse.signature ); - await new Promise((resolve, reject) => { - claimTx.signAndSend(alice, (result: ISubmittableResult) => { - if (result.dispatchError) { - if (result.dispatchError.isModule) { - const decoded = api.registry.findMetaError( - result.dispatchError.asModule - ); - reject( - new Error(`Claim failed: ${decoded.section}.${decoded.name}`) - ); - } else { - reject(new Error(result.dispatchError.toString())); - } - } else if (result.status.isInBlock) { - resolve(); - } - }); - }); - - console.log("āœ… Claim transaction confirmed\n"); + console.log("šŸš€ Submitting claim transaction..."); + await submitWithRetry(claimTx, alice, api, "Claim ERC20"); const balanceAfter = await api.query.erc20Vault.userBalances( alice.address, @@ -476,8 +550,4 @@ describe("ERC20 Vault Integration", () => { const hash = ethers.keccak256("0x" + pubKey.slice(4)); return "0x" + hash.slice(-40); } - - function sleep(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); - } }); From a9ef18eab98b1d0191120d70c6feef3e71c638e2 Mon Sep 17 00:00:00 2001 From: Osman Abdelnasir Date: Fri, 10 Oct 2025 14:00:47 +0400 Subject: [PATCH 28/32] fix: README --- signet-ts-client-scripts/README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/signet-ts-client-scripts/README.md b/signet-ts-client-scripts/README.md index fbcb29d932..415cd96d4f 100644 --- a/signet-ts-client-scripts/README.md +++ b/signet-ts-client-scripts/README.md @@ -17,7 +17,7 @@ Clone and run the signature server that responds to Substrate signature requests ```bash # Clone the server repository -git clone https://github.com/sig-net/chain-signatures-solana.git +git clone https://github.com/sig-net/solana-signet-program cd chain-signatures-solana/clients/response-server # Install dependencies @@ -33,7 +33,10 @@ SUBSTRATE_SIGNER_SEED=//Bob PRIVATE_KEY_TESTNET=0x... # Your private key for signing # Ethereum Configuration (for vault monitoring) -SEPOLIA_RPC=https://sepolia.infura.io/v3/your_infura_key_here +SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/your_infura_key_here + +# Dummy solana key +SOLANA_PRIVATE_KEY='[16,151,155,240,122,151,187,95,145,26,179,205,196,113,3,62,17,105,18,240,197,176,45,90,176,108,30,106,182,43,7,104,80,202,59,51,239,219,236,17,39,204,155,35,175,195,17,172,201,196,134,125,25,214,148,76,102,47,123,37,203,86,159,147]' EOF # Start the server From d868914f6553db5ef965db3c1a4dab697f173227 Mon Sep 17 00:00:00 2001 From: Osman Abdelnasir Date: Fri, 10 Oct 2025 14:28:18 +0400 Subject: [PATCH 29/32] fix: README --- signet-ts-client-scripts/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signet-ts-client-scripts/README.md b/signet-ts-client-scripts/README.md index 415cd96d4f..1cbd447589 100644 --- a/signet-ts-client-scripts/README.md +++ b/signet-ts-client-scripts/README.md @@ -13,7 +13,7 @@ Test client for the Signet pallet on Substrate/Polkadot. Validates signature gen ### 1. Start the Signature Server -Clone and run the signature server that responds to Substrate signature requests: +Clone and run the signature server that responds to Substrate signature requests. Add .env to the root of the repository: ```bash # Clone the server repository From 9a74d13345603dadfbf7ce5c213fedf3ac71819c Mon Sep 17 00:00:00 2001 From: Osman Abdelnasir Date: Mon, 3 Nov 2025 09:21:51 +0400 Subject: [PATCH 30/32] feat: move to caip2 --- pallets/erc20-vault/src/lib.rs | 23 +++++------ pallets/erc20-vault/src/tests.rs | 6 +-- pallets/signet/src/lib.rs | 45 ++++++++++----------- pallets/signet/src/tests.rs | 26 ++++++------ signet-ts-client-scripts/erc20vault.test.ts | 12 +++--- signet-ts-client-scripts/signet-client.ts | 16 +++++--- signet-ts-client-scripts/signet.test.ts | 10 ++--- 7 files changed, 68 insertions(+), 70 deletions(-) diff --git a/pallets/erc20-vault/src/lib.rs b/pallets/erc20-vault/src/lib.rs index a9fdaf4ccb..b802857565 100644 --- a/pallets/erc20-vault/src/lib.rs +++ b/pallets/erc20-vault/src/lib.rs @@ -243,14 +243,14 @@ pub mod pallet { tx_params.max_fee_per_gas, tx_params.max_priority_fee_per_gas, vec![], - tx_params.chain_id, + 11155111, )?; // Generate and verify request ID let computed_request_id = Self::generate_request_id( &Self::account_id(), &rlp_encoded, - 60, + "eip155:11155111", 0, &path, b"ecdsa", @@ -284,20 +284,19 @@ pub mod pallet { let callback_schema = serde_json::to_vec(&serde_json::json!("bool")).map_err(|_| Error::::SerializationError)?; - // Call sign_respond from the pallet account - pallet_signet::Pallet::::sign_respond( + // Call sign_bidirectional from the pallet account + pallet_signet::Pallet::::sign_bidirectional( frame_system::RawOrigin::Signed(Self::account_id()).into(), BoundedVec::try_from(rlp_encoded).map_err(|_| Error::::SerializationError)?, - 60, - 0, + BoundedVec::try_from(b"eip155:11155111".to_vec()).map_err(|_| Error::::SerializationError)?, // CAIP-2 format for Sepolia + 0, // key_version BoundedVec::try_from(path).map_err(|_| Error::::PathTooLong)?, BoundedVec::try_from(b"ecdsa".to_vec()).map_err(|_| Error::::SerializationError)?, BoundedVec::try_from(b"ethereum".to_vec()).map_err(|_| Error::::SerializationError)?, BoundedVec::try_from(vec![]).map_err(|_| Error::::SerializationError)?, - pallet_signet::SerializationFormat::AbiJson, - BoundedVec::try_from(explorer_schema).map_err(|_| Error::::SerializationError)?, - pallet_signet::SerializationFormat::Borsh, - BoundedVec::try_from(callback_schema).map_err(|_| Error::::SerializationError)?, + Self::account_id(), // program_id (use pallet account) + BoundedVec::try_from(explorer_schema).map_err(|_| Error::::SerializationError)?, // output_deserialization_schema + BoundedVec::try_from(callback_schema).map_err(|_| Error::::SerializationError)?, // respond_serialization_schema )?; Self::deposit_event(Event::DepositRequested { @@ -373,7 +372,7 @@ pub mod pallet { fn generate_request_id( sender: &T::AccountId, transaction_data: &[u8], - slip44_chain_id: u32, + caip2_id: &str, key_version: u32, path: &[u8], algo: &[u8], @@ -394,7 +393,7 @@ pub mod pallet { let encoded = ( sender_ss58.as_str(), transaction_data, - slip44_chain_id, + caip2_id, key_version, core::str::from_utf8(path).unwrap_or(""), core::str::from_utf8(algo).unwrap_or(""), diff --git a/pallets/erc20-vault/src/tests.rs b/pallets/erc20-vault/src/tests.rs index 68e9c6c2e7..9f79c40643 100644 --- a/pallets/erc20-vault/src/tests.rs +++ b/pallets/erc20-vault/src/tests.rs @@ -255,7 +255,7 @@ fn compute_request_id( tx_params.max_fee_per_gas, tx_params.max_priority_fee_per_gas, vec![], - tx_params.chain_id, + 11155111, ) .expect("build_evm_tx should succeed"); @@ -276,7 +276,7 @@ fn compute_request_id( let encoded = ( sender_ss58.as_str(), rlp_encoded.as_slice(), - 60u32, + "eip155:11155111", 0u32, path.as_str(), "ecdsa", @@ -452,7 +452,7 @@ fn test_deposit_erc20_success() { assert!(events.iter().any(|e| { matches!( &e.event, - RuntimeEvent::Signet(pallet_signet::Event::SignRespondRequested { .. }) + RuntimeEvent::Signet(pallet_signet::Event::SignBidirectionalRequested { .. }) ) })); diff --git a/pallets/signet/src/lib.rs b/pallets/signet/src/lib.rs index d336832665..43e689dc55 100644 --- a/pallets/signet/src/lib.rs +++ b/pallets/signet/src/lib.rs @@ -156,21 +156,20 @@ pub mod pallet { params: Vec, }, - /// Sign-respond request event - SignRespondRequested { + /// Sign bidirectional request event + SignBidirectionalRequested { sender: T::AccountId, - transaction_data: Vec, - slip44_chain_id: u32, + serialized_transaction: Vec, + caip2_id: Vec, key_version: u32, deposit: BalanceOf, path: Vec, algo: Vec, dest: Vec, params: Vec, - explorer_deserialization_format: u8, - explorer_deserialization_schema: Vec, - callback_serialization_format: u8, - callback_serialization_schema: Vec, + program_id: T::AccountId, + output_deserialization_schema: Vec, + respond_serialization_schema: Vec, }, /// Signature response event @@ -188,7 +187,7 @@ pub mod pallet { }, /// Read response event - ReadResponded { + RespondBidirectionalEvent { request_id: [u8; 32], responder: T::AccountId, serialized_output: Vec, @@ -336,19 +335,18 @@ pub mod pallet { /// Request a signature for a serialized transaction #[pallet::call_index(4)] #[pallet::weight(::WeightInfo::sign_respond())] - pub fn sign_respond( + pub fn sign_bidirectional( origin: OriginFor, serialized_transaction: BoundedVec>, - slip44_chain_id: u32, + caip2_id: BoundedVec>, key_version: u32, path: BoundedVec>, algo: BoundedVec>, dest: BoundedVec>, params: BoundedVec>, - explorer_deserialization_format: SerializationFormat, - explorer_deserialization_schema: BoundedVec>, - callback_serialization_format: SerializationFormat, - callback_serialization_schema: BoundedVec>, + program_id: T::AccountId, + output_deserialization_schema: BoundedVec>, + respond_serialization_schema: BoundedVec>, ) -> DispatchResult { let requester = ensure_signed(origin)?; @@ -366,20 +364,19 @@ pub mod pallet { T::Currency::transfer(&requester, &pallet_account, deposit, ExistenceRequirement::KeepAlive)?; // Emit event - Self::deposit_event(Event::SignRespondRequested { + Self::deposit_event(Event::SignBidirectionalRequested { sender: requester, - transaction_data: serialized_transaction.to_vec(), - slip44_chain_id, + serialized_transaction: serialized_transaction.to_vec(), + caip2_id: caip2_id.to_vec(), key_version, deposit, path: path.to_vec(), algo: algo.to_vec(), dest: dest.to_vec(), params: params.to_vec(), - explorer_deserialization_format: explorer_deserialization_format as u8, - explorer_deserialization_schema: explorer_deserialization_schema.to_vec(), - callback_serialization_format: callback_serialization_format as u8, - callback_serialization_schema: callback_serialization_schema.to_vec(), + program_id, + output_deserialization_schema: output_deserialization_schema.to_vec(), + respond_serialization_schema: respond_serialization_schema.to_vec(), }); Ok(()) @@ -434,7 +431,7 @@ pub mod pallet { /// Provide a read response with signature #[pallet::call_index(7)] #[pallet::weight(::WeightInfo::read_respond())] - pub fn read_respond( + pub fn respond_bidirectional( origin: OriginFor, request_id: [u8; 32], serialized_output: BoundedVec>, @@ -443,7 +440,7 @@ pub mod pallet { let responder = ensure_signed(origin)?; // Just emit event - Self::deposit_event(Event::ReadResponded { + Self::deposit_event(Event::RespondBidirectionalEvent { request_id, responder, serialized_output: serialized_output.to_vec(), diff --git a/pallets/signet/src/tests.rs b/pallets/signet/src/tests.rs index 1d37305df5..df6b7475a8 100644 --- a/pallets/signet/src/tests.rs +++ b/pallets/signet/src/tests.rs @@ -618,7 +618,7 @@ fn create_test_signature() -> Signature { } #[test] -fn test_sign_respond_works() { +fn test_sign_bidirectional_works() { new_test_ext().execute_with(|| { let admin = 1u64; let requester = 2u64; @@ -635,18 +635,17 @@ fn test_sign_respond_works() { let slip44_chain_id = 60u32; let balance_before = Balances::free_balance(&requester); - assert_ok!(Signet::sign_respond( + assert_ok!(Signet::sign_bidirectional( RuntimeOrigin::signed(requester), bounded_u8::<65536>(tx_data.clone()), - slip44_chain_id, + bounded_u8::<64>(b"eip155:60".to_vec()), 1, bounded_u8::<256>(b"path".to_vec()), bounded_u8::<32>(b"ecdsa".to_vec()), bounded_u8::<64>(b"callback".to_vec()), bounded_u8::<1024>(b"{}".to_vec()), - SerializationFormat::AbiJson, + requester, bounded_u8::<4096>(b"schema1".to_vec()), - SerializationFormat::Borsh, bounded_u8::<4096>(b"schema2".to_vec()) )); @@ -655,13 +654,13 @@ fn test_sign_respond_works() { let events = System::events(); let event_found = events .iter() - .any(|e| matches!(&e.event, RuntimeEvent::Signet(Event::SignRespondRequested { .. }))); + .any(|e| matches!(&e.event, RuntimeEvent::Signet(Event::SignBidirectionalRequested { .. }))); assert!(event_found); }); } #[test] -fn test_sign_respond_empty_transaction_fails() { +fn test_sign_bidirectional_empty_transaction_fails() { new_test_ext().execute_with(|| { let admin = 1u64; let requester = 2u64; @@ -674,18 +673,17 @@ fn test_sign_respond_empty_transaction_fails() { )); assert_noop!( - Signet::sign_respond( + Signet::sign_bidirectional( RuntimeOrigin::signed(requester), bounded_u8::<65536>(vec![]), - 60, + bounded_u8::<64>(b"eip155:60".to_vec()), 1, bounded_u8::<256>(b"path".to_vec()), bounded_u8::<32>(b"algo".to_vec()), bounded_u8::<64>(b"dest".to_vec()), bounded_u8::<1024>(b"params".to_vec()), - SerializationFormat::Borsh, + requester, bounded_u8::<4096>(vec![]), - SerializationFormat::Borsh, bounded_u8::<4096>(vec![]) ), Error::::InvalidTransaction @@ -818,14 +816,14 @@ fn test_respond_error_batch() { } #[test] -fn test_read_respond() { +fn test_respond_bidirectional() { new_test_ext().execute_with(|| { let responder = 1u64; let request_id = [99u8; 32]; let output = b"read_output_data".to_vec(); let signature = create_test_signature(); - assert_ok!(Signet::read_respond( + assert_ok!(Signet::respond_bidirectional( RuntimeOrigin::signed(responder), request_id, bounded_u8::<65536>(output.clone()), @@ -833,7 +831,7 @@ fn test_read_respond() { )); System::assert_last_event( - Event::ReadResponded { + Event::RespondBidirectionalEvent { request_id, responder, serialized_output: output, diff --git a/signet-ts-client-scripts/erc20vault.test.ts b/signet-ts-client-scripts/erc20vault.test.ts index f5c169e1a8..dbaa27fe55 100644 --- a/signet-ts-client-scripts/erc20vault.test.ts +++ b/signet-ts-client-scripts/erc20vault.test.ts @@ -307,7 +307,7 @@ describe("ERC20 Vault Integration", () => { palletSS58, Array.from(ethers.getBytes(tx.unsignedSerialized)), { - slip44ChainId: 60, + caip2Id: "eip155:11155111", keyVersion: 0, path: aliceHexPath, algo: "ecdsa", @@ -339,19 +339,19 @@ describe("ERC20 Vault Integration", () => { const signetEvents = depositResult.events.filter( (record: any) => record.event.section === "signet" && - record.event.method === "SignRespondRequested" + record.event.method === "SignBidirectionalRequested" ); console.log( - `šŸ“Š Found ${signetEvents.length} SignRespondRequested event(s)` + `šŸ“Š Found ${signetEvents.length} SignBidirectionalRequested event(s)` ); if (signetEvents.length > 0) { console.log( - "āœ… SignRespondRequested event emitted - MPC should pick it up!" + "āœ… SignBidirectionalRequested event emitted - MPC should pick it up!" ); } else { - console.log("āš ļø No SignRespondRequested event found!"); + console.log("āš ļø No SignBidirectionalRequested event found!"); } console.log("ā³ Waiting for MPC signature..."); @@ -525,7 +525,7 @@ describe("ERC20 Vault Integration", () => { const { event } = record; if ( event.section === "signet" && - event.method === "ReadResponded" + event.method === "RespondBidirectionalEvent" ) { const [reqId, responder, output, signature] = event.data; if (ethers.hexlify(reqId.toU8a()) === requestId) { diff --git a/signet-ts-client-scripts/signet-client.ts b/signet-ts-client-scripts/signet-client.ts index a67d2c5bcf..38f8c33380 100644 --- a/signet-ts-client-scripts/signet-client.ts +++ b/signet-ts-client-scripts/signet-client.ts @@ -50,17 +50,19 @@ export class SignetClient { serializedTx: number[], params: any ): Promise { - const tx = this.api.tx.signet.signRespond( + const caip2Id = params.caip2Id; + const caip2Bytes = Array.from(new TextEncoder().encode(caip2Id)); + + const tx = this.api.tx.signet.signBidirectional( serializedTx, - params.slip44ChainId, + caip2Bytes, params.keyVersion, params.path, params.algo || "", params.dest || "", params.params || "", - params.schemas.explorer.format, + this.signer.address, // program_id Array.from(new TextEncoder().encode(params.schemas.explorer.schema)), - params.schemas.callback.format, Array.from(new TextEncoder().encode(params.schemas.callback.schema)) ); @@ -139,11 +141,13 @@ export class SignetClient { params: any ): string { const txHex = "0x" + Buffer.from(txData).toString("hex"); + const caip2Id = params.caip2Id; + const encoded = ethers.solidityPacked( [ "string", "bytes", - "uint32", + "string", "uint32", "string", "string", @@ -153,7 +157,7 @@ export class SignetClient { [ sender, txHex, - params.slip44ChainId, + caip2Id, params.keyVersion, params.path, params.algo || "", diff --git a/signet-ts-client-scripts/signet.test.ts b/signet-ts-client-scripts/signet.test.ts index c77eb70ded..db52d385a8 100644 --- a/signet-ts-client-scripts/signet.test.ts +++ b/signet-ts-client-scripts/signet.test.ts @@ -102,7 +102,7 @@ describe("Signet Pallet Integration", () => { expect(isValid).toBe(true); console.log(" āœ… Signature verification PASSED"); - }, 40000); + }, 80000); }); describe("SignRespond", () => { @@ -120,15 +120,15 @@ describe("Signet Pallet Integration", () => { }); const params = { - slip44ChainId: 60, + caip2Id: "eip155:11155111", keyVersion: 0, path: "testPath", algo: "", dest: "", params: "", schemas: { - explorer: { format: 0, schema: "{}" }, - callback: { format: 0, schema: "{}" }, + explorer: { schema: "{}" }, + callback: { schema: "{}" }, }, }; @@ -165,6 +165,6 @@ describe("Signet Pallet Integration", () => { expect(isValid).toBe(true); console.log(" āœ… Transaction signature verification PASSED"); - }, 40000); + }, 80000); }); }); From c735e2081598315208c23caebb1ae1e736d4621d Mon Sep 17 00:00:00 2001 From: Osman Abdelnasir Date: Tue, 25 Nov 2025 18:59:49 +0400 Subject: [PATCH 31/32] feat: add btc vault pallet --- Cargo.lock | 539 +++++++----- Cargo.toml | 2 + pallets/btc-vault/Cargo.toml | 69 ++ pallets/btc-vault/src/lib.rs | 444 ++++++++++ pallets/btc-vault/src/tests.rs | 436 ++++++++++ pallets/build-btc-tx/Cargo.toml | 43 + pallets/build-btc-tx/src/lib.rs | 172 ++++ pallets/build-btc-tx/src/tests.rs | 202 +++++ pallets/build-evm-tx/Cargo.toml | 3 +- pallets/build-evm-tx/src/lib.rs | 53 +- runtime/hydradx/Cargo.toml | 8 + runtime/hydradx/src/assets.rs | 19 + runtime/hydradx/src/lib.rs | 4 + signet-ts-client-scripts/btc-vault.test.ts | 917 +++++++++++++++++++++ signet-ts-client-scripts/coinselect.d.ts | 28 + signet-ts-client-scripts/package.json | 4 + signet-ts-client-scripts/yarn.lock | 536 +++++++++++- 17 files changed, 3250 insertions(+), 229 deletions(-) create mode 100644 pallets/btc-vault/Cargo.toml create mode 100644 pallets/btc-vault/src/lib.rs create mode 100644 pallets/btc-vault/src/tests.rs create mode 100644 pallets/build-btc-tx/Cargo.toml create mode 100644 pallets/build-btc-tx/src/lib.rs create mode 100644 pallets/build-btc-tx/src/tests.rs create mode 100644 signet-ts-client-scripts/btc-vault.test.ts create mode 100644 signet-ts-client-scripts/coinselect.d.ts diff --git a/Cargo.lock b/Cargo.lock index 13284cab62..6dca975bb6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -91,17 +91,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "ahash" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" -dependencies = [ - "getrandom 0.2.11", - "once_cell", - "version_check", -] - [[package]] name = "ahash" version = "0.8.12" @@ -109,7 +98,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.2.11", + "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -131,14 +120,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] -name = "alloy-json-abi" -version = "0.7.7" +name = "alloy-primitives" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc05b04ac331a9f07e3a4036ef7926e49a8bf84a99a1ccfc7e2ab55a5fcbb372" +checksum = "a0628ec0ba5b98b3370bb6be17b12f23bfce8ee4ad83823325a20546d9b03b78" dependencies = [ - "alloy-primitives", - "alloy-sol-type-parser", + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more 0.99.18", + "hex-literal", + "itoa", + "proptest", + "rand 0.8.5", + "ruint", "serde", + "tiny-keccak", ] [[package]] @@ -151,7 +149,7 @@ dependencies = [ "bytes", "cfg-if", "const-hex", - "derive_more", + "derive_more 0.99.18", "hex-literal", "itoa", "k256", @@ -173,6 +171,23 @@ dependencies = [ "bytes", ] +[[package]] +name = "alloy-sol-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a98ad1696a2e17f010ae8e43e9f2a1e930ed176a8e3ff77acfeff6dfb07b42c" +dependencies = [ + "const-hex", + "dunce", + "heck 0.4.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.106", + "syn-solidity 0.4.2", + "tiny-keccak", +] + [[package]] name = "alloy-sol-macro" version = "0.7.7" @@ -193,7 +208,6 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "867a5469d61480fea08c7333ffeca52d5b621f5ca2e44f271b117ec1fc9a0525" dependencies = [ - "alloy-json-abi", "alloy-sol-macro-input", "const-hex", "heck 0.5.0", @@ -202,7 +216,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.106", - "syn-solidity", + "syn-solidity 0.7.7", "tiny-keccak", ] @@ -212,38 +226,36 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e482dc33a32b6fadbc0f599adea520bd3aaa585c141a80b404d0a3e3fa72528" dependencies = [ - "alloy-json-abi", "const-hex", "dunce", "heck 0.5.0", "proc-macro2", "quote", - "serde_json", "syn 2.0.106", - "syn-solidity", + "syn-solidity 0.7.7", ] [[package]] -name = "alloy-sol-type-parser" -version = "0.7.7" +name = "alloy-sol-types" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbcba3ca07cf7975f15d871b721fb18031eec8bce51103907f6dcce00b255d98" +checksum = "98d7107bed88e8f09f0ddcc3335622d87bfb6821f3e0c7473329fb1cfad5e015" dependencies = [ + "alloy-primitives 0.4.2", + "alloy-sol-macro 0.4.2", + "const-hex", "serde", - "winnow 0.6.20", ] [[package]] name = "alloy-sol-types" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a49042c6d3b66a9fe6b2b5a8bf0d39fc2ae1ee0310a2a26ffedd79fb097878dd" +checksum = "a91ca40fa20793ae9c3841b83e74569d1cc9af29a2f5237314fd3452d51e38c7" dependencies = [ - "alloy-json-abi", - "alloy-primitives", - "alloy-sol-macro", + "alloy-primitives 0.7.7", + "alloy-sol-macro 0.7.7", "const-hex", - "serde", ] [[package]] @@ -362,6 +374,18 @@ dependencies = [ "ark-std 0.4.0", ] +[[package]] +name = "ark-bls12-377-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c7021f180a0cbea0380eba97c2af3c57074cdaffe0eef7e840e1c9f2841e55" +dependencies = [ + "ark-bls12-377", + "ark-ec", + "ark-models-ext", + "ark-std 0.4.0", +] + [[package]] name = "ark-bls12-381" version = "0.4.0" @@ -374,6 +398,45 @@ dependencies = [ "ark-std 0.4.0", ] +[[package]] +name = "ark-bls12-381-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1dc4b3d08f19e8ec06e949712f95b8361e43f1391d94f65e4234df03480631c" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff 0.4.2", + "ark-models-ext", + "ark-serialize 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-bw6-761" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e0605daf0cc5aa2034b78d008aaf159f56901d92a52ee4f6ecdfdac4f426700" +dependencies = [ + "ark-bls12-377", + "ark-ec", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-bw6-761-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccee5fba47266f460067588ee1bf070a9c760bf2050c1c509982c5719aadb4f2" +dependencies = [ + "ark-bw6-761", + "ark-ec", + "ark-ff 0.4.2", + "ark-models-ext", + "ark-std 0.4.0", +] + [[package]] name = "ark-ec" version = "0.4.2" @@ -462,39 +525,41 @@ dependencies = [ [[package]] name = "ark-ff" -version = "0.3.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" dependencies = [ - "ark-ff-asm 0.3.0", - "ark-ff-macros 0.3.0", - "ark-serialize 0.3.0", - "ark-std 0.3.0", + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", + "digest 0.10.7", + "itertools 0.10.5", "num-bigint", "num-traits", "paste", - "rustc_version 0.3.3", + "rustc_version 0.4.0", "zeroize", ] [[package]] name = "ark-ff" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" dependencies = [ - "ark-ff-asm 0.4.2", - "ark-ff-macros 0.4.2", - "ark-serialize 0.4.2", - "ark-std 0.4.0", - "derivative", + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec 0.7.4", "digest 0.10.7", - "itertools 0.10.5", + "educe", + "itertools 0.13.0", "num-bigint", "num-traits", "paste", - "rustc_version 0.4.0", "zeroize", ] @@ -518,6 +583,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.106", +] + [[package]] name = "ark-ff-macros" version = "0.3.0" @@ -543,6 +618,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "ark-models-ext" version = "0.4.1" @@ -595,22 +683,24 @@ dependencies = [ [[package]] name = "ark-serialize" -version = "0.3.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ - "ark-std 0.3.0", - "digest 0.9.0", + "ark-serialize-derive", + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", ] [[package]] name = "ark-serialize" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" dependencies = [ - "ark-serialize-derive", - "ark-std 0.4.0", + "ark-std 0.5.0", + "arrayvec 0.7.4", "digest 0.10.7", "num-bigint", ] @@ -641,6 +731,17 @@ name = "ark-std" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", + "rayon", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" dependencies = [ "num-traits", "rand 0.8.5", @@ -1312,30 +1413,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "borsh" -version = "1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" -dependencies = [ - "borsh-derive", - "cfg_aliases 0.2.1", - "hashbrown 0.12.3", -] - -[[package]] -name = "borsh-derive" -version = "1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" -dependencies = [ - "once_cell", - "proc-macro-crate 3.1.0", - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "bounded-collections" version = "0.2.0" @@ -1689,9 +1766,6 @@ name = "bytes" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" -dependencies = [ - "serde", -] [[package]] name = "bzip2-sys" @@ -1816,12 +1890,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - [[package]] name = "chacha" version = "0.3.0" @@ -2003,7 +2071,7 @@ checksum = "71367d3385c716342014ad17e3d19f7788ae514885a1f4c24f500260fb365e1a" dependencies = [ "libc", "once_cell", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] @@ -2035,7 +2103,7 @@ dependencies = [ "nom", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -2095,9 +2163,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6407bff74dea37e0fa3dc1c1c974e5d46405f0c987bf9997a0762adce71eda6" +checksum = "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735" dependencies = [ "cfg-if", "cpufeatures", @@ -3327,6 +3395,27 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "unicode-xid", +] + [[package]] name = "diff" version = "0.1.13" @@ -3555,6 +3644,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "either" version = "1.9.0" @@ -3611,6 +3712,26 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "enumflags2" version = "0.7.8" @@ -4936,19 +5057,19 @@ checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.7+wasi-0.2.4", + "wasip2", ] [[package]] @@ -5127,7 +5248,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.12", ] [[package]] @@ -5136,7 +5257,7 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.12", "allocator-api2", "serde", ] @@ -5581,7 +5702,6 @@ dependencies = [ "pallet-dynamic-fees", "pallet-elections-phragmen", "pallet-ema-oracle", - "pallet-erc20-vault", "pallet-ethereum", "pallet-evm", "pallet-evm-accounts", @@ -6156,6 +6276,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.10" @@ -7432,7 +7561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.52.0", ] @@ -7843,7 +7972,7 @@ checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ "bitflags 2.6.0", "cfg-if", - "cfg_aliases 0.1.1", + "cfg_aliases", "libc", ] @@ -7956,7 +8085,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -8379,6 +8508,26 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "pallet-alliance" +version = "37.0.0" +source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409-patch6#1ed662757b03c4f17dc2429c1df84e975d98513a" +dependencies = [ + "array-bytes", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-collective", + "pallet-identity", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-crypto-hashing", + "sp-io", + "sp-runtime", +] + [[package]] name = "pallet-asset-conversion" version = "20.0.0" @@ -9075,7 +9224,7 @@ source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409- dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -9443,31 +9592,6 @@ dependencies = [ "test-utils", ] -[[package]] -name = "pallet-erc20-vault" -version = "1.0.0" -dependencies = [ - "alloy-primitives", - "alloy-sol-types", - "borsh", - "frame-benchmarking", - "frame-support", - "frame-system", - "hex", - "pallet-balances", - "pallet-build-evm-tx", - "pallet-signet", - "parity-scale-codec", - "scale-info", - "secp256k1 0.27.0", - "serde", - "serde_json", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std", -] - [[package]] name = "pallet-ethereum" version = "4.0.0-dev" @@ -9576,7 +9700,7 @@ dependencies = [ name = "pallet-evm-precompile-call-permit" version = "0.1.4" dependencies = [ - "derive_more", + "derive_more 0.99.18", "evm", "fp-evm", "frame-support", @@ -9679,7 +9803,7 @@ dependencies = [ name = "pallet-genesis-history" version = "2.1.6" dependencies = [ - "derive_more", + "derive_more 0.99.18", "frame-support", "frame-system", "hex-literal", @@ -10667,7 +10791,7 @@ source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409- dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -10848,7 +10972,6 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "pallet-balances", "parity-scale-codec", "scale-info", "sp-core", @@ -10857,6 +10980,18 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-skip-feeless-payment" +version = "13.0.0" +source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409-patch6#1ed662757b03c4f17dc2429c1df84e975d98513a" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-runtime", +] + [[package]] name = "pallet-society" version = "38.0.0" @@ -11877,7 +12012,7 @@ name = "polkadot-availability-distribution" version = "18.0.0" source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409-patch6#1ed662757b03c4f17dc2429c1df84e975d98513a" dependencies = [ - "derive_more", + "derive_more 0.99.18", "fatality", "futures", "parity-scale-codec", @@ -11996,7 +12131,7 @@ name = "polkadot-dispute-distribution" version = "18.0.0" source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409-patch6#1ed662757b03c4f17dc2429c1df84e975d98513a" dependencies = [ - "derive_more", + "derive_more 0.99.18", "fatality", "futures", "futures-timer", @@ -12099,7 +12234,7 @@ version = "18.2.0" source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409-patch6#1ed662757b03c4f17dc2429c1df84e975d98513a" dependencies = [ "bitvec", - "derive_more", + "derive_more 0.99.18", "futures", "futures-timer", "itertools 0.11.0", @@ -12436,7 +12571,7 @@ dependencies = [ "async-channel 1.9.0", "async-trait", "bitvec", - "derive_more", + "derive_more 0.99.18", "fatality", "futures", "hex", @@ -12497,7 +12632,7 @@ source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409- dependencies = [ "async-trait", "bitvec", - "derive_more", + "derive_more 0.99.18", "fatality", "futures", "orchestra", @@ -12526,7 +12661,7 @@ version = "18.0.0" source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409-patch6#1ed662757b03c4f17dc2429c1df84e975d98513a" dependencies = [ "async-trait", - "derive_more", + "derive_more 0.99.18", "fatality", "futures", "futures-channel", @@ -12653,7 +12788,7 @@ version = "14.0.0" source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409-patch6#1ed662757b03c4f17dc2429c1df84e975d98513a" dependencies = [ "bounded-collections", - "derive_more", + "derive_more 0.99.18", "parity-scale-codec", "polkadot-core-primitives", "scale-info", @@ -12793,7 +12928,7 @@ source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409- dependencies = [ "bitflags 1.3.2", "bitvec", - "derive_more", + "derive_more 0.99.18", "frame-benchmarking", "frame-support", "frame-system", @@ -13341,7 +13476,7 @@ dependencies = [ "polkavm-common 0.9.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -13362,7 +13497,17 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ - "polkavm-derive-impl", + "polkavm-derive-impl 0.9.0", + "syn 2.0.106", +] + +[[package]] +name = "polkavm-derive-impl-macro" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9324fe036de37c17829af233b46ef6b5562d4a0c09bb7fdb9f8378856dee30cf" +dependencies = [ + "polkavm-derive-impl 0.10.0", "syn 2.0.106", ] @@ -13484,7 +13629,7 @@ name = "precompile-utils" version = "0.1.7" dependencies = [ "affix", - "derive_more", + "derive_more 0.99.18", "environmental", "evm", "fp-evm", @@ -13650,7 +13795,7 @@ checksum = "a172e6cc603231f2cf004232eabcecccc0da53ba576ab286ef7baa0cfc7927ad" dependencies = [ "coarsetime", "crossbeam-queue", - "derive_more", + "derive_more 0.99.18", "futures", "futures-timer", "nanorand", @@ -13915,7 +14060,7 @@ dependencies = [ "libc", "once_cell", "raw-cpuid", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "web-sys", "winapi", ] @@ -14122,7 +14267,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] @@ -14244,7 +14389,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87413ebb313323d431e85d0afc5a68222aaed972843537cbfe5f061cf1b4bcab" dependencies = [ - "derive_more", + "derive_more 0.99.18", "fs-err", "static_init", "thiserror 1.0.63", @@ -14602,13 +14747,14 @@ dependencies = [ [[package]] name = "ruint" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecb38f82477f20c5c3d62ef52d7c4e536e38ea9b73fb570a20c5cae0e14bcf6" +checksum = "a68df0380e5c9d20ce49534f292a36a7514ae21350726efe1865bdb1fa91d278" dependencies = [ "alloy-rlp", "ark-ff 0.3.0", "ark-ff 0.4.2", + "ark-ff 0.5.0", "bytes", "fastrlp 0.3.1", "fastrlp 0.4.0", @@ -14622,7 +14768,7 @@ dependencies = [ "rand 0.9.2", "rlp", "ruint-macro", - "serde", + "serde_core", "valuable", "zeroize", ] @@ -14811,6 +14957,15 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + [[package]] name = "rustc_version" version = "0.3.3" @@ -15483,7 +15638,7 @@ name = "sc-consensus-grandpa" version = "0.30.0" source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409-patch6#1ed662757b03c4f17dc2429c1df84e975d98513a" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.12", "array-bytes", "async-trait", "dyn-clone", @@ -15764,7 +15919,7 @@ name = "sc-network-gossip" version = "0.45.0" source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409-patch6#1ed662757b03c4f17dc2429c1df84e975d98513a" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.12", "futures", "futures-timer", "log", @@ -15868,7 +16023,7 @@ dependencies = [ "multiaddr 0.18.2", "multihash 0.19.1", "rand 0.8.5", - "thiserror", + "thiserror 1.0.63", "zeroize", ] @@ -16135,7 +16290,7 @@ name = "sc-sysinfo" version = "38.0.0" source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409-patch6#1ed662757b03c4f17dc2429c1df84e975d98513a" dependencies = [ - "derive_more", + "derive_more 0.99.18", "futures", "libc", "log", @@ -16285,7 +16440,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e98f3262c250d90e700bb802eb704e1f841e03331c2eb815e46516c4edbf5b27" dependencies = [ - "derive_more", + "derive_more 0.99.18", "parity-scale-codec", "scale-bits", "scale-type-resolver", @@ -16300,7 +16455,7 @@ checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" dependencies = [ "bitvec", "cfg-if", - "derive_more", + "derive_more 0.99.18", "parity-scale-codec", "scale-info-derive", "serde", @@ -16339,7 +16494,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9a8ef13a93c54d20580de1e5c413e624e53121d42fc7e2c11d10ef7f8b02367" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.12", "cfg-if", "hashbrown 0.13.2", ] @@ -16470,31 +16625,13 @@ dependencies = [ "libc", ] -[[package]] -name = "secp256k1" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" -dependencies = [ - "secp256k1-sys 0.8.2", -] - [[package]] name = "secp256k1" version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" dependencies = [ - "secp256k1-sys 0.9.2", -] - -[[package]] -name = "secp256k1-sys" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4473013577ec77b4ee3668179ef1186df3146e2cf2d927bd200974c6fe60fd99" -dependencies = [ - "cc", + "secp256k1-sys", ] [[package]] @@ -16548,6 +16685,15 @@ dependencies = [ "semver-parser 0.7.0", ] +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser 0.7.0", +] + [[package]] name = "semver" version = "0.11.0" @@ -16939,7 +17085,7 @@ dependencies = [ "bs58 0.5.1", "chacha20", "crossbeam-queue", - "derive_more", + "derive_more 0.99.18", "ed25519-zebra", "either", "event-listener 2.5.3", @@ -16988,7 +17134,7 @@ dependencies = [ "async-lock 2.8.0", "base64 0.21.5", "blake2-rfc", - "derive_more", + "derive_more 0.99.18", "either", "event-listener 2.5.3", "fnv", @@ -17642,7 +17788,7 @@ dependencies = [ "rand 0.8.5", "scale-info", "schnorrkel 0.11.4", - "secp256k1 0.28.2", + "secp256k1", "secrecy", "serde", "sp-crypto-hashing", @@ -17777,7 +17923,7 @@ dependencies = [ "parity-scale-codec", "polkavm-derive 0.9.1", "rustversion", - "secp256k1 0.28.2", + "secp256k1", "sp-core", "sp-crypto-hashing", "sp-externalities", @@ -18098,7 +18244,7 @@ name = "sp-trie" version = "37.0.0" source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409-patch6#1ed662757b03c4f17dc2429c1df84e975d98513a" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.12", "hash-db", "lazy_static", "memory-db", @@ -18361,7 +18507,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a2a1c578e98c1c16fc3b8ec1328f7659a500737d7a0c6d625e73e830ff9c1f6" dependencies = [ "bitflags 1.3.2", - "cfg_aliases 0.1.1", + "cfg_aliases", "libc", "parking_lot 0.11.2", "parking_lot_core 0.8.6", @@ -18375,7 +18521,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70a2595fc3aa78f2d0e45dd425b22282dd863273761cc77780914b2cf3003acf" dependencies = [ - "cfg_aliases 0.1.1", + "cfg_aliases", "memchr", "proc-macro2", "quote", @@ -18660,6 +18806,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn-solidity" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b837ef12ab88835251726eb12237655e61ec8dc8a280085d1961cdc3dfd047" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "syn-solidity" version = "0.7.7" @@ -18868,7 +19026,7 @@ checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -19713,15 +19871,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" diff --git a/Cargo.toml b/Cargo.toml index 1b63243d48..5170f86dcc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -162,9 +162,11 @@ pallet-broadcast = { path = "pallets/broadcast", default-features = false } liquidation-worker-support = { path = "liquidation-worker-support", default-features = false } pallet-hsm = { path = "pallets/hsm", default-features = false } pallet-build-evm-tx = { path = "pallets/build-evm-tx", default-features = false } +pallet-build-btc-tx = { path = "pallets/build-btc-tx", default-features = false } pallet-parameters = { path = "pallets/parameters", default-features = false } pallet-signet = { path = "pallets/signet", default-features = false } pallet-erc20-vault = { path = "pallets/erc20-vault", default-features = false } +pallet-btc-vault = { path = "pallets/btc-vault", default-features = false } hydra-dx-build-script-utils = { path = "utils/build-script-utils", default-features = false } scraper = { path = "scraper", default-features = false } diff --git a/pallets/btc-vault/Cargo.toml b/pallets/btc-vault/Cargo.toml new file mode 100644 index 0000000000..1c931f5a70 --- /dev/null +++ b/pallets/btc-vault/Cargo.toml @@ -0,0 +1,69 @@ +[package] +name = "pallet-btc-vault" +version = "1.0.0" +authors = ["Substrate DevHub "] +edition = "2021" +license = "Apache-2.0" +publish = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.12", default-features = false, features = ["derive"] } +scale-info = { version = "2.11.1", default-features = false, features = ["derive"] } +hex = { version = "0.4", default-features = false, features = ["alloc"] } +serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } + +frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409-2", default-features = false, optional = true } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409-2", default-features = false } +frame-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409-2", default-features = false } +sp-io = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409-2", default-features = false } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409-2", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409-2", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409-2", default-features = false } + +alloy-sol-types = { version = "0.8.14", default-features = false } + +pallet-signet = { path = "../signet", default-features = false } +pallet-build-btc-tx = { path = "../build-btc-tx", default-features = false } + +[dev-dependencies] +pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409-2" } +secp256k1 = { version = "0.29.1", features = ["rand", "recovery"] } + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "hex/std", + "serde/std", + "serde_json/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "sp-io/std", + "sp-core/std", + "sp-std/std", + "sp-runtime/std", + "alloy-sol-types/std", + "pallet-signet/std", + "pallet-build-btc-tx/std" +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", +] + +[target.'cfg(not(feature = "std"))'.dependencies] +borsh = { version = "1.5", default-features = false, features = ["derive", "hashbrown"] } + +[target.'cfg(feature = "std")'.dependencies] +borsh = { version = "1.5", default-features = false, features = ["derive", "std"] } \ No newline at end of file diff --git a/pallets/btc-vault/src/lib.rs b/pallets/btc-vault/src/lib.rs new file mode 100644 index 0000000000..380a278949 --- /dev/null +++ b/pallets/btc-vault/src/lib.rs @@ -0,0 +1,444 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; +use alloc::{format, vec}; + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::pallet_prelude::*; +use frame_support::traits::Currency; +use frame_support::PalletId; +use frame_support::{dispatch::DispatchResult, pallet_prelude::Weight, BoundedVec}; +use frame_system::pallet_prelude::*; +use sp_runtime::traits::{AccountIdConversion, Saturating}; +use sp_std::vec::Vec; + +const MAX_SERIALIZED_OUTPUT_LENGTH: u32 = 65536; + +#[cfg(test)] +mod tests; + +pub use pallet::*; + +/// Bitcoin testnet vault address (P2WPKH pubkey hash - 20 bytes) +/// tb1q38c2sg5nc3v0tkcle2uncdyqspc4kuawjxpf0j +pub const TESTNET_VAULT_ADDRESS: [u8; 20] = [ + 0x89, 0xf0, 0xa8, 0x23, 0x93, 0x8c, 0x58, 0xcf, 0x5b, 0x17, 0xc8, 0xeb, 0x93, 0xc6, 0x82, 0x80, 0x63, 0x5b, 0x73, + 0x4e, +]; + +/// Bitcoin testnet CAIP-2 chain ID +const BITCOIN_TESTNET_CAIP2: &str = "bip122:000000000933ea01ad0ee984209779ba"; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use sp_io::hashing; + + // ========================= Configuration ========================= + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_build_btc_tx::Config + pallet_signet::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type VaultPalletId: Get; + } + + #[pallet::pallet] + pub struct Pallet(_); + + // ========================= Storage ========================= + + /// Global vault configuration + #[pallet::storage] + #[pallet::getter(fn vault_config)] + pub type VaultConfig = StorageValue<_, VaultConfigData, OptionQuery>; + + /// Pending deposits awaiting signature + #[pallet::storage] + #[pallet::getter(fn pending_deposits)] + pub type PendingDeposits = StorageMap< + _, + Blake2_128Concat, + [u8; 32], // request_id + PendingDepositData, + OptionQuery, + >; + + /// User BTC balances (in satoshis) + #[pallet::storage] + #[pallet::getter(fn user_balances)] + pub type UserBalances = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + u64, // satoshis + ValueQuery, + >; + + // ========================= Types ========================= + + #[derive(Encode, Decode, TypeInfo, Clone, Debug, PartialEq, MaxEncodedLen)] + pub struct VaultConfigData { + pub mpc_root_signer_address: [u8; 20], + } + + #[derive(Encode, Decode, TypeInfo, Clone, Debug, MaxEncodedLen)] + pub struct PendingDepositData { + pub requester: AccountId, + pub amount: u64, // satoshis + pub path: BoundedVec>, + } + + // ========================= Events ========================= + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + VaultInitialized { + mpc_address: [u8; 20], + initialized_by: T::AccountId, + }, + DepositRequested { + request_id: [u8; 32], + requester: T::AccountId, + amount: u64, + }, + DepositClaimed { + request_id: [u8; 32], + claimer: T::AccountId, + amount: u64, + }, + DebugTxid { + txid: [u8; 32], + }, + DebugTransaction { + tx_hex: BoundedVec>, + version: u32, + locktime: u32, + }, + } + + // ========================= Errors ========================= + + #[pallet::error] + pub enum Error { + NotInitialized, + AlreadyInitialized, + InvalidRequestId, + DuplicateRequest, + DepositNotFound, + UnauthorizedClaimer, + InvalidSignature, + InvalidSigner, + InvalidOutput, + TransferFailed, + Overflow, + SerializationError, + PathTooLong, + PalletAccountNotFunded, + NoVaultOutput, + InvalidVaultOutput, + } + + // ========================= Hooks ========================= + + #[pallet::hooks] + impl Hooks> for Pallet {} + + // ========================= Extrinsics ========================= + + #[pallet::call] + impl Pallet { + /// Initialize the vault with MPC signer address + #[pallet::call_index(0)] + #[pallet::weight(Weight::from_parts(10_000, 0))] + pub fn initialize(origin: OriginFor, mpc_root_signer_address: [u8; 20]) -> DispatchResult { + let initializer = ensure_signed(origin)?; + ensure!(VaultConfig::::get().is_none(), Error::::AlreadyInitialized); + + VaultConfig::::put(VaultConfigData { + mpc_root_signer_address, + }); + + Self::deposit_event(Event::VaultInitialized { + mpc_address: mpc_root_signer_address, + initialized_by: initializer, + }); + + Ok(()) + } + + /// Request to deposit BTC tokens + /// Note: The pallet account must be funded before calling this + #[pallet::call_index(1)] + #[pallet::weight(Weight::from_parts(100_000, 0))] + pub fn deposit_btc( + origin: OriginFor, + request_id: [u8; 32], + inputs: BoundedVec, + outputs: BoundedVec, + lock_time: u32, + ) -> DispatchResult { + let requester = ensure_signed(origin)?; + + // Ensure vault is initialized + ensure!(VaultConfig::::get().is_some(), Error::::NotInitialized); + + // Ensure no duplicate request + ensure!( + PendingDeposits::::get(&request_id).is_none(), + Error::::DuplicateRequest + ); + + // Get signet deposit amount and pallet account + let signet_deposit = pallet_signet::Pallet::::signature_deposit(); + let pallet_account = Self::account_id(); + let existential_deposit = ::Currency::minimum_balance(); + + // Ensure pallet account has sufficient balance + let pallet_balance = ::Currency::free_balance(&pallet_account); + let required_balance = existential_deposit.saturating_add(signet_deposit); + ensure!(pallet_balance >= required_balance, Error::::PalletAccountNotFunded); + + // Transfer signet deposit from requester to pallet account + ::Currency::transfer( + &requester, + &pallet_account, + signet_deposit, + frame_support::traits::ExistenceRequirement::AllowDeath, + )?; + + // Find vault output and extract deposit amount + let vault_script_pubkey = Self::create_p2wpkh_script(&TESTNET_VAULT_ADDRESS); + let deposit_amount = outputs + .iter() + .find(|output| output.script_pubkey.to_vec() == vault_script_pubkey) + .map(|output| output.value) + .ok_or(Error::::NoVaultOutput)?; + + ensure!(deposit_amount > 0, Error::::InvalidVaultOutput); + + // Use requester account as path + let path = { + let encoded = requester.encode(); + format!("0x{}", hex::encode(encoded)).into_bytes() + }; + + // Build PSBT + let psbt_bytes = pallet_build_btc_tx::Pallet::::build_bitcoin_tx( + frame_system::RawOrigin::Signed(requester.clone()).into(), + inputs.clone(), + outputs.clone(), + lock_time, + )?; + + let txid = pallet_build_btc_tx::Pallet::::get_txid( + frame_system::RawOrigin::Signed(requester.clone()).into(), + inputs.clone(), + outputs.clone(), + lock_time, + )?; + + Self::deposit_event(Event::DebugTxid { txid }); + + Self::deposit_event(Event::DebugTransaction { + tx_hex: psbt_bytes.clone().try_into().unwrap_or_default(), + version: 2, + locktime: lock_time, + }); + + // Generate and verify request ID + let computed_request_id = Self::generate_request_id( + &Self::account_id(), + &txid, + BITCOIN_TESTNET_CAIP2, + 0, + &path, + b"ecdsa", + b"bitcoin", + b"", + ); + + ensure!(computed_request_id == request_id, Error::::InvalidRequestId); + + // Store pending deposit + PendingDeposits::::insert( + &request_id, + PendingDepositData { + requester: requester.clone(), + amount: deposit_amount, + path: path.clone().try_into().map_err(|_| Error::::PathTooLong)?, + }, + ); + + // Create schemas for the response (simple boolean for Bitcoin) + let explorer_schema = + serde_json::to_vec(&serde_json::json!("bool")).map_err(|_| Error::::SerializationError)?; + + let callback_schema = + serde_json::to_vec(&serde_json::json!("bool")).map_err(|_| Error::::SerializationError)?; + + // Call sign_bidirectional from the pallet account + pallet_signet::Pallet::::sign_bidirectional( + frame_system::RawOrigin::Signed(Self::account_id()).into(), + BoundedVec::try_from(psbt_bytes).map_err(|_| Error::::SerializationError)?, + BoundedVec::try_from(BITCOIN_TESTNET_CAIP2.as_bytes().to_vec()) + .map_err(|_| Error::::SerializationError)?, + 0, // key_version + BoundedVec::try_from(path).map_err(|_| Error::::PathTooLong)?, + BoundedVec::try_from(b"ecdsa".to_vec()).map_err(|_| Error::::SerializationError)?, + BoundedVec::try_from(b"bitcoin".to_vec()).map_err(|_| Error::::SerializationError)?, + BoundedVec::try_from(vec![]).map_err(|_| Error::::SerializationError)?, + Self::account_id(), // program_id (use pallet account) + BoundedVec::try_from(explorer_schema).map_err(|_| Error::::SerializationError)?, + BoundedVec::try_from(callback_schema).map_err(|_| Error::::SerializationError)?, + )?; + + Self::deposit_event(Event::DepositRequested { + request_id, + requester, + amount: deposit_amount, + }); + + Ok(()) + } + + /// Claim deposited BTC tokens after signature verification + #[pallet::call_index(2)] + #[pallet::weight(Weight::from_parts(50_000, 0))] + pub fn claim_btc( + origin: OriginFor, + request_id: [u8; 32], + serialized_output: BoundedVec>, + signature: pallet_signet::Signature, + ) -> DispatchResult { + let claimer = ensure_signed(origin)?; + + // Get pending deposit + let pending = PendingDeposits::::get(&request_id).ok_or(Error::::DepositNotFound)?; + + // Verify claimer is the original requester + ensure!(pending.requester == claimer, Error::::UnauthorizedClaimer); + + // Get vault config + let config = VaultConfig::::get().ok_or(Error::::NotInitialized)?; + + // Verify signature + let message_hash = Self::hash_message(&request_id, &serialized_output); + Self::verify_signature_from_address(&message_hash, &signature, &config.mpc_root_signer_address)?; + + // Check for error magic prefix + const ERROR_PREFIX: [u8; 4] = [0xDE, 0xAD, 0xBE, 0xEF]; + + let success = if serialized_output.len() >= 4 && &serialized_output[..4] == ERROR_PREFIX { + false + } else { + // Decode boolean (Borsh serialized) + use borsh::BorshDeserialize; + bool::try_from_slice(&serialized_output).map_err(|_| Error::::InvalidOutput)? + }; + + ensure!(success, Error::::TransferFailed); + + // Update user balance + UserBalances::::mutate(&claimer, |balance| -> DispatchResult { + *balance = balance.checked_add(pending.amount).ok_or(Error::::Overflow)?; + Ok(()) + })?; + + // Clean up storage + PendingDeposits::::remove(&request_id); + + Self::deposit_event(Event::DepositClaimed { + request_id, + claimer, + amount: pending.amount, + }); + + Ok(()) + } + } + + // ========================= Helper Functions ========================= + + impl Pallet { + fn generate_request_id( + sender: &T::AccountId, + txid: &[u8; 32], + caip2_id: &str, + key_version: u32, + path: &[u8], + algo: &[u8], + dest: &[u8], + params: &[u8], + ) -> [u8; 32] { + use alloy_sol_types::SolValue; + use sp_core::crypto::Ss58Codec; + + let encoded = sender.encode(); + let mut account_bytes = [0u8; 32]; + let len = encoded.len().min(32); + account_bytes[..len].copy_from_slice(&encoded[..len]); + + let account_id32 = sp_runtime::AccountId32::from(account_bytes); + let sender_ss58 = account_id32.to_ss58check_with_version(sp_core::crypto::Ss58AddressFormat::custom(0)); + + let encoded = ( + sender_ss58.as_str(), + txid.as_ref(), + caip2_id, + key_version, + core::str::from_utf8(path).unwrap_or(""), + core::str::from_utf8(algo).unwrap_or(""), + core::str::from_utf8(dest).unwrap_or(""), + core::str::from_utf8(params).unwrap_or(""), + ) + .abi_encode_packed(); + + sp_io::hashing::keccak_256(&encoded) + } + + fn hash_message(request_id: &[u8; 32], output: &[u8]) -> [u8; 32] { + let mut data = Vec::with_capacity(32 + output.len()); + data.extend_from_slice(request_id); + data.extend_from_slice(output); + hashing::keccak_256(&data) + } + + fn verify_signature_from_address( + message_hash: &[u8; 32], + signature: &pallet_signet::Signature, + expected_address: &[u8; 20], + ) -> DispatchResult { + ensure!(signature.recovery_id < 4, Error::::InvalidSignature); + + let mut sig_bytes = [0u8; 65]; + sig_bytes[..32].copy_from_slice(&signature.big_r.x); + sig_bytes[32..64].copy_from_slice(&signature.s); + sig_bytes[64] = signature.recovery_id; + + let pubkey = sp_io::crypto::secp256k1_ecdsa_recover(&sig_bytes, message_hash) + .map_err(|_| Error::::InvalidSignature)?; + + let pubkey_hash = hashing::keccak_256(&pubkey); + let recovered_address = &pubkey_hash[12..]; + + ensure!(recovered_address == expected_address, Error::::InvalidSigner); + + Ok(()) + } + + fn create_p2wpkh_script(pubkey_hash: &[u8; 20]) -> Vec { + let mut script = Vec::with_capacity(22); + script.push(0x00); // OP_0 + script.push(0x14); // Push 20 bytes + script.extend_from_slice(pubkey_hash); + script + } + } + + impl Pallet { + pub fn account_id() -> T::AccountId { + T::VaultPalletId::get().into_account_truncating() + } + } +} diff --git a/pallets/btc-vault/src/tests.rs b/pallets/btc-vault/src/tests.rs new file mode 100644 index 0000000000..1ae820612b --- /dev/null +++ b/pallets/btc-vault/src/tests.rs @@ -0,0 +1,436 @@ +use crate::{self as pallet_btc_vault, *}; +use frame_support::{assert_noop, assert_ok, parameter_types, traits::Currency as CurrencyTrait, PalletId, BoundedVec}; +use frame_system as system; +use secp256k1::{Message, PublicKey, Secp256k1, SecretKey}; +use sp_core::H256; +use sp_io::hashing::keccak_256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; + +// Test secret key for signing +fn get_test_secret_key() -> SecretKey { + SecretKey::from_slice(&[42u8; 32]).expect("Valid secret key") +} + +fn bounded_u8(v: Vec) -> BoundedVec> { + BoundedVec::try_from(v).unwrap() +} + +fn bounded_chain_id(v: Vec) -> BoundedVec { + BoundedVec::try_from(v).unwrap() +} + +// Get public key from secret key +fn get_test_public_key() -> PublicKey { + let secp = Secp256k1::new(); + let secret_key = get_test_secret_key(); + PublicKey::from_secret_key(&secp, &secret_key) +} + +fn public_key_to_btc_address(public_key: &PublicKey) -> [u8; 20] { + let uncompressed = public_key.serialize_uncompressed(); + let hash = keccak_256(&uncompressed[1..]); + let mut address = [0u8; 20]; + address.copy_from_slice(&hash[12..]); + address +} + +// Create a valid signature for testing using secp256k1 directly +fn create_valid_signature(message_hash: &[u8; 32]) -> pallet_signet::Signature { + let secp = Secp256k1::new(); + let secret_key = get_test_secret_key(); + let message = Message::from_slice(message_hash).expect("Valid message hash"); + + let sig = secp.sign_ecdsa_recoverable(&message, &secret_key); + let (recovery_id, sig_bytes) = sig.serialize_compact(); + + let mut r = [0u8; 32]; + let mut s = [0u8; 32]; + r.copy_from_slice(&sig_bytes[0..32]); + s.copy_from_slice(&sig_bytes[32..64]); + + pallet_signet::Signature { + big_r: pallet_signet::AffinePoint { + x: r, + y: [0u8; 32], + }, + s, + recovery_id: recovery_id.to_i32() as u8, + } +} + +type Block = frame_system::mocking::MockBlock; + +// Mock runtime construction +frame_support::construct_runtime!( + pub enum Test { + System: frame_system, + Balances: pallet_balances, + Signet: pallet_signet, + BuildBitcoinTx: pallet_build_btc_tx, + BtcVault: pallet_btc_vault, + } +); + +// System config +parameter_types! { + pub const BlockHashCount: u64 = 250; +} + +impl system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; + type RuntimeTask = (); + type SingleBlockMigrations = (); + type MultiBlockMigrator = (); + type PreInherents = (); + type PostInherents = (); + type PostTransactions = (); +} + +// Balances config +parameter_types! { + pub const ExistentialDeposit: u128 = 1; +} + +impl pallet_balances::Config for Test { + type Balance = u128; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type RuntimeFreezeReason = (); +} + +// Signet config +parameter_types! { + pub const SignetPalletId: PalletId = PalletId(*b"py/signt"); + pub const MaxChainIdLength: u32 = 128; +} + +impl pallet_signet::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type PalletId = SignetPalletId; + type MaxChainIdLength = MaxChainIdLength; + type WeightInfo = (); +} + +// Build Bitcoin TX config +parameter_types! { + pub const MaxInputs: u32 = 10; + pub const MaxOutputs: u32 = 10; +} + +impl pallet_build_btc_tx::Config for Test { + type MaxInputs = MaxInputs; + type MaxOutputs = MaxOutputs; +} + +// BTC Vault config +parameter_types! { + pub const BtcVaultPalletId: PalletId = PalletId(*b"py/btcvt"); +} + +impl pallet_btc_vault::Config for Test { + type RuntimeEvent = RuntimeEvent; + type VaultPalletId = BtcVaultPalletId; +} + +// Helper to build test externalities +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = system::GenesisConfig::::default().build_storage().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + // Fund test accounts + let _ = >::deposit_creating(&1, 1_000_000); + let _ = >::deposit_creating(&2, 1_000_000); + let _ = >::deposit_creating(&3, 100); + // Initialize signet pallet + let _ = pallet_signet::Pallet::::initialize( + RuntimeOrigin::signed(1), + 1, + 100, + bounded_chain_id(b"test-chain".to_vec()), + ); + // Fund pallet account + let pallet_account = BtcVault::account_id(); + let _ = >::deposit_creating(&pallet_account, 10_000); + }); + ext +} + +fn create_test_mpc_address() -> [u8; 20] { + let public_key = get_test_public_key(); + public_key_to_btc_address(&public_key) +} + +// ======================================== +// INITIALIZATION TESTS +// ======================================== + +#[test] +fn test_initialize_works() { + new_test_ext().execute_with(|| { + let initializer = 2u64; + let mpc_address = create_test_mpc_address(); + + assert_eq!(BtcVault::vault_config(), None); + + assert_ok!(BtcVault::initialize(RuntimeOrigin::signed(initializer), mpc_address)); + + assert_eq!( + BtcVault::vault_config(), + Some(VaultConfigData { + mpc_root_signer_address: mpc_address + }) + ); + + System::assert_last_event( + Event::VaultInitialized { + mpc_address, + initialized_by: initializer, + } + .into(), + ); + }); +} + +#[test] +fn test_cannot_initialize_twice() { + new_test_ext().execute_with(|| { + let mpc_address = create_test_mpc_address(); + + assert_ok!(BtcVault::initialize(RuntimeOrigin::signed(1), mpc_address)); + + assert_noop!( + BtcVault::initialize(RuntimeOrigin::signed(2), [4u8; 20]), + Error::::AlreadyInitialized + ); + }); +} + +// ======================================== +// DEPOSIT TESTS +// ======================================== + +#[test] +fn test_deposit_btc_fails_without_initialization() { + new_test_ext().execute_with(|| { + let requester = 1u64; + let request_id = [1u8; 32]; + + let inputs = BoundedVec::try_from(vec![pallet_build_btc_tx::UtxoInput { + txid: [0x42; 32], + vout: 0, + value: 100_000_000, + script_pubkey: BoundedVec::try_from(vec![0x00, 0x14]).unwrap(), + sequence: 0xFFFFFFFF, + }]) + .unwrap(); + + let outputs = BoundedVec::try_from(vec![pallet_build_btc_tx::BitcoinOutput { + value: 99_900_000, + script_pubkey: BoundedVec::try_from(vec![0x00, 0x14]).unwrap(), + }]) + .unwrap(); + + assert_noop!( + BtcVault::deposit_btc(RuntimeOrigin::signed(requester), request_id, inputs, outputs, 0), + Error::::NotInitialized + ); + }); +} + +#[test] +fn test_deposit_btc_fails_without_vault_output() { + new_test_ext().execute_with(|| { + assert_ok!(BtcVault::initialize(RuntimeOrigin::signed(1), create_test_mpc_address())); + + let requester = 2u64; + let request_id = [1u8; 32]; + + let inputs = BoundedVec::try_from(vec![pallet_build_btc_tx::UtxoInput { + txid: [0x42; 32], + vout: 0, + value: 100_000_000, + script_pubkey: BoundedVec::try_from(vec![0x00, 0x14]).unwrap(), + sequence: 0xFFFFFFFF, + }]) + .unwrap(); + + // Output that doesn't go to vault + let outputs = BoundedVec::try_from(vec![pallet_build_btc_tx::BitcoinOutput { + value: 99_900_000, + script_pubkey: BoundedVec::try_from(vec![0x00, 0x14, 0x99, 0x99]).unwrap(), + }]) + .unwrap(); + + assert_noop!( + BtcVault::deposit_btc(RuntimeOrigin::signed(requester), request_id, inputs, outputs, 0), + Error::::NoVaultOutput + ); + }); +} + +// ======================================== +// CLAIM TESTS +// ======================================== + +#[test] +fn test_claim_nonexistent_deposit_fails() { + new_test_ext().execute_with(|| { + assert_ok!(BtcVault::initialize(RuntimeOrigin::signed(1), create_test_mpc_address())); + + let claimer = 2u64; + let request_id = [99u8; 32]; + + assert_noop!( + BtcVault::claim_btc( + RuntimeOrigin::signed(claimer), + request_id, + bounded_u8::<65536>(vec![1u8]), + pallet_signet::Signature { + big_r: pallet_signet::AffinePoint { + x: [1u8; 32], + y: [2u8; 32], + }, + s: [3u8; 32], + recovery_id: 0, + }, + ), + Error::::DepositNotFound + ); + }); +} + +#[test] +fn test_claim_with_error_response_fails() { + new_test_ext().execute_with(|| { + let mpc_address = create_test_mpc_address(); + assert_ok!(BtcVault::initialize(RuntimeOrigin::signed(1), mpc_address)); + + // Manually insert a pending deposit for testing + let requester = 2u64; + let request_id = [1u8; 32]; + let amount = 50_000_000u64; + + PendingDeposits::::insert( + &request_id, + PendingDepositData { + requester, + amount, + path: bounded_u8::<256>(b"test".to_vec()), + }, + ); + + // Error response with magic prefix + let error_output = vec![0xDE, 0xAD, 0xBE, 0xEF, 1, 2, 3]; + + let message_hash = { + let mut data = Vec::with_capacity(32 + error_output.len()); + data.extend_from_slice(&request_id); + data.extend_from_slice(&error_output); + keccak_256(&data) + }; + + let valid_signature = create_valid_signature(&message_hash); + + assert_noop!( + BtcVault::claim_btc( + RuntimeOrigin::signed(requester), + request_id, + bounded_u8::<65536>(error_output), + valid_signature, + ), + Error::::TransferFailed + ); + + assert_eq!(BtcVault::user_balances(requester), 0); + }); +} + +#[test] +fn test_claim_successful_with_valid_signature() { + new_test_ext().execute_with(|| { + let mpc_address = create_test_mpc_address(); + assert_ok!(BtcVault::initialize(RuntimeOrigin::signed(1), mpc_address)); + + let requester = 2u64; + let request_id = [1u8; 32]; + let amount = 50_000_000u64; + + PendingDeposits::::insert( + &request_id, + PendingDepositData { + requester, + amount, + path: bounded_u8::<256>(b"test".to_vec()), + }, + ); + + // Success response: Borsh-encoded true (1u8) + let success_output = vec![1u8]; + + let message_hash = { + let mut data = Vec::with_capacity(32 + success_output.len()); + data.extend_from_slice(&request_id); + data.extend_from_slice(&success_output); + keccak_256(&data) + }; + + let valid_signature = create_valid_signature(&message_hash); + + assert_eq!(BtcVault::user_balances(requester), 0); + + assert_ok!(BtcVault::claim_btc( + RuntimeOrigin::signed(requester), + request_id, + bounded_u8::<65536>(success_output), + valid_signature, + )); + + assert_eq!(BtcVault::user_balances(requester), amount); + assert!(BtcVault::pending_deposits(&request_id).is_none()); + + System::assert_has_event( + Event::DepositClaimed { + request_id, + claimer: requester, + amount, + } + .into(), + ); + }); +} \ No newline at end of file diff --git a/pallets/build-btc-tx/Cargo.toml b/pallets/build-btc-tx/Cargo.toml new file mode 100644 index 0000000000..1c42527f3c --- /dev/null +++ b/pallets/build-btc-tx/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "pallet-build-btc-tx" +version = "1.0.0" +description = "Pallet for building PSBT-encoded Bitcoin transactions" +authors = ["GalacticCouncil"] +edition = "2021" +homepage = "https://github.com/galacticcouncil/hydration-node" +license = "Apache 2.0" +repository = "https://github.com/galacticcouncil/hydration-node" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true } +scale-info = { workspace = true } +sp-std = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +signet-rs = { git = "https://github.com/esaminu/signet.rs", branch = "borsh-1.5.7", default-features = false, features = ["bitcoin"] } +frame-benchmarking = { workspace = true, optional = true } + +[dev-dependencies] +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-core = { workspace = true } + +[features] +default = ["std"] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-system/runtime-benchmarks", + "frame-support/runtime-benchmarks", +] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "sp-std/std", + "scale-info/std", + "signet-rs/std", +] +try-runtime = ["frame-support/try-runtime"] \ No newline at end of file diff --git a/pallets/build-btc-tx/src/lib.rs b/pallets/build-btc-tx/src/lib.rs new file mode 100644 index 0000000000..64a33823f3 --- /dev/null +++ b/pallets/build-btc-tx/src/lib.rs @@ -0,0 +1,172 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::pallet_prelude::*; +use frame_system::{ensure_signed, pallet_prelude::*}; +use sp_std::vec::Vec; + +pub use pallet::*; + +#[cfg(test)] +mod tests; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_system::pallet_prelude::BlockNumberFor; // ← Add this + + #[pallet::config] + pub trait Config: frame_system::Config { + #[pallet::constant] + type MaxInputs: Get; + #[pallet::constant] + type MaxOutputs: Get; + } + + #[pallet::pallet] + pub struct Pallet(_); + + /// Bitcoin script max size (standard Bitcoin limit) + pub type MaxScriptLength = ConstU32<520>; + + /// Bitcoin UTXO input with metadata required for PSBT construction + #[derive(Encode, Decode, TypeInfo, Clone, PartialEq, Eq, RuntimeDebug)] + pub struct UtxoInput { + pub txid: [u8; 32], + pub vout: u32, + pub value: u64, + pub script_pubkey: BoundedVec, + pub sequence: u32, + } + + /// Bitcoin transaction output + #[derive(Encode, Decode, TypeInfo, Clone, PartialEq, Eq, RuntimeDebug)] + pub struct BitcoinOutput { + pub value: u64, + pub script_pubkey: BoundedVec, + } + + #[pallet::error] + pub enum Error { + NoInputs, + NoOutputs, + InvalidLockTime, + PsbtCreationFailed, + PsbtSerializationFailed, + } + + #[pallet::hooks] + impl Hooks> for Pallet {} + + impl Pallet { + /// Build a Bitcoin PSBT and return the serialized bytes + /// + /// # Parameters + /// - `origin`: The signed origin + /// - `inputs`: UTXOs to spend + /// - `outputs`: Transaction outputs + /// - `lock_time`: Transaction locktime + /// + /// # Returns + /// Serialized PSBT bytes + pub fn build_bitcoin_tx( + origin: OriginFor, + inputs: BoundedVec, + outputs: BoundedVec, + lock_time: u32, + ) -> Result, DispatchError> { + ensure_signed(origin)?; + + ensure!(!inputs.is_empty(), Error::::NoInputs); + ensure!(!outputs.is_empty(), Error::::NoOutputs); + + let (psbt_bytes, _) = Self::build_psbt(&inputs, &outputs, lock_time)?; + Ok(psbt_bytes) + } + + /// Get the transaction ID (txid) for a Bitcoin transaction + /// + /// # Parameters + /// - `origin`: The signed origin + /// - `inputs`: UTXOs to spend + /// - `outputs`: Transaction outputs + /// - `lock_time`: Transaction locktime + /// + /// # Returns + /// 32-byte transaction ID (canonical, excludes witness) + pub fn get_txid( + origin: OriginFor, + inputs: BoundedVec, + outputs: BoundedVec, + lock_time: u32, + ) -> Result<[u8; 32], DispatchError> { + ensure_signed(origin)?; + + ensure!(!inputs.is_empty(), Error::::NoInputs); + ensure!(!outputs.is_empty(), Error::::NoOutputs); + + let (_, txid) = Self::build_psbt(&inputs, &outputs, lock_time)?; + Ok(txid) + } + + fn build_psbt( + inputs: &[UtxoInput], + outputs: &[BitcoinOutput], + lock_time: u32, + ) -> Result<(Vec, [u8; 32]), DispatchError> { + use signet_rs::bitcoin::types::{ + Amount, Hash, LockTime, OutPoint, ScriptBuf, Sequence, TxIn, TxOut, Txid, Version, Witness, + }; + use signet_rs::{TransactionBuilder, TxBuilder, BITCOIN}; + + let tx_inputs: Vec = inputs + .iter() + .map(|input| TxIn { + previous_output: OutPoint::new(Txid(Hash(input.txid)), input.vout), + script_sig: ScriptBuf::default(), + sequence: Sequence(input.sequence), + witness: Witness::default(), + }) + .collect(); + + let tx_outputs: Vec = outputs + .iter() + .map(|output| TxOut { + value: Amount::from_sat(output.value), + script_pubkey: ScriptBuf(output.script_pubkey.to_vec()), + }) + .collect(); + + let lock_time_parsed = if lock_time < 500_000_000 { + LockTime::from_height(lock_time).map_err(|_| Error::::InvalidLockTime)? + } else { + LockTime::from_time(lock_time).map_err(|_| Error::::InvalidLockTime)? + }; + + let tx = TransactionBuilder::new::() + .version(Version::Two) + .inputs(tx_inputs) + .outputs(tx_outputs) + .lock_time(lock_time_parsed) + .build(); + + // Extract txid before creating PSBT + let txid = tx.compute_txid(); + let mut txid_bytes = txid.as_byte_array(); + txid_bytes.reverse(); + + let mut psbt = tx.to_psbt(); + + for (i, input) in inputs.iter().enumerate() { + psbt.update_input_with_witness_utxo(i, input.script_pubkey.to_vec(), input.value) + .map_err(|_| Error::::PsbtCreationFailed)?; + + psbt.update_input_with_sighash_type(i, 1) + .map_err(|_| Error::::PsbtCreationFailed)?; + } + + let psbt_bytes = psbt.serialize().map_err(|_| Error::::PsbtSerializationFailed)?; + + Ok((psbt_bytes, txid_bytes)) + } + } +} diff --git a/pallets/build-btc-tx/src/tests.rs b/pallets/build-btc-tx/src/tests.rs new file mode 100644 index 0000000000..2a52c29544 --- /dev/null +++ b/pallets/build-btc-tx/src/tests.rs @@ -0,0 +1,202 @@ +use crate::{self as pallet_build_btc_tx, *}; +use frame_support::{assert_noop, assert_ok, parameter_types, traits::{ConstU16, ConstU32, ConstU64}, BoundedVec}; +use sp_core::H256; +use sp_runtime::{traits::{BlakeTwo256, IdentityLookup}, BuildStorage}; + +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test { + System: frame_system, + BuildBitcoinTx: pallet_build_btc_tx, + } +); + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = ConstU16<42>; + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; + type RuntimeTask = (); + type SingleBlockMigrations = (); + type MultiBlockMigrator = (); + type PreInherents = (); + type PostInherents = (); + type PostTransactions = (); +} + +parameter_types! { + pub const MaxInputs: u32 = 10; + pub const MaxOutputs: u32 = 10; +} + +impl pallet_build_btc_tx::Config for Test { + type MaxInputs = MaxInputs; + type MaxOutputs = MaxOutputs; +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + }); + ext +} + +#[test] +fn test_build_simple_transaction() { + new_test_ext().execute_with(|| { + let inputs = BoundedVec::try_from(vec![UtxoInput { + txid: [0x42; 32], + vout: 0, + value: 100_000_000, + script_pubkey: BoundedVec::try_from(vec![0x00, 0x14]).unwrap(), + sequence: 0xFFFFFFFF, + }]) + .unwrap(); + + let outputs = BoundedVec::try_from(vec![BitcoinOutput { + value: 99_900_000, + script_pubkey: BoundedVec::try_from(vec![0x00, 0x14]).unwrap(), + }]) + .unwrap(); + + let result = BuildBitcoinTx::build_bitcoin_tx( + RuntimeOrigin::signed(1), + inputs, + outputs, + 0 + ); + + assert_ok!(&result); + let psbt = result.unwrap(); + assert!(!psbt.is_empty(), "PSBT should not be empty"); + }); +} + +#[test] +fn test_get_txid() { + new_test_ext().execute_with(|| { + let inputs = BoundedVec::try_from(vec![UtxoInput { + txid: [0x42; 32], + vout: 0, + value: 100_000_000, + script_pubkey: BoundedVec::try_from(vec![0x00, 0x14]).unwrap(), + sequence: 0xFFFFFFFF, + }]) + .unwrap(); + + let outputs = BoundedVec::try_from(vec![BitcoinOutput { + value: 99_900_000, + script_pubkey: BoundedVec::try_from(vec![0x00, 0x14]).unwrap(), + }]) + .unwrap(); + + let result = BuildBitcoinTx::get_txid( + RuntimeOrigin::signed(1), + inputs.clone(), + outputs.clone(), + 0 + ); + + assert_ok!(&result); + let txid = result.unwrap(); + assert_eq!(txid.len(), 32, "Txid should be 32 bytes"); + + // Verify txid is deterministic + let result2 = BuildBitcoinTx::get_txid( + RuntimeOrigin::signed(1), + inputs, + outputs, + 0 + ); + assert_eq!(txid, result2.unwrap(), "Same inputs should produce same txid"); + }); +} + +#[test] +fn test_no_inputs_fails() { + new_test_ext().execute_with(|| { + let outputs = BoundedVec::try_from(vec![BitcoinOutput { + value: 100_000_000, + script_pubkey: BoundedVec::try_from(vec![0x00, 0x14]).unwrap(), + }]) + .unwrap(); + + assert_noop!( + BuildBitcoinTx::build_bitcoin_tx( + RuntimeOrigin::signed(1), + BoundedVec::default(), + outputs.clone(), + 0 + ), + Error::::NoInputs + ); + + assert_noop!( + BuildBitcoinTx::get_txid( + RuntimeOrigin::signed(1), + BoundedVec::default(), + outputs, + 0 + ), + Error::::NoInputs + ); + }); +} + +#[test] +fn test_no_outputs_fails() { + new_test_ext().execute_with(|| { + let inputs = BoundedVec::try_from(vec![UtxoInput { + txid: [0x42; 32], + vout: 0, + value: 100_000_000, + script_pubkey: BoundedVec::try_from(vec![0x00, 0x14]).unwrap(), + sequence: 0xFFFFFFFF, + }]) + .unwrap(); + + assert_noop!( + BuildBitcoinTx::build_bitcoin_tx( + RuntimeOrigin::signed(1), + inputs.clone(), + BoundedVec::default(), + 0 + ), + Error::::NoOutputs + ); + + assert_noop!( + BuildBitcoinTx::get_txid( + RuntimeOrigin::signed(1), + inputs, + BoundedVec::default(), + 0 + ), + Error::::NoOutputs + ); + }); +} \ No newline at end of file diff --git a/pallets/build-evm-tx/Cargo.toml b/pallets/build-evm-tx/Cargo.toml index aeae60fd55..fa8054b3c3 100644 --- a/pallets/build-evm-tx/Cargo.toml +++ b/pallets/build-evm-tx/Cargo.toml @@ -22,7 +22,7 @@ frame-support = { workspace = true } frame-system = { workspace = true } ethereum = { workspace = true } -rlp = { version = "0.5.2", default-features = false } +signet-rs = { git = "https://github.com/esaminu/signet.rs", branch = "borsh-1.5.7", default-features = false, features = ["evm"] } # Optionals frame-benchmarking = { workspace = true, optional = true } @@ -48,6 +48,5 @@ std = [ "sp-core/std", "scale-info/std", "ethereum/std", - "rlp/std", ] try-runtime = ["frame-support/try-runtime"] \ No newline at end of file diff --git a/pallets/build-evm-tx/src/lib.rs b/pallets/build-evm-tx/src/lib.rs index 03a944e5f5..dee3a93c0b 100644 --- a/pallets/build-evm-tx/src/lib.rs +++ b/pallets/build-evm-tx/src/lib.rs @@ -21,13 +21,12 @@ use frame_support::pallet_prelude::*; use frame_system::{ensure_signed, pallet_prelude::OriginFor}; use sp_std::vec::Vec; -use ethereum::{AccessListItem, EIP1559TransactionMessage, TransactionAction}; -use sp_core::{H160, U256}; +use ethereum::AccessListItem; +use signet_rs::{TransactionBuilder, TxBuilder, EVM}; +use sp_core::H160; pub use pallet::*; -const EIP1559_TX_TYPE: u8 = 0x02; - #[cfg(test)] mod tests; @@ -84,32 +83,32 @@ pub mod pallet { chain_id: u64, ) -> Result, DispatchError> { ensure_signed(origin)?; - ensure!(data.len() <= T::MaxDataLength::get() as usize, Error::::DataTooLong); ensure!(max_priority_fee_per_gas <= max_fee_per_gas, Error::::InvalidGasPrice); - let action = match to_address { - Some(addr) => TransactionAction::Call(addr), - None => TransactionAction::Create, - }; - - let tx_message = EIP1559TransactionMessage { - chain_id, - nonce: U256::from(nonce), - max_priority_fee_per_gas: U256::from(max_priority_fee_per_gas), - max_fee_per_gas: U256::from(max_fee_per_gas), - gas_limit: U256::from(gas_limit), - action, - value: U256::from(value), - input: data, - access_list, - }; - - let mut output = Vec::new(); - output.push(EIP1559_TX_TYPE); - output.extend_from_slice(&rlp::encode(&tx_message)); - - Ok(output) + let to_address_bytes = to_address.map(|h| h.0); + let access_list_converted = convert_access_list(access_list); + + let tx = TransactionBuilder::new::() + .chain_id(chain_id) + .nonce(nonce) + .max_priority_fee_per_gas(max_priority_fee_per_gas) + .max_fee_per_gas(max_fee_per_gas) + .gas_limit(gas_limit as u128) + .value(value) + .input(data) + .access_list(access_list_converted); + + let tx = if let Some(to) = to_address_bytes { tx.to(to) } else { tx }; + + Ok(tx.build().build_for_signing()) } } + + fn convert_access_list(items: Vec) -> Vec<([u8; 20], Vec<[u8; 32]>)> { + items + .into_iter() + .map(|item| (item.address.0, item.storage_keys.into_iter().map(|k| k.0).collect())) + .collect() + } } diff --git a/runtime/hydradx/Cargo.toml b/runtime/hydradx/Cargo.toml index 23035021c3..d8002a2ed3 100644 --- a/runtime/hydradx/Cargo.toml +++ b/runtime/hydradx/Cargo.toml @@ -64,6 +64,7 @@ pallet-staking = { workspace = true } pallet-liquidation = { workspace = true } pallet-hsm = { workspace = true } pallet-build-evm-tx = { workspace = true } +pallet-build-btc-tx = { workspace = true } pallet-parameters = { workspace = true } # pallets @@ -94,6 +95,7 @@ pallet-message-queue = { workspace = true } pallet-state-trie-migration = { workspace = true } pallet-signet = { workspace = true } pallet-erc20-vault = { workspace = true } +pallet-btc-vault = { workspace = true } # ORML dependencies orml-tokens = { workspace = true } @@ -245,6 +247,7 @@ runtime-benchmarks = [ "pallet-xyk/runtime-benchmarks", "pallet-hsm/runtime-benchmarks", "pallet-build-evm-tx/runtime-benchmarks", + "pallet-build-btc-tx/runtime-benchmarks", "pallet-elections-phragmen/runtime-benchmarks", "pallet-referrals/runtime-benchmarks", "pallet-evm-accounts/runtime-benchmarks", @@ -262,6 +265,7 @@ runtime-benchmarks = [ "pallet-token-gateway/runtime-benchmarks", "pallet-signet/runtime-benchmarks", "pallet-erc20-vault/runtime-benchmarks", + "pallet-btc-vault/runtime-benchmarks", ] std = [ "codec/std", @@ -343,6 +347,7 @@ std = [ "pallet-duster-rpc-runtime-api/std", "pallet-hsm/std", "pallet-build-evm-tx/std", + "pallet-build-btc-tx/std", "pallet-parameters/std", "warehouse-liquidity-mining/std", "sp-api/std", @@ -399,6 +404,7 @@ std = [ "ismp-parachain-runtime-api/std", "pallet-signet/std", "pallet-erc20-vault/std", + "pallet-btc-vault/std", ] try-runtime = [ "frame-try-runtime", @@ -467,6 +473,7 @@ try-runtime = [ "pallet-xyk/try-runtime", "pallet-hsm/try-runtime", "pallet-build-evm-tx/try-runtime", + "pallet-build-btc-tx/try-runtime", "pallet-referrals/try-runtime", "pallet-evm-accounts/try-runtime", "pallet-xyk-liquidity-mining/try-runtime", @@ -480,6 +487,7 @@ try-runtime = [ "pallet-dispatcher/try-runtime", "pallet-signet/try-runtime", "pallet-erc20-vault/try-runtime", + "pallet-btc-vault/try-runtime", ] metadata-hash = [ diff --git a/runtime/hydradx/src/assets.rs b/runtime/hydradx/src/assets.rs index c4aacf8975..dc9be06eaf 100644 --- a/runtime/hydradx/src/assets.rs +++ b/runtime/hydradx/src/assets.rs @@ -1840,6 +1840,16 @@ impl pallet_build_evm_tx::Config for Runtime { type MaxDataLength = MaxEvmDataLength; } +parameter_types! { + pub const MaxBTCInputs: u32 = 100; + pub const MaxBTCOutputs: u32 = 100; +} + +impl pallet_build_btc_tx::Config for Runtime { + type MaxInputs = MaxBTCInputs; + type MaxOutputs = MaxBTCOutputs; +} + parameter_types! { pub const SignetPalletId: PalletId = PalletId(*b"py/signt"); pub const MaxChainIdLength: u32 = 128; @@ -1862,6 +1872,15 @@ impl pallet_erc20_vault::Config for Runtime { type VaultPalletId = Erc20VaultPalletId; } +parameter_types! { + pub const BTCVaultPalletId: PalletId = PalletId(*b"py/btcvt"); +} + +impl pallet_btc_vault::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type VaultPalletId = BTCVaultPalletId; +} + pub struct ConvertViaOmnipool(PhantomData); impl Convert for ConvertViaOmnipool where diff --git a/runtime/hydradx/src/lib.rs b/runtime/hydradx/src/lib.rs index c474496517..b133c06d53 100644 --- a/runtime/hydradx/src/lib.rs +++ b/runtime/hydradx/src/lib.rs @@ -193,8 +193,10 @@ construct_runtime!( HSM: pallet_hsm = 82, Parameters: pallet_parameters = 83, BuildEvmTx: pallet_build_evm_tx = 86, + BuildBTCTx: pallet_build_btc_tx = 87, Signet: pallet_signet = 84, Erc20Vault: pallet_erc20_vault = 85, + BTCVault: pallet_btc_vault = 88, // ORML related modules Tokens: orml_tokens = 77, @@ -345,11 +347,13 @@ mod benches { [pallet_dispatcher, Dispatcher] [pallet_hsm, HSM] [pallet_build_evm_tx, BuildEvmTx] + [pallet_build_btc_tx, BuildBTCTx] [pallet_dynamic_fees, DynamicFees] [ismp_parachain, IsmpParachain] [pallet_token_gateway, TokenGateway] [pallet_signet, Signet] [pallet_erc20_vault, Erc20Vault] + [pallet_btc_vault, BTCVault] ); } diff --git a/signet-ts-client-scripts/btc-vault.test.ts b/signet-ts-client-scripts/btc-vault.test.ts new file mode 100644 index 0000000000..3ff7bf0a25 --- /dev/null +++ b/signet-ts-client-scripts/btc-vault.test.ts @@ -0,0 +1,917 @@ +import { ApiPromise, WsProvider, Keyring } from "@polkadot/api"; +import { ISubmittableResult } from "@polkadot/types/types"; +import { waitReady } from "@polkadot/wasm-crypto"; +import { u8aToHex } from "@polkadot/util"; +import { encodeAddress } from "@polkadot/keyring"; +import { ethers } from "ethers"; +import * as bitcoin from "bitcoinjs-lib"; +import Client from "bitcoin-core"; +import { SignetClient } from "./signet-client"; +import { KeyDerivation } from "./key-derivation"; +import * as ecc from "tiny-secp256k1"; +import coinSelect from "coinselect"; + +const ROOT_PUBLIC_KEY = + "0x044eef776e4f257d68983e45b340c2e9546c5df95447900b6aadfec68fb46fdee257e26b8ba383ddba9914b33c60e869265f859566fff4baef283c54d821ca3b64"; +const CHAIN_ID = "bip122:000000000933ea01ad0ee984209779ba"; + +function normalizeSignature(r: Buffer, s: Buffer): { r: Buffer; s: Buffer } { + const N = BigInt( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141" + ); + const halfN = N / 2n; + const sBigInt = BigInt("0x" + s.toString("hex")); + + if (sBigInt > halfN) { + const normalizedS = N - sBigInt; + const sBuffer = Buffer.from( + normalizedS.toString(16).padStart(64, "0"), + "hex" + ); + return { r, s: sBuffer }; + } + + return { r, s }; +} + +const TESTNET_VAULT_ADDRESS_HASH = new Uint8Array([ + 0x89, 0xf0, 0xa8, 0x23, 0x93, 0x8c, 0x58, 0xcf, 0x5b, 0x17, 0xc8, 0xeb, 0x93, + 0xc6, 0x82, 0x80, 0x63, 0x5b, 0x73, 0x4e, +]); + +function getPalletAccountId(): Uint8Array { + const palletId = new TextEncoder().encode("py/btcvt"); + const modl = new TextEncoder().encode("modl"); + const data = new Uint8Array(32); + data.set(modl, 0); + data.set(palletId, 4); + return data; +} + +async function submitWithRetry( + tx: any, + signer: any, + api: ApiPromise, + label: string, + maxRetries: number = 1, + timeoutMs: number = 60000 +): Promise<{ events: any[] }> { + let attempt = 0; + + while (attempt <= maxRetries) { + try { + console.log(`${label} - Attempt ${attempt + 1}/${maxRetries + 1}`); + + const result = await new Promise<{ events: any[] }>((resolve, reject) => { + let unsubscribe: any; + + const timer = setTimeout(() => { + if (unsubscribe) unsubscribe(); + console.log(`ā±ļø ${label} timed out after ${timeoutMs}ms`); + reject(new Error("TIMEOUT")); + }, timeoutMs); + + tx.signAndSend(signer, (result: ISubmittableResult) => { + const { status, events, dispatchError } = result; + + if (status.isInBlock) { + clearTimeout(timer); + if (unsubscribe) unsubscribe(); + + console.log( + `āœ… ${label} included in block ${status.asInBlock.toHex()}` + ); + + console.log(`šŸ“‹ All events (${events.length}):`); + for (const record of events) { + const { event } = record; + console.log(` ${event.section}.${event.method}`); + + if ( + event.section === "btcVault" && + event.method === "DebugTxid" + ) { + const palletTxid = event.data[0].toU8a(); + console.log( + `šŸ” PALLET TXID: ${Buffer.from(palletTxid).toString("hex")}` + ); + } + } + + if (dispatchError) { + if (dispatchError.isModule) { + const decoded = api.registry.findMetaError( + dispatchError.asModule + ); + reject( + new Error( + `${decoded.section}.${decoded.name}: ${decoded.docs.join( + " " + )}` + ) + ); + } else { + reject(new Error(dispatchError.toString())); + } + return; + } + + resolve({ events: Array.from(events) }); + } else if (status.isInvalid) { + clearTimeout(timer); + if (unsubscribe) unsubscribe(); + reject(new Error("INVALID_TX")); + } else if (status.isDropped) { + clearTimeout(timer); + if (unsubscribe) unsubscribe(); + reject(new Error(`${label} dropped`)); + } + }) + .then((unsub: any) => { + unsubscribe = unsub; + }) + .catch((error: any) => { + clearTimeout(timer); + reject(error); + }); + }); + + return result; + } catch (error: any) { + if ( + (error.message === "INVALID_TX" || error.message === "TIMEOUT") && + attempt < maxRetries + ) { + console.log(`šŸ”„ Retrying ${label}...`); + attempt++; + await new Promise((resolve) => setTimeout(resolve, 2000)); + continue; + } + throw error; + } + } + + throw new Error(`${label} failed after ${maxRetries + 1} attempts`); +} + +describe("BTC Vault Integration", () => { + let api: ApiPromise; + let alice: any; + let signetClient: SignetClient; + let btcClient: any; + let derivedBtcAddress: string; + let derivedPubKey: string; + let network: bitcoin.Network; + + beforeAll(async () => { + await waitReady(); + + console.log("šŸ”— Connecting to Bitcoin regtest..."); + btcClient = new Client({ + host: "http://localhost:18443", + username: "test", + password: "test123", + }); + + try { + const blockCount = await btcClient.command("getblockcount"); + console.log( + `āœ… Connected to Bitcoin regtest (block height: ${blockCount})\n` + ); + } catch (error) { + throw new Error( + "āŒ Cannot connect to Bitcoin regtest. Make sure it's running:\n" + + " cd bitcoin-regtest && yarn docker:dev" + ); + } + + api = await ApiPromise.create({ + provider: new WsProvider("ws://127.0.0.1:8000"), + types: { + AffinePoint: { x: "[u8; 32]", y: "[u8; 32]" }, + Signature: { big_r: "AffinePoint", s: "[u8; 32]", recovery_id: "u8" }, + }, + }); + + const keyring = new Keyring({ type: "sr25519" }); + alice = keyring.addFromUri("//Alice"); + const bob = keyring.addFromUri("//Bob"); + + const { data: bobBalance } = (await api.query.system.account( + bob.address + )) as any; + + if (bobBalance.free.toBigInt() < 1000000000000n) { + console.log("Funding Bob's account for server responses..."); + const bobFundTx = api.tx.balances.transferKeepAlive( + bob.address, + 100000000000000n + ); + await submitWithRetry(bobFundTx, alice, api, "Fund Bob account"); + } + + const palletAccountId = getPalletAccountId(); + const palletSS58 = encodeAddress(palletAccountId, 0); + + const { data: palletBalance } = (await api.query.system.account( + palletSS58 + )) as any; + + const fundingAmount = 10000000000000n; + + if (palletBalance.free.toBigInt() < fundingAmount) { + console.log(`Funding BTC vault pallet account ${palletSS58}...`); + const fundTx = api.tx.balances.transferKeepAlive( + palletSS58, + fundingAmount + ); + await submitWithRetry(fundTx, alice, api, "Fund pallet account"); + } + + signetClient = new SignetClient(api, alice); + await signetClient.ensureInitialized(CHAIN_ID); + + const aliceAccountId = keyring.decodeAddress(alice.address); + const aliceHexPath = "0x" + u8aToHex(aliceAccountId).slice(2); + + derivedPubKey = KeyDerivation.derivePublicKey( + ROOT_PUBLIC_KEY, + palletSS58, + aliceHexPath, + "polkadot:2034" + ); + + const compressedForComparison = compressPubkey(derivedPubKey); + console.log( + `šŸ” Test expects compressed pubkey: ${compressedForComparison.toString( + "hex" + )}` + ); + + console.log(`šŸ” Test using path: ${aliceHexPath}`); + console.log(`šŸ” Test using sender: ${palletSS58}`); + console.log(`šŸ” Derived public key: ${derivedPubKey}`); + + network = bitcoin.networks.regtest; + derivedBtcAddress = btcAddressFromPubKey(derivedPubKey, network); + + console.log(`\nšŸ”‘ Derived Bitcoin Address: ${derivedBtcAddress}`); + + await fundBitcoinAddress(derivedBtcAddress); + }, 180000); + + afterAll(async () => { + if (api) { + await api.disconnect(); + } + }); + + async function fundBitcoinAddress(address: string) { + console.log(`šŸ’° Funding ${address} with 1 BTC...`); + + try { + let walletAddress; + try { + walletAddress = await btcClient.command("getnewaddress"); + } catch (e) { + try { + await btcClient.command("createwallet", "testwallet"); + walletAddress = await btcClient.command("getnewaddress"); + } catch (createErr: any) { + await btcClient.command("loadwallet", "testwallet"); + walletAddress = await btcClient.command("getnewaddress"); + } + } + + await btcClient.command("generatetoaddress", 101, walletAddress); + console.log(` Mined 101 blocks to wallet`); + + const txid = await btcClient.command("sendtoaddress", address, 1.0); + console.log(` Funding txid: ${txid}`); + + await btcClient.command("generatetoaddress", 1, walletAddress); + + const scanResult = await btcClient.command("scantxoutset", "start", [ + `addr(${derivedBtcAddress})`, + ]); + + console.log(`šŸ“¦ Found ${scanResult.unspents.length} UTXO(s)`); + + for (const utxo of scanResult.unspents) { + console.log(`\nšŸ” UTXO ${utxo.txid}:${utxo.vout}`); + console.log(` scriptPubKey: ${utxo.scriptPubKey}`); + console.log(` amount: ${utxo.amount} BTC`); + } + + if (scanResult.unspents.length === 0) { + throw new Error("No UTXOs found after funding"); + } + + console.log("āœ… Funding confirmed\n"); + } catch (error: any) { + console.error("Funding error:", error.message); + throw error; + } + } + + it("should complete full deposit and claim flow", async () => { + const mpcEthAddress = ethAddressFromPubKey(ROOT_PUBLIC_KEY); + console.log("Checking vault initialization..."); + + const existingConfig = await api.query.btcVault.vaultConfig(); + const configJson = existingConfig.toJSON(); + + if (configJson !== null) { + console.log("āš ļø Vault already initialized, skipping initialization"); + console.log(" Existing config:", existingConfig.toHuman()); + } else { + console.log("Initializing vault with MPC address hash"); + const initTx = api.tx.btcVault.initialize(Array.from(mpcEthAddress)); + await submitWithRetry(initTx, alice, api, "Initialize vault"); + } + + const scanResult = await btcClient.command("scantxoutset", "start", [ + `addr(${derivedBtcAddress})`, + ]); + + if (scanResult.unspents.length === 0) { + throw new Error("No UTXOs available for derived address"); + } + + console.log(`šŸ“¦ Found ${scanResult.unspents.length} UTXO(s)`); + + const depositAmount = 36879690; + const feeRate = 1; // satoshis per byte + + // Convert UTXOs to coinselect format + const utxos = scanResult.unspents.map((u: any) => ({ + txid: u.txid, + vout: u.vout, + value: Math.floor(u.amount * 100000000), + script: Buffer.from( + bitcoin.address.toOutputScript(derivedBtcAddress, network) + ), + })); + + // Vault output + const vaultScript = Buffer.concat([ + Buffer.from([0x00, 0x14]), + Buffer.from(TESTNET_VAULT_ADDRESS_HASH), + ]); + + const targets = [ + { + script: vaultScript, + value: depositAmount, + }, + ]; + + // Let coinselect pick optimal UTXOs and calculate fee + const { inputs, outputs, fee } = coinSelect(utxos, targets, feeRate); + + if (!inputs || !outputs) { + throw new Error("Insufficient funds for transaction"); + } + + console.log(`šŸ“Š Transaction breakdown:`); + console.log(` Inputs: ${inputs.length}`); + inputs.forEach((inp: any, i: number) => { + console.log( + ` Input ${i}: ${inp.value} sats (${inp.txid.slice(0, 8)}...)` + ); + }); + console.log(` To vault: ${outputs[0].value} sats`); + if (outputs.length > 1) { + console.log(` Change: ${outputs[1].value} sats`); + } + console.log(` Fee: ${fee} sats\n`); + + // Build PSBT with selected inputs/outputs + const psbt = new bitcoin.Psbt({ network }); + + for (const input of inputs) { + const txidBytes = Buffer.from(input.txid, "hex").reverse(); + psbt.addInput({ + hash: txidBytes, + index: input.vout, + sequence: 0xffffffff, + witnessUtxo: { + script: input.script!, + value: input.value, + }, + }); + } + + for (let i = 0; i < inputs.length; i++) { + psbt.updateInput(i, { + sighashType: bitcoin.Transaction.SIGHASH_ALL, + }); + } + + for (const output of outputs) { + if (output.script) { + psbt.addOutput({ + script: output.script, + value: output.value, + }); + } else { + // Change output + psbt.addOutput({ + address: derivedBtcAddress, + value: output.value, + }); + } + } + + const psbtBytes = psbt.toBuffer(); + console.log(`šŸ“ PSBT bytes length: ${psbtBytes.length}`); + + console.log(`šŸ” Test prevout txid (as sent to pallet): ${inputs[0].txid}`); + console.log( + `šŸ” Test prevout txid (reversed for PSBT): ${Buffer.from( + inputs[0].txid, + "hex" + ) + .reverse() + .toString("hex")}` + ); + + // Extract txid from PSBT + const unsignedTxBuffer = psbt.data.globalMap.unsignedTx.toBuffer(); + const unsignedTx = bitcoin.Transaction.fromBuffer(unsignedTxBuffer); + const txid = Buffer.from(unsignedTx.getId(), "hex"); + + const testUnsignedTx = bitcoin.Transaction.fromBuffer(unsignedTxBuffer); + for (let i = 0; i < inputs.length; i++) { + const testSighash = testUnsignedTx.hashForWitnessV0( + i, + inputs[i].script!, + inputs[i].value, + bitcoin.Transaction.SIGHASH_ALL + ); + console.log(`šŸ” Test Input ${i} sighash: ${testSighash.toString("hex")}`); + } + + console.log(`šŸ”‘ Transaction ID: ${txid.toString("hex")}`); + + console.log(`šŸ” Test tx version: ${unsignedTx.version}`); + console.log(`šŸ” Test tx locktime: ${unsignedTx.locktime}`); + + const keyring = new Keyring({ type: "sr25519" }); + const palletAccountId = getPalletAccountId(); + const palletSS58 = encodeAddress(palletAccountId, 0); + const aliceAccountId = keyring.decodeAddress(alice.address); + const aliceHexPath = "0x" + u8aToHex(aliceAccountId).slice(2); + + console.log(`šŸ” Pallet SS58 (sender): ${palletSS58}`); + console.log(`šŸ” Alice hex path: ${aliceHexPath}`); + + // Calculate aggregate request ID (for monitoring) + const aggregateRequestId = signetClient.calculateSignRespondRequestId( + palletSS58, + Array.from(txid), + { + caip2Id: CHAIN_ID, + keyVersion: 0, + path: aliceHexPath, + algo: "ecdsa", + dest: "bitcoin", + params: "", + } + ); + + console.log( + `šŸ“‹ Aggregate Request ID: ${ethers.hexlify(aggregateRequestId)}` + ); + + // Generate per-input request IDs + const txidForPerInputRequestId = Buffer.from(unsignedTx.getId(), "hex"); + + // Generate per-input request IDs + const perInputRequestIds: string[] = []; + for (let i = 0; i < inputs.length; i++) { + const inputIndexBytes = Buffer.alloc(4); + inputIndexBytes.writeUInt32LE(i, 0); + // Use the display order txid reversed (internal order) to match server + const txDataForInput = Buffer.concat([ + txidForPerInputRequestId, + inputIndexBytes, + ]); + + const perInputRequestId = signetClient.calculateSignRespondRequestId( + palletSS58, + Array.from(txDataForInput), + { + caip2Id: CHAIN_ID, + keyVersion: 0, + path: aliceHexPath, + algo: "ecdsa", + dest: "bitcoin", + params: "", + } + ); + + perInputRequestIds.push(ethers.hexlify(perInputRequestId)); + console.log(`šŸ“‹ Input ${i} Request ID: ${perInputRequestIds[i]}`); + } + console.log(""); // Empty line + + // Convert to pallet format + const palletInputs = inputs.map((input: any) => ({ + txid: Array.from(Buffer.from(input.txid, "hex")), + vout: input.vout, + value: input.value, + scriptPubkey: Array.from(input.script), + sequence: 0xffffffff, + })); + + const palletOutputs = outputs.map((output: any) => { + if (output.script) { + return { + value: output.value, + scriptPubkey: Array.from(output.script), + }; + } else { + return { + value: output.value, + scriptPubkey: Array.from( + bitcoin.address.toOutputScript(derivedBtcAddress, network) + ), + }; + } + }); + + const depositTx = api.tx.btcVault.depositBtc( + Array.from(ethers.getBytes(aggregateRequestId)), + palletInputs, + palletOutputs, + 0 + ); + + console.log("šŸš€ Submitting deposit_btc transaction..."); + const depositResult = await submitWithRetry( + depositTx, + alice, + api, + "Deposit BTC" + ); + + const debugEvent = depositResult.events.find( + (record: any) => + record.event.section === "btcVault" && + record.event.method === "DebugTxid" + ); + + if (debugEvent) { + const palletTxid = debugEvent.event.data[0].toU8a(); + console.log( + `šŸ” Pallet computed txid: ${Buffer.from(palletTxid).toString("hex")}` + ); + console.log( + `šŸ” Test computed txid: ${Buffer.from(txid).toString("hex")}` + ); + console.log( + `šŸ” Match: ${ + Buffer.from(palletTxid).toString("hex") === + Buffer.from(txid).toString("hex") + }` + ); + } else { + console.log("āš ļø DebugTxid event not found"); + } + + const debugTxEvent = depositResult.events.find( + (record: any) => + record.event.section === "btcVault" && + record.event.method === "DebugTransaction" + ); + + if (debugTxEvent) { + const palletTxHex = debugTxEvent.event.data[0].toHex(); + const palletVersion = debugTxEvent.event.data[1].toNumber(); + const palletLocktime = debugTxEvent.event.data[2].toNumber(); + + console.log(`šŸ” Pallet PSBT hex: ${palletTxHex}`); + console.log( + `šŸ” Pallet version: ${palletVersion}, locktime: ${palletLocktime}` + ); + } + + const signetEvents = depositResult.events.filter( + (record: any) => + record.event.section === "signet" && + record.event.method === "SignBidirectionalRequested" + ); + + console.log( + `šŸ“Š Found ${signetEvents.length} SignBidirectionalRequested event(s)` + ); + + if (signetEvents.length > 0) { + console.log( + "āœ… SignBidirectionalRequested event emitted - MPC should pick it up!" + ); + } + + console.log("ā³ Waiting for MPC signature(s)..."); + + // Wait for each input's signature separately + const signatures: any[] = []; + for (let i = 0; i < perInputRequestIds.length; i++) { + console.log( + ` Waiting for signature ${i + 1}/${perInputRequestIds.length}...` + ); + const sig = await waitForSingleSignature( + api, + perInputRequestIds[i], + 120000 + ); + signatures.push(sig); + console.log(` āœ… Received signature ${i + 1}`); + } + + console.log( + `\nāœ… Received all ${signatures.length} signature(s) from MPC\n` + ); + + const compressedPubkey = compressPubkey(derivedPubKey); + + for (let i = 0; i < signatures.length; i++) { + const sig = signatures[i]; + + const rBuf = + typeof sig.bigR.x === "string" + ? Buffer.from(sig.bigR.x.slice(2), "hex") + : Buffer.from(sig.bigR.x); + + const sBuf = + typeof sig.s === "string" + ? Buffer.from(sig.s.slice(2), "hex") + : Buffer.from(sig.s); + + const { r: normalizedR, s: normalizedS } = normalizeSignature(rBuf, sBuf); + + console.log(`šŸ” Signature ${i} verification:`); + console.log(` R: ${normalizedR.toString("hex")}`); + console.log(` S: ${normalizedS.toString("hex")}`); + console.log(` Recovery ID: ${sig.recoveryId}`); + + // VERIFY THE SIGNATURE + const testSighash = testUnsignedTx.hashForWitnessV0( + i, + inputs[i].script!, + inputs[i].value, + bitcoin.Transaction.SIGHASH_ALL + ); + + // Verify signature is valid + const rawSig = Buffer.concat([normalizedR, normalizedS]); + const isValid = ecc.verify(testSighash, compressedPubkey, rawSig); + console.log(` Signature valid (ecc.verify): ${isValid}`); + + const derSig = encodeDER(normalizedR, normalizedS); + const fullSig = Buffer.concat([derSig, Buffer.from([0x01])]); + + psbt.updateInput(i, { + partialSig: [ + { + pubkey: compressedPubkey, + signature: fullSig, + }, + ], + }); + } + + console.log(`šŸ” Input 0 witnessUtxo:`, psbt.data.inputs[0].witnessUtxo); + psbt.finalizeAllInputs(); + const signedTx = psbt.extractTransaction(); + console.log(`šŸ” Witness for input 0:`); + const witness = signedTx.ins[0].witness; + witness.forEach((w, idx) => { + console.log(` Witness ${idx}: ${w.toString("hex")}`); + }); + const signedTxHex = signedTx.toHex(); + + console.log(`\nšŸ” Full signed transaction hex (first 200 chars):`); + console.log(` ${signedTxHex.substring(0, 200)}...`); + console.log( + `šŸ” Transaction input 0 sequence: 0x${signedTx.ins[0].sequence.toString( + 16 + )}` + ); + + console.log(`āœ… Transaction finalized: ${signedTx.getId()}\n`); + + console.log("šŸ“” Broadcasting transaction to regtest..."); + const broadcastTxid = await btcClient.command( + "sendrawtransaction", + signedTxHex + ); + console.log(` Tx Hash: ${broadcastTxid}`); + + console.log("ā›ļø Mining block to confirm transaction..."); + await btcClient.command("generatetoaddress", 1, derivedBtcAddress); + + const txDetails = await btcClient.command( + "getrawtransaction", + broadcastTxid, + true + ); + + console.log( + `āœ… Transaction confirmed (${txDetails.confirmations} confirmations)\n` + ); + + console.log("ā³ Waiting for MPC to read transaction result..."); + const readResponse = await waitForReadResponse( + api, + ethers.hexlify(aggregateRequestId), + 120000 + ); + + if (!readResponse) { + throw new Error("āŒ Timeout waiting for read response"); + } + + console.log("āœ… Received read response\n"); + + console.log("\nšŸ” Claim Debug:"); + console.log(" Request ID:", ethers.hexlify(aggregateRequestId)); + console.log( + " Output (hex):", + Buffer.from(readResponse.output).toString("hex") + ); + + let outputBytes = new Uint8Array(readResponse.output); + if (outputBytes.length > 0) { + const mode = outputBytes[0] & 0b11; + if (mode === 0) { + outputBytes = outputBytes.slice(1); + } else if (mode === 1) { + outputBytes = outputBytes.slice(2); + } else if (mode === 2) { + outputBytes = outputBytes.slice(4); + } + } + + console.log( + " Stripped output (hex):", + Buffer.from(outputBytes).toString("hex") + ); + + const balanceBefore = await api.query.btcVault.userBalances(alice.address); + + const claimTx = api.tx.btcVault.claimBtc( + Array.from(ethers.getBytes(aggregateRequestId)), + Array.from(outputBytes), + readResponse.signature + ); + + console.log("šŸš€ Submitting claim transaction..."); + await submitWithRetry(claimTx, alice, api, "Claim BTC"); + + const balanceAfter = await api.query.btcVault.userBalances(alice.address); + + const balanceIncrease = + BigInt(balanceAfter.toString()) - BigInt(balanceBefore.toString()); + + expect(balanceIncrease.toString()).toBe(depositAmount.toString()); + console.log(`āœ… Balance increased by: ${balanceIncrease} sats`); + console.log(` Total balance: ${balanceAfter} sats\n`); + }, 300000); + + function compressPubkey(pubKey: string): Buffer { + const uncompressed = Buffer.from(pubKey.slice(4), "hex"); + const fullUncompressed = Buffer.concat([Buffer.from([0x04]), uncompressed]); + const compressed = ecc.pointCompress(fullUncompressed, true); + return Buffer.from(compressed); + } + + function btcAddressFromPubKey( + pubKey: string, + network: bitcoin.Network + ): string { + const compressedPubkey = compressPubkey(pubKey); + const payment = bitcoin.payments.p2wpkh({ + pubkey: compressedPubkey, + network, + }); + return payment.address!; + } + + function ethAddressFromPubKey(pubKey: string): Uint8Array { + const uncompressedPubkey = Buffer.from(pubKey.slice(4), "hex"); + const hash = ethers.keccak256(uncompressedPubkey); + return new Uint8Array(Buffer.from(hash.slice(2), "hex").slice(-20)); + } + + function encodeDER(r: Buffer, s: Buffer): Buffer { + function toDER(x: Buffer): Buffer { + let i = 0; + while (i < x.length - 1 && x[i] === 0 && x[i + 1] < 0x80) i++; + + const xDER = x.slice(i); + + if (xDER[0] >= 0x80) { + return Buffer.concat([Buffer.from([0x00]), xDER]); + } + + return xDER; + } + + const rDER = toDER(r); + const sDER = toDER(s); + + const len = 2 + rDER.length + 2 + sDER.length; + const buf = Buffer.allocUnsafe(2 + len); + + buf[0] = 0x30; + buf[1] = len; + buf[2] = 0x02; + buf[3] = rDER.length; + rDER.copy(buf, 4); + buf[4 + rDER.length] = 0x02; + buf[5 + rDER.length] = sDER.length; + sDER.copy(buf, 6 + rDER.length); + + return buf; + } + + async function waitForSingleSignature( + api: ApiPromise, + requestId: string, + timeout: number + ): Promise { + return new Promise((resolve, reject) => { + let unsubscribe: any; + const timer = setTimeout(() => { + if (unsubscribe) unsubscribe(); + reject( + new Error( + `Timeout waiting for signature with request ID ${requestId}` + ) + ); + }, timeout); + + api.query.system + .events((events: any) => { + events.forEach((record: any) => { + const { event } = record; + if ( + event.section === "signet" && + event.method === "SignatureResponded" + ) { + const [reqId, responder, signature] = event.data; + if (ethers.hexlify(reqId.toU8a()) === requestId) { + clearTimeout(timer); + if (unsubscribe) unsubscribe(); + resolve(signature.toJSON()); + } + } + }); + }) + .then((unsub: any) => { + unsubscribe = unsub; + }); + }); + } + + async function waitForReadResponse( + api: ApiPromise, + requestId: string, + timeout: number + ): Promise { + return new Promise((resolve) => { + let unsubscribe: any; + const timer = setTimeout(() => { + if (unsubscribe) unsubscribe(); + resolve(null); + }, timeout); + + api.query.system + .events((events: any) => { + events.forEach((record: any) => { + const { event } = record; + if ( + event.section === "signet" && + event.method === "RespondBidirectionalEvent" + ) { + const [reqId, responder, output, signature] = event.data; + if (ethers.hexlify(reqId.toU8a()) === requestId) { + clearTimeout(timer); + if (unsubscribe) unsubscribe(); + resolve({ + responder: responder.toString(), + output: Array.from(output.toU8a()), + signature: signature.toJSON(), + }); + } + } + }); + }) + .then((unsub: any) => { + unsubscribe = unsub; + }); + }); + } +}); diff --git a/signet-ts-client-scripts/coinselect.d.ts b/signet-ts-client-scripts/coinselect.d.ts new file mode 100644 index 0000000000..8c421164b7 --- /dev/null +++ b/signet-ts-client-scripts/coinselect.d.ts @@ -0,0 +1,28 @@ +declare module 'coinselect' { + export interface UTXO { + txid: string; + vout: number; + value: number; + script?: Buffer; + } + + export interface Target { + address?: string; + script?: Buffer; + value: number; + } + + export interface CoinSelectResult { + inputs: UTXO[] | undefined; + outputs: Array<{ address?: string; script?: Buffer; value: number }> | undefined; + fee: number; + } + + function coinSelect( + utxos: UTXO[], + targets: Target[], + feeRate: number + ): CoinSelectResult; + + export default coinSelect; +} \ No newline at end of file diff --git a/signet-ts-client-scripts/package.json b/signet-ts-client-scripts/package.json index a3ddea57bd..d46b625b76 100644 --- a/signet-ts-client-scripts/package.json +++ b/signet-ts-client-scripts/package.json @@ -18,8 +18,12 @@ "@polkadot/api": "^11.2.1", "@polkadot/keyring": "^12.6.2", "@polkadot/util": "^12.6.2", + "bitcoin-regtest": "^1.0.1", + "bitcoinjs-lib": "^6.1.6", + "coinselect": "^3.1.13", "elliptic": "^6.6.1", "ethers": "^6.15.0", + "tiny-secp256k1": "^2.2.4", "ts-node": "^10.9.2", "typescript": "^5.3.3", "viem": "^2.37.6" diff --git a/signet-ts-client-scripts/yarn.lock b/signet-ts-client-scripts/yarn.lock index be8eed5d72..aab6414af4 100644 --- a/signet-ts-client-scripts/yarn.lock +++ b/signet-ts-client-scripts/yarn.lock @@ -639,7 +639,7 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== -"@noble/hashes@1.8.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.3.3", "@noble/hashes@^1.8.0", "@noble/hashes@~1.8.0": +"@noble/hashes@1.8.0", "@noble/hashes@^1.2.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.3.3", "@noble/hashes@^1.8.0", "@noble/hashes@~1.8.0": version "1.8.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== @@ -1352,6 +1352,13 @@ 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== +"@uphold/request-logger@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@uphold/request-logger/-/request-logger-2.0.0.tgz#c585c0bdb94210198945c6597e4fe23d6e63e084" + integrity sha512-UvGS+v87C7VTtQDcFHDLfvfl1zaZaLSwSmAnV35Ne7CzAVvotmZqt9lAIoNpMpaoRpdjVIcnUDwPSeIeA//EoQ== + dependencies: + uuid "^3.0.1" + abitype@1.1.0, abitype@^1.0.9: version "1.1.0" resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.1.0.tgz#510c5b3f92901877977af5e864841f443bf55406" @@ -1374,6 +1381,16 @@ aes-js@4.0.0-beta.5: resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-4.0.0-beta.5.tgz#8d2452c52adedebc3a3e28465d858c11ca315873" integrity sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q== +ajv@^6.12.3: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + ansi-escapes@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" @@ -1428,6 +1445,33 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +asn1@~0.2.3: + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== + +aws4@^1.8.0: + version "1.13.2" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.13.2.tgz#0aa167216965ac9474ccfa83892cfb6b3e1e52ef" + integrity sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw== + babel-jest@30.1.2: version "30.1.2" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-30.1.2.tgz#decd53b3a0cafca49443f93fb7a2c0fba55510da" @@ -1495,11 +1539,70 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base-x@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-4.0.1.tgz#817fb7b57143c501f649805cb247617ad016a885" + integrity sha512-uAZ8x6r6S3aUM9rbHGVOIsR15U/ZSc82b3ymnCPsT45Gk1DDvhDPdIgB5MrhirZWt+5K0EEPQH985kNqZgNPFw== + baseline-browser-mapping@^2.8.3: version "2.8.6" resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.6.tgz#c37dea4291ed8d01682f85661dbe87967028642e" integrity sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw== +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== + dependencies: + tweetnacl "^0.14.3" + +bech32@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-2.0.0.tgz#078d3686535075c8c79709f054b1b226a133b355" + integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg== + +bignumber.js@^9.0.0: + version "9.3.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.3.1.tgz#759c5aaddf2ffdc4f154f7b493e1c8770f88c4d7" + integrity sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ== + +bip174@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bip174/-/bip174-2.1.1.tgz#ef3e968cf76de234a546962bcf572cc150982f9f" + integrity sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ== + +bitcoin-core@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/bitcoin-core/-/bitcoin-core-5.0.0.tgz#6ae38e457814f55ee37c5fa6a5b33c48d9ca8f2d" + integrity sha512-XqHsD5LjtshN8yWzRrq2kof57e1eXCGDx3i5+sFKBRi9MktSlXOR4SRLyXLkfzfBmPEs5q/76RotQJuaWg75DQ== + dependencies: + "@uphold/request-logger" "^2.0.0" + debugnyan "^1.0.0" + json-bigint "^1.0.0" + lodash "^4.0.0" + request "^2.53.0" + semver "^5.1.0" + standard-error "^1.1.0" + +bitcoin-regtest@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/bitcoin-regtest/-/bitcoin-regtest-1.0.1.tgz#953591e77cf6db16a459e5565f73350337c8818e" + integrity sha512-BYQuendqQAdBl1jRZ4qytTdCqAoKrQcMHmlLcY/K74BBzrNIJNqlhWbY70zimHnIBFI1vUOV5aMoSm6JQFq6pA== + dependencies: + bitcoin-core "^5.0.0" + +bitcoinjs-lib@^6.1.6: + version "6.1.7" + resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-6.1.7.tgz#0f98dec1333d658574eefa455295668cfae38bb0" + integrity sha512-tlf/r2DGMbF7ky1MgUqXHzypYHakkEnm0SZP23CJKIqNY/5uNAnMbFhMJdhjrL/7anfb/U8+AlpdjPWjPnAalg== + dependencies: + "@noble/hashes" "^1.2.0" + bech32 "^2.0.0" + bip174 "^2.1.1" + bs58check "^3.0.1" + typeforce "^1.11.3" + varuint-bitcoin "^1.1.2" + bn.js@^4.11.9: version "4.12.2" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.2.tgz#3d8fed6796c24e177737f7cc5172ee04ef39ec99" @@ -1555,6 +1658,21 @@ bs-logger@^0.2.6: dependencies: fast-json-stable-stringify "2.x" +bs58@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-5.0.0.tgz#865575b4d13c09ea2a84622df6c8cbeb54ffc279" + integrity sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ== + dependencies: + base-x "^4.0.0" + +bs58check@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-3.0.1.tgz#2094d13720a28593de1cba1d8c4e48602fdd841c" + integrity sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ== + dependencies: + "@noble/hashes" "^1.2.0" + bs58 "^5.0.0" + bser@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -1567,6 +1685,16 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +bunyan@^1.8.1: + version "1.8.15" + resolved "https://registry.yarnpkg.com/bunyan/-/bunyan-1.8.15.tgz#8ce34ca908a17d0776576ca1b2f6cbd916e93b46" + integrity sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig== + optionalDependencies: + dtrace-provider "~0.8" + moment "^2.19.3" + mv "~2" + safe-json-stringify "~1" + callsites@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -1587,6 +1715,11 @@ caniuse-lite@^1.0.30001741: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz#50ff91a991220a1ee2df5af00650dd5c308ea7cd" integrity sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw== +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== + chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -1624,6 +1757,11 @@ co@^4.6.0: resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== +coinselect@^3.1.13: + version "3.1.13" + resolved "https://registry.yarnpkg.com/coinselect/-/coinselect-3.1.13.tgz#b88c7f9659ed4891d1f1d0c894105b1c10ef89a1" + integrity sha512-iJOrKH/7N9gX0jRkxgOHuGjvzvoxUMSeylDhH1sHn+CjLjdin5R0Hz2WEBu/jrZV5OrHcm+6DMzxwu9zb5mSZg== + 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" @@ -1641,6 +1779,13 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + 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" @@ -1651,6 +1796,11 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== + create-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" @@ -1665,11 +1815,25 @@ cross-spawn@^7.0.3, cross-spawn@^7.0.6: shebang-command "^2.0.0" which "^2.0.1" +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== + dependencies: + assert-plus "^1.0.0" + data-uri-to-buffer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== +debug@^2.2.0: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: version "4.4.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" @@ -1677,6 +1841,14 @@ debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: dependencies: ms "^2.1.3" +debugnyan@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/debugnyan/-/debugnyan-1.0.0.tgz#90386d5ebc2c63588f17f272be5c2a93b7665d83" + integrity sha512-dTvKxcLZCammDLFYi31NRVr5g6vjJ33uf1wcdbIPPxPxxnJ9/xj00Mh/YQkhFMw/VGavaG5KpjSC+4o9r/JjRg== + dependencies: + bunyan "^1.8.1" + debug "^2.2.0" + dedent@^1.6.0: version "1.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.7.0.tgz#c1f9445335f0175a96587be245a282ff451446ca" @@ -1687,6 +1859,11 @@ deepmerge@^4.3.1: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + detect-newline@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -1697,11 +1874,26 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +dtrace-provider@~0.8: + version "0.8.8" + resolved "https://registry.yarnpkg.com/dtrace-provider/-/dtrace-provider-0.8.8.tgz#2996d5490c37e1347be263b423ed7b297fb0d97e" + integrity sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg== + dependencies: + nan "^2.14.0" + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + electron-to-chromium@^1.5.218: version "1.5.222" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.222.tgz#965c93783ad989116b74593ae3068b9466fdb237" @@ -1807,7 +1999,27 @@ expect@30.1.2, expect@^30.0.0: jest-mock "30.0.5" jest-util "30.0.5" -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.1.0: +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== + +extsprintf@^1.2.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" + integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -1850,6 +2062,20 @@ foreground-child@^3.1.0: cross-spawn "^7.0.6" signal-exit "^4.0.1" +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + formdata-polyfill@^4.0.10: version "4.0.10" resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" @@ -1887,6 +2113,13 @@ get-stream@^6.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== + dependencies: + assert-plus "^1.0.0" + glob@^10.3.10: version "10.4.5" resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" @@ -1899,6 +2132,17 @@ glob@^10.3.10: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" +glob@^6.0.1: + version "6.0.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" + integrity sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A== + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + 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" @@ -1928,6 +2172,19 @@ handlebars@^4.7.8: optionalDependencies: uglify-js "^3.1.4" +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== + +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== + dependencies: + ajv "^6.12.3" + har-schema "^2.0.0" + has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" @@ -1955,6 +2212,15 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" @@ -2011,6 +2277,11 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -2021,6 +2292,11 @@ isows@1.0.7: resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.7.tgz#1c06400b7eed216fbba3bcbd68f12490fc342915" integrity sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg== +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== + 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" @@ -2441,17 +2717,39 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== + jsesc@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== +json-bigint@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" + integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ== + dependencies: + bignumber.js "^9.0.0" + 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" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== -json-stringify-safe@^5.0.1: +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== + +json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== @@ -2461,6 +2759,16 @@ json5@^2.2.3: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +jsprim@^1.2.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" + integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.4.0" + verror "1.10.0" + leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" @@ -2483,6 +2791,11 @@ lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== +lodash@^4.0.0: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + lru-cache@^10.2.0: version "10.4.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" @@ -2527,6 +2840,18 @@ micromatch@^4.0.8: braces "^3.0.3" picomatch "^2.3.1" +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + 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" @@ -2542,7 +2867,7 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== -minimatch@^3.0.4, minimatch@^3.1.1: +"minimatch@2 || 3", minimatch@^3.0.4, minimatch@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -2556,7 +2881,7 @@ minimatch@^9.0.4: dependencies: brace-expansion "^2.0.1" -minimist@^1.2.5: +minimist@^1.2.5, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -2566,16 +2891,47 @@ minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== +mkdirp@~0.5.1: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + mock-socket@^9.3.1: version "9.3.1" resolved "https://registry.yarnpkg.com/mock-socket/-/mock-socket-9.3.1.tgz#24fb00c2f573c84812aa4a24181bb025de80cc8e" integrity sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw== +moment@^2.19.3: + version "2.30.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +mv@~2: + version "2.1.1" + resolved "https://registry.yarnpkg.com/mv/-/mv-2.1.1.tgz#ae6ce0d6f6d5e0a4f7d893798d03c1ea9559b6a2" + integrity sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg== + dependencies: + mkdirp "~0.5.1" + ncp "~2.0.0" + rimraf "~2.4.0" + +nan@^2.14.0: + version "2.23.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.23.1.tgz#6f86a31dd87e3d1eb77512bf4b9e14c8aded3975" + integrity sha512-r7bBUGKzlqk8oPBDYxt6Z0aEdF1G1rwlMcLk8LCOMbOzf0mG+JUfUzG4fIMWwHWP0iyaLWEQZJmtB7nOHEm/qw== + napi-postinstall@^0.3.0: version "0.3.3" resolved "https://registry.yarnpkg.com/napi-postinstall/-/napi-postinstall-0.3.3.tgz#93d045c6b576803ead126711d3093995198c6eb9" @@ -2586,6 +2942,11 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +ncp@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" + integrity sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA== + neo-async@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" @@ -2636,6 +2997,11 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -2728,6 +3094,11 @@ path-scurry@^1.11.1: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== + picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" @@ -2769,16 +3140,59 @@ propagate@^2.0.0: resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== +psl@^1.1.28: + version "1.15.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.15.0.tgz#bdace31896f1d97cec6a79e8224898ce93d974c6" + integrity sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w== + dependencies: + punycode "^2.3.1" + +punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + pure-rand@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-7.0.1.tgz#6f53a5a9e3e4a47445822af96821ca509ed37566" integrity sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ== +qs@~6.5.2: + version "6.5.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" + integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== + react-is@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== +request@^2.53.0: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -2796,6 +3210,13 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== +rimraf@~2.4.0: + version "2.4.5" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.4.5.tgz#ee710ce5d93a8fdb856fb5ea8ff0e2d75934b2da" + integrity sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ== + dependencies: + glob "^6.0.1" + rxjs@^7.8.1: version "7.8.2" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" @@ -2803,11 +3224,31 @@ rxjs@^7.8.1: dependencies: tslib "^2.1.0" +safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-json-stringify@~1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz#356e44bc98f1f93ce45df14bcd7c01cda86e0afd" + integrity sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg== + +safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + scale-ts@^1.6.0: version "1.6.1" resolved "https://registry.yarnpkg.com/scale-ts/-/scale-ts-1.6.1.tgz#45151e156d6c04792223c39d8e7484ce926445f2" integrity sha512-PBMc2AWc6wSEqJYBDPcyCLUj9/tMKnLX70jLOSndMtcUoLQucP/DM0vnQo1wJAYjTrQiq8iG9rD0q6wFzgjH7g== +semver@^5.1.0: + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" @@ -2870,6 +3311,21 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== +sshpk@^1.7.0: + version "1.18.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.18.0.tgz#1663e55cddf4d688b86a46b77f0d5fe363aba028" + integrity sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + stack-utils@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" @@ -2877,6 +3333,11 @@ stack-utils@^2.0.6: dependencies: escape-string-regexp "^2.0.0" +standard-error@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/standard-error/-/standard-error-1.1.0.tgz#23e5168fa1c0820189e5812701a79058510d0d34" + integrity sha512-4v7qzU7oLJfMI5EltUSHCaaOd65J6S4BqKRWgzMi4EYaE5fvNabPxmAPGdxpGXqrcWjhDGI/H09CIdEuUOUeXg== + string-length@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" @@ -2978,6 +3439,13 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" +tiny-secp256k1@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/tiny-secp256k1/-/tiny-secp256k1-2.2.4.tgz#1d9e45c2facb8607847da71a0a3d9cb2fd027eb2" + integrity sha512-FoDTcToPqZE454Q04hH9o2EhxWsm7pOSpicyHkgTwKhdKWdsTUuqfP5MLq3g+VjAtl2vSx6JpXGdwA2qpYkI0Q== + dependencies: + uint8array-tools "0.0.7" + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -2990,6 +3458,14 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + ts-jest@^29.4.4: version "29.4.4" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.4.4.tgz#fc6fefe28652ed81b8e1381ef8391901d9f81417" @@ -3034,6 +3510,18 @@ tslib@^2.1.0, tslib@^2.4.0, tslib@^2.6.2, tslib@^2.7.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== + type-detect@4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" @@ -3049,6 +3537,11 @@ type-fest@^4.41.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== +typeforce@^1.11.3: + version "1.18.0" + resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc" + integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g== + typescript@^5.3.3: version "5.9.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6" @@ -3059,6 +3552,11 @@ uglify-js@^3.1.4: resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== +uint8array-tools@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/uint8array-tools/-/uint8array-tools-0.0.7.tgz#a7a2bb5d8836eae2fade68c771454e6a438b390d" + integrity sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ== + undici-types@~6.19.2: version "6.19.8" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" @@ -3109,6 +3607,18 @@ update-browserslist-db@^1.1.3: escalade "^3.2.0" picocolors "^1.1.1" +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +uuid@^3.0.1, uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + 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" @@ -3123,6 +3633,22 @@ v8-to-istanbul@^9.0.1: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^2.0.0" +varuint-bitcoin@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz#e76c138249d06138b480d4c5b40ef53693e24e92" + integrity sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw== + dependencies: + safe-buffer "^5.1.1" + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + viem@^2.37.6: version "2.37.6" resolved "https://registry.yarnpkg.com/viem/-/viem-2.37.6.tgz#3b05586555bd4b2c1b7351ed148f9fa98df72027" From 9ee139feb8520753bb68f6b7b9e88a0a91bde0f7 Mon Sep 17 00:00:00 2001 From: Osman Abdelnasir Date: Tue, 25 Nov 2025 19:27:44 +0400 Subject: [PATCH 32/32] chore: README --- signet-ts-client-scripts/README.md | 78 ++++++++++++++++++++++++++---- 1 file changed, 69 insertions(+), 9 deletions(-) diff --git a/signet-ts-client-scripts/README.md b/signet-ts-client-scripts/README.md index 1cbd447589..faee2707c9 100644 --- a/signet-ts-client-scripts/README.md +++ b/signet-ts-client-scripts/README.md @@ -8,17 +8,30 @@ Test client for the Signet pallet on Substrate/Polkadot. Validates signature gen - Running Substrate node with Signet pallet deployed (port 8000) - Access to the Signet signature server - For ERC20 vault tests: Funded Ethereum Sepolia account with ETH and USDC +- For Bitcoin vault tests: Docker for running Bitcoin regtest ## Setup -### 1. Start the Signature Server +### 1. Start Bitcoin Regtest (for Bitcoin vault tests) +```bash +# Clone the Bitcoin regtest repository +git clone https://github.com/Pessina/bitcoin-regtest.git +cd bitcoin-regtest + +# Start Bitcoin Core in regtest mode +yarn docker:dev +``` + +The Bitcoin regtest node will be available at `http://localhost:18443`. + +### 2. Start the Signature Server Clone and run the signature server that responds to Substrate signature requests. Add .env to the root of the repository: ```bash -# Clone the server repository -git clone https://github.com/sig-net/solana-signet-program -cd chain-signatures-solana/clients/response-server +# Clone the server repository (substrate-new branch) +git clone -b substrate-new https://github.com/sig-net/solana-signet-program.git +cd solana-signet-program/clients/response-server # Install dependencies yarn install @@ -35,6 +48,9 @@ PRIVATE_KEY_TESTNET=0x... # Your private key for signing # Ethereum Configuration (for vault monitoring) SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/your_infura_key_here +# Bitcoin Configuration (for Bitcoin vault monitoring) +BITCOIN_NETWORK=regtest + # Dummy solana key SOLANA_PRIVATE_KEY='[16,151,155,240,122,151,187,95,145,26,179,205,196,113,3,62,17,105,18,240,197,176,45,90,176,108,30,106,182,43,7,104,80,202,59,51,239,219,236,17,39,204,155,35,175,195,17,172,201,196,134,125,25,214,148,76,102,47,123,37,203,86,159,147]' EOF @@ -47,14 +63,15 @@ The server will: - Connect to your Substrate node - Automatically respond to signature requests - Monitor Ethereum transactions and report results back to Substrate +- Monitor Bitcoin transactions on regtest and report results back to Substrate -### 2. Install Test Client Dependencies +### 3. Install Test Client Dependencies ```bash yarn install ``` -### 3. Ensure Substrate Node is Running +### 4. Ensure Substrate Node is Running The tests expect a Substrate node with the Signet pallet at `ws://localhost:8000`. If using Chopsticks: @@ -64,7 +81,7 @@ npx @acala-network/chopsticks@latest --config=hydradx \ --db=:memory: ``` -### 4. Fund Ethereum Account for Vault Tests +### 5. Fund Ethereum Account for Vault Tests The ERC20 vault test requires a funded account on Sepolia. The test derives an Ethereum address from your Substrate account and expects it to have: @@ -82,6 +99,7 @@ yarn test # Run specific test suite yarn test signet.test.ts yarn test erc20vault.test.ts +yarn test btc-vault.test.ts # Run with watch mode yarn test:watch @@ -108,6 +126,22 @@ yarn test:watch - Claim deposited tokens in Substrate vault - **Multi-token Support**: Vault supports any ERC20 token (decimal-agnostic) +### Bitcoin Vault Integration (`btc-vault.test.ts`) +- **Vault Initialization**: Initialize vault with MPC signer address hash +- **UTXO Management**: Automatically discover and select UTXOs for spending +- **Deposit Flow**: + - Fund Bitcoin address derived from Substrate account + - Build PSBT (Partially Signed Bitcoin Transaction) with vault output + - Request per-input MPC signatures for each PSBT input + - Finalize and broadcast signed transaction to regtest + - Wait for confirmation +- **Result Monitoring**: MPC server observes transaction confirmation on Bitcoin +- **Claim Flow**: + - Receive transaction output from MPC + - Verify MPC signature on result + - Claim deposited sats in Substrate vault +- **Per-Input Signing**: Each UTXO input receives an individual signature with unique request ID + ## Expected Output ### Basic Signature Test @@ -168,6 +202,28 @@ Tests: 1 passed, 1 total Time: 48.056 s ``` +### Bitcoin Vault Test +``` +PASS ./btc-vault-perinput.test.ts (131.227 s) +BTC Vault Integration +āœ“ should complete full deposit and claim flow (125678 ms) +šŸ”‘ Derived Bitcoin Address: bcrt1qmr8q7udefh6e3mld7xpahh7g652pq3zurswrxz +šŸ’° Funding bcrt1qmr8q7udefh6e3mld7xpahh7g652pq3zurswrxz with 1 BTC... +šŸ“¦ Found 32 UTXO(s) +šŸ“Š Transaction breakdown: +Inputs: 1 +To vault: 36879590 sats +Fee: 138 sats +āœ… Deposit BTC included in block +ā³ Waiting for MPC signature(s)... +āœ… Received all 1 signature(s) from MPC +šŸ“” Broadcasting transaction to regtest... +āœ… Transaction confirmed (1 confirmations) +āœ… Balance increased by: 36879590 sats +Test Suites: 1 passed, 1 total +Tests: 1 passed, 1 total +``` + ## Configuration The root public key used for derivation is hardcoded in the tests. Ensure the server's `PRIVATE_KEY_TESTNET` corresponds to: @@ -181,8 +237,12 @@ const ROOT_PUBLIC_KEY = "0x044eef776e4f257d68983e45b340c2e9546c5df95447900b6aadf - **Timeout errors**: Ensure the signature server is running and connected to the same Substrate node - **Address mismatch**: Verify the server's private key matches the client's expected public key - **Transaction errors**: Check that the Signet pallet is initialized (tests handle this automatically) -- **Vault test failures**: +- **ERC20 vault test failures**: - Ensure your derived Ethereum address is funded with ETH and USDC on Sepolia - - Verify the MPC server has Ethereum monitoring enabled with `SEPOLIA_RPC` configured + - Verify the MPC server has Ethereum monitoring enabled with `SEPOLIA_RPC_URL` configured - Check that the vault's MPC address matches the server's signing key +- **Bitcoin vault test failures**: + - Ensure Bitcoin regtest is running at `http://localhost:18443` + - Verify the MPC server has `BITCOIN_NETWORK=regtest` configured + - The test automatically funds addresses and mines blocks, but ensure Docker has sufficient resources - **InvalidSigner errors**: The output bytes from Substrate events include SCALE encoding that must be stripped before verification \ No newline at end of file