Skip to content

Commit

Permalink
Merge pull request #751 from multiversx/development
Browse files Browse the repository at this point in the history
Development
  • Loading branch information
andrei-marinica committed Nov 20, 2023
2 parents 3800953 + 4ef23be commit 17ac64a
Show file tree
Hide file tree
Showing 5 changed files with 335 additions and 9 deletions.
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.

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.
:::

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
{
"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).
40 changes: 39 additions & 1 deletion docs/developers/data/multi-values.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,42 @@ In effect, all serializable types implement the multi-value traits.

## Parsing and limitations

It is important to understand that arguments get read one by one from left to right, so there are some limitations as to how var-args can be positioned. Argument types also define how the arguments are consumed, so, for instance, if a type specifies that all remaining arguments will be consumed, it doesn't really make sense to have any other argument after that.
It is important to understand that arguments get read one by one from left to right, so there are some limitations as to how var-args can be positioned. Argument types also define how the arguments are consumed, so, for instance, if a type specifies that all remaining arguments will be consumed, it doesn't really make sense to have any other argument after that.

For instance, let's consider the behavior of `MultiValueEncoded`, which consumes all subsequent arguments. Hence, it's advisable to place it as the last argument in the function, like so:

```rust
#[endpoint(myEndpoint)]
fn my_endpoint(&self, first_arg: ManagedBuffer, second_arg: TokenIdentifier, last_arg: MultiValueEncoded<u64>)
```
Placing any argument after `MultiValueEncoded` will not initialize that argument, because `MultiValueEncoded` will consume all arguments following it. An important rule to remember is that an endpoint can have only one `MultiValueEncoded` argument, and it should always occupy the last position in order to achieve the desired outcome.

Another scenario to consider involves the use of multiple `Option` arguments. Take, for instance, the following endpoint:

```rust
#[endpoint(myOptionalEndpoint)]
fn my_optional_endpoint(&self, first_arg: OptionalValue<TokenIdentifier>, second_arg: OptionalValue<ManagedBuffer>)
```
In this context, both arguments (or none) should be provided at the same time in order to get the desired effect. Since arguments are processed sequentially from left to right, supplying a single value will automatically assign it to the first argument, making it impossible to determine which argument should receive that value.

The same rule applies when any regular argument is placed after a var-arg, thus, a strong restriction regarding arguments' order has been enforced. Regular arguments `must not` be placed after var-args.
To further enhance clarity and minimize potential errors related to var-args, starting from framework version `v0.44.0`, it is no longer allowed by default to have multiple var-args. This restriction can be lifted by using the #[allow_multiple_var_args] annotation.
:::info Note
`#[allow_multiple_var_args]` is required when using more than one var-arg in an endpoint and is placed at the endpoint level, alongside the `#[endpoint]` annotation. Utilizing `#[allow_multiple_var_args]` in any other manner will not work.
Considering this, our optional endpoint from the example before becomes:
```rust
#[allow_multiple_var_args]
#[endpoint(myOptionalEndpoint)]
fn my_optional_endpoint(&self, first_arg: OptionalValue<TokenIdentifier>, second_arg: OptionalValue<ManagedBuffer>)
```
:::
The absence of #[allow_multiple_var_args] as an endpoint attribute, along with the use of multiple var-args and/or the placement of regular arguments after var-args, leads to build failure, as the parsing validations now consider the count and positions of var-args.
However, when `#[allow_multiple_var_args]` is used, there is no other parsing validation (except the ones from above) to enforce the var-args rules mentioned before. In simpler terms, using the annotation implies that the developer is assuming responsibility for handling multiple var-args and anticipating the outcomes, effectively placing trust in their ability to manage the situation.
[comment]: # (mx-context-auto)
Expand Down Expand Up @@ -218,3 +253,6 @@ where
```

To create a custom multi-value type, one needs to manually implement these two traits for the type. Unlike for single values, there is no [equivalent derive syntax](/developers/data/custom-types).



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).

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
Loading

0 comments on commit 17ac64a

Please sign in to comment.