11

I'm a newbie with rust and substrate. I'm currently trying to build a PSP34 compatible smart contract (NFT) for documents. In this contract, I'd like to have the following structure:

#[derive(Default, Debug, Clone, Encode, Decode)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub struct DocumentInfo {
    // ...
    /// Mapping to check who has signed
    pub signatures: Mapping<AccountId, ()>,
}

Then, in the smart contract, I'm using OpenBrush and it has the following storage definition:

    #[ink(storage)]
    #[derive(SpreadAllocate, PSP34Storage)]
    pub struct DocumentContract {
        #[PSP34StorageField]
        psp34: PSP34Data,

        // Fields of current contract
        /// mapping from token id to `DocumentInfo`
        document_info: Mapping<Id, DocumentInfo>,
        /// the id of last document
        last_document_id: Id,
        /// ids no longer used (can be reused)
        freed_ids: Vec<Id>,
    }

The problem is that the signatures mapping gives me the following error:

 | #[derive(Default, Debug, Clone, Encode, Decode)]
   |                          ----- in this derive macro expansion
...
53 |     pub signatures: Mapping<AccountId, ()>,
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `Mapping<ink_env::AccountId, ()>`
   |
   = note: this error originates in the derive macro `Clone` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: the trait bound `Mapping<ink_env::AccountId, ()>: WrapperTypeEncode` is not satisfied
  --> traits/document.rs:39:33
   |
39 | #[derive(Default, Debug, Clone, Encode, Decode)]
   |                                 ^^^^^^ the trait `WrapperTypeEncode` is not implemented for `Mapping<ink_env::AccountId, ()>`
   |
   = note: required because of the requirements on the impl of `Encode` for `Mapping<ink_env::AccountId, ()>`
   = note: this error originates in the derive macro `Encode` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: the trait bound `Mapping<ink_env::AccountId, ()>: WrapperTypeDecode` is not satisfied
  --> traits/document.rs:53:5
   |
53 |     pub signatures: Mapping<AccountId, ()>,
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `Mapping<ink_env::AccountId, ()>`
   |
   = note: required because of the requirements on the impl of `parity_scale_codec::Decode` for `Mapping<ink_env::AccountId, ()>`

I kind of understand what the errors mean, but not really and I have no clue how to fix it. Would appreciate if someone could shine a light on this for me.

3 Answers 3

12

In your original question the errors have to do with you trying to derive traits on your struct which Mapping doesn't implement. For example, the #[derive(Clone)] macro tries to call clone() on every field in the struct - and Mapping doesn't implement Clone resulting in the errors you see.

Now, moving on to the Mapping inside a Mapping situation. It is possible to get the following to compile:

#![cfg_attr(not(feature = "std"), no_std)]

use ink_lang as ink;

#[ink::contract]
pub mod mapper {
    use ink_storage::{
        traits::{
            SpreadAllocate,
            SpreadLayout,
        },
        Mapping,
    };

    type Id = u8;

    #[derive(SpreadAllocate, SpreadLayout)]
    #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
    pub struct Map {
        map: Mapping<AccountId, ()>,
    }

    #[ink(storage)]
    #[derive(SpreadAllocate)]
    pub struct Mapper {
        map: Mapping<Id, Map>,
    }

    impl Mapper {
        #[ink(constructor)]
        pub fn new() -> Self {
            ink_lang::utils::initialize_contract(|_| {})
        }

        #[ink(message)]
        pub fn get(&mut self) {
            // self.map.get(0); <- Doesn't work!
            todo!()
        }
    }
}

However, if you try and use this it won't work.

The reason for this is the Mapping requires that values implement PackedLayout. This essentially says that the value should be able to be stored in a single storage cell.

Mapping itself does not implement PackedLayout since it cannot be stored in a single storage cell. Instead, a Mapping is "stored" across many storage cells, each corresponding to one of the entries in the map (I'm glossing over some details here, technically Mapping isn't stored anywhere).

This is why nested Mapping's are not supported.

You are correct in finding that the best approach here is to use a tuple as a key instead.

1
  • Thanks for the explanation! Apr 7, 2022 at 13:54
5

Found the reason for the problem. Basically it is because I'm using a mapping inside a mapping and that is not allowed (https://github.com/paritytech/ink/issues/1175). The solution is to have this information outside the struct as follows:

    #[ink(storage)]
    #[derive(SpreadAllocate, PSP34Storage)]
    pub struct DocumentContract {
        #[PSP34StorageField]
        psp34: PSP34Data,

        // Fields of current contract
        /// mapping from token id to `DocumentInfo`
        document_info: Mapping<Id, DocumentInfo>,
        /// Mapping with tuple key
        signatures: Mapping<(Id, AccountId), ()>,
    }
0
-2

only developers should answer this question but i have somthings to notice:

  1. you can use an array instead of Mapping for signatures
  2. the DocumentInfo needs #[ink(storage)] and any maps inside of it should be initialized with ink_lang::utils::initialize_contract inside new or any constructor of DocumentInfo
1
  • Thanks for the feedback. The reason I was trying to use a mapping is because it is more efficient both storage and computational-wise. I've found the reason for my issue. Apr 6, 2022 at 14:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.