1

I have an auxiliary struct:

#[derive(SpreadAllocate, SpreadLayout)]
#[cfg_attr(feature = "std", derive(TypeInfo, ink_storage::traits::StorageLayout))]
pub struct MerkleTree<const LEAVES: u32> {
    nodes: Mapping<u32, Hash>,
    next_free_leaf: u32,
}

/// The only way of constructing `MerkleTree`.
impl<const LEAVES: u32> Default for MerkleTree<LEAVES> {
    fn default() -> Self {
        if !LEAVES.is_power_of_two() {
            panic!("Please have 2^n leaves")
        }

        Self {
            nodes: Mapping::default(),
            next_free_leaf: LEAVES,
        }
    }
}

And I use it in my contract:

#[ink(storage)]
#[derive(SpreadAllocate)]
pub struct Contract {
    tree: MerkleTree<1024>,
}

The contract has a single constructor:

impl Contract {
    #[ink(constructor)]
    pub fn new() -> Self {
        ink_lang::utils::initialize_contract(|blender: &mut Self|{})
    }
...
}

I observe that my struct (tree field is not initialized properly) - next_free_leaf is set to 0 (which is Default::default() for u32). Thus my constructor couldn't have been called. I suppose that the mapping inside this struct might not have been initialized properly. How should I initialize such structs properly?

I'm working with ink 3.4.0 (I have to).

4

2 Answers 2

2

The problem is here that the Test value passed to initialize_contract is initialized with SpreadAllocate::allocate_spread. You are expecting that it would be initialized using your Default implementation. This is why you need to manually override this value in initialize_contract to make it work as expected.

This behaviour is really confusing and should have never made it into ink!. This is why we got rid of SpreadAllocate and initialize_contract in ink!4.

2

As a followup, there's a smaller version of the contract that suffers from the same problems:

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

use ink_lang as ink;

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

    #[derive(Debug, SpreadAllocate, SpreadLayout)]
    #[cfg_attr(feature = "std", derive(scale_info::TypeInfo, ink_storage::traits::StorageLayout))]
    pub struct Inner {
        value: u32,
    }

    impl Default for Inner {
        fn default() -> Self {
            Inner {
                value: 100
            }
        }
    }

    #[ink(storage)]
    #[derive(SpreadAllocate)]
    pub struct Test {
        inner: Inner,
    }

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

            })
        }
        
        #[ink(message)]
        pub fn get(&self) -> u32 {
            self.inner.value
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::test::Test;

    use super::*;

    #[ink::test]
    fn default_works() {
        let t = Test::new();
        assert_eq!(t.get(), 100)
    }

}

"Fortunately", the unit test fails as expected - the value returned from get() call is not 100 (as set in the default constructor) but 0.

7
  • you can update your constructor like this: Self {inner: Default::default()} and it works
    – Ganesh11
    Nov 16 at 7:25
  • but this won't work if Self has e.g. Mapping field as well, right? we would have to use ink_lang::utils::initialize_contract then Nov 16 at 7:45
  • I don't think so because Mapping can be initialize by Mapping::default() and they have excluded in updated version as you can view in this example
    – Ganesh11
    Nov 16 at 8:26
  • then it is in opposite to use.ink/4.0.0-alpha.1/datastructures/mapping/… Nov 16 at 8:29
  • As Mapping is in storage then ink_lang::utils::initialize_contract must be included inside constructor but In this example, I seperated Mapping to different struct and there is no requirement of ink_lang::utils::initialize_contract in constructor
    – Ganesh11
    Nov 16 at 8:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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