protos/
traits.rs

1use crate::categories::{BinaryCategories, TextCategories};
2use parity_scale_codec::{Compact, Decode, Encode, Input, Output};
3use scale_info::prelude::{boxed::Box, vec::Vec};
4
5#[cfg(feature = "std")]
6use serde::{Deserialize, Serialize};
7
8// For more info refer to:
9// https://github.com/fragcolor-xyz/shards/blob/devel/include/shards.h
10
11#[cfg(not(feature = "std"))]
12type String = Vec<u8>;
13
14/// Struct representing limits on numbers (such has min and max values)
15/// Sadly SCALE supports only unsigned integers, so we need to wrap the limits to u64 and unwrap them when decoding.
16#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
17#[derive(Clone, PartialEq, Debug, Eq, scale_info::TypeInfo)]
18pub struct Limits {
19  /// The minimum value
20  pub min: i64,
21  /// The maximum value
22  pub max: i64,
23  /// Only used when we representing floating point numbers as integers
24  /// The amount of scaling to apply to the fixed point value
25  /// Convert the limit values to float by dividing the fixed point values with 10^scale.
26  /// This allows us to derive a float representation of the limit values with the desired precision.
27  pub scale: u32,
28}
29
30fn wrap_to_u64(x: i64) -> u64 {
31  (x as u64).wrapping_add(u64::MAX / 2 + 1)
32}
33
34fn to_i64(x: u64) -> i64 {
35  ((x as i64) ^ (1 << 63)) & (1 << 63) | (x & (u64::MAX >> 1)) as i64
36}
37
38impl Encode for Limits {
39  fn encode_to<W: Output + ?Sized>(&self, dest: &mut W) {
40    Compact(wrap_to_u64(self.min)).encode_to(dest);
41    Compact(wrap_to_u64(self.max)).encode_to(dest);
42    Compact(self.scale).encode_to(dest);
43  }
44}
45
46impl Decode for Limits {
47  fn decode<I: Input>(input: &mut I) -> Result<Self, parity_scale_codec::Error> {
48    Ok(Self {
49      min: to_i64(Compact::<u64>::decode(input)?.into()),
50      max: to_i64(Compact::<u64>::decode(input)?.into()),
51      scale: Compact::<u32>::decode(input)?.into(),
52    })
53  }
54}
55
56/// Enum that represents the type of Code.
57///
58/// There are only two possible types of code:
59/// 1. Shard
60/// 2. Wire
61#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
62#[derive(Encode, Decode, Copy, Clone, PartialEq, Debug, Eq, scale_info::TypeInfo)]
63pub enum CodeType {
64  /// A collection of shards that can be injected into more complex blocks of code or wires.
65  Shards,
66  /// A single wire that can be executed.
67  Wire {
68    looped: Option<bool>,
69    pure: Option<bool>,
70  },
71}
72
73/// Struct that represents information about a Code.
74///
75/// Note: There are only two possible types of code: Shard and Wire.
76/// See the `CodeType` enum for more information.
77#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
78#[derive(Encode, Decode, Clone, PartialEq, Debug, Eq, scale_info::TypeInfo)]
79pub struct CodeInfo {
80  /// The type of code, either Shard or Wire.
81  pub kind: CodeType,
82  /// A list of variables that must be available to the code context before the code can be executed.
83  /// Each variable is represented as a tuple of its name and its type.
84  pub requires: Vec<(String, VariableType)>,
85  /// A list of variables that are available in the code context.
86  /// Each variable is represented as a tuple of its name and its type.
87  pub exposes: Vec<(String, VariableType)>,
88  /// A list of variable types that are inputted into the code.
89  pub inputs: Vec<VariableType>,
90  /// The variable type of the output generated by the code.
91  pub output: VariableType,
92}
93
94#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
95#[derive(Encode, Decode, Clone, PartialEq, Debug, Eq, scale_info::TypeInfo)]
96pub struct TableInfo {
97  /// The names of the keys. An empty key represents any name and allows multiple instances of the corresponding index type.
98  pub keys: Vec<String>,
99  /// The types expected for each key, following the keys array (should be the same length).
100  pub types: Vec<Vec<VariableType>>,
101}
102
103/// Enum represents all the possible types that a variable can be
104#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
105#[derive(Encode, Decode, Clone, PartialEq, Debug, Eq, scale_info::TypeInfo)]
106pub enum VariableType {
107  // No type
108  None,
109  // Any type
110  Any,
111  // Boolean type
112  Bool,
113  // Color type (vector of 4 8-bit unsigned integers)
114  Color,
115  // Binary data type
116  Bytes(Option<BinaryCategories>),
117  // String type
118  String(Option<TextCategories>),
119  // Image type
120  Image,
121  // Audio type
122  Audio,
123  // Shards Mesh type
124  Mesh,
125
126  // Enum type with vendor ID and type ID
127  Enum {
128    #[codec(compact)]
129    vendor_id: u32,
130    #[codec(compact)]
131    type_id: u32,
132  },
133
134  // Integer type with optional limits
135  Int(Option<Limits>),
136  // Vector of 2 integers with optional limits
137  Int2([Option<Limits>; 2]),
138  // Vector of 3 integers with optional limits
139  Int3([Option<Limits>; 3]),
140  // Vector of 4 integers with optional limits
141  Int4([Option<Limits>; 4]),
142  // Vector of 8 integers with optional limits
143  Int8([Option<Limits>; 8]),
144  // Vector of 16 integers with optional limits
145  Int16([Option<Limits>; 16]),
146
147  // Float type with optional limits
148  Float(Option<Limits>),
149  // Vector of 2 floats with optional limits
150  Float2([Option<Limits>; 2]),
151  // Vector of 3 floats with optional limits
152  Float3([Option<Limits>; 3]),
153  // Vector of 4 floats with optional limits
154  Float4([Option<Limits>; 4]),
155
156  // Sequence of variable types with optional length limits
157  Seq {
158    types: Vec<VariableType>,
159    length_limits: Option<Limits>,
160  },
161
162  // Table type
163  Table(TableInfo),
164
165  // Object type with vendor ID and type ID
166  Object {
167    #[codec(compact)]
168    vendor_id: u32,
169    #[codec(compact)]
170    type_id: u32,
171  },
172
173  // Code type with information
174  Code(Box<CodeInfo>),
175  // Channel type with variable type
176  Channel(Box<VariableType>),
177  // Event type with variable type
178  Event(Box<VariableType>),
179}
180
181/// Struct contains information about a variable type
182#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
183#[derive(Encode, Decode, Clone, PartialEq, Debug, Eq, scale_info::TypeInfo)]
184pub struct VariableTypeInfo {
185  /// The variable type
186  #[cfg_attr(feature = "std", serde(alias = "type"))]
187  pub type_: VariableType,
188  /// Raw-bytes representation of the default value of the variable type (optional)
189  pub default: Option<Vec<u8>>,
190}
191
192#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
193#[derive(Encode, Decode, Clone, PartialEq, Debug, Eq, scale_info::TypeInfo)]
194pub struct Record {
195  pub name: String,
196  pub types: Vec<VariableTypeInfo>,
197}
198
199impl From<(String, Vec<VariableTypeInfo>)> for Record {
200  fn from((name, types): (String, Vec<VariableTypeInfo>)) -> Self {
201    Self { name, types }
202  }
203}
204
205/// Struct represents a Trait
206#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
207#[derive(Encode, Decode, Clone, PartialEq, Debug, Eq, scale_info::TypeInfo)]
208pub struct Trait {
209  /// Name of the Trait
210  pub name: String,
211  /// List of attributes of the Trait. An attribute is represented as a **tuple that contains the attribute's name and the attribute's type**.
212  pub records: Vec<Record>,
213}
214
215#[cfg(test)]
216mod tests {
217  use super::*;
218  use crate::categories::TextCategories;
219
220  #[test]
221  fn encode_decode_simple_1() {
222    let mut trait1: Vec<Record> = vec![(
223      "int1".to_string(),
224      vec![VariableTypeInfo {
225        type_: VariableType::Int(None),
226        default: None,
227      }],
228    )
229      .into()];
230
231    // THIS IS the way we reprocess the trait declaration before sorting it on chain and hashing it
232    trait1 = trait1
233      .into_iter()
234      .map(|r| (r.name.to_lowercase(), r.types).into())
235      .collect();
236    trait1.dedup_by(|a, b| a.name == b.name);
237    // Note: "Strings are ordered lexicographically by their byte values ... This is not necessarily the same as “alphabetical” order, which varies by language and locale". Source: https://doc.rust-lang.org/std/primitive.str.html#impl-Ord-for-str
238    trait1.sort_by(|a, b| a.name.cmp(&b.name));
239
240    let trait1 = Trait {
241      name: "Trait1".to_string(),
242      records: trait1,
243    };
244
245    let e_trait1 = trait1.encode();
246
247    let d_trait1 = Trait::decode(&mut e_trait1.as_slice()).unwrap();
248
249    assert!(trait1 == d_trait1);
250  }
251
252  #[test]
253  fn encode_decode_boxed_1() {
254    let mut trait1: Vec<Record> = vec![
255      (
256        "int1".to_string(),
257        vec![VariableTypeInfo {
258          type_: VariableType::Int(None),
259          default: None,
260        }],
261      )
262        .into(),
263      (
264        "boxed1".to_string(),
265        vec![VariableTypeInfo {
266          type_: VariableType::Code(Box::new(CodeInfo {
267            kind: CodeType::Wire {
268              looped: None,
269              pure: None,
270            },
271            requires: vec![("int1".to_string(), VariableType::Int(None))],
272            exposes: vec![],
273            inputs: vec![],
274            output: VariableType::None,
275          })),
276          default: None,
277        }],
278      )
279        .into(),
280    ];
281
282    // THIS IS the way we reprocess the trait declaration before sorting it on chain and hashing it
283    trait1 = trait1
284      .into_iter()
285      .map(|r| (r.name.to_lowercase(), r.types).into())
286      .collect();
287    trait1.dedup_by(|a, b| a.name == b.name);
288    trait1.sort_by(|a, b| a.name.cmp(&b.name));
289
290    let trait1 = Trait {
291      name: "Trait1".to_string(),
292      records: trait1,
293    };
294
295    let e_trait1 = trait1.encode();
296
297    let d_trait1 = Trait::decode(&mut e_trait1.as_slice()).unwrap();
298
299    assert!(trait1 == d_trait1);
300    assert!(d_trait1.records[0].name == "boxed1".to_string());
301    let type_ = &d_trait1.records[0].types[0].type_;
302    let requires = match type_ {
303      VariableType::Code(code) => &code.requires,
304      _ => panic!("Should be a code"),
305    };
306    assert!(requires[0].0 == "int1".to_string());
307  }
308
309  #[test]
310  fn test_json_simple_1() {
311    let mut trait1: Vec<Record> = vec![(
312      "int1".to_string(),
313      vec![VariableTypeInfo {
314        type_: VariableType::Int(None),
315        default: None,
316      }],
317    )
318      .into()];
319
320    // THIS IS the way we reprocess the trait declaration before sorting it on chain and hashing it
321    trait1 = trait1
322      .into_iter()
323      .map(|r| (r.name.to_lowercase(), r.types).into())
324      .collect();
325    trait1.dedup_by(|a, b| a.name == b.name);
326    trait1.sort_by(|a, b| a.name.cmp(&b.name));
327
328    let trait1 = Trait {
329      name: "Trait1".to_string(),
330      records: trait1,
331    };
332
333    let e_trait1 = serde_json::to_string(&trait1).unwrap();
334
335    let d_trait1: Trait = serde_json::from_str(&e_trait1).unwrap();
336
337    assert!(trait1 == d_trait1);
338  }
339
340  #[test]
341  fn test_json_boxed_1() {
342    let mut trait1: Vec<Record> = vec![
343      (
344        "int1".to_string(),
345        vec![VariableTypeInfo {
346          type_: VariableType::Int(None),
347          default: None,
348        }],
349      )
350        .into(),
351      (
352        "boxed1".to_string(),
353        vec![VariableTypeInfo {
354          type_: VariableType::Code(Box::new(CodeInfo {
355            kind: CodeType::Wire {
356              looped: None,
357              pure: None,
358            },
359            requires: vec![("int1".to_string(), VariableType::Int(None))],
360            exposes: vec![],
361            inputs: vec![],
362            output: VariableType::None,
363          })),
364          default: None,
365        }],
366      )
367        .into(),
368    ];
369
370    // THIS IS the way we reprocess the trait declaration before sorting it on chain and hashing it
371    trait1 = trait1
372      .into_iter()
373      .map(|r| (r.name.to_lowercase(), r.types).into())
374      .collect();
375    trait1.dedup_by(|a, b| a.name == b.name);
376    trait1.sort_by(|a, b| a.name.cmp(&b.name));
377
378    let trait1 = Trait {
379      name: "Trait1".to_string(),
380      records: trait1,
381    };
382
383    let e_trait1 = serde_json::to_string(&trait1).unwrap();
384
385    let d_trait1: Trait = serde_json::from_str(&e_trait1).unwrap();
386
387    assert!(trait1 == d_trait1);
388    assert!(d_trait1.records[0].name == "boxed1".to_string());
389    let type_ = &d_trait1.records[0].types[0].type_;
390    let requires = match type_ {
391      VariableType::Code(code) => &code.requires,
392      _ => panic!("Should be a code"),
393    };
394    assert!(requires[0].0 == "int1".to_string());
395  }
396
397  #[test]
398  fn test_json_textual_from_str() {
399    let trait1 = Trait {
400      name: "Trait1".to_string(),
401      records: vec![(
402        "int1".to_string(),
403        vec![VariableTypeInfo {
404          type_: VariableType::Int(None),
405          default: None,
406        }],
407      )
408        .into()],
409    };
410
411    let json_trait1 = r#"{
412      "name": "Trait1",
413      "records": [
414        {
415          "name": "int1",
416          "types": [
417            {
418              "type": {"Int": null},
419              "default": null
420            }
421          ]
422        }
423      ]
424    }"#;
425
426    let d_trait1 = serde_json::from_str(&json_trait1).unwrap();
427
428    assert!(trait1 == d_trait1);
429  }
430
431  #[test]
432  fn test_json_textual_from_str_ambal() {
433    let json_trait1 = r#"{
434      "name": "AmbalLoreFragment",
435      "records": [
436        {
437          "name": "banner",
438          "types": [
439            {"type": "Image"}
440          ]
441        },
442        {
443          "name": "content",
444          "types": [
445            {"type": {"String": "markdown"}}
446          ]
447        }
448      ]
449    }"#;
450
451    let d_trait1 = serde_json::from_str(&json_trait1).unwrap();
452
453    let trait1 = Trait {
454      name: "AmbalLoreFragment".to_string(),
455      records: vec![
456        (
457          "banner".to_string(),
458          vec![VariableTypeInfo {
459            type_: VariableType::Image,
460            default: None,
461          }
462          .into()],
463        )
464          .into(),
465        (
466          "content".to_string(),
467          vec![VariableTypeInfo {
468            type_: VariableType::String(Some(TextCategories::Markdown)),
469            default: None,
470          }
471          .into()],
472        )
473          .into(),
474      ],
475    };
476
477    assert!(trait1 == d_trait1);
478  }
479
480  #[test]
481  fn test_limits() {
482    let limits = Limits {
483      min: -100,
484      max: 100,
485      scale: 2,
486    };
487
488    let expected_min = -1.0;
489    let expected_max = 1.0;
490
491    // Convert the limit values to float by dividing the fixed point values with 10^scale.
492    // This allows us to derive a float representation of the limit values with the desired precision.
493    let float_min = limits.min as f64 / 10u64.pow(limits.scale) as f64;
494    let float_max = limits.max as f64 / 10u64.pow(limits.scale) as f64;
495
496    assert_eq!(float_min, expected_min);
497    assert_eq!(float_max, expected_max);
498
499    let encoded = limits.encode();
500    let decoded = Limits::decode(&mut encoded.as_slice()).unwrap();
501    assert!(limits == decoded);
502  }
503}