Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a way to allocate a storage facility using spread (and packed) layouts. #961

Closed
Robbepop opened this issue Oct 15, 2021 · 1 comment
Closed

Comments

@Robbepop
Copy link
Collaborator

Robbepop commented Oct 15, 2021

Manual Storage Allocation

Motivation

When dealing with the Mapping storage facility introduced in #946 we clearly see that ink!'s current storage management and constructors do not allow for storage facilities that do not support caching their intermediate state.

So far this has not been a problem since all major storage facilities provided by the ink_storage crate did in fact cache their intermediate state and this was seen necessary due to the high costs of storage transactions (read/write).
However, due to the most recent developments it became clear that compiled Wasm file size are far more critical to the success of parachains. Caching, while efficient at runtime and good at preventing storage interaction costs, is very costly for Wasm file sizes.

Therefore we need a new ink_storage storage way of storage management that allows for uncached storage facilities.

Proposal

For this we want to extend the SpreadLayout and PackedLayout traits both by yet another trait method:

pub trait SpreadLayout {
    fn spread_push(&self, ptr: &mut KeyPtr);
    fn spread_pull(ptr: &mut KeyPtr) -> Self;
    fn spread_clean(&self, ptr: &mut KeyPtr);
    // the below trait method will be new
    fn spread_allocate(ptr: &mut KeyPtr) -> Self;
}

Now it might seem that SpreadLayout::spread_pull and SpreadLayout::spread_allocate are kind of similar.
However, while spread_pull actively pulls from contract storage in order to create the new Self instance spread_allocate won't touch contract storage and instead relies fully on the given KeyPtr parameter to fully initialize Self without storage look-ups.

For most types and especially Rust primitives SpreadLayout::spread_allocate will create those as their default values.
Ideally we could trivially implement SpreadLayout::spread_allocate for all T: Default where T does not already have a proper SpreadLayout::spread_allocate implementation. For this, however, we'd require spread_allocate to be defined in yet another trait and we'd also require the unstable Rust specialization feature. So let's maybe discuss this in the very future.

Usage

Storage facilities like the new Mapping could use the SpreadLayout::spread_allocate trait method in order to be properly initialized. Users would then initialize an ink! storage struct like the following ERC-20 example smart contract:

mod erc20 {
    #[ink(storage)]
    pub struct Erc20 {
        total_supply: Balance,
        balances: Mapping<AccountId, Balance>,
    }

    impl Erc20 {
        /// Initializes the ERC-20 smart contract.
        #[ink(constructor]
        fn new(initial_supply: Balance) -> Self {
            // Root key usually is initialized as `[0x00; 32]` in ink!:
            let root_key = Key::from([0x00; 32]);
            let mut ptr = KeyPtr::from(root_key);
            let mut contract = <Self as SpreadLayout>::spread_allocate(&mut ptr);
            contract.initialize(initial_supply);
            contract
        }

        /// Private and hidden initializer for the `new` constructor.
        fn initialize(&mut self, initial_supply: Balance) {
            let owner = self.env().caller();
            self.mapping.insert(owner, initial_supply);
            self.total_supply = initial_supply;
        }
    }
}

Downsides

The big downside of this approach is teachability: Users have to learn yet another way how to properly initialize their smart contracts. Funnily the proposed SpreadLayout extension could profit from syntactic sugar that allows for #[ink(initializer)] constructs that are similar to #[ink(constructor)] instances with the difference of not constructing Self but merely initializing an already constructed Self given a &mut Self reference just like in the very old day of ink! version 1.0 where this was the original constructor syntax.

@Robbepop
Copy link
Collaborator Author

Implemented in #978. Closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant