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

Proposal to initialize the contract on demand #1388

Open
xgreenx opened this issue Sep 7, 2022 · 5 comments
Open

Proposal to initialize the contract on demand #1388

xgreenx opened this issue Sep 7, 2022 · 5 comments
Labels
C-discussion An issue for discussion for a given topic.

Comments

@xgreenx
Copy link
Collaborator

xgreenx commented Sep 7, 2022

With the introduction of delegate_call, we can have situations when one contract X uses another contract Y as a blueprint. The blueprint may have totally another storage layout. Then the X do the first delegate_call to Y, Y can't load the contract's storage from the X's storage because it is not initialized(Y's constructor was not called).

It creates a situation when we need support initializing the storage during the execution of the #[ink(message)]. The user can always wrap his storage into another structure and implement the Storable trait for this struct that supports initialization on demand. But we can add native support of it into the ink!.

The proposal is to introduce the OnCallInitialize trait.

/// The contract can implement that trait to support initialization on the runtime
 /// if it is unable to pull from the storage.
 ///
 /// It can be in several cases:
 /// - The contract doesn't have a constructor. That initializer can be an alternative for the constructor.
 /// - The constructor was not called due to upgradeability, `Proxy` or `Diamond` pattern.
 /// - The storage was moved or corrupted.
 ///
 /// If the trait is not implemented, the storage behavior is the default.
 /// It should be first initialized by the constructor.
 pub trait OnCallInitializer: Default {
     /// `Default::default` creates the instance of the contract.
     /// After the `initialize` method is called on that instance.
     /// The developer can do everything that he wants during initialization or do nothing.
     fn initialize(&mut self);
 }

If the contract implements Default and OnCallInitialize traits, the ink! will try to load the contract from the storage, and in the case of fail, it will initialize it.

 impl<T: OnCallInitializer + Storable> PullOrInit<T> {
     #[allow(dead_code)]
     pub fn pull_or_init(key: &Key) -> T {
         let maybe_instance = ink_env::get_contract_storage::<Key, T>(key);
         match maybe_instance {
             Ok(None) | Err(_) => {
                 let mut instance = Default::default();
                 <T as OnCallInitializer>::initialize(&mut instance);
                 instance
             }
             Ok(Some(storage)) => storage,
         }
     }
 }

This idea was implemented in #1331. But we decided to discuss it separately because it is an important API change. After implementation "initialization on demand" we need to return back delegate-call example, removed in the mentioned PR.

@ascjones
Copy link
Collaborator

ascjones commented Sep 27, 2022

Some downsides of this are

  • Lack of guarantees about who triggers this or when the initialization occurs. It may indeed still be safe but I think it is safer to be able to control the who and the when.
  • It adds some language complexity requiring knowledge of the behaviour of the OnCallInitializer (hence the detailed comments required on the OnCallInitializer trait.

Just a few alternatives for brainstorming purposes:

  1. Add a special type of message which does not enforce pre-loading of contract state, with a signature much like a constructor
    #[ink(initialize)]
    pub fn init(args: ...) -> Self
    
  2. Another angle: How can we do this without modifying the language at all? One way would be to deploy a special initialization/migration contract with a single message that performs the init/migration. The storage could implement a custom Storable implementation that would handle the loading of empty/incompatible storage. Admittedly this would be a bit of a pain, but this upgrade pattern could be built into the tooling rather than the language itself?
  3. Can/should we modify pallet_contracts at all to allow for this special (common?) case of a one time delegate initialization function?

@HCastano HCastano added the C-discussion An issue for discussion for a given topic. label Nov 23, 2022
@WookashWackomy
Copy link

@ascjones Any updates on that matter?

@ascjones
Copy link
Collaborator

Not yet, this won't make it into the upcoming 4.0 release but is a good candidate for 4.1.

In the meantime you could investigate an approach similar to that outlined in 2., although I haven't attempted it myself.

@zabuxx
Copy link

zabuxx commented May 22, 2023

Hi, are there any updates on this issue?

@ascjones
Copy link
Collaborator

Not yet, unfortunately. It may be something that @xgreenx will be able to pick up soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-discussion An issue for discussion for a given topic.
Projects
None yet
Development

No branches or pull requests

5 participants