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

PSP22 chain extension example #1244

Merged
merged 24 commits into from
Sep 8, 2022
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ examples-fmt:
cargo +nightly fmt --verbose --manifest-path ./examples/upgradeable-contracts/${contract}/Cargo.toml -- --check;
done
- cargo +nightly fmt --verbose --manifest-path ./examples/upgradeable-contracts/set-code-hash/updated-incrementer/Cargo.toml -- --check
# This file is not a part of the cargo project, so it wouldn't be formatted the usual way
- rustfmt --verbose --check ./examples/psp22-extension/runtime/psp22-extension-example.rs
Wiezzel marked this conversation as resolved.
Show resolved Hide resolved
allow_failure: true

clippy-std:
Expand Down
9 changes: 9 additions & 0 deletions examples/psp22-extension/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Ignore build artifacts from the local tests sub-crate.
/target/

# Ignore backup files creates by cargo fmt.
**/*.rs.bk

# Remove Cargo.lock when creating an executable, leave it for libraries
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
Cargo.lock
35 changes: 35 additions & 0 deletions examples/psp22-extension/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[package]
name = "psp22_extension"
version = "4.0.0-alpha.1"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
publish = false

[dependencies]
ink_prelude = { path = "../../crates/prelude", default-features = false }
ink_primitives = { path = "../../crates/primitives", default-features = false }
ink_metadata = { path = "../../crates/metadata", default-features = false, features = ["derive"], optional = true }
ink_env = { path = "../../crates/env", default-features = false }
ink_storage = { path = "../../crates/storage", default-features = false }
ink_lang = { path = "../../crates/lang", default-features = false }

scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
scale-info = { version = "2", default-features = false, features = ["derive"], optional = true }

[lib]
name = "psp22_extension"
path = "lib.rs"
crate-type = ["cdylib"]

[features]
default = ["std"]
std = [
"ink_metadata/std",
"ink_env/std",
"ink_storage/std",
"ink_prelude/std",
"ink_primitives/std",
"scale/std",
"scale-info/std",
]
ink-as-dependency = []
47 changes: 47 additions & 0 deletions examples/psp22-extension/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# PSP22 Chain Extension Example
Wiezzel marked this conversation as resolved.
Show resolved Hide resolved

## What is this example about?

It is an example implementation of the
[PSP22 Fungible Token Standard](https://github.com/w3f/PSPs/blob/master/PSPs/psp-22.md)
as a chain extension, supporting a multi-token system provided by the
[FRAME assets pallet](https://docs.substrate.io/rustdocs/latest/pallet_assets/index.html).
It effectively allows ink! contracts (L2) to interact with native assets (L1) from the
chain runtime in a standardized way.

See [this chapter](https://paritytech.github.io/ink-docs/macros-attributes/chain-extension)
in our ink! documentation for more details about chain extensions.

There are two parts to this example:

* Defining and calling the extension in ink!.
* Defining the extension in Substrate.

## Chain-side Integration

To integrate this example into Substrate you need to do two things:

* In your runtime, use the code in
[`psp22-extension-example.rs`](runtime/psp22-extension-example.rs)
as an implementation for the trait `ChainExtension` in Substrate.
You can just copy/paste that file as a new module, e.g. `runtime/src/chain_extension.rs`.

* In your runtime, use the implementation as the associated type `ChainExtension` of the
trait `pallet_contracts::Config`:
```rust
impl pallet_contracts::Config for Runtime {
type ChainExtension = Psp22Extension;
}
```

## ink! Integration

See the example contract in [`lib.rs`](lib.rs).

## Disclaimer

:warning: This is not a feature-complete or production-ready PSP22 implementation. This
example currently lacks proper error management, precise weight accounting, tests (these
all might be added at a later point).
264 changes: 264 additions & 0 deletions examples/psp22-extension/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
#![cfg_attr(not(feature = "std"), no_std)]
HCastano marked this conversation as resolved.
Show resolved Hide resolved

use ink_env::Environment;
use ink_lang as ink;
use ink_prelude::vec::Vec;

type DefaultAccountId = <ink_env::DefaultEnvironment as Environment>::AccountId;
type DefaultBalance = <ink_env::DefaultEnvironment as Environment>::Balance;

#[ink::chain_extension]
pub trait Psp22Extension {
type ErrorCode = Psp22Error;

// PSP22 Metadata interfaces

#[ink(extension = 0x3d26)]
fn token_name(asset_id: u32) -> Result<Vec<u8>>;

#[ink(extension = 0x3420)]
fn token_symbol(asset_id: u32) -> Result<Vec<u8>>;

#[ink(extension = 0x7271)]
fn token_decimals(asset_id: u32) -> Result<u8>;

// PSP22 interface queries

#[ink(extension = 0x162d)]
fn total_supply(asset_id: u32) -> Result<DefaultBalance>;

#[ink(extension = 0x6568)]
fn balance_of(asset_id: u32, owner: DefaultAccountId) -> Result<DefaultBalance>;

#[ink(extension = 0x4d47)]
fn allowance(
asset_id: u32,
owner: DefaultAccountId,
spender: DefaultAccountId,
) -> Result<DefaultBalance>;

// PSP22 transfer
#[ink(extension = 0xdb20)]
fn transfer(asset_id: u32, to: DefaultAccountId, value: DefaultBalance)
-> Result<()>;

// PSP22 transfer_from
#[ink(extension = 0x54b3)]
fn transfer_from(
asset_id: u32,
from: DefaultAccountId,
to: DefaultAccountId,
value: DefaultBalance,
) -> Result<()>;

// PSP22 approve
#[ink(extension = 0xb20f)]
fn approve(
asset_id: u32,
spender: DefaultAccountId,
value: DefaultBalance,
) -> Result<()>;

// PSP22 increase_allowance
#[ink(extension = 0x96d6)]
fn increase_allowance(
asset_id: u32,
spender: DefaultAccountId,
value: DefaultBalance,
) -> Result<()>;

// PSP22 decrease_allowance
#[ink(extension = 0xfecb)]
fn decrease_allowance(
asset_id: u32,
spender: DefaultAccountId,
value: DefaultBalance,
) -> Result<()>;
}

#[derive(scale::Encode, scale::Decode)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum Psp22Error {
TotalSupplyFailed,
}

pub type Result<T> = core::result::Result<T, Psp22Error>;

impl From<scale::Error> for Psp22Error {
fn from(_: scale::Error) -> Self {
panic!("encountered unexpected invalid SCALE encoding")
}
}

impl ink_env::chain_extension::FromStatusCode for Psp22Error {
fn from_status_code(status_code: u32) -> core::result::Result<(), Self> {
match status_code {
0 => Ok(()),
1 => Err(Self::TotalSupplyFailed),
_ => panic!("encountered unknown status code"),
}
}
}

/// An environment using default ink environment types, with PSP-22 extension included
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum CustomEnvironment {}

impl Environment for CustomEnvironment {
const MAX_EVENT_TOPICS: usize =
<ink_env::DefaultEnvironment as Environment>::MAX_EVENT_TOPICS;

type AccountId = DefaultAccountId;
type Balance = DefaultBalance;
type Hash = <ink_env::DefaultEnvironment as Environment>::Hash;
type Timestamp = <ink_env::DefaultEnvironment as Environment>::Timestamp;
type BlockNumber = <ink_env::DefaultEnvironment as Environment>::BlockNumber;

type ChainExtension = crate::Psp22Extension;
}

#[ink::contract(env = crate::CustomEnvironment)]
mod psp22_ext {
use super::{
Result,
Vec,
};

/// A chain extension which implements the PSP-22 fungible token standard.
/// For more details see <https://github.com/w3f/PSPs/blob/master/PSPs/psp-22.md>
#[ink(storage)]
#[derive(Default)]
pub struct Psp22Extension {}

impl Psp22Extension {
/// Creates a new instance of this contract.
#[ink(constructor)]
pub fn new() -> Self {
Default::default()
}

// PSP22 Metadata interfaces

/// Returns the token name of the specified asset.
#[ink(message, selector = 0x3d261bd4)]
pub fn token_name(&self, asset_id: u32) -> Result<Vec<u8>> {
self.env().extension().token_name(asset_id)
}

/// Returns the token symbol of the specified asset.
#[ink(message, selector = 0x34205be5)]
pub fn token_symbol(&self, asset_id: u32) -> Result<Vec<u8>> {
self.env().extension().token_symbol(asset_id)
}

/// Returns the token decimals of the specified asset.
#[ink(message, selector = 0x7271b782)]
pub fn token_decimals(&self, asset_id: u32) -> Result<u8> {
self.env().extension().token_decimals(asset_id)
}

// PSP22 interface queries

/// Returns the total token supply of the specified asset.
#[ink(message, selector = 0x162df8c2)]
pub fn total_supply(&self, asset_id: u32) -> Result<Balance> {
self.env().extension().total_supply(asset_id)
}

/// Returns the account balance for the specified asset & owner.
#[ink(message, selector = 0x6568382f)]
pub fn balance_of(&self, asset_id: u32, owner: AccountId) -> Result<Balance> {
self.env().extension().balance_of(asset_id, owner)
}

/// Returns the amount which `spender` is still allowed to withdraw from `owner`
/// for the specified asset.
#[ink(message, selector = 0x4d47d921)]
pub fn allowance(
&self,
asset_id: u32,
owner: AccountId,
spender: AccountId,
) -> Result<Balance> {
self.env().extension().allowance(asset_id, owner, spender)
}

// PSP22 transfer

/// Transfers `value` amount of specified asset from the caller's account to the
/// account `to`.
#[ink(message, selector = 0xdb20f9f5)]
pub fn transfer(
&mut self,
asset_id: u32,
to: AccountId,
value: Balance,
) -> Result<()> {
self.env().extension().transfer(asset_id, to, value)
}

// PSP22 transfer_from

/// Transfers `value` amount of specified asset on the behalf of `from` to the
/// account `to`.
#[ink(message, selector = 0x54b3c76e)]
pub fn transfer_from(
&mut self,
asset_id: u32,
from: AccountId,
to: AccountId,
value: Balance,
) -> Result<()> {
self.env()
.extension()
.transfer_from(asset_id, from, to, value)
}

// PSP22 approve

/// Allows `spender` to withdraw from the caller's account multiple times, up to
/// the `value` amount of the specified asset.
#[ink(message, selector = 0xb20f1bbd)]
pub fn approve(
&mut self,
asset_id: u32,
spender: AccountId,
value: Balance,
) -> Result<()> {
self.env().extension().approve(asset_id, spender, value)
}

// PSP22 increase_allowance

/// Atomically increases the allowance for the specified asset granted to `spender`
/// by the caller.
#[ink(message, selector = 0x96d6b57a)]
pub fn increase_allowance(
&mut self,
asset_id: u32,
spender: AccountId,
value: Balance,
) -> Result<()> {
self.env()
.extension()
.increase_allowance(asset_id, spender, value)
}

// PSP22 decrease_allowance

/// Atomically decreases the allowance for the specified asset granted to `spender`
/// by the caller.
#[ink(message, selector = 0xfecb57d5)]
pub fn decrease_allowance(
&mut self,
asset_id: u32,
spender: AccountId,
value: Balance,
) -> Result<()> {
self.env()
.extension()
.decrease_allowance(asset_id, spender, value)
}
}
}
Loading