Files
pezkuwi-subxt/core/src/utils/wrapper_opaque.rs
T
Tadeo Hepperle a0cb14aa4f subxt-core crate (#1466)
* start migrating, broken

* first iteration of updating

* fmt and clippy

* add Composite<u32> decoding via scale value patch

* bump scale type gen versions

* fix decoding with new scale decode

* compiling with changed deps

* core utils, condig, client, metadata

* core crate compiling

* signer crate no once lock

* add core to no-std-tests, change imports

* broken commit, start pulling everything together in subxt

* port more things to subxt

* events in core crate, extrinsics sadly much more difficult

* almost all examples pass again

* dynamic values fix in examples

* fix no std issue and fmt

* remove unused dependencies

* fix lightclient impl

* runtime version refactor

* formatting and addressing nits

* more comments addressed

* update wasm example and no-std-signer tests

* other nits and error impl on signer errors

* fix feature flag

* fix runtime version refactor

* fix doc links

* fix integration tests

* fix feature flag gated client state

* fix native feature in CI

* fix lightclient utils

* make imports more lean in subxt-core

* integrate changes from subxt-core imports into subxt

* other changes in subxt simplify imports more

* fix  and docs

* doc false for cli

* fix clippy

* remove events block hash in tests

* codegen no-std support in generated code

* export alloc crate for no-std codegen

* fix doc test

* implement James comments

* remove std traits, use core traits instead

* address nits

* remove unusued dep in no-std tests

* fix Box import in no_std

* sp-crypto-hashing instead of sp-core-hashing

* bump scale-typegen, add no std codegen tests

* fix some things

* replace unmaintained derivative with derive_where to remove non-canonical warnings

* fmt

* remove unused dep

* fix deps

* update artifacts to fix type ID mismatches

* bump to latest scale-typegen

---------

Co-authored-by: James Wilson <james@jsdw.me>
2024-03-27 08:55:08 +00:00

232 lines
8.1 KiB
Rust

// 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 derive_where::derive_where;
use scale_decode::{ext::scale_type_resolver::visitor, IntoVisitor, TypeResolver, Visitor};
use scale_encode::EncodeAsType;
use alloc::format;
use alloc::vec::Vec;
/// A wrapper for any type `T` which implement encode/decode in a way compatible with `Vec<u8>`.
/// [`WrapperKeepOpaque`] stores the type only in its opaque format, aka as a `Vec<u8>`. 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<u8>`, but we need a custom [`EncodeAsType`]
// and [`Visitor`] implementation to encode and decode based on TypeInfo.
#[derive(Encode, Decode)]
#[derive_where(Debug, Clone, PartialEq, Eq, Default, Hash)]
pub struct WrapperKeepOpaque<T> {
data: Vec<u8>,
_phantom: PhantomDataSendSync<T>,
}
impl<T> WrapperKeepOpaque<T> {
/// Try to decode the wrapped type from the inner `data`.
///
/// Returns `None` if the decoding failed.
pub fn try_decode(&self) -> Option<T>
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<u8>) -> 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<T> EncodeAsType for WrapperKeepOpaque<T> {
fn encode_as_type_to<R: TypeResolver>(
&self,
type_id: &R::TypeId,
types: &R,
out: &mut Vec<u8>,
) -> Result<(), scale_encode::Error> {
use scale_encode::error::{Error, ErrorKind, Kind};
let visitor = visitor::new(out, |_, _| {
// Check that the target shape lines up: any other shape but composite is wrong.
Err(Error::new(ErrorKind::WrongShape {
actual: Kind::Struct,
expected_id: format!("{:?}", type_id),
}))
})
.visit_composite(|out, _fields| {
self.data.encode_to(out);
Ok(())
});
types
.resolve_type(type_id, visitor)
.map_err(|_| Error::new(ErrorKind::TypeNotFound(format!("{:?}", type_id))))?
}
}
pub struct WrapperKeepOpaqueVisitor<T, R>(core::marker::PhantomData<(T, R)>);
impl<T, R: TypeResolver> Visitor for WrapperKeepOpaqueVisitor<T, R> {
type Value<'scale, 'info> = WrapperKeepOpaque<T>;
type Error = scale_decode::Error;
type TypeResolver = R;
fn visit_composite<'scale, 'info>(
self,
value: &mut scale_decode::visitor::types::Composite<'scale, 'info, R>,
_type_id: &R::TypeId,
) -> Result<Self::Value<'scale, 'info>, Self::Error> {
use scale_decode::error::{Error, ErrorKind};
// TODO: When `scale-type-resolver` [provides struct names](https://github.com/paritytech/scale-type-resolver/issues/4), check that this struct name is `WrapperKeepOpaque`
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::<u32>::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<T> IntoVisitor for WrapperKeepOpaque<T> {
type AnyVisitor<R: TypeResolver> = WrapperKeepOpaqueVisitor<T, R>;
fn into_visitor<R: TypeResolver>() -> WrapperKeepOpaqueVisitor<T, R> {
WrapperKeepOpaqueVisitor(core::marker::PhantomData)
}
}
#[cfg(test)]
mod test {
use scale_decode::DecodeAsType;
use alloc::vec;
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<T: scale_info::TypeInfo + 'static> scale_info::TypeInfo for WrapperKeepOpaque<T> {
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::<T>()))])
.composite(
Fields::unnamed()
.field(|f| f.compact::<u32>())
.field(|f| f.ty::<T>().type_name("T")),
)
}
}
/// Given a type definition, return type ID and registry representing it.
fn make_type<T: scale_info::TypeInfo + 'static>() -> (u32, scale_info::PortableRegistry) {
let m = scale_info::MetaType::new::<T>();
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: T)
where
T: EncodeAsType
+ DecodeAsType
+ Encode
+ Decode
+ PartialEq
+ core::fmt::Debug
+ scale_info::TypeInfo
+ 'static,
{
let (type_id, types) = make_type::<T>();
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]));
}
}