From 675a0d7bba2eb857fe16708ef58fb785ccc1cde2 Mon Sep 17 00:00:00 2001 From: Andrei Marinica Date: Thu, 31 Aug 2023 18:32:02 +0300 Subject: [PATCH 01/15] SC reorg & expand: data, tooling, config --- docs/developers/data/abi.md | 281 +++++++ .../code-metadata.md | 4 +- docs/developers/data/composite-values.md | 83 ++ docs/developers/data/custom-types.md | 297 ++++++++ docs/developers/data/defaults.md | 148 ++++ docs/developers/data/multi-values.md | 128 ++++ .../developers/data/serialization-overview.md | 51 ++ docs/developers/data/simple-values.md | 211 ++++++ .../developer-reference/sc-build-reference.md | 432 ----------- .../developers/developer-reference/sc-meta.md | 142 ---- .../serialization-format.md | 713 ------------------ docs/developers/meta/sc-allocator.md | 25 + docs/developers/meta/sc-build-reference.md | 187 +++++ docs/developers/meta/sc-config.md | 478 ++++++++++++ docs/developers/meta/sc-meta-cli.md | 367 +++++++++ docs/developers/meta/sc-meta.md | 77 ++ docs/developers/overview.md | 12 +- .../scenario-reference/values-complex.md | 4 +- .../scenario-reference/values-simple.md | 4 +- docs/developers/tutorials/staking-contract.md | 2 +- .../sdk-py/smart-contract-interactions.md | 2 +- docusaurus.config.js | 20 +- sidebars.js | 29 +- .../ide-build-screenshot.png | Bin static/developers/sc-meta/sc-meta-info.png | Bin 0 -> 102226 bytes 25 files changed, 2390 insertions(+), 1307 deletions(-) create mode 100644 docs/developers/data/abi.md rename docs/developers/{developer-reference => data}/code-metadata.md (98%) create mode 100644 docs/developers/data/composite-values.md create mode 100644 docs/developers/data/custom-types.md create mode 100644 docs/developers/data/defaults.md create mode 100644 docs/developers/data/multi-values.md create mode 100644 docs/developers/data/serialization-overview.md create mode 100644 docs/developers/data/simple-values.md delete mode 100644 docs/developers/developer-reference/sc-build-reference.md delete mode 100644 docs/developers/developer-reference/sc-meta.md delete mode 100644 docs/developers/developer-reference/serialization-format.md create mode 100644 docs/developers/meta/sc-allocator.md create mode 100644 docs/developers/meta/sc-build-reference.md create mode 100644 docs/developers/meta/sc-config.md create mode 100644 docs/developers/meta/sc-meta-cli.md create mode 100644 docs/developers/meta/sc-meta.md rename static/developers/{sc-build-reference => sc-meta}/ide-build-screenshot.png (100%) create mode 100644 static/developers/sc-meta/sc-meta-info.png diff --git a/docs/developers/data/abi.md b/docs/developers/data/abi.md new file mode 100644 index 000000000..9e98c2748 --- /dev/null +++ b/docs/developers/data/abi.md @@ -0,0 +1,281 @@ +--- +id: abi +title: ABI +--- + +[comment]: # (mx-abstract) + +## ABI Overview + +To interact with a smart contract it is essential to understand its inputs and outputs. This is valid both for on-chain calls, and for off-chain tools, and can in most cases also tell us a lot about what the smart contract does and how it does it. + +For this reason, blockchain smart contracts have so-called ABIs, expressed in a platform-agnostic language - JSON in our case. + +Note that the name ABI is short for _Application Binary Interface_, which is a concept borrowed from low-level and systems programming. The word _binary_ does not refer to its representation, but rather to the fact that it describes the binary encoding of inputs and outputs. + +--- + +[comment]: # (mx-context-auto) + +## Minimal example + +At its base minimum, an ABI contains: +- Some general build information, mostly used by humans, rather than tools: + - the compiler version; + - the name and version of the contract crate; + - the framework version used. +- The name of the contract crate and the Rust docs associated to it (again, mostly for documentation purposes). +- Under it, the list of all endpoints. For each endpoint we get: + - The Rust docs associated to them; + - Mutability, meaning whether or not the endpoint can modify smart contract state. At this point in the evolution of the framework, this mutability is purely cosmetic, not enforced. It can be viewed as a form of documentation, or declaration of intent. This might change, though, in the future into a hard guarantee. + - Whether the endpoint is payable. + - The list of all inputs, with their corresponding names and types. + - The list of all outputs, with their corresponding types. It is rare but possible to have more than one declared output value. It is also rare but possible to have output values named. + +```json +{ + "buildInfo": { + "rustc": { + "version": "1.71.0-nightly", + "commitHash": "a2b1646c597329d0a25efa3889b66650f65de1de", + "commitDate": "2023-05-25", + "channel": "Nightly", + "short": "rustc 1.71.0-nightly (a2b1646c5 2023-05-25)" + }, + "contractCrate": { + "name": "adder", + "version": "0.0.0", + "gitVersion": "v0.43.2-5-gfe62c37d2" + }, + "framework": { + "name": "multiversx-sc", + "version": "0.43.2" + } + }, + "docs": [ + "One of the simplest smart contracts possible,", + "it holds a single variable in storage, which anyone can increment." + ], + "name": "Adder", + "constructor": { + "inputs": [ + { + "name": "initial_value", + "type": "BigUint" + } + ], + "outputs": [] + }, + "endpoints": [ + { + "name": "getSum", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "docs": [ + "Add desired amount to the storage variable." + ], + "name": "add", + "mutability": "mutable", + "inputs": [ + { + "name": "value", + "type": "BigUint" + } + ], + "outputs": [] + } + ], + "events": [], + "hasCallback": false, + "types": {} +} +``` + +--- + +[comment]: # (mx-context-auto) + +## Data types + +Smart contract inputs and outputs almost universally use the [standard MultiversX serialization format](/developers/data/serialization-overview). + +The ABI is supposed to contain enough information, so that, knowing this standard, a developer can write an encoder or decoder for the data of a smart contract in any language. + +:::important Important +Please note that the type names are not necessarily the ones from Rust, we are trying to keep this language-agnostic to some extent. +::: + +[comment]: # (mx-context-auto) + +### Basic types + +First off, there are a number of [basic types](/developers/data/simple-values) that are known, and which have a universal representation: +- Numerical types: `BigUint`, `BigInt`, `u64`, `i32`, etc. +- Booleans: `bool`. +- Raw byte arrays are all specified as `bytes`, irrespective of the underlying implementation in the contract. Someone who just interacts with the contract does not care whether the contracts works with `ManagedBuffer`, `Vec`, or something else, it's all the same to the exterior. +- Text: `utf-8 string`. +- 32 byte account address: `Address`. +- ESDT token identifier: `TokenIdentifier`. Encoded the same as `bytes`, but with more specific semantics. + +[comment]: # (mx-context-auto) + +### Composite types + +Then, there are several standard [composite types](/developers/data/composite-values). They also have type arguments that describe the type of their content: +- Variable-length lists: `List`, where `T` can be any type; e.g. `List`. +- Fixed-length arrays: `arrayN`, where `N` is a number and `T` can be any type; e.g. `array5` represents 5 bytes. +- Heterogenous fixed-length tuples, `tuple`, no spaces, where `T1`, `T2` , ... , `TN` can be any types; e.g. `tuple`. +- Optional data, `Option`, where `T` can be any type. Represented as either nothing, or a byte of `0x01` followed by the serialized contents. + +[comment]: # (mx-context-auto) + +### Custom types: overview + +All the types until here were standard and it is expected that any project using the ABI knows about them. + +But here it gets interesting: the ABI also needs to describe types that are defined the smart contract itself. + +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: + +```json +{ + "buildInfo": {}, + "docs": [ + "Struct & Enum example" + ], + "name": "TypesExample", + "constructor": { + "inputs": [], + "outputs": [] + }, + "endpoints": [ + { + "name": "doSomething", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [ + { + "name": "s", + "type": "MyAbiStruct" + } + ], + "outputs": [ + { + "type": "MyAbiEnum" + } + ] + } + ], + "events": [], + "hasCallback": false, + "types": { + "MyAbiStruct": { + "type": "struct", + "docs": [ + "ABI example of a struct." + ], + "fields": [ + { + "docs": [ + "Fields can also have docs." + ], + "name": "field1", + "type": "BigUint" + }, + { + "name": "field2", + "type": "List>" + }, + { + "name": "field3", + "type": "tuple" + } + ] + }, + "MyAbiEnum": { + "type": "enum", + "docs": [ + "ABI example of an enum." + ], + "variants": [ + { + "name": "Nothing", + "discriminant": 0 + }, + { + "name": "Something", + "discriminant": 1, + "fields": [ + { + "name": "0", + "type": "i32" + } + ] + }, + { + "name": "SomethingMore", + "discriminant": 2, + "fields": [ + { + "name": "0", + "type": "u8" + }, + { + "name": "1", + "type": "MyAbiStruct" + } + ] + } + ] + } + } +} +``` + + +[comment]: # (mx-context-auto) + +### Custom types: struct + +ABI [structures](/developers/data/custom-types#custom-structures) are defined by: +- __Name__; +- __Docs__ (optionally); +- A list of ___fields___. Each field has: + - __Name__; + - __Docs__ (optionally); + - The __type__ of the field. Any type is allowed, so: + - simple types, + - composite types, + - other custom types, + - even the type itself (if you manage to pull that off). + +In the example above, we are declaring a structure called `MyAbiStruct`, with 3 fields, called `field1`, `field2`, and `field3`. + + +[comment]: # (mx-context-auto) + +### Custom types: enum + +Similarly, [enums](/developers/data/custom-types#custom-enums) are defined by: +- __Name__; +- __Docs__ (optionally); +- A list of ___variants___. Each variant has: + - A __name__; + - __Docs__ (optionally); + - The __discriminant__. This is the index of the variant (starts from 0). It is always serialized as the first byte. + - Optionally, __data fields__ associated with the enum. + - It is most common to have single unnamed field, which will pe named `0`. There are, however, other options. Rust syntax allows: + - Tuple varians, named `0`, `1`, `2`, etc. + - 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). diff --git a/docs/developers/developer-reference/code-metadata.md b/docs/developers/data/code-metadata.md similarity index 98% rename from docs/developers/developer-reference/code-metadata.md rename to docs/developers/data/code-metadata.md index 27f30c824..d02a10ac6 100644 --- a/docs/developers/developer-reference/code-metadata.md +++ b/docs/developers/data/code-metadata.md @@ -23,7 +23,7 @@ Once a contract is marked as _**not** upgradeable_, its code and code metadata b [comment]: # (mx-context-auto) -## Usability +## Usage When deploying (or upgrading) a smart contract using **mxpy**, its default _code metadata flags_ are: `upgradeable`, `readable` and **non-**`payable`. The default values can be overwritten by decorating the command `mxpy contract deploy` (or `mxpy contract upgrade`) as follows: - `--metadata-not-upgradeable` - mark the contract as **non-** `upgradeable` @@ -35,7 +35,7 @@ For more information, please follow [mxpy CLI](/sdk-and-tools/sdk-py/mxpy-cli). [comment]: # (mx-context-auto) -## Converting Metadata to bytes +## Bit-flag layout Internally, the metadata is stored as a 2-byte wide bit-flag. For easier visualization, let's define the flags like this: ```rust diff --git a/docs/developers/data/composite-values.md b/docs/developers/data/composite-values.md new file mode 100644 index 000000000..6c1da9b0f --- /dev/null +++ b/docs/developers/data/composite-values.md @@ -0,0 +1,83 @@ +--- +id: composite-values +title: Composite Values +--- +[comment]: # (mx-abstract) + +We often need to group simple values into more complex ones, without splitting them into [several arguments](/developers/data/multi-value). + +Here too we opted for an encoding that is, above all, very easy to read. + + +[comment]: # (mx-context-auto) + +### Lists of items + +This is an umbrella term for all types of lists or arrays of various item types. They all serialize the same way. + +**Rust types**: `&[T]`, `Vec`, `Box<[T]>`, `LinkedList`, `VecMapper`, etc. + +**Top-encoding**: All nested encodings of the items, concatenated. + +**Nested encoding**: First, the length of the list, encoded on 4 bytes (`usize`/`u32`). +Then, all nested encodings of the items, concatenated. + +**Examples** + +| Type | Value | Top-level encoding | Nested encoding | Explanation | +| ---------------- | -------------------- | --------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `Vec` | `vec![1, 2]` | `0x0102` | `0x00000002 0102` | Length = `2` | +| `Vec` | `vec![1, 2]` | `0x00010002` | `0x00000002 00010002` | Length = `2` | +| `Vec` | `vec![]` | `0x` | `0x00000000` | Length = `0` | +| `Vec` | `vec![7]` | `0x00000007` | `0x00000001 00000007` | Length = `1` | +| `Vec< Vec>` | `vec![ vec![7]]` | `0x00000001 00000007` | `0x00000001 00000001 00000007` | There is 1 element, which is a vector. In both cases the inner Vec needs to be nested-encoded in the larger Vec. | +| `Vec<&[u8]>` | `vec![ &[7u8][..]]` | `0x00000001 07` | `0x00000001 00000001 07` | Same as above, but the inner list is a simple list of bytes. | +| `Vec< BigUint>` | `vec![ 7u32.into()]` | `0x00000001 07` | `0x00000001 00000001 07` | `BigUint`s need to encode their length when nested. The `7` is encoded the same way as a list of bytes of length 1, so the same as above. | + +--- + +[comment]: # (mx-context-auto) + +### Arrays and tuples + +The only difference between these types and the lists in the previous section is that their length is known at compile time. +Therefore, there is never any need to encode their length. + +**Rust types**: `[T; N]`, `Box<[T; N]>`, `(T1, T2, ... , TN)`. + +**Top-encoding**: All nested encodings of the items, concatenated. + +**Nested encoding**: All nested encodings of the items, concatenated. + +**Examples** + +| Type | Value | Top-level encoding | Nested encoding | +| ---------------- | ------------------- | ------------------ | ------------------ | +| `[u8; 2]` | `[1, 2]` | `0x0102` | `0x0102` | +| `[u16; 2]` | `[1, 2]` | `0x00010002` | `0x00010002` | +| `(u8, u16, u32)` | `[1u8, 2u16, 3u32]` | `0x01000200000003` | `0x01000200000003` | + +--- + +[comment]: # (mx-context-auto) + +### Options + +An `Option` represents an optional value: every Option is either `Some` and contains a value, or `None`, and does not. + +**Rust types**: `Option`. + +**Top-encoding**: If `Some`, a `0x01` byte gets encoded, and after it the encoded value. If `None`, nothing gets encoded. + +**Nested encoding**: If `Some`, a `0x01` byte gets encoded, and after it the encoded value. If `None`, a `0x00` byte get encoded. + +**Examples** + +| Type | Value | Top-level encoding | Nested encoding | Explanation | +| ------------------ | ---------------------------------- | -------------------- | -------------------- | -------------------------------------------------------------------------------------------- | +| `Option` | `Some(5)` | `0x010005` | `0x010005` | | +| `Option` | `Some(0)` | `0x010000` | `0x010000` | | +| `Option` | `None` | `0x` | `0x00` | Note that `Some` has different encoding than `None` for any type | +| `Option< BigUint>` | `Some( BigUint::from( 0x1234u32))` | `0x01 00000002 1234` | `0x01 00000002 1234` | The `Some` value is nested-encoded. For a `BigUint` this adds the length, which here is `2`. | + +--- diff --git a/docs/developers/data/custom-types.md b/docs/developers/data/custom-types.md new file mode 100644 index 000000000..4c372260b --- /dev/null +++ b/docs/developers/data/custom-types.md @@ -0,0 +1,297 @@ +--- +id: custom-types +title: Custom Types +--- +[comment]: # (mx-abstract) + +We sometimes create new types that we want to serialize and deserialize directly when interacting with contracts. For `struct`s and `enum`s it is very easy to set them up, with barely any code. + + + +[comment]: # (mx-context-auto) + +### Custom structures + +Any structure defined in a contract of library can become serializable if it is annotated with either or all of: `TopEncode`, `TopDecode`, `NestedEncode`, `NestedDecode`. + +**Example implementation:** + +```rust +#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode)] +pub struct Struct { + pub int: u16, + pub seq: Vec, + pub another_byte: u8, + pub uint_32: u32, + pub uint_64: u64, +} +``` + +**Top-encoding**: All fields nested-encoded one after the other. + +**Nested encoding**: The same, all fields nested-encoded one after the other. + +**Example value** + +```rust +Struct { + int: 0x42, + seq: vec![0x1, 0x2, 0x3, 0x4, 0x5], + another_byte: 0x6, + uint_32: 0x12345, + uint_64: 0x123456789, +} +``` + +It will be encoded (both top-encoding and nested encoding) as: `0x004200000005010203040506000123450000000123456789`. + +Explanation: + +``` +[ +/* int */ 0, 0x42, +/* seq length */ 0, 0, 0, 5, +/* seq contents */ 1, 2, 3, 4, 5, +/* another_byte */ 6, +/* uint_32 */ 0x00, 0x01, 0x23, 0x45, +/* uint_64 */ 0x00, 0x00, 0x00, 0x01, 0x23, 0x45, 0x67, 0x89 +] +``` + +--- + +[comment]: # (mx-context-auto) + +### Custom enums + +Any enum defined in a contract of library can become serializable if it is annotated with either or all of: `TopEncode`, `TopDecode`, `NestedEncode`, `NestedDecode`. + +**A simple enum example:** + +_Example taken from the multiversx-sc-codec tests._ + +```rust +#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode)] +enum DayOfWeek { + Monday, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday, + Sunday, +} +``` + +**A more complex enum example:** + +```rust +#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode)] +enum EnumWithEverything { + Default, + Today(DayOfWeek), + Write(Vec, u16), + Struct { + int: u16, + seq: Vec, + another_byte: u8, + uint_32: u32, + uint_64: u64, + }, +} +``` + +**Nested encoding**: +First, the discriminant is encoded. The discriminant is the index of the variant, starting with `0`. +Then the fields in that variant (if any) get nested-encoded one after the other. + +**Top-encoding**: Same as nested-encoding, but with an additional rule: +if the discriminant is `0` (first variant) and there are no fields, nothing is encoded. + +**Example values** + +_The examples below are taken from the multiversx-sc-codec tests._ + + + +export const ExampleTable = () => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueTop-encoding bytesNested encoding bytes
+ +
 
+            DayOfWeek::Monday
+          
+
+
+ +
 
+            /* nothing */
+          
+
+
+ +
 
+            /* discriminant */ 0,
+          
+
+
+ +
 
+          DayOfWeek::Tuesday
+        
+
+
+ +
 
+            /* discriminant */ 1,
+          
+
+
+ +
 
+            EnumWithEverything::Default
+          
+
+
+ +
 
+            /* nothing */
+          
+
+
+ +
 
+            /* discriminant */ 0,
+          
+
+
+ +
 
+            EnumWithEverything::Today(
+            
DayOfWeek::Monday +
) +
+
+
+ +
 
+            
/* discriminant */ 1, +
/* DayOfWeek discriminant */ 0 +
+
+
+ +
 
+            EnumWithEverything::Today(
+            
DayOfWeek::Friday +
) +
+
+
+ +
 
+            
/* discriminant */ 1, +
/* DayOfWeek discriminant */ 4 +
+
+
+ +
 
+            EnumWithEverything::Write(
+            
Vec::new(), +
0, +
) +
+
+
+ +
 
+            
/* discriminant */ 2, +
/* vec length */ 0, 0, 0, 0, +
/* u16 */ 0, 0, +
+
+
+ +
 
+            EnumWithEverything::Write(
+            
[1, 2, 3].to_vec(), +
4 +
) +
+
+
+ +
 
+            
/* discriminant */ 2, +
/* vec length */ 0, 0, 0, 3, +
/* vec contents */ 1, 2, 3, +
/* an extra 16 */ 0, 4, +
+
+
+ +
 
+            EnumWithEverything::Struct (
+            
int: 0x42, +
seq: vec![0x1, 0x2, 0x3, 0x4, 0x5], +
another_byte: 0x6, +
uint_32: 0x12345, +
uint_64: 0x123456789, +
); +
+
+
+ +
 
+            
/* discriminant */ 3, +
/* int */ 0, 0x42, +
/* seq length */ 0, 0, 0, 5, +
/* seq contents */ 1, 2, 3, 4, 5, +
/* another_byte */ 6, +
/* uint_32 */ 0x00, 0x01, 0x23, 0x45, +
/* uint_64 */ 0x00, 0x00, 0x00, 0x01, +
0x23, 0x45, 0x67, 0x89, +
+
+
+); diff --git a/docs/developers/data/defaults.md b/docs/developers/data/defaults.md new file mode 100644 index 000000000..5b764e541 --- /dev/null +++ b/docs/developers/data/defaults.md @@ -0,0 +1,148 @@ +--- +id: defaults +title: Defaults +--- +[comment]: # (mx-abstract) + +Smart contracts occasionally need to interact with uninitialized data. Most notably, whenever a smart contract is deployed, its entire storage will be uninitialized. + +Uninitialized storage is indistinguishable from empty storage values, or storage that has been deleted. It always acts like + + +[comment]: # (mx-context-auto) + +## Built-in defaults + +The serialization format naturally supports defaults for most types. + +The default value of a type is the value that we receive when deserializing an empty buffer. The same value will serialize to an empty buffer. We like having easy access to empty serialized buffers, because that way we can easily clear storage and we minimize gas costs in transactions. + +For instance, for all numeric types, zero is the default value, because we represent it as an empty buffer. + +| Type | Default value | +| ----------------------------------------- | ------------------------------ | +| `u8` | `0` | +| `u16` | `0` | +| `u32` | `0` | +| `u64` | `0` | +| `usize` | `0` | +| `BigUnt` | `0` | +| `i8` | `0` | +| `i16` | `0` | +| `i32` | `0` | +| `i64` | `0` | +| `isize` | `0` | +| `BigInt` | `0` | +| `bool` | `false` | +| `Option` | `None` | +| `ManagedBuffer` | `ManagedBuffer::empty()` | +| `Vec` | `Vec::new()` | +| `String` | `"".to_string()` | +| `DayOfWeek` (see example above) | `DayOfWeek::Monday` | +| `EnumWithEverything` (see example above) | `EnumWithEverything::Default` | + +### Types that have no defaults + +Certain types have no values that can be represented as an empty buffer, and therefore they have no default value. + +When trying to decode any of these types from an empty buffer, we will receive a deserialization error. + +Examples: +- `(usize, usize)` always gets serialized as exactly 8 bytes, no less; +- `(usize, usize, usize)` always gets serialized as exactly 12 bytes; +- `[u8; 20]` always gets serialized as exactly 20 bytes. + +The same goes for any custom `struct`, its representation is the concatenation of the nested encoding of its components, which is fixed size. + +In some cases a custom `enum` faces the same problem. If the first variant has no additional data, the default is simply the first variant. We saw two examples above: +- `DayOfWeek` is a simple enum top to bottom, so `DayOfWeek::Monday` is naturally its default; +- `EnumWithEverything` has data in some of the variants, but not in the first, so in a similar manner, `EnumWithEverything::Default` works as its default. + +However, if we were to define the enum: +```rust +#[derive(TopEncode, TopDecode)] +enum Either { + Something(u32), + SomethingElse(u64), +} +``` +... there is no way to find a natural default value for it. Both variants are represented as non-empty buffers. + +If you need the default, one workaround is to place these structures inside an `Option`. Options always have the default `None`, no matter the contents. + +There is, however, another way to do it: for custom structures it is possible to define custom defaults, as we will see in the next section. + +## Custom defaults + +A structure that does not have a natural default value can receive one via custom code. First of all this applies to structures, but it can also be useful for some enums. + +To do so, instead of deriving `TopEncode` and `TopDecode`, we will derive `TopEncodeOrDefault` and `TopDecodeOrDefault`, respectively. + +We need to also specify what we want that default value to be, both when encoding and decoding. For this, we need to explicitly implement traits `EncodeDefault` and `DecodeDefault` for our structure. + +Let's look at an example: + +```rust +#[derive(TopEncodeOrDefault, TopDecodeOrDefault)] +pub struct StructWithDefault { + pub first_field: u16, + pub seq: Vec, + pub another_byte: u8, + pub uint_32: u32, + pub uint_64: u64, +} + +impl EncodeDefault for StructWithDefault { + fn is_default(&self) -> bool { + self.first_field == 5 + } +} + +impl DecodeDefault for StructWithDefault { + fn default() -> Self { + StructWithDefault { + first_field: 5, + seq: vec![], + another_byte: 0, + uint_32: 0, + uint_64: 0, + } + } +} +``` + +We just specified the following: +- `is_default`:whenever the `first_field` field is equal to 5, the other fields don't matter anymore and we save the structure as an empty buffer; +- `default`: whenever we try to decode an empty buffer, we yield a structure that has the `first_field` set to 5, and all the other fields empty or zero. + +It should always be the case that `::is_default(::default())` is true. The framework does not enforce this in any way, but it should be common sense. + +Other than that, there are no constraints on what the default value should be. + +We can do the same for an enum: + +```rust +#[derive(TopEncodeOrDefault, TopDecodeOrDefault)] +enum Either { + Something(u32), + SomethingElse(u64), +} + +impl EncodeDefault for Either { + fn is_default(&self) -> bool { + matches!(*self, Either::Something(3)) + } +} + +impl DecodeDefault for Either { + fn default() -> Self { + Either::Something(3) + } +} +``` + +We just specified the following: +- `is_default`: whenever we have variant `Either::Something` _and_ the value contained is 3, encode as empty buffer; +- `default`: whenever we try to decode an empty buffer, we yield `Either::Something(3)`. + +The same here, `::is_default(::default())` should be true. No other constraints over what the default value should be, of which variant. `Either::SomethingElse(0)` could also have been chosen to be the default. diff --git a/docs/developers/data/multi-values.md b/docs/developers/data/multi-values.md new file mode 100644 index 000000000..d25f9adc9 --- /dev/null +++ b/docs/developers/data/multi-values.md @@ -0,0 +1,128 @@ +--- +id: multi-values +title: Multi-Values +--- +[comment]: # (mx-abstract) + +## Single values vs. multi-values + +To recap, we have discussed about data being represented either in a: +- nested encoding, as part of the byte representation of a larger object; +- top encoding, the full byte represention of an object. + +But even the top encoding only refers to a _single_ object, being represented as a _single_ array of bytes. This encoding, no matter how simple of complex, is the representation for a _single_ argument, result, log topic, log event, NFT attribute, etc. + +However, we sometimes want to work with _multiple_, variadic arguments, or an arbitrary number of results. An elegant solution is modelling them as special collections of top-encodable objects that each represent an individual item. For instance, a we could have a list of separate arguments, of arbitrary length. + +Multi-values work similarly to varargs in other languages, such as C, where you can write `void f(int arg, ...) { ... }`. In the smart contract framework they do not need specialized syntax, we use the type system to define theit behavior. + +:::info Note +In the framework, single values are treated as a special case of multi-value, one that consumes exactly one argument, or returns exactly one value. + +In effect, all serializable types implement the multi-value traits. +::: + +[comment]: # (mx-context-auto) + +## 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. + + +[comment]: # (mx-context-auto) + +## Standard multi-values + +These are the common multi-values provided by the framework: +- Straightforward var-args, an arbitrary number of arguments of the same type. + - Can be defined in code as: + - `MultiValueVec` - the unmanaged version. To be used outside of contracts. + - `MultiValueManagedVec` - the equivalent managed version. The only limitation is that `T` must implement `ManagedVecItem`, because we are working with an underlying `ManagedVec`. Values are deserialized eagerly, the endpoint receives them already prepared. + - `MultiValueEncoded` - the lazy version of the above. Arguments are only deserialized when iterating over this structure. This means that `T` does not need to implement `ManagedVecItem`, since it never gets stored in a `ManagedVec`. + - In all these 3 cases, `T` can be any serializable type, either single or multi-value. + - Such a var-arg will always consume all the remaining arguments, so it doesn't make sense to place any other arguments after it, single or multi-value. The framework doesn't forbid it, but single values will crash at runtime, since they always need a value, and multi-values will always be empty. +- Multi-value tuples. + - Defined as `MultiValueN`, where N is a number between 2 and 16, and `T1`, `T2`, ..., `TN` can be any serializable types, either single or multi-value; e.g. `MultiValue3`. + - It doesn't make much sense to use them as arguments on their own (it is easier and equivalent to just have separate named arguments), but they do have the following uses: + - They can be embedded in a regular var-arg to obtain groups of arguments. For example `MultiValueVec>` defines pairs of numbers. There is no more need to check in code that an even number of arguments was passed, the deserializer resolves this on its own. + - Rust does not allow returing more than one result, but by returning a multi-value tuple we can have an endpoint return several values, of different types. +- Optional arguments. + - Defined as `OptionalValue`, where `T` can be any serializable type, either single or multi-value. + - At most one argument will be consumed. For this reason it sometimes makes sense to have several optional arguments one after the other, or optional arguments followed by var-args. + - Do note, however, that an optional argument cannot be missing if there is anything else coming after it. For example if an endpoint has arguments `a: OptionalValue, b: OptionalValue`, `b` can be missing, or both can be missing, but there is no way to have `a` missing and `b` to be there, because passing any argument will automatically get it assigned to `a`. +- Counted var-args. + - Suppose we actually do want two sets of var-args in an endpoint. One solution would be to explicitly state how many arguments each of them contain (or at least the first one). Counted var-args make this simple. + - Defined as `MultiValueManagedVecCounted`, where `T` can be any serializable type, either single or multi-value. + - Always takes a number argument first, which represents how many arguments follow. Then it consumes exactly that many arguments. + - Can be followed by other arguments, single or multi-value. +- Async call result. + - Asynchronous call callbacks also need to know whether or not the call failed, so they have a special format for trasmitting arguments. The first argument is always the error code. If the error code is `0`, then the call result follows. Otherwise, we get one additional argument, which is the error message. To easily deserialize this, we use a special type. + - Defined as `ManagedAsyncCallResult`. + - There is also an unmanaged version, `AsyncCallResult`, but it is no longer used nowadays. + - They are both enums, the managed part only refers to the error message. +- Ignored arguments. + - Sometimes, for backwards compatibility or other reasons it can happen to have (optional) arguments that are never used and not of interest. To avoid any useless deserialization, it is possible to define an argument of type `IgnoreValue` at the end. + - By doing so, any number of arguments are allowed at the end, all of which will be completely ignored. + + +So, to recap: + +| Managed Version | Unmanaged version | What it represents | Similar single value | +| ---------------------------------- | -------------------- | ------------------------------- | ------------------------------ | +| `MultiValueN` | | A fixed number of arguments | Tuple `(T1, T2, ..., TN)` | +| `OptionalValue` | | An optional argument | `Option` | +| `MultiValueEncoded` | `MultiValueVec` | Variable number of arguments | `Vec` | +| `MultiValueManagedVec` | `MultiValueVec` | Variable number of arguments | `Vec` | +| `MultiValueManagedVecCounted` | | Counted number of arguments | `(usize, Vec)` | +| `ManagedAsyncCallResult` | `AsyncCallResult` | Async call result in callback | `Result` | +| `IgnoreValue` | | Any number of ignored arguments | Unit `()` | + + + +[comment]: # (mx-context-auto) + +## Storage mapper contents as multi-values + +The storage mapper declaration is a method that can normally also be made into a public view endpoint. If so, when calling them, the entire contents of the mapper will be read from storage and serialized as multi-value. Only recommended when there is little data, or in tests. + +These storage mappers are, in no particular order: +- BiDiMapper +- LinkedListMapper +- MapMapper +- QueueMapper +- SetMapper +- SingleValueMapper +- UniqueIdMapper +- UnorderedSetMapper +- UserMapper +- VecMapper +- FungibleTokenMapper +- NonFungibleTokenMapper + +[comment]: # (mx-context-auto) + +## Implementation details + +All serializable types will implement traits `TopEncodeMulti` and `TopDecodeMulti`. + +The components that do argument parsing, returning results, or handling of event logs all work with these two traits. + +All serializable types (the ones that implement `TopEncode`) are explicitly decalred to also be multi-value in this declaration: + +```rust +/// All single top encode types also work as multi-value encode types. +impl TopEncodeMulti for T +where + T: TopEncode, +{ + fn multi_encode_or_handle_err(&self, output: &mut O, h: H) -> Result<(), H::HandledErr> + where + O: TopEncodeMultiOutput, + H: EncodeErrorHandler, + { + output.push_single_value(self, h) + } +} +``` + +To create a custom multi-value type, one needs to implement these two traits for the type, by hand. Unlike for single values, there is no [equivalent derive syntax](/developers/data/custom-types). diff --git a/docs/developers/data/serialization-overview.md b/docs/developers/data/serialization-overview.md new file mode 100644 index 000000000..576852280 --- /dev/null +++ b/docs/developers/data/serialization-overview.md @@ -0,0 +1,51 @@ +--- +id: serialization-overview +title: The MultiversX Serialization Format +--- +[comment]: # (mx-abstract) + +In MultiversX, there is a specific serialization format for all data that interacts with a smart contract. The serialization format is central to any project because all values entering and exiting a contract are represented as byte arrays that need to be interpreted according to a consistent specification. + +In Rust, the **multiversx-sc-codec** crate ([crate](https://crates.io/crates/multiversx-sc-codec), [docs](https://docs.rs/multiversx-sc-codec/latest/multiversx_sc_codec/)) exclusively deals with this format. Both Go and Rust implementations of scenarios have a component that serializes to this format. DApp developers need to be aware of this format when interacting with the smart contract on the backend. + +[comment]: # (mx-context-auto) + +## Rationale + +We want the format to be somewhat readable and to interact with the rest of the blockchain ecosystem as easily as possible. This is why we have chosen **big endian representation for all numeric types.** + +More importantly, the format needs to be as compact as possible, since each additional byte costs additional fees. + +[comment]: # (mx-context-auto) + +## The concept of top-level vs. nested objects + +There is a perk that is central to the formatter: we know the size of the byte arrays entering the contract. All arguments have a known size in bytes, and we normally learn the length of storage values before loading the value itself into the contract. This gives us some additional data straight away that allows us to encode less. + +Imagine that we have an argument of type int32. During a smart contract call we want to transmit the value "5" to it. A standard deserializer might expect us to send the full 4 bytes `0x00000005`, but there is clearly no need for the leading zeroes. It's a single argument, and we know where to stop, there is no risk of reading too much. So sending `0x05` is enough. We saved 3 bytes. Here we say that the integer is represented in its **top-level form**, it exists on its own and can be represented more compactly. + +But now imagine that an argument that deserializes as a vector of int32. The numbers are serialized one after the other. We no longer have the possibility of having variable length integers because we won't know where one number begins and one ends. Should we interpret `0x0101` as`[1, 1]` or `[257]`? So the solution is to always represent each integer in its full 4-byte form. `[1, 1]` is thus represented as `0x0000000100000001` and`[257]`as `0x00000101`, there is no more ambiguity. The integers here are said to be in their **nested form**. This means that because they are part of a larger structure, the length of their representation must be apparent from the encoding. + +But what about the vector itself? Its representation must always be a multiple of 4 bytes in length, so from the representation we can always deduce the length of the vector by dividing the number of bytes by 4. If the encoded byte length is not divisible by 4, this is a deserialization error. Because the vector is top-level we don't have to worry about encoding its length, but if the vector itself gets embedded into an even larger structure, this can be a problem. If, for instance, the argument is a vector of vectors of int32, each nested vector also needs to have its length encoded before its data. + +[comment]: # (mx-context-auto) + +## A note about the value zero + +We are used to writing the number zero as "0" or "0x00", but if we think about it, we don't need 1 byte for representing it, 0 bytes or an "empty byte array" represent the number 0 just as well. In fact, just like in `0x0005`, the leading 0 byte is superfluous, so is the byte `0x00` just like an unnecessary leading 0. + +With this being said, the format always encodes zeroes of any type as empty byte arrays. + +[comment]: # (mx-context-auto) + +## How each type gets serialized + +This guide is split into several sections: +- [Simple values, such as numbers, strings, etc.](/developers/data/simple-values) +- [Lists, tuples, Option](/developers/data/composite-values) +- [Custom types defined in the contracts.](/developers/data/custom-types) +- [Var-args and other multi-values](/developers/data/multi-values) +- [The code metadata flag](/developers/data/code-metadata) + +There is a special section about [unitialized data and how defaults relate to serialization](/developers/data/defaults). + diff --git a/docs/developers/data/simple-values.md b/docs/developers/data/simple-values.md new file mode 100644 index 000000000..230922794 --- /dev/null +++ b/docs/developers/data/simple-values.md @@ -0,0 +1,211 @@ +--- +id: simple-values +title: Simple Values +--- +[comment]: # (mx-abstract) + +We will start by going through the basic types used in smart contracts: +- Fixed-width numbers +- Arbitrary width (big) numbers +- Boolean values + + +[comment]: # (mx-context-auto) + +### Fixed-width numbers + +Small numbers can be stored in variables of up to 64 bits. We use big endian encoding for all numbers in our projects. + +**Rust types**: `u8`, `u16`, `u32`, `usize`, `u64`, `i8`, `i16`, `i32`, `isize`, `i64`. + +**Top-encoding**: The same as for all numerical types, the minimum number of bytes that +can fit their 2's complement, big endian representation. + +**Nested encoding**: Fixed width big endian encoding of the type, using 2's complement. + +:::important +A note about the types `usize` and `isize`: these Rust-specific types have the width of the underlying architecture, +i.e. 32 on 32-bit systems and 64 on 64-bit systems. However, smart contracts always run on a wasm32 architecture, so +these types will always be identical to `u32` and `i32` respectively. +Even when simulating smart contract execution on 64-bit systems, they must still be serialized on 32 bits. +::: + +**Examples** + +| Type | Number | Top-level encoding | Nested encoding | +| ------- | --------------------- | -------------------- | -------------------- | +| `u8` | `0` | `0x` | `0x00` | +| `u8` | `1` | `0x01` | `0x01` | +| `u8` | `0x11` | `0x11` | `0x11` | +| `u8` | `255` | `0xFF` | `0xFF` | +| `u16` | `0` | `0x` | `0x0000` | +| `u16` | `0x11` | `0x11` | `0x0011` | +| `u16` | `0x1122` | `0x1122` | `0x1122` | +| `u32` | `0` | `0x` | `0x00000000` | +| `u32` | `0x11` | `0x11` | `0x00000011` | +| `u32` | `0x1122` | `0x1122` | `0x00001122` | +| `u32` | `0x112233` | `0x112233` | `0x00112233` | +| `u32` | `0x11223344` | `0x11223344` | `0x11223344` | +| `u64` | `0` | `0x` | `0x0000000000000000` | +| `u64` | `0x11` | `0x11` | `0x0000000000000011` | +| `u64` | `0x1122` | `0x1122` | `0x0000000000001122` | +| `u64` | `0x112233` | `0x112233` | `0x0000000000112233` | +| `u64` | `0x11223344` | `0x11223344` | `0x0000000011223344` | +| `u64` | `0x1122334455` | `0x1122334455` | `0x0000001122334455` | +| `u64` | `0x112233445566` | `0x112233445566` | `0x0000112233445566` | +| `u64` | `0x11223344556677` | `0x11223344556677` | `0x0011223344556677` | +| `u64` | `0x1122334455667788` | `0x1122334455667788` | `0x1122334455667788` | +| `usize` | `0` | `0x` | `0x00000000` | +| `usize` | `0x11` | `0x11` | `0x00000011` | +| `usize` | `0x1122` | `0x1122` | `0x00001122` | +| `usize` | `0x112233` | `0x112233` | `0x00112233` | +| `usize` | `0x11223344` | `0x11223344` | `0x11223344` | +| `i8` | `0` | `0x` | `0x00` | +| `i8` | `1` | `0x01` | `0x01` | +| `i8` | `-1` | `0xFF` | `0xFF` | +| `i8` | `127` | `0x7F` | `0x7F` | +| `i8` | `-0x11` | `0xEF` | `0xEF` | +| `i8` | `-128` | `0x80` | `0x80` | +| `i16` | `-1` | `0xFF` | `0xFFFF` | +| `i16` | `-0x11` | `0xEF` | `0xFFEF` | +| `i16` | `-0x1122` | `0xEEDE` | `0xEEDE` | +| `i32` | `-1` | `0xFF` | `0xFFFFFFFF` | +| `i32` | `-0x11` | `0xEF` | `0xFFFFFFEF` | +| `i32` | `-0x1122` | `0xEEDE` | `0xFFFFEEDE` | +| `i32` | `-0x112233` | `0xEEDDCD` | `0xFFEEDDCD` | +| `i32` | `-0x11223344` | `0xEEDDCCBC` | `0xEEDDCCBC` | +| `i64` | `-1` | `0xFF` | `0xFFFFFFFFFFFFFFFF` | +| `i64` | `-0x11` | `0xEF` | `0xFFFFFFFFFFFFFFEF` | +| `i64` | `-0x1122` | `0xEEDE` | `0xFFFFFFFFFFFFEEDE` | +| `i64` | `-0x112233` | `0xEEDDCD` | `0xFFFFFFFFFFEEDDCD` | +| `i64` | `-0x11223344` | `0xEEDDCCBC` | `0xFFFFFFFFEEDDCCBC` | +| `i64` | `-0x1122334455` | `0xEEDDCCBBAB` | `0xFFFFFFEEDDCCBBAB` | +| `i64` | `-0x112233445566` | `0xEEDDCCBBAA9A` | `0xFFFFEEDDCCBBAA9A` | +| `i64` | `-0x11223344556677` | `0xEEDDCCBBAA9989` | `0xFFEEDDCCBBAA9989` | +| `i64` | `-0x1122334455667788` | `0xEEDDCCBBAA998878` | `0xEEDDCCBBAA998878` | +| `isize` | `0` | `0x` | `0x00000000` | +| `isize` | `-1` | `0xFF` | `0xFFFFFFFF` | +| `isize` | `-0x11` | `0xEF` | `0xFFFFFFEF` | +| `isize` | `-0x1122` | `0xEEDE` | `0xFFFFEEDE` | +| `isize` | `-0x112233` | `0xEEDDCD` | `0xFFEEDDCD` | +| `isize` | `-0x11223344` | `0xEEDDCCBC` | `0xEEDDCCBC` | + +--- + +[comment]: # (mx-context-auto) + +### Arbitrary width (big) numbers + +For most smart contracts applications, number larger than the maximum uint64 value are needed. +EGLD balances for instance are represented as fixed-point decimal numbers with 18 decimals. +This means that to represent even just 100 EGLD we use the number 100*1018, which already exceeds the capacity of a regular 64-bit integer. + +**Rust types**: `BigUint`, `BigInt`, + +:::important +These types are managed by MultiversX VM, in many cases the contract never sees the data, only a handle. +This is to reduce the burden on the smart contract. +::: + +**Top-encoding**: The same as for all numerical types, the minimum number of bytes that +can fit their 2's complement, big endian representation. + +**Nested encoding**: Since these types are variable length, we need to encode their length, so that the decodes knows when to stop decoding. +The length of the encoded number always comes first, on 4 bytes (`usize`/`u32`). +Next we encode: + +- For `BigUint` the big endian bytes +- For `BigInt` the shortest 2's complement number that can unambiguously represent the number. Positive numbers must always have the most significant bit `0`, while the negative ones `1`. See examples below. + +**Examples** + +| Type | Number | Top-level encoding | Nested encoding | Explanation | +| --------- | ------ | ------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------ | +| `BigUint` | `0` | `0x` | `0x00000000` | The length of `0` is considered `0`. | +| `BigUint` | `1` | `0x01` | `0x0000000101` | `1` can be represented on 1 byte, so the length is 1. | +| `BigUint` | `256` | `0x0100` | `0x000000020100` | `256` is the smallest number that takes 2 bytes. | +| `BigInt` | `0` | `0x` | `0x00000000` | Signed `0` is also represented as zero-length bytes. | +| `BigInt` | `1` | `0x01` | `0x0000000101` | Signed `1` is also represented as 1 byte. | +| `BigInt` | `-1` | `0xFF` | `0x00000001FF` | The shortest 2's complement representation of `-1` is `FF`. The most significant bit is 1. | +| `BigUint` | `127` | `0x7F` | `0x000000017F` | | +| `BigInt` | `127` | `0x7F` | `0x000000017F` | | +| `BigUint` | `128` | `0x80` | `0x0000000180` | | +| `BigInt` | `128` | `0x0080` | `0x000000020080` | The most significant bit of this number is 1, so to avoid ambiguity an extra `0` byte needs to be prepended. | +| `BigInt` | `255` | `0x00FF` | `0x0000000200FF` | Same as above. | +| `BigInt` | `256` | `0x0100` | `0x000000020100` | `256` requires 2 bytes to represent, of which the MSB is 0, no more need to prepend a `0` byte. | + +--- + +[comment]: # (mx-context-auto) + +### Boolean values + +Booleans are serialized the same as a byte (`u8`) that can take values `1` or `0`. + +**Rust type**: `bool` + +**Values** + +| Type | Value | Top-level encoding | Nested encoding | +| ------ | ------- | ------------------ | --------------- | +| `bool` | `true` | `0x01` | `0x01` | +| `bool` | `false` | `0x` | `0x00` | + +--- + +[comment]: # (mx-context-auto) + +### Byte slices and ASCII strings + +Byte slices are technically a special case of the [list types](developers/data/composite-values#lists-of-items), but they are usually thought of as basic types. Their encoding is, in any case, consistent with the rules for lists of "byte items". + +:::important +Strings are treated from the point of view of serialization as series of bytes. Using Unicode strings, while often a good practice in programming, tends to add unnecessary overhead to smart contracts. The difference is that Unicode strings get validated on input and concatenation. + +We consider best practice to use Unicode on the frontend, but keep all messages and error messages in ASCII format on smart contract level. +::: + +**Rust types**: `ManagedBuffer`, `BoxedBytes`, `&[u8]`, `Vec`, `String`, `&str`. + +**Top-encoding**: The byte slice, as-is. + +**Nested encoding**: The length of the byte slice on 4 bytes, followed by the byte slice as-is. + +**Examples** + +| Type | Value | Top-level encoding | Nested encoding | Explanation | +| --------------- | --------------------------- | ------------------ | ------------------ | ----------------------------------------------------------------- | +| `&'static [u8]` | `b"abc"` | `0x616263` | `0x00000003616263` | ASCII strings are regular byte slices of buffers. | +| `ManagedBuffer` | `ManagedBuffer::from("abc")`| `0x616263` | `0x00000003616263` | Use `Vec` for a buffer that can grow. | +| `BoxedBytes` | `BoxedBytes::from( b"abc")` | `0x616263` | `0x00000003616263` | BoxedBytes are just optimized owned byte slices that cannot grow. | +| `Vec` | `b"abc".to_vec()` | `0x616263` | `0x00000003616263` | Use `Vec` for a buffer that can grow. | +| `&'static str` | `"abc"` | `0x616263` | `0x00000003616263` | Unicode string (slice). | +| `String` | `"abc".to_string()` | `0x616263` | `0x00000003616263` | Unicode string (owned). | + + +:::info Note +Inside contracts, `ManagedBuffer` is [the only recommended type for generic bytes](developers/best-practices/the-dynamic-allocation-problem). +::: + +--- + +[comment]: # (mx-context-auto) + +### Address + +MultiversX addresses are 32 byte arrays, so they get serialized as such, both in top and nested encodings. + +--- + +[comment]: # (mx-context-auto) + +### Token identifiers + +MultiversX ESDT token identifiers are of the form `XXXXXX-123456`, where the first part is the token ticker, 3 to 20 characters in length, and the last is a random generated number. + +They are top-encoded as is, the exact bytes and nothing else. + +Because of their variable length, they need to be serialized like variable length byte slices when nested, so the length is explicitly encoded at the start. + +--- + diff --git a/docs/developers/developer-reference/sc-build-reference.md b/docs/developers/developer-reference/sc-build-reference.md deleted file mode 100644 index 0844ea2df..000000000 --- a/docs/developers/developer-reference/sc-build-reference.md +++ /dev/null @@ -1,432 +0,0 @@ ---- -id: sc-build-reference -title: Smart Contract Build Reference ---- - -[comment]: # (mx-abstract) - -## How to: Basic build - -To build a contract, it is enough to navigate in your contract crate and run - -```sh -mxpy contract build -``` - -Alternatively you can go to your installed `MultiversX Workspace Explorer` VS Code extension and right click your Smart Contract followed by `Build Contract` - -![build contract screenshot](/developers/sc-build-reference/ide-build-screenshot.png "Build Contract from the MultiversX Workspace Explorer extension") - -[comment]: # (mx-exclude-context) - -## How to: Multi contract build - -[comment]: # (mx-context-auto) - -### Rationale - -Starting with version `0.37`, it is possible to configure a so-called "multi contract build". - -The idea is to produce several smart contract binaries from the same smart contract project. These "output" contracts may or may not share most of their endpoints and logic, but they always originate from the same code base. Think of them as flavors of the same contract. - -The main rationale of this system (for now at least) are the "external view" contracts. It is very common for contracts to have certain endpoints that are very useful for grabbing data off-chain, but are very rarely used on-chain, if ever. Their code is basically bloating the main contract, and the idea is to extract them into a separate contract. This second contract (called an "external view contract") works because contracts can read from the storage of other contracts directly. - -The framework does the storage access rerouting automatically behind the scenes. The contract code cannot even tell the difference between a regular view from the same contract and one that has been relegated to an external view. Even more so, the same view endpoint can function both as external view and as regular view in different configurations/output contracts. - -It is possible that this component becomes a building block of a more advanced versioning system, but we have not experimented with that yet. - -[comment]: # (mx-context-auto) - -### Configuration example - -To get a multi-contract build, it is enough to add a `multicontract.toml` file to the smart contract root. The build is triggered the same way as for basic builds. - -The `multicontract.toml` file contains data on what the output contracts are, and what endpoints go in which. - -We will use the multisig contract as an example. In this contracts we have several endpoints that are never used on-chain: `getPendingActionFullInfo`, `userRole`, `getAllBoardMembers`, `getAllProposers`, `getActionData`, `getActionSigners`, `getActionSignerCount`, `getActionValidSignerCount`. We want to place these contracts in an external view contract. - -In order to make configuration easier, we label them in code, as can be seen in the excerpt below: - -```rust -#[multiversx_sc::module] -pub trait MultisigStateModule { - // ... - - /// Serialized action data of an action with index. - #[label("multisig-external-view")] - #[view(getActionData)] - fn get_action_data(&self, action_id: usize) -> Action { - // ... - } - - /// Gets addresses of all users who signed an action. - #[label("multisig-external-view")] - #[view(getActionSigners)] - fn get_action_signers(&self, action_id: usize) -> ManagedVec { - // ... - } - - // ... -} - -``` - -Labels don't do anything more than provide a handy way to refer to groups of endpoints in the `multicontract.toml`. - -Now for the `multicontract.toml` config itself, with explanations in comments: - -```toml -[settings] -# one of the output contracts is considered "multisig-main" -# it can have any id -main = "multisig-main" - -# contracts are identified by a contract id -# this id is only relevant in this file and in test setup -[contracts.multisig-main] -# the contract name is the important one, -# the output will be .wasm/.abi.json -name = "multisig" -# we can choose to add all unlabelled endpoints to a contract -add-unlabelled = true - -# this is the external view contract, here we call it "view" -[contracts.view] -# the output will be multisig-view.wasm/multisig-view.abi.json -name = "multisig-view" -# this is how we signal that this contract will be built as an external view -external-view = true -# we only add the endpoints labelled "multisig-external-view", as in the code snippet above -# any number of labels can be added -add-labels = ["multisig-external-view"] - -# this is how you get a version of the contract with all endpoints -# (main and external view, as defined above), -[contracts.full] -name = "multisig-full" -add-unlabelled = true -add-labels = ["multisig-external-view"] -``` - -[comment]: # (mx-context-auto) - -### The external view contract - -An _external view_ contract has a behavior different from that of a regular contract. The framework adds some logic to such a contract, which is invisible to the developer. There are two main points: - -1. Storage access is different. All storage reads are done from the target contract given in the constructor. -2. The constructor is different. Be mindful of this when deploying the external view contract. - - The original constructor is ignored, [a specific constructor](https://docs.rs/multiversx-sc/0.39.0/multiversx_sc/external_view_contract/fn.external_view_contract_constructor.html) is always provided instead. - - This constructor always takes one single argument, which is the address of the target contract to read from. From this on, the target address can no longer be changed. - - The external view constructor ABI is always as follows: - -```json -{ - "constructor": { - "docs": [ - "The external view init prepares a contract that looks in another contract's storage.", - "It takes a single argument, the other contract's address", - "You won't find this constructors' definition in the contract, it gets injected automatically by the framework. See `multiversx_sc::external_view_contract`." - ], - "inputs": [ - { - "name": "target_contract_address", - "type": "Address" - } - ], - "outputs": [] - } -} -``` - -[comment]: # (mx-context-auto) - -### Testing with multi-contracts - -It is possible (and recommended) to use the contracts in scenario tests as they would be used on-chain. - -The Go scenario runner will work with the produced contract binaries without further ado. Calling an endpoint that is not available in a certain output contract will fail, even if said endpoint exists in the original contract code. - -To achieve the same effect on the Rust scenario runner, configure as in the following snippet. This is an actual excerpt from `multisig_scenario_rs_test.rs`, one of the multisig test files. - -```rust -fn world() -> ScenarioWorld { - // Initialize the blockchain mock, the same as for a regular test. - let mut blockchain = ScenarioWorld::new(); - blockchain.set_current_dir_from_workspace("contracts/examples/multisig"); - - // Contracts that have no multi-contract config are provided the same as before. - blockchain.register_contract("file:test-contracts/adder.wasm", adder::ContractBuilder); - - // For multi-contract outputs we need to provide: - // - the ABI, via the generated AbiProvider type - // - a scenario expression to bind to, same as for simple contracts - // - a contract builder, same as for simple contracts - // - the contract name, as specified in multicontract.toml - blockchain.register_partial_contract::( - "file:output/multisig.wasm", - multisig::ContractBuilder, - "multisig", - ); - - // The same goes for the external view contract. - // There is no need to specify here that it is an external view, - // the framework gets all the data from multicontract.toml. - blockchain.register_partial_contract::( - "file:output/multisig-view.wasm", - multisig::ContractBuilder, - "multisig-view", - ); - - blockchain -} -``` - -[comment]: # (mx-context-auto) - -### The `multicontract.toml` specification - -- `settings` - - `main` - The contract id of the main wasm crate. The only thing special about this contract's crate is that it is simply called `wasm` and that its `Cargo.toml` is the basis for the `Cargo.toml` configs in all other output contract wasm crates. -- `contracts` map, indexed by contract id. Each contract has: - - `name` (optional) - The output contract name. If missing, the contract id will be used. - - `external-view` - Specifies that a contract should be built as an external view contract. False if unspecified. - - `add-unlabelled` - Specifies that all unlabelled endpoints should be added to this contract. False if unspecified. - - `add-labels` - A list of labels. All endpoints labelled with at least one of these labels will be added to the contract. - - `add-endpoints` - A list of endpoint names to be added directly to this contract. It bypasses the label system. -- `labels-for-contracts` - It is also possible to map in reverse, labels to contracts. It contains a mapping from labels to lists of contract ids. It can be a little harder to read than the contract to label map, but it can be used. It - -[comment]: # (mx-exclude-context) - -## CLI specification - -[comment]: # (mx-context-auto) - -### Calling `build` - -A build can be triggered by calling either `mxpy contract build ` or `cargo run build` in the meta crate. In fact, mxpy calls the meta crate itself. - -By default, this command will produce three files for each output contract: the ABI (`.abi.json`), the contract (`.wasm`) and a json file with all the used VM EI imported functions (`.imports.json`). For the multisig example above, the produced files are as follows: - -```text -output -├── multisig-full.abi.json -├── multisig-full.imports.json -├── multisig-full.wasm -├── multisig-view.abi.json -├── multisig-view.imports.json -├── multisig-view.wasm -├── multisig.abi.json -├── multisig.imports.json -└── multisig.wasm -``` - -Several arguments can be added to the `build` command, both in mxpy and directly: - -- `--locked` Uses the version from `Cargo.lock`, without updating. Required for reproducible builds. -- `--wasm-name` followed by name: Replaces the main contract's name with this one. Does nothing for secondary contracts. -- `--wasm-suffix` followed by a suffix: Adds a dash and this suffix to all produced contracts. E.g. `cargo run build --wasm-suffix dbg` on multisig will produce contracts `multisig-dbg.wasm`, `multisig-view-dbg.wasm` and `multisig-full-dbg.wasm`. -- `--wasm-symbols` Does not optimize away symbols at compile time, retains function names, good for investigating the WAT. -- `--no-wasm-opt` Does not apply `wasm-opt` after the build, this retains function names, good for investigating the WAT. -- `--wat` Also generates a WAT file for each of the contract outputs. It does so by calling `wasm2wat`. -- `--mir` Also emit MIR files when building. -- `--no-abi-git-version` Skips loading the Git version into the ABI. -- `--no-imports` Does not generate an EI imports JSON file for each contract, as is the default. -- `--target-dir` 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. -- `--twiggy-top` Generate a twiggy top report after building. -- `--twiggy-paths` Generate a twiggy paths report after building. -- `--twiggy-monos` Generate a twiggy monos report after building. -- `--twiggy-dominators` Generate a twiggy dominators report after building. - -[comment]: # (mx-context-auto) - -### Calling `build-dbg` - -There is another command, provided for convenience: `cargo run build-dbg`. Calling this is equivalent to `cargo run build --wasm-symbols --no-wasm-opt --wasm-suffix "dbg" --wat --no-imports`. It is ideal for developers who want to investigate the WebAssembly output produced by the compiler. - -The output for `build-dbg` in the multisig example would be: - -```text -output -├── multisig.abi.json -├── multisig-dbg.wasm -├── multisig-dbg.wat -├── multisig-full.abi.json -├── multisig-full-dbg.wasm -├── multisig-full-dbg.wat -├── multisig-view.abi.json -├── multisig-view-dbg.wasm -└── multisig-view-dbg.wat -``` - -It accepts all the arguments from `build`, so `--target-dir` works here too. - -[comment]: # (mx-context-auto) - -### Calling `twiggy` - -This command is similar to `build-dbg`, in that it provides a shorthand for building contracts and analyzing their size. It is equivalent to running `cargo run build-dbg --twiggy-top --twiggy-paths --twiggy-monos --twiggy-dominators`. - -[comment]: # (mx-context-auto) - -### Calling `clean` - -Calling `mxpy contract clean ` or `cargo run clean` in the meta crate will delete the `output` folder and clean outputs of the Rust crates. - -[comment]: # (mx-context-auto) - -### Calling `snippets` - -Calling `cargo run snippets` in the meta crate will create a project called `interact-rs` in the contract main directory, containing auto-generated boilerplate code for building an interactor for the current contract. - -An interactor is a small tool, meant for developers to interact with the contract on-chain. Being written in Rust, it is ideal for quick interactions and tinkering, directly from the contract project. There will be more documentation in the works on this topic. - -[comment]: # (mx-context-auto) - -## Contract build process deep dive - -This section provides an overview for those who want to understand the system on a deeper level. If you are simply looking to build some contracts, feel free to skip this. - -Building a contract is a complex process, but luckily it gets handled invisibly by the framework. We will follow the components step by step and give some justification for this architecture. - -[comment]: # (mx-context-auto) - -### a. The smart contract itself - -The smart contract is defined as a trait without an implementation. This is good, because it means the contract can be executed on various platforms. Some of the implementation gets generated automatically by the `[multiversx_sc::contract]` macro. - -Not everything, though, can be performed here. Notably, macros cannot access data from other modules or crates, all processing is local to the current contract or module. Therefore, we need another mechanism for working with the complete contract data. - -[comment]: # (mx-context-auto) - -### b. The (generated) ABI generator - -ABIs are a collection of metatada about the contract. To build an ABI, we also need the data from the modules. The module macros cannot be called from the contract macros (macros are run at compilation, we are not even sure that modules will need to be recompiled!). Modules, however can be called. That is why we are actually generating _ABI generator functions_ for each module, which can call one another to retrieve the composite picture. - -Note: The ABI generator comes as an implementation of trait [ContractAbiProvider](https://docs.rs/multiversx-sc/0.39.0/multiversx_sc/contract_base/trait.ContractAbiProvider.html). - -[comment]: # (mx-context-auto) - -### c. Meta crate: generating the ABI - -The next question is how will this function be called. Whenever we compile the WASM contracts, we also produce the ABIs in JSON format. Rust has something called build scripts, which get called _after_ compiling a project, but for reasons that will become apparent later, they are not powerful enough for our usecase. - -Therefore, we have decided to include an extra crate in each smart contract project. It is always found in the `meta` folder in a contract, and it handles the entire build. To minimize boilerplate, it always only contains one line, that simply defers execution to the framework: - -```rust -fn main() { - multiversx_sc_meta::cli_main::(); -} -``` - -To produce the ABI, in fact, it is enough to call: - -```sh -cd meta -cargo run abi -``` - -The meta crate has access to the ABI generator, because it always has a dependency to the contract crate. This is the `my_contract_crate::AbiProvider` in the example above. - -This is also the step where the meta crate parses and processes the `multicontract.toml` file. If there are multiple outputs, one ABI will be produced for each. - -[comment]: # (mx-context-auto) - -### d. Meta crate: generating `wasm` crate code - -Each contract must contain at least one `wasm` crate. This is separate from the contract crate because it has a different purpose: it only needs to be the basis for compiling wasm. Please take it as an intermediary step between the contract logic and the Rust to WASM compiler. This is also where the WASM compilation options are specified (e.g. the optimization level). These options can be seen in the `Cargo.toml` file of the `wasm` crate. - -The separation is helpful because, in this way, the smart contract crate can act as a pure Rust crate with no knowledge of WebAssembly. This makes testing and coverage easy, as well as enabling integration with other unrelated technologies. - -The `wasm` crates do not add any meaningful code to the smart contract. Everything they need to do is to provide an adapter to the WASM function syntax. More specifically, they expose an external function for each desired endpoint, which forwards execution to the corresponding smart contract method. - -If we are not careful, we risk adding unwanted endpoints to the contract. A classic example is when we have a crate with multiple modules, of which only one is imported into the smart contract. In some older versions, you might have gotten unwanted endpoints from the other modules of that crate. To avoid this, we are using the ABI to generate a curated list of endpoints in each `wasm` crate. This way, our contracts always have the exact same endpoints as the ones specified in the ABIs. - -This requires code generation. The `meta` crate will handle this code generation too. An example of such generated code lies below: - -```rust -// Code generated by the multiversx-sc multi-contract system. DO NOT EDIT. - -//////////////////////////////////////////////////// -////////////////// AUTO-GENERATED ////////////////// -//////////////////////////////////////////////////// - -// Init: 1 -// Endpoints: 2 -// Async Callback (empty): 1 -// Total number of exported functions: 4 - -#![no_std] -#![feature(alloc_error_handler, lang_items)] - -multiversx_sc_wasm_adapter::allocator!(); -multiversx_sc_wasm_adapter::panic_handler!(); - -multiversx_sc_wasm_adapter::endpoints! { - adder - ( - getSum - add - ) -} - -multiversx_sc_wasm_adapter::empty_callback! {} -``` - -The `multiversx_sc_wasm_adapter` macros help keep even this generated code to a minimum. - -For multi-contract builds, one `wasm` crate needs to be generated for each of the output contracts: - -- The main wasm crate must always reside in the `wasm` folder. The source file is auto-generated, but the `Cargo.toml` must be provided by the developer. -- The other wasm contracts (called "secondary") receive a crate folder starting with `wasm-`, e.g. `wasm-multisig-view`. These crates are fully generated based on data from `multicontract.toml`. The respective `Cargo.toml` files are based on the `Cargo.toml` of the main wasm crate. All configs are taken from there, except for the crate name. -- Warning: Any folders starting with `wasm-` that are unaccounted for will be removed without prompt. This is to keep the folder structure clean in case of renames. - -[comment]: # (mx-context-auto) - -### e. Meta crate: the actual WASM build - -The previous two steps happen by just calling `cargo run` in the meta crate, but to perform a build, one must call `cargo run build`. - -With the ABI information and the code generated, the meta crate can now build all the WASM contracts, one for each output contract. - -The rust compiler places the result in the designated `target` folder, but for convenience, the meta crate moves the executables to the project `output` folder and renames them according to the configured names. - -You might have performed this step automatically from mxpy, but mxpy simply calls the meta crate to do this job. This is because at this point only the meta crate has access to the ABIs and can do it easily. - -[comment]: # (mx-context-auto) - -### f. Meta crate: build post-processing - -After building the contracts, there are three more operations left to perform, based on the compiled WebAssembly outputs: - -1. All contracts are optimized, using `wasm-opt`. This operation can be disabled (via `--no-wasm-opt`). -2. A WAT file id generated for each contract. Not enabled by default, can be enabled (via `--wat`). The framework simply calls the `wasm2wat` tool to do this. -3. An `.imports.json` file is generated for each contract. Can be disabled (via `--no-imports`). The framework uses the `wasm-objdump` tool to retrieve the imports. It parses the output and saves it as JSON. - -[comment]: # (mx-context-auto) - -### g. Cleaning a project - -Calling `cargo run clean` in the meta crate will run `cargo clean` in all wasm crates and delete the `output` folder. - -`mxpy contract clean` also just forwards to this. - -Note that even the clean operation relies on the ABI, in order to reach all the wasm crates. - -[comment]: # (mx-context-auto) - -### Build process summary - -To recap, the build process steps are as follows: - -1. Generate code using the contract/module macros, including the ABI generator; -2. Call the ABI generator, to produce an ABI in memory; -3. Parse the `multicontract.toml` file (if present); -4. Deciding based on that which endpoints go to which output contracts; -5. Save the ABI as JSON in the output folder (one for each output contract); -6. Generate the wasm crates for all output contracts (all sources generated, Cargo.toml contents copied from the main wasm crate); -7. Build all wasm crates; -8. Copy binaries from the `target` folder(s) to `output`. -9. Perform post-processing for each contract: `wasm-opt`, `wasm2wat`, imports; - -Luckily, the framework can do all of this automatically, with a single click. diff --git a/docs/developers/developer-reference/sc-meta.md b/docs/developers/developer-reference/sc-meta.md deleted file mode 100644 index 96891c049..000000000 --- a/docs/developers/developer-reference/sc-meta.md +++ /dev/null @@ -1,142 +0,0 @@ ---- -id: sc-meta -title: Smart Contract Developer Tooling ---- - -[comment]: # (mx-abstract) - -## Introduction - -We have developed a universal smart contract management tool, called `multiversx-sc-meta` (`sc-meta` in short). - -It is called that, because it provides a layer of meta-programming over the regular smart contract development. It can read and interact with some of the code written by developers. - -You can find it on [crates.io](https://crates.io/crates/multiversx-sc-meta) [![crates.io](https://img.shields.io/crates/v/multiversx-sc-meta.svg?style=flat)](https://crates.io/crates/multiversx-sc-meta) - -To install it, simply call - -``` -cargo install multiversx-sc-meta -``` - -After that, try calling `sc-meta help` or `sc-meta -h` to see the CLI docs. - -[comment]: # (mx-context-auto) - -## Standalone tool vs. contract tool - -The unusual thing about this tool is that it comes in two flavors. One of them is the standalone tool, installed as above. The other is a tool that gets provided specifically for every contract, and which helps with building. - -The contract tool lies in the `meta` folder under each contract. It just contains these 3 lines of code: - -```rust -fn main() { - multiversx_sc_meta::cli_main::(); -} -``` - -... but they are important, because they link the contract tool to the contract code, via the ABI. - -The contract tool is required in order to build contracts, because it is the only tool that we have that calls the ABI generator, manages the wasm crate and the multi-contract config, and has the data on how to build the contract. - -Therefore, all the functionality that needs the ABI goes into the contract tool, whereas the rest in the standalone tool. - -To see the contract meta CLI docs, `cd` into the `/meta` crate and call `cargo run help` or `cargo run -- -h`. - -[comment]: # (mx-context-auto) - -## Contract functionality - -Currently the contract functionality is: - - `abi` Generates the contract ABI and nothing else. - - `build` Builds contract(s) for deploy on the blockchain. - - `build-dbg` Builds contract(s) with symbols and WAT. - - `twiggy` Builds contract(s) and generate twiggy reports. - - `clean` Clean the Rust project and the output folder. - - `update` Update the Cargo.lock files in all wasm crates. - - `snippets` Generates a snippets project, based on the contract ABI. - -To learn more about building contracts, see the [the build reference](/developers/developer-reference/sc-build-reference). - -The `snippets` documentation is still under construction, it will follow soon. - -[comment]: # (mx-context-auto) - -## Standalone functionality - -The standalone functionality is: - - `info` General info about the contract an libraries residing in the targetted directory. - - `all` Calls the meta crates for all contracts under given path with the given arguments. - - `upgrade` Upgrades a contract to the latest version. Multiple contract crates are allowed. - - `local-deps` Generates a report on the local depedencies of contract crates. Will explore indirect depdencies too. - -All the standalone tools take an optional `--path` argument. if not provided, it will be the current directory. - -[comment]: # (mx-context-auto) - -### The `all` command - -There is a special feature that needs additional explanation: the `sc-meta all ...` command. - -It is the bridge between the standalone tool and the contract tools. What the `all` comman does is simply to find all contracts in a given folder and call the contract tool for each of them with the given arguments. - -For example: -- `sc-meta all abi` will generate the ABIs for all contracts; -- `sc-meta all build --locked` will build all contracts, with the crate versions given by Cargo.lock; -- `sc-meta all clean` will clean all projects; -- etc. - -You can even call `sc-meta all help` and see that the CLI docs are almost the same as those of the individual contract tool. - -A related command is the `info` command, which just prints a tree with all the contract and contract libraries in a folder, without doing anything to them. - -[comment]: # (mx-context-auto) - -### The contract upgrade tool - -Calling `sc-meta upgrade` will try to automatically upgrade a contract or group of contracts to the latest version. - -The oldest version currently supported is `0.28.0`. Any older than that, and the developer will need to upgrade it by hand to `0.28.0`. - -It is especially important when upgrading from `0.38` to `0.39.0`, since a lot of changes happened at that point. - -:::tip -For projects with multiple contract crates, we recommend upgrading all of them at once. The upgrade algorithm goes step by step, version after version. For some of the major versions, it also checks that the project compiles before moving on. This is to give developers the chance to fix issues manually, if necessary, and not have those issues pile up. If there are local depdencies between contracts, the upgrader will not be able to do the check unless all of them are upgraded together. -::: - -[comment]: # (mx-context-auto) - -### Local depdendencies - -Calling `sc-meta local-deps` will create in each contract a report of the local dependencies between contracts and libraries. This helps with the reproducible builds, but might be extended in the future for other uses. - -Example output (abridged): -```json -{ - "root": "/home/user/multiversx/mx-exchange-sc/", - "contractPath": "/home/user/multiversx/mx-exchange-sc/dex/pair", - "commonDependencyPath": "/home/user/multiversx/mx-exchange-sc", - "dependencies": [ - { - "path": "common/common_errors", - "depth": 1 - }, - { - "path": "common/common_structs", - "depth": 1 - }, - { - "path": "common/modules/legacy_token_decode_module", - "depth": 3 - }, - { - "path": "common/modules/locking_module", - "depth": 2 - }, - { - "path": "common/modules/math", - "depth": 2 - } - ] -} -``` diff --git a/docs/developers/developer-reference/serialization-format.md b/docs/developers/developer-reference/serialization-format.md deleted file mode 100644 index 81866af14..000000000 --- a/docs/developers/developer-reference/serialization-format.md +++ /dev/null @@ -1,713 +0,0 @@ ---- -id: serialization-format -title: The MultiversX Serialization Format ---- -[comment]: # (mx-abstract) - -In MultiversX, there is a specific serialization format for all data that interacts with a smart contract. The serialization format is central to any project because all values entering and exiting a contract are represented as byte arrays that need to be interpreted according to a consistent specification. - -In Rust, the **multiversx-sc-codec** crate ([crate](https://crates.io/crates/multiversx-sc-codec), [doc](https://docs.rs/multiversx-sc-codec/0.17.0/multiversx_sc_codec/)) exclusively deals with this format. Both Go and Rust implementations of scenarios have a component that serializes to this format. DApp developers need to be aware of this format when interacting with the smart contract on the backend. - -[comment]: # (mx-context-auto) - -## Rationale - -We want the format to be somewhat readable and to interact with the rest of the blockchain ecosystem as easily as possible. This is why we have chosen **big endian representation for all numeric types.** - -More importantly, the format needs to be as compact as possible, since each additional byte costs additional fees. - -[comment]: # (mx-context-auto) - -## The concept of top-level vs. nested objects - -There is a perk that is central to the formatter: we know the size of the byte arrays entering the contract. All arguments have a known size in bytes, and we normally learn the length of storage values before loading the value itself into the contract. This gives us some additional data straight away that allows us to encode less. - -Imagine that we have an argument of type int32. During a smart contract call we want to transmit the value "5" to it. A standard deserializer might expect us to send the full 4 bytes `0x00000005`, but there is clearly no need for the leading zeroes. It's a single argument, and we know where to stop, there is no risk of reading too much. So sending `0x05` is enough. We saved 3 bytes. Here we say that the integer is represented in its **top-level form**, it exists on its own and can be represented more compactly. - -But now imagine that an argument that deserializes as a vector of int32. The numbers are serialized one after the other. We no longer have the possibility of having variable length integers because we won't know where one number begins and one ends. Should we interpret `0x0101` as`[1, 1]` or `[257]`? So the solution is to always represent each integer in its full 4-byte form. `[1, 1]` is thus represented as `0x0000000100000001` and`[257]`as `0x00000101`, there is no more ambiguity. The integers here are said to be in their **nested form**. This means that because they are part of a larger structure, the length of their representation must be apparent from the encoding. - -But what about the vector itself? Its representation must always be a multiple of 4 bytes in length, so from the representation we can always deduce the length of the vector by dividing the number of bytes by 4. If the encoded byte length is not divisible by 4, this is a deserialization error. Because the vector is top-level we don't have to worry about encoding its length, but if the vector itself gets embedded into an even larger structure, this can be a problem. If, for instance, the argument is a vector of vectors of int32, each nested vector also needs to have its length encoded before its data. - -[comment]: # (mx-context-auto) - -## A note about the value zero - -We are used to writing the number zero as "0" or "0x00", but if we think about it, we don't need 1 byte for representing it, 0 bytes or an "empty byte array" represent the number 0 just as well. In fact, just like in `0x0005`, the leading 0 byte is superfluous, so is the byte `0x00` just like an unnecessary leading 0. - -With this being said, the format always encodes zeroes of any type as empty byte arrays. - -[comment]: # (mx-context-auto) - -## How each type gets serialized - -[comment]: # (mx-context-auto) - -### Fixed-width numbers - -Small numbers can be stored in variables of up to 64 bits. - -**Rust types**: `u8`, `u16`, `u32`, `usize`, `u64`, `i8`, `i16`, `i32`, `isize`, `i64`. - -**Top-encoding**: The same as for all numerical types, the minimum number of bytes that -can fit their 2's complement, big endian representation. - -**Nested encoding**: Fixed width big endian encoding of the type, using 2's complement. - -:::important -A note about the types `usize` and `isize`: these Rust-specific types have the width of the underlying architecture, -i.e. 32 on 32-bit systems and 64 on 64-bit systems. However, smart contracts always run on a wasm32 architecture, so -these types will always be identical to `u32` and `i32` respectively. -Even when simulating smart contract execution on 64-bit systems, they must still be serialized on 32 bits. -::: - -**Examples** - -| Type | Number | Top-level encoding | Nested encoding | -| ------- | --------------------- | -------------------- | -------------------- | -| `u8` | `0` | `0x` | `0x00` | -| `u8` | `1` | `0x01` | `0x01` | -| `u8` | `0x11` | `0x11` | `0x11` | -| `u8` | `255` | `0xFF` | `0xFF` | -| `u16` | `0` | `0x` | `0x0000` | -| `u16` | `0x11` | `0x11` | `0x0011` | -| `u16` | `0x1122` | `0x1122` | `0x1122` | -| `u32` | `0` | `0x` | `0x00000000` | -| `u32` | `0x11` | `0x11` | `0x00000011` | -| `u32` | `0x1122` | `0x1122` | `0x00001122` | -| `u32` | `0x112233` | `0x112233` | `0x00112233` | -| `u32` | `0x11223344` | `0x11223344` | `0x11223344` | -| `u64` | `0` | `0x` | `0x0000000000000000` | -| `u64` | `0x11` | `0x11` | `0x0000000000000011` | -| `u64` | `0x1122` | `0x1122` | `0x0000000000001122` | -| `u64` | `0x112233` | `0x112233` | `0x0000000000112233` | -| `u64` | `0x11223344` | `0x11223344` | `0x0000000011223344` | -| `u64` | `0x1122334455` | `0x1122334455` | `0x0000001122334455` | -| `u64` | `0x112233445566` | `0x112233445566` | `0x0000112233445566` | -| `u64` | `0x11223344556677` | `0x11223344556677` | `0x0011223344556677` | -| `u64` | `0x1122334455667788` | `0x1122334455667788` | `0x1122334455667788` | -| `usize` | `0` | `0x` | `0x00000000` | -| `usize` | `0x11` | `0x11` | `0x00000011` | -| `usize` | `0x1122` | `0x1122` | `0x00001122` | -| `usize` | `0x112233` | `0x112233` | `0x00112233` | -| `usize` | `0x11223344` | `0x11223344` | `0x11223344` | -| `i8` | `0` | `0x` | `0x00` | -| `i8` | `1` | `0x01` | `0x01` | -| `i8` | `-1` | `0xFF` | `0xFF` | -| `i8` | `127` | `0x7F` | `0x7F` | -| `i8` | `-0x11` | `0xEF` | `0xEF` | -| `i8` | `-128` | `0x80` | `0x80` | -| `i16` | `-1` | `0xFF` | `0xFFFF` | -| `i16` | `-0x11` | `0xEF` | `0xFFEF` | -| `i16` | `-0x1122` | `0xEEDE` | `0xEEDE` | -| `i32` | `-1` | `0xFF` | `0xFFFFFFFF` | -| `i32` | `-0x11` | `0xEF` | `0xFFFFFFEF` | -| `i32` | `-0x1122` | `0xEEDE` | `0xFFFFEEDE` | -| `i32` | `-0x112233` | `0xEEDDCD` | `0xFFEEDDCD` | -| `i32` | `-0x11223344` | `0xEEDDCCBC` | `0xEEDDCCBC` | -| `i64` | `-1` | `0xFF` | `0xFFFFFFFFFFFFFFFF` | -| `i64` | `-0x11` | `0xEF` | `0xFFFFFFFFFFFFFFEF` | -| `i64` | `-0x1122` | `0xEEDE` | `0xFFFFFFFFFFFFEEDE` | -| `i64` | `-0x112233` | `0xEEDDCD` | `0xFFFFFFFFFFEEDDCD` | -| `i64` | `-0x11223344` | `0xEEDDCCBC` | `0xFFFFFFFFEEDDCCBC` | -| `i64` | `-0x1122334455` | `0xEEDDCCBBAB` | `0xFFFFFFEEDDCCBBAB` | -| `i64` | `-0x112233445566` | `0xEEDDCCBBAA9A` | `0xFFFFEEDDCCBBAA9A` | -| `i64` | `-0x11223344556677` | `0xEEDDCCBBAA9989` | `0xFFEEDDCCBBAA9989` | -| `i64` | `-0x1122334455667788` | `0xEEDDCCBBAA998878` | `0xEEDDCCBBAA998878` | -| `isize` | `0` | `0x` | `0x00000000` | -| `isize` | `-1` | `0xFF` | `0xFFFFFFFF` | -| `isize` | `-0x11` | `0xEF` | `0xFFFFFFEF` | -| `isize` | `-0x1122` | `0xEEDE` | `0xFFFFEEDE` | -| `isize` | `-0x112233` | `0xEEDDCD` | `0xFFEEDDCD` | -| `isize` | `-0x11223344` | `0xEEDDCCBC` | `0xEEDDCCBC` | - ---- - -[comment]: # (mx-context-auto) - -### Arbitrary width (big) numbers - -For most smart contracts applications, number larger than the maximum uint64 value are needed. -EGLD balances for instance are represented as fixed-point decimal numbers with 18 decimals. -This means that to represent even just 100 EGLD we use the number 100*1018, which already exceeds the capacity of a regular 64-bit integer. - -**Rust types**: `BigUint`, `BigInt`, - -:::important -These types are managed by MultiversX VM, in many cases the contract never sees the data, only a handle. -This is to reduce the burden on the smart contract. -::: - -**Top-encoding**: The same as for all numerical types, the minimum number of bytes that -can fit their 2's complement, big endian representation. - -**Nested encoding**: Since these types are variable length, we need to encode their length, so that the decodes knows when to stop decoding. -The length of the encoded number always comes first, on 4 bytes (`usize`/`u32`). -Next we encode: - -- For `BigUint` the big endian bytes -- For `BigInt` the shortest 2's complement number that can unambiguously represent the number. Positive numbers must always have the most significant bit `0`, while the negative ones `1`. See examples below. - -**Examples** - -| Type | Number | Top-level encoding | Nested encoding | Explanation | -| --------- | ------ | ------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------ | -| `BigUint` | `0` | `0x` | `0x00000000` | The length of `0` is considered `0`. | -| `BigUint` | `1` | `0x01` | `0x0000000101` | `1` can be represented on 1 byte, so the length is 1. | -| `BigUint` | `256` | `0x0100` | `0x000000020100` | `256` is the smallest number that takes 2 bytes. | -| `BigInt` | `0` | `0x` | `0x00000000` | Signed `0` is also represented as zero-length bytes. | -| `BigInt` | `1` | `0x01` | `0x0000000101` | Signed `1` is also represented as 1 byte. | -| `BigInt` | `-1` | `0xFF` | `0x00000001FF` | The shortest 2's complement representation of `-1` is `FF`. The most significant bit is 1. | -| `BigUint` | `127` | `0x7F` | `0x000000017F` | | -| `BigInt` | `127` | `0x7F` | `0x000000017F` | | -| `BigUint` | `128` | `0x80` | `0x0000000180` | | -| `BigInt` | `128` | `0x0080` | `0x000000020080` | The most significant bit of this number is 1, so to avoid ambiguity an extra `0` byte needs to be prepended. | -| `BigInt` | `255` | `0x00FF` | `0x0000000200FF` | Same as above. | -| `BigInt` | `256` | `0x0100` | `0x000000020100` | `256` requires 2 bytes to represent, of which the MSB is 0, no more need to prepend a `0` byte. | - ---- - -[comment]: # (mx-context-auto) - -### Boolean values - -Booleans are serialized the same as a byte (`u8`) that can take values `1` or `0`. - -**Rust type**: `bool` - -**Values** - -| Type | Value | Top-level encoding | Nested encoding | -| ------ | ------- | ------------------ | --------------- | -| `bool` | `true` | `0x01` | `0x01` | -| `bool` | `false` | `0x` | `0x00` | - ---- - -[comment]: # (mx-context-auto) - -### Lists of items - -This is an umbrella term for all types of lists or arrays of various item types. They all serialize the same way. - -**Rust types**: `&[T]`, `Vec`, `Box<[T]>`, `LinkedList`, `VecMapper`, etc. - -**Top-encoding**: All nested encodings of the items, concatenated. - -**Nested encoding**: First, the length of the list, encoded on 4 bytes (`usize`/`u32`). -Then, all nested encodings of the items, concatenated. - -**Examples** - -| Type | Value | Top-level encoding | Nested encoding | Explanation | -| ---------------- | -------------------- | --------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | -| `Vec` | `vec![1, 2]` | `0x0102` | `0x00000002 0102` | Length = `2` | -| `Vec` | `vec![1, 2]` | `0x00010002` | `0x00000002 00010002` | Length = `2` | -| `Vec` | `vec![]` | `0x` | `0x00000000` | Length = `0` | -| `Vec` | `vec![7]` | `0x00000007` | `0x00000001 00000007` | Length = `1` | -| `Vec< Vec>` | `vec![ vec![7]]` | `0x00000001 00000007` | `0x00000001 00000001 00000007` | There is 1 element, which is a vector. In both cases the inner Vec needs to be nested-encoded in the larger Vec. | -| `Vec<&[u8]>` | `vec![ &[7u8][..]]` | `0x00000001 07` | `0x00000001 00000001 07` | Same as above, but the inner list is a simple list of bytes. | -| `Vec< BigUint>` | `vec![ 7u32.into()]` | `0x00000001 07` | `0x00000001 00000001 07` | `BigUint`s need to encode their length when nested. The `7` is encoded the same way as a list of bytes of length 1, so the same as above. | - ---- - -[comment]: # (mx-context-auto) - -### Arrays and tuples - -The only difference between these types and the lists in the previous section is that their length is known at compile time. -Therefore, there is never any need to encode their length. - -**Rust types**: `[T; N]`, `Box<[T; N]>`, `(T1, T2, ... , TN)`. - -**Top-encoding**: All nested encodings of the items, concatenated. - -**Nested encoding**: All nested encodings of the items, concatenated. - -**Examples** - -| Type | Value | Top-level encoding | Nested encoding | -| ---------------- | ------------------- | ------------------ | ------------------ | -| `[u8; 2]` | `[1, 2]` | `0x0102` | `0x0102` | -| `[u16; 2]` | `[1, 2]` | `0x00010002` | `0x00010002` | -| `(u8, u16, u32)` | `[1u8, 2u16, 3u32]` | `0x01000200000003` | `0x01000200000003` | - ---- - -[comment]: # (mx-context-auto) - -### Byte slices and ASCII strings - -A special case of the list types, they behave according to the same rules as lists of items. - -:::important -Strings are treated from the point of view of serialization as series of bytes. Using Unicode strings, while often a good practice in programming, tends to add unnecessary overhead to smart contracts. The difference is that Unicode strings get validated on input and concatenation. - -We consider best practice to use Unicode on the frontend, but keep all messages and error messages in ASCII format on smart contract level. -::: - -**Rust types**: `BoxedBytes`, `&[u8]`, `Vec`, `String`, `&str`. - -**Top-encoding**: The byte slice, as-is. - -**Nested encoding**: The length of the byte slice on 4 bytes, followed by the byte slice as-is. - -**Examples** - -| Type | Value | Top-level encoding | Nested encoding | Explanation | -| --------------- | --------------------------- | ------------------ | ------------------ | ----------------------------------------------------------------- | -| `&'static [u8]` | `b"abc"` | `0x616263` | `0x00000003616263` | ASCII strings are regular byte slices of buffers. | -| `BoxedBytes` | `BoxedBytes::from( b"abc")` | `0x616263` | `0x00000003616263` | BoxedBytes are just optimized owned byte slices that cannot grow. | -| `Vec` | `b"abc".to_vec()` | `0x616263` | `0x00000003616263` | Use `Vec` for a buffer that can grow. | -| `&'static str` | `"abc"` | `0x616263` | `0x00000003616263` | Unicode string (slice). | -| `String` | `"abc".to_string()` | `0x616263` | `0x00000003616263` | Unicode string (owned). | - ---- - -[comment]: # (mx-context-auto) - -### Options - -An `Option` represents an optional value: every Option is either `Some` and contains a value, or `None`, and does not. - -**Rust types**: `Option`. - -**Top-encoding**: If `Some`, a `0x01` byte gets encoded, and after it the encoded value. If `None`, nothing gets encoded. - -**Nested encoding**: If `Some`, a `0x01` byte gets encoded, and after it the encoded value. If `None`, a `0x00` byte get encoded. - -**Examples** - -| Type | Value | Top-level encoding | Nested encoding | Explanation | -| ------------------ | ---------------------------------- | -------------------- | -------------------- | -------------------------------------------------------------------------------------------- | -| `Option` | `Some(5)` | `0x010005` | `0x010005` | | -| `Option` | `Some(0)` | `0x010000` | `0x010000` | | -| `Option` | `None` | `0x` | `0x00` | Note that `Some` has different encoding than `None` for any type | -| `Option< BigUint>` | `Some( BigUint::from( 0x1234u32))` | `0x01 00000002 1234` | `0x01 00000002 1234` | The `Some` value is nested-encoded. For a `BigUint` this adds the length, which here is `2`. | - ---- - -[comment]: # (mx-context-auto) - -### Custom structures - -Any structure defined in a contract of library can become serializable if it is annotated with either or all of: `TopEncode`, `TopDecode`, `NestedEncode`, `NestedDecode`. - -**Example implementation:** - -```rust -#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode)] -pub struct Struct { - pub int: u16, - pub seq: Vec, - pub another_byte: u8, - pub uint_32: u32, - pub uint_64: u64, -} -``` - -**Top-encoding**: All fields nested-encoded one after the other. - -**Nested encoding**: The same, all fields nested-encoded one after the other. - -**Example value** - -```rust -Struct { - int: 0x42, - seq: vec![0x1, 0x2, 0x3, 0x4, 0x5], - another_byte: 0x6, - uint_32: 0x12345, - uint_64: 0x123456789, -} -``` - -It will be encoded (both top-encoding and nested encoding) as: `0x004200000005010203040506000123450000000123456789`. - -Explanation: - -``` -[ -/* int */ 0, 0x42, -/* seq length */ 0, 0, 0, 5, -/* seq contents */ 1, 2, 3, 4, 5, -/* another_byte */ 6, -/* uint_32 */ 0x00, 0x01, 0x23, 0x45, -/* uint_64 */ 0x00, 0x00, 0x00, 0x01, 0x23, 0x45, 0x67, 0x89 -] -``` - ---- - -[comment]: # (mx-context-auto) - -### Custom enums - -Any enum defined in a contract of library can become serializable if it is annotated with either or all of: `TopEncode`, `TopDecode`, `NestedEncode`, `NestedDecode`. - -**A simple enum example:** - -_Example taken from the multiversx-sc-codec tests._ - -```rust -#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode)] -enum DayOfWeek { - Monday, - Tuesday, - Wednesday, - Thursday, - Friday, - Saturday, - Sunday, -} -``` - -**A more complex enum example:** - -```rust -#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode)] -enum EnumWithEverything { - Default, - Today(DayOfWeek), - Write(Vec, u16), - Struct { - int: u16, - seq: Vec, - another_byte: u8, - uint_32: u32, - uint_64: u64, - }, -} -``` - -**Nested encoding**: -First, the discriminant is encoded. The discriminant is the index of the variant, starting with `0`. -Then the fields in that variant (if any) get nested-encoded one after the other. - -**Top-encoding**: Same as nested-encoding, but with an additional rule: -if the discriminant is `0` (first variant) and there are no fields, nothing is encoded. - -**Example values** - -_The examples below are taken from the multiversx-sc-codec tests._ - - - -export const ExampleTable = () => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ValueTop-encoding bytesNested encoding bytes
- -
 
-            DayOfWeek::Monday
-          
-
-
- -
 
-            /* nothing */
-          
-
-
- -
 
-            /* discriminant */ 0,
-          
-
-
- -
 
-          DayOfWeek::Tuesday
-        
-
-
- -
 
-            /* discriminant */ 1,
-          
-
-
- -
 
-            EnumWithEverything::Default
-          
-
-
- -
 
-            /* nothing */
-          
-
-
- -
 
-            /* discriminant */ 0,
-          
-
-
- -
 
-            EnumWithEverything::Today(
-            
DayOfWeek::Monday -
) -
-
-
- -
 
-            
/* discriminant */ 1, -
/* DayOfWeek discriminant */ 0 -
-
-
- -
 
-            EnumWithEverything::Today(
-            
DayOfWeek::Friday -
) -
-
-
- -
 
-            
/* discriminant */ 1, -
/* DayOfWeek discriminant */ 4 -
-
-
- -
 
-            EnumWithEverything::Write(
-            
Vec::new(), -
0, -
) -
-
-
- -
 
-            
/* discriminant */ 2, -
/* vec length */ 0, 0, 0, 0, -
/* u16 */ 0, 0, -
-
-
- -
 
-            EnumWithEverything::Write(
-            
[1, 2, 3].to_vec(), -
4 -
) -
-
-
- -
 
-            
/* discriminant */ 2, -
/* vec length */ 0, 0, 0, 3, -
/* vec contents */ 1, 2, 3, -
/* an extra 16 */ 0, 4, -
-
-
- -
 
-            EnumWithEverything::Struct (
-            
int: 0x42, -
seq: vec![0x1, 0x2, 0x3, 0x4, 0x5], -
another_byte: 0x6, -
uint_32: 0x12345, -
uint_64: 0x123456789, -
); -
-
-
- -
 
-            
/* discriminant */ 3, -
/* int */ 0, 0x42, -
/* seq length */ 0, 0, 0, 5, -
/* seq contents */ 1, 2, 3, 4, 5, -
/* another_byte */ 6, -
/* uint_32 */ 0x00, 0x01, 0x23, 0x45, -
/* uint_64 */ 0x00, 0x00, 0x00, 0x01, -
0x23, 0x45, 0x67, 0x89, -
-
-
-); - -## Defaults - -### Built-in defaults - -The serialization format naturally supports defaults for most types. - -The default value of a type is the value that we receive when deserializing an empty buffer. The same value will serialize to an empty buffer. We like having easy access to empty serialized buffers, because that way we can easily clear storage and we minimize gas costs in transactions. - -For instance, for all numeric types, zero is the default value, because we represent it as an empty buffer. - -| Type | Default value | -| ----------------------------------------- | ------------------------------ | -| `u8` | `0` | -| `u16` | `0` | -| `u32` | `0` | -| `u64` | `0` | -| `usize` | `0` | -| `BigUnt` | `0` | -| `i8` | `0` | -| `i16` | `0` | -| `i32` | `0` | -| `i64` | `0` | -| `isize` | `0` | -| `BigInt` | `0` | -| `bool` | `false` | -| `Option` | `None` | -| `ManagedBuffer` | `ManagedBuffer::empty()` | -| `Vec` | `Vec::new()` | -| `String` | `"".to_string()` | -| `DayOfWeek` (see example above) | `DayOfWeek::Monday` | -| `EnumWithEverything` (see example above) | `EnumWithEverything::Default` | - -### Types that have no defaults - -Certain types have no values that can be represented as an empty buffer, and therefore they have no default value. - -When trying to decode any of these types from an empty buffer, we will receive a deserialization error. - -Examples: -- `(usize, usize)` always gets serialized as exactly 8 bytes, no less; -- `(usize, usize, usize)` always gets serialized as exactly 12 bytes; -- `[u8; 20]` always gets serialized as exactly 20 bytes. - -The same goes for any custom `struct`, its representation is the concatenation of the nested encoding of its components, which is fixed size. - -In some cases a custom `enum` faces the same problem. If the first variant has no additional data, the default is simply the first variant. We saw two examples above: -- `DayOfWeek` is a simple enum top to bottom, so `DayOfWeek::Monday` is naturally its default; -- `EnumWithEverything` has data in some of the variants, but not in the first, so in a similar manner, `EnumWithEverything::Default` works as its default. - -However, if we were to define the enum: -```rust -#[derive(TopEncode, TopDecode)] -enum Either { - Something(u32), - SomethingElse(u64), -} -``` -... there is no way to find a natural default value for it. Both variants are represented as non-empty buffers. - -If you need the default, one workaround is to place these structures inside an `Option`. Options always have the default `None`, no matter the contents. - -There is, however, another way to do it: for custom structures it is possible to define custom defaults, as we will see in the next section. - -### Custom defaults - -A structure that does not have a natural default value can receive one via custom code. First of all this applies to structures, but it can also be useful for some enums. - -To do so, instead of deriving `TopEncode` and `TopDecode`, we will derive `TopEncodeOrDefault` and `TopDecodeOrDefault`, respectively. - -We need to also specify what we want that default value to be, both when encoding and decoding. For this, we need to explicitly implement traits `EncodeDefault` and `DecodeDefault` for our structure. - -Let's look at an example: - -```rust -#[derive(TopEncodeOrDefault, TopDecodeOrDefault)] -pub struct StructWithDefault { - pub first_field: u16, - pub seq: Vec, - pub another_byte: u8, - pub uint_32: u32, - pub uint_64: u64, -} - -impl EncodeDefault for StructWithDefault { - fn is_default(&self) -> bool { - self.first_field == 5 - } -} - -impl DecodeDefault for StructWithDefault { - fn default() -> Self { - StructWithDefault { - first_field: 5, - seq: vec![], - another_byte: 0, - uint_32: 0, - uint_64: 0, - } - } -} -``` - -We just specified the following: -- `is_default`:whenever the `first_field` field is equal to 5, the other fields don't matter anymore and we save the structure as an empty buffer; -- `default`: whenever we try to decode an empty buffer, we yield a structure that has the `first_field` set to 5, and all the other fields empty or zero. - -It should always be the case that `::is_default(::default())` is true. The framework does not enforce this in any way, but it should be common sense. - -Other than that, there are no constraints on what the default value should be. - -We can do the same for an enum: - -```rust -#[derive(TopEncodeOrDefault, TopDecodeOrDefault)] -enum Either { - Something(u32), - SomethingElse(u64), -} - -impl EncodeDefault for Either { - fn is_default(&self) -> bool { - matches!(*self, Either::Something(3)) - } -} - -impl DecodeDefault for Either { - fn default() -> Self { - Either::Something(3) - } -} -``` - -We just specified the following: -- `is_default`: whenever we have variant `Either::Something` _and_ the value contained is 3, encode as empty buffer; -- `default`: whenever we try to decode an empty buffer, we yield `Either::Something(3)`. - -The same here, `::is_default(::default())` should be true. No other constraints over what the default value should be, of which variant. `Either::SomethingElse(0)` could also have been chosen to be the default. diff --git a/docs/developers/meta/sc-allocator.md b/docs/developers/meta/sc-allocator.md new file mode 100644 index 000000000..366e693a3 --- /dev/null +++ b/docs/developers/meta/sc-allocator.md @@ -0,0 +1,25 @@ +--- +id: sc-allocator +title: Memory allocation +--- + +[comment]: # (mx-abstract) + +MultiversX smart contracts are compiled to WebAssembly, which does not come with memory allocation out of the box. In general WebAssembly programs need special memory allocation functionality to work with the heap. + +Using traditional memory allocation is highly discouraged on MultiversX. There are several reasons: +- We have “managed types”, which are handled by the VM, and which offer a cheaper and more reliable alternative. +- “Memory grow” operations can be expensive and unreliable. For the stability of the blockchain we have chosen to limit them drastically. +- Memory allocators end up in smart contract code, bloating it with something that is not related in any way to its specifications. This contradicts out design philosophy. + +Even so, it is unreasonable to forbid the use of allocators altogether, whether to use them or not ultimately needs to be the developers' choice. Before framework version 0.41.0, the only allocator solution offered was `wee_alloc`. Unfortunately, it has not been maintained for a few years and has some known vulnerabilities. This was also causing Github’s Dependabot to produce critical warnings, not only to our framework, but to all contract projects, despite most of them not really using it. + +First of all, we made the allocator [configurable](/developers/meta/sc-config#single-contract-configuration) from multicontract.toml, currently the main source of contract build specifications. Developers currently have 4 allocators to choose from. + +Then, we added the following allocators to our framework: +- FailAllocator (the default). It simply crashes whenever any memory allocation of deallocation is attempted. For the first time we have a tool that completely prevents accidental memory allocation. We already had an "alloc" feature in Cargo.toml, but it is only operating high-level and can easily (and sometimes accidentally) be circumvented. +- StaticAllocator64k. Pre-allocates a static 2-page buffer, where all memory is allocated. It can never call memory.grow . It never deallocates and crashes when the buffer is full. It can be suitable for small contracts with limited data being processed, who want to avoid the pitfalls of a memory.grow . +- LeakingAllocator . It uses memory.grow to get hold of memory pages. It also never deallocates. This is because contracts do not generally fill up so much memory and all memory is erased at the end of execution anyway. Suitable for contracts with a little more data. +- `wee_alloc` is still supported. It is, however, not included in the framework. Contracts need to import it explicitly. + +I must reiterate: while these allocators are functional, they should be avoided by all contracts. Only consider this functionality when all else fails, in extremely niche situations, or for dealing very old code. \ No newline at end of file diff --git a/docs/developers/meta/sc-build-reference.md b/docs/developers/meta/sc-build-reference.md new file mode 100644 index 000000000..a31bc2fa0 --- /dev/null +++ b/docs/developers/meta/sc-build-reference.md @@ -0,0 +1,187 @@ +--- +id: sc-build-reference +title: Build Reference +--- + +[comment]: # (mx-abstract) + +## How to: Basic build + +To build a contract, it is enough to navigate in your contract crate and run + +```sh +sc-meta all build +``` + +:::info Note +The traditional way to trigger a build in console is to call `mxpy contract build`, which works as well. However, mxpy currently just forwards commands to the [MultiversX Metaprogramming standalone tool](/developers/meta/sc-meta#introduction), so you might as well call it directly. +::: + +Alternatively you can go to your installed `MultiversX Workspace Explorer` VS Code extension and right click your Smart Contract followed by `Build Contract` + +![build contract screenshot](/developers/sc-meta/ide-build-screenshot.png "Build Contract from the MultiversX Workspace Explorer extension") + + +--- + +[comment]: # (mx-exclude-context) + +## Configuring the build + +The build process is mostly configured using the [build configuration file](/developers/meta/sc-config), currently called `multicontract.toml`, and placed in the contract crate root. + +It is also possible for the build process to produce [more than one contract per project crate](/developers/meta/sc-config#multi-contract-configuration). + + + +--- + +[comment]: # (mx-context-auto) + +## Contract build process deep dive + +This section provides an overview for those who want to understand the system on a deeper level. If you are simply looking to build some contracts, feel free to skip this. + +Building a contract is a complex process, but luckily it gets handled invisibly by the framework. We will follow the components step by step and give some justification for this architecture. + +[comment]: # (mx-context-auto) + +### a. The smart contract itself + +The smart contract is defined as a trait without an implementation. This is good, because it means the contract can be executed on various platforms. Some of the implementation gets generated automatically by the `[multiversx_sc::contract]` macro. + +Not everything, though, can be performed here. Notably, macros cannot access data from other modules or crates, all processing is local to the current contract or module. Therefore, we need another mechanism for working with the complete contract data. + +[comment]: # (mx-context-auto) + +### b. The (generated) ABI generator + +ABIs are a collection of metatada about the contract. To build an ABI, we also need the data from the modules. The module macros cannot be called from the contract macros (macros are run at compilation, we are not even sure that modules will need to be recompiled!). Modules, however can be called. That is why we are actually generating _ABI generator functions_ for each module, which can call one another to retrieve the composite picture. + +Note: The ABI generator comes as an implementation of trait [ContractAbiProvider](https://docs.rs/multiversx-sc/0.39.0/multiversx_sc/contract_base/trait.ContractAbiProvider.html). + +[comment]: # (mx-context-auto) + +### c. Meta crate: generating the ABI + +The next question is how will this function be called. Whenever we compile the WASM contracts, we also produce the ABIs in JSON format. Rust has something called build scripts, which get called _after_ compiling a project, but for reasons that will become apparent later, they are not powerful enough for our usecase. + +Therefore, we have decided to include an extra crate in each smart contract project. It is always found in the `meta` folder in a contract, and it handles the entire build. To minimize boilerplate, it always only contains one line, that simply defers execution to the framework: + +```rust +fn main() { + multiversx_sc_meta::cli_main::(); +} +``` + +To produce the ABI, in fact, it is enough to call: + +```sh +cd meta +cargo run abi +``` + +The meta crate has access to the ABI generator, because it always has a dependency to the contract crate. This is the `my_contract_crate::AbiProvider` in the example above. + +This is also the step where the meta crate parses and processes the `multicontract.toml` file. If there are multiple outputs, one ABI will be produced for each. + +[comment]: # (mx-context-auto) + +### d. Meta crate: generating `wasm` crate code + +Each contract must contain at least one `wasm` crate. This is separate from the contract crate because it has a different purpose: it only needs to be the basis for compiling wasm. Please take it as an intermediary step between the contract logic and the Rust to WASM compiler. This is also where the WASM compilation options are specified (e.g. the optimization level). These options can be seen in the `Cargo.toml` file of the `wasm` crate. + +The separation is helpful because, in this way, the smart contract crate can act as a pure Rust crate with no knowledge of WebAssembly. This makes testing and coverage easy, as well as enabling integration with other unrelated technologies. + +The `wasm` crates do not add any meaningful code to the smart contract. Everything they need to do is to provide an adapter to the WASM function syntax. More specifically, they expose an external function for each desired endpoint, which forwards execution to the corresponding smart contract method. + +If we are not careful, we risk adding unwanted endpoints to the contract. A classic example is when we have a crate with multiple modules, of which only one is imported into the smart contract. In some older versions, you might have gotten unwanted endpoints from the other modules of that crate. To avoid this, we are using the ABI to generate a curated list of endpoints in each `wasm` crate. This way, our contracts always have the exact same endpoints as the ones specified in the ABIs. + +This requires code generation. The `meta` crate will handle this code generation too. An example of such generated code lies below: + +```rust +// Code generated by the multiversx-sc multi-contract system. DO NOT EDIT. + +//////////////////////////////////////////////////// +////////////////// AUTO-GENERATED ////////////////// +//////////////////////////////////////////////////// + +// Init: 1 +// Endpoints: 2 +// Async Callback (empty): 1 +// Total number of exported functions: 4 + +#![no_std] +#![feature(alloc_error_handler, lang_items)] + +multiversx_sc_wasm_adapter::allocator!(); +multiversx_sc_wasm_adapter::panic_handler!(); + +multiversx_sc_wasm_adapter::endpoints! { + adder + ( + getSum + add + ) +} + +multiversx_sc_wasm_adapter::empty_callback! {} +``` + +The `multiversx_sc_wasm_adapter` macros help keep even this generated code to a minimum. + +For multi-contract builds, one `wasm` crate needs to be generated for each of the output contracts: + +- The main wasm crate must always reside in the `wasm` folder. The source file is auto-generated, but the `Cargo.toml` must be provided by the developer. +- The other wasm contracts (called "secondary") receive a crate folder starting with `wasm-`, e.g. `wasm-multisig-view`. These crates are fully generated based on data from `multicontract.toml`. The respective `Cargo.toml` files are based on the `Cargo.toml` of the main wasm crate. All configs are taken from there, except for the crate name. +- Warning: Any folders starting with `wasm-` that are unaccounted for will be removed without prompt. This is to keep the folder structure clean in case of renames. + +[comment]: # (mx-context-auto) + +### e. Meta crate: the actual WASM build + +The previous two steps happen by just calling `cargo run` in the meta crate, but to perform a build, one must call `cargo run build`. + +With the ABI information and the code generated, the meta crate can now build all the WASM contracts, one for each output contract. + +The rust compiler places the result in the designated `target` folder, but for convenience, the meta crate moves the executables to the project `output` folder and renames them according to the configured names. + +You might have performed this step automatically from mxpy, but mxpy simply calls the meta crate to do this job. This is because at this point only the meta crate has access to the ABIs and can do it easily. + +[comment]: # (mx-context-auto) + +### f. Meta crate: build post-processing + +After building the contracts, there are three more operations left to perform, based on the compiled WebAssembly outputs: + +1. All contracts are optimized, using `wasm-opt`. This operation can be disabled (via `--no-wasm-opt`). +2. A WAT file id generated for each contract. Not enabled by default, can be enabled (via `--wat`). The framework simply calls the `wasm2wat` tool to do this. +3. An `.imports.json` file is generated for each contract. Can be disabled (via `--no-imports`). The framework uses the `wasm-objdump` tool to retrieve the imports. It parses the output and saves it as JSON. + +[comment]: # (mx-context-auto) + +### g. Cleaning a project + +Calling `cargo run clean` in the meta crate will run `cargo clean` in all wasm crates and delete the `output` folder. + +`mxpy contract clean` also just forwards to this. + +Note that even the clean operation relies on the ABI, in order to reach all the wasm crates. + +[comment]: # (mx-context-auto) + +### Build process summary + +To recap, the build process steps are as follows: + +1. Generate code using the contract/module macros, including the ABI generator; +2. Call the ABI generator, to produce an ABI in memory; +3. Parse the `multicontract.toml` file (if present); +4. Deciding based on that which endpoints go to which output contracts; +5. Save the ABI as JSON in the output folder (one for each output contract); +6. Generate the wasm crates for all output contracts (all sources generated, Cargo.toml contents copied from the main wasm crate); +7. Build all wasm crates; +8. Copy binaries from the `target` folder(s) to `output`. +9. Perform post-processing for each contract: `wasm-opt`, `wasm2wat`, imports; + +Luckily, the framework can do all of this automatically, with a single click. diff --git a/docs/developers/meta/sc-config.md b/docs/developers/meta/sc-config.md new file mode 100644 index 000000000..4364cb9a6 --- /dev/null +++ b/docs/developers/meta/sc-config.md @@ -0,0 +1,478 @@ +--- +id: sc-config +title: Configuration +--- + +[comment]: # (mx-abstract) + +We like to say that developers don't write smart contracts directly, rather they write _specification_ for smart contracts, from which an automated process creates the smart contracts themselves. + +This philosophy has two practical implications: +1. The smart contract code itself has no direct knowledge of the underlying technology or of blockchain, and can therefore be used to build other products too, like tests, interactors, services, etc. +2. The build process is its own separate thing, which needs to be configured. + +It is also possible to build different variants of smart contracts from the same code base. These variants can contain only subsets of the endpoints available in code, or they might have different build settings and underlying API. We call this system "multi-contract", and it is explained in greater depth further on. + +In order not to overburden the build CLI, the bulk of the build configuration resides in a configuration file in the contract crate root. This file must necessarily be called `multicontract.toml` at the present moment. There are plans to change this name to something more general, since its contents have become as of late broader in scope. + +--- + +[comment]: # (mx-context-auto) + +## Single contract configuration + +### Specification + +Assume we want to build a single contract from the project, encompassing all of the available functionality. Let's look at all the ways in which we can configure it: + +```toml +[settings] +main = "main" + +[contracts.main] +name = "my-contract" +add-unlabelled = true +panic-message = true +ei = "1.3" +allocator = "leaking" +stack-size = "3 pages" +features = ["example_feature_1", "example_feature_2"] +kill-legacy-callback = true +``` + +The settings are as follows: +- `panic-message` + - “Panic with message” is a feature very useful for debugging. It displays messages from regular Rust panics in a contract, at the cost of ~1.5kB of additional contract size. It is disabled by default, we advise against using it in production. + - _values_: `true` | `false + - _default_: `false` +- `ei` + - Configures the post-processor that checks the environment interface (EI) used by the built smart contract. + - The post-processor currently only emits a warning, but this might become a hard error in the future. + - _values_: + - `"1.3"` - the EI version that comes with VM 1.5, coming to mainnet in September 2023 + - `"1.2"` - the EI version that comes with VM 1.4, currently available on mainnet + - `"1.1"` - older version of the EI, here for historical reasons + - `"1.0"` - older version of the EI, here for historical reasons + - _default_: `"1.2"` +- `allocator` + - Read about it in more detail [here](/developers/meta/sc-allocator). + - In short: configures the heap memory allocator to be used inside the compiled contract. + - _values_: + - `"fail"` - execution crashes when any allocation is attempted; + - `"leaking"` - requests pages, allocates in them, but never frees up memory on the heap; + - `"static64k"` - pre-allocated static 2-page buffer is used for the heap; + - `"wee_alloc"` - the `wee_alloc` allocator is used, which must be imported separately to the wasm crate. + - _default_: `"fail"` +- `stack-size` + - Allows adjusting the amount of memory set aside for the stack, in a WebAssembly contract. + - _values_: + - either number of bytes, e.g. `655360`; + - or the same number expressed as kilobytes with the suffix `k`, e.g. `"64k"`, `"128k"`, etc.; + - or the same number expressed in 64k pages, with the suffix `pages`, e.g. `"1 pages"`, `"8 pages"`, etc.; + - spaces are fine; + - there are some restrictions on this number, it can't be arbitrarily small. We advise against anything less than a page. + - _default_: `131072` or `"128k"`, the size of 2 pages of memory in WebAssembly. +- `features` + - Smart contract crates can have feature flags for conditional compilation. These feature flags allow the possiblity of having differences between variants of the smart contract and the usage of the code in tools and off-chain projects. + - How it works: the contract will be built with these feature flags activated. + - _values_: + - a list of feature flags, similar to `Cargo.toml`, e.g. `features = ["example_feature_1", "example_feature_2"]` + - _default_: `[]` +- `kill-legacy-callback` + - The framework has no way of knowing whether or not a certain smart contract variant actually needs the async callback code or not, so in rare situations it is necessary to forcefully remove it. + - _values_: `true` | `false + - _default_: `false` + + +--- + +[comment]: # (mx-context-auto) + +### Default configuration + +Just to clarify defaults once again, if there is no configuration file, all defaults will be taken. This is equivalent to the minimal file below: + +```toml +[settings] +main = "main" + +[contracts.main] +name = "my-contract" +add-unlabelled = true +``` + +It is also equivalent to this more verbose version: + +```toml +[settings] +main = "main" + +[contracts.main] +name = "my-contract" +add-unlabelled = true +panic-message = false +ei = "1.2" +allocator = "fail" +stack-size = "2 pages" +features = [] +kill-legacy-callback = false +``` + + +--- + +[comment]: # (mx-context-auto) + +## Multi-contract configuration + +[comment]: # (mx-context-auto) + +### Introduction + +Starting with version `0.37`, it is possible to configure a so-called "multi contract build". + +Just as we defined a single output contract (or contract variant) in the examples above, it is possible to define any number of such outputs. + +Currently the most popular use of having multiple contracts built from the same source is having _external view contracts_. + +An external view contract is a contract that allows convenient reading from another contract's storage directly. Its purpose is to off-load some of the logic required for view functions from the main contract to a secondary one. This can in some cases decrease the main contract's size by many kilobytes of binary code. + +We develop this in more depth [a little later on](#the-external-view-contract-explained), but let's start with an example. + +We will use the multisig contract as an example. In this contract we have several endpoints that are never used on-chain: `getPendingActionFullInfo`, `userRole`, `getAllBoardMembers`, `getAllProposers`, `getActionData`, `getActionSigners`, `getActionSignerCount`, `getActionValidSignerCount`. We want to place these contracts in an external view contract. + +In order to make configuration easier, we label them in code, as can be seen in the excerpt below: + +```rust +#[multiversx_sc::module] +pub trait MultisigStateModule { + // ... + + /// Serialized action data of an action with index. + #[label("multisig-external-view")] + #[view(getActionData)] + fn get_action_data(&self, action_id: usize) -> Action { + // ... + } + + /// Gets addresses of all users who signed an action. + #[label("multisig-external-view")] + #[view(getActionSigners)] + fn get_action_signers(&self, action_id: usize) -> ManagedVec { + // ... + } + + // ... +} + +``` + +Labels don't do anything more than provide a handy way to refer to groups of endpoints in `multicontract.toml`. + +Now for the `multicontract.toml` config itself, with explanations in comments: + +```toml +[settings] +# one of the output contracts is considered "multisig-main" +# it can have any id +main = "multisig-main" + +# contracts are identified by a contract id +# this id is only relevant in this file and in test setup +[contracts.multisig-main] +# the contract name is the important one, +# the output will be .wasm/.abi.json +name = "multisig" +# we can choose to add all unlabelled endpoints to a contract +add-unlabelled = true + +# this is the external view contract, here we call it "view" +[contracts.view] +# the output will be multisig-view.wasm/multisig-view.abi.json +name = "multisig-view" +# this is how we signal that this contract will be built as an external view +external-view = true +# we only add the endpoints labelled "multisig-external-view", as in the code snippet above +# any number of labels can be added +add-labels = ["multisig-external-view"] + +# this is how you get a version of the contract with all endpoints +# (main and external view, as defined above), +[contracts.full] +name = "multisig-full" +add-unlabelled = true +add-labels = ["multisig-external-view"] +``` + +[comment]: # (mx-context-auto) + +### The external view contract explained + +The main rationale for _external view contracts_. It is very common for contracts to have certain endpoints that are very useful for grabbing data off-chain, but are very rarely used on-chain, if ever. Their code is basically bloating the main contract, and the idea is to extract them into a separate contract. This second contract (called an "external view contract") works because contracts can read from the storage of other contracts directly. + +The framework does the storage access rerouting automatically behind the scenes. The contract code cannot even tell the difference between a regular view from the same contract and one that has been relegated to an external view. Even more so, the same view endpoint can function both as external view and as regular view in different contract variants. + +An _external view contract_ has a behavior different from that of a regular contract. The framework adds some logic to such a contract, which is invisible to the developer. There are two main points: + +1. Storage access is different. All storage reads are done from the target contract given in the constructor. +2. An external view contract is allowed to write to storage, but it will be its own storage. In general avoid writing in such a contract. This might become an error in the future. +3. The constructor is different. Be mindful of this when deploying the external view contract. + - The original constructor is ignored, [a specific constructor is always provided instead](https://docs.rs/multiversx-sc/0.39.0/multiversx_sc/external_view_contract/fn.external_view_contract_constructor.html). + - This constructor always takes one single argument, which is the address of the target contract to read from. From this on, the target address can no longer be changed. + - The external view constructor ABI is always as follows: + +```json +{ + "constructor": { + "docs": [ + "The external view init prepares a contract that looks in another contract's storage.", + "It takes a single argument, the other contract's address", + "You won't find this constructors' definition in the contract, it gets injected automatically by the framework. See `multiversx_sc::external_view_contract`." + ], + "inputs": [ + { + "name": "target_contract_address", + "type": "Address" + } + ], + "outputs": [] + } +} +``` + +--- + +[comment]: # (mx-context-auto) + +### Specification + +- `settings` + - `main` - The contract id of the main wasm crate. The only thing special about this contract's crate is that it is simply called `wasm` and that its `Cargo.toml` is the basis for the `Cargo.toml` configs in all other output contract wasm crates. +- `contracts` map, indexed by contract id. Each contract has: + - `name` (optional) + - The output contract name. + - It forms the basis of all output artifacts, e.g. from `my-contract` we get `my-contract.abi.json`, `my-contract.wasm`, `my-contract.mxsc.json`, etc. + - _values_: any alphanumeric string, dashed and underscores are allowed. Dashes are preferred over underscores. + - _default_: if missing, the contract id will be used. + - `external-view` + - Specifies that a contract should be built as an external view contract. + - _values_: `true` | `false` + - _default_: `false` + - `add-unlabelled` + - Specifies that all unlabelled endpoints should be added to this contract. + - _values_: `true` | `false` + - _default_: `false` + - `add-labels` + - All endpoints labelled with at least one of these labels will be added to the contract. + - _values_: a list of string labels, e.g. `add-labels = ["label1", "label2"]` + - _default_: `[]` + - `add-endpoints` + - A list of endpoint names to be added directly to this contract. + - It bypasses the label system. + - Can be useful if for some reason labels are missing in code or deemed too cumbersome. + - Use the public endpoin names, not the rust function names. + - _values_: a list of endpoint names, e.g. `add-labels = ["myEndpoint1", "myEndpoint1"]` + - _default_: `[]` +- `labels-for-contracts` + - Currently not used in any of our projects, probably better to stay away from this feature. Providing documentation for reference, anyway. + - The idea is that it is also possible to map in reverse, labels to contracts. It contains a mapping from labels to lists of contract ids. + - It can be a little harder to read than the contract to label map, but it can be used. + - There is a special key, `default`, which refers to the unlabelled endpoints. + - Example, equivalent to the labels in : + +```toml +[settings] +main = "multisig-main" + +[contracts.multisig-main] +name = "multisig" + +[contracts.view] +name = "multisig-view" +external-view = true + +[contracts.full] +name = "multisig-full" + +[labels-for-contracts] +default = ["multisig-main", "full"] +multisig-external-view = ["view", "full"] +``` + +--- + +[comment]: # (mx-context-auto) + +## More examples + +[comment]: # (mx-context-auto) + +### Example 1 + +Suppose we want to use some unreleased functionality on the devnet, but also want a version of the contract that we can deploy to mainnet. + +We can use a single code base, but produce two contracts from it. + +In this example we use the async promises system, which is unreleased on the mainnet at the time of writing this. + +Excerpts from the contract code, for context: + +```rust +#[multiversx_sc::contract] +pub trait ForwarderQueue { + // ... + + #[endpoint] + #[payable("*")] + fn forward_queued_calls(&self) { + while let Some(node) = self.queued_calls().pop_front() { + // ... + + match call.call_type { + // ... + QueuedCallType::Promise => { + #[cfg(feature = "promises")] + // ... + }, + } + } + } + + #[promises_callback] + #[label("promises-callback")] + fn promises_callback_method(&self) { + // ... + } +} +``` + +And the configuration that comes with it: + +```toml +[settings] +main = "main" + +[contracts.main] +name = "example1-mainnet" +ei = "1.2" +add-unlabelled = true + +[contracts.promises] +name = "example1-devnet" +ei = "1.3" +add-unlabelled = true +add-labels = ["promises-callback"] +features = ["promises"] +``` + +The result: + +``` +output +├── example1-devnet.abi.json +├── example1-devnet.imports.json +├── example1-devnet.mxsc.json +├── example1-devnet.wasm +├── example1-mainnet.abi.json +├── example1-mainnet.imports.json +├── example1-mainnet.mxsc.json +└── example1-mainnet.wasm +``` + +[comment]: # (mx-context-auto) + +### Example 2 + +Since framework version 0.43.0, it is possible to have different variants of a contract with different constructors. This might desirable for several reasons: +- Keeping separate versions for upgrades and migrations. +- Optimizing contract size by first deploying just with the constructor, and then immediately upgrading to a version with all the code but without the constructor. + +It is also possible to have different versions of the same endpoint in different contract variants. In the example below you will see that the endpoint `sampleValue` has two different implementations. + +A minimal example: + +```rust +#[multiversx_sc::contract] +pub trait Example2 { + #[init] + fn default_init(&self, sample_value: BigUint) { + self.sample_value().set(sample_value); + } + + #[init] + #[label("alt-init")] + fn alternative_init(&self) -> &'static str { + "alternative init" + } + + #[view(sampleValue)] + #[storage_mapper("sample-value")] + fn sample_value(&self) -> SingleValueMapper; + + #[view(sampleValue)] + #[label("alt-impl")] + fn alternative_sample_value(&self) -> &'static str { + "alternative message instead of sample value" + } +} +``` + +And the configuration that comes with it: + +```toml +[settings] +main = "example2-main" + +[contracts.example2-main] +add-unlabelled = true + +[contracts.example2-alt-impl] +add-labels = ["alt-impl"] +``` + + +--- + +[comment]: # (mx-context-auto) + +## Testing with multi-contracts + +It is possible (and recommended) to use the contracts in scenario tests as they would be used on-chain. + +The Go scenario runner will work with the produced contract binaries without further ado. Calling an endpoint that is not available in a certain output contract will fail, even if said endpoint exists in the original contract code. + +To achieve the same effect on the Rust scenario runner, configure as in the following snippet. This is an actual excerpt from `multisig_scenario_rs_test.rs`, one of the multisig test files. + +```rust +fn world() -> ScenarioWorld { + // Initialize the blockchain mock, the same as for a regular test. + let mut blockchain = ScenarioWorld::new(); + blockchain.set_current_dir_from_workspace("contracts/examples/multisig"); + + // Contracts that have no multi-contract config are provided the same as before. + blockchain.register_contract("file:test-contracts/adder.wasm", adder::ContractBuilder); + + // For multi-contract outputs we need to provide: + // - the ABI, via the generated AbiProvider type + // - a scenario expression to bind to, same as for simple contracts + // - a contract builder, same as for simple contracts + // - the contract name, as specified in multicontract.toml + blockchain.register_partial_contract::( + "file:output/multisig.wasm", + multisig::ContractBuilder, + "multisig", + ); + + // The same goes for the external view contract. + // There is no need to specify here that it is an external view, + // the framework gets all the data from multicontract.toml. + blockchain.register_partial_contract::( + "file:output/multisig-view.wasm", + multisig::ContractBuilder, + "multisig-view", + ); + + blockchain +} +``` + diff --git a/docs/developers/meta/sc-meta-cli.md b/docs/developers/meta/sc-meta-cli.md new file mode 100644 index 000000000..b5a9a6860 --- /dev/null +++ b/docs/developers/meta/sc-meta-cli.md @@ -0,0 +1,367 @@ +--- +id: sc-meta-cli +title: CLI +--- + +[comment]: # (mx-context-auto) + +## Introduction + +As mentioned [before](/developers/meta/sc-meta#standalone-tool-vs-contract-tool), the meta tool comes in two flavors, one for the individual contract (based on the contract's ABI), and the other as a standalone tool. + +We will go through the CLI of both of these flavors. + + +[comment]: # (mx-exclude-context) + +## Standalone tool CLI + + +[comment]: # (mx-context-auto) + +### The `all` command + +But first, there is a special feature that needs additional explanation: the `sc-meta all ...` command. + +The standalone and the contract tools are not completely separate: the standalone tool can control multiple smart contract projects in a single command. + +This is where the `all` command comes in: all it does is find all contracts in a given folder and call the contract tool for each of them with the given arguments. + +For example: +- `sc-meta all abi` will generate the ABIs for all contracts; +- `sc-meta all build --locked` will build all contracts, with the crate versions given by Cargo.lock; +- `sc-meta all clean` will clean all projects; +- etc. + +You can call `sc-meta all help` and see that the CLI docs are almost the same as those of the individual contract tool. + +A related command is the `info` command, which just prints a tree with all the contract and contract libraries in a folder, without doing anything to them. + +:::info Note +Since the CLI of the individual contract tool is available in the standalone tool, the following two commands are equivalent: + +``` +cd my-contract +sc-meta all build +``` + +``` +cd my-contract/meta +cargo run build +``` +::: + +Paramameters: +- `--path` + - Target directory where to call all contract meta crates. + - _default_: current directory. +- `--ignore` + - 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. + - _default_: uses the workspace, or builds in `meta/target`. +- `--target-dir-all` + - Overrides both the `--target-dir-meta` and the `--target-dir-wasm` args. + + +[comment]: # (mx-context-auto) + +### Calling `info` + +The info command prints an overview of the contracts and libraries and residing under a folder. It also prints their framework versions. + +As an example, below is the output of calling it in the example contract folder in the framework: + +![sc-meta info screenshot](/developers/sc-meta/sc-meta-info.png "Result of calling sc-meta info in the example contract folder in the framework") + +Paramameters: +- `--path` + - Target directory where to call all contract meta crates. + - _default_: current directory. +- `--ignore` + - Ignore all directories with these names. + - _default_: `target`. + + +[comment]: # (mx-context-auto) + +### Calling `upgrade` + +Calling `sc-meta upgrade` will try to automatically upgrade a contract or group of contracts to the latest version. + +The oldest version currently supported is `0.28.0`. Any older than that, and the developer will need to upgrade it by hand to `0.28.0`. + +It is especially important when upgrading from `0.38` to `0.39.0`, since a lot of changes happened at that point. + +:::tip +For projects with multiple contract crates, we recommend upgrading all of them at once. The upgrade algorithm goes step by step, version after version. For some of the major versions, it also checks that the project compiles before moving on. This is to give developers the chance to fix issues manually, if necessary, and not have those issues pile up. If there are local depdencies between contracts, the upgrader will not be able to do the check unless all of them are upgraded together. +::: + +Paramameters: +- `--path` + - Target directory where to call all contract meta crates. + - _default_: current directory. +- `--ignore` + - Ignore all directories with these names. + - _default_: `target`. +- `--to` + - Overrides the version to upgrade to. + - _default_: the last released version. + +[comment]: # (mx-context-auto) + +### Calling `local-deps` + +Calling `sc-meta local-deps` will create in each contract a report of the local dependencies between contracts and libraries. This helps with the reproducible builds, but might be extended in the future for other uses. + +Example output (abridged): +```json +{ + "root": "/home/user/multiversx/mx-exchange-sc/", + "contractPath": "/home/user/multiversx/mx-exchange-sc/dex/pair", + "commonDependencyPath": "/home/user/multiversx/mx-exchange-sc", + "dependencies": [ + { + "path": "common/common_errors", + "depth": 1 + }, + { + "path": "common/common_structs", + "depth": 1 + }, + { + "path": "common/modules/legacy_token_decode_module", + "depth": 3 + }, + { + "path": "common/modules/locking_module", + "depth": 2 + }, + { + "path": "common/modules/math", + "depth": 2 + } + ] +} +``` + +Paramameters: +- `--path` + - Target directory where to call all contract meta crates. + - _default_: current directory. +- `--ignore` + - Ignore all directories with these names. + - _default_: `target`. + +[comment]: # (mx-context-auto) + +### Calling `new` + + + +Creates a new smart contract project from a standard template. + +The tool will replace all necessary names in the project, based on the the project name given by the user. These include: +- the crate name, +- the contract trait name, +- the file name of the main source file. + +Paramameters: +- `--template` + - The contract template to clone. + - Required. +- `--name` + - The new name the contract is to receive. + - _default_: If missing, the template name will be kept. +- `--tag` + - The framework version on which the contracts should be created. + - _default_: The latest released version. +- `--path` + - Target directory where to create the new contract directory. + - _default_: current directory. + + + +[comment]: # (mx-context-auto) + +### Calling `templates` + +This command lists all available templates. As of framework version 0.43.2, they are: + +``` +crypto-zombies +empty +adder +``` + +Paramameter: +- `--tag` + - The framework version on which the contracts should be created. + - _default_: The latest released version. + + +[comment]: # (mx-context-auto) + +### Calling `test-gen` + + + +Contracts often have JSON scenario tests associated with them, which normally reside in the `scenarios` folder, under the contract crate root. + +In order to execute them as part of the CI, it is helpful to generate a Rust test for each of them. The `test-gen` tool does just that. + +These integration tests come in two flavors: +- Rust tests, that exclusively use the Rust debugger infrastructure; +- VM tests that use the Go infrastructure. + +An example: + +```rust title="adder/tests/adder_scenario_rs_test.rs" +use multiversx_sc_scenario::*; + +fn world() -> ScenarioWorld { + let mut blockchain = ScenarioWorld::new(); + blockchain.set_current_dir_from_workspace("contracts/examples/adder"); + + blockchain.register_contract("file:output/adder.wasm", adder::ContractBuilder); + blockchain +} + +#[test] +fn adder_rs() { + world().run("scenarios/adder.scen.json"); +} + +#[test] +fn interactor_trace_rs() { + world().run("scenarios/interactor_trace.scen.json"); +} +``` + +```rust title="adder/tests/adder_scenario_go_test.rs" +use multiversx_sc_scenario::*; + +fn world() -> ScenarioWorld { + ScenarioWorld::vm_go() +} + +#[test] +fn adder_go() { + world().run("scenarios/adder.scen.json"); +} + +#[test] +fn interactor_trace_go() { + world().run("scenarios/interactor_trace.scen.json"); +} +``` + +The `world()` definition is expected form the developer, but the tests themselves are generated and updated automatically when calling `sc-meta test-gen`. + +:::caution +The tool does not work well with code that is commented-out. In order to temporarily disable a test, annotate it with `#[ignore]`. +::: + +Paramameters: +- `--path` + - Target directory where to call all contract meta crates. + - _default_: current directory. +- `--ignore` + - Ignore all directories with these names. + - _default_: `target`. +- `--create` + - Creates test files if they don't exist. + + + +--- + +[comment]: # (mx-exclude-context) + +## Individual contract CLI + +[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. + +By default, this command will produce three files for each output contract: the ABI (`.abi.json`), the contract (`.wasm`) and a json file with all the used VM EI imported functions (`.imports.json`). For the multisig example above, the produced files are as follows: + +```text +output +├── multisig-full.abi.json +├── multisig-full.imports.json +├── multisig-full.wasm +├── multisig-view.abi.json +├── multisig-view.imports.json +├── multisig-view.wasm +├── multisig.abi.json +├── multisig.imports.json +└── multisig.wasm +``` + +Several arguments can be added to the `build` command, both in mxpy and directly: + +- `--locked` Uses the version from `Cargo.lock`, without updating. Required for reproducible builds. +- `--wasm-name` followed by name: Replaces the main contract's name with this one. Does nothing for secondary contracts. +- `--wasm-suffix` followed by a suffix: Adds a dash and this suffix to all produced contracts. E.g. `cargo run build --wasm-suffix dbg` on multisig will produce contracts `multisig-dbg.wasm`, `multisig-view-dbg.wasm` and `multisig-full-dbg.wasm`. +- `--wasm-symbols` Does not optimize away symbols at compile time, retains function names, good for investigating the WAT. +- `--no-wasm-opt` Does not apply `wasm-opt` after the build, this retains function names, good for investigating the WAT. +- `--wat` Also generates a WAT file for each of the contract outputs. It does so by calling `wasm2wat`. +- `--mir` Also emit MIR files when building. +- `--no-abi-git-version` Skips loading the Git version into the ABI. +- `--no-imports` Does not generate an EI imports JSON file for each contract, as is the default. +- `--target-dir` 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. +- `--twiggy-top` Generate a twiggy top report after building. +- `--twiggy-paths` Generate a twiggy paths report after building. +- `--twiggy-monos` Generate a twiggy monos report after building. +- `--twiggy-dominators` Generate a twiggy dominators report after building. + +[comment]: # (mx-context-auto) + +### Calling `build-dbg` + +There is another command, provided for convenience: `cargo run build-dbg`. Calling this is equivalent to `cargo run build --wasm-symbols --no-wasm-opt --wasm-suffix "dbg" --wat --no-imports`. It is ideal for developers who want to investigate the WebAssembly output produced by the compiler. + +The output for `build-dbg` in the multisig example would be: + +```text +output +├── multisig.abi.json +├── multisig-dbg.wasm +├── multisig-dbg.wat +├── multisig-full.abi.json +├── multisig-full-dbg.wasm +├── multisig-full-dbg.wat +├── multisig-view.abi.json +├── multisig-view-dbg.wasm +└── multisig-view-dbg.wat +``` + +It accepts all the arguments from `build`, so `--target-dir` works here too. + +[comment]: # (mx-context-auto) + +### Calling `twiggy` + +This command is similar to `build-dbg`, in that it provides a shorthand for building contracts and analyzing their size. It is equivalent to running `cargo run build-dbg --twiggy-top --twiggy-paths --twiggy-monos --twiggy-dominators`. + +[comment]: # (mx-context-auto) + +### Calling `clean` + +Calling `mxpy contract clean ` or `cargo run clean` in the meta crate will delete the `output` folder and clean outputs of the Rust crates. + +[comment]: # (mx-context-auto) + +### Calling `snippets` + +Calling `cargo run snippets` in the meta crate will create a project called `interact-rs` in the contract main directory, containing auto-generated boilerplate code for building an interactor for the current contract. + +An interactor is a small tool, meant for developers to interact with the contract on-chain. Being written in Rust, it is ideal for quick interactions and tinkering, directly from the contract project. There will be more documentation in the works on this topic. + + diff --git a/docs/developers/meta/sc-meta.md b/docs/developers/meta/sc-meta.md new file mode 100644 index 000000000..428499663 --- /dev/null +++ b/docs/developers/meta/sc-meta.md @@ -0,0 +1,77 @@ +--- +id: sc-meta +title: Tooling Overview +--- + +[comment]: # (mx-abstract) + +## Introduction + +We have developed a universal smart contract management tool, called `multiversx-sc-meta` (`sc-meta` in short). + +It is called that, because it provides a layer of meta-programming over the regular smart contract development. It can read and interact with some of the code written by developers. + +You can find it on [crates.io](https://crates.io/crates/multiversx-sc-meta) [![crates.io](https://img.shields.io/crates/v/multiversx-sc-meta.svg?style=flat)](https://crates.io/crates/multiversx-sc-meta) + +To install it, simply call + +``` +cargo install multiversx-sc-meta +``` + +After that, try calling `sc-meta help` or `sc-meta -h` to see the CLI docs. + +[comment]: # (mx-context-auto) + +## Standalone tool vs. contract tool + +The unusual thing about this tool is that it comes in two flavors. One of them is the standalone tool, installed as above. The other is a tool that gets provided specifically for every contract, and which helps with building. + +The contract tool lies in the `meta` folder under each contract. It just contains these 3 lines of code: + +```rust +fn main() { + multiversx_sc_meta::cli_main::(); +} +``` + +... but they are important, because they link the contract tool to the contract code, via the [ABI](/developers/data/abi). + +The contract tool is required in order to build contracts, because it is the only tool that we have that calls the ABI generator, manages the wasm crate and the multi-contract config, and has the data on how to build the contract. + +Therefore, all the functionality that needs the ABI goes into the contract tool, whereas the rest in the standalone tool. + +To see the contract meta CLI docs, `cd` into the `/meta` crate and call `cargo run help` or `cargo run -- -h`. + +[comment]: # (mx-context-auto) + +## Contract functionality + +Currently the contract functionality is: + - `abi` Generates the contract ABI and nothing else. + - `build` Builds contract(s) for deploy on the blockchain. + - `build-dbg` Builds contract(s) with symbols and WAT. + - `twiggy` Builds contract(s) and generate twiggy reports. + - `clean` Clean the Rust project and the output folder. + - `update` Update the Cargo.lock files in all wasm crates. + - `snippets` Generates a snippets project, based on the contract ABI. + +To learn more about the smart contract ABI and ABI-based individual contract tools, see [the CLI reference](/developers/meta/sc-meta-cli). + + +[comment]: # (mx-context-auto) + +## Standalone functionality + +The standalone functionality is: + - `info` General info about the contract an libraries residing in the targetted directory. + - `all` Calls the meta crates for all contracts under given path with the given arguments. + - `new` Creates a new smart contract from a template. + - `templates` Lists the available templates. + - `upgrade` Upgrades a contract to the latest version. Multiple contract crates are allowed. + - `local-deps` Generates a report on the local depedencies of contract crates. Will explore indirect depdencies too. + +All the standalone tools take an optional `--path` argument. if not provided, it will be the current directory. + + + diff --git a/docs/developers/overview.md b/docs/developers/overview.md index c0b2dc5ac..6f928ee8b 100644 --- a/docs/developers/overview.md +++ b/docs/developers/overview.md @@ -85,18 +85,18 @@ Learn about transaction's gas and how a fee is calculated: | Name | Description | |--------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------| | [How to format the data field for Smart Contract calls](/developers/sc-calls-format) | Learn how a Smart Contract call looks like and how arguments should be encoded. | -| [MultiversX serialization format](/developers/developer-reference/serialization-format) | How MultiversX smart contracts serialize arguments, results, and storage. | +| [MultiversX serialization format](/developers/data/serialization-overview) | How MultiversX smart contracts serialize arguments, results, and storage. | | [MultiversX SC annotations](/developers/developer-reference/sc-annotations) | How to use annotations in your contracts to make use of many built-in features from the framework. | -| [MultiversX wasm modules](/developers/developer-reference/sc-modules) | Learn how to divide a Smart Contract into multiples smaller components by using modules. | -| [MultiversX wasm contract calls](/developers/developer-reference/sc-contract-calls) | Learn how to call a Smart Contract from another Smart Contract. | -| [Code metadata](/developers/developer-reference/code-metadata) | Choose the properties / eligible actions of your Smart Contract. | +| [MultiversX SC modules](/developers/developer-reference/sc-modules) | Learn how to divide a Smart Contract into multiples smaller components by using modules. | +| [MultiversX SC contract calls](/developers/developer-reference/sc-contract-calls) | Learn how to call a Smart Contract from another Smart Contract. | +| [Code metadata](/developers/data/code-metadata) | Choose the properties / eligible actions of your Smart Contract. | | [Upgrading smart contracts](/developers/developer-reference/upgrading-smart-contracts) | The implications of upgrading a Smart Contract. | -| [MultiversX wasm api functions](/developers/developer-reference/sc-api-functions) | Make use of the MultiversX VM API functions to query relevant data from the blockchain. | +| [MultiversX SC api functions](/developers/developer-reference/sc-api-functions) | Make use of the MultiversX VM API functions to query relevant data from the blockchain. | | [Storage mappers](/developers/developer-reference/storage-mappers) | Decide from multiple ways of storing data in your SC, by considering performance. | | [Rust testing framework](/developers/developer-reference/rust-testing-framework) | Test your Smart Contract directly in Rust. | | [Rust testing framework functions reference](/developers/developer-reference/rust-testing-framework-functions-reference) | A list of available functions to be used when testing your Smart Contract in Rust. | | [Rust smart contract debugging](/developers/developer-reference/sc-debugging) | How to debug your Smart Contract. | -| [Rust smart contract build reference](/developers/developer-reference/sc-build-reference) | How to build and organize your Smart Contract. | +| [Rust smart contract build reference](/developers/meta/sc-build-reference) | How to build and organize your Smart Contract. | | [Random numbers in smart contracts](/developers/developer-reference/sc-random-numbers) | How to generate random number in Smart Contracts. | [comment]: # (mx-context-auto) diff --git a/docs/developers/scenario-reference/values-complex.md b/docs/developers/scenario-reference/values-complex.md index bf2eefb6a..84e742502 100644 --- a/docs/developers/scenario-reference/values-complex.md +++ b/docs/developers/scenario-reference/values-complex.md @@ -21,7 +21,7 @@ This is ideal for short lists or small structs. - a `SimpleStruct { a: u8, b: BoxedBytes }` can be expressed as `"u8:4|nested:str:value-b"` ::: -Please note that the pipe operator only takes care of the concatenation itself. You are responsible for making sure that [nested encoding](/developers/developer-reference/serialization-format/#the-concept-of-top-level-vs-nested-objects) is used where appropriate. +Please note that the pipe operator only takes care of the concatenation itself. You are responsible for making sure that [nested encoding](/developers/data/serialization-overview/#the-concept-of-top-level-vs-nested-objects) is used where appropriate. [comment]: # (mx-context-auto) @@ -148,7 +148,7 @@ pub struct LotteryInfo { :::tip -Once again, note that all contained values are in [nested encoding format](/developers/developer-reference/serialization-format/#the-concept-of-top-level-vs-nested-objects): +Once again, note that all contained values are in [nested encoding format](/developers/data/serialization-overview/#the-concept-of-top-level-vs-nested-objects): - the token identifier has its length automatically prepended by the `nested:` prefix, - big ints are given with the `biguint:` syntax, which prepends their byte length, diff --git a/docs/developers/scenario-reference/values-simple.md b/docs/developers/scenario-reference/values-simple.md index dead708b6..e9c7a2a69 100644 --- a/docs/developers/scenario-reference/values-simple.md +++ b/docs/developers/scenario-reference/values-simple.md @@ -21,7 +21,7 @@ We chose to create a single universal format to be used everywhere in a scenario The advantage of this unique value format is that it is enough to understand it once to then use it everywhere. -The scenario value format is closely related to the [MultiversX serialization format](/developers/developer-reference/serialization-format). This is not by accident, Scenarios are designed to make it easy to interact MultiversX contracts and their data. +The scenario value format is closely related to the [MultiversX serialization format](/developers/data/serialization-overview). This is not by accident, Scenarios are designed to make it easy to interact MultiversX contracts and their data. Exceptions: `txId`, `comment` and `asyncCallData` are simple strings. `asyncCallData` might be changed to the default value format in the future and/or reworked. @@ -110,7 +110,7 @@ Sometimes positive numbers can start with a "1" bit and get accidentally interpr - `"-1"` is represented as `"0xff"`. Negative numbers are also represented in the minimum number of bytes possible. ::: -For more about signed number encoding, see [the big number serialization format](/developers/developer-reference/serialization-format/#arbitrary-width-big-numbers). +For more about signed number encoding, see [the big number serialization format](/developers/data/serialization-overview/#arbitrary-width-big-numbers). [comment]: # (mx-context-auto) diff --git a/docs/developers/tutorials/staking-contract.md b/docs/developers/tutorials/staking-contract.md index b40eb46c9..081c56ab7 100644 --- a/docs/developers/tutorials/staking-contract.md +++ b/docs/developers/tutorials/staking-contract.md @@ -936,7 +936,7 @@ We've also added `TypeAbi`, since this is required for ABI generation. ABIs are Additionally, we've added `PartialEq` and `Debug` derives, for easier use within tests. This will not affect performance in any way, as the code for these is only used during testing/debugging. `PartialEq` allows us to use `==` for comparing instances, while `Debug` will pretty-print the struct, field by field, in case of errors. -If you want to learn more about how such a struct is encoded, and the difference between top and nested encoding/decoding, you can read more [here](/developers/developer-reference/serialization-format): +If you want to learn more about how such a struct is encoded, and the difference between top and nested encoding/decoding, you can read more [here](/developers/data/serialization-overview): [comment]: # (mx-context-auto) diff --git a/docs/sdk-and-tools/sdk-py/smart-contract-interactions.md b/docs/sdk-and-tools/sdk-py/smart-contract-interactions.md index 52bdfc27e..c76512112 100644 --- a/docs/sdk-and-tools/sdk-py/smart-contract-interactions.md +++ b/docs/sdk-and-tools/sdk-py/smart-contract-interactions.md @@ -79,7 +79,7 @@ When we run the **upgrade** function, we once again call the **init** function o Here we have 2 new different elements that we need to observe. First, we changed the **deploy** function with the **upgrade** function, which in turn requires the address of the previously deployed SC address, in order to be able to identify what SC to upgrade. Is important to note that this function can only be called by the SC's owner. The second element we need to observe is the **payable** keyword, which represents a code metadata flag that allows the SC to receive payments. :::tip -More information about Code Metadata can be found [here](/developers/developer-reference/code-metadata). +More information about Code Metadata can be found [here](/developers/data/code-metadata). ::: [comment]: # (mx-context-auto) diff --git a/docusaurus.config.js b/docusaurus.config.js index 232ea83a2..4f0bb97fe 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -179,7 +179,7 @@ const config = { prism: { theme: lightCodeTheme, darkTheme: darkCodeTheme, - additionalLanguages: ["rust", "tsx", "jsonp"], + additionalLanguages: ["rust", "tsx", "jsonp", "toml"], }, algolia: { // The application ID provided by Algolia @@ -244,12 +244,28 @@ const config = { }, { from: "/developers/developer-reference/smart-contract-build-reference", - to: "/developers/developer-reference/sc-build-reference", + to: "/developers/meta/sc-build-reference", + }, + { + from: "/developers/developer-reference/serialization-format", + to: "/developers/data/serialization-overview", }, { from: "/developers/developer-reference/random-numbers-in-smart-contracts", to: "/developers/developer-reference/sc-random-numbers", }, + { + from: "/developers/developer-reference/sc-meta", + to: "/developers/meta/sc-meta", + }, + { + from: "/developers/developer-reference/sc-build-reference", + to: "/developers/meta/sc-build-reference", + }, + { + from: "/developers/developer-reference/code-metadata", + to: "/developers/data/code-metadata", + }, { from: "/sdk-and-tools/erdjs", to: "/sdk-and-tools/sdk-js", diff --git a/sidebars.js b/sidebars.js index c1ddc8be3..d8cc260a7 100644 --- a/sidebars.js +++ b/sidebars.js @@ -65,22 +65,32 @@ const sidebars = { type: "category", label: "Rust Developer reference", items: [ - "developers/developer-reference/sc-meta", - "developers/developer-reference/serialization-format", "developers/developer-reference/sc-annotations", "developers/developer-reference/sc-modules", "developers/developer-reference/sc-contract-calls", "developers/developer-reference/upgrading-smart-contracts", - "developers/developer-reference/code-metadata", "developers/developer-reference/sc-api-functions", "developers/developer-reference/storage-mappers", "developers/developer-reference/rust-testing-framework", "developers/developer-reference/rust-testing-framework-functions-reference", "developers/developer-reference/sc-debugging", - "developers/developer-reference/sc-build-reference", "developers/developer-reference/sc-random-numbers", ], }, + { + type: "category", + label: "Data", + items: [ + "developers/data/serialization-overview", + "developers/data/simple-values", + "developers/data/composite-values", + "developers/data/custom-types", + "developers/data/defaults", + "developers/data/multi-values", + "developers/data/code-metadata", + "developers/data/abi", + ], + }, { type: "category", label: "Rust Developers Best Practices", @@ -91,6 +101,17 @@ const sidebars = { "developers/best-practices/multi-values", ], }, + { + type: "category", + label: "Configuration & Tooling", + items: [ + "developers/meta/sc-meta", + "developers/meta/sc-build-reference", + "developers/meta/sc-config", + "developers/meta/sc-meta-cli", + "developers/meta/sc-allocator", + ], + }, { type: "category", label: "Testing Scenarios", diff --git a/static/developers/sc-build-reference/ide-build-screenshot.png b/static/developers/sc-meta/ide-build-screenshot.png similarity index 100% rename from static/developers/sc-build-reference/ide-build-screenshot.png rename to static/developers/sc-meta/ide-build-screenshot.png diff --git a/static/developers/sc-meta/sc-meta-info.png b/static/developers/sc-meta/sc-meta-info.png new file mode 100644 index 0000000000000000000000000000000000000000..b9af775eb117d06cf10683b5c5a76e6b8a0870e4 GIT binary patch literal 102226 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 z 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 literal 0 HcmV?d00001 From 2a001b646a1fef8f3b57ffe3b7cd201ffdb726d8 Mon Sep 17 00:00:00 2001 From: Andrei Marinica Date: Thu, 31 Aug 2023 18:49:12 +0300 Subject: [PATCH 02/15] SC build CLI update --- docs/developers/meta/sc-meta-cli.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/developers/meta/sc-meta-cli.md b/docs/developers/meta/sc-meta-cli.md index b5a9a6860..5a19dfd4e 100644 --- a/docs/developers/meta/sc-meta-cli.md +++ b/docs/developers/meta/sc-meta-cli.md @@ -304,7 +304,7 @@ output └── multisig.wasm ``` -Several arguments can be added to the `build` command, both in mxpy and directly: +Arguments: - `--locked` Uses the version from `Cargo.lock`, without updating. Required for reproducible builds. - `--wasm-name` followed by name: Replaces the main contract's name with this one. Does nothing for secondary contracts. @@ -312,15 +312,21 @@ Several arguments can be added to the `build` command, both in mxpy and directly - `--wasm-symbols` Does not optimize away symbols at compile time, retains function names, good for investigating the WAT. - `--no-wasm-opt` Does not apply `wasm-opt` after the build, this retains function names, good for investigating the WAT. - `--wat` Also generates a WAT file for each of the contract outputs. It does so by calling `wasm2wat`. -- `--mir` Also emit MIR files when building. +- `--mir` Also emit MIR files when building. +- `--llvm-ir` Also emit LL (LLVM) files when building. - `--no-abi-git-version` Skips loading the Git version into the ABI. - `--no-imports` Does not generate an EI imports JSON file for each contract, as is the default. -- `--target-dir` 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-wasm` 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` Synonym of `--wasm-target-dir`, used for backwards compatibility. - `--twiggy-top` Generate a twiggy top report after building. - `--twiggy-paths` Generate a twiggy paths report after building. - `--twiggy-monos` Generate a twiggy monos report after building. - `--twiggy-dominators` Generate a twiggy dominators report after building. +Additional parameters are inherited from `all`, when running out of the standalone tool: +- `--target-dir-meta` For the meta crates, allows specifying the target directory where the Rust compiler will build the intermediary files. +- `--target-dir-all` Overrides both the `--target-dir-meta` and the `--target-dir-wasm` args. + [comment]: # (mx-context-auto) ### Calling `build-dbg` From 88ec88b6973389c4bec54c93d3b69e14d21933af Mon Sep 17 00:00:00 2001 From: Andrei Marinica Date: Thu, 31 Aug 2023 18:56:14 +0300 Subject: [PATCH 03/15] title fix --- docs/developers/data/defaults.md | 4 ++++ docs/developers/meta/sc-config.md | 2 ++ 2 files changed, 6 insertions(+) diff --git a/docs/developers/data/defaults.md b/docs/developers/data/defaults.md index 5b764e541..4d875128b 100644 --- a/docs/developers/data/defaults.md +++ b/docs/developers/data/defaults.md @@ -41,6 +41,10 @@ For instance, for all numeric types, zero is the default value, because we repre | `DayOfWeek` (see example above) | `DayOfWeek::Monday` | | `EnumWithEverything` (see example above) | `EnumWithEverything::Default` | + + +[comment]: # (mx-context-auto) + ### Types that have no defaults Certain types have no values that can be represented as an empty buffer, and therefore they have no default value. diff --git a/docs/developers/meta/sc-config.md b/docs/developers/meta/sc-config.md index 4364cb9a6..cdf3f8f22 100644 --- a/docs/developers/meta/sc-config.md +++ b/docs/developers/meta/sc-config.md @@ -21,6 +21,8 @@ In order not to overburden the build CLI, the bulk of the build configuration re ## Single contract configuration +[comment]: # (mx-context-auto) + ### Specification Assume we want to build a single contract from the project, encompassing all of the available functionality. Let's look at all the ways in which we can configure it: From dfff8ee972c6b00d6c241459804e7cbd6a79fbc9 Mon Sep 17 00:00:00 2001 From: Andrei Marinica Date: Thu, 31 Aug 2023 18:56:22 +0300 Subject: [PATCH 04/15] redirect fix --- docusaurus.config.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docusaurus.config.js b/docusaurus.config.js index 4f0bb97fe..4d455cedd 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -246,6 +246,10 @@ const config = { from: "/developers/developer-reference/smart-contract-build-reference", to: "/developers/meta/sc-build-reference", }, + { + from: "/developers/developer-reference/sc-build-reference", + to: "/developers/meta/sc-build-reference", + }, { from: "/developers/developer-reference/serialization-format", to: "/developers/data/serialization-overview", From ef15c46531cf3934acf4c86918da34721109856d Mon Sep 17 00:00:00 2001 From: Andrei Marinica Date: Fri, 1 Sep 2023 12:26:31 +0300 Subject: [PATCH 05/15] typos --- docs/developers/meta/sc-config.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/developers/meta/sc-config.md b/docs/developers/meta/sc-config.md index cdf3f8f22..6da143a57 100644 --- a/docs/developers/meta/sc-config.md +++ b/docs/developers/meta/sc-config.md @@ -5,10 +5,10 @@ title: Configuration [comment]: # (mx-abstract) -We like to say that developers don't write smart contracts directly, rather they write _specification_ for smart contracts, from which an automated process creates the smart contracts themselves. +We like to say that developers don't write smart contracts directly, rather they write _specifications_ for smart contracts, from which an automated process creates the smart contracts themselves. This philosophy has two practical implications: -1. The smart contract code itself has no direct knowledge of the underlying technology or of blockchain, and can therefore be used to build other products too, like tests, interactors, services, etc. +1. The smart contract code itself has no direct knowledge of the underlying technology or of the blockchain, and can therefore be used to build other products too, such as tests, interactors, services, etc. 2. The build process is its own separate thing, which needs to be configured. It is also possible to build different variants of smart contracts from the same code base. These variants can contain only subsets of the endpoints available in code, or they might have different build settings and underlying API. We call this system "multi-contract", and it is explained in greater depth further on. From b5f6254b4dcd641020587b1b0a3bcbb31ac610bf Mon Sep 17 00:00:00 2001 From: Andrei Marinica Date: Tue, 5 Sep 2023 03:26:01 +0300 Subject: [PATCH 06/15] old multi-value merged into data/multi-value --- .../developers/best-practices/multi-values.md | 86 ----------------- docs/developers/data/multi-values.md | 94 ++++++++++++++++++- docusaurus.config.js | 4 + sidebars.js | 1 - 4 files changed, 97 insertions(+), 88 deletions(-) delete mode 100644 docs/developers/best-practices/multi-values.md diff --git a/docs/developers/best-practices/multi-values.md b/docs/developers/best-practices/multi-values.md deleted file mode 100644 index bf6b9cd4c..000000000 --- a/docs/developers/best-practices/multi-values.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -id: multi-values -title: Multi-values ---- - -[comment]: # (mx-context-auto) - -## Variadic inputs and outputs (multi-values) - -The Rust language does not natively support variadic arguments. Smart contracts, on the other hand, have no limitations on accepting a variable number of inputs or producing a variable number of results. To accommodate this, and to make I/O processing succinct, the Rust framework provides a number of so-called multi-value types, that deserialize from multiple inputs and serialize to multiple outputs. - -Please note that the same types are used both as arguments and as results. This makes sense especially for places like the callbacks, where the results of the asynchronous call become the inputs of the callback. - -There are 4 types of variadic arguments supported for functions: -- `OptionalValue` - arguments that can be skipped. Can have multiple per endpoint, but they all must be the last arguments of the endpoint. -- `MultiValueN` - A multi-value tuple. Can be used to return multiple results (since Rust methods can only have a single result value). They also work well in conjunction with other multi-values, for instance `MultiValueEncoded>` will accept any number of pairs of value, but will crash if an odd number of arguments is provided. -- `MultiValueEncoded` - can receive any number of arguments. Note that only one `MultiValueEncoded` can be used per endpoint and must be the last argument in the endpoint. Cannot use both `OptionalValue` and `MultiValueEncoded` in the same endpoint. It keeps its contents encoded, so it decodes lazily when used as an argument and encodes eagerly when used as a result. -- `MultiValueManagedVec` - Similar to `MultiValueEncoded`, but it decodes eagerly when used as an argument and encodes lazily when used as a result. It is practically a `ManagedVec` with multi-value encoding, and so `T` in this case must be a type that implements `ManagedVecItem`. It cannot contain multi-values such as `MultiValueN`. - -Below you can find some examples: -``` -#[endpoint(myOptArgEndpoint)] -fn my_opt_arg_endpoint(&self, obligatory_arg: T1, opt_arg: OptionalValue) {} - -#[endpoint(myVarArgsEndpoint)] -fn my_var_args_endpoint(&self, obligatory_arg: T1, args: MultiValueEncoded) {} -``` - -This might seem over-complicated for no good reason. Why not simply use `Option` instead of `OptionalValue` and `ManagedVec` instead of `MultiValueEncoded`? The reason is the type of encoding used for each of them. - -[comment]: # (mx-context-auto) - -### Option\ vs OptionalValue\ - -Let's use the following endpoints as examples: -``` -#[endpoint(myOptArgEndpoint)] -fn my_opt_arg_endpoint(&self, token_id: TokenIdentifier, opt_nonce: Option) {} -``` - -``` -#[endpoint(myOptArgEndpoint)] -fn my_opt_arg_endpoint(&self, token_id: TokenIdentifier, opt_nonce: OptionalValue) {} -``` - -With the following arguments: TOKEN-123456 (0x544f4b454e2d313233343536) and 5. - -The most important factor is the user experience, but there's also a matter of efficiency. For the first one, the call data would look like this (notice the `01` in front for `Some(T)`) -`myOptArgEndpoint@544f4b454e2d313233343536@010000000000000005` - -While for the second one, this is a lot cleaner: -`myOptArgEndpoint@544f4b454e2d313233343536@05` - -For the same token ID and skipped nonce, the encodings look like this: -`myOptArgEndpoint@544f4b454e2d313233343536@00` - -`myOptArgEndpoint@544f4b454e2d313233343536` - -As you can see, the argument can be skipped altogether instead of passing a `00` (`None`). - -[comment]: # (mx-context-auto) - -### ManagedVec\ vs MultiValueEncoded\ - -For the sake of the example, let's assume you want to receive pairs of (token ID, nonce, amount). This can be implemented in two ways: -``` -#[endpoint(myVarArgsEndpoint)] -fn my_var_args_endpoint(&self, args: ManagedVec<(TokenIdentifier, u64, BigUint)>) {} -``` - -``` -#[endpoint(myVarArgsEndpoint)] -fn my_var_args_endpoint(&self, args: MultiValueManagedVec) {} -``` - -The first approach looks a lot simpler, just a `ManagedVec` of tuples. But, the implications are quite devastating, both for performance and usability. To use the first endpoint, with the pairs (TOKEN-123456, 5, 100) and (TOKEN-123456, 10, 500), the call data would have to look like this: -`myVarArgsEndpoint@0000000c_544f4b454e2d313233343536_0000000000000005_00000001_64_0000000c_544f4b454e2d313233343536_000000000000000a_00000002_01f4` - -Note: Above, we've separated the parts with `_` for readability purposes only. On the real blockchain, there would be no underscores, everything would be concatenated. - -As you can see, that endpoint is very hard to work with. All arguments have to be passed into this big chunk, with nested encoding, which also adds additional lengths for the `TokenIdentifier` (i.e. the 0000000c in front, which is length 12) and for `BigUint` (i.e. the length in bytes). - -For the `MultiValueEncoded` approach, this endpoint is a lot easier to use. For the same arguments, the call data looks like this: -`myVarArgsEndpoint@544f4b454e2d313233343536@05@64@544f4b454e2d313233343536@0a@01f4` - -The call data is a lot shorter, and it's much more readable, and as we use top-encoding instead of nested-encoding, there's no need for lengths either. diff --git a/docs/developers/data/multi-values.md b/docs/developers/data/multi-values.md index d25f9adc9..f8cb9caf4 100644 --- a/docs/developers/data/multi-values.md +++ b/docs/developers/data/multi-values.md @@ -99,6 +99,98 @@ These storage mappers are, in no particular order: - FungibleTokenMapper - NonFungibleTokenMapper +[comment]: # (mx-context-auto) + +## Multi-values in action + +[comment]: # (mx-context-auto) + +To clarify the way multi-values work in real life, let's provide some examples of how one would go avout calling an endpoint with variadic arguments. + +[comment]: # (mx-context-auto) + +### Option vs. OptionalValue + +Assume we want to have an endpoint that takes a token identifier, and, optionally, a token nonce. There are two ways of doing this: + +```rust +#[endpoint(myOptArgEndpoint1)] +fn my_opt_arg_endpoint_1(&self, token_id: TokenIdentifier, opt_nonce: Option) {} +``` + +```rust +#[endpoint(myOptArgEndpoint2)] +fn my_opt_arg_endpoint_2(&self, token_id: TokenIdentifier, opt_nonce: OptionalValue) {} +``` + +We want to call these endpoints with arguments: `TOKEN-123456` (`0x544f4b454e2d313233343536`) and `5`. To contrast for the two endpoints: +- Endpoint 1: `myOptArgEndpoint1@544f4b454e2d313233343536@010000000000000005` +- Endpoint 2: `myOptArgEndpoint2@544f4b454e2d313233343536@05` + + +:::info Note +In the first case, we are dealing with an [Option](/developers/data/composite-values#options), whose first encoded byte needs to be `0x01`, to signal `Some`. In the second case there is no need for `Option`, `Some` is signalled simply by the fact that the argument was provided. + +Also note that the nonce itself is nested-encoded in the first case (being _nested_ in an `Option`), whereas in the second case it can be top-encoded directly. +::: + +Now let's do the same exercise for the case where we want to omit the nonce altogether: +- Endpoint 1: + - `myOptArgEndpoint1@544f4b454e2d313233343536@`, or + - `myOptArgEndpoint1@544f4b454e2d313233343536@00` - also accepted +- Endpoint 2: + - `myOptArgEndpoint2@544f4b454e2d313233343536` + +:::info Note +The difference is less striking in this case. + +In the first case, we encoded `None` as an empty byte array (encoding it as `0x00` is also accepted). In any case, we do need to pass it as an explicit argument. + +In the second case, the last argument is omitted altogether. +::: + +We also want to point out that the multi-value implementation is more efficient in terms of gas. It is more easier for the smart contract to count the number of arguments and top-decode, than parse a composite type, like `Option`. + +[comment]: # (mx-context-auto) + +### ManagedVec vs. MultiValueEncoded + +In this example, let's assume we want to receive any number of triples of the form (token ID, nonce, amount). This can be implemented in two ways: + +```rust +#[endpoint(myVarArgsEndpoint1)] +fn my_var_args_endpoint_1(&self, args: ManagedVec<(TokenIdentifier, u64, BigUint)>) {} +``` + +```rust +#[endpoint(myVarArgsEndpoint2)] +fn my_var_args_endpoint_2(&self, args: MultiValueManagedVec) {} +``` + +The first approach seems a little simpler from the perspective of the smart contract implementation, since we only have a `ManagedVec` of tuples. But when we try to encode this argument, to call the endpoint, we are struck with a format that is quite devastating, both for performance and usability. + +Let's call these endpoints with triples: `(TOKEN-123456, 5, 100)` and `(TOKEN-123456, 10, 500)`. The call data would have to look like this: +`myVarArgsEndpoint1@0000000c_544f4b454e2d313233343536_0000000000000005_00000001_64_0000000c_544f4b454e2d313233343536_000000000000000a_00000002_01f4`. + +:::info Note +Above, we've separated the parts with `_` for readability purposes only. On the real blockchain, there would be no underscores, everything would be concatenated. + +Every single value in this call data needs to be nested-encoded. We need to lay out the length or each token identifier, nonces are spelled out in full 8 bytes, and we also need the length of each `BigUint` value. +::: + +As you can see, that endpoint is very hard to work with. All arguments are concatenated into one big chunk, and every single value needs to be nested-encoded. This is why we need to lay out the length for each `TokenIdentifier` (e.g. the 0000000c in front, which is length 12) as well as for each `BigUint` (e.g. the `00000001` before `64`). The nonces are spelled out in their full 8 bytes. + +The second endpoint is a lot easier to use. For the same arguments, the call data looks like this: +`myVarArgsEndpoint2@544f4b454e2d313233343536@05@64@544f4b454e2d313233343536@0a@01f4`. + +It is a lot more readable, for several reasons: +- We have 6 arguments instead of 1; +- The argument separator makes it much easier for both us and the smart contract to distinguish where each value ends and where the next one begins; +- All values are top-encoded, so there is no more need for lengths; the nonces can be expressed in a more compact form. + +Once again, the multi-value implementation is more efficient in terms of gas. All the contract needs to do is to make sure that the number of arguments is a multiple of 3, and then top-decode each value. Conversely, in the first example, a lot more memory needs to be moved around when splitting the large argument into pieces. + + [comment]: # (mx-context-auto) ## Implementation details @@ -107,7 +199,7 @@ All serializable types will implement traits `TopEncodeMulti` and `TopDecodeMult The components that do argument parsing, returning results, or handling of event logs all work with these two traits. -All serializable types (the ones that implement `TopEncode`) are explicitly decalred to also be multi-value in this declaration: +All serializable types (the ones that implement `TopEncode`) are explicitly declared to also be multi-value in this declaration: ```rust /// All single top encode types also work as multi-value encode types. diff --git a/docusaurus.config.js b/docusaurus.config.js index 4d455cedd..586d10ee0 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -270,6 +270,10 @@ const config = { from: "/developers/developer-reference/code-metadata", to: "/developers/data/code-metadata", }, + { + from: "/developers/best-practices/multi-values", + to: "/developers/data/multi-values", + }, { from: "/sdk-and-tools/erdjs", to: "/sdk-and-tools/sdk-js", diff --git a/sidebars.js b/sidebars.js index 26e2a9362..ea3764d49 100644 --- a/sidebars.js +++ b/sidebars.js @@ -98,7 +98,6 @@ const sidebars = { "developers/best-practices/best-practices-basics", "developers/best-practices/biguint-operations", "developers/best-practices/the-dynamic-allocation-problem", - "developers/best-practices/multi-values", ], }, { From b8033f9841ca0daa6e8b2d40e3a114995832d44e Mon Sep 17 00:00:00 2001 From: Andrei Marinica Date: Tue, 5 Sep 2023 03:33:52 +0300 Subject: [PATCH 07/15] Apply suggestions from code review fix after review: typos and syntax fixes Co-authored-by: Ovidiu Olteanu <88948634+ovidiuolteanu@users.noreply.github.com> --- docs/developers/data/multi-values.md | 8 ++++---- docs/developers/meta/sc-allocator.md | 8 ++++---- docs/developers/meta/sc-build-reference.md | 2 +- docs/developers/meta/sc-config.md | 2 +- docs/developers/meta/sc-meta-cli.md | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/developers/data/multi-values.md b/docs/developers/data/multi-values.md index f8cb9caf4..e05abc039 100644 --- a/docs/developers/data/multi-values.md +++ b/docs/developers/data/multi-values.md @@ -10,11 +10,11 @@ To recap, we have discussed about data being represented either in a: - nested encoding, as part of the byte representation of a larger object; - top encoding, the full byte represention of an object. -But even the top encoding only refers to a _single_ object, being represented as a _single_ array of bytes. This encoding, no matter how simple of complex, is the representation for a _single_ argument, result, log topic, log event, NFT attribute, etc. +But even the top encoding only refers to a _single_ object, being represented as a _single_ array of bytes. This encoding, no matter how simple or complex, is the representation for a _single_ argument, result, log topic, log event, NFT attribute, etc. -However, we sometimes want to work with _multiple_, variadic arguments, or an arbitrary number of results. An elegant solution is modelling them as special collections of top-encodable objects that each represent an individual item. For instance, a we could have a list of separate arguments, of arbitrary length. +However, we sometimes want to work with _multiple_, variadic arguments, or an arbitrary number of results. An elegant solution is modelling them as special collections of top-encodable objects that each represent an individual item. For instance, we could have a list of separate arguments, of arbitrary length. -Multi-values work similarly to varargs in other languages, such as C, where you can write `void f(int arg, ...) { ... }`. In the smart contract framework they do not need specialized syntax, we use the type system to define theit behavior. +Multi-values work similarly to varargs in other languages, such as C, where you can write `void f(int arg, ...) { ... }`. In the smart contract framework they do not need specialized syntax, we use the type system to define their behavior. :::info Note In the framework, single values are treated as a special case of multi-value, one that consumes exactly one argument, or returns exactly one value. @@ -217,4 +217,4 @@ where } ``` -To create a custom multi-value type, one needs to implement these two traits for the type, by hand. Unlike for single values, there is no [equivalent derive syntax](/developers/data/custom-types). +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). diff --git a/docs/developers/meta/sc-allocator.md b/docs/developers/meta/sc-allocator.md index 366e693a3..4a0449eed 100644 --- a/docs/developers/meta/sc-allocator.md +++ b/docs/developers/meta/sc-allocator.md @@ -10,16 +10,16 @@ MultiversX smart contracts are compiled to WebAssembly, which does not come with Using traditional memory allocation is highly discouraged on MultiversX. There are several reasons: - We have “managed types”, which are handled by the VM, and which offer a cheaper and more reliable alternative. - “Memory grow” operations can be expensive and unreliable. For the stability of the blockchain we have chosen to limit them drastically. -- Memory allocators end up in smart contract code, bloating it with something that is not related in any way to its specifications. This contradicts out design philosophy. +- Memory allocators end up in smart contract code, bloating it with something that is not related in any way to its specifications. This contradicts our design philosophy. Even so, it is unreasonable to forbid the use of allocators altogether, whether to use them or not ultimately needs to be the developers' choice. Before framework version 0.41.0, the only allocator solution offered was `wee_alloc`. Unfortunately, it has not been maintained for a few years and has some known vulnerabilities. This was also causing Github’s Dependabot to produce critical warnings, not only to our framework, but to all contract projects, despite most of them not really using it. First of all, we made the allocator [configurable](/developers/meta/sc-config#single-contract-configuration) from multicontract.toml, currently the main source of contract build specifications. Developers currently have 4 allocators to choose from. Then, we added the following allocators to our framework: -- FailAllocator (the default). It simply crashes whenever any memory allocation of deallocation is attempted. For the first time we have a tool that completely prevents accidental memory allocation. We already had an "alloc" feature in Cargo.toml, but it is only operating high-level and can easily (and sometimes accidentally) be circumvented. -- StaticAllocator64k. Pre-allocates a static 2-page buffer, where all memory is allocated. It can never call memory.grow . It never deallocates and crashes when the buffer is full. It can be suitable for small contracts with limited data being processed, who want to avoid the pitfalls of a memory.grow . -- LeakingAllocator . It uses memory.grow to get hold of memory pages. It also never deallocates. This is because contracts do not generally fill up so much memory and all memory is erased at the end of execution anyway. Suitable for contracts with a little more data. +- `FailAllocator` (the default) simply crashes whenever any memory allocation or deallocation is attempted. For the first time we have a tool that completely prevents accidental memory allocation. We already had an "alloc" feature in Cargo.toml, but it is only operating high-level and can easily (and sometimes accidentally) be circumvented. +- `StaticAllocator64k` pre-allocates a static 2-page buffer, where all memory is allocated. It can never call memory.grow . It never deallocates and crashes when the buffer is full. It can be suitable for small contracts with limited data being processed, who want to avoid the pitfalls of a memory.grow . +- `LeakingAllocator` uses memory.grow to get hold of memory pages. It also never deallocates. This is because contracts do not generally fill up so much memory and all memory is erased at the end of execution anyway. Suitable for contracts with a little more data. - `wee_alloc` is still supported. It is, however, not included in the framework. Contracts need to import it explicitly. I must reiterate: while these allocators are functional, they should be avoided by all contracts. Only consider this functionality when all else fails, in extremely niche situations, or for dealing very old code. \ No newline at end of file diff --git a/docs/developers/meta/sc-build-reference.md b/docs/developers/meta/sc-build-reference.md index a31bc2fa0..bf41d9ddc 100644 --- a/docs/developers/meta/sc-build-reference.md +++ b/docs/developers/meta/sc-build-reference.md @@ -66,7 +66,7 @@ Note: The ABI generator comes as an implementation of trait [ContractAbiProvider The next question is how will this function be called. Whenever we compile the WASM contracts, we also produce the ABIs in JSON format. Rust has something called build scripts, which get called _after_ compiling a project, but for reasons that will become apparent later, they are not powerful enough for our usecase. -Therefore, we have decided to include an extra crate in each smart contract project. It is always found in the `meta` folder in a contract, and it handles the entire build. To minimize boilerplate, it always only contains one line, that simply defers execution to the framework: +Therefore, we have decided to include an extra crate in each smart contract project. It is always found in the `meta` folder in a contract, and it handles the entire build. To minimize boilerplate, it always only contains one line that simply defers execution to the framework: ```rust fn main() { diff --git a/docs/developers/meta/sc-config.md b/docs/developers/meta/sc-config.md index 6da143a57..0adc5ab7c 100644 --- a/docs/developers/meta/sc-config.md +++ b/docs/developers/meta/sc-config.md @@ -66,7 +66,7 @@ The settings are as follows: - `"wee_alloc"` - the `wee_alloc` allocator is used, which must be imported separately to the wasm crate. - _default_: `"fail"` - `stack-size` - - Allows adjusting the amount of memory set aside for the stack, in a WebAssembly contract. + - Allows adjusting the amount of memory allocated for the stack, in a WebAssembly contract. - _values_: - either number of bytes, e.g. `655360`; - or the same number expressed as kilobytes with the suffix `k`, e.g. `"64k"`, `"128k"`, etc.; diff --git a/docs/developers/meta/sc-meta-cli.md b/docs/developers/meta/sc-meta-cli.md index 5a19dfd4e..18b90b558 100644 --- a/docs/developers/meta/sc-meta-cli.md +++ b/docs/developers/meta/sc-meta-cli.md @@ -92,7 +92,7 @@ Paramameters: Calling `sc-meta upgrade` will try to automatically upgrade a contract or group of contracts to the latest version. -The oldest version currently supported is `0.28.0`. Any older than that, and the developer will need to upgrade it by hand to `0.28.0`. +The oldest version currently supported is `0.28.0`. Any older than that, and the developer will need to manually upgrade it to `0.28.0`. It is especially important when upgrading from `0.38` to `0.39.0`, since a lot of changes happened at that point. From 93d4f7a37f16a5f7533aac72f082dba04c8b0fdd Mon Sep 17 00:00:00 2001 From: Andrei Marinica Date: Tue, 5 Sep 2023 03:34:41 +0300 Subject: [PATCH 08/15] fix after review: additional warning Co-authored-by: Ovidiu Olteanu <88948634+ovidiuolteanu@users.noreply.github.com> --- docs/developers/meta/sc-meta-cli.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/developers/meta/sc-meta-cli.md b/docs/developers/meta/sc-meta-cli.md index 18b90b558..596fbc1d8 100644 --- a/docs/developers/meta/sc-meta-cli.md +++ b/docs/developers/meta/sc-meta-cli.md @@ -100,6 +100,10 @@ It is especially important when upgrading from `0.38` to `0.39.0`, since a lot o For projects with multiple contract crates, we recommend upgrading all of them at once. The upgrade algorithm goes step by step, version after version. For some of the major versions, it also checks that the project compiles before moving on. This is to give developers the chance to fix issues manually, if necessary, and not have those issues pile up. If there are local depdencies between contracts, the upgrader will not be able to do the check unless all of them are upgraded together. ::: +:::caution +Generally, we strongly recommend to ensure code versioning or at least a backup of the contract code to avoid the impossibility of reverting permanent changes. This automatic code altering process involved in using `sc-meta upgrade` highly raises this recommendation. +::: + Paramameters: - `--path` - Target directory where to call all contract meta crates. From 27aada6f7beaa72d812a95b229f35e2ba8857815 Mon Sep 17 00:00:00 2001 From: Andrei Marinica Date: Tue, 5 Sep 2023 03:35:17 +0300 Subject: [PATCH 09/15] fix after review: additional table Co-authored-by: Ovidiu Olteanu <88948634+ovidiuolteanu@users.noreply.github.com> --- docs/developers/data/simple-values.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/developers/data/simple-values.md b/docs/developers/data/simple-values.md index 230922794..f160f5e05 100644 --- a/docs/developers/data/simple-values.md +++ b/docs/developers/data/simple-values.md @@ -207,5 +207,9 @@ They are top-encoded as is, the exact bytes and nothing else. Because of their variable length, they need to be serialized like variable length byte slices when nested, so the length is explicitly encoded at the start. + Type | Value | Top-level encoding | Nested encoding | +| --------------- | --------------------------- | ------------------ | ------------------ | +| `TokenIdentifier` | `ABC-123456` | `0x4142432d313233343536` | `0x0000000A4142432d313233343536` | + --- From 0bbc0350dedb30c6a054875baea65fa48a0aa87d Mon Sep 17 00:00:00 2001 From: Andrei Marinica Date: Tue, 5 Sep 2023 03:36:35 +0300 Subject: [PATCH 10/15] fix after review: upgrade clarification Co-authored-by: Ovidiu Olteanu <88948634+ovidiuolteanu@users.noreply.github.com> --- docs/developers/meta/sc-meta-cli.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/developers/meta/sc-meta-cli.md b/docs/developers/meta/sc-meta-cli.md index 596fbc1d8..c79dcfd7e 100644 --- a/docs/developers/meta/sc-meta-cli.md +++ b/docs/developers/meta/sc-meta-cli.md @@ -90,7 +90,7 @@ Paramameters: ### Calling `upgrade` -Calling `sc-meta upgrade` will try to automatically upgrade a contract or group of contracts to the latest version. +Calling `sc-meta upgrade` will try to automatically alter the code of a contract or group of contracts to make it/them compatible with the latest rust framework version. The oldest version currently supported is `0.28.0`. Any older than that, and the developer will need to manually upgrade it to `0.28.0`. From db9737ce8d5077ddb79b25d26d7758aa99a4883b Mon Sep 17 00:00:00 2001 From: Andrei Marinica Date: Tue, 5 Sep 2023 03:37:10 +0300 Subject: [PATCH 11/15] fix after review: additional note Co-authored-by: Ovidiu Olteanu <88948634+ovidiuolteanu@users.noreply.github.com> --- docs/developers/meta/sc-meta.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/developers/meta/sc-meta.md b/docs/developers/meta/sc-meta.md index 428499663..667d4e9fa 100644 --- a/docs/developers/meta/sc-meta.md +++ b/docs/developers/meta/sc-meta.md @@ -21,6 +21,10 @@ cargo install multiversx-sc-meta After that, try calling `sc-meta help` or `sc-meta -h` to see the CLI docs. +:::note endure dependencies +Ubuntu users have to ensure the existence of the `build_essential` package installed in their system. +::: + [comment]: # (mx-context-auto) ## Standalone tool vs. contract tool From 00c1c2e27af0c26355c0e985b5704f75480b3cc5 Mon Sep 17 00:00:00 2001 From: Andrei Marinica Date: Tue, 5 Sep 2023 03:37:45 +0300 Subject: [PATCH 12/15] fix after review: additional note Co-authored-by: Ovidiu Olteanu <88948634+ovidiuolteanu@users.noreply.github.com> --- docs/developers/meta/sc-meta-cli.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/developers/meta/sc-meta-cli.md b/docs/developers/meta/sc-meta-cli.md index c79dcfd7e..6d13acbf6 100644 --- a/docs/developers/meta/sc-meta-cli.md +++ b/docs/developers/meta/sc-meta-cli.md @@ -175,7 +175,7 @@ The tool will replace all necessary names in the project, based on the the proje Paramameters: - `--template` - - The contract template to clone. + - The contract template to clone. Available options can be retrieve by using [this](/developers/meta/sc-meta-cli#calling-templates) - Required. - `--name` - The new name the contract is to receive. From dc9f26ea456e9b01f406aef6833fdce32df24c0e Mon Sep 17 00:00:00 2001 From: Andrei Marinica Date: Tue, 5 Sep 2023 03:38:05 +0300 Subject: [PATCH 13/15] fix after review: additional warning Co-authored-by: Ovidiu Olteanu <88948634+ovidiuolteanu@users.noreply.github.com> --- docs/developers/meta/sc-allocator.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/developers/meta/sc-allocator.md b/docs/developers/meta/sc-allocator.md index 4a0449eed..b576fb27c 100644 --- a/docs/developers/meta/sc-allocator.md +++ b/docs/developers/meta/sc-allocator.md @@ -22,4 +22,6 @@ Then, we added the following allocators to our framework: - `LeakingAllocator` uses memory.grow to get hold of memory pages. It also never deallocates. This is because contracts do not generally fill up so much memory and all memory is erased at the end of execution anyway. Suitable for contracts with a little more data. - `wee_alloc` is still supported. It is, however, not included in the framework. Contracts need to import it explicitly. -I must reiterate: while these allocators are functional, they should be avoided by all contracts. Only consider this functionality when all else fails, in extremely niche situations, or for dealing very old code. \ No newline at end of file +:::caution +While these allocators are functional, they should be avoided by all contracts. Only consider this functionality when all else fails, in extremely niche situations, or for dealing with very old code. +::: \ No newline at end of file From f2199d9668a07b8171ff35123b9488d5b42d7527 Mon Sep 17 00:00:00 2001 From: Andrei Marinica Date: Tue, 5 Sep 2023 03:46:27 +0300 Subject: [PATCH 14/15] Apply suggestions from code review fix after review: EI release notes link Co-authored-by: Ovidiu Olteanu <88948634+ovidiuolteanu@users.noreply.github.com> --- docs/developers/meta/sc-config.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/developers/meta/sc-config.md b/docs/developers/meta/sc-config.md index 0adc5ab7c..0bae26757 100644 --- a/docs/developers/meta/sc-config.md +++ b/docs/developers/meta/sc-config.md @@ -51,8 +51,8 @@ The settings are as follows: - Configures the post-processor that checks the environment interface (EI) used by the built smart contract. - The post-processor currently only emits a warning, but this might become a hard error in the future. - _values_: - - `"1.3"` - the EI version that comes with VM 1.5, coming to mainnet in September 2023 - - `"1.2"` - the EI version that comes with VM 1.4, currently available on mainnet + - `"1.3"` - the EI version that comes with VM 1.5; check [release notes](https://multiversx.com/releases) for currently running VM version + - `"1.2"` - the EI version that comes with VM 1.4; check [release notes](https://multiversx.com/releases) for currently running VM version - `"1.1"` - older version of the EI, here for historical reasons - `"1.0"` - older version of the EI, here for historical reasons - _default_: `"1.2"` From 19c993f0cfa2ecf3bc357f7695314bcdd82c3d1d Mon Sep 17 00:00:00 2001 From: Andrei Marinica Date: Tue, 5 Sep 2023 10:49:07 +0300 Subject: [PATCH 15/15] removed vs code extension refernce in build reference --- docs/developers/meta/sc-build-reference.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/developers/meta/sc-build-reference.md b/docs/developers/meta/sc-build-reference.md index 064959190..f03c7b14f 100644 --- a/docs/developers/meta/sc-build-reference.md +++ b/docs/developers/meta/sc-build-reference.md @@ -17,11 +17,6 @@ sc-meta all build The traditional way to trigger a build in console is to call `mxpy contract build --path `, which works as well. However, mxpy currently just forwards commands to the [MultiversX Metaprogramming standalone tool](/developers/meta/sc-meta#introduction), so you might as well call it directly. ::: -Alternatively you can go to your installed `MultiversX Workspace Explorer` VS Code extension and right click your Smart Contract followed by `Build Contract` - -![build contract screenshot](/developers/sc-meta/ide-build-screenshot.png "Build Contract from the MultiversX Workspace Explorer extension") - - --- [comment]: # (mx-exclude-context)