#[cfg(test)]
mod tests;
mod validate;
use core::fmt::Display;
pub use validate::ValidateLayout;
use crate::{
serde_hex,
utils::{
deserialize_from_byte_str,
serialize_as_byte_str,
},
};
use derive_more::From;
use ink_prelude::collections::btree_map::BTreeMap;
use ink_primitives::Key;
use scale::{
Decode,
Encode,
};
use scale_info::{
form::{
Form,
MetaForm,
PortableForm,
},
meta_type,
IntoPortable,
Registry,
TypeInfo,
};
use schemars::JsonSchema;
use serde::{
de::{
DeserializeOwned,
Error,
},
Deserialize,
Serialize,
};
#[derive(
Debug, PartialEq, Eq, PartialOrd, Ord, From, Serialize, Deserialize, JsonSchema,
)]
#[serde(bound(
serialize = "F::Type: Serialize, F::String: Serialize",
deserialize = "F::Type: DeserializeOwned, F::String: DeserializeOwned"
))]
#[serde(rename_all = "camelCase")]
pub enum Layout<F: Form = MetaForm> {
Leaf(LeafLayout<F>),
Root(RootLayout<F>),
Hash(HashLayout<F>),
Array(ArrayLayout<F>),
Struct(StructLayout<F>),
Enum(EnumLayout<F>),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, From, JsonSchema)]
pub struct LayoutKey {
key: Key,
}
impl serde::Serialize for LayoutKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serde_hex::serialize(&self.key.encode(), serializer)
}
}
impl<'de> serde::Deserialize<'de> for LayoutKey {
fn deserialize<D>(d: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let mut arr = [0; 4];
serde_hex::deserialize_check_len(d, serde_hex::ExpectedLen::Exact(&mut arr[..]))?;
let key = Key::decode(&mut &arr[..]).map_err(|err| {
Error::custom(format!("Error decoding layout key: {}", err))
})?;
Ok(key.into())
}
}
impl<'a> From<&'a Key> for LayoutKey {
fn from(key: &'a Key) -> Self {
Self { key: *key }
}
}
impl LayoutKey {
pub fn new<T>(key: T) -> Self
where
T: Into<u32>,
{
Self { key: key.into() }
}
pub fn key(&self) -> &Key {
&self.key
}
}
#[derive(
Debug, PartialEq, Eq, PartialOrd, Ord, From, Serialize, Deserialize, JsonSchema,
)]
#[serde(bound(
serialize = "F::Type: Serialize, F::String: Serialize",
deserialize = "F::Type: DeserializeOwned, F::String: DeserializeOwned"
))]
pub struct RootLayout<F: Form = MetaForm> {
#[schemars(with = "String")]
root_key: LayoutKey,
layout: Box<Layout<F>>,
ty: <F as Form>::Type,
}
impl IntoPortable for RootLayout {
type Output = RootLayout<PortableForm>;
fn into_portable(self, registry: &mut Registry) -> Self::Output {
RootLayout {
root_key: self.root_key,
layout: Box::new(self.layout.into_portable(registry)),
ty: registry.register_type(&self.ty),
}
}
}
impl RootLayout<MetaForm> {
pub fn new_empty<L>(root_key: LayoutKey, layout: L) -> Self
where
L: Into<Layout<MetaForm>>,
{
Self::new::<L>(root_key, layout, meta_type::<()>())
}
}
impl<F> RootLayout<F>
where
F: Form,
{
pub fn new<L>(root_key: LayoutKey, layout: L, ty: <F as Form>::Type) -> Self
where
L: Into<Layout<F>>,
{
Self {
root_key,
layout: Box::new(layout.into()),
ty,
}
}
pub fn root_key(&self) -> &LayoutKey {
&self.root_key
}
pub fn layout(&self) -> &Layout<F> {
&self.layout
}
pub fn ty(&self) -> &F::Type {
&self.ty
}
}
#[derive(
Debug, PartialEq, Eq, PartialOrd, Ord, From, Serialize, Deserialize, JsonSchema,
)]
#[serde(bound(
serialize = "F::Type: Serialize, F::String: Serialize",
deserialize = "F::Type: DeserializeOwned, F::String: DeserializeOwned"
))]
pub struct LeafLayout<F: Form = MetaForm> {
#[schemars(with = "String")]
key: LayoutKey,
ty: <F as Form>::Type,
}
impl LeafLayout {
pub fn from_key<T>(key: LayoutKey) -> Self
where
T: TypeInfo + 'static,
{
Self {
key,
ty: meta_type::<T>(),
}
}
}
impl IntoPortable for LeafLayout {
type Output = LeafLayout<PortableForm>;
fn into_portable(self, registry: &mut Registry) -> Self::Output {
LeafLayout {
key: self.key,
ty: registry.register_type(&self.ty),
}
}
}
impl IntoPortable for Layout {
type Output = Layout<PortableForm>;
fn into_portable(self, registry: &mut Registry) -> Self::Output {
match self {
Layout::Leaf(encoded_cell) => {
Layout::Leaf(encoded_cell.into_portable(registry))
}
Layout::Root(encoded_cell) => {
Layout::Root(encoded_cell.into_portable(registry))
}
Layout::Hash(hash_layout) => {
Layout::Hash(hash_layout.into_portable(registry))
}
Layout::Array(array_layout) => {
Layout::Array(array_layout.into_portable(registry))
}
Layout::Struct(struct_layout) => {
Layout::Struct(struct_layout.into_portable(registry))
}
Layout::Enum(enum_layout) => {
Layout::Enum(enum_layout.into_portable(registry))
}
}
}
}
impl<F> LeafLayout<F>
where
F: Form,
{
pub fn key(&self) -> &LayoutKey {
&self.key
}
pub fn ty(&self) -> &F::Type {
&self.ty
}
pub fn new(key: LayoutKey, ty: <F as Form>::Type) -> Self {
Self { key, ty }
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema)]
#[serde(bound(
serialize = "F::Type: Serialize, F::String: Serialize",
deserialize = "F::Type: DeserializeOwned, F::String: DeserializeOwned"
))]
pub struct HashLayout<F: Form = MetaForm> {
#[schemars(with = "String")]
offset: LayoutKey,
strategy: HashingStrategy,
layout: Box<Layout<F>>,
}
impl IntoPortable for HashLayout {
type Output = HashLayout<PortableForm>;
fn into_portable(self, registry: &mut Registry) -> Self::Output {
HashLayout {
offset: self.offset,
strategy: self.strategy,
layout: Box::new(self.layout.into_portable(registry)),
}
}
}
impl HashLayout {
pub fn new<K, L>(offset: K, strategy: HashingStrategy, layout: L) -> Self
where
K: Into<LayoutKey>,
L: Into<Layout>,
{
Self {
offset: offset.into(),
strategy,
layout: Box::new(layout.into()),
}
}
}
impl<F> HashLayout<F>
where
F: Form,
{
pub fn offset(&self) -> &LayoutKey {
&self.offset
}
pub fn strategy(&self) -> &HashingStrategy {
&self.strategy
}
pub fn layout(&self) -> &Layout<F> {
&self.layout
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema)]
pub struct HashingStrategy {
hasher: CryptoHasher,
#[serde(
serialize_with = "serialize_as_byte_str",
deserialize_with = "deserialize_from_byte_str"
)]
prefix: Vec<u8>,
#[serde(
serialize_with = "serialize_as_byte_str",
deserialize_with = "deserialize_from_byte_str"
)]
postfix: Vec<u8>,
}
impl HashingStrategy {
pub fn new(hasher: CryptoHasher, prefix: Vec<u8>, postfix: Vec<u8>) -> Self {
Self {
hasher,
prefix,
postfix,
}
}
pub fn hasher(&self) -> &CryptoHasher {
&self.hasher
}
pub fn prefix(&self) -> &[u8] {
&self.prefix
}
pub fn postfix(&self) -> &[u8] {
&self.postfix
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema)]
pub enum CryptoHasher {
Blake2x256,
Sha2x256,
Keccak256,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema)]
#[serde(bound(
serialize = "F::Type: Serialize, F::String: Serialize",
deserialize = "F::Type: DeserializeOwned, F::String: DeserializeOwned"
))]
#[serde(rename_all = "camelCase")]
pub struct ArrayLayout<F: Form = MetaForm> {
#[schemars(with = "String")]
offset: LayoutKey,
len: u32,
layout: Box<Layout<F>>,
}
impl ArrayLayout {
pub fn new<K, L>(at: K, len: u32, layout: L) -> Self
where
K: Into<LayoutKey>,
L: Into<Layout>,
{
Self {
offset: at.into(),
len,
layout: Box::new(layout.into()),
}
}
}
#[allow(clippy::len_without_is_empty)]
impl<F> ArrayLayout<F>
where
F: Form,
{
pub fn offset(&self) -> &LayoutKey {
&self.offset
}
pub fn len(&self) -> u32 {
self.len
}
pub fn layout(&self) -> &Layout<F> {
&self.layout
}
}
impl IntoPortable for ArrayLayout {
type Output = ArrayLayout<PortableForm>;
fn into_portable(self, registry: &mut Registry) -> Self::Output {
ArrayLayout {
offset: self.offset,
len: self.len,
layout: Box::new(self.layout.into_portable(registry)),
}
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema)]
#[serde(bound(
serialize = "F::Type: Serialize, F::String: Serialize",
deserialize = "F::Type: DeserializeOwned, F::String: DeserializeOwned"
))]
pub struct StructLayout<F: Form = MetaForm> {
name: F::String,
fields: Vec<FieldLayout<F>>,
}
impl<F> StructLayout<F>
where
F: Form,
{
pub fn new<N, T>(name: N, fields: T) -> Self
where
N: Into<F::String>,
T: IntoIterator<Item = FieldLayout<F>>,
{
Self {
name: name.into(),
fields: fields.into_iter().collect(),
}
}
pub fn name(&self) -> &F::String {
&self.name
}
pub fn fields(&self) -> &[FieldLayout<F>] {
&self.fields
}
}
impl IntoPortable for StructLayout {
type Output = StructLayout<PortableForm>;
fn into_portable(self, registry: &mut Registry) -> Self::Output {
StructLayout {
name: self.name.to_string(),
fields: self
.fields
.into_iter()
.map(|field| field.into_portable(registry))
.collect::<Vec<_>>(),
}
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema)]
#[serde(bound(
serialize = "F::Type: Serialize, F::String: Serialize",
deserialize = "F::Type: DeserializeOwned, F::String: DeserializeOwned"
))]
pub struct FieldLayout<F: Form = MetaForm> {
name: F::String,
layout: Layout<F>,
}
impl<F> FieldLayout<F>
where
F: Form,
{
pub fn new<N, L>(name: N, layout: L) -> Self
where
N: Into<F::String>,
L: Into<Layout<F>>,
{
Self {
name: name.into(),
layout: layout.into(),
}
}
pub fn name(&self) -> &F::String {
&self.name
}
pub fn layout(&self) -> &Layout<F> {
&self.layout
}
}
impl IntoPortable for FieldLayout {
type Output = FieldLayout<PortableForm>;
fn into_portable(self, registry: &mut Registry) -> Self::Output {
FieldLayout {
name: self.name.to_string(),
layout: self.layout.into_portable(registry),
}
}
}
#[derive(
Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
)]
pub struct Discriminant(usize);
impl From<usize> for Discriminant {
fn from(value: usize) -> Self {
Self(value)
}
}
impl Discriminant {
pub fn value(&self) -> usize {
self.0
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema)]
#[serde(bound(
serialize = "F::Type: Serialize, F::String: Serialize",
deserialize = "F::Type: DeserializeOwned, F::String: DeserializeOwned"
))]
#[serde(rename_all = "camelCase")]
pub struct EnumLayout<F: Form = MetaForm> {
name: F::String,
#[schemars(with = "String")]
dispatch_key: LayoutKey,
variants: BTreeMap<Discriminant, StructLayout<F>>,
}
impl EnumLayout {
pub fn new<N, K, V>(name: N, dispatch_key: K, variants: V) -> Self
where
N: Into<<MetaForm as Form>::String>,
K: Into<LayoutKey>,
V: IntoIterator<Item = (Discriminant, StructLayout)>,
{
Self {
name: name.into(),
dispatch_key: dispatch_key.into(),
variants: variants.into_iter().collect(),
}
}
}
impl<F> EnumLayout<F>
where
F: Form,
{
pub fn name(&self) -> &F::String {
&self.name
}
pub fn dispatch_key(&self) -> &LayoutKey {
&self.dispatch_key
}
pub fn variants(&self) -> &BTreeMap<Discriminant, StructLayout<F>> {
&self.variants
}
}
impl IntoPortable for EnumLayout {
type Output = EnumLayout<PortableForm>;
fn into_portable(self, registry: &mut Registry) -> Self::Output {
EnumLayout {
name: self.name.to_string(),
dispatch_key: self.dispatch_key,
variants: self
.variants
.into_iter()
.map(|(discriminant, layout)| {
(discriminant, layout.into_portable(registry))
})
.collect(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MetadataError {
Collision(String, String),
}
impl Display for MetadataError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match self {
Self::Collision(prev_path, curr_path) => {
write!(
f,
"storage key collision occurred for `{}`. \
The same storage key is occupied by the `{}`.",
curr_path,
if prev_path.is_empty() {
"contract storage"
} else {
prev_path
}
)
}
}
}
}
#[test]
fn valid_error_message() {
assert_eq!(
MetadataError::Collision("".to_string(), "Contract.c:".to_string()).to_string(),
"storage key collision occurred for `Contract.c:`. \
The same storage key is occupied by the `contract storage`."
);
assert_eq!(
MetadataError::Collision("Contract.a:".to_string(), "Contract.c:".to_string())
.to_string(),
"storage key collision occurred for `Contract.c:`. \
The same storage key is occupied by the `Contract.a:`."
)
}