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

Support Result<Self, E> returning ink! constructors #988

Closed
Robbepop opened this issue Oct 29, 2021 · 9 comments · Fixed by #1446
Closed

Support Result<Self, E> returning ink! constructors #988

Robbepop opened this issue Oct 29, 2021 · 9 comments · Fixed by #1446
Assignees
Labels
A-ink_lang [ink_lang] Work item E-help wanted Extra attention is needed P-high High priority issue.

Comments

@Robbepop
Copy link
Collaborator

Robbepop commented Oct 29, 2021

Currently it is not possible to return anything but Self in ink! constructors.
This is an artificial limitation created to make things a bit easier, however, technically as well as from a design perspective nothing speaks against introducing fallible ink! constructors.
They will behave in the same way to fallible ink! messages in that they will revert state in case Result::Err is returned.

Mainly ink_lang_ir as well as ink_lang_codegen crates are required to be modified in order to implement this feature.

After this change ink! constructors can either return Self or Result<Self, E> where E is some error type:

use ink_lang as ink;

#[ink::contract]
mod contract {
    #[ink(storage)]
    pub struct Contract {
        value: Balance,
    }

    enum Error {
        TooMuchInitValue,
    }

    impl Contract {
        #[ink(constructor)]
        fn new() -> Self {
            self.value = 0;
        }

        #[ink(constructor)]
        fn with_balance(balance: Balance) -> Result<Self, Error> {
            if balance >= 1000 {
                return Err(Error::TooMuchInitValue)
            }
            Self { value: balance }
        }
    }
}
@Robbepop Robbepop added the A-ink_lang [ink_lang] Work item label Oct 29, 2021
@cmichi cmichi added E-help wanted Extra attention is needed P-high High priority issue. labels Mar 15, 2022
@HCastano HCastano assigned HCastano and unassigned Robbepop May 11, 2022
@cmichi cmichi mentioned this issue Jul 28, 2022
@athei athei moved this to Backlog 🗒 in Smart Contracts Aug 10, 2022
@cmichi cmichi assigned SkymanOne and unassigned HCastano Sep 5, 2022
@SkymanOne
Copy link
Contributor

After some fiddling, we can use the same approach for failable constructors as one for messages, but it would require including [derive(scale::Encode)] for the storage struct, not sure what implications it will have.

Otherwise, we need a different approach where we deduce whether the constructor has a Result return type and have different token parsing for each variant

@cmichi
Copy link
Collaborator

cmichi commented Oct 13, 2022

it would require including [derive(scale::Encode)] for the storage struct, not sure what implications it will have.

I'm just wondering why we don't require this bound for #[ink(storage)] as of now or derive scale::Encode by default for it. All storage items need to be encoded to storage anyway. So I can't see a negative implication. @xgreenx I'm interested in your thoughts?

@xgreenx
Copy link
Collaborator

xgreenx commented Oct 13, 2022

I guess [derive(scale::Encode)] helped you because now you store in the storage Result<Contract>, instead of Contract =)

Why just not use the approach that we did for messages?
is_result_type and is_result_err works well, we also need to add one more macro to extract Self from the Result<Self>, but you can do it in the same way as we did it for is_result_err.

@SkymanOne
Copy link
Contributor

@xgreenx - This is exactly the approach I am following. As you correctly mentioned, there is already partial support for failable constructors, it wasn't just used at the time.

@cmichi - I also realised that, it absolutely makes sense for storage to also derive scale::Encode since the Result output should be encoded whether it is the error or storage itself.

@xgreenx
Copy link
Collaborator

xgreenx commented Oct 13, 2022

I also realised that, it absolutely makes sense for storage to also derive scale::Encode since the Result output should be encoded whether it is the error or storage itself.

If the type implements scale::Encode, it means that it can be transferred across contracts. We can't transfer the storage itself, only {Contract}Ref so contract ref should implement scale::Encode and scale::Decode. If the result is Ok(Contract) we should use Storable methods to push the contract into the storage.

@SkymanOne
Copy link
Contributor

I also realised that, it absolutely makes sense for storage to also derive scale::Encode since the Result output should be encoded whether it is the error or storage itself.

If the type implements scale::Encode, it means that it can be transferred across contracts. We can't transfer the storage itself, only {Contract}Ref so contract ref should implement scale::Encode and scale::Decode. If the result is Ok(Contract) we should use Storable methods to push the contract into the storage.

I was thinking of manually checking the result output. If the constructor fails => return encoded Error, otherwise push the contract to storage.

@xgreenx
Copy link
Collaborator

xgreenx commented Oct 13, 2022

I was thinking of manually checking the result output. If the constructor fails => return encoded Error, otherwise push the contract to storage.

Yeah, it is what we need to do, but to push the contract into the storage we don't need to impl scale::Encode

@xgreenx
Copy link
Collaborator

xgreenx commented Oct 13, 2022

The usage of Result in the constructor means that the CallBuilder part({Contract}Ref implementation) also should support it.

We have two possible solutions to do it:

  • Encode Result<{Contract}Ref> and decode it. It means that we need to pass encoded AccountId between contracts.
  • Encode only error, decode this error in the {Contract}Ref implementation and if it is not an error, then return Ok(AccountId.into()), if error, return Err(ContractsError)

@SkymanOne
Copy link
Contributor

SkymanOne commented Oct 17, 2022

messages

While this is true, is it necessary bad? The reason why the storage derives scale::Encode is because there is a trait bound in ::ink::env::return_value()

Event existing implementation of execute_constructor() support failable constructors and enforces the return type to derive scale::Encode

Repository owner moved this from Backlog 🗒 to Done ✅ in Smart Contracts Oct 27, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ink_lang [ink_lang] Work item E-help wanted Extra attention is needed P-high High priority issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants