diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d0e6dc0f45..69adf22025e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Persist `Environment` in metadata - [#1741](https://github.com/paritytech/ink/pull/1741) + ### Changed - Upgraded `syn` to version `2` - [#1731](https://github.com/paritytech/ink/pull/1731) diff --git a/crates/env/Cargo.toml b/crates/env/Cargo.toml index 7157be4b9e6..a55143051cc 100644 --- a/crates/env/Cargo.toml +++ b/crates/env/Cargo.toml @@ -16,7 +16,6 @@ categories = ["no-std", "embedded"] include = ["Cargo.toml", "src/**/*.rs", "README.md", "LICENSE"] [dependencies] -ink_metadata = { version = "4.1.0", path = "../metadata", default-features = false, features = ["derive"], optional = true } ink_allocator = { version = "4.1.0", path = "../allocator", default-features = false } ink_storage_traits = { version = "4.1.0", path = "../storage/traits", default-features = false } ink_prelude = { version = "4.1.0", path = "../prelude", default-features = false } @@ -56,7 +55,6 @@ ink = { path = "../ink" } [features] default = ["std"] std = [ - "ink_metadata/std", "ink_allocator/std", "ink_prelude/std", "ink_primitives/std", diff --git a/crates/env/src/types.rs b/crates/env/src/types.rs index 1e861c9daa1..fab2503dc21 100644 --- a/crates/env/src/types.rs +++ b/crates/env/src/types.rs @@ -177,6 +177,7 @@ pub trait Environment { } /// Placeholder for chains that have no defined chain extension. +#[cfg_attr(feature = "std", derive(TypeInfo))] pub enum NoChainExtension {} /// The fundamental types of the default configuration. diff --git a/crates/ink/codegen/src/generator/chain_extension.rs b/crates/ink/codegen/src/generator/chain_extension.rs index 8f36e838eec..d35a2a82e42 100644 --- a/crates/ink/codegen/src/generator/chain_extension.rs +++ b/crates/ink/codegen/src/generator/chain_extension.rs @@ -133,6 +133,9 @@ impl GenerateCode for ChainExtension<'_> { let instance_ident = format_ident!("__ink_{}Instance", ident); quote_spanned!(span => #(#attrs)* + #[cfg_attr(feature = "std", derive( + ::scale_info::TypeInfo, + ))] pub enum #ident {} const _: () = { diff --git a/crates/ink/codegen/src/generator/env.rs b/crates/ink/codegen/src/generator/env.rs index 31361cd9cbd..c72627732ac 100644 --- a/crates/ink/codegen/src/generator/env.rs +++ b/crates/ink/codegen/src/generator/env.rs @@ -39,6 +39,8 @@ impl GenerateCode for Env<'_> { type Hash = <<#storage_ident as ::ink::env::ContractEnv>::Env as ::ink::env::Environment>::Hash; type Timestamp = <<#storage_ident as ::ink::env::ContractEnv>::Env as ::ink::env::Environment>::Timestamp; type BlockNumber = <<#storage_ident as ::ink::env::ContractEnv>::Env as ::ink::env::Environment>::BlockNumber; + type ChainExtension = <<#storage_ident as ::ink::env::ContractEnv>::Env as ::ink::env::Environment>::ChainExtension; + const MAX_EVENT_TOPICS: usize = <<#storage_ident as ::ink::env::ContractEnv>::Env as ::ink::env::Environment>::MAX_EVENT_TOPICS; } } } diff --git a/crates/ink/codegen/src/generator/metadata.rs b/crates/ink/codegen/src/generator/metadata.rs index 803372ca930..76fa16c3ce2 100644 --- a/crates/ink/codegen/src/generator/metadata.rs +++ b/crates/ink/codegen/src/generator/metadata.rs @@ -28,7 +28,10 @@ use quote::{ quote, quote_spanned, }; -use syn::spanned::Spanned as _; +use syn::{ + parse_quote, + spanned::Spanned as _, +}; /// Generates code to generate the metadata of the contract. #[derive(From)] @@ -96,6 +99,7 @@ impl Metadata<'_> { ::ink::LangError }; let error = Self::generate_type_spec(&error_ty); + let environment = self.generate_environment(); quote! { ::ink::metadata::ContractSpec::new() .constructors([ @@ -113,6 +117,9 @@ impl Metadata<'_> { .lang_error( #error ) + .environment( + #environment + ) .done() } } @@ -407,6 +414,35 @@ impl Metadata<'_> { ) }) } + + fn generate_environment(&self) -> TokenStream2 { + let span = self.contract.module().span(); + + let account_id: syn::Type = parse_quote!(AccountId); + let balance: syn::Type = parse_quote!(Balance); + let hash: syn::Type = parse_quote!(Hash); + let timestamp: syn::Type = parse_quote!(Timestamp); + let block_number: syn::Type = parse_quote!(BlockNumber); + let chain_extension: syn::Type = parse_quote!(ChainExtension); + + let account_id = Self::generate_type_spec(&account_id); + let balance = Self::generate_type_spec(&balance); + let hash = Self::generate_type_spec(&hash); + let timestamp = Self::generate_type_spec(×tamp); + let block_number = Self::generate_type_spec(&block_number); + let chain_extension = Self::generate_type_spec(&chain_extension); + quote_spanned!(span=> + ::ink::metadata::EnvironmentSpec::new() + .account_id(#account_id) + .balance(#balance) + .hash(#hash) + .timestamp(#timestamp) + .block_number(#block_number) + .chain_extension(#chain_extension) + .max_event_topics(MAX_EVENT_TOPICS) + .done() + ) + } } #[cfg(test)] diff --git a/crates/ink/src/lib.rs b/crates/ink/src/lib.rs index ca0b8d4b695..f599470ef80 100644 --- a/crates/ink/src/lib.rs +++ b/crates/ink/src/lib.rs @@ -36,6 +36,8 @@ pub use ink_env as env; pub use ink_metadata as metadata; pub use ink_prelude as prelude; pub use ink_primitives as primitives; +#[cfg(feature = "std")] +pub use metadata::TypeInfo; pub mod storage { pub mod traits { diff --git a/crates/metadata/src/lib.rs b/crates/metadata/src/lib.rs index 9e19dae96df..29673caeba3 100644 --- a/crates/metadata/src/lib.rs +++ b/crates/metadata/src/lib.rs @@ -36,6 +36,8 @@ pub use self::specs::{ ContractSpec, ContractSpecBuilder, DisplayName, + EnvironmentSpec, + EnvironmentSpecBuilder, EventParamSpec, EventParamSpecBuilder, EventSpec, @@ -51,6 +53,7 @@ pub use self::specs::{ use impl_serde::serialize as serde_hex; +pub use scale_info::TypeInfo; #[cfg(feature = "derive")] use scale_info::{ form::PortableForm, diff --git a/crates/metadata/src/specs.rs b/crates/metadata/src/specs.rs index b8b71b4a8bd..c817db7049e 100644 --- a/crates/metadata/src/specs.rs +++ b/crates/metadata/src/specs.rs @@ -48,7 +48,10 @@ use serde::{ serialize = "F::Type: Serialize, F::String: Serialize", deserialize = "F::Type: DeserializeOwned, F::String: DeserializeOwned" ))] -pub struct ContractSpec { +pub struct ContractSpec +where + TypeSpec: Default, +{ /// The set of constructors of the contract. constructors: Vec>, /// The external messages of the contract. @@ -59,6 +62,8 @@ pub struct ContractSpec { docs: Vec, /// The language specific error type. lang_error: TypeSpec, + /// The environment types of the contract specification. + environment: EnvironmentSpec, } impl IntoPortable for ContractSpec { @@ -83,6 +88,7 @@ impl IntoPortable for ContractSpec { .collect::>(), docs: registry.map_into_portable(self.docs), lang_error: self.lang_error.into_portable(registry), + environment: self.environment.into_portable(registry), } } } @@ -90,6 +96,7 @@ impl IntoPortable for ContractSpec { impl ContractSpec where F: Form, + TypeSpec: Default, { /// Returns the set of constructors of the contract. pub fn constructors(&self) -> &[ConstructorSpec] { @@ -115,6 +122,10 @@ where pub fn lang_error(&self) -> &TypeSpec { &self.lang_error } + // Returns the environment types of the contract specification. + pub fn environment(&self) -> &EnvironmentSpec { + &self.environment + } } /// The message builder is ready to finalize construction. @@ -126,6 +137,7 @@ pub enum Invalid {} pub struct ContractSpecBuilder where F: Form, + TypeSpec: Default, { /// The to-be-constructed contract specification. spec: ContractSpec, @@ -136,6 +148,7 @@ where impl ContractSpecBuilder where F: Form, + TypeSpec: Default, { /// Sets the constructors of the contract specification. pub fn constructors(self, constructors: C) -> ContractSpecBuilder @@ -156,6 +169,7 @@ where impl ContractSpecBuilder where F: Form, + TypeSpec: Default, { /// Sets the messages of the contract specification. pub fn messages(self, messages: M) -> Self @@ -202,7 +216,7 @@ where } } - /// Sets the language error of the contract specification + /// Sets the language error of the contract specification. pub fn lang_error(self, lang_error: TypeSpec) -> Self { Self { spec: ContractSpec { @@ -212,11 +226,23 @@ where ..self } } + + /// Sets the environment types of the contract specification. + pub fn environment(self, environment: EnvironmentSpec) -> Self { + Self { + spec: ContractSpec { + environment, + ..self.spec + }, + ..self + } + } } impl ContractSpecBuilder where F: Form, + TypeSpec: Default, { /// Finalizes construction of the contract specification. pub fn done(self) -> ContractSpec { @@ -236,6 +262,7 @@ impl ContractSpec where F: Form, TypeSpec: Default, + EnvironmentSpec: Default, { /// Creates a new contract specification. pub fn new() -> ContractSpecBuilder { @@ -246,6 +273,7 @@ where events: Vec::new(), docs: Vec::new(), lang_error: Default::default(), + environment: Default::default(), }, marker: PhantomData, } @@ -516,6 +544,20 @@ mod state { pub struct IsPayable; /// Type state for the message return type. pub struct Returns; + /// Type state for the `AccountId` type of the environment. + pub struct AccountId; + /// Type state for the `Balance` type of the environment. + pub struct Balance; + /// Type state for the `Hash` type of the environment. + pub struct Hash; + /// Type state for the `Timestamp` type of the environment. + pub struct Timestamp; + /// Type state for the `BlockNumber` type of the environment. + pub struct BlockNumber; + /// Type state for the `ChainExtension` type of the environment. + pub struct ChainExtension; + /// Type state for the max number of topics specified in the environment. + pub struct MaxEventTopics; } impl MessageSpec @@ -1298,3 +1340,302 @@ where self.spec } } + +/// Describes a contract environment. +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(bound( + serialize = "F::Type: Serialize, F::String: Serialize", + deserialize = "F::Type: DeserializeOwned, F::String: DeserializeOwned" +))] +#[serde(rename_all = "camelCase")] +pub struct EnvironmentSpec +where + TypeSpec: Default, +{ + account_id: TypeSpec, + balance: TypeSpec, + hash: TypeSpec, + timestamp: TypeSpec, + block_number: TypeSpec, + chain_extension: TypeSpec, + max_event_topics: usize, +} + +impl Default for EnvironmentSpec +where + F: Form, + TypeSpec: Default, +{ + fn default() -> Self { + Self { + account_id: Default::default(), + balance: Default::default(), + hash: Default::default(), + timestamp: Default::default(), + block_number: Default::default(), + chain_extension: Default::default(), + max_event_topics: Default::default(), + } + } +} + +impl IntoPortable for EnvironmentSpec { + type Output = EnvironmentSpec; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + EnvironmentSpec { + account_id: self.account_id.into_portable(registry), + balance: self.balance.into_portable(registry), + hash: self.hash.into_portable(registry), + timestamp: self.timestamp.into_portable(registry), + block_number: self.block_number.into_portable(registry), + chain_extension: self.chain_extension.into_portable(registry), + max_event_topics: self.max_event_topics, + } + } +} + +impl EnvironmentSpec +where + F: Form, + TypeSpec: Default, +{ + /// Returns the `AccountId` type of the environment. + pub fn account_id(&self) -> &TypeSpec { + &self.account_id + } + /// Returns the `Balance` type of the environment. + pub fn balance(&self) -> &TypeSpec { + &self.balance + } + /// Returns the `Hash` type of the environment. + pub fn hash(&self) -> &TypeSpec { + &self.hash + } + /// Returns the `Timestamp` type of the environment. + pub fn timestamp(&self) -> &TypeSpec { + &self.timestamp + } + /// Returns the `BlockNumber` type of the environment. + pub fn block_number(&self) -> &TypeSpec { + &self.block_number + } + /// Returns the `ChainExtension` type of the environment. + pub fn chain_extension(&self) -> &TypeSpec { + &self.chain_extension + } + /// Returns the `MAX_EVENT_TOPICS` value of the environment. + pub fn max_event_topics(&self) -> usize { + self.max_event_topics + } +} + +#[allow(clippy::type_complexity)] +impl EnvironmentSpec +where + F: Form, + TypeSpec: Default, + EnvironmentSpec: Default, +{ + pub fn new() -> EnvironmentSpecBuilder< + F, + Missing, + Missing, + Missing, + Missing, + Missing, + Missing, + Missing, + > { + EnvironmentSpecBuilder { + spec: Default::default(), + marker: PhantomData, + } + } +} + +/// An environment specification builder. +#[allow(clippy::type_complexity)] +#[must_use] +pub struct EnvironmentSpecBuilder +where + F: Form, + TypeSpec: Default, + EnvironmentSpec: Default, +{ + spec: EnvironmentSpec, + marker: PhantomData (A, B, H, T, BN, C, M)>, +} + +impl + EnvironmentSpecBuilder, B, H, T, BN, C, M> +where + F: Form, + TypeSpec: Default, + EnvironmentSpec: Default, +{ + /// Sets the `AccountId` type of the environment. + pub fn account_id( + self, + account_id: TypeSpec, + ) -> EnvironmentSpecBuilder { + EnvironmentSpecBuilder { + spec: EnvironmentSpec { + account_id, + ..self.spec + }, + marker: PhantomData, + } + } +} + +impl + EnvironmentSpecBuilder, H, T, BN, C, M> +where + F: Form, + TypeSpec: Default, + EnvironmentSpec: Default, +{ + /// Sets the `Balance` type of the environment. + pub fn balance( + self, + balance: TypeSpec, + ) -> EnvironmentSpecBuilder { + EnvironmentSpecBuilder { + spec: EnvironmentSpec { + balance, + ..self.spec + }, + marker: PhantomData, + } + } +} + +impl + EnvironmentSpecBuilder, T, BN, C, M> +where + F: Form, + TypeSpec: Default, + EnvironmentSpec: Default, +{ + /// Sets the `Hash` type of the environment. + pub fn hash( + self, + hash: TypeSpec, + ) -> EnvironmentSpecBuilder { + EnvironmentSpecBuilder { + spec: EnvironmentSpec { hash, ..self.spec }, + marker: PhantomData, + } + } +} + +impl + EnvironmentSpecBuilder, BN, C, M> +where + F: Form, + TypeSpec: Default, + EnvironmentSpec: Default, +{ + /// Sets the `Timestamp` type of the environment. + pub fn timestamp( + self, + timestamp: TypeSpec, + ) -> EnvironmentSpecBuilder { + EnvironmentSpecBuilder { + spec: EnvironmentSpec { + timestamp, + ..self.spec + }, + marker: PhantomData, + } + } +} + +impl + EnvironmentSpecBuilder, C, M> +where + F: Form, + TypeSpec: Default, + EnvironmentSpec: Default, +{ + /// Sets the `BlockNumber` type of the environment. + pub fn block_number( + self, + block_number: TypeSpec, + ) -> EnvironmentSpecBuilder { + EnvironmentSpecBuilder { + spec: EnvironmentSpec { + block_number, + ..self.spec + }, + marker: PhantomData, + } + } +} + +impl + EnvironmentSpecBuilder, M> +where + F: Form, + TypeSpec: Default, + EnvironmentSpec: Default, +{ + /// Sets the `ChainExtension` type of the environment. + pub fn chain_extension( + self, + chain_extension: TypeSpec, + ) -> EnvironmentSpecBuilder { + EnvironmentSpecBuilder { + spec: EnvironmentSpec { + chain_extension, + ..self.spec + }, + marker: PhantomData, + } + } +} + +impl + EnvironmentSpecBuilder> +where + F: Form, + TypeSpec: Default, + EnvironmentSpec: Default, +{ + /// Sets the `MAX_EVENT_TOPICS` value of the environment. + pub fn max_event_topics( + self, + max_event_topics: usize, + ) -> EnvironmentSpecBuilder { + EnvironmentSpecBuilder { + spec: EnvironmentSpec { + max_event_topics, + ..self.spec + }, + marker: PhantomData, + } + } +} + +impl + EnvironmentSpecBuilder< + F, + state::AccountId, + state::Balance, + state::Hash, + state::Timestamp, + state::BlockNumber, + state::ChainExtension, + state::MaxEventTopics, + > +where + F: Form, + TypeSpec: Default, + EnvironmentSpec: Default, +{ + /// Finished constructing the `EnvironmentSpec` object. + pub fn done(self) -> EnvironmentSpec { + self.spec + } +} diff --git a/crates/metadata/src/tests.rs b/crates/metadata/src/tests.rs index c539ffc377b..f463ac75bda 100644 --- a/crates/metadata/src/tests.rs +++ b/crates/metadata/src/tests.rs @@ -55,6 +55,17 @@ fn spec_constructor_selector_must_serialize_to_hex() { #[test] fn spec_contract_json() { + #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] + pub enum NoChainExtension {} + + type AccountId = ink_primitives::AccountId; + type Balance = u64; + type Hash = ink_primitives::Hash; + type Timestamp = u64; + type BlockNumber = u128; + type ChainExtension = NoChainExtension; + const MAX_EVENT_TOPICS: usize = 4; + // given let contract: ContractSpec = ContractSpec::new() .constructors(vec![ @@ -117,6 +128,47 @@ fn spec_contract_json() { ::core::convert::AsRef::as_ref, ), )) + .environment( + EnvironmentSpec::new() + .account_id(TypeSpec::with_name_segs::( + ::core::iter::Iterator::map( + ::core::iter::IntoIterator::into_iter(["AccountId"]), + ::core::convert::AsRef::as_ref, + ), + )) + .balance(TypeSpec::with_name_segs::( + ::core::iter::Iterator::map( + ::core::iter::IntoIterator::into_iter(["Balance"]), + ::core::convert::AsRef::as_ref, + ), + )) + .hash(TypeSpec::with_name_segs::( + ::core::iter::Iterator::map( + ::core::iter::IntoIterator::into_iter(["Hash"]), + ::core::convert::AsRef::as_ref, + ), + )) + .timestamp(TypeSpec::with_name_segs::( + ::core::iter::Iterator::map( + ::core::iter::IntoIterator::into_iter(["Timestamp"]), + ::core::convert::AsRef::as_ref, + ), + )) + .block_number(TypeSpec::with_name_segs::( + ::core::iter::Iterator::map( + ::core::iter::IntoIterator::into_iter(["BlockNumber"]), + ::core::convert::AsRef::as_ref, + ), + )) + .chain_extension(TypeSpec::with_name_segs::( + ::core::iter::Iterator::map( + ::core::iter::IntoIterator::into_iter(["ChainExtension"]), + ::core::convert::AsRef::as_ref, + ), + )) + .max_event_topics(MAX_EVENT_TOPICS) + .done(), + ) .done(); let mut registry = Registry::new(); @@ -172,6 +224,45 @@ fn spec_contract_json() { } ], "docs": [], + "environment": { + "accountId": { + "displayName": [ + "AccountId", + ], + "type": 4, + }, + "balance": { + "displayName": [ + "Balance", + ], + "type": 7, + }, + "blockNumber": { + "displayName": [ + "BlockNumber", + ], + "type": 9, + }, + "chainExtension": { + "displayName": [ + "ChainExtension", + ], + "type": 10, + }, + "hash": { + "displayName": [ + "Hash", + ], + "type": 8, + }, + "maxEventTopics": 4, + "timestamp": { + "displayName": [ + "Timestamp", + ], + "type": 7, + }, + }, "events": [], "lang_error": { "displayName": [