Skip to main content

frame_decode/methods/
extrinsic_encoder.rs

1// Copyright (C) 2022-2026 Parity Technologies (UK) Ltd. (admin@parity.io)
2// This file is a part of the frame-decode crate.
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//         http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16mod transaction_extension;
17mod transaction_extensions;
18use super::extrinsic_type_info::{
19    ExtrinsicCallInfo, ExtrinsicExtensionInfo, ExtrinsicInfoError, ExtrinsicSignatureInfo,
20    ExtrinsicTypeInfo,
21};
22use alloc::vec::Vec;
23use parity_scale_codec::Encode;
24use scale_encode::{EncodeAsFields, EncodeAsType};
25use scale_type_resolver::{Field, TypeResolver};
26
27pub use transaction_extension::{TransactionExtension, TransactionExtensionError};
28pub use transaction_extensions::{TransactionExtensions, TransactionExtensionsError};
29
30/// An error returned trying to encode extrinsic call data.
31#[non_exhaustive]
32#[allow(missing_docs)]
33#[derive(Debug, thiserror::Error)]
34pub enum ExtrinsicEncodeError {
35    #[error("Cannot get extrinsic info: {0}")]
36    CannotGetInfo(ExtrinsicInfoError<'static>),
37    #[error("Extrinsic encoding failed: cannot encode call data: {0}")]
38    CannotEncodeCallData(scale_encode::Error),
39    #[error("Extrinsic encoding failed: cannot encode address: {0}")]
40    CannotEncodeAddress(scale_encode::Error),
41    #[error("Extrinsic encoding failed: cannot encode signature: {0}")]
42    CannotEncodeSignature(scale_encode::Error),
43    #[error("Extrinsic encoding failed: cannot encode transaction extensions: {0}")]
44    TransactionExtensions(TransactionExtensionsError),
45    #[error(
46        "Extrinsic encoding failed: cannot find a transaction extensions version which relies only on the transaction extensions we were given."
47    )]
48    CannotFindGoodExtensionVersion,
49}
50
51/// Encode a V4 unsigned extrinsic (also known as an inherent).
52///
53/// This is the same as [`encode_v4_unsigned_to`], but returns the encoded extrinsic as a `Vec<u8>`,
54/// rather than accepting a mutable output buffer.
55///
56/// # Example
57///
58/// ```rust
59/// use frame_decode::extrinsics::encode_v4_unsigned;
60/// use frame_metadata::RuntimeMetadata;
61/// use parity_scale_codec::Decode;
62///
63/// let metadata_bytes = std::fs::read("artifacts/metadata_10000000_9180.scale").unwrap();
64/// let RuntimeMetadata::V14(metadata) = RuntimeMetadata::decode(&mut &*metadata_bytes).unwrap() else { return };
65///
66/// // Encode a call to Timestamp.set with an argument.
67/// // The call_data type must implement `scale_encode::EncodeAsFields`.
68/// let call_data = scale_value::value!({
69///     now: 1234567890u64,
70/// });
71///
72/// let encoded = encode_v4_unsigned(
73///     "Timestamp",
74///     "set",
75///     &call_data,
76///     &metadata,
77///     &metadata.types,
78/// ).unwrap();
79/// ```
80pub fn encode_v4_unsigned<CallData, Info, Resolver>(
81    pallet_name: &str,
82    call_name: &str,
83    call_data: &CallData,
84    info: &Info,
85    type_resolver: &Resolver,
86) -> Result<Vec<u8>, ExtrinsicEncodeError>
87where
88    CallData: EncodeAsFields,
89    Resolver: TypeResolver<TypeId = Info::TypeId>,
90    Info: ExtrinsicTypeInfo,
91{
92    let mut out = Vec::new();
93    encode_v4_unsigned_to(
94        pallet_name,
95        call_name,
96        call_data,
97        info,
98        type_resolver,
99        &mut out,
100    )?;
101    Ok(out)
102}
103
104/// Encode a V4 unsigned extrinsic (also known as an inherent) to a provided output buffer.
105///
106/// This is the same as [`encode_v4_unsigned`], but writes the encoded extrinsic to the provided
107/// `Vec<u8>` rather than returning a new one.
108///
109/// # Example
110///
111/// ```rust
112/// use frame_decode::extrinsics::encode_v4_unsigned_to;
113/// use frame_metadata::RuntimeMetadata;
114/// use parity_scale_codec::Decode;
115///
116/// let metadata_bytes = std::fs::read("artifacts/metadata_10000000_9180.scale").unwrap();
117/// let RuntimeMetadata::V14(metadata) = RuntimeMetadata::decode(&mut &*metadata_bytes).unwrap() else { return };
118///
119/// // Encode a call to Timestamp.set with an argument.
120/// let call_data = scale_value::value!({
121///     now: 1234567890u64,
122/// });
123///
124/// let mut encoded = Vec::new();
125/// encode_v4_unsigned_to(
126///     "Timestamp",
127///     "set",
128///     &call_data,
129///     &metadata,
130///     &metadata.types,
131///     &mut encoded,
132/// ).unwrap();
133/// ```
134pub fn encode_v4_unsigned_to<CallData, Info, Resolver>(
135    pallet_name: &str,
136    call_name: &str,
137    call_data: &CallData,
138    info: &Info,
139    type_resolver: &Resolver,
140    out: &mut Vec<u8>,
141) -> Result<(), ExtrinsicEncodeError>
142where
143    CallData: EncodeAsFields,
144    Resolver: TypeResolver<TypeId = Info::TypeId>,
145    Info: ExtrinsicTypeInfo,
146{
147    let call_info = info
148        .extrinsic_call_info_by_name(pallet_name, call_name)
149        .map_err(|i| i.into_owned())
150        .map_err(ExtrinsicEncodeError::CannotGetInfo)?;
151
152    encode_v4_unsigned_with_info_to(call_data, type_resolver, &call_info, out)
153}
154
155/// Encode a V4 unsigned extrinsic (also known as an inherent) to a provided output buffer,
156/// using pre-computed call information.
157///
158/// Unlike [`encode_v4_unsigned_to`], which obtains the call info internally given the pallet and call names,
159/// this function takes the call info as an argument. This is useful if you already have the call info available,
160/// for example if you are encoding multiple extrinsics for the same call.
161pub fn encode_v4_unsigned_with_info_to<CallData, Resolver>(
162    call_data: &CallData,
163    type_resolver: &Resolver,
164    call_info: &ExtrinsicCallInfo<Resolver::TypeId>,
165    out: &mut Vec<u8>,
166) -> Result<(), ExtrinsicEncodeError>
167where
168    CallData: EncodeAsFields,
169    Resolver: TypeResolver,
170{
171    encode_unsigned_at_version_with_info_to(
172        call_data,
173        call_info,
174        type_resolver,
175        TransactionVersion::V4,
176        out,
177    )
178}
179
180/// Encode a V4 signed extrinsic, ready to submit.
181///
182/// A signed V4 extrinsic includes an address, signature, and transaction extensions (such as
183/// nonce and tip) alongside the call data. The signature should be computed over the signer
184/// payload, which can be obtained via [`encode_v4_signer_payload`].
185///
186/// This is the same as [`encode_v4_signed_to`], but returns the encoded extrinsic as a `Vec<u8>`,
187/// rather than accepting a mutable output buffer.
188///
189/// # Example
190///
191/// ```rust,ignore
192/// use frame_decode::extrinsics::{encode_v4_signed, TransactionExtensions};
193/// use frame_metadata::RuntimeMetadata;
194/// use parity_scale_codec::Decode;
195///
196/// let metadata_bytes = std::fs::read("artifacts/metadata_10000000_9180.scale").unwrap();
197/// let RuntimeMetadata::V14(metadata) = RuntimeMetadata::decode(&mut &*metadata_bytes).unwrap() else { return };
198///
199/// // The call data, address, signature, and transaction extensions must implement
200/// // the appropriate scale_encode traits.
201/// let call_data = /* ... */;
202/// let address = /* your address type */;
203/// let signature = /* your signature type */;
204/// let transaction_extensions = /* your TransactionExtensions impl */;
205///
206/// let encoded = encode_v4_signed(
207///     "Balances",
208///     "transfer_keep_alive",
209///     &call_data,
210///     &transaction_extensions,
211///     &address,
212///     &signature,
213///     &metadata,
214///     &metadata.types,
215/// ).unwrap();
216/// ```
217#[allow(clippy::too_many_arguments)]
218pub fn encode_v4_signed<CallData, Info, Resolver, Exts, Address, Signature>(
219    pallet_name: &str,
220    call_name: &str,
221    call_data: &CallData,
222    transaction_extensions: &Exts,
223    address: &Address,
224    signature: &Signature,
225    info: &Info,
226    type_resolver: &Resolver,
227) -> Result<Vec<u8>, ExtrinsicEncodeError>
228where
229    CallData: EncodeAsFields,
230    Resolver: TypeResolver<TypeId = Info::TypeId>,
231    Info: ExtrinsicTypeInfo,
232    Exts: TransactionExtensions<Resolver>,
233    Address: EncodeAsType,
234    Signature: EncodeAsType,
235{
236    let mut out = Vec::new();
237    encode_v4_signed_to(
238        pallet_name,
239        call_name,
240        call_data,
241        transaction_extensions,
242        address,
243        signature,
244        info,
245        type_resolver,
246        &mut out,
247    )?;
248    Ok(out)
249}
250
251/// Encode a V4 signed extrinsic to a provided output buffer.
252///
253/// A signed extrinsic includes an address, signature, and transaction extensions (such as
254/// nonce and tip) alongside the call data. The signature should be computed over the signer
255/// payload, which can be obtained via [`encode_v4_signer_payload`].
256///
257/// This is the same as [`encode_v4_signed`], but writes the encoded extrinsic to the provided
258/// `Vec<u8>` rather than returning a new one.
259///
260/// # Example
261///
262/// ```rust,ignore
263/// use frame_decode::extrinsics::{encode_v4_signed_to, TransactionExtensions};
264/// use frame_metadata::RuntimeMetadata;
265/// use parity_scale_codec::Decode;
266///
267/// let metadata_bytes = std::fs::read("artifacts/metadata_10000000_9180.scale").unwrap();
268/// let RuntimeMetadata::V14(metadata) = RuntimeMetadata::decode(&mut &*metadata_bytes).unwrap() else { return };
269///
270/// let call_data = /* ... */;
271/// let address = /* your address type */;
272/// let signature = /* your signature type */;
273/// let transaction_extensions = /* your TransactionExtensions impl */;
274///
275/// let mut encoded = Vec::new();
276/// encode_v4_signed_to(
277///     "Balances",
278///     "transfer_keep_alive",
279///     &call_data,
280///     &transaction_extensions,
281///     &address,
282///     &signature,
283///     &metadata,
284///     &metadata.types,
285///     &mut encoded,
286/// ).unwrap();
287/// ```
288#[allow(clippy::too_many_arguments)]
289pub fn encode_v4_signed_to<CallData, Info, Resolver, Exts, Address, Signature>(
290    pallet_name: &str,
291    call_name: &str,
292    call_data: &CallData,
293    transaction_extensions: &Exts,
294    address: &Address,
295    signature: &Signature,
296    info: &Info,
297    type_resolver: &Resolver,
298    out: &mut Vec<u8>,
299) -> Result<(), ExtrinsicEncodeError>
300where
301    CallData: EncodeAsFields,
302    Resolver: TypeResolver<TypeId = Info::TypeId>,
303    Info: ExtrinsicTypeInfo,
304    Exts: TransactionExtensions<Resolver>,
305    Address: EncodeAsType,
306    Signature: EncodeAsType,
307{
308    let call_info = info
309        .extrinsic_call_info_by_name(pallet_name, call_name)
310        .map_err(|i| i.into_owned())
311        .map_err(ExtrinsicEncodeError::CannotGetInfo)?;
312
313    let ext_info = info
314        .extrinsic_extension_info(None)
315        .map_err(|i| i.into_owned())
316        .map_err(ExtrinsicEncodeError::CannotGetInfo)?;
317
318    let sig_info = info
319        .extrinsic_signature_info()
320        .map_err(|i| i.into_owned())
321        .map_err(ExtrinsicEncodeError::CannotGetInfo)?;
322
323    encode_v4_signed_with_info_to(
324        call_data,
325        transaction_extensions,
326        address,
327        signature,
328        type_resolver,
329        &call_info,
330        &sig_info,
331        &ext_info,
332        out,
333    )
334}
335
336/// Encode a V4 signed extrinsic to a provided output buffer, using pre-computed type information.
337///
338/// Unlike [`encode_v4_signed_to`], which obtains the call, signature, and extension info internally
339/// given the pallet and call names, this function takes these as arguments. This is useful if you
340/// already have this information available, for example if you are encoding multiple extrinsics.
341#[allow(clippy::too_many_arguments)]
342pub fn encode_v4_signed_with_info_to<CallData, Resolver, Exts, Address, Signature>(
343    call_data: &CallData,
344    transaction_extensions: &Exts,
345    address: &Address,
346    signature: &Signature,
347    type_resolver: &Resolver,
348    call_info: &ExtrinsicCallInfo<Resolver::TypeId>,
349    sig_info: &ExtrinsicSignatureInfo<Resolver::TypeId>,
350    ext_info: &ExtrinsicExtensionInfo<Resolver::TypeId>,
351    out: &mut Vec<u8>,
352) -> Result<(), ExtrinsicEncodeError>
353where
354    CallData: EncodeAsFields,
355    Resolver: TypeResolver,
356    Exts: TransactionExtensions<Resolver>,
357    Address: EncodeAsType,
358    Signature: EncodeAsType,
359{
360    // Encode the "inner" bytes
361    let mut encoded_inner = Vec::new();
362
363    // "is signed" + transaction protocol version (4)
364    (0b10000000 + 4u8).encode_to(&mut encoded_inner);
365
366    // Who is this transaction from (corresponds to public key of signature)
367    address
368        .encode_as_type_to(
369            sig_info.address_id.clone(),
370            type_resolver,
371            &mut encoded_inner,
372        )
373        .map_err(ExtrinsicEncodeError::CannotEncodeAddress)?;
374
375    // Signature for the above identity
376    signature
377        .encode_as_type_to(
378            sig_info.signature_id.clone(),
379            type_resolver,
380            &mut encoded_inner,
381        )
382        .map_err(ExtrinsicEncodeError::CannotEncodeSignature)?;
383
384    // Signed extensions (now Transaction Extensions)
385    for (name, id) in iter_nonempty_extension_values(ext_info, type_resolver) {
386        transaction_extensions
387            .encode_extension_value_to(name, id, type_resolver, &mut encoded_inner)
388            .map_err(ExtrinsicEncodeError::TransactionExtensions)?;
389    }
390
391    // And now the actual call data, ie the arguments we're passing to the call
392    encode_call_data_with_info_to(call_data, call_info, type_resolver, &mut encoded_inner)?;
393
394    // Now, encoding these inner bytes prefixes the compact length to the beginning:
395    encoded_inner.encode_to(out);
396    Ok(())
397}
398
399/// Encode the signer payload for a V4 signed extrinsic.
400///
401/// The signer payload is the data that should be signed to produce the signature for
402/// a signed extrinsic. It consists of the encoded call data, the transaction extension
403/// values, and the transaction extension implicit data. If the resulting payload exceeds
404/// 256 bytes, it is hashed using Blake2-256.
405///
406/// Use this function to obtain the bytes that should be signed, then pass the resulting
407/// signature to [`encode_v4_signed`] to construct the final extrinsic.
408///
409/// # Example
410///
411/// ```rust,ignore
412/// use frame_decode::extrinsics::{encode_v4_signer_payload, TransactionExtensions};
413/// use frame_metadata::RuntimeMetadata;
414/// use parity_scale_codec::Decode;
415///
416/// let metadata_bytes = std::fs::read("artifacts/metadata_10000000_9180.scale").unwrap();
417/// let RuntimeMetadata::V14(metadata) = RuntimeMetadata::decode(&mut &*metadata_bytes).unwrap() else { return };
418///
419/// let call_data = /* ... */;
420/// let transaction_extensions = /* your TransactionExtensions impl */;
421///
422/// // Get the payload to sign
423/// let signer_payload = encode_v4_signer_payload(
424///     "Balances",
425///     "transfer_keep_alive",
426///     &call_data,
427///     &transaction_extensions,
428///     &metadata,
429///     &metadata.types,
430/// ).unwrap();
431///
432/// // Sign the payload with your signing key, then use encode_v4_signed
433/// // to construct the final extrinsic.
434/// ```
435pub fn encode_v4_signer_payload<CallData, Info, Resolver, Exts>(
436    pallet_name: &str,
437    call_name: &str,
438    call_data: &CallData,
439    transaction_extensions: &Exts,
440    info: &Info,
441    type_resolver: &Resolver,
442) -> Result<Vec<u8>, ExtrinsicEncodeError>
443where
444    CallData: EncodeAsFields,
445    Resolver: TypeResolver<TypeId = Info::TypeId>,
446    Info: ExtrinsicTypeInfo,
447    Exts: TransactionExtensions<Resolver>,
448    Info::TypeId: Clone,
449{
450    let call_info = info
451        .extrinsic_call_info_by_name(pallet_name, call_name)
452        .map_err(|i| i.into_owned())
453        .map_err(ExtrinsicEncodeError::CannotGetInfo)?;
454
455    let ext_info = info
456        .extrinsic_extension_info(None)
457        .map_err(|i| i.into_owned())
458        .map_err(ExtrinsicEncodeError::CannotGetInfo)?;
459
460    encode_v4_signer_payload_with_info(
461        call_data,
462        transaction_extensions,
463        type_resolver,
464        &call_info,
465        &ext_info,
466    )
467}
468
469/// Encode the signer payload for a V4 signed extrinsic, using pre-computed type information.
470///
471/// Unlike [`encode_v4_signer_payload`], which obtains the call and extension info internally
472/// given the pallet and call names, this function takes these as arguments. This is useful if you
473/// already have this information available.
474///
475/// The signer payload consists of the encoded call data, the transaction extension values,
476/// and the transaction extension implicit data. If the resulting payload exceeds 256 bytes,
477/// it is hashed using Blake2-256.
478pub fn encode_v4_signer_payload_with_info<CallData, Resolver, Exts>(
479    call_data: &CallData,
480    transaction_extensions: &Exts,
481    type_resolver: &Resolver,
482    call_info: &ExtrinsicCallInfo<Resolver::TypeId>,
483    ext_info: &ExtrinsicExtensionInfo<Resolver::TypeId>,
484) -> Result<Vec<u8>, ExtrinsicEncodeError>
485where
486    CallData: EncodeAsFields,
487    Resolver: TypeResolver,
488    Exts: TransactionExtensions<Resolver>,
489{
490    let mut out = Vec::new();
491
492    // First encode call data
493    encode_call_data_with_info_to(call_data, call_info, type_resolver, &mut out)?;
494
495    // Then the signer payload value (ie roughly the bytes that will appear in the tx)
496    for (name, id) in iter_nonempty_extension_values(ext_info, type_resolver) {
497        transaction_extensions
498            .encode_extension_value_for_signer_payload_to(name, id, type_resolver, &mut out)
499            .map_err(ExtrinsicEncodeError::TransactionExtensions)?;
500    }
501
502    // Then the signer payload implicits (ie data we want to verify that is NOT in the tx)
503    for (name, id) in iter_nonempty_extension_implicits(ext_info, type_resolver) {
504        transaction_extensions
505            .encode_extension_implicit_to(name, id, type_resolver, &mut out)
506            .map_err(ExtrinsicEncodeError::TransactionExtensions)?;
507    }
508
509    // Finally we need to hash it if it's too long
510    if out.len() > 256 {
511        out = sp_crypto_hashing::blake2_256(&out).to_vec();
512    }
513
514    Ok(out)
515}
516
517/// Encode a V5 bare extrinsic (also known as an inherent), ready to submit.
518///
519/// V5 bare extrinsics contain only call data with no transaction extensions or signature.
520/// They are functionally equivalent to V4 unsigned extrinsics and are typically used for
521/// inherents (data provided by block authors).
522///
523/// This is the same as [`encode_v5_bare_to`], but returns the encoded extrinsic as a `Vec<u8>`,
524/// rather than accepting a mutable output buffer.
525///
526/// # Example
527///
528/// ```rust
529/// use frame_decode::extrinsics::encode_v5_bare;
530/// use frame_metadata::RuntimeMetadata;
531/// use parity_scale_codec::Decode;
532///
533/// let metadata_bytes = std::fs::read("artifacts/metadata_10000000_9180.scale").unwrap();
534/// let RuntimeMetadata::V14(metadata) = RuntimeMetadata::decode(&mut &*metadata_bytes).unwrap() else { return };
535///
536/// // Encode a call to Timestamp.set with an argument.
537/// let call_data = scale_value::value!({
538///     now: 1234567890u64,
539/// });
540///
541/// let encoded = encode_v5_bare(
542///     "Timestamp",
543///     "set",
544///     &call_data,
545///     &metadata,
546///     &metadata.types,
547/// ).unwrap();
548/// ```
549pub fn encode_v5_bare<CallData, Info, Resolver>(
550    pallet_name: &str,
551    call_name: &str,
552    call_data: &CallData,
553    info: &Info,
554    type_resolver: &Resolver,
555) -> Result<Vec<u8>, ExtrinsicEncodeError>
556where
557    CallData: EncodeAsFields,
558    Resolver: TypeResolver<TypeId = Info::TypeId>,
559    Info: ExtrinsicTypeInfo,
560{
561    let mut out = Vec::new();
562    encode_v5_bare_to(
563        pallet_name,
564        call_name,
565        call_data,
566        info,
567        type_resolver,
568        &mut out,
569    )?;
570    Ok(out)
571}
572
573/// Encode a V5 bare extrinsic (also known as an inherent) to a provided output buffer.
574///
575/// This is the same as [`encode_v5_bare`], but writes the encoded extrinsic to the provided
576/// `Vec<u8>` rather than returning a new one.
577///
578/// # Example
579///
580/// ```rust
581/// use frame_decode::extrinsics::encode_v5_bare_to;
582/// use frame_metadata::RuntimeMetadata;
583/// use parity_scale_codec::Decode;
584///
585/// let metadata_bytes = std::fs::read("artifacts/metadata_10000000_9180.scale").unwrap();
586/// let RuntimeMetadata::V14(metadata) = RuntimeMetadata::decode(&mut &*metadata_bytes).unwrap() else { return };
587///
588/// // Encode a call to Timestamp.set with an argument.
589/// let call_data = scale_value::value!({
590///     now: 1234567890u64,
591/// });
592///
593/// let mut encoded = Vec::new();
594/// encode_v5_bare_to(
595///     "Timestamp",
596///     "set",
597///     &call_data,
598///     &metadata,
599///     &metadata.types,
600///     &mut encoded,
601/// ).unwrap();
602/// ```
603pub fn encode_v5_bare_to<CallData, Info, Resolver>(
604    pallet_name: &str,
605    call_name: &str,
606    call_data: &CallData,
607    info: &Info,
608    type_resolver: &Resolver,
609    out: &mut Vec<u8>,
610) -> Result<(), ExtrinsicEncodeError>
611where
612    CallData: EncodeAsFields,
613    Resolver: TypeResolver<TypeId = Info::TypeId>,
614    Info: ExtrinsicTypeInfo,
615{
616    let call_info = info
617        .extrinsic_call_info_by_name(pallet_name, call_name)
618        .map_err(|i| i.into_owned())
619        .map_err(ExtrinsicEncodeError::CannotGetInfo)?;
620
621    encode_v5_bare_with_info_to(call_data, type_resolver, &call_info, out)
622}
623
624/// Encode a V5 bare extrinsic (also known as an inherent) to a provided output buffer,
625/// using pre-computed call information.
626///
627/// Unlike [`encode_v5_bare_to`], which obtains the call info internally given the pallet and call names,
628/// this function takes the call info as an argument. This is useful if you already have the call info available,
629/// for example if you are encoding multiple extrinsics for the same call.
630pub fn encode_v5_bare_with_info_to<CallData, Resolver>(
631    call_data: &CallData,
632    type_resolver: &Resolver,
633    call_info: &ExtrinsicCallInfo<Resolver::TypeId>,
634    out: &mut Vec<u8>,
635) -> Result<(), ExtrinsicEncodeError>
636where
637    CallData: EncodeAsFields,
638    Resolver: TypeResolver,
639{
640    encode_unsigned_at_version_with_info_to(
641        call_data,
642        call_info,
643        type_resolver,
644        TransactionVersion::V5,
645        out,
646    )
647}
648
649/// Determine the best transaction extension version to use for a V5 general extrinsic.
650///
651/// V5 general extrinsics support multiple versions of transaction extensions. This function
652/// iterates through the available extension versions and returns the first version for which
653/// all required extension data is provided.
654///
655/// Use this function to determine which `transaction_extension_version` to pass to
656/// [`encode_v5_general`] or [`encode_v5_general_to`].
657///
658/// # Errors
659///
660/// Returns [`ExtrinsicEncodeError::CannotFindGoodExtensionVersion`] if no extension version
661/// can be found for which all required data is available.
662///
663/// # Example
664///
665/// ```rust,ignore
666/// use frame_decode::extrinsics::{best_v5_general_transaction_extension_version, encode_v5_general};
667/// use frame_metadata::RuntimeMetadata;
668/// use parity_scale_codec::Decode;
669///
670/// let metadata_bytes = std::fs::read("artifacts/metadata.scale").unwrap();
671/// let RuntimeMetadata::V16(metadata) = RuntimeMetadata::decode(&mut &*metadata_bytes).unwrap() else { return };
672///
673/// let transaction_extensions = /* your TransactionExtensions impl */;
674///
675/// // Find the best extension version for your provided extensions
676/// let ext_version = best_v5_general_transaction_extension_version(
677///     &transaction_extensions,
678///     &metadata,
679/// ).unwrap();
680///
681/// // Use this version when encoding the extrinsic
682/// let encoded = encode_v5_general(
683///     "Balances",
684///     "transfer_keep_alive",
685///     &call_data,
686///     ext_version,
687///     &transaction_extensions,
688///     &metadata,
689///     &metadata.types,
690/// ).unwrap();
691/// ```
692pub fn best_v5_general_transaction_extension_version<Exts, Info, Resolver>(
693    transaction_extensions: &Exts,
694    info: &Info,
695    type_resolver: &Resolver,
696) -> Result<u8, ExtrinsicEncodeError>
697where
698    Info: ExtrinsicTypeInfo,
699    Exts: TransactionExtensions<Resolver>,
700    Resolver: TypeResolver<TypeId = Info::TypeId>,
701    Info::TypeId: Clone,
702{
703    let extension_versions = info
704        .extrinsic_extension_version_info()
705        .map_err(|i| i.into_owned())
706        .map_err(ExtrinsicEncodeError::CannotGetInfo)?;
707
708    for ext_version in extension_versions {
709        // get extension info for each version.
710        let ext_info = info
711            .extrinsic_extension_info(Some(ext_version))
712            .map_err(|i| i.into_owned())
713            .map_err(ExtrinsicEncodeError::CannotGetInfo)?;
714
715        // Do we have all of the extension data for this version?
716        let have_data = ext_info.extension_ids.iter().all(|e| {
717            let is_value_empty = is_type_empty(e.id.clone(), type_resolver);
718            let is_implicit_empty = is_type_empty(e.implicit_id.clone(), type_resolver);
719            (is_value_empty && is_implicit_empty)
720                || transaction_extensions.contains_extension(&e.name)
721        });
722
723        // If we have all of the data we need, encode the extrinsic,
724        // else loop and try the next extension version.
725        if have_data {
726            return Ok(ext_version);
727        }
728    }
729
730    Err(ExtrinsicEncodeError::CannotFindGoodExtensionVersion)
731}
732
733/// Encode a V5 general extrinsic, ready to submit.
734///
735/// V5 general extrinsics include transaction extensions but no separate signature field.
736/// Instead, the signature (if needed) is provided as part of one of the transaction extensions.
737/// This is the new extrinsic format introduced in newer Substrate runtimes.
738///
739/// Use [`best_v5_general_transaction_extension_version`] to determine which extension version
740/// to use based on the extensions you have available.
741///
742/// This is the same as [`encode_v5_general_to`], but returns the encoded extrinsic as a `Vec<u8>`,
743/// rather than accepting a mutable output buffer.
744///
745/// # Example
746///
747/// ```rust,ignore
748/// use frame_decode::extrinsics::{encode_v5_general, best_v5_general_transaction_extension_version};
749/// use frame_metadata::RuntimeMetadata;
750/// use parity_scale_codec::Decode;
751///
752/// let metadata_bytes = std::fs::read("artifacts/metadata.scale").unwrap();
753/// let RuntimeMetadata::V16(metadata) = RuntimeMetadata::decode(&mut &*metadata_bytes).unwrap() else { return };
754///
755/// let call_data = /* ... */;
756/// let transaction_extensions = /* your TransactionExtensions impl */;
757///
758/// let ext_version = best_v5_general_transaction_extension_version(
759///     &transaction_extensions,
760///     &metadata,
761/// ).unwrap();
762///
763/// let encoded = encode_v5_general(
764///     "Balances",
765///     "transfer_keep_alive",
766///     &call_data,
767///     ext_version,
768///     &transaction_extensions,
769///     &metadata,
770///     &metadata.types,
771/// ).unwrap();
772/// ```
773pub fn encode_v5_general<CallData, Info, Resolver, Exts>(
774    pallet_name: &str,
775    call_name: &str,
776    call_data: &CallData,
777    transaction_extension_version: u8,
778    transaction_extensions: &Exts,
779    info: &Info,
780    type_resolver: &Resolver,
781) -> Result<Vec<u8>, ExtrinsicEncodeError>
782where
783    CallData: EncodeAsFields,
784    Resolver: TypeResolver<TypeId = Info::TypeId>,
785    Info: ExtrinsicTypeInfo,
786    Exts: TransactionExtensions<Resolver>,
787{
788    let mut out = Vec::new();
789    encode_v5_general_to(
790        pallet_name,
791        call_name,
792        call_data,
793        transaction_extension_version,
794        transaction_extensions,
795        info,
796        type_resolver,
797        &mut out,
798    )?;
799    Ok(out)
800}
801
802/// Encode a V5 general extrinsic to a provided output buffer.
803///
804/// V5 general extrinsics include transaction extensions but no separate signature field.
805/// Instead, the signature (if needed) is provided as part of one of the transaction extensions.
806///
807/// This is the same as [`encode_v5_general`], but writes the encoded extrinsic to the provided
808/// `Vec<u8>` rather than returning a new one.
809///
810/// # Example
811///
812/// ```rust,ignore
813/// use frame_decode::extrinsics::{encode_v5_general_to, best_v5_general_transaction_extension_version};
814/// use frame_metadata::RuntimeMetadata;
815/// use parity_scale_codec::Decode;
816///
817/// let metadata_bytes = std::fs::read("artifacts/metadata.scale").unwrap();
818/// let RuntimeMetadata::V16(metadata) = RuntimeMetadata::decode(&mut &*metadata_bytes).unwrap() else { return };
819///
820/// let call_data = /* ... */;
821/// let transaction_extensions = /* your TransactionExtensions impl */;
822///
823/// let ext_version = best_v5_general_transaction_extension_version(
824///     &transaction_extensions,
825///     &metadata,
826/// ).unwrap();
827///
828/// let mut encoded = Vec::new();
829/// encode_v5_general_to(
830///     "Balances",
831///     "transfer_keep_alive",
832///     &call_data,
833///     ext_version,
834///     &transaction_extensions,
835///     &metadata,
836///     &metadata.types,
837///     &mut encoded,
838/// ).unwrap();
839/// ```
840#[allow(clippy::too_many_arguments)]
841pub fn encode_v5_general_to<CallData, Info, Resolver, Exts>(
842    pallet_name: &str,
843    call_name: &str,
844    call_data: &CallData,
845    transaction_extension_version: u8,
846    transaction_extensions: &Exts,
847    info: &Info,
848    type_resolver: &Resolver,
849    out: &mut Vec<u8>,
850) -> Result<(), ExtrinsicEncodeError>
851where
852    CallData: EncodeAsFields,
853    Resolver: TypeResolver<TypeId = Info::TypeId>,
854    Info: ExtrinsicTypeInfo,
855    Exts: TransactionExtensions<Resolver>,
856{
857    let call_info = info
858        .extrinsic_call_info_by_name(pallet_name, call_name)
859        .map_err(|i| i.into_owned())
860        .map_err(ExtrinsicEncodeError::CannotGetInfo)?;
861
862    let ext_info = info
863        .extrinsic_extension_info(Some(transaction_extension_version))
864        .map_err(|i| i.into_owned())
865        .map_err(ExtrinsicEncodeError::CannotGetInfo)?;
866
867    encode_v5_general_with_info_to(
868        call_data,
869        transaction_extension_version,
870        transaction_extensions,
871        type_resolver,
872        &call_info,
873        &ext_info,
874        out,
875    )
876}
877
878/// Encode a V5 general extrinsic to a provided output buffer, using pre-computed type information.
879///
880/// Unlike [`encode_v5_general_to`], which obtains the call and extension info internally
881/// given the pallet and call names, this function takes these as arguments. This is useful if you
882/// already have this information available, for example if you are encoding multiple extrinsics.
883pub fn encode_v5_general_with_info_to<CallData, Resolver, Exts>(
884    call_data: &CallData,
885    transaction_extension_version: u8,
886    transaction_extensions: &Exts,
887    type_resolver: &Resolver,
888    call_info: &ExtrinsicCallInfo<Resolver::TypeId>,
889    ext_info: &ExtrinsicExtensionInfo<Resolver::TypeId>,
890    out: &mut Vec<u8>,
891) -> Result<(), ExtrinsicEncodeError>
892where
893    CallData: EncodeAsFields,
894    Resolver: TypeResolver,
895    Exts: TransactionExtensions<Resolver>,
896{
897    // Encode the "inner" bytes
898    let mut encoded_inner = Vec::new();
899
900    // "is signed" (2 bits now) + transaction protocol version (5)
901    (0b01000000 + 5u8).encode_to(&mut encoded_inner);
902
903    // Version of the transaction extensions.
904    transaction_extension_version.encode_to(&mut encoded_inner);
905
906    // Transaction Extensions next. These may include a signature/address
907    for (name, id) in iter_nonempty_extension_values(ext_info, type_resolver) {
908        transaction_extensions
909            .encode_extension_value_to(name, id, type_resolver, &mut encoded_inner)
910            .map_err(ExtrinsicEncodeError::TransactionExtensions)?;
911    }
912
913    // And now the actual call data, ie the arguments we're passing to the call
914    encode_call_data_with_info_to(call_data, call_info, type_resolver, &mut encoded_inner)?;
915
916    // Now, encoding these inner bytes prefixes the compact length to the beginning:
917    encoded_inner.encode_to(out);
918    Ok(())
919}
920
921/// Encode the signer payload for a V5 general extrinsic.
922///
923/// The signer payload is the data that should be signed to produce the signature for
924/// a general extrinsic. It consists of the encoded call data, the transaction extension
925/// values (for the signer payload), and the transaction extension implicit data.
926///
927/// Unlike [`encode_v4_signer_payload`], which conditionally hashes the payload if it exceeds
928/// 256 bytes, V5 signer payloads are always hashed using Blake2-256, returning a fixed 32-byte
929/// array.
930///
931/// Use this function to obtain the bytes that should be signed, then include the resulting
932/// signature in the appropriate transaction extension when calling [`encode_v5_general`].
933///
934/// # Example
935///
936/// ```rust,ignore
937/// use frame_decode::extrinsics::{encode_v5_signer_payload, best_v5_general_transaction_extension_version};
938/// use frame_metadata::RuntimeMetadata;
939/// use parity_scale_codec::Decode;
940///
941/// let metadata_bytes = std::fs::read("artifacts/metadata.scale").unwrap();
942/// let RuntimeMetadata::V16(metadata) = RuntimeMetadata::decode(&mut &*metadata_bytes).unwrap() else { return };
943///
944/// let call_data = /* ... */;
945/// let transaction_extensions = /* your TransactionExtensions impl */;
946///
947/// let ext_version = best_v5_general_transaction_extension_version(
948///     &transaction_extensions,
949///     &metadata,
950/// ).unwrap();
951///
952/// // Get the 32-byte payload hash to sign
953/// let signer_payload = encode_v5_signer_payload(
954///     "Balances",
955///     "transfer_keep_alive",
956///     &call_data,
957///     ext_version,
958///     &transaction_extensions,
959///     &metadata,
960///     &metadata.types,
961/// ).unwrap();
962///
963/// // Sign the payload with your signing key, then include the signature
964/// // in your transaction extensions when calling encode_v5_general.
965/// ```
966pub fn encode_v5_signer_payload<CallData, Info, Resolver, Exts>(
967    pallet_name: &str,
968    call_name: &str,
969    call_data: &CallData,
970    transaction_extension_version: u8,
971    transaction_extensions: &Exts,
972    info: &Info,
973    type_resolver: &Resolver,
974) -> Result<[u8; 32], ExtrinsicEncodeError>
975where
976    CallData: EncodeAsFields,
977    Resolver: TypeResolver<TypeId = Info::TypeId>,
978    Info: ExtrinsicTypeInfo,
979    Exts: TransactionExtensions<Resolver>,
980    Info::TypeId: Clone,
981{
982    let call_info = info
983        .extrinsic_call_info_by_name(pallet_name, call_name)
984        .map_err(|i| i.into_owned())
985        .map_err(ExtrinsicEncodeError::CannotGetInfo)?;
986
987    let ext_info = info
988        .extrinsic_extension_info(Some(transaction_extension_version))
989        .map_err(|i| i.into_owned())
990        .map_err(ExtrinsicEncodeError::CannotGetInfo)?;
991
992    encode_v5_signer_payload_with_info(
993        call_data,
994        transaction_extensions,
995        type_resolver,
996        &call_info,
997        &ext_info,
998    )
999}
1000
1001/// Encode the signer payload for a V5 general extrinsic, using pre-computed type information.
1002///
1003/// Unlike [`encode_v5_signer_payload`], which obtains the call and extension info internally
1004/// given the pallet and call names, this function takes these as arguments. This is useful if you
1005/// already have this information available.
1006///
1007/// The signer payload consists of the encoded call data, the transaction extension values
1008/// (for the signer payload), and the transaction extension implicit data. The result is always
1009/// hashed using Blake2-256, returning a fixed 32-byte array.
1010pub fn encode_v5_signer_payload_with_info<CallData, Resolver, Exts>(
1011    call_data: &CallData,
1012    transaction_extensions: &Exts,
1013    type_resolver: &Resolver,
1014    call_info: &ExtrinsicCallInfo<Resolver::TypeId>,
1015    ext_info: &ExtrinsicExtensionInfo<Resolver::TypeId>,
1016) -> Result<[u8; 32], ExtrinsicEncodeError>
1017where
1018    CallData: EncodeAsFields,
1019    Resolver: TypeResolver,
1020    Exts: TransactionExtensions<Resolver>,
1021{
1022    let mut out = Vec::new();
1023
1024    // First encode call data
1025    encode_call_data_with_info_to(call_data, call_info, type_resolver, &mut out)?;
1026
1027    // Then the signer payload value (ie roughly the bytes that will appear in the tx)
1028    for (name, id) in iter_nonempty_extension_values(ext_info, type_resolver) {
1029        transaction_extensions
1030            .encode_extension_value_for_signer_payload_to(name, id, type_resolver, &mut out)
1031            .map_err(ExtrinsicEncodeError::TransactionExtensions)?;
1032    }
1033
1034    // Then the signer payload implicits (ie data we want to verify that is NOT in the tx)
1035    for (name, id) in iter_nonempty_extension_implicits(ext_info, type_resolver) {
1036        transaction_extensions
1037            .encode_extension_implicit_to(name, id, type_resolver, &mut out)
1038            .map_err(ExtrinsicEncodeError::TransactionExtensions)?;
1039    }
1040
1041    // Finally hash it (regardless of length).
1042    Ok(sp_crypto_hashing::blake2_256(&out))
1043}
1044
1045/// Encode the call data for an extrinsic.
1046///
1047/// This is basically an alias for [`scale_encode::EncodeAsFields::encode_as_fields()`].
1048pub fn encode_call_data<CallData, Info, Resolver>(
1049    pallet_name: &str,
1050    call_name: &str,
1051    call_data: &CallData,
1052    info: &Info,
1053    type_resolver: &Resolver,
1054) -> Result<Vec<u8>, ExtrinsicEncodeError>
1055where
1056    CallData: EncodeAsFields,
1057    Resolver: TypeResolver<TypeId = Info::TypeId>,
1058    Info: ExtrinsicTypeInfo,
1059{
1060    let mut out = Vec::new();
1061    encode_call_data_to(
1062        pallet_name,
1063        call_name,
1064        call_data,
1065        info,
1066        type_resolver,
1067        &mut out,
1068    )?;
1069    Ok(out)
1070}
1071
1072/// Encode the call data for an extrinsic to the given Vec.
1073///
1074/// This is basically an alias for [`scale_encode::EncodeAsFields::encode_as_fields()`], but
1075/// with a byte for the pallet index and call index prepended.
1076pub fn encode_call_data_to<CallData, Info, Resolver>(
1077    pallet_name: &str,
1078    call_name: &str,
1079    call_data: &CallData,
1080    info: &Info,
1081    type_resolver: &Resolver,
1082    out: &mut Vec<u8>,
1083) -> Result<(), ExtrinsicEncodeError>
1084where
1085    CallData: EncodeAsFields,
1086    Resolver: TypeResolver<TypeId = Info::TypeId>,
1087    Info: ExtrinsicTypeInfo,
1088{
1089    let call_info = info
1090        .extrinsic_call_info_by_name(pallet_name, call_name)
1091        .map_err(|i| i.into_owned())
1092        .map_err(ExtrinsicEncodeError::CannotGetInfo)?;
1093
1094    encode_call_data_with_info_to(call_data, &call_info, type_resolver, out)
1095}
1096
1097/// Encode the call data for an extrinsic, given some already-computed [`ExtrinsicCallInfo`].
1098///
1099/// This is basically an alias for [`scale_encode::EncodeAsFields::encode_as_fields()`], but
1100/// with a byte for the pallet index and call index prepended.
1101pub fn encode_call_data_with_info<CallData, Info, Resolver>(
1102    call_data: &CallData,
1103    call_info: &ExtrinsicCallInfo<Resolver::TypeId>,
1104    type_resolver: &Resolver,
1105) -> Result<Vec<u8>, ExtrinsicEncodeError>
1106where
1107    Resolver: TypeResolver,
1108    CallData: EncodeAsFields,
1109{
1110    let mut out = Vec::new();
1111    encode_call_data_with_info_to(call_data, call_info, type_resolver, &mut out)?;
1112    Ok(out)
1113}
1114
1115/// Encode the call data for an extrinsic, given some already-computed [`ExtrinsicCallInfo`],
1116/// to the given Vec.
1117///
1118/// This is basically an alias for [`scale_encode::EncodeAsFields::encode_as_fields()`], but
1119/// with a byte for the pallet index and call index prepended.
1120pub fn encode_call_data_with_info_to<CallData, Resolver>(
1121    call_data: &CallData,
1122    call_info: &ExtrinsicCallInfo<Resolver::TypeId>,
1123    type_resolver: &Resolver,
1124    out: &mut Vec<u8>,
1125) -> Result<(), ExtrinsicEncodeError>
1126where
1127    Resolver: TypeResolver,
1128    CallData: EncodeAsFields,
1129{
1130    // Pallet and call index to identify the call:
1131    call_info.pallet_index.encode_to(out);
1132    call_info.call_index.encode_to(out);
1133
1134    // Arguments to this call:
1135    let mut fields = call_info.args.iter().map(|arg| Field {
1136        name: Some(&*arg.name),
1137        id: arg.id.clone(),
1138    });
1139    call_data
1140        .encode_as_fields_to(&mut fields, type_resolver, out)
1141        .map_err(ExtrinsicEncodeError::CannotEncodeCallData)?;
1142
1143    Ok(())
1144}
1145
1146// V4 unsigned and V5 bare extrinsics are basically encoded
1147// in the same way; this helper can do either.
1148fn encode_unsigned_at_version_with_info_to<CallData, Resolver>(
1149    call_data: &CallData,
1150    call_info: &ExtrinsicCallInfo<Resolver::TypeId>,
1151    type_resolver: &Resolver,
1152    tx_version: TransactionVersion,
1153    out: &mut Vec<u8>,
1154) -> Result<(), ExtrinsicEncodeError>
1155where
1156    Resolver: TypeResolver,
1157    CallData: EncodeAsFields,
1158{
1159    // Build our inner, non-length-prefixed extrinsic:
1160    let inner = {
1161        let mut out = Vec::new();
1162        // Transaction version (4):
1163        (tx_version as u8).encode_to(&mut out);
1164        // Then the arguments for the call:
1165        encode_call_data_with_info_to(call_data, call_info, type_resolver, &mut out)?;
1166        out
1167    };
1168
1169    // Encode the inner vec to prefix the compact length to it:
1170    inner.encode_to(out);
1171    Ok(())
1172}
1173
1174#[derive(Copy, Clone)]
1175#[repr(u8)]
1176enum TransactionVersion {
1177    V4 = 4u8,
1178    V5 = 5u8,
1179}
1180
1181/// Iterate over the non-empty extension implicit name/IDs
1182fn iter_nonempty_extension_implicits<'exts, 'info, Resolver: TypeResolver>(
1183    extension_info: &'exts ExtrinsicExtensionInfo<'info, Resolver::TypeId>,
1184    types: &Resolver,
1185) -> impl Iterator<Item = (&'exts str, Resolver::TypeId)> {
1186    extension_info
1187        .extension_ids
1188        .iter()
1189        .filter(|arg| !is_type_empty(arg.implicit_id.clone(), types))
1190        .map(|arg| (&*arg.name, arg.implicit_id.clone()))
1191}
1192
1193/// Iterate over the non-empty extension value name/IDs
1194fn iter_nonempty_extension_values<'exts, 'info, Resolver: TypeResolver>(
1195    extension_info: &'exts ExtrinsicExtensionInfo<'info, Resolver::TypeId>,
1196    types: &Resolver,
1197) -> impl Iterator<Item = (&'exts str, Resolver::TypeId)> {
1198    extension_info
1199        .extension_ids
1200        .iter()
1201        .filter(|arg| !is_type_empty(arg.id.clone(), types))
1202        .map(|arg| (&*arg.name, arg.id.clone()))
1203}
1204
1205/// Checks to see whether the type being given is empty, ie would require
1206/// 0 bytes to encode. We use this to skip 0 byte transaction extensions; ones
1207/// that are mentioned in the metadata but only used in the node side and require
1208/// no bytes to be given.
1209fn is_type_empty<Resolver: TypeResolver>(type_id: Resolver::TypeId, types: &Resolver) -> bool {
1210    struct IsEmptyVisitor<'r, R> {
1211        types: &'r R,
1212    }
1213    impl<'r, R: TypeResolver> scale_type_resolver::ResolvedTypeVisitor<'r> for IsEmptyVisitor<'r, R> {
1214        type TypeId = R::TypeId;
1215        type Value = bool;
1216
1217        // The default ans safe assumption is that a type is _not_ empty.
1218        fn visit_unhandled(self, _: scale_type_resolver::UnhandledKind) -> Self::Value {
1219            false
1220        }
1221        // Arrays are empty if they are 0 length or the type inside is empty.
1222        fn visit_array(self, type_id: Self::TypeId, len: usize) -> Self::Value {
1223            len == 0 || is_type_empty(type_id, self.types)
1224        }
1225        // Composites are empty if all of their fields are empty.
1226        fn visit_composite<Path, Fields>(self, _path: Path, mut fields: Fields) -> Self::Value
1227        where
1228            Path: scale_type_resolver::PathIter<'r>,
1229            Fields: scale_decode::FieldIter<'r, Self::TypeId>,
1230        {
1231            fields.all(|f| is_type_empty(f.id, self.types))
1232        }
1233        // Tuples are empty if all of their fields are empty.
1234        fn visit_tuple<TypeIds>(self, mut type_ids: TypeIds) -> Self::Value
1235        where
1236            TypeIds: ExactSizeIterator<Item = Self::TypeId>,
1237        {
1238            type_ids.all(|id| is_type_empty(id, self.types))
1239        }
1240    }
1241
1242    types
1243        .resolve_type(type_id, IsEmptyVisitor { types })
1244        .unwrap_or_default()
1245}