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

docs: pop-api v0 fungibles READMEs #365

Merged
merged 12 commits into from
Nov 5, 2024
72 changes: 53 additions & 19 deletions pop-api/README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
# Pop API

_A stable runtime interface for ink! smart contracts that elevates the experience of building Web3 applications._

---

## What is the Pop API?

One of the core value propositions of Pop Network is to enable smart contracts to easily access the power of Polkadot. As such, the Pop API was built to enable smart contracts to easily utilize the functionality provided by the Pop Network parachain runtime.

## Versions

- [V0](./src/v0/README.md)

## Examples
ink! smart contract examples using the Pop API
- [balance-transfer](./examples/balance-transfer/)
- [NFTs](./examples/nfts/)
- [place-spot-order](./examples/place-spot-order/)

- [fungibles](./examples/fungibles/)
- [read-runtime-state](./examples/read-runtime-state/)

## What is the Pop API?
---

One of the core value propositions of Pop Network is to enable smart contracts to easily access the power of Polkadot. As such, the Pop API was built to enable smart contracts to easily utilize the functionality provided by the Pop Network parachain runtime.
## Design

Substrate already exposes a Runtime API which is typically associated with the “outer node” calling into the runtime via RPC. Pop Network extends this concept in a direction that makes it usable by smart contracts, allowing untrusted contracts to make use of carefully exposed trusted functionality available within the runtime - this is the Pop API.

Expand All @@ -31,7 +42,7 @@ Everything in [`pop-api`](./src/) **is the** Pop API ink! library.

So when the ink! smart contract wants to use the Pop API library, it can simply have a line like:
```rust
use pop_api::nfts::*;
use pop_api::fungibles::{self as api};
```

## The Glue
Expand All @@ -42,32 +53,55 @@ Certain types are shared between the ink! library portion of the Pop API and the

When we use the Pop API in our smart contract like so:
```rust
use pop_api::nfts::*;
mint(collection_id, item_id, receiver)?;
use pop_api::fungibles::{self as api};
// -- snip --
#[ink(message)]
pub fn transfer(&mut self, token: TokenId, to: AccountId, value: Balance) -> Result<()> {
// Use of Pop API to call into the runtime to transfer some fungible assets.
api::transfer(token, to, value)
}
```

This will call the Pop API `mint` function in the [./src/v0/nfts.rs](./src/v0/nfts.rs) file, which is a wrapper to `dispatch` a `Runtime::Call` to the NFTs pallet's mint function. This is how most of the Pop API is built. This abstraction allows for creating a developer-friendly API over runtime level features such as calling pallets, reading state, and cross-chain interactions. All Pop API functionality can be found in [./src/v0/](./src/v0/) which is the current version of the Pop API.
This will call the Pop API `transfer` function in the [./src/v0/fungibles/mod.rs](./src/v0/fungibles/mod.rs) file, which is a wrapper to `dispatch` a `Runtime::Call` to the assets pallet's transfer function. This is how most of the Pop API is built. This abstraction allows for creating a developer-friendly API over runtime level features such as calling pallets, reading state, and cross-chain interactions. All Pop API versions can be found in [pop-api/src/](./src/).


## Dispatching to the runtime ([./src/lib.rs](./src/lib.rs))

### `PopApi`
The `PopApi` trait is an ink! chain extension trait with three functions:
- `dispatch()`
- `read_state()`
- `send_xcm()`
### `PopApi`
al3mart marked this conversation as resolved.
Show resolved Hide resolved
The `PopApi` is an ink! [`ChainExtensionMethod`](https://docs.rs/ink_env/5.0.0/ink_env/chain_extension/struct.ChainExtensionMethod.html) instance used to derive the execution of the different calls a smart contract can do into the runtime.

Its purpose its two fold, constructing the runtime calls that are going to be executed by the chain extension and handling the information returned.

The calls are built out of the following information:
- The `function` Id: Identifies the specific function within the chain extension.
- The API `version`: This byte allows the runtime to distinguish between different API versions, ensuring that older contracts call the correct, version-specific implementation..
- A `module` index: Identifies the pallet responsible for handling the call.
- A `dispatchable` index: Indicates the specific dispatchable or state read function within the pallet.

Multiple **functions** can be implemented for the chain extension, so whenever something needs to be added or changed, a new function will have to be implemented.
`DISPATCH` and `READ_STATE` functions are the workhorse functions of the Pop API.
Through these functions all the interactions between the ink! smart contract and the runtime are carried out.

These are the workhorse functions of the Pop API. Through these functions all the interactions between the ink! smart contract and the runtime are carried out. So in our example above, the `mint` function in [nfts.rs](./src/v0/nfts.rs) calls a `dispatch`, this `dispatch` is defined here in the [lib.rs](./src/lib.rs) file. It is what calls into the runtime chain extension.
By embedding the **version** directly into the encoding scheme, the runtime can manage different versions of dispatch calls and queries, ensuring that both legacy and new contracts function as intended, even as the underlying system evolves. This structure provides the flexibility needed to support ongoing improvements and changes in the runtime without disrupting existing smart contracts.

> Notice how each function is assigned a function ID e.g. `#[ink(function = 1)]`. This will play a role later when we cover the runtime portion of the chain extension.
So in our example above, when the `trasnfer` function in [./src/v0/fungibles/mod.rs](./src/v0/fungibles/mod.rs) is called, the following is constructed `u32::from_le_bytes([DISPATCH, 0, FUNGIBLES, TRANSFER])` to be executed by the runtime chain extension.

### `PopApiError`
When Pop API calls the runtime, it will either receive a successful result or an encoded error. `PopApiError` translates the encoded error into the appropriate module error according to the index that the pallet has been configured to.

### `StatusCode`
When Pop API calls the runtime, it will either receive a successful result or an encoded error. `StatusCode` translates the encoded error into the appropriate module error according to the index that the pallet has been configured to.

## The Pop API Chain Extension

So we have covered how the ink! Pop API library calls the chain extension. But where is the chain extension actually defined? In the Pop Network runtime.

Chain extensions "extend" a runtime. We can find the `PopApiExtension` chain extension in [extension.rs](../runtime/devnet/src/extensions.rs). The `PopApiExtension` chain extension is matching based on the function IDs that we defined on the ink! side of the Pop API. The chain extension here will execute the appropriate functions e.g. `dispatch` or `read_state`. These functions are defined in this file as well and interact directly with the Pop Network runtime.

If you would like to see the whole flow, checkout the end-to-end tests in [extensions.rs](../runtime/devnet/src/extensions.rs) file e.g. `dispatch_nfts_mint_from_contract_works()`
`PopApiExtension` implements the different **functions** that can be called by the API. Two functions are provided:

- `DISPATCH`:
The Dispatch function decodes the received bytes into a `RuntimeCall`, optionally processing them as needed (versioning). It filters out any calls that are not explicitly permitted, then charges the appropriate weight, dispatches the call, and adjusts the weight accordingly before returning the result of the dispatch call.

- `READ_STATE`:
The ReadState function decodes the received bytes into a `Readable` type, with optional processing (versioning). It filters out any unauthorised state reads, charges the appropriate weight, executes the state read, optionally converts the result (versioning), and finally returns the result.

If you would like to see the whole flow, checkout the integration tests in [pop-api/integration-tests](./integration-tests/) e.g. [`instantiate_and_create_fungible_works()`](./integration-tests/src/fungibles/mod.rs).
18 changes: 18 additions & 0 deletions pop-api/src/v0/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Pop API V0

The version `0` of Pop API features integrations with the following pallets:
- [Fungibles](#fungibles)


---
### FUNGIBLES
```rust
#[runtime::pallet_index(150)]
pub type Fungibles = fungibles::Pallet<Runtime>;
al3mart marked this conversation as resolved.
Show resolved Hide resolved
```
The fungibles pallet offers a streamlined interface for interacting with fungible tokens. The
goal is to provide a simplified, consistent API that adheres to standards in the smart contract
space.

For more details please refer to:
[Fungibles API](./fungibles/README.md)
162 changes: 162 additions & 0 deletions pop-api/src/v0/fungibles/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# Fungibles API

The `fungibles` module provides an API for interacting and managing fungible tokens.

The API includes the following interfaces:
1. PSP-22
2. PSP-22 Metadata
3. Management
4. PSP-22 Mintable & Burnable
---

## Interface

#### PSP-22, PSP-22 Mintable & PSP-22 Burnable
The interface for transferring, delegating, minting and burning tokens.
```rust
/// Returns the total token supply for a specified token.
///
/// # Parameters
/// - `token` - The token.
pub fn total_supply(token: TokenId) -> Result<Balance> {}

/// Returns the account balance for a specified `token` and `owner`. Returns `0` if
/// the account is non-existent.
///
/// # Parameters
/// - `token` - The token.
/// - `owner` - The account whose balance is being queried.
pub fn balance_of(token: TokenId, owner: AccountId) -> Result<Balance> {}

/// Returns the allowance for a `spender` approved by an `owner`, for a specified `token`. Returns
/// `0` if no allowance has been set.
///
/// # Parameters
/// - `token` - The token.
/// - `owner` - The account that owns the tokens.
/// - `spender` - The account that is allowed to spend the tokens.
pub fn allowance(token: TokenId, owner: AccountId, spender: AccountId) -> Result<Balance> {}

/// Transfers `value` amount of tokens from the caller's account to account `to`.
///
/// # Parameters
/// - `token` - The token to transfer.
/// - `to` - The recipient account.
/// - `value` - The number of tokens to transfer.
pub fn transfer(token: TokenId, to: AccountId, value: Balance) -> Result<()> {}

/// Transfers `value` amount tokens on behalf of `from` to account `to`.
///
/// # Parameters
/// - `token` - The token to transfer.
/// - `from` - The account from which the token balance will be withdrawn.
/// - `to` - The recipient account.
/// - `value` - The number of tokens to transfer.
pub fn transfer_from(token: TokenId, from: AccountId, to: AccountId, value: Balance) -> Result<()> {}

/// Approves `spender` to spend `value` amount of tokens on behalf of the caller.
///
/// # Parameters
/// - `token` - The token to approve.
/// - `spender` - The account that is allowed to spend the tokens.
/// - `value` - The number of tokens to approve.
pub fn approve(token: TokenId, spender: AccountId, value: Balance) -> Result<()> {}

/// Increases the allowance of `spender` by `value` amount of tokens.
///
/// # Parameters
/// - `token` - The token to have an allowance increased.
/// - `spender` - The account that is allowed to spend the tokens.
/// - `value` - The number of tokens to increase the allowance by.
pub fn increase_allowance(token: TokenId, spender: AccountId, value: Balance) -> Result<()> {}

/// Decreases the allowance of `spender` by `value` amount of tokens.
///
/// # Parameters
/// - `token` - The token to have an allowance decreased.
/// - `spender` - The account that is allowed to spend the tokens.
/// - `value` - The number of tokens to decrease the allowance by.
pub fn decrease_allowance(token: TokenId, spender: AccountId, value: Balance) -> Result<()> {}

/// Creates `value` amount of tokens and assigns them to `account`, increasing the total supply.
///
/// # Parameters
/// - `token` - The token to mint.
/// - `account` - The account to be credited with the created tokens.
/// - `value` - The number of tokens to mint.
pub fn mint(token: TokenId, account: AccountId, value: Balance) -> Result<()> {}

/// Destroys `value` amount of tokens from `account`, reducing the total supply.
///
/// # Parameters
/// - `token` - the token to burn.
/// - `account` - The account from which the tokens will be destroyed.
/// - `value` - The number of tokens to destroy.
pub fn burn(token: TokenId, account: AccountId, value: Balance) -> Result<()> {}
```

#### PSP-22 Metadata
The PSP-22 compliant interface for querying metadata.
```rust
/// Returns the name of the specified token, if available.
///
/// # Parameters
/// - `token` - The token.
pub fn token_name(token: TokenId) -> Result<Option<Vec<u8>>> {}

/// Returns the symbol for the specified token, if available.
///
/// # Parameters
/// - `token` - The token.
pub fn token_symbol(token: TokenId) -> Result<Option<Vec<u8>>> {}

/// Returns the decimals for the specified token.
///
/// # Parameters
/// - `token` - The token.
pub fn token_decimals(token: TokenId) -> Result<u8> {}
```

#### PSP-22 Management
The interface for creating, managing and destroying fungible tokens.
```rust
/// Create a new token with a given identifier.
///
/// # Parameters
/// - `id` - The identifier of the token.
/// - `admin` - The account that will administer the token.
/// - `min_balance` - The minimum balance required for accounts holding this token.
pub fn create(id: TokenId, admin: AccountId, min_balance: Balance) -> Result<()> {}

/// Start the process of destroying a token.
///
/// # Parameters
/// - `token` - The token to be destroyed.
pub fn start_destroy(token: TokenId) -> Result<()> {}

/// Set the metadata for a token.
///
/// # Parameters
/// - `token`: The token to update.
/// - `name`: The user friendly name of this token.
/// - `symbol`: The exchange symbol for this token.
/// - `decimals`: The number of decimals this token uses to represent one unit.
pub fn set_metadata(
token: TokenId,
name: Vec<u8>,
symbol: Vec<u8>,
decimals: u8,
) -> Result<()> {}

/// Clear the metadata for a token.
///
/// # Parameters
/// - `token` - The token to update
pub fn clear_metadata(token: TokenId) -> Result<()> {}

/// Checks if a specified token exists.
///
/// # Parameters
/// - `token` - The token.
pub fn token_exists(token: TokenId) -> Result<bool> {}
```
Loading