-
Notifications
You must be signed in to change notification settings - Fork 455
Follow-up: SpreadLayout #995
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
Changes from all commits
b192f55
dc374ac
b78d849
147b5e2
e1c8a7e
96d6b0c
00372bb
82e93ad
8f626ce
1dad2b1
6156806
5160b17
ee53370
a292116
c3999ca
ce48ebc
bd84d77
276df24
499befc
5c20590
6368f2b
7fed332
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -103,3 +103,4 @@ wildcard/S | |
natively | ||
payability | ||
unpayable | ||
initializer |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,23 +18,41 @@ use crate::reflect::{ | |
}; | ||
use core::{ | ||
any::TypeId, | ||
convert::Infallible, | ||
mem::ManuallyDrop, | ||
}; | ||
use ink_env::{ | ||
Environment, | ||
ReturnFlags, | ||
}; | ||
use ink_primitives::Key; | ||
use ink_primitives::{ | ||
Key, | ||
KeyPtr, | ||
}; | ||
use ink_storage::{ | ||
alloc, | ||
alloc::ContractPhase, | ||
traits::{ | ||
pull_spread_root, | ||
push_spread_root, | ||
SpreadAllocate, | ||
SpreadLayout, | ||
}, | ||
}; | ||
|
||
/// The root key of the ink! smart contract. | ||
/// | ||
/// # Note | ||
/// | ||
/// - This is the key where storage allocation, pushing and pulling is rooted | ||
/// using the `SpreadLayout` and `SpreadAllocate` traits primarily. | ||
/// - This trait is automatically implemented by the ink! codegen. | ||
/// - The existence of this trait allows to customize the root key in future | ||
/// versions of ink! if needed. | ||
pub trait ContractRootKey { | ||
const ROOT_KEY: Key; | ||
} | ||
|
||
/// Returns `Ok` if the caller did not transfer additional value to the callee. | ||
/// | ||
/// # Errors | ||
|
@@ -70,24 +88,192 @@ pub struct ExecuteConstructorConfig { | |
/// The closure is supposed to already contain all the arguments that the real | ||
/// constructor message requires and forwards them. | ||
#[inline] | ||
pub fn execute_constructor<S, F>( | ||
pub fn execute_constructor<Contract, F, R>( | ||
config: ExecuteConstructorConfig, | ||
f: F, | ||
) -> Result<(), DispatchError> | ||
where | ||
S: ink_storage::traits::SpreadLayout, | ||
F: FnOnce() -> S, | ||
Contract: SpreadLayout + ContractRootKey, | ||
F: FnOnce() -> R, | ||
<private::Seal<R> as ConstructorReturnType<Contract>>::ReturnValue: scale::Encode, | ||
private::Seal<R>: ConstructorReturnType<Contract>, | ||
{ | ||
if config.dynamic_storage_alloc { | ||
alloc::initialize(ContractPhase::Deploy); | ||
} | ||
let storage = ManuallyDrop::new(f()); | ||
let root_key = Key::from([0x00; 32]); | ||
push_spread_root::<S>(&storage, &root_key); | ||
if config.dynamic_storage_alloc { | ||
alloc::finalize(); | ||
let result = ManuallyDrop::new(private::Seal(f())); | ||
match result.as_result() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it fix the issue #985? (if you apply this change for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I now know what causes failure of result check. It is explained here: Message return values are strictly more powerful than constructor return values therefore this cannot directly applied to fix the bug with messages. However, I am sure we can still fix the result bug with another technique. |
||
Ok(contract) => { | ||
// Constructor is infallible or is fallible but succeeded. | ||
// | ||
// This requires us to sync back the changes of the contract storage. | ||
let root_key = <Contract as ContractRootKey>::ROOT_KEY; | ||
push_spread_root::<Contract>(contract, &root_key); | ||
if config.dynamic_storage_alloc { | ||
alloc::finalize(); | ||
} | ||
Ok(()) | ||
} | ||
Err(_) => { | ||
// 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(), | ||
) | ||
} | ||
} | ||
} | ||
|
||
/// Initializes the ink! contract using the given initialization routine. | ||
/// | ||
/// # Note | ||
/// | ||
/// - This uses `SpreadAllocate` trait in order to default initialize the | ||
/// ink! smart contract before calling the initialization routine. | ||
/// - This either returns `Contract` or `Result<Contract, E>` depending | ||
/// on the return type `R` of the initializer closure `F`. | ||
/// If `R` is `()` then `Contract` is returned and if `R` is any type of | ||
/// `Result<(), E>` then `Result<Contract, E>` is returned. | ||
/// Other return types for `F` than the ones listed above are not allowed. | ||
#[inline] | ||
pub fn initialize_contract<Contract, F, R>( | ||
initializer: F, | ||
) -> <R as InitializerReturnType<Contract>>::Wrapped | ||
where | ||
Contract: ContractRootKey + SpreadAllocate, | ||
F: FnOnce(&mut Contract) -> R, | ||
R: InitializerReturnType<Contract>, | ||
{ | ||
let mut key_ptr = KeyPtr::from(<Contract as ContractRootKey>::ROOT_KEY); | ||
let mut instance = <Contract as SpreadAllocate>::allocate_spread(&mut key_ptr); | ||
let result = initializer(&mut instance); | ||
result.into_wrapped(instance) | ||
} | ||
|
||
mod private { | ||
/// Seals the implementation of `ContractInitializerReturnType`. | ||
pub trait Sealed {} | ||
impl Sealed for () {} | ||
impl<T, E> Sealed for Result<T, E> {} | ||
/// A thin-wrapper type that automatically seals its inner type. | ||
/// | ||
/// Since it is private it can only be used from within this crate. | ||
/// We need this type in order to properly seal the `ConstructorReturnType` | ||
/// trait from unwanted external trait implementations. | ||
#[repr(transparent)] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this needed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I dont think it really is. However I tend to add transparent to generic thin wrappers to indicate their use. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So in this case you're using it as an indicator that people shouldn't be using the I'd argue that the naming and docs are enough, but I see what you're getting at There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seal is private so they cannot use it. It is more of a dev documentation to never ever make it public. |
||
pub struct Seal<T>(pub T); | ||
impl<T> Sealed for Seal<T> {} | ||
} | ||
|
||
/// Guards against using invalid contract initializer types. | ||
/// | ||
/// # Note | ||
/// | ||
/// Currently the only allowed types are `()` and `Result<(), E>` | ||
/// where `E` is some unspecified error type. | ||
/// If the contract initializer returns `Result::Err` the utility | ||
/// method that is used to initialize an ink! smart contract will | ||
/// revert the state of the contract instantiation. | ||
pub trait ConstructorReturnType<C>: private::Sealed { | ||
/// Is `true` if `Self` is `Result<C, E>`. | ||
const IS_RESULT: bool = false; | ||
|
||
/// The error type of the constructor return type. | ||
/// | ||
/// # 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 | ||
/// does not matter. | ||
type ReturnValue; | ||
|
||
/// 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 = (); | ||
|
||
#[inline(always)] | ||
fn as_result(&self) -> Result<&C, &Self::Error> { | ||
Ok(&self.0) | ||
} | ||
|
||
#[inline(always)] | ||
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(always)] | ||
fn as_result(&self) -> Result<&C, &Self::Error> { | ||
self.0.as_ref() | ||
} | ||
|
||
#[inline(always)] | ||
fn return_value(&self) -> &Self::ReturnValue { | ||
&self.0 | ||
} | ||
} | ||
|
||
/// Trait used to convert return types of contract initializer routines. | ||
/// | ||
/// Only `()` and `Result<(), E>` are allowed contract initializer return types. | ||
/// For `WrapReturnType<C>` where `C` is the contract type the trait converts | ||
/// `()` into `C` and `Result<(), E>` into `Result<C, E>`. | ||
pub trait InitializerReturnType<C>: private::Sealed { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since there are only two basic cases here, I don't know if I would go ahead with a new trait for this. I'd just do the wrapping in-line There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How would this look like? |
||
type Wrapped; | ||
|
||
/// Performs the type conversion of the initialization routine return type. | ||
fn into_wrapped(self, wrapped: C) -> Self::Wrapped; | ||
} | ||
|
||
impl<C> InitializerReturnType<C> for () { | ||
type Wrapped = C; | ||
|
||
#[inline] | ||
fn into_wrapped(self, wrapped: C) -> C { | ||
wrapped | ||
} | ||
} | ||
impl<C, E> InitializerReturnType<C> for Result<(), E> { | ||
type Wrapped = Result<C, E>; | ||
|
||
#[inline] | ||
fn into_wrapped(self, wrapped: C) -> Self::Wrapped { | ||
self.map(|_| wrapped) | ||
} | ||
Ok(()) | ||
} | ||
|
||
/// Configuration for execution of ink! messages. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't we want this getting dropped here? From what I can tell
f
isn't used in the codegen after being passed through to hereThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We dont want and dont need the otherwise automated Drop call.