-
Notifications
You must be signed in to change notification settings - Fork 431
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
PSP22 Chain Extension Example (#1244)
* 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
1 parent
2b7f20e
commit a314b34
Showing
6 changed files
with
802 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 = [] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} |
Oops, something went wrong.