Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(abi)!: add a readme and some quality of life code changes to abi #219

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
5 changes: 5 additions & 0 deletions .changeset/four-cows-shout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@axelar-network/axelar-cgp-sui': patch
Foivos marked this conversation as resolved.
Show resolved Hide resolved
---

Added a two utility function in abi to allow for structs and nested vectors to be encoded/decoded, added a readme to abi, and used macros to clean up the code a bit.
Foivos marked this conversation as resolved.
Show resolved Hide resolved
112 changes: 112 additions & 0 deletions move/abi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Abi

This package aims to port abi encoding and decoding capabilities to Sui. Read more about the specification of abi encoding [here](https://docs.soliditylang.org/en/develop/abi-spec.html#formal-specification-of-the-encoding)

## Singletons

There are no singletons in this package.

## Types

There are two types exported by this package: `AbiWriter` and `AbiReader`.

### `AbiWriter`

This type can be used to encode abi data. It has the following relevant functions:
- `abi::new_writer(length: u64): AbiWriter`: Creates a new `AbiWriter` with the specified length (number of encoded arguments)
- `abi::into_bytes(self: AbiWriter): vector<u8>`: Destroys an `AbiWriter` and returns the encoded bytes for it.
- `abi::write_u256(self: &mut AbiWriter, var: u256): &mut AbiWriter`: Writes the provided `u256` into the next slot in the `AbiWriter`. This should be used to write all fixed length variables (`u8`, `u16`, `u32`, `u64`, `u128`, `address` for example) by converting them to `u256`.
- `abi::write_u8(self: &mut AbiWriter, var: u8): &mut AbiWriter`: Wrapper for the above for `u8` specifically.
- `abi::write_bytes(self: &mut AbiWriter, var: vector<u8>): &mut AbiWriter`: Writes the provided bytes into the next slot in the `AbiWriter`. This should be used to write all types that are equivelant to `vector<u8>` (`ascii::String` and `string::String` for example) by converting them to `vector<u8>`.
- `abi::write_vector_u256(self: &mut AbiWriter, var: vector<u256>,): &mut AbiWriter`: Writes the provided `vector<u256>` into the next slot in the `AbiWriter`. This should be used for vectors of other fixed length variables as well.
- `abi::write_vector_bytes(self: &mut AbiWriter, var: vector<vector<u8>>,): &mut AbiWriter`: Writes the provided `vector<vector<u8>>` into the nexts slot in the `AbiWriter`. This should be used for vectors of other variable length variables as well.
- `abi::write_bytes_raw(self: &mut AbiWriter, var: vector<u8>,): &mut AbiWriter`: Writes the raw bytes provided to the next slot of the `AbiWriter`. These bytes are not length prefixed, and can therefore not be decoded as bytes. The purpose of this function is to allow for encoding of more complex, unavailable structs.

#### Example
```rust
let mut writer = abi::new_writer(4);
writer
.write_u256(1234)
.write_bytes(b"some_bytes")
.write_vector_u256(vector[12, 34, 56])
.write_vector_bytes(vector[b"some", b"more", b"bytes"]);
let encoded_data = writer.into_bytes();
```

#### More complex types
More complex types are curently not supported. This is because Sui Move does not support any sort of type inspection (like `is_vector<T>`) to recursively encode vectors. However with `abi::write_bytes_raw` these types can be encoded with some extra work from the user.
For example to encode a struct consisting of `u256` called `number` and a `vector<u8>` called `data` into an `AbiWriter` named `writer` a user could do
```rust
let mut struct_writer = new_writer(2);
struct_writer
.write_u256(number)
.write_bytes(data);
writer
.write_bytes_raw(struct_writer.into_bytes());
```
As another example, to abi encode a `vector<vector<u256>>` named `table` into an `AbiWriter` named `writer` a user could do
```rust
let length = table.length();

let mut length_writer = new_writer(1);
length_writer.write_u256(length as u256);
let mut bytes = length_writer.into_bytes();

let mut table_writer = new_writer(length);
table.do!(|row| {
table_writer.write_vector_u256(row);
});
bytes.append(table_writer.into_bytes());

writer
.write_bytes_raw(bytes);
```

### `AbiReader`

This type can be used to decode abi enocded data. The relevant functions are as follows:
- `abi::new_reader(bytes: vector<u8>): AbiReader`: Creates a new `AbiReader` to decode the bytes provided.
- `abi::into_remaining_bytes(self: AbiReader): vector<u8>`: Get all the bytes stored in the `AbiReader` (name is misleading).
- `abi::read_u256(self: &mut AbiReader): u256`: Read a `u256` from the next slot of the `AbiReader`. Should be used to read other fixed length types as well.
Comment on lines +41 to +43
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

function specific docs should live in the code itself so they're easier to maintain. Use Rust like /// ... doc convention above the functions
https://doc.rust-lang.org/rustdoc/how-to-write-documentation.html#getting-started

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These docstrings will then be included in our docs website, e.g.
https://axelarnetwork.github.io/axelar-cgp-sui/axelar_gateway/proof.html#function-signers

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, please check.

- `abi::read_u8(self: &mut AbiReader): u8`: Wrapper for the above function for `u8`.
- `abi::skip_slot(self: &mut AbiReader)`: Used to ingore a slot on the `AbiReader`, used if it has data encoded there that should be ignored.
- `abi::read_bytes(self: &mut AbiReader): vector<8>`: Read a `vector<u8>` from the next slot of the `AbiReader`. Should be used to read other variable length types as well.
- `abi::read_vector_u256(self: &mut AbiReader): vector<u256>`: Read a `vector<u256>` from the next slot of the `AbiReader`. Should be used to read other fixed length types as well.
- `abi::read_vector_bytes(self: &mut AbiReader): vector<vector<u8>>`: Read a `vector<vector<u8>>` from the next slot of the `AbiReader`. Should be used to read other vectors of variable length types as well (such as `vector<ascii::String>`).
- `abi::read_bytes_raw(self: &mut AbiReader): vector<u8>`: Read the raw bytes encoded in the next slot of the `AbiReader`. This will include any bytes encoded after the raw bytes desired which should be ignored.

#### Example
```rust
let mut reader = abi::new_reader(data);
let number = reader.read_u256();
let name = reader.read_bytes().to_string();
let addresses = reader.read_vector_u256().map!(|val| sui::address::from_u256(val));
let info = reader.read_vector_bytes();
```
Foivos marked this conversation as resolved.
Show resolved Hide resolved

#### More Complex Types

For more complex types like structs or nested vectors `read_bytes_raw` can be used and decoded. For to read a struct that contains a `u256` and a `vector<u8>` from an `AbiReader` called `reader` a user may:
Foivos marked this conversation as resolved.
Show resolved Hide resolved
```rust
let struct_bytes = reader.read_bytes_raw();

let mut struct_reader = new_reader(struct_bytes);
let number = struct_reader.read_u256();
let data = struct_reader.read_bytes();
```
As another example, to decode a `vector<vector<u256>>` into a variable called table from an `AbiReader` called `reader` a user can:
```rust
let mut table_bytes = reader.read_bytes_raw();
let mut length_bytes = vector[];

// Split the data into the lenth and the actual table contents.
32u64.do!(|_| length_bytes.push_back(table_bytes.remove(0)));

let mut length_reader = new_reader(length_bytes);
let length = length_reader.read_u256();

let mut table = vector[];
let mut table_reader = new_reader(table_bytes);
length.do!(|_| table.push_back(table_reader.read_vector_u256()));
```

Loading
Loading