Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added esdt_attribute description and short tutorial in abi page #736

Merged
merged 8 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
256 changes: 255 additions & 1 deletion docs/developers/data/abi.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ At its base minimum, an ABI contains:
}
],
"events": [],
"esdtAttributes": [],
"hasCallback": false,
"types": {}
}
Expand Down Expand Up @@ -145,7 +146,27 @@ But here it gets interesting: the ABI also needs to describe types that are defi
There is simply not enough room to do it inline with the arguments, so a separate section is necessary, which contains all these descriptions. This section is called `"types"`, and it can describe `struct` and `enum` types.


Have a look at this example with custom types:
Have a look at this example with custom types.

Let's take the following `enum` and `struct`:

```rust
#[derive(TypeAbi)]
pub struct MyAbiStruct<M: ManagedTypeApi> {
pub field1: BigUint<M>,
pub field2: ManagedVec<M, Option<u32>>,
pub field3: (bool, i32)
}

#[derive(TypeAbi)]
pub enum MyAbiEnum<M: ManagedTypeApi> {
Nothing,
Something(i32),
SomethingMore(u8, MyAbiStruct<M>),
}
```

And this is their json representation:

```json
{
Expand Down Expand Up @@ -177,6 +198,7 @@ Have a look at this example with custom types:
}
],
"events": [],
"esdtAttributes": [],
"hasCallback": false,
"types": {
"MyAbiStruct": {
Expand Down Expand Up @@ -279,3 +301,235 @@ Similarly, [enums](/developers/data/custom-types#custom-enums) are defined by:
- Struct-like variants, with named fields.

You can read more about Rust enums [here](https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html).

---

[comment]: # (mx-context-auto)

## ESDT Attribute ABI

### Overview
The framework will export all data types found in arguments, results, and events, but it doesn't intrinsically know abut the data that we use in SFT and NFT attributes. This is why there is a special annotation to specify this explicitly.

Starting with the framework version `0.44`, developers can use the new trait annotation `#[esdt_attribute("name", Type)]` in order to export ESDT attributes types in the ABI file.

andrei-marinica marked this conversation as resolved.
Show resolved Hide resolved
The name field is an arbitrary name provided by the developer, to identify the token. Token identifiers are not hard-coded in contracts, but it can make sense to use the ticker here, if known.

The type field is simply the name of the type, as it would show up in regular smart contract code.

:::important Important
The annotation can only be used at trait level along with `#[multiversx_sc::contract]` or `#[multiversx_sc::module]` annotations. Using it anywhere else will not work.
:::

andrei-marinica marked this conversation as resolved.
Show resolved Hide resolved
The exported data will end up in 2 places:
1. In the contract ABI, in a special `"esdt_attributes"` section;
2. In a special ESDT ABI file (`name.esdt-abi.json`), one for each such declared ESDT.

More examples of this below.

### Details

A new field called `esdtAttributes` was added to the ABI file, where developers can find the structs (name, type) exported using the `esdt_attribute` trait annotation. Additionally, each `esdt_attribute` will create a new json file with the name given by the developer (followed by `.esdt-abi`) and containing its exported structs (names, types and descriptions).

The name/ticker is just a way to identify the idea of the token because we do not have the exact identifier or the possibility to create it through this annotation. We only use this annotation as a mark up for a specific ESDT, in order to define its fields' attributes type. It is useful to define ESDT attributes' type beforehand in order to get more specific and overall better results fetching data from other services.

### Example using basic types

Let's take a simple contract `SomeContract` as an example and try out the new annotation.

```rust title=lib.rs
#![no_std]

multiversx_sc::imports!();
multiversx_sc::derive_imports!();

#[multiversx_sc::contract]
#[esdt_attribute("testBasic", BigUint)]
pub trait SomeContract {

#[init]
fn init(&self) {}
}
```

Adding the `#[esdt_attribute("testBasic", BigUint)]` at trait level along with `#[multiversx_sc::contract]` should export a new structure named `testBasic` with a `BigUint` field type. This structure resembles an ESDT with the ticker `testBasic` and the attributes fields of type `BigUint`.

The abi can be generated calling `sc-meta all abi` in the contract folder, or by building the contract using `sc-meta all build` (this command also adds the `wasm` file to the `output` folder).

Building the contract using `sc-meta all build` will generate the following folder structure:
```
some_contract
├── output
│ ├── some_contract.abi.json
│ ├── some_contract.imports.json
| ├── some_contract.mxsc.json
| ├── some_contract.wasm
│ ├── testBasic.esdt-abi.json
```

Let's check out the `some_contract.abi.json` file first. Here we discover the new `esdtAttributes` field, containing the value mentioned in the annotation.

```json
andrei-marinica marked this conversation as resolved.
Show resolved Hide resolved
{
"esdtAttributes": [
{
"ticker": "testBasic",
"type": "BigUint"
}
]
}

```
We can also check the specific json file exported for the newly defined type where we can find information about the type separated from the main abi file.

```json title=testBasic.esdt-abi.json
{
"esdtAttribute": {
"ticker": "testBasic",
"type": "BigUint"
}
}
```

### Using more complex types

Now, let's see what happens when we use other types than basic ones. Let's add a `Vec`, an `Enum (MyAbiEnum)` and an `Option` to our esdt attributes.


```rust title=lib.rs
#![no_std]

multiversx_sc::imports!();
multiversx_sc::derive_imports!();

#[multiversx_sc::contract]
#[esdt_attribute("testBasic", BigUint)]
#[esdt_attribute("testEnum", MyAbiEnum<Self::Api>)]
#[esdt_attribute("testOption", Option<TokenIdentifier>)]
#[esdt_attribute("testVec", ManagedVec<u64>)]
pub trait SomeContract {
#[init]
fn init(&self) {}
}
```

If we call `sc-meta all abi` (or `sc-meta all build` if we also wish to build the contract), the new attributes will be added to our __some_contract.abi.json__ file and new separate json files will be created for each attribute. Now, our `esdtAttributes` section from our abi file should look like this:

```json title=some_contract.abi.json
{
"esdtAttributes": [
{
"ticker": "testBasic",
"type": "BigUint"
},
{
"ticker": "testEnum",
"type": "MyAbiEnum"
},
{
"ticker": "testOption",
"type": "Option<TokenIdentifier>"
},
{
"ticker": "testVec",
"type": "List<u64>"
}
],
}
```

Now, if we take a look into the folder structure of the contract, we should see the following updated folder structure containing the newly generated files in `output`:

```
some_contract
├── output
│ ├── some_contract.abi.json
│ ├── some_contract.imports.json
| ├── some_contract.mxsc.json
| ├── some_contract.wasm
│ ├── testBasic.esdt-abi.json
│ ├── testEnum.esdt-abi.json
│ ├── testOption.esdt-abi.json
│ ├── testVec.esdt-abi.json
```

Each file contains the new struct with its name and the type field's description such as:

```json title=testOption.esdt-abi.json
{
"esdtAttribute": {
"ticker": "testOption",
"type": "Option<TokenIdentifier>"
}
}
```

Let's also add a custom `struct` into the mix. For this example we are going to use `MyAbiStruct` declared above.

Here is the updated code for __lib.rs:__

```rust title=lib.rs
#![no_std]

multiversx_sc::imports!();
multiversx_sc::derive_imports!();

#[multiversx_sc::contract]
#[esdt_attribute("testBasic", BigUint)]
#[esdt_attribute("testEnum", MyAbiEnum<Self::Api>)]
#[esdt_attribute("testOption", Option<TokenIdentifier>)]
#[esdt_attribute("testVec", ManagedVec<u64>)]
#[esdt_attribute("testStruct", MyAbiStruct<Self::Api>)]
pub trait SomeContract {
#[init]
fn init(&self) {}
}
```

Same as before, we use `sc-meta all abi` and a new file named `testStruct.esdt-abi.json` shows up in our folder structure:

```
some_contract
├── output
│ ├── some_contract.abi.json
│ ├── some_contract.imports.json
| ├── some_contract.mxsc.json
| ├── some_contract.wasm
│ ├── testBasic.esdt-abi.json
│ ├── testEnum.esdt-abi.json
│ ├── testOption.esdt-abi.json
│ ├── testStruct.esdt-abi.json
│ ├── testVec.esdt-abi.json
```


As a final check, let's take a look at what changed in the main abi file, `some_contract.abi.json`, after adding multiple new attributes.

```json title=some_contract.abi.json
{
"esdtAttributes": [
{
"ticker": "testBasic",
"type": "BigUint"
},
{
"ticker": "testEnum",
"type": "MyAbiEnum"
},
{
"ticker": "testOption",
"type": "Option<TokenIdentifier>"
},
{
"ticker": "testVec",
"type": "List<u64>"
},
{
"ticker": "testStruct",
"type": "MyAbiStruct"
}
],
}
```

You can find more examples containing multiple data types in the `abi-tester` from [here](https://github.com/multiversx/mx-sdk-rs/tree/master/contracts/feature-tests/abi-tester).
36 changes: 35 additions & 1 deletion docs/developers/meta/sc-meta-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,6 @@ Paramameters:
- Creates test files if they don't exist.



---

[comment]: # (mx-exclude-context)
Expand All @@ -289,6 +288,41 @@ Paramameters:

[comment]: # (mx-context-auto)

### Calling `abi`

ABI generation can be triggered by calling `sc-meta all abi` or `cargo run abi` in the contract root folder. This command generates the main ABI file of the contract (`<contract>.abi.json`) along with all the other json files created if `#[esdt_attribute("name", type)]` was used (`<name>.esdt-abi.json`). You can read more about ESDT Attribute ABI [here](/developers/data/abi#esdt-attribute-abi).

andrei-marinica marked this conversation as resolved.
Show resolved Hide resolved
ABI generation will also be triggered for all the other contract commands, such as `build`, `build-dbg`, `update`, etc. The `abi` command is for when we just want to generate the ABI and do nothing else.

For a simple contract such as:

```rust title=lib.rs
#[multiversx_sc::contract]
#[esdt_attribute("myTicker", u64)]
pub trait SomeContract {
#[init]
fn init(&self) {}
}
```

The produced files are:

```text
output
├── myTicker.esdt-abi.json
└── some_contract.abi.json
```

Arguments:

- `--path` Used to specify a target directory where to call all contract meta crates. Will be current directory if not specified.
- `--ignore` Followed by a name is used to ignore all directories with these names [default: `target`].
- `--no-abi-git-version` Skips loading the Git version into the ABI
- `--target-dir-meta` For the meta crates, allows specifying the target directory where the Rust compiler will build the intermediary files. Sharing the same target directory can speed up building multiple contract crates at once.
- `--target-dir-all` Overrides both the `--target-dir-meta` and the `--target-dir-wasm` args.

[comment]: # (mx-context-auto)

### Calling `build`

A build can be triggered by calling either `sc-meta all build` or `cargo run build` in the meta crate of the contract. In fact, the standalone `sc-meta` tool simply forwards the command to the contract meta crate itself.
Expand Down
4 changes: 2 additions & 2 deletions docs/developers/tutorials/crowdfunding-p1.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ Let's have a quick look around the project.

Open `Cargo.toml` in the text editor of your choice, and add the following content:

```toml,file=Cargo.toml
```toml title=Cargo.toml
[package]
name = "crowdfunding"
version = "0.0.0"
Expand Down Expand Up @@ -117,7 +117,7 @@ Let's see what this means:

With the structure in place, you can now write the code and build it. Open `src/crowdfunding.rs` , remove the existing `Empty` code and insert the following:

```rust,file=hello-world.rs
```rust title=hello-world.rs
#![no_std]

multiversx_sc::imports!();
Expand Down
8 changes: 4 additions & 4 deletions docs/developers/tutorials/crowdfunding-p2.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Also note that BigUint logic does not reside in the contract, but is built into

Let's test that initialization works.

```json,file=crowdfunding-init.scen.json
```json title=crowdfunding-init.scen.json
{
"name": "crowdfunding deployment test",
"steps": [
Expand Down Expand Up @@ -151,7 +151,7 @@ To test the function, we'll add a new test file, in the same `scenarios` folder.

To avoid duplicating the deployment code, we import it from `crowdfunding-init.scen.json` .

```json,file=crowdfunding-fund.scen.json
```json title=crowdfunding-fund.scen.json
{
"name": "crowdfunding funding",
"steps": [
Expand Down Expand Up @@ -268,7 +268,7 @@ It doesn't make sense to fund after the deadline has passed, so fund transaction

We'll create another test file to verify that the validation works: `test-fund-too-late.scen.json` .

```json,file=crowdfunding-fund-too-late.scen.json
```json title=crowdfunding-fund-too-late.scen.json
{
"name": "trying to fund one block too late",
"steps": [
Expand Down Expand Up @@ -468,7 +468,7 @@ The only new function here is `self.send().direct_egld()`, which simply forwards

If you followed all the steps presented until now, you should have ended up with a contract that looks something like:

```rust,file=final.rs
```rust title=final.rs
#![no_std]

multiversx_sc::imports!();
Expand Down
Loading