Skip to content

Commit

Permalink
PSP22 Chain Extension Example (#1244)
Browse files Browse the repository at this point in the history
* PSP22 chain extension example

* Format code

* Format code

* Format code

* Rename example folder

* Code & README cleanup

* ink version bump and reformat

* Split call function into smaller functions

* Added some comments

* Implemented decrease_allowance

* Removed unnecessary local package versions

* Apply suggestions from code review

Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com>

* Resolve issues mentioned in review

* Amend to the latest changes in chain extensions

* Reformat

* Adjust to review comments.

* Move `Ok` out of match.

Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com>

* Comments & formatting

* Fix up comment style

Co-authored-by: Adam Wierzbicki <adam.wierzbicki@parity.io>
Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com>
  • Loading branch information
3 people authored Sep 8, 2022
1 parent 2b7f20e commit a314b34
Show file tree
Hide file tree
Showing 6 changed files with 802 additions and 0 deletions.
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 +nightly --verbose --check ./examples/psp22-extension/runtime/psp22-extension-example.rs
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

## 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)]

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

0 comments on commit a314b34

Please sign in to comment.