mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-23 03:51:06 +00:00
Merge remote-tracking branch 'origin/master' into lexnv/codegen-config
This commit is contained in:
+6
-2
@@ -35,7 +35,6 @@ std = [
|
||||
substrate-compat = ["sp-core", "sp-runtime"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
codec = { package = "parity-scale-codec", workspace = true, default-features = false, features = ["derive"] }
|
||||
scale-info = { workspace = true, default-features = false, features = ["bit-vec"] }
|
||||
scale-value = { workspace = true, default-features = false }
|
||||
@@ -45,7 +44,6 @@ scale-encode = { workspace = true, default-features = false, features = ["derive
|
||||
frame-metadata = { workspace = true, default-features = false }
|
||||
subxt-metadata = { workspace = true, default-features = false }
|
||||
derive-where = { workspace = true }
|
||||
derive_more = { workspace = true }
|
||||
hex = { workspace = true, default-features = false, features = ["alloc"] }
|
||||
serde = { workspace = true, default-features = false, features = ["derive"] }
|
||||
serde_json = { workspace = true, default-features = false, features = ["raw_value", "alloc"] }
|
||||
@@ -66,6 +64,9 @@ sp-core = { workspace = true, optional = true }
|
||||
sp-runtime = { workspace = true, optional = true }
|
||||
tracing = { workspace = true, default-features = false }
|
||||
|
||||
# AccountId20
|
||||
keccak-hash = { workspace = true}
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches = { workspace = true }
|
||||
bitvec = { workspace = true }
|
||||
@@ -84,3 +85,6 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[package.metadata.playground]
|
||||
defalt-features = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -3,15 +3,14 @@
|
||||
// see LICENSE for license details.
|
||||
|
||||
use crate::blocks::extrinsic_signed_extensions::ExtrinsicSignedExtensions;
|
||||
use crate::utils::strip_compact_prefix;
|
||||
use crate::{
|
||||
config::Config,
|
||||
config::{Config, Hasher},
|
||||
error::{BlockError, Error, MetadataError},
|
||||
Metadata,
|
||||
};
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec::Vec;
|
||||
use codec::Decode;
|
||||
use codec::{Compact, CompactLen, Decode};
|
||||
use scale_decode::DecodeAsType;
|
||||
use subxt_metadata::PalletMetadata;
|
||||
|
||||
@@ -169,24 +168,28 @@ where
|
||||
const VERSION_MASK: u8 = 0b0111_1111;
|
||||
const LATEST_EXTRINSIC_VERSION: u8 = 4;
|
||||
|
||||
// removing the compact encoded prefix:
|
||||
let bytes: Arc<[u8]> = strip_compact_prefix(extrinsic_bytes)?.1.into();
|
||||
// Wrap all of the bytes in Arc for easy sharing.
|
||||
let bytes: Arc<[u8]> = Arc::from(extrinsic_bytes);
|
||||
|
||||
// The compact encoded length prefix.
|
||||
let prefix = <Compact<u64>>::decode(&mut &*extrinsic_bytes)?;
|
||||
let prefix_len = <Compact<u64>>::compact_len(&prefix.0);
|
||||
|
||||
// Extrinsic are encoded in memory in the following way:
|
||||
// - first byte: abbbbbbb (a = 0 for unsigned, 1 for signed, b = version)
|
||||
// - signature: [unknown TBD with metadata].
|
||||
// - extrinsic data
|
||||
let first_byte: u8 = Decode::decode(&mut &bytes[..])?;
|
||||
let version_byte: u8 = Decode::decode(&mut &bytes[prefix_len..])?;
|
||||
|
||||
let version = first_byte & VERSION_MASK;
|
||||
let version = version_byte & VERSION_MASK;
|
||||
if version != LATEST_EXTRINSIC_VERSION {
|
||||
return Err(BlockError::UnsupportedVersion(version).into());
|
||||
}
|
||||
|
||||
let is_signed = first_byte & SIGNATURE_MASK != 0;
|
||||
let is_signed = version_byte & SIGNATURE_MASK != 0;
|
||||
|
||||
// Skip over the first byte which denotes the version and signing.
|
||||
let cursor = &mut &bytes[1..];
|
||||
// Skip over the prefix and first byte which denotes the version and signing.
|
||||
let cursor = &mut &bytes[prefix_len + 1..];
|
||||
|
||||
let signed_details = is_signed
|
||||
.then(|| -> Result<SignedExtrinsicDetails, Error> {
|
||||
@@ -248,6 +251,12 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
/// Calculate and return the hash of the extrinsic, based on the configured hasher.
|
||||
pub fn hash(&self) -> T::Hash {
|
||||
// Use hash(), not hash_of(), because we don't want to double encode the bytes.
|
||||
T::Hasher::hash(&self.bytes)
|
||||
}
|
||||
|
||||
/// Is the extrinsic signed?
|
||||
pub fn is_signed(&self) -> bool {
|
||||
self.signed_details.is_some()
|
||||
@@ -630,6 +639,40 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tx_hashes_line_up() {
|
||||
let metadata = metadata();
|
||||
let ids = ExtrinsicPartTypeIds::new(&metadata).unwrap();
|
||||
|
||||
let tx = crate::dynamic::tx(
|
||||
"Test",
|
||||
"TestCall",
|
||||
vec![
|
||||
Value::u128(10),
|
||||
Value::bool(true),
|
||||
Value::string("SomeValue"),
|
||||
],
|
||||
);
|
||||
|
||||
// Encoded TX ready to submit.
|
||||
let tx_encoded = crate::tx::create_unsigned::<SubstrateConfig, _>(&tx, &metadata)
|
||||
.expect("Valid dynamic parameters are provided");
|
||||
|
||||
// Extrinsic details ready to decode.
|
||||
let extrinsic = ExtrinsicDetails::<SubstrateConfig>::decode_from(
|
||||
1,
|
||||
tx_encoded.encoded(),
|
||||
metadata,
|
||||
ids,
|
||||
)
|
||||
.expect("Valid extrinsic");
|
||||
|
||||
// Both of these types should produce the same bytes.
|
||||
assert_eq!(tx_encoded.encoded(), extrinsic.bytes(), "bytes should eq");
|
||||
// Both of these types should produce the same hash.
|
||||
assert_eq!(tx_encoded.hash(), extrinsic.hash(), "hashes should eq");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn statically_decode_extrinsic() {
|
||||
let metadata = metadata();
|
||||
|
||||
@@ -32,9 +32,9 @@
|
||||
//!
|
||||
//! // Some extrinsics we'd like to decode:
|
||||
//! let ext_bytes = vec![
|
||||
//! hex::decode("280402000bf18367a38e01").unwrap(),
|
||||
//! hex::decode("c10184008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4801f4de97941fcc3f95c761cd58d480bb41ce64836850f51b6fcc7542e809eb0a346fe95eb1b72de542273d4f1b00b636eb025e2b0e98cc498a095e7ce48f3d4f82b501040000001848656c6c6f21").unwrap(),
|
||||
//! hex::decode("5102840090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe2201ac0c06f55cf3461067bbe48da16efbb50dfad555e2821ce20d37b2e42d6dcb439acd40f742b12ef00f8889944060b04373dc4d34a1992042fd269e8ec1e64a848502000004000090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe2217000010632d5ec76b05").unwrap()
|
||||
//! hex::decode("1004020000").unwrap(),
|
||||
//! hex::decode("c10184001cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c01a27c400241aeafdea1871b32f1f01e92acd272ddfe6b2f8b73b64c606572a530c470a94ef654f7baa5828474754a1fe31b59f91f6bb5c2cd5a07c22d4b8b8387350100000000001448656c6c6f").unwrap(),
|
||||
//! hex::decode("550284001cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c0144bb92734447c893ab16d520fae0d455257550efa28ee66bf6dc942cb8b00d5d2799b98bc2865d21812278a9a266acd7352f40742ff11a6ce1f400013961598485010000000400008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a481700505a4f7e9f4eb106").unwrap()
|
||||
//! ];
|
||||
//!
|
||||
//! // Given some chain config and metadata, we know how to decode the bytes.
|
||||
|
||||
@@ -18,6 +18,7 @@ pub type DefaultExtrinsicParams<T> = signed_extensions::AnyOf<
|
||||
signed_extensions::CheckMortality<T>,
|
||||
signed_extensions::ChargeAssetTxPayment<T>,
|
||||
signed_extensions::ChargeTransactionPayment,
|
||||
signed_extensions::CheckMetadataHash,
|
||||
),
|
||||
>;
|
||||
|
||||
@@ -151,6 +152,7 @@ impl<T: Config> DefaultExtrinsicParamsBuilder<T> {
|
||||
check_mortality_params,
|
||||
charge_asset_tx_params,
|
||||
charge_transaction_params,
|
||||
(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ use alloc::vec::Vec;
|
||||
/// This trait allows you to configure the "signed extra" and
|
||||
/// "additional" parameters that are a part of the transaction payload
|
||||
/// or the signer payload respectively.
|
||||
pub trait ExtrinsicParams<T: Config>: ExtrinsicParamsEncoder + Sized + 'static {
|
||||
pub trait ExtrinsicParams<T: Config>: ExtrinsicParamsEncoder + Sized + Send + 'static {
|
||||
/// These parameters can be provided to the constructor along with
|
||||
/// some default parameters that `subxt` understands, in order to
|
||||
/// help construct your [`ExtrinsicParams`] object.
|
||||
|
||||
@@ -58,7 +58,7 @@ pub trait Config: Sized + Send + Sync + 'static {
|
||||
type ExtrinsicParams: ExtrinsicParams<Self>;
|
||||
|
||||
/// This is used to identify an asset in the `ChargeAssetTxPayment` signed extension.
|
||||
type AssetId: Debug + Clone + Encode + DecodeAsType + EncodeAsType;
|
||||
type AssetId: Debug + Clone + Encode + DecodeAsType + EncodeAsType + Send;
|
||||
}
|
||||
|
||||
/// given some [`Config`], this return the other params needed for its `ExtrinsicParams`.
|
||||
|
||||
@@ -40,6 +40,61 @@ pub trait SignedExtension<T: Config>: ExtrinsicParams<T> {
|
||||
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool;
|
||||
}
|
||||
|
||||
/// The [`CheckMetadataHash`] signed extension.
|
||||
pub struct CheckMetadataHash {
|
||||
// Eventually we might provide or calculate the metadata hash here,
|
||||
// but for now we never provide a hash and so this is empty.
|
||||
}
|
||||
|
||||
impl<T: Config> ExtrinsicParams<T> for CheckMetadataHash {
|
||||
type Params = ();
|
||||
|
||||
fn new(_client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
|
||||
Ok(CheckMetadataHash {})
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtrinsicParamsEncoder for CheckMetadataHash {
|
||||
fn encode_extra_to(&self, v: &mut Vec<u8>) {
|
||||
// A single 0 byte in the TX payload indicates that the chain should
|
||||
// _not_ expect any metadata hash to exist in the signer payload.
|
||||
0u8.encode_to(v);
|
||||
}
|
||||
fn encode_additional_to(&self, v: &mut Vec<u8>) {
|
||||
// We provide no metadata hash in the signer payload to align with the above.
|
||||
None::<()>.encode_to(v);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> SignedExtension<T> for CheckMetadataHash {
|
||||
type Decoded = CheckMetadataHashMode;
|
||||
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
|
||||
identifier == "CheckMetadataHash"
|
||||
}
|
||||
}
|
||||
|
||||
/// Is metadata checking enabled or disabled?
|
||||
// Dev note: The "Disabled" and "Enabled" variant names match those that the
|
||||
// signed extension will be encoded with, in order that DecodeAsType will work
|
||||
// properly.
|
||||
#[derive(Copy, Clone, Debug, DecodeAsType)]
|
||||
pub enum CheckMetadataHashMode {
|
||||
/// No hash was provided in the signer payload.
|
||||
Disabled,
|
||||
/// A hash was provided in the signer payload.
|
||||
Enabled,
|
||||
}
|
||||
|
||||
impl CheckMetadataHashMode {
|
||||
/// Is metadata checking enabled or disabled for this transaction?
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
match self {
|
||||
CheckMetadataHashMode::Disabled => false,
|
||||
CheckMetadataHashMode::Enabled => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The [`CheckSpecVersion`] signed extension.
|
||||
pub struct CheckSpecVersion(u32);
|
||||
|
||||
@@ -380,7 +435,7 @@ impl<T: Config> SignedExtension<T> for ChargeTransactionPayment {
|
||||
/// ones are actually required for the chain in the correct order, ignoring the rest. This
|
||||
/// is a sensible default, and allows for a single configuration to work across multiple chains.
|
||||
pub struct AnyOf<T, Params> {
|
||||
params: Vec<Box<dyn ExtrinsicParamsEncoder>>,
|
||||
params: Vec<Box<dyn ExtrinsicParamsEncoder + Send + 'static>>,
|
||||
_marker: core::marker::PhantomData<(T, Params)>,
|
||||
}
|
||||
|
||||
@@ -415,7 +470,7 @@ macro_rules! impl_tuples {
|
||||
// Break and record as soon as we find a match:
|
||||
if $ident::matches(e.identifier(), e.extra_ty(), types) {
|
||||
let ext = $ident::new(client, params.$index)?;
|
||||
let boxed_ext: Box<dyn ExtrinsicParamsEncoder> = Box::new(ext);
|
||||
let boxed_ext: Box<dyn ExtrinsicParamsEncoder + Send + 'static> = Box::new(ext);
|
||||
exts_by_index.insert(idx, boxed_ext);
|
||||
break
|
||||
}
|
||||
|
||||
+125
-55
@@ -4,134 +4,168 @@
|
||||
|
||||
//! The errors that can be emitted in this crate.
|
||||
|
||||
use core::fmt::Display;
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use alloc::string::String;
|
||||
use derive_more::{Display, From};
|
||||
use subxt_metadata::StorageHasher;
|
||||
|
||||
/// The error emitted when something goes wrong.
|
||||
#[derive(Debug, Display, From)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Codec error.
|
||||
#[display(fmt = "Scale codec error: {_0}")]
|
||||
Codec(codec::Error),
|
||||
/// Metadata error.
|
||||
#[display(fmt = "Metadata Error: {_0}")]
|
||||
Metadata(MetadataError),
|
||||
/// Storage address error.
|
||||
#[display(fmt = "Storage Error: {_0}")]
|
||||
StorageAddress(StorageAddressError),
|
||||
/// Error decoding to a [`crate::dynamic::Value`].
|
||||
#[display(fmt = "Error decoding into dynamic value: {_0}")]
|
||||
Decode(scale_decode::Error),
|
||||
/// Error encoding from a [`crate::dynamic::Value`].
|
||||
#[display(fmt = "Error encoding from dynamic value: {_0}")]
|
||||
Encode(scale_encode::Error),
|
||||
/// Error constructing the appropriate extrinsic params.
|
||||
#[display(fmt = "Extrinsic params error: {_0}")]
|
||||
ExtrinsicParams(ExtrinsicParamsError),
|
||||
/// Block body error.
|
||||
#[display(fmt = "Error working with block body: {_0}")]
|
||||
Block(BlockError),
|
||||
}
|
||||
|
||||
impl core::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
Error::Codec(e) => write!(f, "Scale codec error: {e}"),
|
||||
Error::Metadata(e) => write!(f, "Metadata Error: {e}"),
|
||||
Error::StorageAddress(e) => write!(f, "Storage Error: {e}"),
|
||||
Error::Decode(e) => write!(f, "Error decoding into dynamic value: {e}"),
|
||||
Error::Encode(e) => write!(f, "Error encoding from dynamic value: {e}"),
|
||||
Error::ExtrinsicParams(e) => write!(f, "Extrinsic params error: {e}"),
|
||||
Error::Block(e) => write!(f, "Error working with block_body: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl From<scale_decode::visitor::DecodeError> for Error {
|
||||
fn from(value: scale_decode::visitor::DecodeError) -> Self {
|
||||
Error::Decode(value.into())
|
||||
}
|
||||
}
|
||||
impl_from!(ExtrinsicParamsError => Error::ExtrinsicParams);
|
||||
impl_from!(BlockError => Error::Block);
|
||||
impl_from!(MetadataError => Error::Metadata);
|
||||
impl_from!(scale_decode::Error => Error::Decode);
|
||||
impl_from!(scale_decode::visitor::DecodeError => Error::Decode);
|
||||
impl_from!(scale_encode::Error => Error::Encode);
|
||||
impl_from!(StorageAddressError => Error::StorageAddress);
|
||||
impl_from!(codec::Error => Error::Codec);
|
||||
|
||||
/// Block error
|
||||
#[derive(Clone, Debug, Display, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum BlockError {
|
||||
/// Extrinsic type ID cannot be resolved with the provided metadata.
|
||||
#[display(
|
||||
fmt = "Extrinsic type ID cannot be resolved with the provided metadata. Make sure this is a valid metadata"
|
||||
)]
|
||||
MissingType,
|
||||
/// Unsupported signature.
|
||||
#[display(fmt = "Unsupported extrinsic version, only version 4 is supported currently")]
|
||||
/// The extrinsic has an unsupported version.
|
||||
UnsupportedVersion(u8),
|
||||
/// Decoding error.
|
||||
#[display(fmt = "Cannot decode extrinsic: {_0}")]
|
||||
DecodingError(codec::Error),
|
||||
}
|
||||
|
||||
impl Display for BlockError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
BlockError::MissingType => write!(f, "Extrinsic type ID cannot be resolved with the provided metadata. Make sure this is a valid metadata"),
|
||||
BlockError::UnsupportedVersion(_) => write!(f, "Unsupported extrinsic version, only version 4 is supported currently"),
|
||||
BlockError::DecodingError(e) => write!(f, "Cannot decode extrinsic: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for BlockError {}
|
||||
|
||||
/// Something went wrong trying to access details in the metadata.
|
||||
#[derive(Clone, Debug, PartialEq, Display)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum MetadataError {
|
||||
/// The DispatchError type isn't available in the metadata
|
||||
#[display(fmt = "The DispatchError type isn't available")]
|
||||
DispatchErrorNotFound,
|
||||
/// Type not found in metadata.
|
||||
#[display(fmt = "Type with ID {_0} not found")]
|
||||
TypeNotFound(u32),
|
||||
/// Pallet not found (index).
|
||||
#[display(fmt = "Pallet with index {_0} not found")]
|
||||
PalletIndexNotFound(u8),
|
||||
/// Pallet not found (name).
|
||||
#[display(fmt = "Pallet with name {_0} not found")]
|
||||
PalletNameNotFound(String),
|
||||
/// Variant not found.
|
||||
#[display(fmt = "Variant with index {_0} not found")]
|
||||
VariantIndexNotFound(u8),
|
||||
/// Constant not found.
|
||||
#[display(fmt = "Constant with name {_0} not found")]
|
||||
ConstantNameNotFound(String),
|
||||
/// Call not found.
|
||||
#[display(fmt = "Call with name {_0} not found")]
|
||||
CallNameNotFound(String),
|
||||
/// Runtime trait not found.
|
||||
#[display(fmt = "Runtime trait with name {_0} not found")]
|
||||
RuntimeTraitNotFound(String),
|
||||
/// Runtime method not found.
|
||||
#[display(fmt = "Runtime method with name {_0} not found")]
|
||||
RuntimeMethodNotFound(String),
|
||||
/// Call type not found in metadata.
|
||||
#[display(fmt = "Call type not found in pallet with index {_0}")]
|
||||
CallTypeNotFoundInPallet(u8),
|
||||
/// Event type not found in metadata.
|
||||
#[display(fmt = "Event type not found in pallet with index {_0}")]
|
||||
EventTypeNotFoundInPallet(u8),
|
||||
/// Storage details not found in metadata.
|
||||
#[display(fmt = "Storage details not found in pallet with name {_0}")]
|
||||
StorageNotFoundInPallet(String),
|
||||
/// Storage entry not found.
|
||||
#[display(fmt = "Storage entry {_0} not found")]
|
||||
StorageEntryNotFound(String),
|
||||
/// The generated interface used is not compatible with the node.
|
||||
#[display(fmt = "The generated code is not compatible with the node")]
|
||||
IncompatibleCodegen,
|
||||
/// Custom value not found.
|
||||
#[display(fmt = "Custom value with name {_0} not found")]
|
||||
CustomValueNameNotFound(String),
|
||||
}
|
||||
impl Display for MetadataError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
MetadataError::DispatchErrorNotFound => {
|
||||
write!(f, "The DispatchError type isn't available")
|
||||
}
|
||||
MetadataError::TypeNotFound(e) => write!(f, "Type with ID {e} not found"),
|
||||
MetadataError::PalletIndexNotFound(e) => write!(f, "Pallet with index {e} not found"),
|
||||
MetadataError::PalletNameNotFound(e) => write!(f, "Pallet with name {e} not found"),
|
||||
MetadataError::VariantIndexNotFound(e) => write!(f, "Variant with index {e} not found"),
|
||||
MetadataError::ConstantNameNotFound(e) => write!(f, "Constant with name {e} not found"),
|
||||
MetadataError::CallNameNotFound(e) => write!(f, "Call with name {e} not found"),
|
||||
MetadataError::RuntimeTraitNotFound(e) => {
|
||||
write!(f, "Runtime trait with name {e} not found")
|
||||
}
|
||||
MetadataError::RuntimeMethodNotFound(e) => {
|
||||
write!(f, "Runtime method with name {e} not found")
|
||||
}
|
||||
MetadataError::CallTypeNotFoundInPallet(e) => {
|
||||
write!(f, "Call type not found in pallet with index {e}")
|
||||
}
|
||||
MetadataError::EventTypeNotFoundInPallet(e) => {
|
||||
write!(f, "Event type not found in pallet with index {e}")
|
||||
}
|
||||
MetadataError::StorageNotFoundInPallet(e) => {
|
||||
write!(f, "Storage details not found in pallet with name {e}")
|
||||
}
|
||||
MetadataError::StorageEntryNotFound(e) => write!(f, "Storage entry {e} not found"),
|
||||
MetadataError::IncompatibleCodegen => {
|
||||
write!(f, "The generated code is not compatible with the node")
|
||||
}
|
||||
MetadataError::CustomValueNameNotFound(e) => {
|
||||
write!(f, "Custom value with name {e} not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for MetadataError {}
|
||||
|
||||
/// Something went wrong trying to encode or decode a storage address.
|
||||
#[derive(Clone, Debug, Display)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum StorageAddressError {
|
||||
/// Storage lookup does not have the expected number of keys.
|
||||
#[display(fmt = "Storage lookup requires {expected} keys but more keys have been provided.")]
|
||||
TooManyKeys {
|
||||
/// The number of keys provided in the storage address.
|
||||
expected: usize,
|
||||
},
|
||||
/// This storage entry in the metadata does not have the correct number of hashers to fields.
|
||||
#[display(
|
||||
fmt = "Storage entry in metadata does not have the correct number of hashers to fields"
|
||||
)]
|
||||
WrongNumberOfHashers {
|
||||
/// The number of hashers in the metadata for this storage entry.
|
||||
hashers: usize,
|
||||
@@ -139,20 +173,12 @@ pub enum StorageAddressError {
|
||||
fields: usize,
|
||||
},
|
||||
/// We weren't given enough bytes to decode the storage address/key.
|
||||
#[display(fmt = "Not enough remaining bytes to decode the storage address/key")]
|
||||
NotEnoughBytes,
|
||||
/// We have leftover bytes after decoding the storage address.
|
||||
#[display(fmt = "We have leftover bytes after decoding the storage address")]
|
||||
TooManyBytes,
|
||||
/// The bytes of a storage address are not the expected address for decoding the storage keys of the address.
|
||||
#[display(
|
||||
fmt = "Storage address bytes are not the expected format. Addresses need to be at least 16 bytes (pallet ++ entry) and follow a structure given by the hashers defined in the metadata"
|
||||
)]
|
||||
UnexpectedAddressBytes,
|
||||
/// An invalid hasher was used to reconstruct a value from a chunk of bytes that is part of a storage address. Hashers where the hash does not contain the original value are invalid for this purpose.
|
||||
#[display(
|
||||
fmt = "An invalid hasher was used to reconstruct a value with type ID {ty_id} from a hash formed by a {hasher:?} hasher. This is only possible for concat-style hashers or the identity hasher"
|
||||
)]
|
||||
HasherCannotReconstructKey {
|
||||
/// Type id of the key's type.
|
||||
ty_id: u32,
|
||||
@@ -161,17 +187,47 @@ pub enum StorageAddressError {
|
||||
},
|
||||
}
|
||||
|
||||
impl Display for StorageAddressError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
StorageAddressError::TooManyKeys { expected } => write!(
|
||||
f,
|
||||
"Storage lookup requires {expected} keys but more keys have been provided."
|
||||
),
|
||||
StorageAddressError::WrongNumberOfHashers { .. } => write!(
|
||||
f,
|
||||
"Storage entry in metadata does not have the correct number of hashers to fields"
|
||||
),
|
||||
StorageAddressError::NotEnoughBytes => write!(
|
||||
f,
|
||||
"Not enough remaining bytes to decode the storage address/key"
|
||||
),
|
||||
StorageAddressError::TooManyBytes => write!(
|
||||
f,
|
||||
"We have leftover bytes after decoding the storage address"
|
||||
),
|
||||
StorageAddressError::UnexpectedAddressBytes => write!(
|
||||
f,
|
||||
"Storage address bytes are not the expected format. Addresses need to be at least 16 bytes (pallet ++ entry) and follow a structure given by the hashers defined in the metadata"
|
||||
),
|
||||
StorageAddressError::HasherCannotReconstructKey { ty_id, hasher } => write!(
|
||||
f,
|
||||
"An invalid hasher was used to reconstruct a value with type ID {ty_id} from a hash formed by a {hasher:?} hasher. This is only possible for concat-style hashers or the identity hasher"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for StorageAddressError {}
|
||||
|
||||
/// An error that can be emitted when trying to construct an instance of [`crate::config::ExtrinsicParams`],
|
||||
/// encode data from the instance, or match on signed extensions.
|
||||
#[derive(Display, Debug)]
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum ExtrinsicParamsError {
|
||||
/// Cannot find a type id in the metadata. The context provides some additional
|
||||
/// information about the source of the error (eg the signed extension name).
|
||||
#[display(fmt = "Cannot find type id '{type_id} in the metadata (context: {context})")]
|
||||
MissingTypeId {
|
||||
/// Type ID.
|
||||
type_id: u32,
|
||||
@@ -179,15 +235,29 @@ pub enum ExtrinsicParamsError {
|
||||
context: &'static str,
|
||||
},
|
||||
/// A signed extension in use on some chain was not provided.
|
||||
#[display(
|
||||
fmt = "The chain expects a signed extension with the name {_0}, but we did not provide one"
|
||||
)]
|
||||
UnknownSignedExtension(String),
|
||||
/// Some custom error.
|
||||
#[display(fmt = "Error constructing extrinsic parameters: {_0}")]
|
||||
Custom(Box<dyn CustomError>),
|
||||
}
|
||||
|
||||
impl Display for ExtrinsicParamsError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
ExtrinsicParamsError::MissingTypeId { type_id, context } => write!(
|
||||
f,
|
||||
"Cannot find type id '{type_id} in the metadata (context: {context})"
|
||||
),
|
||||
ExtrinsicParamsError::UnknownSignedExtension(e) => write!(
|
||||
f,
|
||||
"The chain expects a signed extension with the name {e}, but we did not provide one"
|
||||
),
|
||||
ExtrinsicParamsError::Custom(e) => {
|
||||
write!(f, "Error constructing extrinsic parameters: {e}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Anything implementing this trait can be used in [`ExtrinsicParamsError::Custom`].
|
||||
#[cfg(feature = "std")]
|
||||
pub trait CustomError: std::error::Error + Send + Sync + 'static {}
|
||||
|
||||
@@ -18,4 +18,14 @@ macro_rules! cfg_substrate_compat {
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_from {
|
||||
($module_path:path => $delegate_ty:ident :: $variant:ident) => {
|
||||
impl From<$module_path> for $delegate_ty {
|
||||
fn from(val: $module_path) -> Self {
|
||||
$delegate_ty::$variant(val.into())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use {cfg_feature, cfg_substrate_compat};
|
||||
|
||||
@@ -182,7 +182,7 @@ impl<K: codec::Encode + ?Sized> StaticStorageKey<K> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: codec::Decode + ?Sized> StaticStorageKey<K> {
|
||||
impl<K: codec::Decode> StaticStorageKey<K> {
|
||||
/// Decodes the encoded inner bytes into the type `K`.
|
||||
pub fn decoded(&self) -> Result<K, Error> {
|
||||
let decoded = K::decode(&mut self.bytes())?;
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::error::MetadataError;
|
||||
use crate::metadata::Metadata;
|
||||
use crate::Error;
|
||||
use alloc::borrow::{Cow, ToOwned};
|
||||
use alloc::boxed::Box;
|
||||
use alloc::string::String;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
@@ -38,6 +39,32 @@ pub trait Payload {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! boxed_payload {
|
||||
($ty:path) => {
|
||||
impl<T: Payload + ?Sized> Payload for $ty {
|
||||
fn encode_call_data_to(
|
||||
&self,
|
||||
metadata: &Metadata,
|
||||
out: &mut Vec<u8>,
|
||||
) -> Result<(), Error> {
|
||||
self.as_ref().encode_call_data_to(metadata, out)
|
||||
}
|
||||
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
|
||||
self.as_ref().encode_call_data(metadata)
|
||||
}
|
||||
fn validation_details(&self) -> Option<ValidationDetails<'_>> {
|
||||
self.as_ref().validation_details()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
boxed_payload!(Box<T>);
|
||||
#[cfg(feature = "std")]
|
||||
boxed_payload!(std::sync::Arc<T>);
|
||||
#[cfg(feature = "std")]
|
||||
boxed_payload!(std::rc::Rc<T>);
|
||||
|
||||
/// Details required to validate the shape of a transaction payload against some metadata.
|
||||
pub struct ValidationDetails<'a> {
|
||||
/// The pallet name.
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
//! 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 core::fmt::Display;
|
||||
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Decode, Encode};
|
||||
use derive_more::Display;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A 32-byte cryptographic identifier. This is a simplified version of Substrate's
|
||||
@@ -105,19 +106,26 @@ impl AccountId32 {
|
||||
}
|
||||
|
||||
/// An error obtained from trying to interpret an SS58 encoded string into an AccountId32
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug, Display)]
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum FromSs58Error {
|
||||
#[display(fmt = "Base 58 requirement is violated")]
|
||||
BadBase58,
|
||||
#[display(fmt = "Length is bad")]
|
||||
BadLength,
|
||||
#[display(fmt = "Invalid checksum")]
|
||||
InvalidChecksum,
|
||||
#[display(fmt = "Invalid SS58 prefix byte.")]
|
||||
InvalidPrefix,
|
||||
}
|
||||
|
||||
impl Display for FromSs58Error {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
FromSs58Error::BadBase58 => write!(f, "Base 58 requirement is violated"),
|
||||
FromSs58Error::BadLength => write!(f, "Length is bad"),
|
||||
FromSs58Error::InvalidChecksum => write!(f, "Invalid checksum"),
|
||||
FromSs58Error::InvalidPrefix => write!(f, "Invalid SS58 prefix byte."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for FromSs58Error {}
|
||||
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! `AccountId20` is a representation of Ethereum address derived from hashing the public key.
|
||||
|
||||
use core::fmt::Display;
|
||||
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use codec::{Decode, Encode};
|
||||
use keccak_hash::keccak;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
Encode,
|
||||
Decode,
|
||||
Debug,
|
||||
scale_encode::EncodeAsType,
|
||||
scale_decode::DecodeAsType,
|
||||
scale_info::TypeInfo,
|
||||
)]
|
||||
/// Ethereum-compatible `AccountId`.
|
||||
pub struct AccountId20(pub [u8; 20]);
|
||||
|
||||
impl AsRef<[u8]> for AccountId20 {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8; 20]> for AccountId20 {
|
||||
fn as_ref(&self) -> &[u8; 20] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 20]> for AccountId20 {
|
||||
fn from(x: [u8; 20]) -> Self {
|
||||
AccountId20(x)
|
||||
}
|
||||
}
|
||||
|
||||
impl AccountId20 {
|
||||
/// Convert to a public key hash
|
||||
pub fn checksum(&self) -> String {
|
||||
let hex_address = hex::encode(self.0);
|
||||
let hash = keccak(hex_address.as_bytes());
|
||||
|
||||
let mut checksum_address = String::with_capacity(42);
|
||||
checksum_address.push_str("0x");
|
||||
|
||||
for (i, ch) in hex_address.chars().enumerate() {
|
||||
// Get the corresponding nibble from the hash
|
||||
let nibble = hash[i / 2] >> (if i % 2 == 0 { 4 } else { 0 }) & 0xf;
|
||||
|
||||
if nibble >= 8 {
|
||||
checksum_address.push(ch.to_ascii_uppercase());
|
||||
} else {
|
||||
checksum_address.push(ch);
|
||||
}
|
||||
}
|
||||
|
||||
checksum_address
|
||||
}
|
||||
}
|
||||
|
||||
/// An error obtained from trying to interpret a hex encoded string into an AccountId20
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum FromChecksumError {
|
||||
BadLength,
|
||||
InvalidChecksum,
|
||||
InvalidPrefix,
|
||||
}
|
||||
|
||||
impl Display for FromChecksumError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
FromChecksumError::BadLength => write!(f, "Length is bad"),
|
||||
FromChecksumError::InvalidChecksum => write!(f, "Invalid checksum"),
|
||||
FromChecksumError::InvalidPrefix => write!(f, "Invalid checksum prefix byte."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for FromChecksumError {}
|
||||
|
||||
impl Serialize for AccountId20 {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.checksum())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for AccountId20 {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
String::deserialize(deserializer)?
|
||||
.parse::<AccountId20>()
|
||||
.map_err(|e| serde::de::Error::custom(format!("{e:?}")))
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for AccountId20 {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
write!(f, "{}", self.checksum())
|
||||
}
|
||||
}
|
||||
|
||||
impl core::str::FromStr for AccountId20 {
|
||||
type Err = FromChecksumError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s.len() != 42 {
|
||||
return Err(FromChecksumError::BadLength);
|
||||
}
|
||||
if !s.starts_with("0x") {
|
||||
return Err(FromChecksumError::InvalidPrefix);
|
||||
}
|
||||
hex::decode(&s.as_bytes()[2..])
|
||||
.map_err(|_| FromChecksumError::InvalidChecksum)?
|
||||
.try_into()
|
||||
.map(AccountId20)
|
||||
.map_err(|_| FromChecksumError::BadLength)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn deserialisation() {
|
||||
let key_hashes = vec![
|
||||
"0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac",
|
||||
"0x3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0",
|
||||
"0x798d4Ba9baf0064Ec19eB4F0a1a45785ae9D6DFc",
|
||||
"0x773539d4Ac0e786233D90A233654ccEE26a613D9",
|
||||
"0xFf64d3F6efE2317EE2807d223a0Bdc4c0c49dfDB",
|
||||
"0xC0F0f4ab324C46e55D02D0033343B4Be8A55532d",
|
||||
];
|
||||
|
||||
for key_hash in key_hashes {
|
||||
let parsed: AccountId20 = key_hash.parse().expect("Failed to parse");
|
||||
|
||||
let encoded = parsed.checksum();
|
||||
|
||||
// `encoded` should be equal to the initial key_hash
|
||||
assert_eq!(encoded, key_hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
//! Miscellaneous utility helpers.
|
||||
|
||||
mod account_id;
|
||||
mod account_id20;
|
||||
pub mod bits;
|
||||
mod era;
|
||||
mod multi_address;
|
||||
@@ -21,6 +22,7 @@ use codec::{Compact, Decode, Encode};
|
||||
use derive_where::derive_where;
|
||||
|
||||
pub use account_id::AccountId32;
|
||||
pub use account_id20::AccountId20;
|
||||
pub use era::Era;
|
||||
pub use multi_address::MultiAddress;
|
||||
pub use multi_signature::MultiSignature;
|
||||
|
||||
Reference in New Issue
Block a user