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}