diff --git a/Cargo.lock b/Cargo.lock index d7fa206e6d..f6cdbcf0e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4471,6 +4471,7 @@ dependencies = [ "sp-core-hashing", "sp-keyring", "sp-runtime", + "subxt-core", "subxt-lightclient", "subxt-macro", "subxt-metadata", diff --git a/core/Cargo.toml b/core/Cargo.toml index edc0307f29..a184871dec 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -15,8 +15,8 @@ description = "Sign extrinsics to be submitted by Subxt" keywords = ["parity", "subxt", "extrinsic", "no-std"] [features] -# default = ["std"] -std = [] +default = ["std"] +std = ["scale-info/std", "scale-value/std", "scale-bits/std", "scale-decode/std", "scale-encode/std", "frame-metadata/std", "subxt-metadata/std"] substrate-compat = ["sp-core", "sp-runtime"] [dependencies] diff --git a/core/src/utils/mod.rs b/core/src/utils/mod.rs index 006864dd64..067c43f3f2 100644 --- a/core/src/utils/mod.rs +++ b/core/src/utils/mod.rs @@ -44,7 +44,7 @@ impl codec::Encode for Encoded { /// Decodes a compact encoded value from the beginning of the provided bytes, /// returning the value and any remaining bytes. -pub(crate) fn strip_compact_prefix(bytes: &[u8]) -> Result<(u64, &[u8]), codec::Error> { +pub fn strip_compact_prefix(bytes: &[u8]) -> Result<(u64, &[u8]), codec::Error> { let cursor = &mut &*bytes; let val = >::decode(cursor)?; Ok((val.0, *cursor)) @@ -67,7 +67,7 @@ pub(crate) fn strip_compact_prefix(bytes: &[u8]) -> Result<(u64, &[u8]), codec:: pub struct PhantomDataSendSync(core::marker::PhantomData); impl PhantomDataSendSync { - pub(crate) fn new() -> Self { + pub fn new() -> Self { Self(core::marker::PhantomData) } } diff --git a/subxt/Cargo.toml b/subxt/Cargo.toml index b53cf79f0b..8ef6cd7ba7 100644 --- a/subxt/Cargo.toml +++ b/subxt/Cargo.toml @@ -56,6 +56,7 @@ scale-value = { workspace = true, features = ["default"] } scale-bits = { workspace = true, features = ["default"] } scale-decode = { workspace = true, features = ["default"] } scale-encode = { workspace = true, features = ["default"] } +subxt-core = { workspace = true, features = ["std"] } futures = { workspace = true } hex = { workspace = true } serde = { workspace = true, features = ["derive"] } diff --git a/subxt/src/utils.rs b/subxt/src/utils.rs new file mode 100644 index 0000000000..b02acebba0 --- /dev/null +++ b/subxt/src/utils.rs @@ -0,0 +1,36 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +//! Utility functions used in subxt. Reexports all elements from [`subxt_core::utils`]; + +pub use subxt_core::utils::*; + +use url::Url; + +use crate::{error::RpcError, Error}; + +/// A URL is considered secure if it uses a secure scheme ("https" or "wss") or is referring to localhost. +/// +/// Returns an error if the the string could not be parsed into a URL. +pub fn url_is_secure(url: &str) -> Result { + let url = Url::parse(url).map_err(|e| Error::Rpc(RpcError::ClientError(Box::new(e))))?; + + let secure_scheme = url.scheme() == "https" || url.scheme() == "wss"; + let is_localhost = url.host().is_some_and(|e| match e { + url::Host::Domain(e) => e == "localhost", + url::Host::Ipv4(e) => e.is_loopback(), + url::Host::Ipv6(e) => e.is_loopback(), + }); + + Ok(secure_scheme || is_localhost) +} + +/// Validates, that the given Url is secure ("https" or "wss" scheme) or is referring to localhost. +pub fn validate_url_is_secure(url: &str) -> Result<(), Error> { + if !url_is_secure(url)? { + Err(Error::Rpc(crate::error::RpcError::InsecureUrl(url.into()))) + } else { + Ok(()) + } +} diff --git a/subxt/src/utils/account_id.rs b/subxt/src/utils/account_id.rs deleted file mode 100644 index faa3dbbd4d..0000000000 --- a/subxt/src/utils/account_id.rs +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 2019-2023 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! The "default" Substrate/Polkadot AccountId. This is used in codegen, as well as signing related bits. -//! This doesn't contain much functionality itself, but is easy to convert to/from an `sp_core::AccountId32` -//! for instance, to gain functionality without forcing a dependency on Substrate crates here. - -use codec::{Decode, Encode}; -use serde::{Deserialize, Serialize}; - -/// A 32-byte cryptographic identifier. This is a simplified version of Substrate's -/// `sp_core::crypto::AccountId32`. To obtain more functionality, convert this into -/// that type. -#[derive( - Clone, - Eq, - PartialEq, - Ord, - PartialOrd, - Encode, - Decode, - Debug, - scale_encode::EncodeAsType, - scale_decode::DecodeAsType, - scale_info::TypeInfo, -)] -pub struct AccountId32(pub [u8; 32]); - -impl AsRef<[u8]> for AccountId32 { - fn as_ref(&self) -> &[u8] { - &self.0[..] - } -} - -impl AsRef<[u8; 32]> for AccountId32 { - fn as_ref(&self) -> &[u8; 32] { - &self.0 - } -} - -impl From<[u8; 32]> for AccountId32 { - fn from(x: [u8; 32]) -> Self { - AccountId32(x) - } -} - -impl AccountId32 { - // Return the ss58-check string for this key. Adapted from `sp_core::crypto`. We need this to - // serialize our account appropriately but otherwise don't care. - fn to_ss58check(&self) -> String { - // For serializing to a string to obtain the account nonce, we use the default substrate - // prefix (since we have no way to otherwise pick one). It doesn't really matter, since when - // it's deserialized back in system_accountNextIndex, we ignore this (so long as it's valid). - const SUBSTRATE_SS58_PREFIX: u8 = 42; - // prefix <= 63 just take up one byte at the start: - let mut v = vec![SUBSTRATE_SS58_PREFIX]; - // then push the account ID bytes. - v.extend(self.0); - // then push a 2 byte checksum of what we have so far. - let r = ss58hash(&v); - v.extend(&r[0..2]); - // then encode to base58. - use base58::ToBase58; - v.to_base58() - } - - // This isn't strictly needed, but to give our AccountId32 a little more usefulness, we also - // implement the logic needed to decode an AccountId32 from an SS58 encoded string. This is exposed - // via a `FromStr` impl. - fn from_ss58check(s: &str) -> Result { - const CHECKSUM_LEN: usize = 2; - let body_len = 32; - - use base58::FromBase58; - let data = s.from_base58().map_err(|_| FromSs58Error::BadBase58)?; - if data.len() < 2 { - return Err(FromSs58Error::BadLength); - } - let prefix_len = match data[0] { - 0..=63 => 1, - 64..=127 => 2, - _ => return Err(FromSs58Error::InvalidPrefix), - }; - if data.len() != prefix_len + body_len + CHECKSUM_LEN { - return Err(FromSs58Error::BadLength); - } - let hash = ss58hash(&data[0..body_len + prefix_len]); - let checksum = &hash[0..CHECKSUM_LEN]; - if data[body_len + prefix_len..body_len + prefix_len + CHECKSUM_LEN] != *checksum { - // Invalid checksum. - return Err(FromSs58Error::InvalidChecksum); - } - - let result = data[prefix_len..body_len + prefix_len] - .try_into() - .map_err(|_| FromSs58Error::BadLength)?; - Ok(AccountId32(result)) - } -} - -/// An error obtained from trying to interpret an SS58 encoded string into an AccountId32 -#[derive(thiserror::Error, Clone, Copy, Eq, PartialEq, Debug)] -#[allow(missing_docs)] -pub enum FromSs58Error { - #[error("Base 58 requirement is violated")] - BadBase58, - #[error("Length is bad")] - BadLength, - #[error("Invalid checksum")] - InvalidChecksum, - #[error("Invalid SS58 prefix byte.")] - InvalidPrefix, -} - -// We do this just to get a checksum to help verify the validity of the address in to_ss58check -fn ss58hash(data: &[u8]) -> Vec { - use blake2::{Blake2b512, Digest}; - const PREFIX: &[u8] = b"SS58PRE"; - let mut ctx = Blake2b512::new(); - ctx.update(PREFIX); - ctx.update(data); - ctx.finalize().to_vec() -} - -impl Serialize for AccountId32 { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(&self.to_ss58check()) - } -} - -impl<'de> Deserialize<'de> for AccountId32 { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - AccountId32::from_ss58check(&String::deserialize(deserializer)?) - .map_err(|e| serde::de::Error::custom(format!("{e:?}"))) - } -} - -impl std::fmt::Display for AccountId32 { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", self.to_ss58check()) - } -} - -impl std::str::FromStr for AccountId32 { - type Err = FromSs58Error; - fn from_str(s: &str) -> Result { - AccountId32::from_ss58check(s) - } -} - -// Improve compat with the substrate version if we're using those crates: -#[cfg(feature = "substrate-compat")] -mod substrate_impls { - use super::*; - - impl From for AccountId32 { - fn from(value: sp_runtime::AccountId32) -> Self { - Self(value.into()) - } - } - impl From for AccountId32 { - fn from(value: sp_core::sr25519::Public) -> Self { - let acc: sp_runtime::AccountId32 = value.into(); - acc.into() - } - } - impl From for AccountId32 { - fn from(value: sp_core::ed25519::Public) -> Self { - let acc: sp_runtime::AccountId32 = value.into(); - acc.into() - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - use sp_core::crypto::Ss58Codec; - use sp_keyring::AccountKeyring; - - #[test] - fn ss58_is_compatible_with_substrate_impl() { - let keyrings = vec![ - AccountKeyring::Alice, - AccountKeyring::Bob, - AccountKeyring::Charlie, - ]; - - for keyring in keyrings { - let substrate_account = keyring.to_account_id(); - // Avoid "From" impl hidden behind "substrate-compat" feature so that this test - // can work either way: - let local_account = AccountId32(substrate_account.clone().into()); - - // Both should encode to ss58 the same way: - let substrate_ss58 = substrate_account.to_ss58check(); - assert_eq!(substrate_ss58, local_account.to_ss58check()); - - // Both should decode from ss58 back to the same: - assert_eq!( - sp_core::crypto::AccountId32::from_ss58check(&substrate_ss58).unwrap(), - substrate_account - ); - assert_eq!( - AccountId32::from_ss58check(&substrate_ss58).unwrap(), - local_account - ); - } - } -} diff --git a/subxt/src/utils/bits.rs b/subxt/src/utils/bits.rs deleted file mode 100644 index ed830a0dea..0000000000 --- a/subxt/src/utils/bits.rs +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright 2019-2023 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! Generic `scale_bits` over `bitvec`-like `BitOrder` and `BitFormat` types. - -use codec::{Compact, Input}; -use scale_bits::{ - scale::format::{Format, OrderFormat, StoreFormat}, - Bits, -}; -use scale_decode::IntoVisitor; -use std::marker::PhantomData; - -/// Associates `bitvec::store::BitStore` trait with corresponding, type-erased `scale_bits::StoreFormat` enum. -/// -/// Used to decode bit sequences by providing `scale_bits::StoreFormat` using -/// `bitvec`-like type type parameters. -pub trait BitStore { - /// Corresponding `scale_bits::StoreFormat` value. - const FORMAT: StoreFormat; - /// Number of bits that the backing store types holds. - const BITS: u32; -} -macro_rules! impl_store { - ($ty:ident, $wrapped:ty) => { - impl BitStore for $wrapped { - const FORMAT: StoreFormat = StoreFormat::$ty; - const BITS: u32 = <$wrapped>::BITS; - } - }; -} -impl_store!(U8, u8); -impl_store!(U16, u16); -impl_store!(U32, u32); -impl_store!(U64, u64); - -/// Associates `bitvec::order::BitOrder` trait with corresponding, type-erased `scale_bits::OrderFormat` enum. -/// -/// Used to decode bit sequences in runtime by providing `scale_bits::OrderFormat` using -/// `bitvec`-like type type parameters. -pub trait BitOrder { - /// Corresponding `scale_bits::OrderFormat` value. - const FORMAT: OrderFormat; -} -macro_rules! impl_order { - ($ty:ident) => { - #[doc = concat!("Type-level value that corresponds to `scale_bits::OrderFormat::", stringify!($ty), "` at run-time")] - #[doc = concat!(" and `bitvec::order::BitOrder::", stringify!($ty), "` at the type level.")] - #[derive(Clone, Debug, PartialEq, Eq)] - pub enum $ty {} - impl BitOrder for $ty { - const FORMAT: OrderFormat = OrderFormat::$ty; - } - }; -} -impl_order!(Lsb0); -impl_order!(Msb0); - -/// Constructs a run-time format parameters based on the corresponding type-level parameters. -fn bit_format() -> Format { - Format { - order: Order::FORMAT, - store: Store::FORMAT, - } -} - -/// `scale_bits::Bits` generic over the bit store (`u8`/`u16`/`u32`/`u64`) and bit order (LSB, MSB) -/// used for SCALE encoding/decoding. Uses `scale_bits::Bits`-default `u8` and LSB format underneath. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct DecodedBits { - bits: Bits, - _marker: PhantomData<(Store, Order)>, -} - -impl DecodedBits { - /// Extracts the underlying `scale_bits::Bits` value. - pub fn into_bits(self) -> Bits { - self.bits - } - - /// References the underlying `scale_bits::Bits` value. - pub fn as_bits(&self) -> &Bits { - &self.bits - } -} - -impl core::iter::FromIterator for DecodedBits { - fn from_iter>(iter: T) -> Self { - DecodedBits { - bits: Bits::from_iter(iter), - _marker: PhantomData, - } - } -} - -impl codec::Decode for DecodedBits { - fn decode(input: &mut I) -> Result { - /// Equivalent of `BitSlice::MAX_BITS` on 32bit machine. - const ARCH32BIT_BITSLICE_MAX_BITS: u32 = 0x1fff_ffff; - - let Compact(bits) = >::decode(input)?; - // Otherwise it is impossible to store it on 32bit machine. - if bits > ARCH32BIT_BITSLICE_MAX_BITS { - return Err("Attempt to decode a BitVec with too many bits".into()); - } - // NOTE: Replace with `bits.div_ceil(Store::BITS)` if `int_roundings` is stabilised - let elements = (bits / Store::BITS) + u32::from(bits % Store::BITS != 0); - let bytes_in_elem = Store::BITS.saturating_div(u8::BITS); - let bytes_needed = (elements * bytes_in_elem) as usize; - - // NOTE: We could reduce allocations if it would be possible to directly - // decode from an `Input` type using a custom format (rather than default ) - // for the `Bits` type. - let mut storage = codec::Encode::encode(&Compact(bits)); - let prefix_len = storage.len(); - storage.reserve_exact(bytes_needed); - storage.extend(vec![0; bytes_needed]); - input.read(&mut storage[prefix_len..])?; - - let decoder = scale_bits::decode_using_format_from(&storage, bit_format::())?; - let bits = decoder.collect::, _>>()?; - let bits = Bits::from_iter(bits); - - Ok(DecodedBits { - bits, - _marker: PhantomData, - }) - } -} - -impl codec::Encode for DecodedBits { - fn size_hint(&self) -> usize { - self.bits.size_hint() - } - - fn encoded_size(&self) -> usize { - self.bits.encoded_size() - } - - fn encode(&self) -> Vec { - scale_bits::encode_using_format(self.bits.iter(), bit_format::()) - } -} - -#[doc(hidden)] -pub struct DecodedBitsVisitor(std::marker::PhantomData<(S, O)>); -impl scale_decode::Visitor for DecodedBitsVisitor { - type Value<'scale, 'info> = DecodedBits; - type Error = scale_decode::Error; - - fn unchecked_decode_as_type<'scale, 'info>( - self, - input: &mut &'scale [u8], - type_id: scale_decode::visitor::TypeId, - types: &'info scale_info::PortableRegistry, - ) -> scale_decode::visitor::DecodeAsTypeResult< - Self, - Result, Self::Error>, - > { - let res = scale_decode::visitor::decode_with_visitor( - input, - type_id.0, - types, - Bits::into_visitor(), - ) - .map(|bits| DecodedBits { - bits, - _marker: PhantomData, - }); - scale_decode::visitor::DecodeAsTypeResult::Decoded(res) - } -} -impl scale_decode::IntoVisitor for DecodedBits { - type Visitor = DecodedBitsVisitor; - fn into_visitor() -> Self::Visitor { - DecodedBitsVisitor(PhantomData) - } -} - -impl scale_encode::EncodeAsType for DecodedBits { - fn encode_as_type_to( - &self, - type_id: u32, - types: &scale_info::PortableRegistry, - out: &mut Vec, - ) -> Result<(), scale_encode::Error> { - self.bits.encode_as_type_to(type_id, types, out) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use core::fmt::Debug; - - use bitvec::vec::BitVec; - use codec::Decode as _; - - // NOTE: We don't use `bitvec::order` types in our implementation, since we - // don't want to depend on `bitvec`. Rather than reimplementing the unsafe - // trait on our types here for testing purposes, we simply convert and - // delegate to `bitvec`'s own types. - trait ToBitVec { - type Order: bitvec::order::BitOrder; - } - impl ToBitVec for Lsb0 { - type Order = bitvec::order::Lsb0; - } - impl ToBitVec for Msb0 { - type Order = bitvec::order::Msb0; - } - - fn scales_like_bitvec_and_roundtrips< - 'a, - Store: BitStore + bitvec::store::BitStore + PartialEq, - Order: BitOrder + ToBitVec + Debug + PartialEq, - >( - input: impl IntoIterator, - ) where - BitVec::Order>: codec::Encode + codec::Decode, - { - let input: Vec<_> = input.into_iter().copied().collect(); - - let decoded_bits = DecodedBits::::from_iter(input.clone()); - let bitvec = BitVec::::Order>::from_iter(input); - - let decoded_bits_encoded = codec::Encode::encode(&decoded_bits); - let bitvec_encoded = codec::Encode::encode(&bitvec); - assert_eq!(decoded_bits_encoded, bitvec_encoded); - - let decoded_bits_decoded = - DecodedBits::::decode(&mut &decoded_bits_encoded[..]) - .expect("SCALE-encoding DecodedBits to roundtrip"); - let bitvec_decoded = - BitVec::::Order>::decode(&mut &bitvec_encoded[..]) - .expect("SCALE-encoding BitVec to roundtrip"); - assert_eq!(decoded_bits, decoded_bits_decoded); - assert_eq!(bitvec, bitvec_decoded); - } - - #[test] - fn decoded_bitvec_scales_and_roundtrips() { - let test_cases = [ - vec![], - vec![true], - vec![false], - vec![true, false, true], - vec![true, false, true, false, false, false, false, false, true], - [vec![true; 5], vec![false; 5], vec![true; 1], vec![false; 3]].concat(), - [vec![true; 9], vec![false; 9], vec![true; 9], vec![false; 9]].concat(), - ]; - - for test_case in &test_cases { - scales_like_bitvec_and_roundtrips::(test_case); - scales_like_bitvec_and_roundtrips::(test_case); - scales_like_bitvec_and_roundtrips::(test_case); - scales_like_bitvec_and_roundtrips::(test_case); - scales_like_bitvec_and_roundtrips::(test_case); - scales_like_bitvec_and_roundtrips::(test_case); - scales_like_bitvec_and_roundtrips::(test_case); - scales_like_bitvec_and_roundtrips::(test_case); - } - } -} diff --git a/subxt/src/utils/era.rs b/subxt/src/utils/era.rs deleted file mode 100644 index c98ebe58f6..0000000000 --- a/subxt/src/utils/era.rs +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright 2019-2023 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -use scale_decode::DecodeAsType; -use scale_encode::EncodeAsType; - -// Dev note: This and related bits taken from `sp_runtime::generic::Era` -/// An era to describe the longevity of a transaction. -#[derive( - PartialEq, - Default, - Eq, - Clone, - Copy, - Debug, - serde::Serialize, - serde::Deserialize, - DecodeAsType, - EncodeAsType, - scale_info::TypeInfo, -)] -pub enum Era { - /// The transaction is valid forever. The genesis hash must be present in the signed content. - #[default] - Immortal, - - /// The transaction will expire. Use [`Era::mortal`] to construct this with correct values. - /// - /// When used on `FRAME`-based runtimes, `period` cannot exceed `BlockHashCount` parameter - /// of `system` module. - Mortal { - /// The number of blocks that the tx will be valid for after the checkpoint block - /// hash found in the signer payload. - period: u64, - /// The phase in the period that this transaction's lifetime begins (and, importantly, - /// implies which block hash is included in the signature material). If the `period` is - /// greater than 1 << 12, then it will be a factor of the times greater than 1<<12 that - /// `period` is. - phase: u64, - }, -} - -// E.g. with period == 4: -// 0 10 20 30 40 -// 0123456789012345678901234567890123456789012 -// |...| -// authored -/ \- expiry -// phase = 1 -// n = Q(current - phase, period) + phase -impl Era { - /// Create a new era based on a period (which should be a power of two between 4 and 65536 - /// inclusive) and a block number on which it should start (or, for long periods, be shortly - /// after the start). - /// - /// If using `Era` in the context of `FRAME` runtime, make sure that `period` - /// does not exceed `BlockHashCount` parameter passed to `system` module, since that - /// prunes old blocks and renders transactions immediately invalid. - pub fn mortal(period: u64, current: u64) -> Self { - let period = period - .checked_next_power_of_two() - .unwrap_or(1 << 16) - .clamp(4, 1 << 16); - let phase = current % period; - let quantize_factor = (period >> 12).max(1); - let quantized_phase = phase / quantize_factor * quantize_factor; - - Self::Mortal { - period, - phase: quantized_phase, - } - } -} - -// Both copied from `sp_runtime::generic::Era`; this is the wire interface and so -// it's really the most important bit here. -impl codec::Encode for Era { - fn encode_to(&self, output: &mut T) { - match self { - Self::Immortal => output.push_byte(0), - Self::Mortal { period, phase } => { - let quantize_factor = (*period >> 12).max(1); - let encoded = (period.trailing_zeros() - 1).clamp(1, 15) as u16 - | ((phase / quantize_factor) << 4) as u16; - encoded.encode_to(output); - } - } - } -} -impl codec::Decode for Era { - fn decode(input: &mut I) -> Result { - let first = input.read_byte()?; - if first == 0 { - Ok(Self::Immortal) - } else { - let encoded = first as u64 + ((input.read_byte()? as u64) << 8); - let period = 2 << (encoded % (1 << 4)); - let quantize_factor = (period >> 12).max(1); - let phase = (encoded >> 4) * quantize_factor; - if period >= 4 && phase < period { - Ok(Self::Mortal { period, phase }) - } else { - Err("Invalid period and phase".into()) - } - } - } -} diff --git a/subxt/src/utils/mod.rs b/subxt/src/utils/mod.rs deleted file mode 100644 index dc9320a303..0000000000 --- a/subxt/src/utils/mod.rs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2019-2023 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! Miscellaneous utility helpers. - -mod account_id; -pub mod bits; -mod era; -mod multi_address; -mod multi_signature; -mod static_type; -mod unchecked_extrinsic; -mod wrapper_opaque; - -use crate::error::RpcError; -use crate::Error; -use codec::{Compact, Decode, Encode}; -use derivative::Derivative; -use url::Url; - -pub use account_id::AccountId32; -pub use era::Era; -pub use multi_address::MultiAddress; -pub use multi_signature::MultiSignature; -pub use static_type::Static; -pub use unchecked_extrinsic::UncheckedExtrinsic; -pub use wrapper_opaque::WrapperKeepOpaque; - -// Used in codegen -#[doc(hidden)] -pub use primitive_types::{H160, H256, H512}; - -/// Wraps an already encoded byte vector, prevents being encoded as a raw byte vector as part of -/// the transaction payload -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct Encoded(pub Vec); - -impl codec::Encode for Encoded { - fn encode(&self) -> Vec { - self.0.to_owned() - } -} - -/// Decodes a compact encoded value from the beginning of the provided bytes, -/// returning the value and any remaining bytes. -pub(crate) fn strip_compact_prefix(bytes: &[u8]) -> Result<(u64, &[u8]), codec::Error> { - let cursor = &mut &*bytes; - let val = >::decode(cursor)?; - Ok((val.0, *cursor)) -} - -/// A URL is considered secure if it uses a secure scheme ("https" or "wss") or is referring to localhost. -/// -/// Returns an error if the the string could not be parsed into a URL. -pub fn url_is_secure(url: &str) -> Result { - let url = Url::parse(url).map_err(|e| Error::Rpc(RpcError::ClientError(Box::new(e))))?; - - let secure_scheme = url.scheme() == "https" || url.scheme() == "wss"; - let is_localhost = url.host().is_some_and(|e| match e { - url::Host::Domain(e) => e == "localhost", - url::Host::Ipv4(e) => e.is_loopback(), - url::Host::Ipv6(e) => e.is_loopback(), - }); - - Ok(secure_scheme || is_localhost) -} - -/// Validates, that the given Url is secure ("https" or "wss" scheme) or is referring to localhost. -pub fn validate_url_is_secure(url: &str) -> Result<(), Error> { - if !url_is_secure(url)? { - Err(Error::Rpc(crate::error::RpcError::InsecureUrl(url.into()))) - } else { - Ok(()) - } -} - -/// A version of [`std::marker::PhantomData`] that is also Send and Sync (which is fine -/// because regardless of the generic param, it is always possible to Send + Sync this -/// 0 size type). -#[derive(Derivative, Encode, Decode, scale_info::TypeInfo)] -#[derivative( - Clone(bound = ""), - PartialEq(bound = ""), - Debug(bound = ""), - Eq(bound = ""), - Default(bound = ""), - Hash(bound = "") -)] -#[scale_info(skip_type_params(T))] -#[doc(hidden)] -pub struct PhantomDataSendSync(core::marker::PhantomData); - -impl PhantomDataSendSync { - pub(crate) fn new() -> Self { - Self(core::marker::PhantomData) - } -} - -unsafe impl Send for PhantomDataSendSync {} -unsafe impl Sync for PhantomDataSendSync {} - -/// This represents a key-value collection and is SCALE compatible -/// with collections like BTreeMap. This has the same type params -/// as `BTreeMap` which allows us to easily swap the two during codegen. -pub type KeyedVec = Vec<(K, V)>; diff --git a/subxt/src/utils/multi_address.rs b/subxt/src/utils/multi_address.rs deleted file mode 100644 index 9b1e556fa9..0000000000 --- a/subxt/src/utils/multi_address.rs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2019-2023 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! The "default" Substrate/Polkadot Address type. This is used in codegen, as well as signing related bits. -//! This doesn't contain much functionality itself, but is easy to convert to/from an `sp_runtime::MultiAddress` -//! for instance, to gain functionality without forcing a dependency on Substrate crates here. - -use codec::{Decode, Encode}; - -/// A multi-format address wrapper for on-chain accounts. This is a simplified version of Substrate's -/// `sp_runtime::MultiAddress`. To obtain more functionality, convert this into that type (this conversion -/// functionality is provided via `From` impls if the `substrate-compat` feature is enabled). -#[derive( - Clone, - Eq, - PartialEq, - Ord, - PartialOrd, - Encode, - Decode, - Debug, - scale_encode::EncodeAsType, - scale_decode::DecodeAsType, - scale_info::TypeInfo, -)] -pub enum MultiAddress { - /// It's an account ID (pubkey). - Id(AccountId), - /// It's an account index. - Index(#[codec(compact)] AccountIndex), - /// It's some arbitrary raw bytes. - Raw(Vec), - /// It's a 32 byte representation. - Address32([u8; 32]), - /// Its a 20 byte representation. - Address20([u8; 20]), -} - -impl From for MultiAddress { - fn from(a: AccountId) -> Self { - Self::Id(a) - } -} - -// Improve compat with the substrate version if we're using those crates: -#[cfg(feature = "substrate-compat")] -mod substrate_impls { - use super::{super::AccountId32, *}; - - impl From for MultiAddress { - fn from(value: sp_runtime::AccountId32) -> Self { - let val: AccountId32 = value.into(); - val.into() - } - } - - impl From> for MultiAddress - where - Id: Into, - { - fn from(value: sp_runtime::MultiAddress) -> Self { - match value { - sp_runtime::MultiAddress::Id(v) => Self::Id(v.into()), - sp_runtime::MultiAddress::Index(v) => Self::Index(v), - sp_runtime::MultiAddress::Raw(v) => Self::Raw(v), - sp_runtime::MultiAddress::Address32(v) => Self::Address32(v), - sp_runtime::MultiAddress::Address20(v) => Self::Address20(v), - } - } - } -} diff --git a/subxt/src/utils/multi_signature.rs b/subxt/src/utils/multi_signature.rs deleted file mode 100644 index 4ed9ea4bd5..0000000000 --- a/subxt/src/utils/multi_signature.rs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2019-2023 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! The "default" Substrate/Polkadot Signature type. This is used in codegen, as well as signing related bits. -//! This doesn't contain much functionality itself, but is easy to convert to/from an `sp_runtime::MultiSignature` -//! for instance, to gain functionality without forcing a dependency on Substrate crates here. - -use codec::{Decode, Encode}; - -/// Signature container that can store known signature types. This is a simplified version of -/// `sp_runtime::MultiSignature`. To obtain more functionality, convert this into that type. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, scale_info::TypeInfo)] -pub enum MultiSignature { - /// An Ed25519 signature. - Ed25519([u8; 64]), - /// An Sr25519 signature. - Sr25519([u8; 64]), - /// An ECDSA/SECP256k1 signature (a 512-bit value, plus 8 bits for recovery ID). - Ecdsa([u8; 65]), -} - -// Improve compat with the substrate version if we're using those crates: -#[cfg(feature = "substrate-compat")] -mod substrate_impls { - use super::*; - - impl From for MultiSignature { - fn from(value: sp_runtime::MultiSignature) -> Self { - match value { - sp_runtime::MultiSignature::Ed25519(s) => Self::Ed25519(s.0), - sp_runtime::MultiSignature::Sr25519(s) => Self::Sr25519(s.0), - sp_runtime::MultiSignature::Ecdsa(s) => Self::Ecdsa(s.0), - } - } - } - - impl From for MultiSignature { - fn from(value: sp_core::ed25519::Signature) -> Self { - let sig: sp_runtime::MultiSignature = value.into(); - sig.into() - } - } - - impl From for MultiSignature { - fn from(value: sp_core::sr25519::Signature) -> Self { - let sig: sp_runtime::MultiSignature = value.into(); - sig.into() - } - } - - impl From for MultiSignature { - fn from(value: sp_core::ecdsa::Signature) -> Self { - let sig: sp_runtime::MultiSignature = value.into(); - sig.into() - } - } -} diff --git a/subxt/src/utils/static_type.rs b/subxt/src/utils/static_type.rs deleted file mode 100644 index 2d13e61eba..0000000000 --- a/subxt/src/utils/static_type.rs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2019-2023 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -use codec::{Decode, Encode}; -use scale_decode::{visitor::DecodeAsTypeResult, IntoVisitor, Visitor}; -use scale_encode::EncodeAsType; - -/// If the type inside this implements [`Encode`], this will implement [`scale_encode::EncodeAsType`]. -/// If the type inside this implements [`Decode`], this will implement [`scale_decode::DecodeAsType`]. -/// -/// In either direction, we ignore any type information and just attempt to encode/decode statically -/// via the [`Encode`] and [`Decode`] implementations. This can be useful as an adapter for types which -/// do not implement [`scale_encode::EncodeAsType`] and [`scale_decode::DecodeAsType`] themselves, but -/// it's best to avoid using it where possible as it will not take into account any type information, -/// and is thus more likely to encode or decode incorrectly. -#[derive(Debug, Encode, Decode, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)] -pub struct Static(pub T); - -impl EncodeAsType for Static { - fn encode_as_type_to( - &self, - _type_id: u32, - _types: &scale_decode::PortableRegistry, - out: &mut Vec, - ) -> Result<(), scale_encode::Error> { - self.0.encode_to(out); - Ok(()) - } -} - -pub struct StaticDecodeAsTypeVisitor(std::marker::PhantomData); - -impl Visitor for StaticDecodeAsTypeVisitor { - type Value<'scale, 'info> = Static; - type Error = scale_decode::Error; - - fn unchecked_decode_as_type<'scale, 'info>( - self, - input: &mut &'scale [u8], - _type_id: scale_decode::visitor::TypeId, - _types: &'info scale_info::PortableRegistry, - ) -> DecodeAsTypeResult, Self::Error>> { - use scale_decode::{visitor::DecodeError, Error}; - let decoded = T::decode(input) - .map(Static) - .map_err(|e| Error::new(DecodeError::CodecError(e).into())); - DecodeAsTypeResult::Decoded(decoded) - } -} - -impl IntoVisitor for Static { - type Visitor = StaticDecodeAsTypeVisitor; - fn into_visitor() -> Self::Visitor { - StaticDecodeAsTypeVisitor(std::marker::PhantomData) - } -} - -// Make it easy to convert types into Static where required. -impl From for Static { - fn from(value: T) -> Self { - Static(value) - } -} - -// Static is just a marker type and should be as transparent as possible: -impl std::ops::Deref for Static { - type Target = T; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl std::ops::DerefMut for Static { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} diff --git a/subxt/src/utils/unchecked_extrinsic.rs b/subxt/src/utils/unchecked_extrinsic.rs deleted file mode 100644 index 882b490bed..0000000000 --- a/subxt/src/utils/unchecked_extrinsic.rs +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2019-2023 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! The "default" Substrate/Polkadot UncheckedExtrinsic. -//! This is used in codegen for runtime API calls. -//! -//! The inner bytes represent the encoded extrinsic expected by the -//! runtime APIs. Deriving `EncodeAsType` would lead to the inner -//! bytes to be re-encoded (length prefixed). - -use std::marker::PhantomData; - -use codec::{Decode, Encode}; -use scale_decode::{visitor::DecodeAsTypeResult, DecodeAsType, IntoVisitor, Visitor}; - -use super::{Encoded, Static}; - -/// The unchecked extrinsic from substrate. -#[derive(Clone, Debug, Eq, PartialEq, Encode)] -pub struct UncheckedExtrinsic( - Static, - #[codec(skip)] PhantomData<(Address, Call, Signature, Extra)>, -); - -impl UncheckedExtrinsic { - /// Construct a new [`UncheckedExtrinsic`]. - pub fn new(bytes: Vec) -> Self { - Self(Static(Encoded(bytes)), PhantomData) - } - - /// Get the bytes of the encoded extrinsic. - pub fn bytes(&self) -> &[u8] { - self.0 .0 .0.as_slice() - } -} - -impl Decode - for UncheckedExtrinsic -{ - fn decode(input: &mut I) -> Result { - // The bytes for an UncheckedExtrinsic are first a compact - // encoded length, and then the bytes following. This is the - // same encoding as a Vec, so easiest ATM is just to decode - // into that, and then encode the vec bytes to get our extrinsic - // bytes, which we save into an `Encoded` to preserve as-is. - let xt_vec: Vec = Decode::decode(input)?; - Ok(UncheckedExtrinsic::new(xt_vec)) - } -} - -impl scale_encode::EncodeAsType - for UncheckedExtrinsic -{ - fn encode_as_type_to( - &self, - type_id: u32, - types: &scale_info::PortableRegistry, - out: &mut Vec, - ) -> Result<(), scale_encode::Error> { - self.0.encode_as_type_to(type_id, types, out) - } -} - -impl From> - for UncheckedExtrinsic -{ - fn from(bytes: Vec) -> Self { - UncheckedExtrinsic::new(bytes) - } -} - -impl From> - for Vec -{ - fn from(bytes: UncheckedExtrinsic) -> Self { - bytes.0 .0 .0 - } -} - -pub struct UncheckedExtrinsicDecodeAsTypeVisitor( - PhantomData<(Address, Call, Signature, Extra)>, -); - -impl Visitor - for UncheckedExtrinsicDecodeAsTypeVisitor -{ - type Value<'scale, 'info> = UncheckedExtrinsic; - type Error = scale_decode::Error; - - fn unchecked_decode_as_type<'scale, 'info>( - self, - input: &mut &'scale [u8], - type_id: scale_decode::visitor::TypeId, - types: &'info scale_info::PortableRegistry, - ) -> DecodeAsTypeResult, Self::Error>> { - DecodeAsTypeResult::Decoded(Self::Value::decode_as_type(input, type_id.0, types)) - } -} - -impl IntoVisitor - for UncheckedExtrinsic -{ - type Visitor = UncheckedExtrinsicDecodeAsTypeVisitor; - - fn into_visitor() -> Self::Visitor { - UncheckedExtrinsicDecodeAsTypeVisitor(PhantomData) - } -} - -#[cfg(test)] -pub mod tests { - use super::*; - - #[test] - fn unchecked_extrinsic_encoding() { - // A tx is basically some bytes with a compact length prefix; ie an encoded vec: - let tx_bytes = vec![1u8, 2, 3].encode(); - - let unchecked_extrinsic = UncheckedExtrinsic::<(), (), (), ()>::new(tx_bytes.clone()); - let encoded_tx_bytes = unchecked_extrinsic.encode(); - - // The encoded representation must not alter the provided bytes. - assert_eq!(tx_bytes, encoded_tx_bytes); - - // However, for decoding we expect to be able to read the extrinsic from the wire - // which would be length prefixed. - let decoded_tx = UncheckedExtrinsic::<(), (), (), ()>::decode(&mut &tx_bytes[..]).unwrap(); - let decoded_tx_bytes = decoded_tx.bytes(); - let encoded_tx_bytes = decoded_tx.encode(); - - assert_eq!(decoded_tx_bytes, encoded_tx_bytes); - // Ensure we can decode the tx and fetch only the tx bytes. - assert_eq!(vec![1, 2, 3], encoded_tx_bytes); - } -} diff --git a/subxt/src/utils/wrapper_opaque.rs b/subxt/src/utils/wrapper_opaque.rs deleted file mode 100644 index 9257405715..0000000000 --- a/subxt/src/utils/wrapper_opaque.rs +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright 2019-2023 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -use super::PhantomDataSendSync; -use codec::{Compact, Decode, DecodeAll, Encode}; -use derivative::Derivative; -use scale_decode::{IntoVisitor, Visitor}; -use scale_encode::EncodeAsType; - -/// A wrapper for any type `T` which implement encode/decode in a way compatible with `Vec`. -/// [`WrapperKeepOpaque`] stores the type only in its opaque format, aka as a `Vec`. To -/// access the real type `T` [`Self::try_decode`] needs to be used. -// Dev notes: -// -// - This is adapted from [here](https://github.com/paritytech/substrate/blob/master/frame/support/src/traits/misc.rs). -// - The encoded bytes will be a compact encoded length followed by that number of bytes. -// - However, the TypeInfo describes the type as a composite with first a compact encoded length and next the type itself. -// [`Encode`] and [`Decode`] impls will "just work" to take this into a `Vec`, but we need a custom [`EncodeAsType`] -// and [`Visitor`] implementation to encode and decode based on TypeInfo. -#[derive(Derivative, Encode, Decode)] -#[derivative( - Debug(bound = ""), - Clone(bound = ""), - PartialEq(bound = ""), - Eq(bound = ""), - Default(bound = ""), - Hash(bound = "") -)] -pub struct WrapperKeepOpaque { - data: Vec, - _phantom: PhantomDataSendSync, -} - -impl WrapperKeepOpaque { - /// Try to decode the wrapped type from the inner `data`. - /// - /// Returns `None` if the decoding failed. - pub fn try_decode(&self) -> Option - where - T: Decode, - { - T::decode_all(&mut &self.data[..]).ok() - } - - /// Returns the length of the encoded `T`. - pub fn encoded_len(&self) -> usize { - self.data.len() - } - - /// Returns the encoded data. - pub fn encoded(&self) -> &[u8] { - &self.data - } - - /// Create from the given encoded `data`. - pub fn from_encoded(data: Vec) -> Self { - Self { - data, - _phantom: PhantomDataSendSync::new(), - } - } - - /// Create from some raw value by encoding it. - pub fn from_value(value: T) -> Self - where - T: Encode, - { - Self { - data: value.encode(), - _phantom: PhantomDataSendSync::new(), - } - } -} - -impl EncodeAsType for WrapperKeepOpaque { - fn encode_as_type_to( - &self, - type_id: u32, - types: &scale_info::PortableRegistry, - out: &mut Vec, - ) -> Result<(), scale_encode::Error> { - use scale_encode::error::{Error, ErrorKind, Kind}; - - let Some(ty) = types.resolve(type_id) else { - return Err(Error::new(ErrorKind::TypeNotFound(type_id))); - }; - - // Do a basic check that the target shape lines up. - let scale_info::TypeDef::Composite(_) = &ty.type_def else { - return Err(Error::new(ErrorKind::WrongShape { - actual: Kind::Struct, - expected: type_id, - })); - }; - - // Check that the name also lines up. - if ty.path.ident().as_deref() != Some("WrapperKeepOpaque") { - return Err(Error::new(ErrorKind::WrongShape { - actual: Kind::Struct, - expected: type_id, - })); - } - - // Just blat the bytes out. - self.data.encode_to(out); - Ok(()) - } -} - -pub struct WrapperKeepOpaqueVisitor(std::marker::PhantomData); -impl Visitor for WrapperKeepOpaqueVisitor { - type Value<'scale, 'info> = WrapperKeepOpaque; - type Error = scale_decode::Error; - - fn visit_composite<'scale, 'info>( - self, - value: &mut scale_decode::visitor::types::Composite<'scale, 'info>, - _type_id: scale_decode::visitor::TypeId, - ) -> Result, Self::Error> { - use scale_decode::error::{Error, ErrorKind}; - - if value.path().ident().as_deref() != Some("WrapperKeepOpaque") { - return Err(Error::custom_str( - "Type to decode is not 'WrapperTypeKeepOpaque'", - )); - } - if value.remaining() != 2 { - return Err(Error::new(ErrorKind::WrongLength { - actual_len: value.remaining(), - expected_len: 2, - })); - } - - // The field to decode is a compact len followed by bytes. Decode the length, then grab the bytes. - let Compact(len) = value - .decode_item(Compact::::into_visitor()) - .expect("length checked")?; - let field = value.next().expect("length checked")?; - - // Sanity check that the compact length we decoded lines up with the number of bytes encoded in the next field. - if field.bytes().len() != len as usize { - return Err(Error::custom_str("WrapperTypeKeepOpaque compact encoded length doesn't line up with encoded byte len")); - } - - Ok(WrapperKeepOpaque { - data: field.bytes().to_vec(), - _phantom: PhantomDataSendSync::new(), - }) - } -} - -impl IntoVisitor for WrapperKeepOpaque { - type Visitor = WrapperKeepOpaqueVisitor; - fn into_visitor() -> Self::Visitor { - WrapperKeepOpaqueVisitor(std::marker::PhantomData) - } -} - -#[cfg(test)] -mod test { - use scale_decode::DecodeAsType; - - use super::*; - - // Copied from https://github.com/paritytech/substrate/blob/master/frame/support/src/traits/misc.rs - // and used for tests to check that we can work with the expected TypeInfo without needing to import - // the frame_support crate, which has quite a lot of dependencies. - impl scale_info::TypeInfo for WrapperKeepOpaque { - type Identity = Self; - fn type_info() -> scale_info::Type { - use scale_info::{build::Fields, meta_type, Path, Type, TypeParameter}; - - Type::builder() - .path(Path::new("WrapperKeepOpaque", module_path!())) - .type_params(vec![TypeParameter::new("T", Some(meta_type::()))]) - .composite( - Fields::unnamed() - .field(|f| f.compact::()) - .field(|f| f.ty::().type_name("T")), - ) - } - } - - /// Given a type definition, return type ID and registry representing it. - fn make_type() -> (u32, scale_info::PortableRegistry) { - let m = scale_info::MetaType::new::(); - let mut types = scale_info::Registry::new(); - let id = types.register_type(&m); - let portable_registry: scale_info::PortableRegistry = types.into(); - (id.id, portable_registry) - } - - fn roundtrips_like_scale_codec(t: T) - where - T: EncodeAsType - + DecodeAsType - + Encode - + Decode - + PartialEq - + std::fmt::Debug - + scale_info::TypeInfo - + 'static, - { - let (type_id, types) = make_type::(); - - let scale_codec_encoded = t.encode(); - let encode_as_type_encoded = t.encode_as_type(type_id, &types).unwrap(); - - assert_eq!( - scale_codec_encoded, encode_as_type_encoded, - "encoded bytes should match" - ); - - let decode_as_type_bytes = &mut &*scale_codec_encoded; - let decoded_as_type = T::decode_as_type(decode_as_type_bytes, type_id, &types) - .expect("decode-as-type decodes"); - - let decode_scale_codec_bytes = &mut &*scale_codec_encoded; - let decoded_scale_codec = T::decode(decode_scale_codec_bytes).expect("scale-codec decodes"); - - assert!( - decode_as_type_bytes.is_empty(), - "no bytes should remain in decode-as-type impl" - ); - assert!( - decode_scale_codec_bytes.is_empty(), - "no bytes should remain in codec-decode impl" - ); - - assert_eq!( - decoded_as_type, decoded_scale_codec, - "decoded values should match" - ); - } - - #[test] - fn wrapper_keep_opaque_roundtrips_ok() { - roundtrips_like_scale_codec(WrapperKeepOpaque::from_value(123u64)); - roundtrips_like_scale_codec(WrapperKeepOpaque::from_value(true)); - roundtrips_like_scale_codec(WrapperKeepOpaque::from_value(vec![1u8, 2, 3, 4])); - } -}