subxt_core/utils/
wrapper_opaque.rs

1// Copyright 2019-2024 Parity Technologies (UK) Ltd.
2// This file is dual-licensed as Apache-2.0 or GPL-3.0.
3// see LICENSE for license details.
4
5use super::PhantomDataSendSync;
6use codec::{Compact, Decode, DecodeAll, Encode};
7use derive_where::derive_where;
8use scale_decode::{IntoVisitor, TypeResolver, Visitor, ext::scale_type_resolver::visitor};
9use scale_encode::EncodeAsType;
10
11use alloc::format;
12use alloc::vec::Vec;
13
14/// A wrapper for any type `T` which implement encode/decode in a way compatible with `Vec<u8>`.
15/// [`WrapperKeepOpaque`] stores the type only in its opaque format, aka as a `Vec<u8>`. To
16/// access the real type `T` [`Self::try_decode`] needs to be used.
17// Dev notes:
18//
19// - This is adapted from [here](https://github.com/paritytech/substrate/blob/master/frame/support/src/traits/misc.rs).
20// - The encoded bytes will be a compact encoded length followed by that number of bytes.
21// - However, the TypeInfo describes the type as a composite with first a compact encoded length and next the type itself.
22//  [`Encode`] and [`Decode`] impls will "just work" to take this into a `Vec<u8>`, but we need a custom [`EncodeAsType`]
23//  and [`Visitor`] implementation to encode and decode based on TypeInfo.
24#[derive(Encode, Decode)]
25#[derive_where(Debug, Clone, PartialEq, Eq, Default, Hash)]
26pub struct WrapperKeepOpaque<T> {
27    data: Vec<u8>,
28    _phantom: PhantomDataSendSync<T>,
29}
30
31impl<T> WrapperKeepOpaque<T> {
32    /// Try to decode the wrapped type from the inner `data`.
33    ///
34    /// Returns `None` if the decoding failed.
35    pub fn try_decode(&self) -> Option<T>
36    where
37        T: Decode,
38    {
39        T::decode_all(&mut &self.data[..]).ok()
40    }
41
42    /// Returns the length of the encoded `T`.
43    pub fn encoded_len(&self) -> usize {
44        self.data.len()
45    }
46
47    /// Returns the encoded data.
48    pub fn encoded(&self) -> &[u8] {
49        &self.data
50    }
51
52    /// Create from the given encoded `data`.
53    pub fn from_encoded(data: Vec<u8>) -> Self {
54        Self {
55            data,
56            _phantom: PhantomDataSendSync::new(),
57        }
58    }
59
60    /// Create from some raw value by encoding it.
61    pub fn from_value(value: T) -> Self
62    where
63        T: Encode,
64    {
65        Self {
66            data: value.encode(),
67            _phantom: PhantomDataSendSync::new(),
68        }
69    }
70}
71
72impl<T> EncodeAsType for WrapperKeepOpaque<T> {
73    fn encode_as_type_to<R: TypeResolver>(
74        &self,
75        type_id: R::TypeId,
76        types: &R,
77        out: &mut Vec<u8>,
78    ) -> Result<(), scale_encode::Error> {
79        use scale_encode::error::{Error, ErrorKind, Kind};
80
81        let ctx = (type_id.clone(), out);
82        let visitor = visitor::new(ctx, |(type_id, _out), _| {
83            // Check that the target shape lines up: any other shape but composite is wrong.
84            Err(Error::new(ErrorKind::WrongShape {
85                actual: Kind::Struct,
86                expected_id: format!("{type_id:?}"),
87            }))
88        })
89        .visit_composite(|(_type_id, out), _path, _fields| {
90            self.data.encode_to(out);
91            Ok(())
92        });
93
94        types
95            .resolve_type(type_id.clone(), visitor)
96            .map_err(|_| Error::new(ErrorKind::TypeNotFound(format!("{type_id:?}"))))?
97    }
98}
99
100pub struct WrapperKeepOpaqueVisitor<T, R>(core::marker::PhantomData<(T, R)>);
101impl<T, R: TypeResolver> Visitor for WrapperKeepOpaqueVisitor<T, R> {
102    type Value<'scale, 'info> = WrapperKeepOpaque<T>;
103    type Error = scale_decode::Error;
104    type TypeResolver = R;
105
106    fn visit_composite<'scale, 'info>(
107        self,
108        value: &mut scale_decode::visitor::types::Composite<'scale, 'info, R>,
109        _type_id: R::TypeId,
110    ) -> Result<Self::Value<'scale, 'info>, Self::Error> {
111        use scale_decode::error::{Error, ErrorKind};
112        use scale_decode::visitor::DecodeError;
113
114        if value.name() != Some("WrapperKeepOpaque") {
115            return Err(Error::new(ErrorKind::VisitorDecodeError(
116                DecodeError::TypeResolvingError(format!(
117                    "Expected a type named 'WrapperKeepOpaque', got: {:?}",
118                    value.name()
119                )),
120            )));
121        }
122
123        if value.remaining() != 2 {
124            return Err(Error::new(ErrorKind::WrongLength {
125                actual_len: value.remaining(),
126                expected_len: 2,
127            }));
128        }
129
130        // The field to decode is a compact len followed by bytes. Decode the length, then grab the bytes.
131        let Compact(len) = value
132            .decode_item(Compact::<u32>::into_visitor())
133            .expect("length checked")?;
134        let field = value.next().expect("length checked")?;
135
136        // Sanity check that the compact length we decoded lines up with the number of bytes encoded in the next field.
137        if field.bytes().len() != len as usize {
138            return Err(Error::custom_str(
139                "WrapperTypeKeepOpaque compact encoded length doesn't line up with encoded byte len",
140            ));
141        }
142
143        Ok(WrapperKeepOpaque {
144            data: field.bytes().to_vec(),
145            _phantom: PhantomDataSendSync::new(),
146        })
147    }
148}
149
150impl<T> IntoVisitor for WrapperKeepOpaque<T> {
151    type AnyVisitor<R: TypeResolver> = WrapperKeepOpaqueVisitor<T, R>;
152    fn into_visitor<R: TypeResolver>() -> WrapperKeepOpaqueVisitor<T, R> {
153        WrapperKeepOpaqueVisitor(core::marker::PhantomData)
154    }
155}
156
157#[cfg(test)]
158mod test {
159    use scale_decode::DecodeAsType;
160
161    use alloc::vec;
162
163    use super::*;
164
165    // Copied from https://github.com/paritytech/substrate/blob/master/frame/support/src/traits/misc.rs
166    // and used for tests to check that we can work with the expected TypeInfo without needing to import
167    // the frame_support crate, which has quite a lot of dependencies.
168    impl<T: scale_info::TypeInfo + 'static> scale_info::TypeInfo for WrapperKeepOpaque<T> {
169        type Identity = Self;
170        fn type_info() -> scale_info::Type {
171            use scale_info::{Path, Type, TypeParameter, build::Fields, meta_type};
172
173            Type::builder()
174                .path(Path::new("WrapperKeepOpaque", module_path!()))
175                .type_params(vec![TypeParameter::new("T", Some(meta_type::<T>()))])
176                .composite(
177                    Fields::unnamed()
178                        .field(|f| f.compact::<u32>())
179                        .field(|f| f.ty::<T>().type_name("T")),
180                )
181        }
182    }
183
184    /// Given a type definition, return type ID and registry representing it.
185    fn make_type<T: scale_info::TypeInfo + 'static>() -> (u32, scale_info::PortableRegistry) {
186        let m = scale_info::MetaType::new::<T>();
187        let mut types = scale_info::Registry::new();
188        let id = types.register_type(&m);
189        let portable_registry: scale_info::PortableRegistry = types.into();
190        (id.id, portable_registry)
191    }
192
193    fn roundtrips_like_scale_codec<T>(t: T)
194    where
195        T: EncodeAsType
196            + DecodeAsType
197            + Encode
198            + Decode
199            + PartialEq
200            + core::fmt::Debug
201            + scale_info::TypeInfo
202            + 'static,
203    {
204        let (type_id, types) = make_type::<T>();
205
206        let scale_codec_encoded = t.encode();
207        let encode_as_type_encoded = t.encode_as_type(type_id, &types).unwrap();
208
209        assert_eq!(
210            scale_codec_encoded, encode_as_type_encoded,
211            "encoded bytes should match"
212        );
213
214        let decode_as_type_bytes = &mut &*scale_codec_encoded;
215        let decoded_as_type = T::decode_as_type(decode_as_type_bytes, type_id, &types)
216            .expect("decode-as-type decodes");
217
218        let decode_scale_codec_bytes = &mut &*scale_codec_encoded;
219        let decoded_scale_codec = T::decode(decode_scale_codec_bytes).expect("scale-codec decodes");
220
221        assert!(
222            decode_as_type_bytes.is_empty(),
223            "no bytes should remain in decode-as-type impl"
224        );
225        assert!(
226            decode_scale_codec_bytes.is_empty(),
227            "no bytes should remain in codec-decode impl"
228        );
229
230        assert_eq!(
231            decoded_as_type, decoded_scale_codec,
232            "decoded values should match"
233        );
234    }
235
236    #[test]
237    fn wrapper_keep_opaque_roundtrips_ok() {
238        roundtrips_like_scale_codec(WrapperKeepOpaque::from_value(123u64));
239        roundtrips_like_scale_codec(WrapperKeepOpaque::from_value(true));
240        roundtrips_like_scale_codec(WrapperKeepOpaque::from_value(vec![1u8, 2, 3, 4]));
241    }
242}