diff --git a/cartesi-rollups_versioned_docs/version-2.0/api-reference/asset-management/getting-started.md b/cartesi-rollups_versioned_docs/version-2.0/api-reference/asset-management/getting-started.md new file mode 100644 index 00000000..a14dd293 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/api-reference/asset-management/getting-started.md @@ -0,0 +1,199 @@ +--- +id: getting-started +title: Getting started +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +This page shows you how to add the CMA library to a Cartesi application and build a small wallet that accepts Ether deposits and answers balance queries. + +## Start from a template + +The fastest way to start is with the official [application templates](https://github.com/Mugen-Builders/libcma-app-templates). The repository contains ready to build projects for Python, Rust and C++. Each one already wires the library, the build files and the portal addresses together. + +```shell +git clone https://github.com/Mugen-Builders/libcma-app-templates +``` + +Copy the folder for your language into your workspace and use it as the base of your application. + +## Add the library to an existing application + +You can also add CMA to an application you already have. + + + + +Install `pycma` from the prebuilt RISC-V wheels: + +```shell +pip3 install pycma --find-links https://prototyp3-dev.github.io/pip-wheels-riscv/wheels/ +``` + +Or build it from the repository: + +```shell +pip3 install pycma@git+https://github.com/Mugen-Builders/libcma-binding-python +``` + +The package is compiled for the RISC-V target, so run the install step inside the Dockerfile that builds your application's machine image. + + + + +Add the binding as a git dependency in your `Cargo.toml`: + +```toml +[dependencies] +libcma_binding_rust = { git = "https://github.com/Mugen-Builders/libcma_binding_rust", branch = "main" } +``` + + + + +Download a prebuilt release from the [releases page](https://github.com/Mugen-Builders/machine-asset-tools/releases). Releases contain runtime and development artifacts for musl and glibc systems. In a Dockerfile: + +```dockerfile +ARG MACHINE_ASSET_TOOLS_VERSION +ADD https://github.com/Mugen-Builders/machine-asset-tools/releases/download/v${MACHINE_ASSET_TOOLS_VERSION}/machine-asset-tools_glibc_riscv64_v${MACHINE_ASSET_TOOLS_VERSION}.tar.gz /tmp/ +RUN tar -xzf /tmp/machine-asset-tools_glibc_riscv64_v${MACHINE_ASSET_TOOLS_VERSION}.tar.gz -C / \ + && rm /tmp/machine-asset-tools_glibc_riscv64_v${MACHINE_ASSET_TOOLS_VERSION}.tar.gz +``` + +This installs the shared library. Use the `_dev` artifact when you also need the headers and the static library. + + + + +## Your first wallet + +The example below accepts Ether deposits, records them in the ledger and answers balance queries. It is a trimmed version of the [wallet sample application](https://github.com/Mugen-Builders/libcma-binding-python/tree/main/sample_apps/wallet_app) that ships with the Python binding. + + + + +```python +from pycma import RollupCma, Ledger, decode_ether_deposit, decode_inspect + +ETHER_PORTAL_ADDRESS = "0xA632c5c05812c6a6149B7af5C56117d1D2603828"[2:].lower() + +rollup = RollupCma() +ledger = Ledger() + +# Create the asset entry for Ether once, at startup +ether = ledger.retrieve_asset(base_token=True) +ETHER_ID = ether["asset_id"] + +def handle_advance(rollup, ledger): + advance = rollup.read_advance_state() + msg_sender = advance["msg_sender"].hex().lower() + + if msg_sender == ETHER_PORTAL_ADDRESS: + deposit = decode_ether_deposit(advance) + account = ledger.retrieve_account(account=deposit["sender"]) + ledger.deposit(ETHER_ID, account["account_id"], deposit["amount"]) + return True + + return False + +def handle_inspect(rollup, ledger): + inspect = rollup.read_inspect_state() + query = decode_inspect(inspect) + + if query["type"] == "BALANCE": + account = ledger.retrieve_account(account=query["account"]) + balance = ledger.balance(ETHER_ID, account["account_id"]) + rollup.emit_report(balance.to_bytes(32, "big")) + return True + + return False + +handlers = {"advance": handle_advance, "inspect": handle_inspect} + +accept = True +while True: + next_request_type = rollup.finish(accept) + accept = handlers[next_request_type](rollup, ledger) +``` + + + + +The Rust template uses [libcmt-binding-rust](https://github.com/Mugen-Builders/libcmt-binding-rust) to read inputs from the machine and the CMA parser to decode them: + +```rust +use libcma_binding_rust::parser::{cma_decode_advance, CmaParserInputType, CmaParserInputData}; +use libcma_binding_rust::Ledger; +use json::object; + +const ETHER_PORTAL: &str = "0xA632c5c05812c6a6149B7af5C56117d1D2603828"; + +fn handle_ether_deposit( + ledger: &mut Ledger, + msg_sender: &str, + payload_hex: &str, +) -> Result<(), Box> { + // The parser reads the payload from data.payload + // and the sender from data.metadata.msg_sender + let request = object! { + data: { + metadata: { msg_sender: msg_sender }, + payload: payload_hex + } + }; + + let decoded = cma_decode_advance( + CmaParserInputType::CmaParserInputTypeEtherDeposit, + request, + )?; + + if let CmaParserInputData::EtherDeposit(deposit) = decoded.input { + let ether_id = ledger.retrieve_ether_assets()?; + let account_id = ledger.retrieve_account_via_address(deposit.sender)?; + ledger.deposit(ether_id, account_id, deposit.amount)?; + } + Ok(()) +} +``` + +Compare the sender of each advance request against the portal addresses to pick the right input type, as shown in the [Rust template](https://github.com/Mugen-Builders/libcma-app-templates/blob/main/rust/src/main.rs). + + + + +In C++ you read inputs with libcmt and pass them straight to the parser: + +```cpp +extern "C" { +#include +#include +#include +} + +// input is a cmt_rollup_advance_t filled by cmt_rollup_read_advance_state(rollup, &input) +cma_parser_input_t parser_input; + +const int err = cma_parser_decode_advance(CMA_PARSER_INPUT_TYPE_ETHER_DEPOSIT, &input, &parser_input); +if (err < 0) { + printf("unable to decode deposit: %d - %s\n", -err, cma_parser_get_last_error_message()); +} + +// parser_input.ether_deposit.sender -> who deposited +// parser_input.ether_deposit.amount -> how much +``` + +The [C++ template](https://github.com/Mugen-Builders/libcma-app-templates/blob/main/cpp/app.cpp) shows the complete request loop. + + + + +## Build and run it + +Applications using the CMA library build and run like any other Cartesi application. Follow [Building an application](../../development/building-an-application.md) to produce the machine image, then [Running an application](../../development/running-an-application.md) to start a local node. Use [Sending inputs and assets](../../development/send-inputs-and-assets.md) to make a deposit and watch your wallet handle it. + +## Next steps + +- [Parsing inputs](./parsing-inputs.md) covers every input the parser can decode. +- [Managing balances](./managing-balances.md) covers the full ledger API. +- [Vouchers and withdrawals](./vouchers.md) shows how users take their assets back to the base layer. diff --git a/cartesi-rollups_versioned_docs/version-2.0/api-reference/asset-management/ledger-reference.md b/cartesi-rollups_versioned_docs/version-2.0/api-reference/asset-management/ledger-reference.md new file mode 100644 index 00000000..8fb0b15c --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/api-reference/asset-management/ledger-reference.md @@ -0,0 +1,152 @@ +--- +id: ledger-reference +title: Ledger reference +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +This page lists every ledger operation with its signature per language. For a guided walk through, see [Managing balances](./managing-balances.md). + +## Lifecycle + +Create, persist, reset and release a ledger. + + + + +| Method | Description | +| :--- | :--- | +| `Ledger()` | Creates an in memory ledger | +| `Ledger(memory_filename, offset, mem_length, n_accounts, n_assets, n_balances, initialize_memory)` | Creates or opens a file backed ledger. Pass `initialize_memory=True` only the first time, to create and size the file | +| `reset()` | Clears all accounts, assets and balances | + + + + +| Method | Returns | Description | +| :--- | :--- | :--- | +| `Ledger::new()` | `Result` | Creates an in memory ledger | +| `Ledger::init_from_file(config)` | `Result` | Opens or creates a file backed ledger from a `LedgerFileConfig` | +| `Ledger::init_from_buffer(config)` | `Result` | Creates a ledger backed by a caller provided buffer from a `LedgerBufferConfig` | +| `reset()` | `Result<(), LedgerError>` | Clears all records | +| `get_last_error_message()` | `Result` | Returns the text of the last error | + + + + +| Function | Description | +| :--- | :--- | +| `cma_ledger_init(ledger)` | Creates an in memory ledger | +| `cma_ledger_init_file(ledger, memory_file_name, mode, offset, mem_length, n_accounts, n_assets, n_balances)` | Opens (`CMA_LEDGER_OPEN_ONLY`) or creates (`CMA_LEDGER_CREATE_ONLY`) a file backed ledger | +| `cma_ledger_init_buffer(ledger, buffer, mem_length, n_accounts, n_assets, n_balances)` | Creates a ledger backed by a caller provided buffer | +| `cma_ledger_reset(ledger)` | Clears all records | +| `cma_ledger_fini(ledger)` | Releases the ledger | +| `cma_ledger_get_last_error_message()` | Returns the text of the last error | + + + + +## Retrieving accounts and assets + +These calls find an entry and return its internal ID. With the find or create operation they also create missing entries, which is the common case when handling deposits. + + + + +| Method | Description | +| :--- | :--- | +| `retrieve_account(account_id=None, account=None)` | Finds or creates an account. `account` is a hex string: a 20 byte wallet address or a 32 byte account ID. Returns a dict with `account_id` and `account` | +| `retrieve_asset(asset_id=None, token=None, token_id=None, token_id_with_amount=None, base_token=None, force_find=None)` | Finds or creates an asset. Use `base_token=True` for Ether, `token` for ERC20, `token` plus `token_id` for ERC721, and add `token_id_with_amount=True` for ERC1155. `force_find=True` fails instead of creating. Returns a dict with `asset_id`, `token`, `token_id` and `total_supply` | + + + + +| Method | Returns | Description | +| :--- | :--- | :--- | +| `retrieve_account_via_address(address)` | `Result` | Finds or creates an account from a wallet address | +| `retrieve_account(account_id, account_type, operation, addr_or_id)` | `Result` | Generic account retrieval with explicit `AccountType` and `RetrieveOperation` | +| `retrieve_ether_assets()` | `Result` | Finds or creates the Ether asset | +| `retrieve_erc20_asset_via_address(token_address)` | `Result` | Finds or creates an ERC20 asset from its contract address | +| `retrieve_erc721_assets_via_address(token_address, token_id)` | `Result` | Finds or creates an ERC721 asset from its contract address and token ID | +| `retrieve_asset(asset_id, token_address, token_id, asset_type, operation)` | `Result` | Generic asset retrieval with explicit `AssetType` and `RetrieveOperation` | + + + + +| Function | Description | +| :--- | :--- | +| `cma_ledger_retrieve_account(ledger, account_id, account, addr_accid, n_balances, account_type, operation)` | Finds or creates an account. When `account_id` is set the call fills the account details, otherwise it fills the ID | +| `cma_ledger_retrieve_asset(ledger, asset_id, token_address, token_id, out_total_supply, asset_type, operation)` | Finds or creates an asset. Also returns the asset's total supply through `out_total_supply` | + + + + +## State changes + +Each operation either completes fully or fails with an error, for example when funds are insufficient. + + + + +| Method | Description | +| :--- | :--- | +| `deposit(asset_id, account_id, amount)` | Adds `amount` of the asset to the account and raises the total supply | +| `withdraw(asset_id, account_id, amount)` | Removes `amount` from the account and lowers the total supply | +| `transfer(asset_id, from_account_id, to_account_id, amount)` | Moves `amount` between two accounts | + + + + +| Method | Returns | Description | +| :--- | :--- | :--- | +| `deposit(asset_id, to_account_id, amount)` | `Result<(), LedgerError>` | Adds `amount` (a `U256`) to the account | +| `withdraw(asset_id, from_account_id, amount)` | `Result<(), LedgerError>` | Removes `amount` from the account | +| `transfer(asset_id, from_account_id, to_account_id, amount)` | `Result<(), LedgerError>` | Moves `amount` between two accounts | + + + + +| Function | Description | +| :--- | :--- | +| `cma_ledger_deposit(ledger, asset_id, to_account_id, amount)` | Adds the amount to the account | +| `cma_ledger_withdraw(ledger, asset_id, from_account_id, amount)` | Removes the amount from the account | +| `cma_ledger_transfer(ledger, asset_id, from_account_id, to_account_id, amount)` | Moves the amount between two accounts | + + + + +## Queries + +Read only calls. They never change the ledger, so they are safe to use in inspect handlers. + + + + +| Method | Description | +| :--- | :--- | +| `balance(asset_id, account_id)` | Returns the account's balance of the asset as an int | +| `supply(asset_id)` | Returns the total supply of the asset as an int | + + + + +| Method | Returns | Description | +| :--- | :--- | :--- | +| `get_balance(asset_id, account_id)` | `Result` | Returns the account's balance of the asset | +| `get_total_supply(asset_id)` | `Result` | Returns the total supply of the asset | + + + + +| Function | Description | +| :--- | :--- | +| `cma_ledger_get_balance(ledger, asset_id, account_id, out_balance, account_balance_info)` | Fills `out_balance` with the account's balance of the asset | +| `cma_ledger_retrieve_asset(...)` | Returns the asset's total supply through its `out_total_supply` parameter | + + + + +## Errors + +Every operation reports failures with a typed error: an exception in Python, a `LedgerError` in Rust, and a negative code in C and C++. The full list, including insufficient funds, missing accounts and storage limits, is in [Types and selectors](./types-and-selectors.md#error-codes). diff --git a/cartesi-rollups_versioned_docs/version-2.0/api-reference/asset-management/managing-balances.md b/cartesi-rollups_versioned_docs/version-2.0/api-reference/asset-management/managing-balances.md new file mode 100644 index 00000000..fb3e8eff --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/api-reference/asset-management/managing-balances.md @@ -0,0 +1,251 @@ +--- +id: managing-balances +title: Managing balances +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +The CMA ledger records which account owns which asset, and how much of it. Think of it as a plug in wallet for your application: instead of writing your own balance bookkeeping, you call deposit, withdraw and transfer, and the ledger keeps the state consistent. + +## Core ideas + +The ledger works with three things: + +- **Accounts**. An account usually maps to a wallet address, but it can also map to a generic 32 byte ID. Internally each account gets a small numeric `account_id`. +- **Assets**. An asset is anything you track: Ether, an ERC20 token (identified by its address), an ERC721 or ERC1155 token (identified by address plus token ID). Internally each asset gets a numeric `asset_id`. +- **Balances**. The amount of an asset that an account holds. The ledger also tracks the total supply of each asset inside your application. + +Most ledger calls follow a retrieve pattern: you describe the account or asset, and the ledger finds it or creates it and hands back its ID. After that, you work with IDs only. + +## Creating a ledger + + + + +Create an in memory ledger with no arguments, or a file backed ledger that survives restarts by passing a memory file and size limits: + +```python +from pycma import Ledger + +# In memory +ledger = Ledger() + +# File backed, sized for your application +ledger = Ledger( + memory_filename="/dev/pmem2", + offset=0, + mem_length=64 * 1024 * 1024, + n_accounts=16 * 1024, + n_assets=8, + n_balances=8 * 16 * 1024, + initialize_memory=True, # True only on first creation +) +``` + + + + +```rust +use libcma_binding_rust::Ledger; + +// In memory +let mut ledger = Ledger::new()?; +``` + +The binding also exposes `Ledger::init_from_file` and `Ledger::init_from_buffer` for persistent ledgers, configured through `LedgerFileConfig` and `LedgerBufferConfig`. + + + + +```cpp +cma_ledger_t ledger; +if (cma_ledger_init(&ledger) < 0) { + printf(cma_ledger_get_last_error_message()); +} + +// ... use the ledger ... + +cma_ledger_fini(&ledger); +``` + +`cma_ledger_init_file` and `cma_ledger_init_buffer` create persistent ledgers backed by a memory file or a buffer you provide. + + + + +## Accounts and assets + + + + +`retrieve_account` and `retrieve_asset` find an entry or create it when it does not exist yet. Both return a dictionary that includes the internal ID: + +```python +# Account from a wallet address +account = ledger.retrieve_account(account="0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266") +account_id = account["account_id"] + +# The Ether asset (the base token has no contract address) +ether = ledger.retrieve_asset(base_token=True) + +# An ERC20 asset, identified by its contract address +erc20 = ledger.retrieve_asset(token="0x491604c0fdf08347dd1fa4ee062a822a5dd06b5d") + +# An ERC721 asset, identified by address plus token ID +nft = ledger.retrieve_asset(token="0x...", token_id=1) + +# An ERC1155 asset, where each token ID has its own amount +sft = ledger.retrieve_asset(token="0x...", token_id=1, token_id_with_amount=True) + +# Find only, do not create +existing = ledger.retrieve_asset(base_token=True, force_find=True) +``` + + + + +The binding has one generic method and shortcuts for the common cases: + +```rust +use libcma_binding_rust::types::{AssetType, AccountType, RetrieveOperation, AddressCBindingsExt}; +use libcma_binding_rust::types::{Address, U256}; + +// Shortcuts +let ether_id = ledger.retrieve_ether_assets()?; +let erc20_id = ledger.retrieve_erc20_asset_via_address(token_address)?; +let erc721_id = ledger.retrieve_erc721_assets_via_address(token_address, token_id)?; +let account_id = ledger.retrieve_account_via_address(wallet_address)?; + +// Generic forms +let asset_id = ledger.retrieve_asset( + None, // asset_id, when you already know it + Some(token_address), + Some(token_id), + AssetType::TokenAddressId, + RetrieveOperation::FindOrCreate, +)?; + +let account_id = ledger.retrieve_account( + None, + AccountType::WalletAddress, + RetrieveOperation::FindOrCreate, + Some(wallet_address.as_bytes()), +)?; +``` + + + + +```cpp +// Find or create an account from a wallet address +cma_ledger_account_t account = {.address = {.data = { + 0xf3, 0x9f, 0xd6, 0xe5, 0x1a, 0xad, 0x88, 0xf6, 0xf4, 0xce, + 0x6a, 0xb8, 0x82, 0x72, 0x79, 0xcf, 0xff, 0xb9, 0x22, 0x66, +}}}; +cma_ledger_account_id_t account_id; +cma_ledger_account_type_t account_type = CMA_LEDGER_ACCOUNT_TYPE_WALLET_ADDRESS; +cma_ledger_retrieve_account(&ledger, &account_id, &account, NULL, NULL, &account_type, CMA_LEDGER_OP_FIND_OR_CREATE); + +// Find or create an asset from a token address +cma_token_address_t token_address = { /* 20 bytes */ }; +cma_ledger_asset_id_t asset_id; +cma_amount_t total_supply; +cma_ledger_asset_type_t asset_type = CMA_LEDGER_ASSET_TYPE_TOKEN_ADDRESS; +cma_ledger_retrieve_asset(&ledger, &asset_id, &token_address, NULL, &total_supply, &asset_type, CMA_LEDGER_OP_FIND_OR_CREATE); +``` + + + + +## Moving assets + +Three operations change balances. Each one fails with a clear error instead of leaving partial state, for example when an account tries to spend more than it holds. + + + + +```python +# Credit an account, for example after a decoded deposit +ledger.deposit(asset_id, account_id, amount) + +# Move assets between two accounts inside the application +ledger.transfer(asset_id, from_account_id, to_account_id, amount) + +# Debit an account, for example before emitting a withdrawal voucher +ledger.withdraw(asset_id, account_id, amount) +``` + + + + +```rust +ledger.deposit(asset_id, to_account_id, amount)?; +ledger.transfer(asset_id, from_account_id, to_account_id, amount)?; +ledger.withdraw(asset_id, from_account_id, amount)?; +``` + + + + +```cpp +cma_ledger_deposit(&ledger, asset_id, to_account_id, &amount); +cma_ledger_transfer(&ledger, asset_id, from_account_id, to_account_id, &amount); +cma_ledger_withdraw(&ledger, asset_id, from_account_id, &amount); +``` + + + + +## Reading balances + +Queries never change state, so you can call them from inspect handlers. + + + + +```python +balance = ledger.balance(asset_id, account_id) # int +supply = ledger.supply(asset_id) # int, total supply of the asset +``` + + + + +```rust +let balance = ledger.get_balance(asset_id, account_id)?; // U256 +let supply = ledger.get_total_supply(asset_id)?; // U256 +``` + + + + +```cpp +cma_amount_t balance; +cma_ledger_get_balance(&ledger, asset_id, account_id, &balance, NULL); + +// The total supply is returned by cma_ledger_retrieve_asset +// through its out_total_supply parameter. +``` + + + + +## A complete deposit flow + +This is how the parser and the ledger work together when an ERC20 deposit arrives. The snippet comes from the [Python wallet sample](https://github.com/Mugen-Builders/libcma-binding-python/blob/main/sample_apps/wallet_app/app.py): + +```python +if msg_sender == ERC20_PORTAL_ADDRESS: + deposit = decode_erc20_deposit(advance) + + asset = ledger.retrieve_asset(token=deposit["token"]) + account = ledger.retrieve_account(account=deposit["sender"]) + + ledger.deposit(asset["asset_id"], account["account_id"], deposit["amount"]) + return True +``` + +## Error handling + +Ledger operations report failures such as insufficient funds, unknown accounts or full storage. The full code list is in [Types and selectors](./types-and-selectors.md#error-codes). In Python a failed call raises an exception, in Rust it returns a `LedgerError`, and in C and C++ it returns a negative code with details available from `cma_ledger_get_last_error_message()`. diff --git a/cartesi-rollups_versioned_docs/version-2.0/api-reference/asset-management/overview.md b/cartesi-rollups_versioned_docs/version-2.0/api-reference/asset-management/overview.md new file mode 100644 index 00000000..c9ce5e0f --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/api-reference/asset-management/overview.md @@ -0,0 +1,62 @@ +--- +id: overview +title: Overview +resources: + - url: https://github.com/Mugen-Builders/machine-asset-tools + title: CMA core library (C and C++) + - url: https://github.com/Mugen-Builders/libcma-binding-python + title: Python binding (pycma) + - url: https://github.com/Mugen-Builders/libcma_binding_rust + title: Rust binding + - url: https://github.com/Mugen-Builders/libcma-app-templates + title: Application templates +--- + +The Cartesi Machine Assets library (CMA), assists Cartesi applications manage assets such as Ether, ERC20, ERC721 and ERC1155 tokens. It handles the two most repetitive jobs in asset management: + +1. **Decoding inputs**. Deposits arrive from the [portal contracts](../contracts/overview.md) as ABI encoded bytes. Withdrawal and transfer requests from users also arrive as encoded bytes. CMA turns these raw bytes into typed values your code can read directly. + +2. **Recording balances**. Once assets are deposited into your application, the application must record who owns what. CMA library offers a ready made ledger that stores accounts, assets and balances, and updates them on every deposit, transfer and withdrawal. + +CMA is written in C and C++ in the [machine-asset-tools](https://github.com/Mugen-Builders/machine-asset-tools) repository, with bindings for Python and Rust. It does not depend on the Rollup HTTP server hence it communicates with the Cartesi machine through [libcmt](https://github.com/cartesi/machine-guest-tools). + +## The two components + +CMA is split into two parts that work together. You can use both, or only the one you need. + +### Parser + +The parser is the translation layer between your application and the base layer. It serializes and deserializes ABI encoded payloads, mapping thes raw bytes to values your application logic understands and operates on: + +- It decodes Ether, ERC20, ERC721 and ERC1155 deposits sent through the Cartesi portals. +- It decodes withdrawal and transfer requests that users send as ABI encoded inputs. +- It decodes balance and supply queries sent as inspect requests. +- It encodes vouchers so users can move their assets back to the base layer. + +See [Parsing inputs](./parsing-inputs.md) for the guide and the [Parser reference](./parser-reference.md) for every function. + +### Ledger + +The ledger is a small in machine database for managing assets. It stores: + +- **Accounts**, identified by a wallet address or a generic ID. +- **Assets**, identified by a token address, a token address plus token ID, or a plain ID. +- **Balances**, the amount of each asset held by each account, plus the total supply of each asset. + +It exposes deposit, withdraw and transfer operations, and read only balance and supply queries. Every operation either succeeds or returns a clear error code, so your application state stays consistent. See [Managing balances](./managing-balances.md) for an advance guide. + +## Language support + +| Language | Package | Where it comes from | +| :-------- | :-------------------- | :----------------------------------------------------------------------------------------------------------------------------------- | +| Python | `pycma` | [libcma-binding-python](https://github.com/Mugen-Builders/libcma-binding-python), prebuilt RISC-V wheels available | +| Rust | `libcma_binding_rust` | [libcma_binding_rust](https://github.com/Mugen-Builders/libcma_binding_rust), added as a git dependency and also published on cargo. | +| C and C++ | `libcma` | [machine-asset-tools](https://github.com/Mugen-Builders/machine-asset-tools), prebuilt RISC-V binaries on the releases page | + +## Where to go next + +- [Getting started](./getting-started.md): install the library and run your first wallet application. +- [Parsing inputs](./parsing-inputs.md): decode deposits, user requests and inspect queries. +- [Managing balances](./managing-balances.md): record and update asset ownership with the ledger. +- [Vouchers and withdrawals](./vouchers.md): send assets back to the base layer. +- [Types and selectors](./types-and-selectors.md): every enum, struct, function selector and error code. diff --git a/cartesi-rollups_versioned_docs/version-2.0/api-reference/asset-management/parser-reference.md b/cartesi-rollups_versioned_docs/version-2.0/api-reference/asset-management/parser-reference.md new file mode 100644 index 00000000..306bdd69 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/api-reference/asset-management/parser-reference.md @@ -0,0 +1,183 @@ +--- +id: parser-reference +title: Parser reference +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +This page lists every parser function with its exact signature per language. The enums, structs and error codes they use are defined in [Types and selectors](./types-and-selectors.md). + +## Decode advance + +Decodes an advance input into a typed result. Pass the deposit type when the sender is a portal, or the automatic type to detect withdrawals and transfers from the payload. + +### Signature + + + + +```python +# One function per deposit type. Each takes the dict returned by +# read_advance_state() and returns a dict of decoded fields. +decode_ether_deposit(input: dict) -> dict +decode_erc20_deposit(input: dict) -> dict +decode_erc721_deposit(input: dict) -> dict +decode_erc1155_single_deposit(input: dict) -> dict +decode_erc1155_batch_deposit(input: dict) -> dict + +# Automatic detection for withdrawals and transfers. +# Returns a dict with a "type" key naming the operation. +decode_advance(input: dict) -> dict +``` + + + + +```rust +pub fn cma_decode_advance( + req_type: CmaParserInputType, + input: JsonValue, +) -> Result +``` + + + + +```cpp +int cma_parser_decode_advance( + cma_parser_input_type_t type, + const cmt_rollup_advance_t *input, + cma_parser_input_t *parser_input); +``` + + + + +### Parameters + +- **type / req_type**: the input type to decode. Use a specific deposit type when you matched the sender to a portal. Use the automatic type (`CMA_PARSER_INPUT_TYPE_AUTO` in C and C++, `CmaParserInputTypeAuto` in Rust) for everything else. The Python deposit functions carry the type in their name, and `decode_advance` always runs in automatic mode. +- **input**: the advance request. + - Python: the dictionary returned by `read_advance_state()`, containing `msg_sender` and `payload`. + - Rust: a JSON object. The parser reads the payload from `data.payload` as a hex string and, in automatic mode, the sender from `data.metadata.msg_sender`. + - C and C++: the `cmt_rollup_advance_t` struct filled by `cmt_rollup_read_advance_state`. +- **parser_input** (C and C++ only): output struct the parser fills. + +### Returns + +- Python: a dictionary of decoded fields. `decode_advance` adds a `type` key, for example `ERC20_WITHDRAWAL`. +- Rust: a `CmaParserInput` with `req_type` (the detected type) and `input` (a `CmaParserInputData` variant holding the fields). +- C and C++: `0` on success, a negative error code on failure. The decoded data lands in `parser_input`, with `parser_input.type` naming the variant. + +### Errors + +Fails when the payload is shorter than the expected layout, the hex is invalid, or the input matches no supported operation. See [error codes](./types-and-selectors.md#error-codes). + +## Decode inspect + +Decodes a balance or total supply query from an inspect request. The payload is a JSON document with `method` and `params` fields, described in [Parsing inputs](./parsing-inputs.md#decoding-inspect-queries). + +### Signature + + + + +```python +decode_inspect(input: dict) -> dict +``` + + + + +```rust +pub fn cma_decode_inspect( + input: JsonValue, +) -> Result +``` + + + + +```cpp +int cma_parser_decode_inspect( + cma_parser_input_type_t type, + const cmt_rollup_inspect_t *input, + cma_parser_input_t *parser_input); +``` + + + + +### Parameters + +- **input**: the inspect request. Python takes the dict from `read_inspect_state()`. Rust takes a JSON object and reads the hex payload from `data.payload`. C and C++ take the `cmt_rollup_inspect_t` struct. +- **type** (C and C++ only): pass `CMA_PARSER_INPUT_TYPE_AUTO` to accept both query methods, or a specific balance or supply type to accept only one. + +### Returns + +- Python: a dict with `type` set to `BALANCE` or `SUPPLY`, plus `account`, `token` and `token_id` fields. Fields not present in the query are `None`. +- Rust: a `CmaParserInput` whose `req_type` is the balance or supply type and whose data variant holds the query fields. +- C and C++: `0` on success with the result in `parser_input`, or a negative error code. + +### Errors + +Fails when the payload is not valid JSON, the `method` key is missing or unknown, or a parameter has the wrong format. + +## Encode voucher + +Builds the destination and payload of a voucher that returns an asset to the base layer. + +### Signature + + + + +```python +# Methods of RollupCma. Each encodes the voucher and emits it +# to the machine in one step. +emit_ether_voucher(receiver: str, amount: int) +emit_erc20_voucher(token: str, receiver: str, amount: int) +emit_erc721_voucher(token: str, receiver: str, token_id: int) +emit_erc1155_single_voucher(token: str, receiver: str, token_id: int, amount: int) +emit_erc1155_batch_voucher(token: str, receiver: str, token_ids: list, amounts: list) +``` + + + + +```rust +pub fn cma_encode_voucher( + req_type: CmaParserVoucherType, + voucher_request: CmaVoucherFieldType, +) -> Result +``` + + + + +```cpp +int cma_parser_encode_voucher( + cma_parser_voucher_type_t type, + const cma_abi_address_t *app_address, + const cma_parser_voucher_data_t *voucher_request, + cma_voucher_t *voucher); +``` + + + + +### Parameters + +- **type / req_type**: the voucher type, one entry per asset standard. See [voucher types](./types-and-selectors.md#voucher-fields). +- **voucher_request**: the fields for that voucher type, such as token address, receiver and amount. +- **app_address** (C and C++ only): your application's address on the base layer, available in the advance request metadata. The Python binding captures it for you from `read_advance_state()`. The Rust binding takes it as a field of the ERC721 voucher request. + +### Returns + +- Python: the emit methods send the voucher directly and return the emit result. +- Rust: a `CmaVoucher` with `destination` and `payload` strings, ready to emit through your rollup I/O layer. +- C and C++: `0` on success with the result in `voucher`, holding the destination address and the call data. + +### Errors + +Fails when the request fields do not match the voucher type. In the Rust binding, ERC1155 single and batch voucher types are not implemented yet and return an error. diff --git a/cartesi-rollups_versioned_docs/version-2.0/api-reference/asset-management/parsing-inputs.md b/cartesi-rollups_versioned_docs/version-2.0/api-reference/asset-management/parsing-inputs.md new file mode 100644 index 00000000..5a76c6d2 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/api-reference/asset-management/parsing-inputs.md @@ -0,0 +1,268 @@ +--- +id: parsing-inputs +title: Parsing inputs +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Every input reaches your application as raw bytes. The CMA parser understands and processes three kinds of inputs and turns each one into typed values: + +1. **Deposits** sent by the Cartesi portal contracts. +2. **Application calls** such as withdrawals and transfers, sent by users as ABI encoded payloads. +3. **Inspect queries** for balances and total supply, sent as JSON. + +## Know who sent the input + +The parser does not guess where an input came from. Your application checks the `msg_sender` of each advance request first: + +- If the sender is a portal contract, the input is a deposit. Pass the matching deposit type to the parser. +- If the sender is anything else, treat it as an application call and let the parser detect the operation from the payload itself. + +:::caution +Portal addresses change between Rollups versions. Always confirm the addresses for the version you deploy against. +::: + +## Decoding deposits + +Each portal encodes its deposits in a fixed ABI layout. The parser knows these layouts, so one call gives you all the deposit fields. + + + + +`pycma` has one decode function per deposit type. Each takes the advance request returned by `read_advance_state()` and returns a plain dictionary: + +```python +from pycma import ( + decode_ether_deposit, + decode_erc20_deposit, + decode_erc721_deposit, + decode_erc1155_single_deposit, + decode_erc1155_batch_deposit, +) + +advance = rollup.read_advance_state() +msg_sender = advance["msg_sender"].hex().lower() + +if msg_sender == ERC20_PORTAL_ADDRESS: + deposit = decode_erc20_deposit(advance) + # deposit["sender"] -> depositor address + # deposit["token"] -> token contract address + # deposit["amount"] -> amount as an int +``` + + + + +`cma_decode_advance` takes the input type and a JSON object. The parser reads the payload from `data.payload` and, for application calls, the sender from `data.metadata.msg_sender`: + +```rust +use libcma_binding_rust::parser::{cma_decode_advance, CmaParserInputType, CmaParserInputData}; +use json::object; + +let request = object! { + data: { + metadata: { msg_sender: msg_sender }, + payload: payload_hex + } +}; + +let decoded = cma_decode_advance( + CmaParserInputType::CmaParserInputTypeErc20Deposit, + request, +)?; + +if let CmaParserInputData::Erc20Deposit(deposit) = decoded.input { + // deposit.sender, deposit.token, deposit.amount, deposit.exec_layer_data +} +``` + + + + +`cma_parser_decode_advance` takes the input type, the advance struct from libcmt and an output struct: + +```cpp +// input is a cmt_rollup_advance_t filled by cmt_rollup_read_advance_state(rollup, &input) +cma_parser_input_t parser_input; + +const int err = cma_parser_decode_advance(CMA_PARSER_INPUT_TYPE_ERC20_DEPOSIT, &input, &parser_input); +if (err < 0) { + printf("unable to decode erc20 deposit: %d - %s\n", -err, cma_parser_get_last_error_message()); +} + +parser_input.erc20_deposit.sender; // sender of the deposit +parser_input.erc20_deposit.token; // token address +parser_input.erc20_deposit.amount; // amount +parser_input.erc20_deposit.exec_layer_data; // extra data sent by the depositor +``` + + + + +The full list of deposit result fields per asset is in [Types and selectors](./types-and-selectors.md#parser-results). + +## Decoding application calls + +Withdrawals and transfers are actions application users call directly. For the parser to decode them, the input must arrive as an ABI encoded call that matches one of the function selectors the parser knows. The first four bytes of the payload identify the operation, and the rest carries the arguments. The complete selector table is in [Types and selectors](./types-and-selectors.md#application-call-selectors). + +When the sender is not a portal, the parser is tasked with detecting the operation automatically: + + + + +`decode_advance` detects the operation from the payload and returns a dictionary with a `type` key: + +```python +from pycma import decode_advance + +decoded = decode_advance(advance) + +if decoded["type"] == "ERC20_WITHDRAWAL": + # decoded["token"], decoded["amount"], decoded["exec_layer_data"] + ... +elif decoded["type"] == "ERC20_TRANSFER": + # decoded["receiver"], decoded["token"], decoded["amount"] + ... +``` + +The Python binding decodes all ten operations: Ether, ERC20, ERC721, ERC1155 single and ERC1155 batch, each as withdrawal and as transfer. It raises an exception when the payload does not match any known selector. + + + + +Pass `CmaParserInputTypeAuto` and match on the result type: + +```rust +let decoded = cma_decode_advance(CmaParserInputType::CmaParserInputTypeAuto, request)?; + +match decoded.req_type { + CmaParserInputType::CmaParserInputTypeErc20Withdrawal => { + if let CmaParserInputData::Erc20Withdrawal(w) = decoded.input { + // w.receiver, w.token, w.amount + } + } + CmaParserInputType::CmaParserInputTypeUnidentified => { + // Not a CMA operation. The raw bytes and sender are returned + // so your own logic can handle the input. + } + _ => {} +} +``` + +:::note current coverage in the Rust binding +In automatic mode the Rust binding currently decodes Ether, ERC20, ERC721 and ERC1155 withdrawals and transfers. Inputs that match no selector come back as `Unidentified` with the raw bytes, so your application can process its own custom input formats. +::: + + + + +Pass `CMA_PARSER_INPUT_TYPE_AUTO` and check the resulting type: + +```cpp +cma_parser_input_t parser_input; + +const int err = cma_parser_decode_advance(CMA_PARSER_INPUT_TYPE_AUTO, &input, &parser_input); +if (err < 0) { + printf("unable to decode input: %d - %s\n", -err, cma_parser_get_last_error_message()); +} + +switch (parser_input.type) { +case CMA_PARSER_INPUT_TYPE_ERC20_WITHDRAWAL: + // parser_input.erc20_withdrawal.token, .amount, .exec_layer_data + break; +case CMA_PARSER_INPUT_TYPE_ERC20_TRANSFER: + // parser_input.erc20_transfer.receiver, .token, .amount + break; +default: + break; +} +``` + + + + +## Decoding inspect queries + +Balance and supply queries arrive as inspect requests. The payload is a small JSON document with a `method` name and a `params` array. + + + + +The C core and the Python binding accept the methods `ledger_getBalance` and `ledger_getTotalSupply`: + +```json +{ + "method": "ledger_getBalance", + "params": ["0x0000000000000000000000000000000000000001"] +} +``` + +`params[0]` is the account. You can add a token address as `params[1]` and a token ID as `params[2]` to query a specific asset: + +```python +from pycma import decode_inspect + +inspect = rollup.read_inspect_state() +query = decode_inspect(inspect) + +if query["type"] == "BALANCE": + # query["account"], query["token"], query["token_id"] + ... +elif query["type"] == "SUPPLY": + # query["token"], query["token_id"] + ... +``` + + + + +The Rust binding accepts the methods `ledgerGetBalance` and `ledgerGetTotalSupply`. The inspect payload is the hex encoding of the JSON document: + +```rust +use libcma_binding_rust::parser::{cma_decode_inspect, CmaParserInputType, CmaParserInputData}; + +let decoded = cma_decode_inspect(request)?; + +match decoded.req_type { + CmaParserInputType::CmaParserInputTypeBalance => { + if let CmaParserInputData::Balance(q) = decoded.input { + // q.account, q.token, q.token_ids + } + } + CmaParserInputType::CmaParserInputTypeSupply => { + if let CmaParserInputData::Supply(q) = decoded.input { + // q.token, q.token_ids + } + } + _ => {} +} +``` + + + + +`cma_parser_decode_inspect` works like the advance variant. Use `CMA_PARSER_INPUT_TYPE_AUTO` to accept both methods: + +```cpp +cma_parser_input_t parser_input; + +const int err = cma_parser_decode_inspect(CMA_PARSER_INPUT_TYPE_AUTO, &input, &parser_input); +if (err < 0) { + printf("unable to decode inspect: %d - %s\n", -err, cma_parser_get_last_error_message()); +} +// parser_input.type tells you whether it is a balance or a supply query +``` + + + + +## Handling parser errors + +Every decode call can fail, for example when a payload is shorter than expected. Always check the result before you touch the ledger: + +- Python raises an exception with the error code and message. +- Rust returns a `Result` with a `CmaParserError`. +- C and C++ return a negative error code, and `cma_parser_get_last_error_message()` gives the matching text. + +The complete error lists are in [Types and selectors](./types-and-selectors.md#error-codes). diff --git a/cartesi-rollups_versioned_docs/version-2.0/api-reference/asset-management/types-and-selectors.md b/cartesi-rollups_versioned_docs/version-2.0/api-reference/asset-management/types-and-selectors.md new file mode 100644 index 00000000..7606ab66 --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/api-reference/asset-management/types-and-selectors.md @@ -0,0 +1,227 @@ +--- +id: types-and-selectors +title: Types and selectors +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +This page is the lookup table for the CMA library: input types, decoded fields, function selectors, voucher fields, portal addresses and error codes. The values below come from the library sources: [machine-asset-tools](https://github.com/Mugen-Builders/machine-asset-tools) for C and C++ and Python, and [libcma_binding_rust](https://github.com/Mugen-Builders/libcma_binding_rust) for Rust. + +## Input types + +The parser identifies every input with one of these types. + + + + +```cpp +typedef enum { + CMA_PARSER_INPUT_TYPE_NONE, + CMA_PARSER_INPUT_TYPE_AUTO, + CMA_PARSER_INPUT_TYPE_ETHER_DEPOSIT, + CMA_PARSER_INPUT_TYPE_ERC20_DEPOSIT, + CMA_PARSER_INPUT_TYPE_ERC721_DEPOSIT, + CMA_PARSER_INPUT_TYPE_ERC1155_SINGLE_DEPOSIT, + CMA_PARSER_INPUT_TYPE_ERC1155_BATCH_DEPOSIT, + CMA_PARSER_INPUT_TYPE_ETHER_WITHDRAWAL, + CMA_PARSER_INPUT_TYPE_ERC20_WITHDRAWAL, + CMA_PARSER_INPUT_TYPE_ERC721_WITHDRAWAL, + CMA_PARSER_INPUT_TYPE_ERC1155_SINGLE_WITHDRAWAL, + CMA_PARSER_INPUT_TYPE_ERC1155_BATCH_WITHDRAWAL, + CMA_PARSER_INPUT_TYPE_ETHER_TRANSFER, + CMA_PARSER_INPUT_TYPE_ERC20_TRANSFER, + CMA_PARSER_INPUT_TYPE_ERC721_TRANSFER, + CMA_PARSER_INPUT_TYPE_ERC1155_SINGLE_TRANSFER, + CMA_PARSER_INPUT_TYPE_ERC1155_BATCH_TRANSFER, + CMA_PARSER_INPUT_TYPE_BALANCE, + CMA_PARSER_INPUT_TYPE_BALANCE_ACCOUNT, + CMA_PARSER_INPUT_TYPE_BALANCE_ACCOUNT_TOKEN_ADDRESS, + CMA_PARSER_INPUT_TYPE_BALANCE_ACCOUNT_TOKEN_ADDRESS_ID, + CMA_PARSER_INPUT_TYPE_SUPPLY, + CMA_PARSER_INPUT_TYPE_SUPPLY_TOKEN_ADDRESS, + CMA_PARSER_INPUT_TYPE_SUPPLY_TOKEN_ADDRESS_ID, +} cma_parser_input_type_t; +``` + +The Python binding uses these same values internally and translates them to the `type` strings returned by `decode_advance` and `decode_inspect`, such as `ERC20_WITHDRAWAL` and `BALANCE`. + + + + +```rust +pub enum CmaParserInputType { + CmaParserInputTypeNone, + CmaParserInputTypeAuto, + CmaParserInputTypeUnidentified, + CmaParserInputTypeEtherDeposit, + CmaParserInputTypeErc20Deposit, + CmaParserInputTypeErc721Deposit, + CmaParserInputTypeErc1155SingleDeposit, + CmaParserInputTypeErc1155BatchDeposit, + CmaParserInputTypeEtherWithdrawal, + CmaParserInputTypeErc20Withdrawal, + CmaParserInputTypeErc721Withdrawal, + CmaParserInputTypeErc1155SingleWithdrawal, + CmaParserInputTypeErc1155BatchWithdrawal, + CmaParserInputTypeEtherTransfer, + CmaParserInputTypeErc20Transfer, + CmaParserInputTypeErc721Transfer, + CmaParserInputTypeErc1155SingleTransfer, + CmaParserInputTypeErc1155BatchTransfer, + CmaParserInputTypeBalance, + CmaParserInputTypeSupply, +} +``` + + + + +## Parser results + +The fields the parser returns for each operation. Names match the Rust struct fields and the Python dictionary keys. + +### Deposits + +| Operation | Fields | +| :--------------------- | :------------------------------------------------------------------------------ | +| Ether deposit | `sender`, `amount`, `exec_layer_data` | +| ERC20 deposit | `sender`, `token`, `amount`, `exec_layer_data` | +| ERC721 deposit | `sender`, `token`, `token_id`, `exec_layer_data` | +| ERC1155 single deposit | `sender`, `token`, `token_id`, `amount`, `exec_layer_data` | +| ERC1155 batch deposit | `sender`, `token`, `token_ids`, `amounts`, `base_layer_data`, `exec_layer_data` | + +### Withdrawals + +The account that withdraws is the `msg_sender` of the input. + +| Operation | Fields | +| :------------------------ | :------------------------------------------------- | +| Ether withdrawal | `amount`, `exec_layer_data` | +| ERC20 withdrawal | `token`, `amount`, `exec_layer_data` | +| ERC721 withdrawal | `token`, `token_id`, `exec_layer_data` | +| ERC1155 single withdrawal | `token`, `token_id`, `amount`, `exec_layer_data` | +| ERC1155 batch withdrawal | `token`, `token_ids`, `amounts`, `exec_layer_data` | + +### Transfers + +Transfers move assets between accounts inside the application. The `receiver` is a 32 byte account identifier. + +| Operation | Fields | +| :---------------------- | :------------------------------------------------------------- | +| Ether transfer | `receiver`, `amount`, `exec_layer_data` | +| ERC20 transfer | `receiver`, `token`, `amount`, `exec_layer_data` | +| ERC721 transfer | `receiver`, `token`, `token_id`, `exec_layer_data` | +| ERC1155 single transfer | `receiver`, `token`, `token_id`, `amount`, `exec_layer_data` | +| ERC1155 batch transfer | `receiver`, `token`, `token_ids`, `amounts`, `exec_layer_data` | + +### Inspect queries + +| Query | Fields | +| :------ | :---------------------------------------------- | +| Balance | `account`, plus optional `token` and `token_id` | +| Supply | optional `token` and `token_id` | + +## Application call selectors + +To request a withdrawal or transfer, the user sends an ABI encoded call as the input payload. The first four bytes select the operation. These are the selectors the C core and the Python binding decode: + +| Function | Selector | Arguments | +| :---------------------------------------------------------------- | :----------- | :--------------------------------------------------- | +| `WithdrawEther(uint256,bytes)` | `0x8cf70f0b` | amount, exec layer data | +| `WithdrawErc20(address,uint256,bytes)` | `0x4f94d342` | token, amount, exec layer data | +| `WithdrawErc721(address,uint256,bytes)` | `0x33acf293` | token, token ID, exec layer data | +| `WithdrawErc1155Single(address,uint256,uint256,bytes)` | `0x8bb0a811` | token, token ID, amount, exec layer data | +| `WithdrawErc1155Batch(address,uint256[],uint256[],bytes)` | `0x50c80019` | token, token IDs, amounts, exec layer data | +| `TransferEther(bytes32,uint256,bytes)` | `0xff67c903` | receiver, amount, exec layer data | +| `TransferErc20(address,bytes32,uint256,bytes)` | `0x03d61dcd` | token, receiver, amount, exec layer data | +| `TransferErc721(address,bytes32,uint256,bytes)` | `0xaf615a5a` | token, receiver, token ID, exec layer data | +| `TransferErc1155Single(address,bytes32,uint256,uint256,bytes)` | `0xe1c913ed` | token, receiver, token ID, amount, exec layer data | +| `TransferErc1155Batch(address,bytes32,uint256[],uint256[],bytes)` | `0x638ac6f9` | token, receiver, token IDs, amounts, exec layer data | + +## Inspect methods + +Inspect payloads are JSON documents with a `method` and a `params` array. + + + + +Methods: `ledger_getBalance` and `ledger_getTotalSupply`. + +```json +{ + "method": "ledger_getBalance", + "params": ["0x0000000000000000000000000000000000000001"] +} +``` + +For balance queries, `params` holds the account, then an optional token address and an optional token ID. For supply queries, `params` holds an optional token address and an optional token ID. + + + + +Methods: `ledgerGetBalance` and `ledgerGetTotalSupply`. The payload is the hex encoding of the JSON document, and `params` holds the account, the token address and an optional array of token IDs. + + + + +## Voucher fields + +Each voucher type takes these fields. In C and C++ the receiver lives in the outer `cma_parser_voucher_data_t` struct, and in Rust it is a field of each struct. + +| Voucher type | Fields | Voucher destination | +| :------------- | :----------------------------------------------------------- | :------------------- | +| Ether | `receiver`, `amount` | The receiver address | +| ERC20 | `token`, `receiver`, `amount` | The token contract | +| ERC721 | `token`, `token_id`, `receiver`, and the application address | The token contract | +| ERC1155 single | `token`, `token_id`, `receiver`, `amount` | The token contract | +| ERC1155 batch | `token`, `receiver`, `token_ids`, `amounts` | The token contract | + +## Ledger types + +| Concept | C and C++ | Rust | +| :----------------- | :------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------- | +| Retrieve operation | `CMA_LEDGER_OP_FIND`, `CMA_LEDGER_OP_CREATE`, `CMA_LEDGER_OP_FIND_OR_CREATE`, `CMA_LEDGER_OP_FIND_AND_REMOVE` | `RetrieveOperation::Find`, `Create`, `FindOrCreate` | +| Asset type | `CMA_LEDGER_ASSET_TYPE_ID`, `BASE`, `TOKEN_ADDRESS`, `TOKEN_ADDRESS_ID`, `TOKEN_ADDRESS_ID_AMOUNT` | `AssetType::Id`, `TokenAddress`, `TokenAddressId` | +| Account type | `CMA_LEDGER_ACCOUNT_TYPE_ID`, `WALLET_ADDRESS`, `ACCOUNT_ID` | `AccountType::Id`, `WalletAddress`, `AccountId` | +| Asset ID | `cma_ledger_asset_id_t` | `LedgerAssetId(u64)` | +| Account ID | `cma_ledger_account_id_t` | `LedgerAccountId(u64)` | + +The Python `Ledger` methods select these types for you from the arguments you pass, as described in the [Ledger reference](./ledger-reference.md). + +## Error codes + +### Parser errors + +| C and C++ | Code | Rust | +| :------------------------------------ | :------ | :---------------------------------- | +| `CMA_PARSER_SUCCESS` | `0` | `Ok(...)` | +| `CMA_PARSER_ERROR_UNKNOWN` | `-2001` | `CmaParserError::Unknown` | +| `CMA_PARSER_ERROR_EXCEPTION` | `-2002` | | +| `CMA_PARSER_ERROR_INCOMPATIBLE_INPUT` | `-2003` | `CmaParserError::IncompatibleInput` | +| `CMA_PARSER_ERROR_MALFORMED_INPUT` | `-2004` | `CmaParserError::MalformedInput` | +| `CMA_PARSER_ERROR_INVALID_AMOUNT` | `-2005` | | + +The Rust binding also returns `CmaParserError::Message(String)` with a description for input validation failures. Python raises an exception that carries the code and the message from `cma_parser_get_last_error_message()`. + +### Ledger errors + +| C and C++ | Code | Rust | +| :-------------------------------------- | :------ | :------------------------------- | +| `CMA_LEDGER_SUCCESS` | `0` | `Ok(...)` | +| `CMA_LEDGER_ERROR_UNKNOWN` | `-1001` | `LedgerError::Unknown` | +| `CMA_LEDGER_ERROR_EXCEPTION` | `-1002` | `LedgerError::Exception` | +| `CMA_LEDGER_ERROR_INSUFFICIENT_FUNDS` | `-1003` | `LedgerError::InsufficientFunds` | +| `CMA_LEDGER_ERROR_ACCOUNT_NOT_FOUND` | `-1004` | `LedgerError::AccountNotFound` | +| `CMA_LEDGER_ERROR_ASSET_NOT_FOUND` | `-1005` | `LedgerError::AssetNotFound` | +| `CMA_LEDGER_ERROR_BALANCE_NOT_FOUND` | `-1006` | `LedgerError::Other(code)` | +| `CMA_LEDGER_ERROR_SUPPLY_OVERFLOW` | `-1007` | `LedgerError::SupplyOverflow` | +| `CMA_LEDGER_ERROR_BALANCE_OVERFLOW` | `-1008` | `LedgerError::BalanceOverflow` | +| `CMA_LEDGER_ERROR_INVALID_ACCOUNT` | `-1009` | `LedgerError::InvalidAccount` | +| `CMA_LEDGER_ERROR_INSERTION_ERROR` | `-1010` | `LedgerError::InsertionError` | +| `CMA_LEDGER_ERROR_MAX_ASSETS_REACHED` | `-1011` | `LedgerError::Other(code)` | +| `CMA_LEDGER_ERROR_MAX_ACCOUNTS_REACHED` | `-1012` | `LedgerError::Other(code)` | +| `CMA_LEDGER_ERROR_MAX_BALANCES_REACHED` | `-1013` | `LedgerError::Other(code)` | +| `CMA_LEDGER_ERROR_ASSET_SUPPLY` | `-1014` | `LedgerError::Other(code)` | +| `CMA_LEDGER_ERROR_ACCOUNT_BALANCE` | `-1015` | `LedgerError::Other(code)` | +| `CMA_LEDGER_ERROR_REMOVE` | `-1016` | `LedgerError::Other(code)` | diff --git a/cartesi-rollups_versioned_docs/version-2.0/api-reference/asset-management/vouchers.md b/cartesi-rollups_versioned_docs/version-2.0/api-reference/asset-management/vouchers.md new file mode 100644 index 00000000..e0d3d1ab --- /dev/null +++ b/cartesi-rollups_versioned_docs/version-2.0/api-reference/asset-management/vouchers.md @@ -0,0 +1,132 @@ +--- +id: vouchers +title: Vouchers and withdrawals +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Users deposit assets into your application, but only your application can send them back. It does this by emitting a [voucher](../backend/vouchers.md), a message that executes a call on the base layer once the epoch settles. CMA builds these vouchers for you with the correct destination and payload for each asset type. + +## The withdrawal flow + +A complete withdrawal has three steps, and you have already seen the first two in the previous guides: + +1. **Decode the request**. The user sends an ABI encoded withdrawal input. The parser identifies it and returns the typed fields. See [Parsing inputs](./parsing-inputs.md). +2. **Debit the ledger**. Call `withdraw` so the user's balance drops inside your application. If the user lacks funds, this call fails and you stop here. See [Managing balances](./managing-balances.md). +3. **Emit the voucher**. Build and emit the voucher that releases the asset on the base layer. + +Running the ledger debit before emitting the voucher matters. It guarantees a user can never withdraw more than they own. + +## Emitting vouchers + + + + +`RollupCma` has one emit method per asset type. Each builds the voucher and sends it to the machine in a single call. The application address needed for the voucher is captured automatically when you call `read_advance_state()`: + +```python +# Ether +rollup.emit_ether_voucher(receiver, amount) + +# ERC20 +rollup.emit_erc20_voucher(token, receiver, amount) + +# ERC721 +rollup.emit_erc721_voucher(token, receiver, token_id) + +# ERC1155 +rollup.emit_erc1155_single_voucher(token, receiver, token_id, amount) +rollup.emit_erc1155_batch_voucher(token, receiver, token_ids, amounts) +``` + +A full withdrawal handler, from the [wallet sample](https://github.com/Mugen-Builders/libcma-binding-python/blob/main/sample_apps/wallet_app/app.py): + +```python +decoded = decode_advance(advance) + +if decoded["type"] == "ERC20_WITHDRAWAL": + asset = ledger.retrieve_asset(token=decoded["token"]) + account = ledger.retrieve_account(account=msg_sender) + + ledger.withdraw(asset["asset_id"], account["account_id"], decoded["amount"]) + + rollup.emit_erc20_voucher(asset["token"], msg_sender, decoded["amount"]) +``` + + + + +`cma_encode_voucher` builds the voucher fields. You then emit the result with your rollup I/O layer, for example [libcmt-binding-rust](https://github.com/Mugen-Builders/libcmt-binding-rust): + +```rust +use libcma_binding_rust::parser::{ + cma_encode_voucher, CmaParserVoucherType, CmaVoucherFieldType, CmaParserErc20VoucherFields, +}; + +let voucher = cma_encode_voucher( + CmaParserVoucherType::CmaParserVoucherTypeErc20, + CmaVoucherFieldType::Erc20VoucherFields(CmaParserErc20VoucherFields { + token, + receiver, + amount, + }), +)?; + +// voucher.destination -> address the voucher calls +// voucher.payload -> hex encoded call data +``` + +ERC721 vouchers also need your application address, because the token contract transfers the token out of the application's custody: + +```rust +use libcma_binding_rust::parser::CmaParserErc721VoucherFields; + +let voucher = cma_encode_voucher( + CmaParserVoucherType::CmaParserVoucherTypeErc721, + CmaVoucherFieldType::Erc721VoucherFields(CmaParserErc721VoucherFields { + token, + token_id, + receiver, + application_address, + }), +)?; +``` + + + + +`cma_parser_encode_voucher` fills a voucher struct that you pass to `cmt_rollup_emit_voucher`: + +```cpp +cma_parser_voucher_data_t voucher_request; +// fill voucher_request.receiver and the union member +// that matches the asset type, for example +// voucher_request.u.erc20_voucher_fields + +cma_voucher_t voucher; +const int err = cma_parser_encode_voucher(CMA_PARSER_VOUCHER_TYPE_ERC20, &app_address, &voucher_request, &voucher); +if (err < 0) { + printf("unable to encode voucher: %d - %s\n", -err, cma_parser_get_last_error_message()); +} +``` + +The `app_address` comes from the advance request metadata of any input your application has received. + + + + +## What each voucher does on the base layer + +| Asset | Voucher destination | Effect when executed | +| :------ | :------------------- | :--------------------------------------------------------------------- | +| Ether | The receiver address | Sends the Ether amount to the receiver | +| ERC20 | The token contract | Calls the token's transfer function to pay the receiver | +| ERC721 | The token contract | Transfers the token ID from the application to the receiver | +| ERC1155 | The token contract | Transfers the token ID and amount, or batches of them, to the receiver | + +After the epoch that contains the input settles, anyone can execute the voucher on the base layer through the application contract. See [Asset handling](../../development/asset-handling.md) for the full lifecycle. + +## Voucher field structs + +The exact fields each voucher type needs are listed in [Types and selectors](./types-and-selectors.md#voucher-fields). diff --git a/cartesi-rollups_versioned_docs/version-2.0/development/asset-handling.md b/cartesi-rollups_versioned_docs/version-2.0/development/asset-handling.md index 4bbf9ff2..b7992bcf 100644 --- a/cartesi-rollups_versioned_docs/version-2.0/development/asset-handling.md +++ b/cartesi-rollups_versioned_docs/version-2.0/development/asset-handling.md @@ -101,6 +101,20 @@ import AssetWithdrawEtherCPP from './snippets/asset_withdraw_ether_cpp.md'; For a full guide, see the Tutorials: [ERC-20 Token Wallet](../tutorials/erc-20-token-wallet.md), [ERC-1155 Token Wallet](../tutorials/erc-1155-token-wallet.md), and [Utilizing test tokens in dev environment](../tutorials/utilizing-the-cli-test-tokens.md). +## Decoding Deposits + +Deposit input payloads arrives applications as an abi-encoded hex, this hex input would need to be decoded to obtain the contained deposit parameters. This decode process could either be handled manually by the developers or could be automated using the assets' manager library. + +The **parser submodule** of the asset manager library simplifies the decode process for deposit payloads by abstracting an already implemented logic to decode the deposit payload for all major assets (ERC721, ERC20, ERC721), it exposes a method called decode input, which the application logic transfers the deposit payload to then receives a struct containing all the parameters contained in the deposit payload. A more detailed guide on the parser library can be found in this [Asset Management Library (CMA)](../api-reference/asset-management/overview.md) + +## Managing and Recording Deposits + +Once a deposit occurs on the base layer, the application receives all necessary details pertaining to the deposit. It's important that the application maintain a record of an address to a token and finally the balance of the address for that token, without a proper record, the application is unable to tell the exact token and also the amount of token that each address owns on the dApp. + +This record can either be implemented manually by the developer or the application could simply import the Asset manager library then leverage the **ledger submodule** to manage and keep this record. + +The ledger submodule can be likened to a plug in wallet for applications, it contains a storage struct which tracks the assets balance for each address as well as the total supply for each token deposited to the wallet, finally it exposes methods to handle deposit, transfer and withdrawal of these assets in the record. For a detailed explanation of the ledger library, check this [Asset Management Library (CMA)](../api-reference/asset-management/overview.md) + ## Withdrawing assets Users can deposit assets to a Cartesi Application, but only the Application can initiate withdrawals. When a withdrawal request is made, it’s processed and interpreted off-chain by the Cartesi Machine running the application’s code. Subsequently, the Cartesi Machine creates a voucher containing the necessary instructions for withdrawal, which is executable when an epoch has settled. @@ -206,7 +220,7 @@ Here are the function signatures used by vouchers to withdraw the different type | Asset | Destination | Function signature | | :------- | :------------- | :------------------------------------------------------------------------------------------------------------------------------------------ | -| Ether | dApp contract | `withdrawEther(address,uint256)` [:page_facing_up:](../api-reference/json-rpc/application.md/#withdrawether) | +| Ether | dApp contract | `withdrawEther(address,uint256)` [:page_facing_up:](../api-reference/json-rpc/application.md/#withdrawether) | | ERC-20 | Token contract | `transfer(address,uint256)` [:page_facing_up:](https://eips.ethereum.org/EIPS/eip-20#methods) | | ERC-20 | Token contract | `transferFrom(address,address,uint256)` [:page_facing_up:](https://eips.ethereum.org/EIPS/eip-20#methods) | | ERC-721 | Token contract | `safeTransferFrom(address,address,uint256)` [:page_facing_up:](https://eips.ethereum.org/EIPS/eip-721#specification) | diff --git a/cartesi-rollups_versioned_sidebars/version-2.0-sidebars.json b/cartesi-rollups_versioned_sidebars/version-2.0-sidebars.json index e0da3dff..a5ccd35a 100644 --- a/cartesi-rollups_versioned_sidebars/version-2.0-sidebars.json +++ b/cartesi-rollups_versioned_sidebars/version-2.0-sidebars.json @@ -84,6 +84,21 @@ "api-reference/backend/finish" ] }, + { + "type": "category", + "label": "Asset Management Library", + "collapsed": true, + "items": [ + "api-reference/asset-management/overview", + "api-reference/asset-management/getting-started", + "api-reference/asset-management/parsing-inputs", + "api-reference/asset-management/managing-balances", + "api-reference/asset-management/vouchers", + "api-reference/asset-management/parser-reference", + "api-reference/asset-management/ledger-reference", + "api-reference/asset-management/types-and-selectors" + ] + }, { "type": "category", "label": "JSON-RPC API", diff --git a/static/llms.txt b/static/llms.txt index 1342fd35..fa325d7e 100644 --- a/static/llms.txt +++ b/static/llms.txt @@ -145,6 +145,17 @@ If a user's code or question references the old GraphQL API, `sunodo`, or v1.x C - [Exception](https://docs.cartesi.io/cartesi-rollups/2.0/api-reference/backend/exception.md): Register exceptions - [Finish](https://docs.cartesi.io/cartesi-rollups/2.0/api-reference/backend/finish.md): Complete request processing +## Cartesi Rollups v2.0 — Asset Management Library (CMA) + +- [Overview](https://docs.cartesi.io/cartesi-rollups/2.0/api-reference/asset-management/overview.md): What the CMA library is and how its parser and ledger work together +- [Getting started](https://docs.cartesi.io/cartesi-rollups/2.0/api-reference/asset-management/getting-started.md): Install CMA in Python, Rust or C++ and build a first wallet +- [Parsing inputs](https://docs.cartesi.io/cartesi-rollups/2.0/api-reference/asset-management/parsing-inputs.md): Decode deposits, withdrawals, transfers and inspect queries +- [Managing balances](https://docs.cartesi.io/cartesi-rollups/2.0/api-reference/asset-management/managing-balances.md): Track accounts, assets and balances with the ledger +- [Vouchers and withdrawals](https://docs.cartesi.io/cartesi-rollups/2.0/api-reference/asset-management/vouchers.md): Build vouchers that return assets to the base layer +- [Parser reference](https://docs.cartesi.io/cartesi-rollups/2.0/api-reference/asset-management/parser-reference.md): Signatures for decode advance, decode inspect and encode voucher +- [Ledger reference](https://docs.cartesi.io/cartesi-rollups/2.0/api-reference/asset-management/ledger-reference.md): Signatures for ledger lifecycle, retrieval, state changes and queries +- [Types and selectors](https://docs.cartesi.io/cartesi-rollups/2.0/api-reference/asset-management/types-and-selectors.md): Input types, function selectors, portal addresses and error codes + ## Cartesi Rollups v2.0 — Contracts API - [Contracts Overview](https://docs.cartesi.io/cartesi-rollups/2.0/api-reference/contracts/overview.md): All smart contracts in the framework