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 Result<Self, Error> return type for constructors #1446

Merged
merged 30 commits into from
Oct 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7a4193b
constructors return Err abort transaction + started example
Sep 22, 2022
e9af561
return type parser
Sep 25, 2022
89ec79c
Merge branch 'master' into gn/constructor-err
Sep 25, 2022
2cd9dd9
add output type to dispatchable constructor
Sep 25, 2022
fdad4dc
codegen adds Result to return type
Sep 26, 2022
47fba58
add type cheking
Sep 26, 2022
109536d
fix CALLABLE type
Sep 26, 2022
ab3897f
add comments for refactoring
Oct 12, 2022
4d6766f
ugly solution for failable contracts
Oct 12, 2022
c0ef46c
Merge branch 'master' into gn/constructor-err
Oct 13, 2022
10126f2
avoid unnecessary error check for constructors
Oct 17, 2022
2fa1ac0
avoids encoding storage, adds demo example
Oct 17, 2022
e2649c5
allows differents syntax format of result return type
Oct 21, 2022
8d79fef
cargo fmt
Oct 21, 2022
89e1789
add changes to CHANGELOG.md
Oct 21, 2022
36de262
add tests to the example
Oct 23, 2022
50cf900
add UI tests
Oct 23, 2022
c372352
return comment to dns example
Oct 23, 2022
52da525
fix formatting in UI examples
Oct 23, 2022
6cea984
fix tests and docs
Oct 24, 2022
d7dcb59
apply formatting suggestions
Oct 24, 2022
9d00fd2
change example and UI tests for more clarity
Oct 24, 2022
f35ef98
add imports for UI test
Oct 24, 2022
0532993
refactor execution.rs
Oct 26, 2022
7b4d56c
remove syntactical check for constructors return type
Oct 26, 2022
0f8abd1
UI test for type aliases
Oct 26, 2022
b5e99fe
LLVM optimisation and refactoring
Oct 27, 2022
ae37fe6
updated docs
Oct 27, 2022
10bbbc6
refactoring and moving feature demo to mother contract
Oct 27, 2022
b94d31f
remove cargo-contract install in docker
Oct 27, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,6 @@ examples-contract-build:
<<: *test-refs
script:
- rustup component add rust-src --toolchain stable
- cargo install cargo-contract --git https://github.com/paritytech/cargo-contract --version 2.0.0-alpha.4 --force
- cargo contract -V
- for example in examples/*/; do
if [ "$example" = "examples/upgradeable-contracts/" ]; then continue; fi;
Expand Down
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
- Allows to use `Result<Self, Error>` as a return type in constructors - [#1446](https://github.com/paritytech/ink/pull/1446)

## Version 4.0.0-alpha.3

Expand All @@ -22,7 +23,7 @@ crate. All existing sub-crates are reexported and should be used via the new `in
- Replace all usages of individual crates with reexports, e.g. `ink_env` ➜ `ink::env`.

#### Storage Rework
[#1331](https://github.com/paritytech/ink/pull/1331) changes the way `ink!` works with contract storage. Storage keys
[#1331](https://github.com/paritytech/ink/pull/1331) changes the way `ink!` works with contract storage. Storage keys
are generated at compile-time, and user facing abstractions which determine how contract data is laid out in storage
have changed.

Expand Down
8 changes: 5 additions & 3 deletions crates/ink/codegen/src/generator/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,16 +256,19 @@ impl Dispatch<'_> {
let payable = constructor.is_payable();
let selector_id = constructor.composed_selector().into_be_u32().hex_padded_suffixed();
let selector_bytes = constructor.composed_selector().hex_lits();
let output_tuple_type = constructor.output().map(quote::ToTokens::to_token_stream)
.unwrap_or_else(|| quote! { () });
let input_bindings = generator::input_bindings(constructor.inputs());
let input_tuple_type = generator::input_types_tuple(constructor.inputs());
let input_tuple_bindings = generator::input_bindings_tuple(constructor.inputs());
quote_spanned!(constructor_span=>
impl ::ink::reflect::DispatchableConstructorInfo<#selector_id> for #storage_ident {
type Input = #input_tuple_type;
type Output = #output_tuple_type;
type Storage = #storage_ident;

const CALLABLE: fn(Self::Input) -> Self::Storage = |#input_tuple_bindings| {
#storage_ident::#constructor_ident( #( #input_bindings ),* )
const CALLABLE: fn(Self::Input) -> Self::Output = |#input_tuple_bindings| {
#storage_ident::#constructor_ident(#( #input_bindings ),* )
};
const PAYABLE: ::core::primitive::bool = #payable;
const SELECTOR: [::core::primitive::u8; 4usize] = [ #( #selector_bytes ),* ];
Expand Down Expand Up @@ -559,7 +562,6 @@ impl Dispatch<'_> {
}>>::IDS[#index]
}>>::PAYABLE
);

quote_spanned!(constructor_span=>
Self::#constructor_ident(input) => {
if #any_constructor_accept_payment && #deny_payment {
Expand Down
3 changes: 2 additions & 1 deletion crates/ink/codegen/src/generator/item_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,10 +239,11 @@ impl ItemImpls<'_> {
let ident = constructor.ident();
let inputs = constructor.inputs();
let statements = constructor.statements();
let output = constructor.output();
quote_spanned!(span =>
#( #attrs )*
#[cfg(not(feature = "__ink_dylint_Constructor"))]
#vis fn #ident( #( #inputs ),* ) -> Self {
#vis fn #ident( #( #inputs ),* ) -> #output {
#( #statements )*
}
)
Expand Down
80 changes: 23 additions & 57 deletions crates/ink/ir/src/ir/item_impl/constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,40 +84,18 @@ impl quote::ToTokens for Constructor {
}

impl Constructor {
/// Returns `true` if the given type is `Self`.
fn type_is_self_val(ty: &syn::Type) -> bool {
matches!(ty, syn::Type::Path(syn::TypePath {
qself: None,
path
}) if path.is_ident("Self"))
}

/// Ensures that the return type of the ink! constructor is `Self`.
///
/// Returns an appropriate error otherwise.
/// Ensure that the constructor has a return.
/// Returns an error otherwise.
///
/// # Errors
///
/// If the ink! constructor does not return `Self` or is missing a return
/// type entirely.
fn ensure_valid_return_type(
method_item: &syn::ImplItemMethod,
) -> Result<(), syn::Error> {
match &method_item.sig.output {
syn::ReturnType::Default => {
return Err(format_err_spanned!(
&method_item.sig,
"missing return for ink! constructor",
))
}
syn::ReturnType::Type(_, return_type) => {
if !Self::type_is_self_val(return_type.as_ref()) {
return Err(format_err_spanned!(
return_type,
"ink! constructors must return Self",
))
}
}
/// If the ink! constructor is missing a return type.
fn ensure_return(method_item: &syn::ImplItemMethod) -> Result<(), syn::Error> {
if let syn::ReturnType::Default = &method_item.sig.output {
return Err(format_err_spanned!(
&method_item.sig,
"missing return for ink! constructor",
))
}
Ok(())
}
Expand Down Expand Up @@ -172,7 +150,7 @@ impl TryFrom<syn::ImplItemMethod> for Constructor {

fn try_from(method_item: syn::ImplItemMethod) -> Result<Self, Self::Error> {
ensure_callable_invariants(&method_item, CallableKind::Constructor)?;
Self::ensure_valid_return_type(&method_item)?;
Self::ensure_return(&method_item)?;
Self::ensure_no_self_receiver(&method_item)?;
let (ink_attrs, other_attrs) = Self::sanitize_attributes(&method_item)?;
let is_payable = ink_attrs.is_payable();
Expand Down Expand Up @@ -241,6 +219,14 @@ impl Constructor {
pub fn attrs(&self) -> &[syn::Attribute] {
&self.item.attrs
}

/// Returns the return type of the ink! constructor if any.
pub fn output(&self) -> Option<&syn::Type> {
match &self.item.sig.output {
syn::ReturnType::Default => None,
syn::ReturnType::Type(_, return_type) => Some(return_type),
}
}
}

#[cfg(test)]
Expand Down Expand Up @@ -390,6 +376,11 @@ mod tests {
#[ink(constructor)]
fn my_constructor(input1: i32, input2: i64, input3: u32, input4: u64) -> Self {}
},
// Result return type
syn::parse_quote! {
#[ink(constructor)]
pub fn my_constructor() -> Result<Self, ()> {}
},
];
for item_method in item_methods {
assert!(<ir::Constructor as TryFrom<_>>::try_from(item_method).is_ok());
Expand Down Expand Up @@ -421,31 +412,6 @@ mod tests {
}
}

#[test]
fn try_from_invalid_return_fails() {
let item_methods: Vec<syn::ImplItemMethod> = vec![
syn::parse_quote! {
#[ink(constructor)]
fn my_constructor() -> &Self {}
},
syn::parse_quote! {
#[ink(constructor)]
pub fn my_constructor() -> &mut Self {}
},
syn::parse_quote! {
#[ink(constructor)]
pub fn my_constructor() -> i32 {}
},
syn::parse_quote! {
#[ink(constructor)]
pub fn my_constructor() -> Result<Self, ()> {}
},
];
for item_method in item_methods {
assert_try_from_fails(item_method, "ink! constructors must return Self")
}
}

Robbepop marked this conversation as resolved.
Show resolved Hide resolved
#[test]
fn try_from_invalid_self_receiver_fails() {
let item_methods: Vec<syn::ImplItemMethod> = vec![
Expand Down
56 changes: 11 additions & 45 deletions crates/ink/src/codegen/dispatch/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ use crate::reflect::{
ContractEnv,
DispatchError,
};
use core::{
convert::Infallible,
mem::ManuallyDrop,
};
use core::mem::ManuallyDrop;
use ink_env::{
Environment,
ReturnFlags,
Expand Down Expand Up @@ -58,7 +55,7 @@ pub fn execute_constructor<Contract, F, R>(f: F) -> Result<(), DispatchError>
where
Contract: Storable + StorageKey + ContractEnv,
F: FnOnce() -> R,
<private::Seal<R> as ConstructorReturnType<Contract>>::ReturnValue: Encode,
<private::Seal<R> as ConstructorReturnType<Contract>>::Error: Encode,
Robbepop marked this conversation as resolved.
Show resolved Hide resolved
private::Seal<R>: ConstructorReturnType<Contract>,
{
let result = ManuallyDrop::new(private::Seal(f()));
Expand All @@ -73,16 +70,13 @@ where
);
Ok(())
}
Err(_) => {
Err(error) => {
// Constructor is fallible and failed.
//
// We need to revert the state of the transaction.
ink_env::return_value::<
<private::Seal<R> as ConstructorReturnType<Contract>>::ReturnValue,
>(
ReturnFlags::default().set_reverted(true),
result.return_value(),
)
<private::Seal<R> as ConstructorReturnType<Contract>>::Error,
>(ReturnFlags::default().set_reverted(true), error)
}
}
}
Expand Down Expand Up @@ -119,65 +113,37 @@ pub trait ConstructorReturnType<C>: private::Sealed {
///
/// # Note
///
/// For infallible constructors this is `core::convert::Infallible`.
type Error;

/// The type of the return value of the constructor.
///
/// # Note
///
/// For infallible constructors this is `()` whereas for fallible
/// constructors this is the actual return value. Since we only ever
/// return a value in case of `Result::Err` the `Result::Ok` value
/// constructors this is the actual return error type. Since we only ever
/// return a value in case of `Result::Err` the `Result::Ok` value type
/// does not matter.
type ReturnValue;
type Error;

/// Converts the return value into a `Result` instance.
///
/// # Note
///
/// For infallible constructor returns this always yields `Ok`.
fn as_result(&self) -> Result<&C, &Self::Error>;

/// Returns the actual return value of the constructor.
///
/// # Note
///
/// For infallible constructor returns this always yields `()`
/// and is basically ignored since this does not get called
/// if the constructor did not fail.
fn return_value(&self) -> &Self::ReturnValue;
}

impl<C> ConstructorReturnType<C> for private::Seal<C> {
type Error = Infallible;
type ReturnValue = ();
type Error = ();

#[inline]
#[inline(always)]
fn as_result(&self) -> Result<&C, &Self::Error> {
Ok(&self.0)
}

#[inline]
fn return_value(&self) -> &Self::ReturnValue {
&()
}
}

impl<C, E> ConstructorReturnType<C> for private::Seal<Result<C, E>> {
const IS_RESULT: bool = true;
type Error = E;
type ReturnValue = Result<C, E>;

#[inline]
#[inline(always)]
fn as_result(&self) -> Result<&C, &Self::Error> {
self.0.as_ref()
}

#[inline]
fn return_value(&self) -> &Self::ReturnValue {
&self.0
}
}

/// Trait used to convert return types of contract initializer routines.
Expand Down
4 changes: 3 additions & 1 deletion crates/ink/src/reflect/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,9 +330,11 @@ pub trait DispatchableConstructorInfo<const ID: u32> {
type Input;
/// The ink! storage struct type.
type Storage;
/// Reflects the output type of the dispatchable ink! message.
type Output;

/// The closure that can be used to dispatch into the dispatchable ink! constructor.
const CALLABLE: fn(Self::Input) -> Self::Storage;
const CALLABLE: fn(Self::Input) -> Self::Output;

/// Yields `true` if the dispatchable ink! constructor is payable.
const PAYABLE: bool;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#[ink::contract]
mod contract {
#[ink(storage)]
pub struct Contract {}

#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
#[cfg_attr(feature = "std", derive(::scale_info::TypeInfo))]
pub enum Error {
Foo,
}

impl Contract {
#[ink(constructor)]
pub fn constructor() -> Result<u8, Error> {
Ok(5_u8)
}

#[ink(message)]
pub fn message(&self) {}
}
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error[E0277]: the trait bound `codegen::dispatch::execution::private::Seal<Result<u8, contract::Error>>: codegen::dispatch::execution::ConstructorReturnType<Contract>` is not satisfied
--> tests/ui/contract/fail/constructor-return-result-invalid.rs:14:9
|
14 | pub fn constructor() -> Result<u8, Error> {
| ^^^ the trait `codegen::dispatch::execution::ConstructorReturnType<Contract>` is not implemented for `codegen::dispatch::execution::private::Seal<Result<u8, contract::Error>>`
|
= help: the following other types implement trait `codegen::dispatch::execution::ConstructorReturnType<C>`:
codegen::dispatch::execution::private::Seal<C>
codegen::dispatch::execution::private::Seal<Result<C, E>>
note: required by a bound in `execute_constructor`
--> src/codegen/dispatch/execution.rs
|
| private::Seal<R>: ConstructorReturnType<Contract>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `execute_constructor`
SkymanOne marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#[ink::contract]
mod contract {
#[ink(storage)]
pub struct Contract {}

#[derive(Debug, PartialEq, Eq)]
pub enum Error {
Foo,
}

impl Contract {
#[ink(constructor)]
pub fn constructor() -> Result<Self, Error> {
Ok(Self {})
}

#[ink(message)]
pub fn message(&self) {}
}
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
error[E0277]: the trait bound `contract::Error: WrapperTypeEncode` is not satisfied
--> tests/ui/contract/fail/constructor-return-result-non-codec-error.rs:13:9
|
13 | pub fn constructor() -> Result<Self, Error> {
| ^^^ the trait `WrapperTypeEncode` is not implemented for `contract::Error`
|
= help: the following other types implement trait `WrapperTypeEncode`:
&T
&mut T
Arc<T>
Box<T>
Cow<'a, T>
Rc<T>
String
Vec<T>
parity_scale_codec::Ref<'a, T, U>
= note: required because of the requirements on the impl of `Encode` for `contract::Error`
note: required by a bound in `execute_constructor`
--> src/codegen/dispatch/execution.rs
|
| <private::Seal<R> as ConstructorReturnType<Contract>>::Error: Encode,
| ^^^^^^ required by this bound in `execute_constructor`
Loading