#![recursion_limit = "256"] //! Provides procedural derive macros for the `Encode` and `Decode` traits of the `eth2_ssz` crate. //! //! ## Attributes //! //! The following struct/enum attributes are available: //! //! - `#[ssz(enum_behaviour = "union")]`: encodes and decodes an `enum` with a one-byte variant selector. //! - `#[ssz(enum_behaviour = "transparent")]`: allows encoding an `enum` by serializing only the //! value whilst ignoring outermost the `enum`. //! - `#[ssz(struct_behaviour = "container")]`: encodes and decodes the `struct` as an SSZ //! "container". //! - `#[ssz(struct_behaviour = "transparent")]`: encodes and decodes a `struct` with exactly one //! non-skipped field as if the outermost `struct` does not exist. //! //! The following field attributes are available: //! //! - `#[ssz(with = "module")]`: uses the methods in `module` to implement `ssz::Encode` and //! `ssz::Decode`. This is useful when it's not possible to create an `impl` for that type //! (e.g. the type is defined in another crate). //! - `#[ssz(skip_serializing)]`: this field will not be included in the serialized SSZ vector. //! - `#[ssz(skip_deserializing)]`: this field will not be expected in the serialized //! SSZ vector and it will be initialized from a `Default` implementation. //! //! ## Examples //! //! ### Structs //! //! ```rust //! use ssz::{Encode, Decode}; //! use ssz_derive::{Encode, Decode}; //! //! /// Represented as an SSZ "list" wrapped in an SSZ "container". //! #[derive(Debug, PartialEq, Encode, Decode)] //! #[ssz(struct_behaviour = "container")] // "container" is the default behaviour //! struct TypicalStruct { //! foo: Vec //! } //! //! assert_eq!( //! TypicalStruct { foo: vec![42] }.as_ssz_bytes(), //! vec![4, 0, 0, 0, 42] //! ); //! //! assert_eq!( //! TypicalStruct::from_ssz_bytes(&[4, 0, 0, 0, 42]).unwrap(), //! TypicalStruct { foo: vec![42] }, //! ); //! //! /// Represented as an SSZ "list" *without* an SSZ "container". //! #[derive(Encode, Decode)] //! #[ssz(struct_behaviour = "transparent")] //! struct WrapperStruct { //! foo: Vec //! } //! //! assert_eq!( //! WrapperStruct { foo: vec![42] }.as_ssz_bytes(), //! vec![42] //! ); //! //! /// Represented as an SSZ "list" *without* an SSZ "container". The `bar` byte is ignored. //! #[derive(Debug, PartialEq, Encode, Decode)] //! #[ssz(struct_behaviour = "transparent")] //! struct WrapperStructSkippedField { //! foo: Vec, //! #[ssz(skip_serializing, skip_deserializing)] //! bar: u8, //! } //! //! assert_eq!( //! WrapperStructSkippedField { foo: vec![42], bar: 99 }.as_ssz_bytes(), //! vec![42] //! ); //! assert_eq!( //! WrapperStructSkippedField::from_ssz_bytes(&[42]).unwrap(), //! WrapperStructSkippedField { foo: vec![42], bar: 0 } //! ); //! //! /// Represented as an SSZ "list" *without* an SSZ "container". //! #[derive(Encode, Decode)] //! #[ssz(struct_behaviour = "transparent")] //! struct NewType(Vec); //! //! assert_eq!( //! NewType(vec![42]).as_ssz_bytes(), //! vec![42] //! ); //! //! /// Represented as an SSZ "list" *without* an SSZ "container". The `bar` byte is ignored. //! #[derive(Debug, PartialEq, Encode, Decode)] //! #[ssz(struct_behaviour = "transparent")] //! struct NewTypeSkippedField(Vec, #[ssz(skip_serializing, skip_deserializing)] u8); //! //! assert_eq!( //! NewTypeSkippedField(vec![42], 99).as_ssz_bytes(), //! vec![42] //! ); //! assert_eq!( //! NewTypeSkippedField::from_ssz_bytes(&[42]).unwrap(), //! NewTypeSkippedField(vec![42], 0) //! ); //! ``` //! //! ### Enums //! //! ```rust //! use ssz::{Encode, Decode}; //! use ssz_derive::{Encode, Decode}; //! //! /// Represented as an SSZ "union". //! #[derive(Debug, PartialEq, Encode, Decode)] //! #[ssz(enum_behaviour = "union")] //! enum UnionEnum { //! Foo(u8), //! Bar(Vec), //! } //! //! assert_eq!( //! UnionEnum::Foo(42).as_ssz_bytes(), //! vec![0, 42] //! ); //! assert_eq!( //! UnionEnum::from_ssz_bytes(&[1, 42, 42]).unwrap(), //! UnionEnum::Bar(vec![42, 42]), //! ); //! //! /// Represented as only the value in the enum variant. //! #[derive(Debug, PartialEq, Encode)] //! #[ssz(enum_behaviour = "transparent")] //! enum TransparentEnum { //! Foo(u8), //! Bar(Vec), //! } //! //! assert_eq!( //! TransparentEnum::Foo(42).as_ssz_bytes(), //! vec![42] //! ); //! assert_eq!( //! TransparentEnum::Bar(vec![42, 42]).as_ssz_bytes(), //! vec![42, 42] //! ); //! ``` use darling::{FromDeriveInput, FromMeta}; use proc_macro::TokenStream; use quote::quote; use std::convert::TryInto; use syn::{parse_macro_input, DataEnum, DataStruct, DeriveInput, Ident, Index}; /// The highest possible union selector value (higher values are reserved for backwards compatible /// extensions). const MAX_UNION_SELECTOR: u8 = 127; const ENUM_TRANSPARENT: &str = "transparent"; const ENUM_UNION: &str = "union"; const NO_ENUM_BEHAVIOUR_ERROR: &str = "enums require an \"enum_behaviour\" attribute with \ a \"transparent\" or \"union\" value, e.g., #[ssz(enum_behaviour = \"transparent\")]"; #[derive(Debug, FromDeriveInput)] #[darling(attributes(ssz))] struct StructOpts { #[darling(default)] enum_behaviour: Option, #[darling(default)] struct_behaviour: Option, } /// Field-level configuration. #[derive(Debug, Default, FromMeta)] struct FieldOpts { #[darling(default)] with: Option, #[darling(default)] skip_serializing: bool, #[darling(default)] skip_deserializing: bool, } enum Procedure<'a> { Struct { data: &'a syn::DataStruct, behaviour: StructBehaviour, }, Enum { data: &'a syn::DataEnum, behaviour: EnumBehaviour, }, } enum StructBehaviour { Container, Transparent, } enum EnumBehaviour { Union, Transparent, } impl<'a> Procedure<'a> { fn read(item: &'a DeriveInput) -> Self { let opts = StructOpts::from_derive_input(item).unwrap(); match &item.data { syn::Data::Struct(data) => { if opts.enum_behaviour.is_some() { panic!("cannot use \"enum_behaviour\" for a struct"); } match opts.struct_behaviour.as_deref() { Some("container") | None => Procedure::Struct { data, behaviour: StructBehaviour::Container, }, Some("transparent") => Procedure::Struct { data, behaviour: StructBehaviour::Transparent, }, Some(other) => panic!( "{} is not a valid struct behaviour, use \"container\" or \"transparent\"", other ), } } syn::Data::Enum(data) => { if opts.struct_behaviour.is_some() { panic!("cannot use \"struct_behaviour\" for an enum"); } match opts.enum_behaviour.as_deref() { Some("union") => Procedure::Enum { data, behaviour: EnumBehaviour::Union, }, Some("transparent") => Procedure::Enum { data, behaviour: EnumBehaviour::Transparent, }, Some(other) => panic!( "{} is not a valid enum behaviour, use \"container\" or \"transparent\"", other ), None => panic!("{}", NO_ENUM_BEHAVIOUR_ERROR), } } _ => panic!("ssz_derive only supports structs and enums"), } } } fn parse_ssz_fields( struct_data: &syn::DataStruct, ) -> Vec<(&syn::Type, Option<&syn::Ident>, FieldOpts)> { struct_data .fields .iter() .map(|field| { let ty = &field.ty; let ident = field.ident.as_ref(); let field_opts_candidates = field .attrs .iter() .filter(|attr| attr.path.get_ident().map_or(false, |ident| *ident == "ssz")) .collect::>(); if field_opts_candidates.len() > 1 { panic!("more than one field-level \"ssz\" attribute provided") } let field_opts = field_opts_candidates .first() .map(|attr| { let meta = attr.parse_meta().unwrap(); FieldOpts::from_meta(&meta).unwrap() }) .unwrap_or_default(); (ty, ident, field_opts) }) .collect() } /// Implements `ssz::Encode` for some `struct` or `enum`. #[proc_macro_derive(Encode, attributes(ssz))] pub fn ssz_encode_derive(input: TokenStream) -> TokenStream { let item = parse_macro_input!(input as DeriveInput); let procedure = Procedure::read(&item); match procedure { Procedure::Struct { data, behaviour } => match behaviour { StructBehaviour::Transparent => ssz_encode_derive_struct_transparent(&item, data), StructBehaviour::Container => ssz_encode_derive_struct(&item, data), }, Procedure::Enum { data, behaviour } => match behaviour { EnumBehaviour::Transparent => ssz_encode_derive_enum_transparent(&item, data), EnumBehaviour::Union => ssz_encode_derive_enum_union(&item, data), }, } } /// Derive `ssz::Encode` for a struct. /// /// Fields are encoded in the order they are defined. /// /// ## Field attributes /// /// - `#[ssz(skip_serializing)]`: the field will not be serialized. fn ssz_encode_derive_struct(derive_input: &DeriveInput, struct_data: &DataStruct) -> TokenStream { let name = &derive_input.ident; let (impl_generics, ty_generics, where_clause) = &derive_input.generics.split_for_impl(); let field_is_ssz_fixed_len = &mut vec![]; let field_fixed_len = &mut vec![]; let field_ssz_bytes_len = &mut vec![]; let field_encoder_append = &mut vec![]; for (ty, ident, field_opts) in parse_ssz_fields(struct_data) { if field_opts.skip_serializing { continue; } let ident = match ident { Some(ref ident) => ident, _ => panic!( "#[ssz(struct_behaviour = \"container\")] only supports named struct fields." ), }; if let Some(module) = field_opts.with { let module = quote! { #module::encode }; field_is_ssz_fixed_len.push(quote! { #module::is_ssz_fixed_len() }); field_fixed_len.push(quote! { #module::ssz_fixed_len() }); field_ssz_bytes_len.push(quote! { #module::ssz_bytes_len(&self.#ident) }); field_encoder_append.push(quote! { encoder.append_parameterized( #module::is_ssz_fixed_len(), |buf| #module::ssz_append(&self.#ident, buf) ) }); } else { field_is_ssz_fixed_len.push(quote! { <#ty as ssz::Encode>::is_ssz_fixed_len() }); field_fixed_len.push(quote! { <#ty as ssz::Encode>::ssz_fixed_len() }); field_ssz_bytes_len.push(quote! { self.#ident.ssz_bytes_len() }); field_encoder_append.push(quote! { encoder.append(&self.#ident) }); } } let output = quote! { impl #impl_generics ssz::Encode for #name #ty_generics #where_clause { fn is_ssz_fixed_len() -> bool { #( #field_is_ssz_fixed_len && )* true } fn ssz_fixed_len() -> usize { if ::is_ssz_fixed_len() { let mut len: usize = 0; #( len = len .checked_add(#field_fixed_len) .expect("encode ssz_fixed_len length overflow"); )* len } else { ssz::BYTES_PER_LENGTH_OFFSET } } fn ssz_bytes_len(&self) -> usize { if ::is_ssz_fixed_len() { ::ssz_fixed_len() } else { let mut len: usize = 0; #( if #field_is_ssz_fixed_len { len = len .checked_add(#field_fixed_len) .expect("encode ssz_bytes_len length overflow"); } else { len = len .checked_add(ssz::BYTES_PER_LENGTH_OFFSET) .expect("encode ssz_bytes_len length overflow for offset"); len = len .checked_add(#field_ssz_bytes_len) .expect("encode ssz_bytes_len length overflow for bytes"); } )* len } } fn ssz_append(&self, buf: &mut Vec) { let mut offset: usize = 0; #( offset = offset .checked_add(#field_fixed_len) .expect("encode ssz_append offset overflow"); )* let mut encoder = ssz::SszEncoder::container(buf, offset); #( #field_encoder_append; )* encoder.finalize(); } } }; output.into() } /// Derive `ssz::Encode` "transparently" for a struct which has exactly one non-skipped field. /// /// The single field is encoded directly, making the outermost `struct` transparent. /// /// ## Field attributes /// /// - `#[ssz(skip_serializing)]`: the field will not be serialized. fn ssz_encode_derive_struct_transparent( derive_input: &DeriveInput, struct_data: &DataStruct, ) -> TokenStream { let name = &derive_input.ident; let (impl_generics, ty_generics, where_clause) = &derive_input.generics.split_for_impl(); let ssz_fields = parse_ssz_fields(struct_data); let num_fields = ssz_fields .iter() .filter(|(_, _, field_opts)| !field_opts.skip_deserializing) .count(); if num_fields != 1 { panic!( "A \"transparent\" struct must have exactly one non-skipped field ({} fields found)", num_fields ); } let (index, (ty, ident, _field_opts)) = ssz_fields .iter() .enumerate() .find(|(_, (_, _, field_opts))| !field_opts.skip_deserializing) .expect("\"transparent\" struct must have at least one non-skipped field"); // Remove the `_usize` suffix from the value to avoid a compiler warning. let index = Index::from(index); let output = if let Some(field_name) = ident { quote! { impl #impl_generics ssz::Encode for #name #ty_generics #where_clause { fn is_ssz_fixed_len() -> bool { <#ty as ssz::Encode>::is_ssz_fixed_len() } fn ssz_fixed_len() -> usize { <#ty as ssz::Encode>::ssz_fixed_len() } fn ssz_bytes_len(&self) -> usize { self.#field_name.ssz_bytes_len() } fn ssz_append(&self, buf: &mut Vec) { self.#field_name.ssz_append(buf) } } } } else { quote! { impl #impl_generics ssz::Encode for #name #ty_generics #where_clause { fn is_ssz_fixed_len() -> bool { <#ty as ssz::Encode>::is_ssz_fixed_len() } fn ssz_fixed_len() -> usize { <#ty as ssz::Encode>::ssz_fixed_len() } fn ssz_bytes_len(&self) -> usize { self.#index.ssz_bytes_len() } fn ssz_append(&self, buf: &mut Vec) { self.#index.ssz_append(buf) } } } }; output.into() } /// Derive `ssz::Encode` for an enum in the "transparent" method. /// /// The "transparent" method is distinct from the "union" method specified in the SSZ specification. /// When using "transparent", the enum will be ignored and the contained field will be serialized as /// if the enum does not exist. Since an union variant "selector" is not serialized, it is not /// possible to reliably decode an enum that is serialized transparently. /// /// ## Limitations /// /// Only supports: /// - Enums with a single field per variant, where /// - All fields are variably sized from an SSZ-perspective (not fixed size). /// /// ## Panics /// /// Will panic at compile-time if the single field requirement isn't met, but will panic *at run /// time* if the variable-size requirement isn't met. fn ssz_encode_derive_enum_transparent( derive_input: &DeriveInput, enum_data: &DataEnum, ) -> TokenStream { let name = &derive_input.ident; let (impl_generics, ty_generics, where_clause) = &derive_input.generics.split_for_impl(); let (patterns, assert_exprs): (Vec<_>, Vec<_>) = enum_data .variants .iter() .map(|variant| { let variant_name = &variant.ident; if variant.fields.len() != 1 { panic!("ssz::Encode can only be derived for enums with 1 field per variant"); } let pattern = quote! { #name::#variant_name(ref inner) }; let ty = &(&variant.fields).into_iter().next().unwrap().ty; let type_assert = quote! { !<#ty as ssz::Encode>::is_ssz_fixed_len() }; (pattern, type_assert) }) .unzip(); let output = quote! { impl #impl_generics ssz::Encode for #name #ty_generics #where_clause { fn is_ssz_fixed_len() -> bool { assert!( #( #assert_exprs && )* true, "not all enum variants are variably-sized" ); false } fn ssz_bytes_len(&self) -> usize { match self { #( #patterns => inner.ssz_bytes_len(), )* } } fn ssz_append(&self, buf: &mut Vec) { match self { #( #patterns => inner.ssz_append(buf), )* } } } }; output.into() } /// Derive `ssz::Encode` for an `enum` following the "union" SSZ spec. /// /// The union selector will be determined based upon the order in which the enum variants are /// defined. E.g., the top-most variant in the enum will have a selector of `0`, the variant /// beneath it will have a selector of `1` and so on. /// /// # Limitations /// /// Only supports enums where each variant has a single field. fn ssz_encode_derive_enum_union(derive_input: &DeriveInput, enum_data: &DataEnum) -> TokenStream { let name = &derive_input.ident; let (impl_generics, ty_generics, where_clause) = &derive_input.generics.split_for_impl(); let patterns: Vec<_> = enum_data .variants .iter() .map(|variant| { let variant_name = &variant.ident; if variant.fields.len() != 1 { panic!("ssz::Encode can only be derived for enums with 1 field per variant"); } let pattern = quote! { #name::#variant_name(ref inner) }; pattern }) .collect(); let union_selectors = compute_union_selectors(patterns.len()); let output = quote! { impl #impl_generics ssz::Encode for #name #ty_generics #where_clause { fn is_ssz_fixed_len() -> bool { false } fn ssz_bytes_len(&self) -> usize { match self { #( #patterns => inner .ssz_bytes_len() .checked_add(1) .expect("encoded length must be less than usize::max_value"), )* } } fn ssz_append(&self, buf: &mut Vec) { match self { #( #patterns => { let union_selector: u8 = #union_selectors; debug_assert!(union_selector <= ssz::MAX_UNION_SELECTOR); buf.push(union_selector); inner.ssz_append(buf) }, )* } } } }; output.into() } /// Derive `ssz::Decode` for a struct or enum. #[proc_macro_derive(Decode, attributes(ssz))] pub fn ssz_decode_derive(input: TokenStream) -> TokenStream { let item = parse_macro_input!(input as DeriveInput); let procedure = Procedure::read(&item); match procedure { Procedure::Struct { data, behaviour } => match behaviour { StructBehaviour::Transparent => ssz_decode_derive_struct_transparent(&item, data), StructBehaviour::Container => ssz_decode_derive_struct(&item, data), }, Procedure::Enum { data, behaviour } => match behaviour { EnumBehaviour::Union => ssz_decode_derive_enum_union(&item, data), EnumBehaviour::Transparent => panic!( "Decode cannot be derived for enum_behaviour \"{}\", only \"{}\" is valid.", ENUM_TRANSPARENT, ENUM_UNION ), }, } } /// Implements `ssz::Decode` for some `struct`. /// /// Fields are decoded in the order they are defined. /// /// ## Field attributes /// /// - `#[ssz(skip_deserializing)]`: during de-serialization the field will be instantiated from a /// `Default` implementation. The decoder will assume that the field was not serialized at all /// (e.g., if it has been serialized, an error will be raised instead of `Default` overriding it). fn ssz_decode_derive_struct(item: &DeriveInput, struct_data: &DataStruct) -> TokenStream { let name = &item.ident; let (impl_generics, ty_generics, where_clause) = &item.generics.split_for_impl(); let mut register_types = vec![]; let mut field_names = vec![]; let mut fixed_decodes = vec![]; let mut decodes = vec![]; let mut is_fixed_lens = vec![]; let mut fixed_lens = vec![]; for (ty, ident, field_opts) in parse_ssz_fields(struct_data) { let ident = match ident { Some(ref ident) => ident, _ => panic!( "#[ssz(struct_behaviour = \"container\")] only supports named struct fields." ), }; field_names.push(quote! { #ident }); // Field should not be deserialized; use a `Default` impl to instantiate. if field_opts.skip_deserializing { decodes.push(quote! { let #ident = <_>::default(); }); fixed_decodes.push(quote! { let #ident = <_>::default(); }); continue; } let is_ssz_fixed_len; let ssz_fixed_len; let from_ssz_bytes; if let Some(module) = field_opts.with { let module = quote! { #module::decode }; is_ssz_fixed_len = quote! { #module::is_ssz_fixed_len() }; ssz_fixed_len = quote! { #module::ssz_fixed_len() }; from_ssz_bytes = quote! { #module::from_ssz_bytes(slice) }; register_types.push(quote! { builder.register_type_parameterized(#is_ssz_fixed_len, #ssz_fixed_len)?; }); decodes.push(quote! { let #ident = decoder.decode_next_with(|slice| #module::from_ssz_bytes(slice))?; }); } else { is_ssz_fixed_len = quote! { <#ty as ssz::Decode>::is_ssz_fixed_len() }; ssz_fixed_len = quote! { <#ty as ssz::Decode>::ssz_fixed_len() }; from_ssz_bytes = quote! { <#ty as ssz::Decode>::from_ssz_bytes(slice) }; register_types.push(quote! { builder.register_type::<#ty>()?; }); decodes.push(quote! { let #ident = decoder.decode_next()?; }); } fixed_decodes.push(quote! { let #ident = { start = end; end = end .checked_add(#ssz_fixed_len) .ok_or_else(|| ssz::DecodeError::OutOfBoundsByte { i: usize::max_value() })?; let slice = bytes.get(start..end) .ok_or_else(|| ssz::DecodeError::InvalidByteLength { len: bytes.len(), expected: end })?; #from_ssz_bytes? }; }); is_fixed_lens.push(is_ssz_fixed_len); fixed_lens.push(ssz_fixed_len); } let output = quote! { impl #impl_generics ssz::Decode for #name #ty_generics #where_clause { fn is_ssz_fixed_len() -> bool { #( #is_fixed_lens && )* true } fn ssz_fixed_len() -> usize { if ::is_ssz_fixed_len() { let mut len: usize = 0; #( len = len .checked_add(#fixed_lens) .expect("decode ssz_fixed_len overflow"); )* len } else { ssz::BYTES_PER_LENGTH_OFFSET } } fn from_ssz_bytes(bytes: &[u8]) -> std::result::Result { if ::is_ssz_fixed_len() { if bytes.len() != ::ssz_fixed_len() { return Err(ssz::DecodeError::InvalidByteLength { len: bytes.len(), expected: ::ssz_fixed_len(), }); } let mut start: usize = 0; let mut end = start; #( #fixed_decodes )* Ok(Self { #( #field_names, )* }) } else { let mut builder = ssz::SszDecoderBuilder::new(bytes); #( #register_types )* let mut decoder = builder.build()?; #( #decodes )* Ok(Self { #( #field_names, )* }) } } } }; output.into() } /// Implements `ssz::Decode` "transparently" for a `struct` with exactly one non-skipped field. /// /// The bytes will be decoded as if they are the inner field, without the outermost struct. The /// outermost struct will then be applied artificially. /// /// ## Field attributes /// /// - `#[ssz(skip_deserializing)]`: during de-serialization the field will be instantiated from a /// `Default` implementation. The decoder will assume that the field was not serialized at all /// (e.g., if it has been serialized, an error will be raised instead of `Default` overriding it). fn ssz_decode_derive_struct_transparent( item: &DeriveInput, struct_data: &DataStruct, ) -> TokenStream { let name = &item.ident; let (impl_generics, ty_generics, where_clause) = &item.generics.split_for_impl(); let ssz_fields = parse_ssz_fields(struct_data); let num_fields = ssz_fields .iter() .filter(|(_, _, field_opts)| !field_opts.skip_deserializing) .count(); if num_fields != 1 { panic!( "A \"transparent\" struct must have exactly one non-skipped field ({} fields found)", num_fields ); } let mut fields = vec![]; let mut wrapped_type = None; for (i, (ty, ident, field_opts)) in ssz_fields.into_iter().enumerate() { if let Some(name) = ident { if field_opts.skip_deserializing { fields.push(quote! { #name: <_>::default(), }); } else { fields.push(quote! { #name: <_>::from_ssz_bytes(bytes)?, }); wrapped_type = Some(ty); } } else { let index = syn::Index::from(i); if field_opts.skip_deserializing { fields.push(quote! { #index:<_>::default(), }); } else { fields.push(quote! { #index:<_>::from_ssz_bytes(bytes)?, }); wrapped_type = Some(ty); } } } let ty = wrapped_type.unwrap(); let output = quote! { impl #impl_generics ssz::Decode for #name #ty_generics #where_clause { fn is_ssz_fixed_len() -> bool { <#ty as ssz::Decode>::is_ssz_fixed_len() } fn ssz_fixed_len() -> usize { <#ty as ssz::Decode>::ssz_fixed_len() } fn from_ssz_bytes(bytes: &[u8]) -> std::result::Result { Ok(Self { #( #fields )* }) } } }; output.into() } /// Derive `ssz::Decode` for an `enum` following the "union" SSZ spec. fn ssz_decode_derive_enum_union(derive_input: &DeriveInput, enum_data: &DataEnum) -> TokenStream { let name = &derive_input.ident; let (impl_generics, ty_generics, where_clause) = &derive_input.generics.split_for_impl(); let (constructors, var_types): (Vec<_>, Vec<_>) = enum_data .variants .iter() .map(|variant| { let variant_name = &variant.ident; if variant.fields.len() != 1 { panic!("ssz::Encode can only be derived for enums with 1 field per variant"); } let constructor = quote! { #name::#variant_name }; let ty = &(&variant.fields).into_iter().next().unwrap().ty; (constructor, ty) }) .unzip(); let union_selectors = compute_union_selectors(constructors.len()); let output = quote! { impl #impl_generics ssz::Decode for #name #ty_generics #where_clause { fn is_ssz_fixed_len() -> bool { false } fn from_ssz_bytes(bytes: &[u8]) -> Result { // Sanity check to ensure the definition here does not drift from the one defined in // `ssz`. debug_assert_eq!(#MAX_UNION_SELECTOR, ssz::MAX_UNION_SELECTOR); let (selector, body) = ssz::split_union_bytes(bytes)?; match selector.into() { #( #union_selectors => { <#var_types as ssz::Decode>::from_ssz_bytes(body).map(#constructors) }, )* other => Err(ssz::DecodeError::UnionSelectorInvalid(other)) } } } }; output.into() } fn compute_union_selectors(num_variants: usize) -> Vec { let union_selectors = (0..num_variants) .map(|i| { i.try_into() .expect("union selector exceeds u8::max_value, union has too many variants") }) .collect::>(); let highest_selector = union_selectors .last() .copied() .expect("0-variant union is not permitted"); assert!( highest_selector <= MAX_UNION_SELECTOR, "union selector {} exceeds limit of {}, enum has too many variants", highest_selector, MAX_UNION_SELECTOR ); union_selectors }