substrate-constructor 0.2.1

Extrinsic constructor for Substrate based chains
Documentation
use external_memory_tools::ExternalMemory;
use frame_metadata::v14::{
    PalletStorageMetadata, StorageEntryMetadata, StorageEntryType, StorageHasher,
};
use parity_scale_codec::Encode;
use scale_info::{form::PortableForm, interner::UntrackedSymbol};
use sp_crypto_hashing::{blake2_128, blake2_256, twox_128, twox_256, twox_64};
use substrate_parser::{
    cards::Documented, decoding_sci::Ty, error::RegistryError, propagated::Propagated,
};

use std::any::TypeId;

use crate::error::StorageRegistryError;
use crate::fill_prepare::{prepare_type, TypeToFill};
use crate::finalize::{Finalize, TypeContent};
use crate::traits::{AsFillMetadata, AsPalletMetadata};

#[derive(Clone, Debug)]
#[allow(clippy::large_enum_variant)]
pub enum EntrySelector {
    Empty,
    Functional(EntrySelectorFunctional),
}

impl EntrySelector {
    pub fn init<E: ExternalMemory, M: AsFillMetadata<E>>(
        available_entries: &[StorageEntryMetadata<PortableForm>],
        ext_memory: &mut E,
        registry: &M::TypeRegistry,
    ) -> Result<Self, RegistryError<E>> {
        if available_entries.is_empty() {
            Ok(Self::Empty)
        } else {
            Ok(Self::Functional(EntrySelectorFunctional::new_at::<E, M>(
                available_entries,
                ext_memory,
                registry,
                0usize,
            )?))
        }
    }
}

#[derive(Clone, Debug)]
pub struct EntrySelectorFunctional {
    pub available_entries: Vec<StorageEntryMetadata<PortableForm>>,
    pub selected_entry: StorageEntry,
}

impl EntrySelectorFunctional {
    pub fn new_at<E: ExternalMemory, M: AsFillMetadata<E>>(
        available_entries: &[StorageEntryMetadata<PortableForm>],
        ext_memory: &mut E,
        registry: &M::TypeRegistry,
        selector_index: usize,
    ) -> Result<Self, RegistryError<E>> {
        let selected_entry_metadata = &available_entries[selector_index];
        let name = selected_entry_metadata.name.to_owned();
        let docs = selected_entry_metadata.collect_docs();
        let type_to_fill = match &selected_entry_metadata.ty {
            StorageEntryType::Plain(ty) => StorageEntryTypeToFill::Plain(*ty),
            StorageEntryType::Map {
                hashers,
                key,
                value,
            } => {
                let key_to_fill = prepare_type::<E, M>(
                    &Ty::Symbol(key),
                    ext_memory,
                    registry,
                    Propagated::new(),
                )?;
                StorageEntryTypeToFill::Map {
                    hashers: hashers.to_vec(),
                    key_to_fill,
                    value: *value,
                }
            }
        };
        Ok(Self {
            available_entries: available_entries.to_owned(),
            selected_entry: StorageEntry {
                selector_index_entry: selector_index,
                name,
                type_to_fill,
                docs,
            },
        })
    }

    pub fn selector_up<E: ExternalMemory, M: AsFillMetadata<E>>(
        &mut self,
        ext_memory: &mut E,
        registry: &M::TypeRegistry,
    ) -> Result<(), RegistryError<E>> {
        let new_selector_index = {
            if self.selected_entry.selector_index_entry + 1 == self.available_entries.len() {
                0
            } else {
                self.selected_entry.selector_index_entry + 1
            }
        };
        *self = EntrySelectorFunctional::new_at::<E, M>(
            &self.available_entries,
            ext_memory,
            registry,
            new_selector_index,
        )?;
        Ok(())
    }

    pub fn selector_down<E: ExternalMemory, M: AsFillMetadata<E>>(
        &mut self,
        ext_memory: &mut E,
        registry: &M::TypeRegistry,
    ) -> Result<(), RegistryError<E>> {
        let new_selector_index = {
            if self.selected_entry.selector_index_entry == 0 {
                self.available_entries.len() - 1
            } else {
                self.selected_entry.selector_index_entry - 1
            }
        };
        *self = EntrySelectorFunctional::new_at::<E, M>(
            &self.available_entries,
            ext_memory,
            registry,
            new_selector_index,
        )?;
        Ok(())
    }
}

#[derive(Clone, Debug)]
pub struct StorageEntry {
    pub selector_index_entry: usize,
    pub name: String,
    pub type_to_fill: StorageEntryTypeToFill,
    pub docs: String,
}

#[derive(Clone, Debug)]
#[allow(clippy::large_enum_variant)]
pub enum StorageEntryTypeToFill {
    Plain(UntrackedSymbol<TypeId>),
    Map {
        hashers: Vec<StorageHasher>,
        key_to_fill: TypeToFill,
        value: UntrackedSymbol<TypeId>,
    },
}

#[derive(Clone, Debug)]
#[allow(clippy::large_enum_variant)]
pub enum StorageSelector {
    Empty,
    Functional(StorageSelectorFunctional),
}

impl StorageSelector {
    pub fn init<E: ExternalMemory, M: AsFillMetadata<E>>(
        ext_memory: &mut E,
        metadata: &M,
    ) -> Result<Self, RegistryError<E>> {
        let mut available_pallets: Vec<PalletStorageMetadata<PortableForm>> = Vec::new();
        for pallet in metadata.pallets().into_iter() {
            if let Some(pallet_storage_metadata) = pallet.storage() {
                available_pallets.push(pallet_storage_metadata)
            }
        }
        if available_pallets.is_empty() {
            Ok(Self::Empty)
        } else {
            Ok(Self::Functional(StorageSelectorFunctional::new_at::<E, M>(
                &available_pallets,
                ext_memory,
                &metadata.types(),
                0usize,
            )?))
        }
    }
}

#[derive(Clone, Debug)]
pub struct StorageSelectorFunctional {
    pub available_pallets: Vec<PalletStorageMetadata<PortableForm>>,
    pub query: StorageQuery,
}

impl StorageSelectorFunctional {
    pub fn new_at<E: ExternalMemory, M: AsFillMetadata<E>>(
        available_pallets: &[PalletStorageMetadata<PortableForm>],
        ext_memory: &mut E,
        registry: &M::TypeRegistry,
        selector_index: usize,
    ) -> Result<Self, RegistryError<E>> {
        // TODO case bad index
        let selected_pallet_metadata = &available_pallets[selector_index];

        let query = StorageQuery {
            selector_index_pallet: selector_index,
            prefix: selected_pallet_metadata.prefix.to_owned(),
            entry_selector: EntrySelector::init::<E, M>(
                &selected_pallet_metadata.entries,
                ext_memory,
                registry,
            )?,
        };

        Ok(Self {
            available_pallets: available_pallets.to_owned(),
            query,
        })
    }

    pub fn selector_up<E: ExternalMemory, M: AsFillMetadata<E>>(
        &mut self,
        ext_memory: &mut E,
        registry: &M::TypeRegistry,
    ) -> Result<(), RegistryError<E>> {
        let new_selector_index = {
            if self.query.selector_index_pallet + 1 == self.available_pallets.len() {
                0
            } else {
                self.query.selector_index_pallet + 1
            }
        };
        *self = StorageSelectorFunctional::new_at::<E, M>(
            &self.available_pallets,
            ext_memory,
            registry,
            new_selector_index,
        )?;
        Ok(())
    }

    pub fn selector_down<E: ExternalMemory, M: AsFillMetadata<E>>(
        &mut self,
        ext_memory: &mut E,
        registry: &M::TypeRegistry,
    ) -> Result<(), RegistryError<E>> {
        let new_selector_index = {
            if self.query.selector_index_pallet == 0 {
                self.available_pallets.len() - 1
            } else {
                self.query.selector_index_pallet - 1
            }
        };
        *self = StorageSelectorFunctional::new_at::<E, M>(
            &self.available_pallets,
            ext_memory,
            registry,
            new_selector_index,
        )?;
        Ok(())
    }
}

#[derive(Clone, Debug)]
pub struct StorageQuery {
    pub selector_index_pallet: usize,
    pub prefix: String,
    pub entry_selector: EntrySelector,
}

#[derive(Clone, Debug)]
pub struct FinalizedStorageQuery {
    pub key: String,
    pub value_ty: UntrackedSymbol<TypeId>,
}

impl StorageQuery {
    pub fn finalize(&self) -> Result<Option<FinalizedStorageQuery>, StorageRegistryError> {
        if let EntrySelector::Functional(entry_selector_functional) = &self.entry_selector {
            let mut key = format!(
                "0x{}{}",
                hex::encode(twox_128(self.prefix.as_bytes())),
                hex::encode(twox_128(
                    entry_selector_functional.selected_entry.name.as_bytes()
                ))
            );
            match &entry_selector_functional.selected_entry.type_to_fill {
                StorageEntryTypeToFill::Plain(value_ty) => Ok(Some(FinalizedStorageQuery {
                    key,
                    value_ty: *value_ty,
                })),
                StorageEntryTypeToFill::Map {
                    hashers,
                    key_to_fill,
                    value,
                } => {
                    if let Some(type_content) = key_to_fill.finalize() {
                        if hashers.len() == 1 {
                            key.push_str(&hex::encode(hashed_key_element(
                                &type_content.encode(),
                                &hashers[0],
                            )));
                            Ok(Some(FinalizedStorageQuery {
                                key,
                                value_ty: *value,
                            }))
                        } else if let TypeContent::Tuple(tuple_elements) = type_content {
                            if tuple_elements.len() == hashers.len() {
                                let imax = tuple_elements.len();
                                for i in 0..imax {
                                    key.push_str(&hex::encode(hashed_key_element(
                                        &tuple_elements[i].encode(),
                                        &hashers[i],
                                    )));
                                }
                                Ok(Some(FinalizedStorageQuery {
                                    key,
                                    value_ty: *value,
                                }))
                            } else {
                                Err(StorageRegistryError::MapHashesNumberMismatch)
                            }
                        } else {
                            Err(StorageRegistryError::MapHashesNotATuple)
                        }
                    } else {
                        Ok(None)
                    }
                }
            }
        } else {
            Ok(None)
        }
    }
}

pub fn hashed_key_element(data: &[u8], hasher: &StorageHasher) -> Vec<u8> {
    match hasher {
        StorageHasher::Blake2_128 => blake2_128(data).to_vec(),
        StorageHasher::Blake2_256 => blake2_256(data).to_vec(),
        StorageHasher::Blake2_128Concat => [blake2_128(data).to_vec(), data.to_vec()].concat(),
        StorageHasher::Twox128 => twox_128(data).to_vec(),
        StorageHasher::Twox256 => twox_256(data).to_vec(),
        StorageHasher::Twox64Concat => [twox_64(data).to_vec(), data.to_vec()].concat(),
        StorageHasher::Identity => data.to_vec(),
    }
}