diff --git a/docs/build/img/swanky/index.md b/docs/build/img/swanky/index.md deleted file mode 100644 index 9daeafb..0000000 --- a/docs/build/img/swanky/index.md +++ /dev/null @@ -1 +0,0 @@ -test diff --git a/docs/learn/tutorials/define-events/_category_.json b/docs/learn/tutorials/define-events/_category_.json new file mode 100644 index 0000000..a880e25 --- /dev/null +++ b/docs/learn/tutorials/define-events/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "ink! Smart Contract Events", + "position": 5 +} diff --git a/docs/learn/tutorials/define-events/define-events.md b/docs/learn/tutorials/define-events/define-events.md new file mode 100644 index 0000000..bacced5 --- /dev/null +++ b/docs/learn/tutorials/define-events/define-events.md @@ -0,0 +1,187 @@ +# How to define ink! smart contract events + +## Prerequisites + +Before this tutorial, you should have already completed the [Flipper Tutorial](https://docs.astar.network/docs/tutorials/flipper_tutorial). This tutorial targets developers with no experience in ink! and a basic level in Rust. + +## To follow this tutorial you will need: + +- To [set up your ink! environment](https://docs.inkdevhub.io/docs/learn/build-environment/ink_environment) + +## What Will We Be Doing? + +In this tutorial we will implement events, using the most basic contract [Flipper](https://github.com/paritytech/ink/blob/v4.0.0/examples/flipper/lib.rs) as an example. + +## What Will We Use? + +- [ink! 4.2.0](https://github.com/paritytech/ink/tree/v4.2.0) +- [cargo-contract 3.2.0](https://github.com/paritytech/cargo-contract/tree/v3.2.0) +- [substrate-contracts-node](https://github.com/paritytech/substrate-contracts-node) + +## What Will You Learn? + +You will learn how to define contract events. + +# Events + +This is a step-by-step explanation of how to implement events in ink! smart contracts. + +## What are Events? + +Smart contract events refer to a feature in blockchain-based smart contracts that allows them to emit signals or notifications when specific conditions are met. Events in smart contracts provide a way for external entities, such as other smart contracts or off-chain applications, to be notified when certain actions or state changes occur within the contract. + +## Types Used in Events + +In Events we can use the same types as in Storage: + +- Rust primitives type + - `bool` + - `u{8,16,32,64,128}` + - `i{8,16,32,64,128}` + - `String` +- Substrate specific types: + - `AccountId` + - `Balance` + - `Hash` +- ink! provided + - `Vec` +- Custom data structure [details](https://use.ink/datastructures/custom-datastructure) + +## Examples + +Let’s take a look at the [Psp22 contract](https://github.com/w3f/PSPs/blob/master/PSPs/psp-22.md). There are two events used in Psp22: + +- Transfer: event emitted when a token transfer occurs. It has 3 arguments: `from`, `to`, and `value`. Where `from`- address of account which sends tokens, `to` - address of account which receives tokens, and `value` is the amount of tokens. +- Approve: event emitted when an approval occurs that `spender` is allowed to withdraw up to the amount of `value` tokens from `owner`. It has 3 arguments: `owner`, `spender`, and `value`. Where `owner`- the address of the account that owns tokens, `sender`- the address of the account that gets an allowance to spend tokens from the `owner`'s account, and `value` is the amount of tokens. + +# How to Define and Use Events? + +Let’s create a learning contract to understand how events work. + +## 1. Flipper Smart Contract + +In a new project folder, execute the following: + +```bash +cargo contract new flipper # flipper is introduced from the beginning. +``` + +## 2. Define an Event + +Let's define an event that will be emitted when the flipper is flipped. Events are just structs with the `#[ink(event)]` attribute and a `#[ink(topic)]` attribute on each field that should be indexed. + +```rust +/// Emitted whenever the stored value changes by a call to the `flip` or `new` methods. +#[ink(event)] +pub struct Flip { + #[ink(topic)] + status: bool, +} +``` + +Then we can emit the event in the `flip` function. + +```rust +pub fn flip(&mut self) { + self.value = !self.value; + + // Emit the `Flip` event. + self.env().emit_event(Flip { + status: self.value, + }); +} +``` + +## 3. Build and Deploy a Contract + +Now, we can compile the contract and deploy it by [Substrate Contracts UI](https://contracts-ui.substrate.io/) to test it. Run the following command to compile the contract: + +```bash +cargo contract build +``` + +Then run your local Substrate node: + +```bash +substrate-contracts-node --dev +``` + +Finally, deploy the contract with [Substrate Contracts UI](https://contracts-ui.substrate.io/). Let's flip the flipper and check the events emitted by the contract. As you can see, the event is emitted when the flipper is flipped and now we have the status `true`. + +We can flip it again and check the events emitted by the contract. As you can see, the event is emitted when the flipper is flipped and now we have the status `false`. + + +## 4. Change event field + +Let's make some changes to the contract and emit another event. Now we will store an integer value in the contract. + +```rust +#[ink(storage)] +pub struct Flipper { + value: i32, +} +``` + +Also, we need to update the `new` function to initialize the `number` field and replace the flip function with a `set` function that will set the value of the `number` field to change `bool` to `i32` in a `get` function. + +```rust +#[ink(constructor)] +pub fn new(init_value: i32) -> Self { + Self { + value: init_value, + } +} + +impl Flipper { + ... + + #[ink(message)] + pub fn set(&mut self, new_value: i32) { + self.value = new_value; + } + + #[ink(message)] + pub fn get(&self) -> i32 { + self.value + } +} +``` + +## 5. Define advanced event + +Let's define an event that will be emitted when the value is set. And we will make it more advanced. + +```rust +#[ink(event)] +pub struct Set { + #[ink(topic)] + old_value: i32, + #[ink(topic)] + new_value: i32, + account: AccountId, +} +``` + +Then we can emit the event in the `set` function. + +```rust +#[ink(message)] +pub fn set(&mut self, new_value: i32) { + let old_value = self.value; + self.value = new_value; + + self.env().emit_event(Set { + old_value, + new_value, + account: self.env().caller(), + }); +} +``` + +This event will be emitted when the value is set. Let's compile the contract and deploy it by [Substrate Contracts UI](https://contracts-ui.substrate.io/) and call the `set` function. + +As you can see, the event is emitted when the value is set and we can see the old value, the new value, and the account that is called the `set` function. + +# Conclusion + +Now you know how to define events in ink! smart contracts. You can check the full code of the contract [here](https://www.notion.so/How-to-define-events-bd4a04049a6a407a8674e33e53e59d57?pvs=21). diff --git a/docs/learn/tutorials/macros/_category_.json b/docs/learn/tutorials/macros/_category_.json new file mode 100644 index 0000000..e3c26ba --- /dev/null +++ b/docs/learn/tutorials/macros/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "ink! Smart Contract Macros", + "position": 6 +} diff --git a/docs/learn/tutorials/macros/macros.md b/docs/learn/tutorials/macros/macros.md new file mode 100644 index 0000000..ca29983 --- /dev/null +++ b/docs/learn/tutorials/macros/macros.md @@ -0,0 +1,245 @@ +# How to work with ink! smart contract macros + +## Prerequisites + +Before this tutorial, you should have already completed the [Flipper Tutorial](https://docs.astar.network/docs/tutorials/from-zero-to-ink-hero/flipper-contract/). This tutorial targets developers with no experience in ink! and a basic level in Rust. + +### To follow this tutorial you will need: + +- To [set up your ink! environment](https://docs.inkdevhub.io/docs/learn/build-environment/ink_environment) + +### What will we do? + +In this tutorial, we will learn how to use macros provided by ink! + +### What will we use? + +- [ink! 4.2.0](https://github.com/paritytech/ink/tree/v4.2.0) + +## #[ink::contract] + +The `#[ink::contract]` macro is used to initiate the creation of ink! smart contracts. This macro examines the given smart contract code and produces the required code structure. To use it you just need to define a simple rust module with `#[ink::contract]` macro. + +```rust +#[ink::contract] +mod contract { +... +} +``` + +However, in addition to that, every contract must include storage, at least one constructor, and a message. To define storage you can use `[#[ink(storage)]](https://www.notion.so/How-to-work-with-macro-1f6cad316db64eac8539e261d845901f?pvs=21)` macro, to define the constructor and message you need to use `[#[ink(constructor)]](https://www.notion.so/How-to-work-with-macro-1f6cad316db64eac8539e261d845901f?pvs=21)` and `[#[ink(message)]](https://www.notion.so/How-to-work-with-macro-1f6cad316db64eac8539e261d845901f?pvs=21)` macros. + +## #[ink(storage)] + +We have a separate tutorial that explains how to define and use storage. + +## #[ink(constructor)] + +This macro applies to a method that should return the contract object with the initial storage values. + +```rust +#[ink::contract] +mod contract { + #[ink(storage)] + pub struct Contract { + ... + } + + impl Contract { + #[ink(constructor)] + pub fn new(args...) -> Self { ... } + + #[ink(constructor)] + pub fn another_constructor() -> Self { ... } + + ... + } +} +``` + +The contract may define multiple constructors, allowing users to instantiate it in various ways. + +## #[ink(message)] + +The `#[ink(message)]` attribute is used to flag a method for the ink! storage struct as a message, allowing it to be called by the contract's API. It is important to note that all public functions must use this attribute. At least one method must be defined with `#[ink(message)]`. Methods marked with `#[ink(message)`] are special, because they can be dispatched when the contract is invoked. The ink! messages defined for an ink! smart contract determine its API surface for user interaction. Multiple ink! messages can be defined for an ink! smart contract. An ink! message with a `&self` receiver can only read state, while an ink! message with a `&mut self` receiver can mutate the contract's storage. + +```rust +#[ink::contract] +mod contract { + #[ink(storage)] + pub struct Contract { + ... + } + + impl Contract { + ... + #[ink(message)] + pub fn can_mutate_storage(&mut self, from: AccountId) { + ... + } + + #[ink(message)] + pub fn can_only_read_storage(&self, from: AccountId) { + ... + } + } +} +``` + +The return value of a message must implement `scale::Encode`. It is important to understand that the collections under `ink_storage`, such as `Vec` or `HashMap`, do not implement `scale::Encode`. This means that you cannot simply return a `Vec` from an ink! message. This restriction is intentional, as returning a complete data structure with potentially unbounded content is an anti-pattern for smart contracts due to unpredictable gas costs. If you truly need to return a data structure in its entirety, then use the ones from `ink_prelude` (for example, `ink_prelude::vec::Vec`), as they implement `scale::Encode`. + +```rust +#[ink::contract] +mod contract { + #[ink(storage)] + pub struct Contract { + ... + } + + impl Contract { + ... + #[ink(message)] + pub fn can_mutate_storage(&mut self, from: AccountId) { + ... + } + + #[ink(message)] + pub fn can_only_read_storage(&self, from: AccountId) { + ... + } + } +} +``` + +## #[ink(selector = …)] + +By default, ink! creates a selector for each message and constructor, which is necessary for invoking functions in the compiled Wasm blob. The `selector` attribute allows specifying a dispatch selector for the entity, giving control over API selectors and the ability to rename APIs without causing breakage. + +Selectors must be `u32` decodable integers, such as `selector = 0xCAFEBABE` or `selector = 42`. An exception is a fallback selector `_`, which allows contract calls not matching any other message selectors to be dispatched to a fallback message. Fallback messages can be `[payable](https://www.notion.so/How-to-work-with-macro-1f6cad316db64eac8539e261d845901f?pvs=21)`. + +```rust +#[ink(message, selector = 0xABCD123)] +fn message(&self) {} + +#[ink(message, selector = 5552)] +fn another_message(&self) {} + +#[ink(message, payable, selector = _)] +fn fallback(&self) {} +``` + +## #[ink(payable)] + +The ink! message allows receiving value as part of the call. Ink! constructors are implicitly payable due to the initial endowment required by a contract. By default, an ink! message will reject calls that try to add additional funds to the smart contract. However, the authors of ink! smart contracts can make an ink! message payable by adding the `payable` flag to it. It's important to note that ink! constructors are always implicitly payable and thus cannot be flagged as such. + +```rust +#[ink(message, payable)] +pub fn payable_message(&self) { + //stores the amount of token transferred to this message + let transferred_value = self.env().transferred_value(); +} +``` + +## #[ink(default)] + +This feature is applicable to ink! messages and constructors. It serves as a hint for UIs to determine whether a constructor or message should be selected as the default option. Only one constructor or message can be marked as the default. + +```rust +#[ink(constructor, default)] +pub fn default_constructor(&self) { +} + +#[ink(message, default)] +pub fn default_message(&self) { +} +``` + +## #[ink(event)] + +We have a separate tutorial that explains how to define events. You can find it [here](../define-events/define-events.md). + +## #[ink(topic)] + +Fields in ink! events can be designated as topics, prompting the ink! codegen to create a topic hash for the specified field. Each ink! event is limited to a certain number of these topic fields. This functionality is similar to indexed event arguments in Solidity. + +```rust +#[ink(event)] +pub struct Transferred { + #[ink(topic)] + from: Option, + + #[ink(topic)] + to: Option, + + amount: Balance +} +``` + +## #[ink(anonymous)] + +This attribute applies to ink! events and directs the ink! codegen to treat the ink! event as anonymous, excluding the event signature as a topic upon emitting. This behavior closely resembles anonymous events in Solidity. + +In ink!, anonymous events have similar semantics as in Solidity, which means that their event signature will not be included in their event topics serialization, reducing event emitting overhead. This functionality is particularly useful for user-defined events. + +By default, the event signature is one of the topics of the event, unless the event is annotated with `#[ink(anonymous)]`. This attribute implies that it is not possible to filter for specific anonymous events by name. + +```rust +#[ink(event)] +#[ink(anonymous)] +pub struct Event { + #[ink(topic)] + state: StateEnum, + value: i32, +} +``` + +## #[ink(impl)] + +This attribute is designed for a specific scenario that is rarely required. It can be used on ink! implementation blocks to make ink! aware of them. This is particularly useful for implementation blocks that don't contain any other ink! attributes, as they would otherwise be flagged by ink! as a Rust item. By adding `#[ink(impl)]` to such implementation blocks, they are treated as ink! implementation blocks, allowing access to the environment, etc. + +It's important to note that ink! messages and constructors still need to be explicitly flagged as such. + +```rust +#[ink::contract] +mod contract { + #[ink(storage)] + pub struct Contract { + ... + } + + impl MyStorage { + #[ink(constructor)] + pub fn constructor() -> Self { + ... + } + + #[ink(message)] + pub fn message(&self) { + ... + } + } + + #[ink(impl)] + impl MyStorage { + fn method(&self) -> i32 { + ... + } + } +} +``` + +## #[ink(namespace = "…")] + +Applicable to ink! trait implementation blocks for disambiguating other trait implementation blocks with equal names. This modifies the resulting selectors for all the ink! messages and ink! constructors within the trait implementation, allowing for disambiguation between trait implementations with overlapping messages or constructor names. + +```rust +#[ink(namespace = "namespace_name")] +impl SomeTrait for Contract { + #[ink(message)] + fn message(&self) {} +} +``` + +# Conclusion + +Now you have the knowledge of when and how to use the ink! macros. diff --git a/docs/learn/tutorials/storage/_category_.json b/docs/learn/tutorials/storage/_category_.json new file mode 100644 index 0000000..6976b13 --- /dev/null +++ b/docs/learn/tutorials/storage/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "ink! Smart Contract Macros", + "position": 7 +} diff --git a/src/css/custom.scss b/src/css/custom.scss index 381057c..fbb26f0 100644 --- a/src/css/custom.scss +++ b/src/css/custom.scss @@ -19,7 +19,7 @@ $astar-blue-dark: #05b6fd; --ifm-code-font-size: 95%; --box-background: #ffffff; --text-color-description: #001f40; - --text-color-title: #0085ff; + --text-color-title: #550CA7; } /* For readability concerns, you should choose a lighter palette in dark mode. */ @@ -52,4 +52,4 @@ $sm: 640px; $md: 768px; $lg: 1024px; $xl: 1280px; -$xxl: 1440px; \ No newline at end of file +$xxl: 1440px; diff --git a/src/pages/index.scss b/src/pages/index.scss index 8362490..ba3099b 100644 --- a/src/pages/index.scss +++ b/src/pages/index.scss @@ -5,7 +5,7 @@ text-align: center; position: relative; overflow: hidden; - background-image: url("/static/docs/build/img/mainpageheronew.svg" + background-image: url("/static/docs/build/img/main-page-hero-simple.png" ); @media (min-width: $lg) { padding: 2rem; diff --git a/static/docs/build/img/main-page-hero-simple.png b/static/docs/build/img/main-page-hero-simple.png new file mode 100644 index 0000000..4090b99 Binary files /dev/null and b/static/docs/build/img/main-page-hero-simple.png differ diff --git a/static/img/docs.svg b/static/img/docs.svg index 29aaa64..992177a 100644 --- a/static/img/docs.svg +++ b/static/img/docs.svg @@ -1,12 +1,17 @@ - - - - - + + + + + + + + + + - - + + diff --git a/static/img/wrench.svg b/static/img/wrench.svg index 5074e60..1e18b8f 100644 --- a/static/img/wrench.svg +++ b/static/img/wrench.svg @@ -1,3 +1,10 @@ - - + + + + + + + + +