Skip to content

Commit c7cc828

Browse files
RobbepopHCastano
andauthored
Follow-up: SpreadLayout (#995)
* implement const_fn Key::new constructor * add ContractRootKey trait to ink_lang crate * use qualified path * re-export ContractRootKey trait * automatically implement ContractRootKey for ink! contracts * implement #[derive(SpreadAllocate)] macro & tests * re-export #[derive(SpreadAllocate)] macro from ink_storage::traits * automatically implement SpreadAllocate for the ink! storage struct * extend execute_constructor to work with fallible ink! constructors Note that this commit does not yet allow to write fallible ink! constructors. * add initialize_contract utility method * re-export initialize_contract utility method * adjust ink! codegen for recent changes with execute_constructor * fix clippy warning * fix hunspell dictionary * add missing docs to codegen types * fix UI test * remove automatic SpreadAllocate implementation This is due to the fact that trivial_bounds feature is not yet stable. Otherwise this automatically generated implementation would be totally fine. We can re-add this feature once Rust adds trivial trait bounds. * Satisfy OCD in test * Remove mention of `enum`s from `derive_struct()` * Remove outdated comment about Clippy lint * RustFmt Co-authored-by: Hernando Castano <[email protected]>
1 parent 55a1620 commit c7cc828

File tree

13 files changed

+426
-17
lines changed

13 files changed

+426
-17
lines changed

.config/cargo_spellcheck.dic

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,4 @@ wildcard/S
103103
natively
104104
payability
105105
unpayable
106+
initializer

crates/lang/codegen/src/generator/dispatch.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,7 @@ impl Dispatch<'_> {
511511
.is_dynamic_storage_allocator_enabled();
512512
quote_spanned!(constructor_span=>
513513
Self::#constructor_ident(input) => {
514-
::ink_lang::codegen::execute_constructor::<#storage_ident, _>(
514+
::ink_lang::codegen::execute_constructor::<#storage_ident, _, _>(
515515
::ink_lang::codegen::ExecuteConstructorConfig {
516516
dynamic_storage_alloc: #is_dynamic_storage_allocation_enabled
517517
},

crates/lang/codegen/src/generator/storage.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ impl GenerateCode for Storage<'_> {
3939
quote! { use ::ink_lang::codegen::EmitEvent as _; }
4040
});
4141
quote_spanned!(storage_span =>
42-
#access_env_impls
4342
#storage_struct
43+
#access_env_impls
4444

4545
const _: () = {
4646
// Used to make `self.env()` and `Self::env()` available in message code.
@@ -106,6 +106,10 @@ impl Storage<'_> {
106106
impl ::ink_lang::reflect::ContractName for #ident {
107107
const NAME: &'static str = ::core::stringify!(#ident);
108108
}
109+
110+
impl ::ink_lang::codegen::ContractRootKey for #ident {
111+
const ROOT_KEY: ::ink_primitives::Key = ::ink_primitives::Key::new([0x00; 32]);
112+
}
109113
};
110114
)
111115
}

crates/lang/src/codegen/dispatch/execution.rs

Lines changed: 196 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,41 @@ use crate::reflect::{
1818
};
1919
use core::{
2020
any::TypeId,
21+
convert::Infallible,
2122
mem::ManuallyDrop,
2223
};
2324
use ink_env::{
2425
Environment,
2526
ReturnFlags,
2627
};
27-
use ink_primitives::Key;
28+
use ink_primitives::{
29+
Key,
30+
KeyPtr,
31+
};
2832
use ink_storage::{
2933
alloc,
3034
alloc::ContractPhase,
3135
traits::{
3236
pull_spread_root,
3337
push_spread_root,
38+
SpreadAllocate,
3439
SpreadLayout,
3540
},
3641
};
3742

43+
/// The root key of the ink! smart contract.
44+
///
45+
/// # Note
46+
///
47+
/// - This is the key where storage allocation, pushing and pulling is rooted
48+
/// using the `SpreadLayout` and `SpreadAllocate` traits primarily.
49+
/// - This trait is automatically implemented by the ink! codegen.
50+
/// - The existence of this trait allows to customize the root key in future
51+
/// versions of ink! if needed.
52+
pub trait ContractRootKey {
53+
const ROOT_KEY: Key;
54+
}
55+
3856
/// Returns `Ok` if the caller did not transfer additional value to the callee.
3957
///
4058
/// # Errors
@@ -70,24 +88,192 @@ pub struct ExecuteConstructorConfig {
7088
/// The closure is supposed to already contain all the arguments that the real
7189
/// constructor message requires and forwards them.
7290
#[inline]
73-
pub fn execute_constructor<S, F>(
91+
pub fn execute_constructor<Contract, F, R>(
7492
config: ExecuteConstructorConfig,
7593
f: F,
7694
) -> Result<(), DispatchError>
7795
where
78-
S: ink_storage::traits::SpreadLayout,
79-
F: FnOnce() -> S,
96+
Contract: SpreadLayout + ContractRootKey,
97+
F: FnOnce() -> R,
98+
<private::Seal<R> as ConstructorReturnType<Contract>>::ReturnValue: scale::Encode,
99+
private::Seal<R>: ConstructorReturnType<Contract>,
80100
{
81101
if config.dynamic_storage_alloc {
82102
alloc::initialize(ContractPhase::Deploy);
83103
}
84-
let storage = ManuallyDrop::new(f());
85-
let root_key = Key::from([0x00; 32]);
86-
push_spread_root::<S>(&storage, &root_key);
87-
if config.dynamic_storage_alloc {
88-
alloc::finalize();
104+
let result = ManuallyDrop::new(private::Seal(f()));
105+
match result.as_result() {
106+
Ok(contract) => {
107+
// Constructor is infallible or is fallible but succeeded.
108+
//
109+
// This requires us to sync back the changes of the contract storage.
110+
let root_key = <Contract as ContractRootKey>::ROOT_KEY;
111+
push_spread_root::<Contract>(contract, &root_key);
112+
if config.dynamic_storage_alloc {
113+
alloc::finalize();
114+
}
115+
Ok(())
116+
}
117+
Err(_) => {
118+
// Constructor is fallible and failed.
119+
//
120+
// We need to revert the state of the transaction.
121+
ink_env::return_value::<
122+
<private::Seal<R> as ConstructorReturnType<Contract>>::ReturnValue,
123+
>(
124+
ReturnFlags::default().set_reverted(true),
125+
result.return_value(),
126+
)
127+
}
128+
}
129+
}
130+
131+
/// Initializes the ink! contract using the given initialization routine.
132+
///
133+
/// # Note
134+
///
135+
/// - This uses `SpreadAllocate` trait in order to default initialize the
136+
/// ink! smart contract before calling the initialization routine.
137+
/// - This either returns `Contract` or `Result<Contract, E>` depending
138+
/// on the return type `R` of the initializer closure `F`.
139+
/// If `R` is `()` then `Contract` is returned and if `R` is any type of
140+
/// `Result<(), E>` then `Result<Contract, E>` is returned.
141+
/// Other return types for `F` than the ones listed above are not allowed.
142+
#[inline]
143+
pub fn initialize_contract<Contract, F, R>(
144+
initializer: F,
145+
) -> <R as InitializerReturnType<Contract>>::Wrapped
146+
where
147+
Contract: ContractRootKey + SpreadAllocate,
148+
F: FnOnce(&mut Contract) -> R,
149+
R: InitializerReturnType<Contract>,
150+
{
151+
let mut key_ptr = KeyPtr::from(<Contract as ContractRootKey>::ROOT_KEY);
152+
let mut instance = <Contract as SpreadAllocate>::allocate_spread(&mut key_ptr);
153+
let result = initializer(&mut instance);
154+
result.into_wrapped(instance)
155+
}
156+
157+
mod private {
158+
/// Seals the implementation of `ContractInitializerReturnType`.
159+
pub trait Sealed {}
160+
impl Sealed for () {}
161+
impl<T, E> Sealed for Result<T, E> {}
162+
/// A thin-wrapper type that automatically seals its inner type.
163+
///
164+
/// Since it is private it can only be used from within this crate.
165+
/// We need this type in order to properly seal the `ConstructorReturnType`
166+
/// trait from unwanted external trait implementations.
167+
#[repr(transparent)]
168+
pub struct Seal<T>(pub T);
169+
impl<T> Sealed for Seal<T> {}
170+
}
171+
172+
/// Guards against using invalid contract initializer types.
173+
///
174+
/// # Note
175+
///
176+
/// Currently the only allowed types are `()` and `Result<(), E>`
177+
/// where `E` is some unspecified error type.
178+
/// If the contract initializer returns `Result::Err` the utility
179+
/// method that is used to initialize an ink! smart contract will
180+
/// revert the state of the contract instantiation.
181+
pub trait ConstructorReturnType<C>: private::Sealed {
182+
/// Is `true` if `Self` is `Result<C, E>`.
183+
const IS_RESULT: bool = false;
184+
185+
/// The error type of the constructor return type.
186+
///
187+
/// # Note
188+
///
189+
/// For infallible constructors this is `core::convert::Infallible`.
190+
type Error;
191+
192+
/// The type of the return value of the constructor.
193+
///
194+
/// # Note
195+
///
196+
/// For infallible constructors this is `()` whereas for fallible
197+
/// constructors this is the actual return value. Since we only ever
198+
/// return a value in case of `Result::Err` the `Result::Ok` value
199+
/// does not matter.
200+
type ReturnValue;
201+
202+
/// Converts the return value into a `Result` instance.
203+
///
204+
/// # Note
205+
///
206+
/// For infallible constructor returns this always yields `Ok`.
207+
fn as_result(&self) -> Result<&C, &Self::Error>;
208+
209+
/// Returns the actual return value of the constructor.
210+
///
211+
/// # Note
212+
///
213+
/// For infallible constructor returns this always yields `()`
214+
/// and is basically ignored since this does not get called
215+
/// if the constructor did not fail.
216+
fn return_value(&self) -> &Self::ReturnValue;
217+
}
218+
219+
impl<C> ConstructorReturnType<C> for private::Seal<C> {
220+
type Error = Infallible;
221+
type ReturnValue = ();
222+
223+
#[inline(always)]
224+
fn as_result(&self) -> Result<&C, &Self::Error> {
225+
Ok(&self.0)
226+
}
227+
228+
#[inline(always)]
229+
fn return_value(&self) -> &Self::ReturnValue {
230+
&()
231+
}
232+
}
233+
234+
impl<C, E> ConstructorReturnType<C> for private::Seal<Result<C, E>> {
235+
const IS_RESULT: bool = true;
236+
type Error = E;
237+
type ReturnValue = Result<C, E>;
238+
239+
#[inline(always)]
240+
fn as_result(&self) -> Result<&C, &Self::Error> {
241+
self.0.as_ref()
242+
}
243+
244+
#[inline(always)]
245+
fn return_value(&self) -> &Self::ReturnValue {
246+
&self.0
247+
}
248+
}
249+
250+
/// Trait used to convert return types of contract initializer routines.
251+
///
252+
/// Only `()` and `Result<(), E>` are allowed contract initializer return types.
253+
/// For `WrapReturnType<C>` where `C` is the contract type the trait converts
254+
/// `()` into `C` and `Result<(), E>` into `Result<C, E>`.
255+
pub trait InitializerReturnType<C>: private::Sealed {
256+
type Wrapped;
257+
258+
/// Performs the type conversion of the initialization routine return type.
259+
fn into_wrapped(self, wrapped: C) -> Self::Wrapped;
260+
}
261+
262+
impl<C> InitializerReturnType<C> for () {
263+
type Wrapped = C;
264+
265+
#[inline]
266+
fn into_wrapped(self, wrapped: C) -> C {
267+
wrapped
268+
}
269+
}
270+
impl<C, E> InitializerReturnType<C> for Result<(), E> {
271+
type Wrapped = Result<C, E>;
272+
273+
#[inline]
274+
fn into_wrapped(self, wrapped: C) -> Self::Wrapped {
275+
self.map(|_| wrapped)
89276
}
90-
Ok(())
91277
}
92278

93279
/// Configuration for execution of ink! messages.

crates/lang/src/codegen/dispatch/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ pub use self::{
2121
deny_payment,
2222
execute_constructor,
2323
finalize_message,
24+
initialize_contract,
2425
initiate_message,
26+
ContractRootKey,
2527
ExecuteConstructorConfig,
2628
ExecuteMessageConfig,
2729
},

crates/lang/src/codegen/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ pub use self::{
2626
deny_payment,
2727
execute_constructor,
2828
finalize_message,
29+
initialize_contract,
2930
initiate_message,
3031
ContractCallBuilder,
32+
ContractRootKey,
3133
DispatchInput,
3234
DispatchOutput,
3335
ExecuteConstructorConfig,

crates/lang/tests/ui/contract/fail/message-returns-non-codec.stderr

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ error[E0277]: the trait bound `NonCodecType: WrapperTypeEncode` is not satisfied
66
|
77
= note: required because of the requirements on the impl of `Encode` for `NonCodecType`
88
note: required by a bound in `DispatchOutput`
9-
--> src/codegen/dispatch/type_check.rs:69:8
9+
--> src/codegen/dispatch/type_check.rs
1010
|
11-
69 | T: scale::Encode + 'static;
11+
| T: scale::Encode + 'static;
1212
| ^^^^^^^^^^^^^ required by this bound in `DispatchOutput`
1313

1414
error[E0277]: the trait bound `NonCodecType: WrapperTypeEncode` is not satisfied
@@ -21,9 +21,9 @@ error[E0277]: the trait bound `NonCodecType: WrapperTypeEncode` is not satisfied
2121
|
2222
= note: required because of the requirements on the impl of `Encode` for `NonCodecType`
2323
note: required by a bound in `finalize_message`
24-
--> src/codegen/dispatch/execution.rs:169:8
24+
--> src/codegen/dispatch/execution.rs
2525
|
26-
169 | R: scale::Encode + 'static,
26+
| R: scale::Encode + 'static,
2727
| ^^^^^^^^^^^^^ required by this bound in `finalize_message`
2828

2929
error[E0599]: the method `fire` exists for struct `ink_env::call::CallBuilder<DefaultEnvironment, Set<ink_env::AccountId>, Unset<u64>, Unset<u128>, Set<ExecutionInput<ArgumentList<ArgumentListEnd, ArgumentListEnd>>>, Set<ReturnType<NonCodecType>>>`, but its trait bounds were not satisfied

crates/primitives/src/key.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,23 @@ use scale_info::{
4646
#[repr(transparent)]
4747
pub struct Key([u8; 32]);
4848

49+
impl Key {
50+
/// Creates a new key instance from the given bytes.
51+
///
52+
/// # Note
53+
///
54+
/// This constructor only exists since it is not yet possible to define
55+
/// the `From` trait implementation as const.
56+
#[inline]
57+
pub const fn new(bytes: [u8; 32]) -> Self {
58+
Self(bytes)
59+
}
60+
}
61+
4962
impl From<[u8; 32]> for Key {
5063
#[inline]
5164
fn from(bytes: [u8; 32]) -> Self {
52-
Self(bytes)
65+
Self::new(bytes)
5366
}
5467
}
5568

0 commit comments

Comments
 (0)