Skip to content

Commit a314b34

Browse files
stiiifffWiezzelHCastano
authored
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 <[email protected]> * 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 <[email protected]> * Comments & formatting * Fix up comment style Co-authored-by: Adam Wierzbicki <[email protected]> Co-authored-by: Hernando Castano <[email protected]>
1 parent 2b7f20e commit a314b34

File tree

6 files changed

+802
-0
lines changed

6 files changed

+802
-0
lines changed

.gitlab-ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ examples-fmt:
128128
cargo +nightly fmt --verbose --manifest-path ./examples/upgradeable-contracts/${contract}/Cargo.toml -- --check;
129129
done
130130
- cargo +nightly fmt --verbose --manifest-path ./examples/upgradeable-contracts/set-code-hash/updated-incrementer/Cargo.toml -- --check
131+
# This file is not a part of the cargo project, so it wouldn't be formatted the usual way
132+
- rustfmt +nightly --verbose --check ./examples/psp22-extension/runtime/psp22-extension-example.rs
131133
allow_failure: true
132134

133135
clippy-std:

examples/psp22-extension/.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Ignore build artifacts from the local tests sub-crate.
2+
/target/
3+
4+
# Ignore backup files creates by cargo fmt.
5+
**/*.rs.bk
6+
7+
# Remove Cargo.lock when creating an executable, leave it for libraries
8+
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
9+
Cargo.lock

examples/psp22-extension/Cargo.toml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
[package]
2+
name = "psp22_extension"
3+
version = "4.0.0-alpha.1"
4+
authors = ["Parity Technologies <[email protected]>"]
5+
edition = "2021"
6+
publish = false
7+
8+
[dependencies]
9+
ink_prelude = { path = "../../crates/prelude", default-features = false }
10+
ink_primitives = { path = "../../crates/primitives", default-features = false }
11+
ink_metadata = { path = "../../crates/metadata", default-features = false, features = ["derive"], optional = true }
12+
ink_env = { path = "../../crates/env", default-features = false }
13+
ink_storage = { path = "../../crates/storage", default-features = false }
14+
ink_lang = { path = "../../crates/lang", default-features = false }
15+
16+
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
17+
scale-info = { version = "2", default-features = false, features = ["derive"], optional = true }
18+
19+
[lib]
20+
name = "psp22_extension"
21+
path = "lib.rs"
22+
crate-type = ["cdylib"]
23+
24+
[features]
25+
default = ["std"]
26+
std = [
27+
"ink_metadata/std",
28+
"ink_env/std",
29+
"ink_storage/std",
30+
"ink_prelude/std",
31+
"ink_primitives/std",
32+
"scale/std",
33+
"scale-info/std",
34+
]
35+
ink-as-dependency = []

examples/psp22-extension/README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# PSP22 Chain Extension Example
2+
3+
## What is this example about?
4+
5+
It is an example implementation of the
6+
[PSP22 Fungible Token Standard](https://github.com/w3f/PSPs/blob/master/PSPs/psp-22.md)
7+
as a chain extension, supporting a multi-token system provided by the
8+
[FRAME assets pallet](https://docs.substrate.io/rustdocs/latest/pallet_assets/index.html).
9+
It effectively allows ink! contracts (L2) to interact with native assets (L1) from the
10+
chain runtime in a standardized way.
11+
12+
See [this chapter](https://paritytech.github.io/ink-docs/macros-attributes/chain-extension)
13+
in our ink! documentation for more details about chain extensions.
14+
15+
There are two parts to this example:
16+
17+
* Defining and calling the extension in ink!.
18+
* Defining the extension in Substrate.
19+
20+
## Chain-side Integration
21+
22+
To integrate this example into Substrate you need to do two things:
23+
24+
* In your runtime, use the code in
25+
[`psp22-extension-example.rs`](runtime/psp22-extension-example.rs)
26+
as an implementation for the trait `ChainExtension` in Substrate.
27+
You can just copy/paste that file as a new module, e.g. `runtime/src/chain_extension.rs`.
28+
29+
* In your runtime, use the implementation as the associated type `ChainExtension` of the
30+
trait `pallet_contracts::Config`:
31+
```rust
32+
impl pallet_contracts::Config for Runtime {
33+
34+
type ChainExtension = Psp22Extension;
35+
36+
}
37+
```
38+
39+
## ink! Integration
40+
41+
See the example contract in [`lib.rs`](lib.rs).
42+
43+
## Disclaimer
44+
45+
:warning: This is not a feature-complete or production-ready PSP22 implementation. This
46+
example currently lacks proper error management, precise weight accounting, tests (these
47+
all might be added at a later point).

examples/psp22-extension/lib.rs

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
#![cfg_attr(not(feature = "std"), no_std)]
2+
3+
use ink_env::Environment;
4+
use ink_lang as ink;
5+
use ink_prelude::vec::Vec;
6+
7+
type DefaultAccountId = <ink_env::DefaultEnvironment as Environment>::AccountId;
8+
type DefaultBalance = <ink_env::DefaultEnvironment as Environment>::Balance;
9+
10+
#[ink::chain_extension]
11+
pub trait Psp22Extension {
12+
type ErrorCode = Psp22Error;
13+
14+
// PSP22 Metadata interfaces
15+
16+
#[ink(extension = 0x3d26)]
17+
fn token_name(asset_id: u32) -> Result<Vec<u8>>;
18+
19+
#[ink(extension = 0x3420)]
20+
fn token_symbol(asset_id: u32) -> Result<Vec<u8>>;
21+
22+
#[ink(extension = 0x7271)]
23+
fn token_decimals(asset_id: u32) -> Result<u8>;
24+
25+
// PSP22 interface queries
26+
27+
#[ink(extension = 0x162d)]
28+
fn total_supply(asset_id: u32) -> Result<DefaultBalance>;
29+
30+
#[ink(extension = 0x6568)]
31+
fn balance_of(asset_id: u32, owner: DefaultAccountId) -> Result<DefaultBalance>;
32+
33+
#[ink(extension = 0x4d47)]
34+
fn allowance(
35+
asset_id: u32,
36+
owner: DefaultAccountId,
37+
spender: DefaultAccountId,
38+
) -> Result<DefaultBalance>;
39+
40+
// PSP22 transfer
41+
#[ink(extension = 0xdb20)]
42+
fn transfer(asset_id: u32, to: DefaultAccountId, value: DefaultBalance)
43+
-> Result<()>;
44+
45+
// PSP22 transfer_from
46+
#[ink(extension = 0x54b3)]
47+
fn transfer_from(
48+
asset_id: u32,
49+
from: DefaultAccountId,
50+
to: DefaultAccountId,
51+
value: DefaultBalance,
52+
) -> Result<()>;
53+
54+
// PSP22 approve
55+
#[ink(extension = 0xb20f)]
56+
fn approve(
57+
asset_id: u32,
58+
spender: DefaultAccountId,
59+
value: DefaultBalance,
60+
) -> Result<()>;
61+
62+
// PSP22 increase_allowance
63+
#[ink(extension = 0x96d6)]
64+
fn increase_allowance(
65+
asset_id: u32,
66+
spender: DefaultAccountId,
67+
value: DefaultBalance,
68+
) -> Result<()>;
69+
70+
// PSP22 decrease_allowance
71+
#[ink(extension = 0xfecb)]
72+
fn decrease_allowance(
73+
asset_id: u32,
74+
spender: DefaultAccountId,
75+
value: DefaultBalance,
76+
) -> Result<()>;
77+
}
78+
79+
#[derive(scale::Encode, scale::Decode)]
80+
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
81+
pub enum Psp22Error {
82+
TotalSupplyFailed,
83+
}
84+
85+
pub type Result<T> = core::result::Result<T, Psp22Error>;
86+
87+
impl From<scale::Error> for Psp22Error {
88+
fn from(_: scale::Error) -> Self {
89+
panic!("encountered unexpected invalid SCALE encoding")
90+
}
91+
}
92+
93+
impl ink_env::chain_extension::FromStatusCode for Psp22Error {
94+
fn from_status_code(status_code: u32) -> core::result::Result<(), Self> {
95+
match status_code {
96+
0 => Ok(()),
97+
1 => Err(Self::TotalSupplyFailed),
98+
_ => panic!("encountered unknown status code"),
99+
}
100+
}
101+
}
102+
103+
/// An environment using default ink environment types, with PSP-22 extension included
104+
#[derive(Debug, Clone, PartialEq, Eq)]
105+
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
106+
pub enum CustomEnvironment {}
107+
108+
impl Environment for CustomEnvironment {
109+
const MAX_EVENT_TOPICS: usize =
110+
<ink_env::DefaultEnvironment as Environment>::MAX_EVENT_TOPICS;
111+
112+
type AccountId = DefaultAccountId;
113+
type Balance = DefaultBalance;
114+
type Hash = <ink_env::DefaultEnvironment as Environment>::Hash;
115+
type Timestamp = <ink_env::DefaultEnvironment as Environment>::Timestamp;
116+
type BlockNumber = <ink_env::DefaultEnvironment as Environment>::BlockNumber;
117+
118+
type ChainExtension = crate::Psp22Extension;
119+
}
120+
121+
#[ink::contract(env = crate::CustomEnvironment)]
122+
mod psp22_ext {
123+
use super::{
124+
Result,
125+
Vec,
126+
};
127+
128+
/// A chain extension which implements the PSP-22 fungible token standard.
129+
/// For more details see <https://github.com/w3f/PSPs/blob/master/PSPs/psp-22.md>
130+
#[ink(storage)]
131+
#[derive(Default)]
132+
pub struct Psp22Extension {}
133+
134+
impl Psp22Extension {
135+
/// Creates a new instance of this contract.
136+
#[ink(constructor)]
137+
pub fn new() -> Self {
138+
Default::default()
139+
}
140+
141+
// PSP22 Metadata interfaces
142+
143+
/// Returns the token name of the specified asset.
144+
#[ink(message, selector = 0x3d261bd4)]
145+
pub fn token_name(&self, asset_id: u32) -> Result<Vec<u8>> {
146+
self.env().extension().token_name(asset_id)
147+
}
148+
149+
/// Returns the token symbol of the specified asset.
150+
#[ink(message, selector = 0x34205be5)]
151+
pub fn token_symbol(&self, asset_id: u32) -> Result<Vec<u8>> {
152+
self.env().extension().token_symbol(asset_id)
153+
}
154+
155+
/// Returns the token decimals of the specified asset.
156+
#[ink(message, selector = 0x7271b782)]
157+
pub fn token_decimals(&self, asset_id: u32) -> Result<u8> {
158+
self.env().extension().token_decimals(asset_id)
159+
}
160+
161+
// PSP22 interface queries
162+
163+
/// Returns the total token supply of the specified asset.
164+
#[ink(message, selector = 0x162df8c2)]
165+
pub fn total_supply(&self, asset_id: u32) -> Result<Balance> {
166+
self.env().extension().total_supply(asset_id)
167+
}
168+
169+
/// Returns the account balance for the specified asset & owner.
170+
#[ink(message, selector = 0x6568382f)]
171+
pub fn balance_of(&self, asset_id: u32, owner: AccountId) -> Result<Balance> {
172+
self.env().extension().balance_of(asset_id, owner)
173+
}
174+
175+
/// Returns the amount which `spender` is still allowed to withdraw from `owner`
176+
/// for the specified asset.
177+
#[ink(message, selector = 0x4d47d921)]
178+
pub fn allowance(
179+
&self,
180+
asset_id: u32,
181+
owner: AccountId,
182+
spender: AccountId,
183+
) -> Result<Balance> {
184+
self.env().extension().allowance(asset_id, owner, spender)
185+
}
186+
187+
// PSP22 transfer
188+
189+
/// Transfers `value` amount of specified asset from the caller's account to the
190+
/// account `to`.
191+
#[ink(message, selector = 0xdb20f9f5)]
192+
pub fn transfer(
193+
&mut self,
194+
asset_id: u32,
195+
to: AccountId,
196+
value: Balance,
197+
) -> Result<()> {
198+
self.env().extension().transfer(asset_id, to, value)
199+
}
200+
201+
// PSP22 transfer_from
202+
203+
/// Transfers `value` amount of specified asset on the behalf of `from` to the
204+
/// account `to`.
205+
#[ink(message, selector = 0x54b3c76e)]
206+
pub fn transfer_from(
207+
&mut self,
208+
asset_id: u32,
209+
from: AccountId,
210+
to: AccountId,
211+
value: Balance,
212+
) -> Result<()> {
213+
self.env()
214+
.extension()
215+
.transfer_from(asset_id, from, to, value)
216+
}
217+
218+
// PSP22 approve
219+
220+
/// Allows `spender` to withdraw from the caller's account multiple times, up to
221+
/// the `value` amount of the specified asset.
222+
#[ink(message, selector = 0xb20f1bbd)]
223+
pub fn approve(
224+
&mut self,
225+
asset_id: u32,
226+
spender: AccountId,
227+
value: Balance,
228+
) -> Result<()> {
229+
self.env().extension().approve(asset_id, spender, value)
230+
}
231+
232+
// PSP22 increase_allowance
233+
234+
/// Atomically increases the allowance for the specified asset granted to `spender`
235+
/// by the caller.
236+
#[ink(message, selector = 0x96d6b57a)]
237+
pub fn increase_allowance(
238+
&mut self,
239+
asset_id: u32,
240+
spender: AccountId,
241+
value: Balance,
242+
) -> Result<()> {
243+
self.env()
244+
.extension()
245+
.increase_allowance(asset_id, spender, value)
246+
}
247+
248+
// PSP22 decrease_allowance
249+
250+
/// Atomically decreases the allowance for the specified asset granted to `spender`
251+
/// by the caller.
252+
#[ink(message, selector = 0xfecb57d5)]
253+
pub fn decrease_allowance(
254+
&mut self,
255+
asset_id: u32,
256+
spender: AccountId,
257+
value: Balance,
258+
) -> Result<()> {
259+
self.env()
260+
.extension()
261+
.decrease_allowance(asset_id, spender, value)
262+
}
263+
}
264+
}

0 commit comments

Comments
 (0)