From 2b888dffbfcd13d0a4afe71fcfee4693fb466e5b Mon Sep 17 00:00:00 2001 From: davidsemakula Date: Sat, 27 May 2023 23:42:37 +0300 Subject: [PATCH] add hover content --- README.md | 3 +- crates/analyzer/README.md | 3 +- crates/analyzer/src/analysis.rs | 15 +- crates/analyzer/src/analysis/hover.rs | 464 +++++++ crates/analyzer/src/analysis/hover/content.rs | 39 + .../src/analysis/hover/content/args.rs | 686 ++++++++++ .../src/analysis/hover/content/macros.rs | 1198 +++++++++++++++++ crates/analyzer/src/lib.rs | 2 +- 8 files changed, 2403 insertions(+), 7 deletions(-) create mode 100644 crates/analyzer/src/analysis/hover.rs create mode 100644 crates/analyzer/src/analysis/hover/content.rs create mode 100644 crates/analyzer/src/analysis/hover/content/args.rs create mode 100644 crates/analyzer/src/analysis/hover/content/macros.rs diff --git a/README.md b/README.md index e99e53c..40bb0d7 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A collection of modular and reusable libraries and tools for semantic analysis o ink! analyzer aims to improve [ink!](https://use.ink/) language support in [integrated development environments (IDEs)](https://en.wikipedia.org/wiki/Integrated_development_environment), [source code editors](https://en.wikipedia.org/wiki/Source-code_editor) and other development tools by providing modular and reusable building blocks for implementing features like diagnostics, code completion, code/intent actions and hover content for the [ink! programming language](https://use.ink/) which is used for writing smart contracts for blockchains built on [Substrate](https://substrate.io/). -**NOTE:** This project is still work in progress, check back over the next few weeks for regular updates. +**NOTE:** 🚧 This project is still work in progress, check back over the next few weeks for regular updates. ## Architecture @@ -19,6 +19,7 @@ It currently implements an [Analysis](/crates/analyzer/src/analysis.rs) entry po - [diagnostics](/crates/analyzer/src/analysis/diagnostics.rs) - errors and warnings based on ink! semantic rules. - [completions](/crates/analyzer/src/analysis/completions.rs) - completion suggestions for ink! attribute macros and arguments. - [code/intent actions](/crates/analyzer/src/analysis/actions.rs) - contextual assists for adding relevant ink! attribute macros and arguments. +- [hover content](/crates/analyzer/src/analysis/hover.rs) - descriptive/informational text for ink! attribute macros and arguments. ### 2. [IR (ink-analyzer-ir)](/crates/ir) This crate implements types, abstractions and utilities for parsing ink! smart contract code into ink! intermediate representations (IRs) and abstractions. diff --git a/crates/analyzer/README.md b/crates/analyzer/README.md index 16a27af..9d26be7 100644 --- a/crates/analyzer/README.md +++ b/crates/analyzer/README.md @@ -10,8 +10,9 @@ It currently implements an [Analysis](/crates/analyzer/src/analysis.rs) entry po - [diagnostics](/crates/analyzer/src/analysis/diagnostics.rs) - errors and warnings based on ink! semantic rules. - [completions](/crates/analyzer/src/analysis/completions.rs) - completion suggestions for ink! attribute macros and arguments. - [code/intent actions](/crates/analyzer/src/analysis/actions.rs) - contextual assists for adding relevant ink! attribute macros and arguments. +- [hover content](/crates/analyzer/src/analysis/hover.rs) - descriptive/informational text for ink! attribute macros and arguments. -**NOTE:** This project is still work in progress, check back over the next few weeks for regular updates. +**NOTE:** 🚧 This project is still work in progress, check back over the next few weeks for regular updates. ## Installation diff --git a/crates/analyzer/src/analysis.rs b/crates/analyzer/src/analysis.rs index 499d48b..b2b5a3f 100644 --- a/crates/analyzer/src/analysis.rs +++ b/crates/analyzer/src/analysis.rs @@ -1,18 +1,20 @@ //! Types and abstractions for performing semantic analysis of ink! smart contract code. -use ink_analyzer_ir::syntax::TextSize; +use ink_analyzer_ir::syntax::{TextRange, TextSize}; use ink_analyzer_ir::InkFile; pub use actions::Action; pub use completions::Completion; pub use diagnostics::{Diagnostic, Severity}; +pub use hover::Hover; mod actions; mod completions; mod diagnostics; +mod hover; mod utils; -/// Analysis is the main entry point for asking for semantic information about ink! smart contract code. +/// Entry point for asking for semantic information about ink! smart contract code. #[derive(Debug)] pub struct Analysis { /// The ink! smart contract code being analyzed. @@ -37,13 +39,18 @@ impl Analysis { diagnostics::diagnostics(&self.file) } - /// Computes completions at the given position. + /// Computes ink! attribute completions at the given position. pub fn completions(&self, position: TextSize) -> Vec { completions::completions(&self.file, position) } - /// Computes code/intent actions for the given position. + /// Computes ink! attribute code/intent actions for the given position. pub fn actions(&self, position: TextSize) -> Vec { actions::actions(&self.file, position) } + + /// Returns descriptive/informational text for the ink! attribute at the given position (if any). + pub fn hover(&self, range: TextRange) -> Option { + hover::hover(&self.file, range) + } } diff --git a/crates/analyzer/src/analysis/hover.rs b/crates/analyzer/src/analysis/hover.rs new file mode 100644 index 0000000..f7c4595 --- /dev/null +++ b/crates/analyzer/src/analysis/hover.rs @@ -0,0 +1,464 @@ +//! ink! attribute hover content. + +use ink_analyzer_ir::syntax::{AstNode, AstToken, SyntaxElement, TextRange}; +use ink_analyzer_ir::{ + ast, FromSyntax, InkArgKind, InkAttribute, InkAttributeKind, InkEntity, InkFile, InkMacroKind, +}; + +mod content; + +/// An ink! attribute hover result. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Hover { + /// Range the hover content applies to. + pub range: TextRange, + /// Replacement text for the action. + pub content: String, +} + +/// Returns descriptive/informational text for the ink! attribute at the given position (if any). + +/// Returns documentation for the ink! attribute kind. +/// +/// Ref: . +/// +/// Ref: . +/// +/// Ref: . +/// +/// Ref: . +/// +/// Ref: . +/// +/// Ref: . +/// +/// Ref: . +/// +/// Ref: . +/// +/// Ref: . +/// +/// Ref: . +/// +/// Ref: . +pub fn hover(file: &InkFile, range: TextRange) -> Option { + // Finds the covering ink! attribute for the text range (if any). + let covering_ink_attr = if range.is_empty() { + // Uses item at offset utility if the range start and end are equal. + file.item_at_offset(range.start()).parent_ink_attr() + } else { + file.syntax() + .text_range() + // Ensure the text range is in the bounds of the source code. + .contains_range(range) + .then(|| { + // Returns deepest ink! attribute that fully covers the text range. + let covering_element = file.syntax().covering_element(range); + let attr = if ast::Attr::can_cast(covering_element.kind()) { + // Casts cover element to `ast::Attr` node if it's an attribute. + covering_element.into_node().and_then(ast::Attr::cast) + } else { + // Finds the parent attribute (if any) of the covering element. + ink_analyzer_ir::closest_ancestor_ast_type::( + &covering_element, + ) + }; + // Converts to ink! attribute (if present). + attr.and_then(InkAttribute::cast) + }) + .flatten() + }; + + // Returns hover content only if the text range is covered by an ink! attribute. + covering_ink_attr.and_then(|ink_attr| { + // Finds the covered ink! attribute argument (if any). + let ink_arg = ink_attr + .args() + .iter() + .find(|arg| arg.text_range().contains_range(range)); + match ink_arg { + // Returns hover content for the covered ink! attribute argument if it's valid. + Some(ink_arg) => (*ink_arg.kind() != InkArgKind::Unknown).then_some(Hover { + range: ink_arg + .name() + .map(|ink_arg_name| ink_arg_name.syntax().text_range()) + .unwrap_or(ink_arg.text_range()), + content: content::doc(&InkAttributeKind::Arg(*ink_arg.kind())).to_string(), + }), + // Returns hover content based on the macro or "primary" argument for the ink! attribute, + // See `ink_analyzer_ir::attrs::utils::sort_ink_args_by_kind` doc. + None => match ink_attr.kind() { + // Returns hover content based on the "primary" ink! attribute argument if it's valid, + // See `ink_analyzer_ir::attrs::utils::sort_ink_args_by_kind` doc. + InkAttributeKind::Arg(arg_kind) => { + (*arg_kind != InkArgKind::Unknown).then_some(Hover { + range: ink_attr + .ink_arg_name() + .map(|ink_arg_name| ink_arg_name.syntax().text_range()) + .unwrap_or(ink_attr.syntax().text_range()), + content: content::doc(&InkAttributeKind::Arg(*arg_kind)).to_string(), + }) + } + // Returns hover content based on the ink! attribute macro if it's valid. + InkAttributeKind::Macro(macro_kind) => (*macro_kind != InkMacroKind::Unknown) + .then_some(Hover { + range: ink_attr + .ink_macro() + .map(|ink_macro_name| ink_macro_name.syntax().text_range()) + .unwrap_or(ink_attr.syntax().text_range()), + content: content::doc(&InkAttributeKind::Macro(*macro_kind)).to_string(), + }), + }, + } + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use ink_analyzer_ir::syntax::TextSize; + use test_utils::parse_offset_at; + + #[test] + fn hover_works() { + for (code, scenarios) in [ + // (code, pat, [(edit, pat_start, pat_end)]) where: + // code = source code, + // pat = substring used to find the cursor offset (see `test_utils::parse_offset_at` doc), + // edit = the text that will inserted (represented without whitespace for simplicity), + // pat_start = substring used to find the start of the edit offset (see `test_utils::parse_offset_at` doc), + // pat_end = substring used to find the end of the edit offset (see `test_utils::parse_offset_at` doc). + + // ink! attribute macros. + ( + "#[ink::contract]", + vec![ + ( + Some("<-#"), + Some("<-#"), + Some(( + content::doc(&InkAttributeKind::Macro(InkMacroKind::Contract)), + Some("<-contract"), + Some("contract"), + )), + ), + ( + Some("<-#"), + Some("ink"), + Some(( + content::doc(&InkAttributeKind::Macro(InkMacroKind::Contract)), + Some("<-contract"), + Some("contract"), + )), + ), + ( + Some("<-contract"), + Some("contract"), + Some(( + content::doc(&InkAttributeKind::Macro(InkMacroKind::Contract)), + Some("<-contract"), + Some("contract"), + )), + ), + ( + Some("<-#"), + Some("]"), + Some(( + content::doc(&InkAttributeKind::Macro(InkMacroKind::Contract)), + Some("<-contract"), + Some("contract"), + )), + ), + ], + ), + ( + r#" + #[ink::contract(env=my::env::Types, keep_attr="foo,bar")] + "#, + vec![ + ( + Some("<-#"), + Some("<-#"), + Some(( + content::doc(&InkAttributeKind::Macro(InkMacroKind::Contract)), + Some("<-contract"), + Some("contract"), + )), + ), + ( + Some("<-#"), + Some("ink"), + Some(( + content::doc(&InkAttributeKind::Macro(InkMacroKind::Contract)), + Some("<-contract"), + Some("contract"), + )), + ), + ( + Some("<-contract"), + Some("contract"), + Some(( + content::doc(&InkAttributeKind::Macro(InkMacroKind::Contract)), + Some("<-contract"), + Some("contract"), + )), + ), + ( + Some("<-#"), + Some("]"), + Some(( + content::doc(&InkAttributeKind::Macro(InkMacroKind::Contract)), + Some("<-contract"), + Some("contract"), + )), + ), + ( + Some("<-env="), + Some("(env"), + Some(( + content::doc(&InkAttributeKind::Arg(InkArgKind::Env)), + Some("<-env="), + Some("(env"), + )), + ), + ( + Some("<-my::env::Types"), + Some("my::env::Types"), + Some(( + content::doc(&InkAttributeKind::Arg(InkArgKind::Env)), + Some("<-env="), + Some("(env"), + )), + ), + ( + Some("<-,"), + Some(","), + Some(( + content::doc(&InkAttributeKind::Macro(InkMacroKind::Contract)), + Some("<-contract"), + Some("contract"), + )), + ), + ( + Some("<-keep_attr"), + Some("keep_attr"), + Some(( + content::doc(&InkAttributeKind::Arg(InkArgKind::KeepAttr)), + Some("<-keep_attr"), + Some("keep_attr"), + )), + ), + ( + Some(r#"<-"foo,bar""#), + Some(r#""foo,bar""#), + Some(( + content::doc(&InkAttributeKind::Arg(InkArgKind::KeepAttr)), + Some("<-keep_attr"), + Some("keep_attr"), + )), + ), + ], + ), + // ink! attribute arguments. + ( + "#[ink(storage)]", + vec![ + ( + Some("<-#"), + Some("<-#"), + Some(( + content::doc(&InkAttributeKind::Arg(InkArgKind::Storage)), + Some("<-storage"), + Some("storage"), + )), + ), + ( + Some("<-#"), + Some("ink"), + Some(( + content::doc(&InkAttributeKind::Arg(InkArgKind::Storage)), + Some("<-storage"), + Some("storage"), + )), + ), + ( + Some("<-storage"), + Some("storage"), + Some(( + content::doc(&InkAttributeKind::Arg(InkArgKind::Storage)), + Some("<-storage"), + Some("storage"), + )), + ), + ( + Some("<-#"), + Some("]"), + Some(( + content::doc(&InkAttributeKind::Arg(InkArgKind::Storage)), + Some("<-storage"), + Some("storage"), + )), + ), + ], + ), + ( + "#[ink(message, default, payable, selector=_)]", + vec![ + ( + Some("<-#"), + Some("<-#"), + Some(( + content::doc(&InkAttributeKind::Arg(InkArgKind::Message)), + Some("<-message"), + Some("message"), + )), + ), + ( + Some("<-#"), + Some("ink"), + Some(( + content::doc(&InkAttributeKind::Arg(InkArgKind::Message)), + Some("<-message"), + Some("message"), + )), + ), + ( + Some("<-message"), + Some("message"), + Some(( + content::doc(&InkAttributeKind::Arg(InkArgKind::Message)), + Some("<-message"), + Some("message"), + )), + ), + ( + Some("<-#"), + Some("]"), + Some(( + content::doc(&InkAttributeKind::Arg(InkArgKind::Message)), + Some("<-message"), + Some("message"), + )), + ), + ( + Some("<-payable"), + Some("payable"), + Some(( + content::doc(&InkAttributeKind::Arg(InkArgKind::Payable)), + Some("<-payable"), + Some("payable"), + )), + ), + ( + Some("<-selector"), + Some("selector"), + Some(( + content::doc(&InkAttributeKind::Arg(InkArgKind::Selector)), + Some("<-selector"), + Some("selector"), + )), + ), + ( + Some("<-_"), + Some("_"), + Some(( + content::doc(&InkAttributeKind::Arg(InkArgKind::Selector)), + Some("<-selector"), + Some("selector"), + )), + ), + ], + ), + ( + "#[ink(extension=1, handle_status=true)]", + vec![ + ( + Some("<-#"), + Some("<-#"), + Some(( + content::doc(&InkAttributeKind::Arg(InkArgKind::Extension)), + Some("<-extension"), + Some("extension"), + )), + ), + ( + Some("<-#"), + Some("ink"), + Some(( + content::doc(&InkAttributeKind::Arg(InkArgKind::Extension)), + Some("<-extension"), + Some("extension"), + )), + ), + ( + Some("<-extension"), + Some("extension"), + Some(( + content::doc(&InkAttributeKind::Arg(InkArgKind::Extension)), + Some("<-extension"), + Some("extension"), + )), + ), + ( + Some("<-#"), + Some("]"), + Some(( + content::doc(&InkAttributeKind::Arg(InkArgKind::Extension)), + Some("<-extension"), + Some("extension"), + )), + ), + ( + Some("<-1"), + Some("1"), + Some(( + content::doc(&InkAttributeKind::Arg(InkArgKind::Extension)), + Some("<-extension"), + Some("extension"), + )), + ), + ( + Some("<-handle_status"), + Some("handle_status"), + Some(( + content::doc(&InkAttributeKind::Arg(InkArgKind::HandleStatus)), + Some("<-handle_status"), + Some("handle_status"), + )), + ), + ( + Some("<-true"), + Some("true"), + Some(( + content::doc(&InkAttributeKind::Arg(InkArgKind::HandleStatus)), + Some("<-handle_status"), + Some("handle_status"), + )), + ), + ], + ), + ] { + for (pat_start, pat_end, expect_result) in scenarios { + let range = TextRange::new( + TextSize::from(parse_offset_at(code, pat_start).unwrap() as u32), + TextSize::from(parse_offset_at(code, pat_end).unwrap() as u32), + ); + + let result = hover(&InkFile::parse(code), range); + + assert_eq!( + result + .as_ref() + .map(|hover_result| (hover_result.content.as_str(), hover_result.range)), + expect_result.map(|(content, pat_start, pat_end)| ( + content, + TextRange::new( + TextSize::from(parse_offset_at(code, pat_start).unwrap() as u32), + TextSize::from(parse_offset_at(code, pat_end).unwrap() as u32) + ) + )) + ); + } + } + } +} diff --git a/crates/analyzer/src/analysis/hover/content.rs b/crates/analyzer/src/analysis/hover/content.rs new file mode 100644 index 0000000..1bbcd53 --- /dev/null +++ b/crates/analyzer/src/analysis/hover/content.rs @@ -0,0 +1,39 @@ +//! Hover content for ink! attributes. + +use ink_analyzer_ir::{InkArgKind, InkAttributeKind, InkMacroKind}; + +mod args; +mod macros; + +/// Returns documentation for the ink! attribute kind. +pub fn doc(attr_kind: &InkAttributeKind) -> &str { + match attr_kind { + InkAttributeKind::Arg(arg_kind) => match arg_kind { + InkArgKind::Anonymous => args::ANONYMOUS_DOC, + InkArgKind::Constructor => args::CONSTRUCTOR_DOC, + InkArgKind::Default => args::DEFAULT_DOC, + InkArgKind::Derive => args::DERIVE_DOC, + InkArgKind::Env => args::ENV_DOC, + InkArgKind::Event => args::EVENT_DOC, + InkArgKind::Extension => args::EXTENSION_DOC, + InkArgKind::HandleStatus => args::HANDLE_STATUS_DOC, + InkArgKind::Impl => args::IMPL_DOC, + InkArgKind::KeepAttr => args::KEEP_ATTR_DOC, + InkArgKind::Message => args::MESSAGE_DOC, + InkArgKind::Namespace => args::NAMESPACE_DOC, + InkArgKind::Payable => args::PAYABLE_DOC, + InkArgKind::Selector => args::SELECTOR_DOC, + InkArgKind::Storage => args::STORAGE_DOC, + InkArgKind::Topic => args::TOPIC_DOC, + InkArgKind::Unknown => "", + }, + InkAttributeKind::Macro(macro_kind) => match macro_kind { + InkMacroKind::ChainExtension => macros::CHAIN_EXTENSION_DOC, + InkMacroKind::Contract => macros::CONTRACT_DOC, + InkMacroKind::StorageItem => macros::STORAGE_ITEM_DOC, + InkMacroKind::Test => macros::TEST_DOC, + InkMacroKind::TraitDefinition => macros::TRAIT_DEFINITION_DOC, + InkMacroKind::Unknown => "", + }, + } +} diff --git a/crates/analyzer/src/analysis/hover/content/args.rs b/crates/analyzer/src/analysis/hover/content/args.rs new file mode 100644 index 0000000..3dcb3bd --- /dev/null +++ b/crates/analyzer/src/analysis/hover/content/args.rs @@ -0,0 +1,686 @@ +//! Hover content for ink! attribute arguments. + +/// Ref: . +/// +/// Ref: . +pub const ANONYMOUS_DOC: &str = r#" +# Attribute + +`#[ink(anonymous)]` + +# Description + +Tells the ink! codegen to treat the ink! event as anonymous which omits the event signature as topic upon emitting. + +Very similar to anonymous events in Solidity. + +# Usage + +Applicable to ink! events. + +# Example +``` +#[ink::contract] +mod my_contract { + #[ink(event, anonymous)] + pub struct MyEvent { + value: bool, + } + + // -snip- +} +``` + +OR + +``` +#[ink::contract] +mod my_contract { + #[ink(event)] + #[ink(anonymous)] + pub struct MyEvent { + value: bool, + } + + // -snip- +} +``` +"#; + +/// Ref: . +/// +/// Ref: . +/// +/// Ref: . +pub const CONSTRUCTOR_DOC: &str = r#" +# Attribute + +`#[ink(constructor)]` + +# Description + +Flags a method for the ink! storage `struct` as constructor making it available to the API for instantiating the contract. + +# Usage + +Applicable to methods. + +# Example + +``` +#[ink::contract] +mod my_contract { + // -snip- + impl MyContract { + #[ink(constructor)] + pub fn new(initial_value: bool) -> Self { + MyContract { value: false } + } + // -snip- + } +} +``` +"#; + +/// Ref: . +pub const DEFAULT_DOC: &str = r#" +# Attribute + +`#[ink(default)]` + +# Description + +Tells UI to treat the ink! message or ink! constructor as the default choice in selection widgets (e.g dropdowns). + +It can be used exactly once for a constructor and once for a message. + +# Usage + +Applicable to ink! messages and ink! constructors. + +# Example + +``` +#[ink::contract] +mod my_contract { + // -snip- + + impl MyContract { + // -snip- + + /// Update the current value. + #[ink(message)] + #[ink(default)] // You can either specify default out-of-line. + pub fn set(&mut self) { + self.value = !self.value; + } + } +} +``` + +OR + +``` +#[ink::contract] +mod my_contract { + // -snip- + + impl MyContract { + // -snip- + + /// Update the current value. + #[ink(message, default)] // ...or specify default inline. + pub fn set(&mut self) { + self.value = !self.value; + } + } +} +``` +"#; + +/// Ref: . +/// +/// Ref: . +pub const DERIVE_DOC: &str = r#" +# Attribute + +`#[ink::storage_item(derive = flag: bool)]` + +# Description + +A configuration parameter used to enable/disable auto deriving of all required storage traits. + +# Usage + +Additional argument for ink! storage item attribute macro. + +**Default value:** `true`. + +# Example + +``` +use ink::storage::Mapping; +use ink::storage::traits::{ + StorableHint, + StorageKey, + Storable, +}; + +#[ink::storage_item(derive = false)] +#[derive(StorableHint, Storable, StorageKey)] +struct NonPackedGeneric { + s1: u32, + s2: Mapping, +} +``` +"#; + +/// Ref: . +/// +/// Ref: . +pub const ENV_DOC: &str = r#" +# Attribute + +`#[ink::contract(env = E: impl Environment)]` + +# Description + +Tells the ink! code generator which environment to use for the ink! smart contract. + +The environment must implement the `Environment` (defined in `ink_env`) trait and provides all the necessary fundamental type definitions for `Balance`, `AccountId` etc. + +# Usage + +Additional argument for ink! contract attribute macro. + +When using a custom `Environment` implementation for a smart contract all types that it exposes to the ink! smart contract and the mirrored types used in the runtime must be aligned with respect to SCALE encoding and semantics. + +**Default value:** `DefaultEnvironment` defined in `ink_env` crate. + +# Example + +Given a custom `Environment` implementation: + +``` +pub struct MyEnvironment; + +impl ink_env::Environment for MyEnvironment { + const MAX_EVENT_TOPICS: usize = 3; + type AccountId = [u8; 16]; + type Balance = u128; + type Hash = [u8; 32]; + type Timestamp = u64; + type BlockNumber = u32; + type ChainExtension = ::ink::env::NoChainExtension; +} +``` + +A user might implement their ink! smart contract using the above custom `Environment` implementation as demonstrated below: + +``` +#[ink::contract(env = MyEnvironment)] +mod my_contract { + // -snip- +} +``` +"#; + +/// Ref: . +/// +/// Ref: . +/// +/// Ref: . +pub const EVENT_DOC: &str = r#" +# Attribute + +`#[ink(event)]` + +# Description + +Defines an ink! event. + +A contract can define multiple such ink! events. + +# Usage + +On `struct` definitions. + +# Example + +``` +#[ink::contract] +mod erc20 { + /// Defines an event that is emitted every time value is transferred. + #[ink(event)] + pub struct Transferred { + from: Option, + to: Option, + value: Balance, + } + + // -snip- +} +``` +"#; + +/// Ref: . +/// +/// Ref: . +pub const EXTENSION_DOC: &str = r#" +# Attribute + +`ink(extension = N:u32)` + +# Description + +Determines the unique function ID of the chain extension method. + +# Usage + +Required attribute for chain extension methods. + +# Example + +``` +type Access = i32; + +#[ink::chain_extension] +pub trait MyChainExtension { + type ErrorCode = i32; + + #[ink(extension = 5)] + fn key_access_for_account(key: &[u8], account: &[u8]) -> Access; +} +``` + +"#; + +/// Ref: . +/// +/// Ref: . +pub const HANDLE_STATUS_DOC: &str = r#" +# Attribute + +`ink(handle_status = flag:bool)` + +# Description + +Assumes that the returned status code of the chain extension method always indicates success and therefore always loads and decodes the output buffer of the call. + +# Usage + +Applicable to chain extension methods. + +**Default value:** `true`. + +# Example + +``` +type Access = i32; + +#[ink::chain_extension] +pub trait MyChainExtension { + type ErrorCode = i32; + + #[ink(extension = 5, handle_status = false)] + fn key_access_for_account(key: &[u8], account: &[u8]) -> Access; +} +``` + +OR + +``` +type Access = i32; + +#[ink::chain_extension] +pub trait MyChainExtension { + type ErrorCode = i32; + + #[ink(extension = 5)] + #[ink(handle_status = false)] + fn key_access_for_account(key: &[u8], account: &[u8]) -> Access; +} +``` +"#; + +/// Ref: . +/// +/// Ref: . +/// +/// Ref: . +pub const IMPL_DOC: &str = r#" +# Attribute + +`#[ink(impl)]` + +# Description + +Tells the ink! codegen that some implementation block shall be granted access to ink! internals even without it containing any ink! messages or ink! constructors. + +# Usage + +Applicable to ink! implementation blocks. + +# Example + +``` +#[ink(impl)] +impl MyContract { + fn my_function(&self) { + // inherent method implementation + unimplemented!() + } +} +``` +"#; + +/// Ref: . +/// +/// Ref: . +/// +/// Ref: . +/// +/// Ref: . +pub const KEEP_ATTR_DOC: &str = r#" +# Attribute + +`#[ink::contract(keep_attr = N:string)]` or `#[ink::trait_definition(keep_attr = N:string)]` + +# Description + +Tells the ink! code generator which attributes should be passed to call builders. + +Call builders are used to doing cross-contract calls and are automatically generated for contracts. + +# Usage + +Additional argument for ink! contract and ink! trait definition attribute macros. + +**Allowed attributes by default:** `cfg`, `cfg_attr`, `allow`, `warn`, `deny`, `forbid`, `deprecated`, `must_use`, `doc`, `rustfmt`. + +# Example + +``` +#[ink::contract(keep_attr = "foo, bar")] +mod my_contract { + #[ink(storage)] + pub struct MyContract; + + impl MyContract { + #[ink(constructor)] + #[bar] + pub fn new() -> Self { MyContract {} } + + #[ink(message)] + #[foo] + pub fn message(&self) {} + } + + // -snip- +} +``` + +OR + +``` +#[ink::trait_definition(keep_attr = "foo, bar")] +pub trait MyTrait { + #[ink(message)] + #[foo] + fn message1(&self); + + #[ink(message)] + #[bar] + fn message2(&self); +} +``` +"#; + +/// Ref: . +/// +/// Ref: . +/// +/// Ref: . +pub const MESSAGE_DOC: &str = r#" +# Attribute + +`#[ink(message)]` + +# Description + +Flags a method for the ink! storage `struct` as message making it available to the API for calling the contract. + +# Usage + +Applicable to methods. + +# Example + +``` +#[ink::contract] +mod my_contract { + // -snip- + + impl MyContract { + // -snip- + + /// Updates the current value. + #[ink(message)] + pub fn set(&mut self) { + self.value = !self.value; + } + + /// Returns the current value. + #[ink(message)] + pub fn get(&self) -> bool { + self.value + } + } +} +``` +"#; + +/// Ref: . +/// +/// Ref: . +pub const NAMESPACE_DOC: &str = r#" +# Attribute + +`#[ink(namespace = N:string)]` + +# Description + +Changes the resulting selectors of all the ink! messages and ink! constructors within the trait implementation. + +Allows to disambiguate between trait implementations with overlapping message or constructor names. + +**Use only with great care and consideration!** + +# Usage + +Applicable to ink! trait implementation blocks. + +**Default value:** Empty. + +# Example + +``` +#[ink::trait_definition(namespace = "foo")] +pub trait TraitDefinition { + #[ink(message)] + fn message1(&self); + + #[ink(message, selector = 42)] + fn message2(&self); +} +``` +"#; + +/// Ref: . +/// +/// Ref: . +/// +/// Ref: . +pub const PAYABLE_DOC: &str = r#" +# Attribute + +`#[ink(payable)]` + +# Description + +Allows receiving value as part of the call of the ink! message. + +ink! constructors are implicitly payable. + +# Usage + +Applicable to ink! messages. + +# Example + +``` +#[ink::contract] +mod my_contract { + // -snip- + + impl MyContract { + // -snip- + + /// Update the current value. + #[ink(message)] + #[ink(payable)] // You can either specify payable out-of-line. + pub fn set(&mut self) { + self.value = !self.value; + } + + /// Returns the current value. + #[ink(message, payable)] // ...or specify payable inline. + pub fn get(&self) -> bool { + self.value + } + } +} +``` +"#; + +/// Ref: . +/// +/// Ref: . +/// +/// Ref: . +pub const SELECTOR_DOC: &str = r#" +# Attribute + +`#[ink(selector = S:u32 | _)]` + +# Description + +The `#[ink(selector = S:u32)]` variant specifies a concrete dispatch selector for the flagged entity. +This allows a contract author to precisely control the selectors of their APIs making it possible to rename their API without breakage. + +While the `#[ink(selector = _)]` variant specifies a fallback message that is invoked if no other ink! message matches a selector. + +# Usage + +The `#[ink(selector = S:u32)]` variant is applicable to ink! messages and ink! constructors. + +While the `#[ink(selector = _)]` variant is applicable to ink! messages. + +# Example + +``` +#[ink::contract] +mod my_contract { + // -snip- + + impl MyContract { + #[ink(constructor)] + #[ink(selector = 0xDEADBEEF)] // Works on constructors as well. + pub fn new(initial_value: bool) -> Self { + MyContract { value: false } + } + + /// Updates the current value. + #[ink(message)] + #[ink(selector = 0xCAFEBABE)] // You can either specify selector out-of-line. + pub fn set(&mut self) { + self.value = !self.value; + } + + /// Returns the current value. + #[ink(message, selector = 0xFEEDBEEF)] // ...or specify selector inline. + pub fn get(&self) -> bool { + self.value + } + } +} +``` +"#; + +/// Ref: . +/// +/// Ref: . +/// +/// Ref: . +pub const STORAGE_DOC: &str = r#" +# Attribute + +`#[ink(storage)]` + +# Description + +Defines the ink! storage `struct`. + +There can only be one ink! storage definition per contract. + +# Usage + +On `struct` definitions. + +# Example + +``` +#[ink::contract] +mod my_contract { + #[ink(storage)] + pub struct MyContract { + value: bool, + } + + // -snip- +} +``` +"#; + +/// Ref: . +pub const TOPIC_DOC: &str = r#" +# Attribute + +`#[ink(topic)]` + +# Description + +Tells the ink! codegen to provide a topic hash for the given field. + +Every ink! event can only have a limited number of such topic fields. +Similar semantics as to indexed event arguments in Solidity. + +# Usage + +Applicable on ink! event field. + +# Example + +``` +#[ink::contract] +mod my_contract { + #[ink(event)] + pub struct MyEvent { + #[ink(topic)] + value: bool, + } + + // -snip- +} +``` +"#; diff --git a/crates/analyzer/src/analysis/hover/content/macros.rs b/crates/analyzer/src/analysis/hover/content/macros.rs new file mode 100644 index 0000000..e7cbe2d --- /dev/null +++ b/crates/analyzer/src/analysis/hover/content/macros.rs @@ -0,0 +1,1198 @@ +//! Hover content for ink! attribute macros. + +/// Ref: . +/// +/// Ref: . +pub const CHAIN_EXTENSION_DOC: &str = r#" +Defines the interface for a chain extension. + +# Structure + +The interface consists of an error code that indicates lightweight errors +as well as the definition of some chain extension methods. + +The overall structure follows that of a simple Rust trait definition. +The error code is defined as an associated type definition of the trait definition. +The methods are defined as associated trait methods without implementation. + +Chain extension methods must not have a `self` receiver such as `&self` or `&mut self` +and must have inputs and output that implement the SCALE encoding and decoding. +Their return value follows specific rules that can be altered using the +`handle_status` attribute which is described in more detail below. + +# Usage + +Usually the chain extension definition using this procedural macro is provided +by the author of the chain extension in a separate crate. +ink! smart contracts using this chain extension simply depend on this crate +and use its associated environment definition in order to make use of +the methods provided by the chain extension. + +# Attributes + +There are three different attributes with which the chain extension methods +can be flagged: + +| Attribute | Required | Default Value | Description | +|:----------|:--------:|:--------------|:-----------:| +| `ink(extension = N: u32)` | Yes | - | Determines the unique function ID of the chain +extension method. | | `ink(handle_status = flag: bool)` | Optional | `true` | Assumes +that the returned status code of the chain extension method always indicates success +and therefore always loads and decodes the output buffer of the call. | + +As with all ink! attributes multiple of them can either appear in a contiguous list: +``` +# type Access = i32; +# #[ink::chain_extension] +# pub trait MyChainExtension { +# type ErrorCode = i32; +#[ink(extension = 5, handle_status = false)] +fn key_access_for_account(key: &[u8], account: &[u8]) -> Access; +# } +``` +…or as multiple stand alone ink! attributes applied to the same item: +``` +# type Access = i32; +# #[ink::chain_extension] +# pub trait MyChainExtension { +# type ErrorCode = i32; +#[ink(extension = 5)] +#[ink(handle_status = false)] +fn key_access_for_account(key: &[u8], account: &[u8]) -> Access; +# } +``` + +## Details: `handle_status` + +Default value: `true` + +By default all chain extension methods should return a `Result` where `E: +From`. The `Self::ErrorCode` represents the error code of the chain +extension. This means that a smart contract calling such a chain extension method +first queries the returned status code of the chain extension method and only loads +and decodes the output if the returned status code indicates a successful call. +This design was chosen as it is more efficient when no output besides the error +code is required for a chain extension call. When designing a chain extension try to +utilize the error code to return errors and only use the output buffer for information +that does not fit in a single `u32` value. + +A chain extension method that is flagged with `handle_status = false` assumes that the +returned error code will always indicate success. Therefore it will always load and +decode the output buffer and loses the `E: From` constraint for the +call. + +Note that if a chain extension method does not return `Result` where `E: +From` but `handle_status = true` it will still return a value of type +`Result`. + +## Usage: `handle_status` + +Use both `handle_status = false` and non-`Result` return type for the same chain +extension method if a call to it may never fail and never returns a `Result` type. + +# Combinations + +Due to the possibility to flag a chain extension method with `handle_status` and +return or not `Result` there are 4 different cases with slightly varying +semantics: + +| `handle_status` | Returns `Result` | Effects | +|:---------------:|:----------------:|:--------| +| `true` | `true` | The chain extension method is required to return a value of type +`Result` where `E: From`. A call will always check if the +returned status code indicates success and only then will load and decode the value in +the output buffer. | | `true` | `false` | The chain extension method may return any +non-`Result` type. A call will always check if the returned status code indicates +success and only then will load and decode the value in the output buffer. The actual +return type of the chain extension method is still `Result` when +the chain extension method was defined to return a value of type `T`. | | `false` | +`true` | The chain extension method is required to return a value of type `Result`. A call will always assume that the returned status code indicates success and +therefore always load and decode the output buffer directly. | | `false` | `false` | +The chain extension method may return any non-`Result` type. A call will always assume +that the returned status code indicates success and therefore always load and decode +the output buffer directly. | + +# Error Code + +Every chain extension defines exactly one `ErrorCode` using the following syntax: + +``` +#[ink::chain_extension] +pub trait MyChainExtension { + type ErrorCode = MyErrorCode; + + // more definitions +} +``` + +The defined `ErrorCode` must implement `FromStatusCode` which should be implemented as +a more or less trivial conversion from the `u32` status code to a `Result<(), +Self::ErrorCode>`. The `Ok(())` value indicates that the call to the chain extension +method was successful. + +By convention an error code of `0` represents success. +However, chain extension authors may use whatever suits their needs. + +# Example: Definition + +In the below example a chain extension is defined that allows its users to read and +write from and to the runtime storage using access privileges: + +``` +/// Custom chain extension to read to and write from the runtime. +#[ink::chain_extension] +pub trait RuntimeReadWrite { + type ErrorCode = ReadWriteErrorCode; + + /// Reads from runtime storage. + /// + /// # Note + /// + /// Actually returns a value of type `Result, Self::ErrorCode>`. + #[ink(extension = 1)] + fn read(key: &[u8]) -> Vec; + + /// Reads from runtime storage. + /// + /// Returns the number of bytes read and up to 32 bytes of the + /// read value. Unused bytes in the output are set to 0. + /// + /// # Errors + /// + /// If the runtime storage cell stores a value that requires more than + /// 32 bytes. + /// + /// # Note + /// + /// This requires `ReadWriteError` to implement `From` + /// and may potentially return any `Self::ErrorCode` through its return value. + #[ink(extension = 2)] + fn read_small(key: &[u8]) -> Result<(u32, [u8; 32]), ReadWriteError>; + + /// Writes into runtime storage. + /// + /// # Note + /// + /// Actually returns a value of type `Result<(), Self::ErrorCode>`. + #[ink(extension = 3)] + fn write(key: &[u8], value: &[u8]); + + /// Returns the access allowed for the key for the caller. + /// + /// # Note + /// + /// Assumes to never fail the call and therefore always returns `Option`. + #[ink(extension = 4, handle_status = false)] + fn access(key: &[u8]) -> Option; + + /// Unlocks previously aquired permission to access key. + /// + /// # Errors + /// + /// If the permission was not granted. + /// + /// # Note + /// + /// Assumes the call to never fail and therefore does _NOT_ require `UnlockAccessError` + /// to implement `From` as in the `read_small` method above. + #[ink(extension = 5, handle_status = false)] + fn unlock_access(key: &[u8], access: Access) -> Result<(), UnlockAccessError>; +} +# #[derive(scale::Encode, scale::Decode, scale_info::TypeInfo)] +# pub enum ReadWriteErrorCode { +# InvalidKey, +# CannotWriteToKey, +# CannotReadFromKey, +# } +# #[derive(scale::Encode, scale::Decode, scale_info::TypeInfo)] +# pub enum ReadWriteError { +# ErrorCode(ReadWriteErrorCode), +# BufferTooSmall { required_bytes: u32 }, +# } +# impl From for ReadWriteError { +# fn from(error_code: ReadWriteErrorCode) -> Self { +# Self::ErrorCode(error_code) +# } +# } +# impl From for ReadWriteError { +# fn from(_: scale::Error) -> Self { +# panic!("encountered unexpected invalid SCALE encoding") +# } +# } +# #[derive(scale::Encode, scale::Decode, scale_info::TypeInfo)] +# pub struct UnlockAccessError { +# reason: String, +# } +# impl From for UnlockAccessError { +# fn from(_: scale::Error) -> Self { +# panic!("encountered unexpected invalid SCALE encoding") +# } +# } +# #[derive(scale::Encode, scale::Decode, scale_info::TypeInfo)] +# pub enum Access { +# ReadWrite, +# ReadOnly, +# WriteOnly, +# } +# impl ink_env::chain_extension::FromStatusCode for ReadWriteErrorCode { +# fn from_status_code(status_code: u32) -> Result<(), Self> { +# match status_code { +# 0 => Ok(()), +# 1 => Err(Self::InvalidKey), +# 2 => Err(Self::CannotWriteToKey), +# 3 => Err(Self::CannotReadFromKey), +# _ => panic!("encountered unknown status code"), +# } +# } +# } +``` + +All the error types and other utility types used in the chain extension definition +above are often required to implement various traits such as SCALE's `Encode` and +`Decode` as well as `scale-info`'s `TypeInfo` trait. + +A full example of the above chain extension definition can be seen +[here](https://github.com/paritytech/ink/blob/017f71d60799b764425334f86b732cc7b7065fe6/crates/lang/macro/tests/ui/chain_extension/simple.rs). + +# Example: Environment + +In order to allow ink! smart contracts to use the above defined chain extension it +needs to be integrated into an `Environment` definition as shown below: + +``` +# type RuntimeReadWrite = i32; +# +use ink_env::{ + DefaultEnvironment, + Environment, +}; + +pub enum CustomEnvironment {} + +impl Environment for CustomEnvironment { + const MAX_EVENT_TOPICS: usize = + ::MAX_EVENT_TOPICS; + + type AccountId = ::AccountId; + type Balance = ::Balance; + type Hash = ::Hash; + type BlockNumber = ::BlockNumber; + type Timestamp = ::Timestamp; + + type ChainExtension = RuntimeReadWrite; +} +``` + +Above we defined the `CustomEnvironment` which defaults to ink!'s `DefaultEnvironment` +for all constants and types but the `ChainExtension` type which is assigned to our +newly defined chain extension. + +# Example: Usage + +An ink! smart contract can use the above defined chain extension through the +`Environment` definition defined in the last example section using the `env` macro +parameter as shown below. + +Note that chain extension methods are accessible through `Self::extension()` or +`self.extension()`. For example as in `Self::extension().read(...)` or +`self.extension().read(...)`. + +``` +#[ink::contract(env = CustomEnvironment)] +mod read_writer { + #[ink(storage)] + pub struct ReadWriter {} + + impl ReadWriter { + #[ink(constructor)] + pub fn new() -> Self { Self {} } + + #[ink(message)] + pub fn read(&self, key: Vec) -> Result, ReadWriteErrorCode> { + self.env() + .extension() + .read(&key) + } + + #[ink(message)] + pub fn read_small(&self, key: Vec) -> Result<(u32, [u8; 32]), ReadWriteError> { + self.env() + .extension() + .read_small(&key) + } + + #[ink(message)] + pub fn write( + &self, + key: Vec, + value: Vec, + ) -> Result<(), ReadWriteErrorCode> { + self.env() + .extension() + .write(&key, &value) + } + + #[ink(message)] + pub fn access(&self, key: Vec) -> Option { + self.env() + .extension() + .access(&key) + } + + #[ink(message)] + pub fn unlock_access(&self, key: Vec, access: Access) -> Result<(), UnlockAccessError> { + self.env() + .extension() + .unlock_access(&key, access) + } + } +# /// Custom chain extension to read to and write from the runtime. +# #[ink::chain_extension] +# pub trait RuntimeReadWrite { +# type ErrorCode = ReadWriteErrorCode; +# #[ink(extension = 1)] +# fn read(key: &[u8]) -> Vec; +# #[ink(extension = 2)] +# fn read_small(key: &[u8]) -> Result<(u32, [u8; 32]), ReadWriteError>; +# #[ink(extension = 3)] +# fn write(key: &[u8], value: &[u8]); +# #[ink(extension = 4, handle_status = false)] +# fn access(key: &[u8]) -> Option; +# #[ink(extension = 5, handle_status = false)] +# fn unlock_access(key: &[u8], access: Access) -> Result<(), UnlockAccessError>; +# } +# #[derive(scale::Encode, scale::Decode, scale_info::TypeInfo)] +# pub enum ReadWriteErrorCode { +# InvalidKey, +# CannotWriteToKey, +# CannotReadFromKey, +# } +# #[derive(scale::Encode, scale::Decode, scale_info::TypeInfo)] +# pub enum ReadWriteError { +# ErrorCode(ReadWriteErrorCode), +# BufferTooSmall { required_bytes: u32 }, +# } +# impl From for ReadWriteError { +# fn from(error_code: ReadWriteErrorCode) -> Self { +# Self::ErrorCode(error_code) +# } +# } +# impl From for ReadWriteError { +# fn from(_: scale::Error) -> Self { +# panic!("encountered unexpected invalid SCALE encoding") +# } +# } +# #[derive(scale::Encode, scale::Decode, scale_info::TypeInfo)] +# pub struct UnlockAccessError { +# reason: String, +# } +# impl From for UnlockAccessError { +# fn from(_: scale::Error) -> Self { +# panic!("encountered unexpected invalid SCALE encoding") +# } +# } +# #[derive(scale::Encode, scale::Decode, scale_info::TypeInfo)] +# pub enum Access { +# ReadWrite, +# ReadOnly, +# WriteOnly, +# } +# impl ink_env::chain_extension::FromStatusCode for ReadWriteErrorCode { +# fn from_status_code(status_code: u32) -> Result<(), Self> { +# match status_code { +# 0 => Ok(()), +# 1 => Err(Self::InvalidKey), +# 2 => Err(Self::CannotWriteToKey), +# 3 => Err(Self::CannotReadFromKey), +# _ => panic!("encountered unknown status code"), +# } +# } +# } +# pub enum CustomEnvironment {} +# impl ink_env::Environment for CustomEnvironment { +# const MAX_EVENT_TOPICS: usize = +# ::MAX_EVENT_TOPICS; +# +# type AccountId = ::AccountId; +# type Balance = ::Balance; +# type Hash = ::Hash; +# type BlockNumber = ::BlockNumber; +# type Timestamp = ::Timestamp; +# +# type ChainExtension = RuntimeReadWrite; +# } +} +``` + +# Technical Limitations + +- Due to technical limitations it is not possible to refer to the `ErrorCode` + associated type using `Self::ErrorCode` anywhere within the chain extension and its + defined methods. Instead chain extension authors should directly use the error code + type when required. This limitation might be lifted in future versions of ink!. +- It is not possible to declare other chain extension traits as super traits or super + chain extensions of another. +"#; + +/// Ref: . +/// +/// Ref: . +pub const CONTRACT_DOC: &str = r#" +Entry point for writing ink! smart contracts. + +If you are a beginner trying to learn ink! we recommend you to check out +our extensive [ink! workshop](https://docs.substrate.io/tutorials/v3/ink-workshop/pt1). + +# Description + +The macro does analysis on the provided smart contract code and generates +proper code. + +ink! smart contracts can compile in several different modes. +There are two main compilation models using either +- on-chain mode: `no_std` and WebAssembly as target +- off-chain mode: `std` + +We generally use the on-chain mode for actual smart contract instantiation +whereas we use the off-chain mode for smart contract testing using the +off-chain environment provided by the `ink_env` crate. + +# Usage + +## Header Arguments + +The `#[ink::contract]` macro can be provided with some additional comma-separated +header arguments: + +- `keep_attr: String` + + Tells the ink! code generator which attributes should be passed to call builders. + Call builders are used to doing cross-contract calls and are automatically + generated for contracts. + + **Usage Example:** + ``` + #[ink::contract(keep_attr = "foo, bar")] + mod my_contract { + # #[ink(storage)] + # pub struct MyStorage; + # impl MyStorage { + # #[ink(constructor)] + // #[bar] + # pub fn construct() -> Self { MyStorage {} } + # #[ink(message)] + // #[foo] + # pub fn message(&self) {} + # } + // ... + } + ``` + + **Allowed attributes by default:** `cfg`, `cfg_attr`, `allow`, `warn`, `deny`, +`forbid`, `deprecated`, `must_use`, `doc`, `rustfmt`. + +- `env: impl Environment` + + Tells the ink! code generator which environment to use for the ink! smart +contract. The environment must implement the `Environment` (defined in `ink_env`) +trait and provides all the necessary fundamental type definitions for `Balance`, +`AccountId` etc. + + When using a custom `Environment` implementation for a smart contract all types + that it exposes to the ink! smart contract and the mirrored types used in the +runtime must be aligned with respect to SCALE encoding and semantics. + + **Usage Example:** + + Given a custom `Environment` implementation: + ``` + pub struct MyEnvironment; + + impl ink_env::Environment for MyEnvironment { + const MAX_EVENT_TOPICS: usize = 3; + type AccountId = [u8; 16]; + type Balance = u128; + type Hash = [u8; 32]; + type Timestamp = u64; + type BlockNumber = u32; + type ChainExtension = ::ink::env::NoChainExtension; + } + ``` + A user might implement their ink! smart contract using the above custom +`Environment` implementation as demonstrated below: + ``` + #[ink::contract(env = MyEnvironment)] + mod my_contract { + # pub struct MyEnvironment; + # + # impl ink_env::Environment for MyEnvironment { + # const MAX_EVENT_TOPICS: usize = 3; + # type AccountId = [u8; 16]; + # type Balance = u128; + # type Hash = [u8; 32]; + # type Timestamp = u64; + # type BlockNumber = u32; + # type ChainExtension = ::ink::env::NoChainExtension; + # } + # + # #[ink(storage)] + # pub struct MyStorage; + # impl MyStorage { + # #[ink(constructor)] + # pub fn construct() -> Self { MyStorage {} } + # #[ink(message)] + # pub fn message(&self) {} + # } + // ... + } + ``` + + **Default value:** `DefaultEnvironment` defined in `ink_env` crate. + +## Analysis + +The `#[ink::contract]` macro fully analyses its input smart contract +against invalid arguments and structure. + +Some example rules include but are not limited to: + +- There must be exactly one `#[ink(storage)]` struct. + + This struct defines the layout of the storage that the ink! smart contract +operates on. The user is able to use a variety of built-in facilities, combine +them in various ways or even provide their own implementations of storage data +structures. + + For more information visit the `ink::storage` crate documentation. + + **Example:** + + ``` + #[ink::contract] + mod flipper { + #[ink(storage)] + pub struct Flipper { + value: bool, + } + # impl Flipper { + # #[ink(constructor)] + # pub fn construct() -> Self { Flipper { value: false } } + # #[ink(message)] + # pub fn message(&self) {} + # } + } + ``` + +- There must be at least one `#[ink(constructor)]` defined method. + + Methods flagged with `#[ink(constructor)]` are special in that they are +dispatchable upon contract instantiation. A contract may define multiple such +constructors which allow users of the contract to instantiate a contract in +multiple different ways. + + **Example:** + + Given the `Flipper` contract definition above we add an `#[ink(constructor)]` + as follows: + + ``` + # #[ink::contract] + # mod flipper { + # #[ink(storage)] + # pub struct Flipper { + # value: bool, + # } + impl Flipper { + #[ink(constructor)] + pub fn new(initial_value: bool) -> Self { + Flipper { value: false } + } + # #[ink(message)] + # pub fn message(&self) {} + } + # } + ``` + +- There must be at least one `#[ink(message)]` defined method. + + Methods flagged with `#[ink(message)]` are special in that they are dispatchable + upon contract invocation. The set of ink! messages defined for an ink! smart +contract define its API surface with which users are allowed to interact. + + An ink! smart contract can have multiple such ink! messages defined. + + **Note:** + + - An ink! message with a `&self` receiver may only read state whereas an ink! + message with a `&mut self` receiver may mutate the contract's storage. + + **Example:** + + Given the `Flipper` contract definition above we add some `#[ink(message)]` +definitions as follows: + + ``` + # #[ink::contract] + # mod flipper { + # #[ink(storage)] + # pub struct Flipper { + # value: bool, + # } + impl Flipper { + # #[ink(constructor)] + # pub fn new(initial_value: bool) -> Self { + # Flipper { value: false } + # } + /// Flips the current value. + #[ink(message)] + pub fn flip(&mut self) { + self.value = !self.value; + } + + /// Returns the current value. + #[ink(message)] + pub fn get(&self) -> bool { + self.value + } + } + # } + ``` + + **Payable Messages:** + + An ink! message by default will reject calls that additional fund the smart +contract. Authors of ink! smart contracts can make an ink! message payable by +adding the `payable` flag to it. An example below: + + Note that ink! constructors are always implicitly payable and thus cannot be +flagged as such. + + ``` + # #[ink::contract] + # mod flipper { + # #[ink(storage)] + # pub struct Flipper { + # value: bool, + # } + impl Flipper { + # #[ink(constructor)] + # pub fn new(initial_value: bool) -> Self { + # Flipper { value: false } + # } + /// Flips the current value. + #[ink(message)] + #[ink(payable)] // You can either specify payable out-of-line. + pub fn flip(&mut self) { + self.value = !self.value; + } + + /// Returns the current value. + #[ink(message, payable)] // ...or specify payable inline. + pub fn get(&self) -> bool { + self.value + } + } + # } + ``` + + **Controlling the messages selector:** + + Every ink! message and ink! constructor has a unique selector with which the + message or constructor can be uniquely identified within the ink! smart contract. + These selectors are mainly used to drive the contract's dispatch upon calling it. + + An ink! smart contract author can control the selector of an ink! message or ink! + constructor using the `selector` flag. An example is shown below: + + ``` + # #[ink::contract] + # mod flipper { + # #[ink(storage)] + # pub struct Flipper { + # value: bool, + # } + impl Flipper { + #[ink(constructor)] + #[ink(selector = 0xDEADBEEF)] // Works on constructors as well. + pub fn new(initial_value: bool) -> Self { + Flipper { value: false } + } + + /// Flips the current value. + #[ink(message)] + #[ink(selector = 0xCAFEBABE)] // You can either specify selector out-of-line. + pub fn flip(&mut self) { + self.value = !self.value; + } + + /// Returns the current value. + #[ink(message, selector = 0xFEEDBEEF)] // ...or specify selector inline. + pub fn get(&self) -> bool { + self.value + } + } + # } + ``` + +## Interacting with the Contract Executor + +The `ink_env` crate provides facilities to interact with the contract executor that +connects ink! smart contracts with the outer world. + +For example it is possible to query the current call's caller via: +``` +# ink_env::test::run_test::(|_| { +let caller = ink_env::caller::(); +# let _caller = caller; +# Ok(()) +# }).unwrap(); +``` + +However, ink! provides a much simpler way to interact with the contract executor +via its environment accessor. An example below: + +``` +#[ink::contract] +mod greeter { + use ink_env::debug_println; + + #[ink(storage)] + pub struct Greeter; + + impl Greeter { + #[ink(constructor)] + pub fn new() -> Self { + let caller = Self::env().caller(); + debug_println!("thanks for instantiation {:?}", caller); + Greeter {} + } + + #[ink(message, payable)] + pub fn fund(&self) { + let caller = self.env().caller(); + let value = self.env().transferred_value(); + debug_println!("thanks for the funding of {:?} from {:?}", value, caller); + } + } +} +``` + +## Events + +An ink! smart contract may define events that it can emit during contract execution. +Emitting events can be used by third party tools to query information about a +contract's execution and state. + +The following example ink! contract shows how an event `Transferred` is defined and +emitted in the `#[ink(constructor)]`. + +``` +#[ink::contract] +mod erc20 { + /// Defines an event that is emitted every time value is transferred. + #[ink(event)] + pub struct Transferred { + from: Option, + to: Option, + value: Balance, + } + + #[ink(storage)] + pub struct Erc20 { + total_supply: Balance, + // more fields... + } + + impl Erc20 { + #[ink(constructor)] + pub fn new(initial_supply: Balance) -> Self { + let caller = Self::env().caller(); + Self::env().emit_event(Transferred { + from: None, + to: Some(caller), + value: initial_supply, + }); + Self { + total_supply: initial_supply, + } + } + + #[ink(message)] + pub fn total_supply(&self) -> Balance { + self.total_supply + } + } +} +``` + +## Example: Flipper + +The below code shows the complete implementation of the so-called Flipper +ink! smart contract. +For us it acts as the "Hello, World!" of the ink! smart contracts because +it is minimal while still providing some more or less useful functionality. + +It controls a single `bool` value that can be either `false` or `true` +and allows the user to flip this value using the `Flipper::flip` message +or retrieve the current value using `Flipper::get`. + +``` +#[ink::contract] +pub mod flipper { + #[ink(storage)] + pub struct Flipper { + value: bool, + } + + impl Flipper { + /// Creates a new flipper smart contract initialized with the given value. + #[ink(constructor)] + pub fn new(init_value: bool) -> Self { + Self { value: init_value } + } + + /// Flips the current value of the Flipper's boolean. + #[ink(message)] + pub fn flip(&mut self) { + self.value = !self.value; + } + + /// Returns the current value of the Flipper's boolean. + #[ink(message)] + pub fn get(&self) -> bool { + self.value + } + } +} +``` +"#; + +/// Ref: . +/// +/// Ref: . +pub const STORAGE_ITEM_DOC: &str = r#" +Prepares the type to be fully compatible and usable with the storage. +It implements all necessary traits and calculates the storage key for types. +`Packed` types don't have a storage key, but non-packed types (like `Mapping`, `Lazy` +etc.) require calculating the storage key during compilation. + +Consider annotating structs and enums that are intended to be a part of +the storage with this macro. If the type is packed then the usage of the +macro is optional. + +If the type is non-packed it is best to rely on automatic storage key +calculation via `ink::storage_item`. + +The usage of `KEY: StorageKey` generic allows to propagate the parent's storage key to +the type and offset the storage key of the type. It is helpful for non-packed types +that can be used several times in the contract. Each field should have a unique +storage key, so propagation of the parent's storage key allows one to achieve it. + +The macro should be called before `derive` macros because it can change the type. + +All required traits can be: +- Derived manually via `#[derive(...)]`. +- Derived automatically via deriving of `scale::Decode` and `scale::Encode`. +- Derived via this macro. + +# Example + +## Trait implementation + +``` +use ink_prelude::vec::Vec; +use ink::storage::{ + Lazy, + Mapping, +}; +use ink::storage::traits::{ + StorageKey, + StorableHint, +}; +use ink::storage::traits::Storable; + +// Deriving `scale::Decode` and `scale::Encode` also derives blanket implementation of all +// required traits to be storable. +#[derive(scale::Decode, scale::Encode)] +#[cfg_attr( + feature = "std", + derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout) +)] +#[derive(Default, Debug)] +struct Packed { + s1: u128, + s2: Vec, + // Fails because `StorableHint` is only implemented for `Vec` where `T: Packed`. + // s3: Vec, +} + +// Example of how to define the packed type with generic. +#[derive(scale::Decode, scale::Encode)] +#[cfg_attr( + feature = "std", + derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout) +)] +#[derive(Default, Debug)] +struct PackedGeneric { + s1: (u128, bool), + s2: Vec, + s3: String, +} + +// Example of how to define the non-packed type. +#[ink::storage_item] +#[derive(Default, Debug)] +struct NonPacked { + s1: Mapping, + s2: Lazy, +} + +// Example of how to define the non-packed generic type. +#[ink::storage_item(derive = false)] +#[derive(Storable, StorableHint, StorageKey)] +#[cfg_attr( + feature = "std", + derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout) +)] +#[derive(Default, Debug)] +struct NonPackedGeneric +where + T: Default + core::fmt::Debug, + T: ink::storage::traits::Packed, +{ + s1: u32, + s2: T, + s3: Mapping, +} + +// Example of how to define a complex packed type. +#[derive(scale::Decode, scale::Encode)] +#[cfg_attr( + feature = "std", + derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout) +)] +#[derive(Default, Debug)] +struct PackedComplex { + s1: u128, + s2: Vec, + s3: Vec, +} + +// Example of how to define a complex non-packed type. +#[ink::storage_item] +#[derive(Default, Debug)] +struct NonPackedComplex { + s1: (String, u128, Packed), + s2: Mapping, + s3: Lazy, + s4: Mapping, + s5: Lazy, + s6: PackedGeneric, + s7: NonPackedGeneric, + // Fails because: the trait `ink::storage::traits::Packed` is not implemented for `NonPacked` + // s8: Mapping, +} +``` + +## Header Arguments + +The `#[ink::storage_item]` macro can be provided with an additional comma-separated +header argument: + +- `derive: bool` + + The `derive` configuration parameter is used to enable/disable auto deriving of + all required storage traits. + + **Usage Example:** + ``` + use ink::storage::Mapping; + use ink::storage::traits::{ + StorableHint, + StorageKey, + Storable, + }; + + #[ink::storage_item(derive = false)] + #[derive(StorableHint, Storable, StorageKey)] + struct NonPackedGeneric { + s1: u32, + s2: Mapping, + } + ``` + + **Default value:** true. +"#; + +/// Ref: . +/// +/// Ref: . +pub const TEST_DOC: &str = r#" +Defines a unit test that makes use of ink!'s off-chain testing capabilities. + +If your unit test does not require the existence of an off-chain environment +it is fine to not use this macro since it bears some overhead with the test. + +Note that this macro is not required to run unit tests that require ink!'s +off-chain testing capabilities but merely improves code readability. + +## How do you find out if your test requires the off-chain environment? + +Normally if the test recursively uses or invokes some contract methods that +call a method defined in `self.env()` or `Self::env()`. + +An examples is the following: + +```no_compile +let caller: AccountId = self.env().caller(); +``` + +# Example + +``` +#[cfg(test)] +mod tests { + // Conventional unit test that works with assertions. + #[ink::test] + fn test1() { + // test code comes here as usual + } + + // Conventional unit test that returns some Result. + // The test code can make use of operator-`?`. + #[ink::test] + fn test2() -> Result<(), ink_env::Error> { + // test code that returns a Rust Result type + } +} +``` +"#; + +/// Ref: . +/// +/// Ref: . +pub const TRAIT_DEFINITION_DOC: &str = r#" +Marks trait definitions to ink! as special ink! trait definitions. + +There are some restrictions that apply to ink! trait definitions that +this macro checks. Also ink! trait definitions are required to have specialized +structure so that the main [`#[ink::contract]`](`macro@crate::contract`) macro can +properly generate code for its implementations. + +# Example + +# Trait definition: + +``` +# type Balance = ::Balance; +# type AccountId = ::AccountId; + +#[ink::trait_definition] +pub trait Erc20 { + /// Returns the total supply of the ERC-20 smart contract. + #[ink(message)] + fn total_supply(&self) -> Balance; + + /// Transfers balance from the caller to the given address. + #[ink(message)] + fn transfer(&mut self, amount: Balance, to: AccountId) -> bool; + + // etc. +} +``` + +# Trait implementation + +Given the above trait definition you can implement it as shown below: + +``` +#[ink::contract] +mod base_erc20 { +# // We somehow cannot put the trait in the doc-test crate root due to bugs. +# #[ink::trait_definition] +# pub trait Erc20 { +# /// Returns the total supply of the ERC-20 smart contract. +# #[ink(message)] +# fn total_supply(&self) -> Balance; +# +# /// Transfers balance from the caller to the given address. +# #[ink(message)] +# fn transfer(&mut self, amount: Balance, to: AccountId) -> bool; +# } +# + #[ink(storage)] + pub struct BaseErc20 { + total_supply: Balance, + } + + impl BaseErc20 { + #[ink(constructor)] + pub fn new(initial_supply: Balance) -> Self { + Self { total_supply: initial_supply } + } + } + + impl Erc20 for BaseErc20 { + /// Returns the total supply of the ERC-20 smart contract. + #[ink(message)] + fn total_supply(&self) -> Balance { + self.total_supply + } + + #[ink(message)] + fn transfer(&mut self, amount: Balance, to: AccountId) -> bool { + unimplemented!() + } + } +} +``` + +## Header Arguments + +The `#[ink::trait_definition]` macro can be provided with some additional +comma-separated header arguments: + +- `namespace: String` + + The namespace configuration parameter is used to influence the generated + selectors of the ink! trait messages. This is useful to disambiguate + ink! trait definitions with equal names. + + **Usage Example:** + ``` + #[ink::trait_definition(namespace = "foo")] + pub trait TraitDefinition { + #[ink(message)] + fn message1(&self); + + #[ink(message, selector = 42)] + fn message2(&self); + } + ``` + + **Default value:** Empty. + +- `keep_attr: String` + + Tells the ink! code generator which attributes should be passed to call builders. + Call builders are used to doing cross-contract calls and are automatically + generated for contracts. + + **Usage Example:** + ``` + #[ink::trait_definition(keep_attr = "foo, bar")] + pub trait Storage { + #[ink(message)] + // #[foo] + fn message1(&self); + + #[ink(message)] + // #[bar] + fn message2(&self); + } + ``` + + **Allowed attributes by default:** `cfg`, `cfg_attr`, `allow`, `warn`, `deny`, +`forbid`, `deprecated`, `must_use`, `doc`, `rustfmt`. +"#; diff --git a/crates/analyzer/src/lib.rs b/crates/analyzer/src/lib.rs index c601a1f..33661d0 100644 --- a/crates/analyzer/src/lib.rs +++ b/crates/analyzer/src/lib.rs @@ -26,6 +26,6 @@ //! } //! ``` -pub use self::analysis::{Analysis, Completion, Diagnostic, Severity}; +pub use self::analysis::{Action, Analysis, Completion, Diagnostic, Hover, Severity}; mod analysis;