sov_universal_wallet/schema/transaction_templates.rs
1use core::panic;
2use std::collections::{btree_map, BTreeMap, HashSet};
3
4use borsh::{BorshDeserialize, BorshSerialize};
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8use super::Link;
9
10#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
11#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
12pub struct TemplateInput {
13 type_link: Link,
14 offset: usize,
15}
16
17impl TemplateInput {
18 pub fn type_link(&self) -> &Link {
19 &self.type_link
20 }
21
22 pub fn offset(&self) -> &usize {
23 &self.offset
24 }
25}
26
27/// Data structure holding a single template for a standard transaction type.
28/// Consists of pre-encoded default bytes, and a list of input fields that must be filled in to use
29/// the template (with name bindings).
30/// During usage, each template input is serialized using the schema accordings to its type, and
31/// inserted into the existing template bytes according to its offset index. The final result is a
32/// well-formed fully encoded transaction.
33/// It is important that input fields must be filled in from last to first (to ensure the `offset`
34/// values of earlier fields remain valid - as encoded fields are variable-length and cannot be
35/// accounted for).
36#[derive(Debug, Default, Clone, BorshSerialize, BorshDeserialize)]
37#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
38pub struct TransactionTemplate {
39 preencoded_bytes: Vec<u8>,
40 inputs: Vec<(String, TemplateInput)>,
41}
42
43impl TransactionTemplate {
44 /// Construct a new template from one pre-encoded field.
45 /// Used during template generation (normally in the UniversalWallet derive macro), at the
46 /// leaf levels of recursive template definition.
47 pub fn from_bytes(preencoded_bytes: Vec<u8>) -> Self {
48 Self {
49 preencoded_bytes,
50 inputs: vec![],
51 }
52 }
53
54 /// Construct a new template from one input field, with a placeholder link type.
55 /// While the offset gets fixed in `concat()`, the field_index has to be correct at
56 /// construction time.
57 /// Used during template generation (normally in the UniversalWallet derive macro), at the
58 /// leaf levels of recursive template definition.
59 pub fn from_input(name: String, field_index: usize) -> Self {
60 Self {
61 inputs: vec![(
62 name,
63 TemplateInput {
64 type_link: Link::IndexedPlaceholder(field_index),
65 offset: 0,
66 },
67 )],
68 preencoded_bytes: vec![],
69 }
70 }
71
72 pub fn preencoded_bytes(&self) -> &[u8] {
73 &self.preencoded_bytes
74 }
75
76 pub fn inputs(&self) -> &[(String, TemplateInput)] {
77 &self.inputs
78 }
79
80 /// Combining template definitions on multiple fields into one template on the parent type.
81 /// Concatenation in this context means: pre-encoded bytes are concatenated directly, and inputs
82 /// are added to the map in order, with offsets adjusted to accout for earlier pre-encoded
83 /// bytes.
84 /// Note that placeholder field indexes are NOT adjusted.
85 pub fn concat(templates: Vec<Self>) -> Self {
86 let mut ret = Self::default();
87 let mut prev_bytes_len = 0usize;
88 for t in templates {
89 ret.preencoded_bytes.extend(t.preencoded_bytes);
90 for (name, TemplateInput { type_link, offset }) in t.inputs {
91 if ret.inputs.iter().any(|input| input.0 == name) {
92 panic!("Schema transaction template contained duplicate input binding name: {name}");
93 }
94 ret.inputs.push((
95 name,
96 TemplateInput {
97 type_link,
98 offset: offset + prev_bytes_len,
99 },
100 ));
101 }
102 prev_bytes_len = ret.preencoded_bytes.len();
103 }
104 ret
105 }
106
107 /// Adjust a template definition for a wrapping enum. This means: the discriminant is
108 /// prepended to the pre-encoded bytes, and all input offsets are adjusted.
109 pub fn prepend_discriminant(&mut self, discriminant: u8) {
110 self.preencoded_bytes.insert(0, discriminant);
111 for (_, TemplateInput { offset, .. }) in self.inputs.iter_mut() {
112 *offset += 1;
113 }
114 }
115}
116
117/// Temporary data structure used to track the origins of each field's template parameters before
118/// constructing the parent type's template.
119/// This is necessary because subtype-originating templates do not force the parent type to
120/// implement this template: the subtype might have been annotated for use elsewhere. A type only
121/// inherits a template from subtypes if _every_ subtype implements the template - otherwise, the
122/// template is dropped. Only explicitly annotated templates are enforced as mandatory on every
123/// field.
124#[derive(Debug, Default, Clone)]
125#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
126pub struct AttributeAndChildTemplateSet {
127 pub attribute_templates: TransactionTemplateSet,
128 pub type_templates: TransactionTemplateSet,
129}
130
131/// Data structure denoting a set of templates on a type, indexed by (string) name.
132#[derive(Debug, Default, Clone, BorshSerialize, BorshDeserialize)]
133#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
134pub struct TransactionTemplateSet(pub BTreeMap<String, TransactionTemplate>);
135
136impl TransactionTemplateSet {
137 /// Replace placeholder links in templates with actual typelinks from the schema (provided by
138 /// the type's child_links).
139 /// Template placeholders must be `IndexedPlaceholder`s because a template does not necessarily
140 /// include placeholders for every field - some fields may be provided with pre-encoded default
141 /// values and thus need to be skipped. The index allows looking up the correct type in the
142 /// child_links vector.
143 pub fn fill_links(
144 field_templates: Vec<(String, TransactionTemplate)>,
145 child_links: Vec<Link>,
146 ) -> Self {
147 let mut attribute_templates = BTreeMap::<String, TransactionTemplate>::default();
148 for (name, mut template) in field_templates.into_iter() {
149 // 1. Fill in any type links
150 for (_, input) in template.inputs.iter_mut() {
151 match input.type_link {
152 Link::IndexedPlaceholder(i) => input.type_link = child_links[i].clone(),
153 Link::Placeholder => panic!("Templates should not have unindexed Placeholder links. This is a bug in a hand-written implementation."),
154 _ => ()
155 }
156 }
157 // 2. Insert into map, sanity checking for no duplicates
158 if let btree_map::Entry::Vacant(e) = attribute_templates.entry(name.clone()) {
159 e.insert(template);
160 } else {
161 panic!("Duplicate template definitions (name: \"{name}\" in attributes. This should never happen (macro should have already errored out).")
162 }
163 }
164
165 Self(attribute_templates)
166 }
167
168 /// Input: a vector of template sets, normally one for each field of a type.
169 /// Outputs: a merged template set. This means:
170 /// - Every individual template is concatenated from each field portion of it, across the set
171 /// of templates.
172 /// - The entire set is enforced to be present on every field, so no fields are skipped.
173 ///
174 /// The exception to the 2nd rule above is for templates only present in the set through field
175 /// types (i.e. subtype templates). See the documentation of AttributeAndChildTemplateSet for a
176 /// full explanation.
177 pub fn concatenate_template_sets(
178 template_sets: Vec<AttributeAndChildTemplateSet>,
179 type_name_for_diagnostics: &'static str,
180 ) -> Self {
181 // Eventual return value
182 let mut final_template_set = Self::default();
183
184 // The union of elements of each set (as indexed by name) originating directly from
185 // attribute annotations on this type's fields
186 let attribute_template_names_set: HashSet<_> = template_sets
187 .iter()
188 .flat_map(|t| t.attribute_templates.0.keys().cloned())
189 .collect();
190 // The union of elements of each set originating from subtypes...
191 let type_template_names_set: HashSet<_> = template_sets
192 .iter()
193 .flat_map(|t| t.type_templates.0.keys().cloned())
194 .collect();
195 // And the difference of the two, giving the subtype-only templates
196 let type_only_names_set: HashSet<_> = type_template_names_set
197 .difference(&attribute_template_names_set)
198 .cloned()
199 .collect();
200
201 // With the template names tracked, the two components of AttributeAndChildTemplateSet are
202 // merged into a single Vec<TransactionTemplateSet> which is then concatenated/merged
203 // according to the rules set out above
204 let mut zipped_templates: Vec<_> = template_sets.into_iter().map(|mut separated| {
205 for (name, template) in separated.type_templates.0.into_iter() {
206 if let btree_map::Entry::Vacant(e) = separated.attribute_templates.0.entry(name.clone()) {
207 e.insert(template);
208 } else {
209 panic!("Field type's template definitions for \"{}\" overlap with field's own template annotations.", name);
210 }
211 }
212 separated.attribute_templates
213 }).collect();
214
215 // Helper function doing the actual merging - as the only difference between the two
216 // origins of templates is whether we abort with an error or not on a missing template
217 fn collect_concated_templates<F: Fn(&String)>(
218 names: HashSet<String>,
219 all_templates: &mut [TransactionTemplateSet],
220 panic_if_needed: F,
221 ) -> TransactionTemplateSet {
222 let mut ret = TransactionTemplateSet::default();
223 // For every potential element in the set...
224 'template: for name in names {
225 // build a vector of individual templates...
226 let mut template_vec = Vec::<TransactionTemplate>::new();
227 for template_set in all_templates.iter_mut() {
228 match template_set.0.remove(&name) {
229 None => {
230 // handle the case where the template is not present on a field
231 panic_if_needed(&name);
232 break 'template;
233 }
234 Some(template) => template_vec.push(template),
235 }
236 }
237 // finally, with the vector consisting of the sub-components of that template
238 // across every single field on our type, concatenate them all into one template -
239 // concatenating pre-encoded values, adjusting input offsets etc. - and add it to
240 // our return set
241 ret.0
242 .insert(name, TransactionTemplate::concat(template_vec));
243 }
244 ret
245 }
246
247 // For templates explicitly annotated on a field of this type, abort immediately if not
248 // every field provides this template.
249 final_template_set.0.extend(collect_concated_templates(attribute_template_names_set, &mut zipped_templates, |name| {
250 panic!("Partial template transaction definition! Metadata for transaction \"{}\" is found on some, but not all, fields of data type {}.\nDouble-check the transaction template attributes and child type definitions. Any template defined on an attribute on any one field must be present for every field, either directly from annotations or from a field's type", name, type_name_for_diagnostics);
251 }).0);
252
253 // For templates only inherited from subtypes, ignore any that aren't implemented for every
254 // field.
255 // Note that calling `final_template_set.extend()` is safe because, earlier, we ensured
256 // type_only_names_set has zero intersection with attribute_template_names_set, thus there
257 // is no danger of duplicate or overwritten templates: the two `collect_concated_templates`
258 // are guaranteed to be disjoint on the space of template names.
259 final_template_set.0.extend(
260 collect_concated_templates(type_only_names_set, &mut zipped_templates, |_| {}).0,
261 );
262
263 final_template_set
264 }
265
266 /// Explicitly filter which templates will be part of a given enum variant.
267 /// Only templates whose name is in the list will be set on the variant. Additionally, any
268 /// names in the list NOT present in the templates will cause an error.
269 ///
270 /// Optionally, an override can be set to inherit all templates from the variant (primarily
271 /// intended for use on the `RuntimeCall`, to inherit all templates from every module's
272 /// `CallMessage`). In this case, the filter list is only used for error checking, to
273 /// explicitly enforce that all templates named in it MUST be present.
274 pub fn filter_enum_variant_templates(
275 mut self,
276 filter: Vec<String>,
277 inherit_all: bool,
278 variant_name_for_diagnostics: &'static str,
279 ) -> Self {
280 let mut filter_set: HashSet<String> = HashSet::from_iter(filter);
281 self.0
282 .retain(|name, _| filter_set.remove(name) || inherit_all);
283 if !filter_set.is_empty() {
284 // Unwrap: we know the set isn't empty, so it must have at least one entry
285 panic!("Enum variant {variant_name_for_diagnostics} specified template \"{}\" which was not defined on the variant's fields", filter_set.iter().next().unwrap());
286 }
287 self
288 }
289
290 pub fn merge_enum_template_sets(
291 template_sets: Vec<TransactionTemplateSet>,
292 type_name_for_diagnostics: &'static str,
293 ) -> Self {
294 // Eventual return value
295 let mut final_template_set = Self::default();
296
297 // For every variant (one entry in the input vec), ensure none of the templates were
298 // already seen in previous variants, then adjust the template for the discriminant and add
299 // it to the output
300 for (discriminant, set) in template_sets.into_iter().enumerate() {
301 for (name, mut template) in set.0.into_iter() {
302 if final_template_set.0.contains_key(&name) {
303 panic!("Different variants of the enum {} both contained definitions for the transaction template \"{}\". At this time, only one enum branch can be defined for any transaction template.", type_name_for_diagnostics, name);
304 }
305 template.prepend_discriminant(
306 discriminant
307 .try_into()
308 .expect("Enum discriminants are only supported up to size u8"),
309 );
310 final_template_set.0.insert(name, template);
311 }
312 }
313
314 final_template_set
315 }
316}