-
Notifications
You must be signed in to change notification settings - Fork 477
PSP22 chain extension example #1244
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
Merged
Merged
Changes from 5 commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
a0e59b2
PSP22 chain extension example
e944c65
Format code
8f02400
Format code
9b2b84f
Format code
7dbeff1
Rename example folder
a974852
Code & README cleanup
Wiezzel 52f2a19
Merge remote-tracking branch 'parity/master' into psp22-chainext-example
Wiezzel 3bb5786
ink version bump and reformat
Wiezzel 6660e1a
Split call function into smaller functions
Wiezzel 01c81c3
Added some comments
Wiezzel 94e7e87
Implemented decrease_allowance
Wiezzel d82c4ee
Merge branch 'master' into psp22-chainext-example
Wiezzel cce561c
Removed unnecessary local package versions
Wiezzel 6d78d25
Apply suggestions from code review
Wiezzel 13dab11
Resolve issues mentioned in review
Wiezzel 14434f6
Amend to the latest changes in chain extensions
Wiezzel 00d3872
Merge remote-tracking branch 'parity/master' into psp22-chainext-example
Wiezzel 7388024
Reformat
Wiezzel 907d94d
Merge branch 'master' into psp22-chainext-example
Wiezzel ea2fb2d
Adjust to review comments.
Wiezzel 59ee286
Merge remote-tracking branch 'parity/master' into psp22-chainext-example
Wiezzel 4f09629
Move `Ok` out of match.
Wiezzel 1717613
Comments & formatting
Wiezzel 5dad725
Fix up comment style
HCastano File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 = "3.0.1" | ||
| authors = ["Parity Technologies <admin@parity.io>"] | ||
| edition = "2021" | ||
| publish = false | ||
|
|
||
| [dependencies] | ||
| ink_prelude = { version = "3.0.1", path = "../../crates/prelude", default-features = false } | ||
|
Wiezzel marked this conversation as resolved.
Outdated
|
||
| ink_primitives = { version = "3.0.1", path = "../../crates/primitives", default-features = false } | ||
| ink_metadata = { version = "3.0.1", path = "../../crates/metadata", default-features = false, features = ["derive"], optional = true } | ||
| ink_env = { version = "3.0.1", path = "../../crates/env", default-features = false } | ||
| ink_storage = { version = "3.0.1", path = "../../crates/storage", default-features = false } | ||
| ink_lang = { version = "3.0.1", 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 hidden or 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,39 @@ | ||
| # PSP22 Chain Extension Example | ||
|
Wiezzel marked this conversation as 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: | ||
|
|
||
| * 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 the content of that file into e.g. your `runtime/src/lib.rs`. | ||
|
HCastano marked this conversation as resolved.
Outdated
|
||
|
|
||
| * Use the implementation as the associated type `ChainExtension` of the trait | ||
|
Wiezzel marked this conversation as resolved.
Outdated
|
||
| `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 | ||
|
|
||
| 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 hidden or 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,281 @@ | ||
| #![cfg_attr(not(feature = "std"), no_std)] | ||
|
HCastano marked this conversation as resolved.
|
||
|
|
||
| use ink_env::Environment; | ||
| use ink_lang as ink; | ||
| use ink_prelude::vec::Vec; | ||
|
|
||
| #[ink::chain_extension] | ||
| pub trait Psp22Extension { | ||
| type ErrorCode = Psp22ErrorCode; | ||
|
|
||
| // PSP22 Metadata interfaces | ||
|
|
||
| #[ink(extension = 0x3d261bd4)] | ||
| fn token_name(asset_id: u32) -> Result<Vec<u8>, Psp22Error>; | ||
|
|
||
| #[ink(extension = 0x34205be5)] | ||
| fn token_symbol(asset_id: u32) -> Result<Vec<u8>, Psp22Error>; | ||
|
|
||
| #[ink(extension = 0x7271b782)] | ||
| fn token_decimals(asset_id: u32) -> Result<u8, Psp22Error>; | ||
|
|
||
| // PSP22 interface queries | ||
|
|
||
| #[ink(extension = 0x162df8c2)] | ||
| fn total_supply( | ||
| asset_id: u32, | ||
| ) -> Result<<ink_env::DefaultEnvironment as Environment>::Balance, Psp22Error>; | ||
|
|
||
| #[ink(extension = 0x6568382f)] | ||
| fn balance_of( | ||
| asset_id: u32, | ||
| owner: <ink_env::DefaultEnvironment as Environment>::AccountId, | ||
| ) -> Result<<ink_env::DefaultEnvironment as Environment>::Balance, Psp22Error>; | ||
|
|
||
| #[ink(extension = 0x4d47d921)] | ||
| fn allowance( | ||
| asset_id: u32, | ||
| spender: <ink_env::DefaultEnvironment as Environment>::AccountId, | ||
| ) -> Result<<ink_env::DefaultEnvironment as Environment>::Balance, Psp22Error>; | ||
|
|
||
| // PSP22 transfer | ||
| #[ink(extension = 0xdb20f9f5)] | ||
| fn transfer( | ||
| asset_id: u32, | ||
| to: <ink_env::DefaultEnvironment as Environment>::AccountId, | ||
| value: <ink_env::DefaultEnvironment as Environment>::Balance, | ||
| ) -> Result<(), Psp22Error>; | ||
|
|
||
| // PSP22 transfer_from | ||
| #[ink(extension = 0x54b3c76e)] | ||
| fn transfer_from( | ||
| asset_id: u32, | ||
| from: <ink_env::DefaultEnvironment as Environment>::AccountId, | ||
| to: <ink_env::DefaultEnvironment as Environment>::AccountId, | ||
| value: <ink_env::DefaultEnvironment as Environment>::Balance, | ||
| ) -> Result<(), Psp22Error>; | ||
|
|
||
| // PSP22 approve | ||
| #[ink(extension = 0xb20f1bbd)] | ||
| fn approve( | ||
| asset_id: u32, | ||
| spender: <ink_env::DefaultEnvironment as Environment>::AccountId, | ||
| value: <ink_env::DefaultEnvironment as Environment>::Balance, | ||
| ) -> Result<(), Psp22Error>; | ||
|
|
||
| // PSP22 increase_allowance | ||
| #[ink(extension = 0x96d6b57a)] | ||
| fn increase_allowance( | ||
| asset_id: u32, | ||
| spender: <ink_env::DefaultEnvironment as Environment>::AccountId, | ||
| value: <ink_env::DefaultEnvironment as Environment>::Balance, | ||
| ) -> Result<(), Psp22Error>; | ||
|
|
||
| // PSP22 decrease_allowance | ||
| #[ink(extension = 0xfecb57d5)] | ||
| fn decrease_allowance( | ||
| asset_id: u32, | ||
| spender: <ink_env::DefaultEnvironment as Environment>::AccountId, | ||
| value: <ink_env::DefaultEnvironment as Environment>::Balance, | ||
| ) -> Result<(), Psp22Error>; | ||
| } | ||
|
|
||
| #[derive(scale::Encode, scale::Decode)] | ||
| #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] | ||
| pub enum Psp22ErrorCode { | ||
| TotalSupplyFailed, | ||
| } | ||
|
|
||
| #[derive(scale::Encode, scale::Decode)] | ||
| #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] | ||
| pub enum Psp22Error { | ||
| ErrorCode(Psp22ErrorCode), | ||
| } | ||
|
HCastano marked this conversation as resolved.
Outdated
|
||
|
|
||
| impl From<Psp22ErrorCode> for Psp22Error { | ||
| fn from(error_code: Psp22ErrorCode) -> Self { | ||
| Self::ErrorCode(error_code) | ||
| } | ||
| } | ||
|
|
||
| impl From<scale::Error> for Psp22Error { | ||
| fn from(_: scale::Error) -> Self { | ||
| panic!("encountered unexpected invalid SCALE encoding") | ||
| } | ||
| } | ||
|
|
||
| impl ink_env::chain_extension::FromStatusCode for Psp22ErrorCode { | ||
| fn from_status_code(status_code: u32) -> Result<(), Self> { | ||
| match status_code { | ||
| 0 => Ok(()), | ||
| 1 => Err(Self::TotalSupplyFailed), | ||
| _ => panic!("encountered unknown status code"), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[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 = <ink_env::DefaultEnvironment as Environment>::AccountId; | ||
| type Balance = <ink_env::DefaultEnvironment as Environment>::Balance; | ||
| type Hash = <ink_env::DefaultEnvironment as Environment>::Hash; | ||
| type BlockNumber = <ink_env::DefaultEnvironment as Environment>::BlockNumber; | ||
| type Timestamp = <ink_env::DefaultEnvironment as Environment>::Timestamp; | ||
|
|
||
| type ChainExtension = crate::Psp22Extension; | ||
| } | ||
|
|
||
| #[ink::contract(env = crate::CustomEnvironment)] | ||
| mod psp22_ext_test { | ||
|
Wiezzel marked this conversation as resolved.
Outdated
|
||
| use super::{ | ||
| Psp22Error, | ||
| Vec, | ||
| }; | ||
|
|
||
| #[ink(storage)] | ||
| pub struct Psp22ExtTest {} | ||
|
Wiezzel marked this conversation as resolved.
Outdated
|
||
|
|
||
| impl Psp22ExtTest { | ||
| #[ink(constructor)] | ||
| pub fn construct() -> Self { | ||
|
Wiezzel marked this conversation as resolved.
Outdated
|
||
| Psp22ExtTest {} | ||
| } | ||
|
|
||
| // PSP22 Metadata interfaces | ||
|
|
||
| /// Returns the token name of the specified asset. | ||
| #[ink(message)] | ||
| #[ink(selector = 0x3d261bd4)] | ||
|
Wiezzel marked this conversation as resolved.
Outdated
|
||
| pub fn token_name(&self, asset_id: u32) -> Result<Vec<u8>, Psp22Error> { | ||
|
Wiezzel marked this conversation as resolved.
Outdated
|
||
| self.env().extension().token_name(asset_id) | ||
| } | ||
|
|
||
| /// Returns the token symbol of the specified asset. | ||
| #[ink(message)] | ||
| #[ink(selector = 0x34205be5)] | ||
| pub fn token_symbol(&self, asset_id: u32) -> Result<Vec<u8>, Psp22Error> { | ||
| self.env().extension().token_symbol(asset_id) | ||
| } | ||
|
|
||
| /// Returns the token decimals of the specified asset. | ||
| #[ink(message)] | ||
| #[ink(selector = 0x7271b782)] | ||
| pub fn token_decimals(&self, asset_id: u32) -> Result<u8, Psp22Error> { | ||
| self.env().extension().token_decimals(asset_id) | ||
| } | ||
|
|
||
| // PSP22 interface queries | ||
|
|
||
| /// Returns the total token supply of the specified asset. | ||
| #[ink(message)] | ||
| #[ink(selector = 0x162df8c2)] | ||
| pub fn total_supply(&self, asset_id: u32) -> Result<Balance, Psp22Error> { | ||
| self.env().extension().total_supply(asset_id) | ||
| } | ||
|
|
||
| /// Returns the account balance for the specified asset & owner. | ||
| #[ink(message)] | ||
| #[ink(selector = 0x6568382f)] | ||
| pub fn balance_of( | ||
| &self, | ||
| asset_id: u32, | ||
| owner: AccountId, | ||
| ) -> Result<Balance, Psp22Error> { | ||
| 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)] | ||
| #[ink(selector = 0x4d47d921)] | ||
| pub fn allowance( | ||
| &self, | ||
| asset_id: u32, | ||
| spender: AccountId, | ||
| ) -> Result<Balance, Psp22Error> { | ||
| self.env().extension().allowance(asset_id, spender) | ||
| } | ||
|
|
||
| // PSP22 transfer | ||
|
|
||
| /// Transfers `value` amount of specified asset from the caller's account to account `to`. | ||
| #[ink(message)] | ||
| #[ink(selector = 0xdb20f9f5)] | ||
| pub fn transfer( | ||
| &mut self, | ||
| asset_id: u32, | ||
| to: AccountId, | ||
| value: Balance, | ||
| ) -> Result<(), Psp22Error> { | ||
| 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)] | ||
| #[ink(selector = 0x54b3c76e)] | ||
| pub fn transfer_from( | ||
| &mut self, | ||
| asset_id: u32, | ||
| from: AccountId, | ||
| to: AccountId, | ||
| value: Balance, | ||
| ) -> Result<(), Psp22Error> { | ||
| 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. | ||
|
HCastano marked this conversation as resolved.
Outdated
|
||
| #[ink(message)] | ||
| #[ink(selector = 0xb20f1bbd)] | ||
| pub fn approve( | ||
| &mut self, | ||
| asset_id: u32, | ||
| spender: AccountId, | ||
| value: Balance, | ||
| ) -> Result<(), Psp22Error> { | ||
| 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)] | ||
| #[ink(selector = 0x96d6b57a)] | ||
| pub fn increase_allowance( | ||
| &mut self, | ||
| asset_id: u32, | ||
| spender: AccountId, | ||
| value: Balance, | ||
| ) -> Result<(), Psp22Error> { | ||
| 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)] | ||
| #[ink(selector = 0xfecb57d5)] | ||
| pub fn decrease_allowance( | ||
| &mut self, | ||
| asset_id: u32, | ||
| spender: AccountId, | ||
| value: Balance, | ||
| ) -> Result<(), Psp22Error> { | ||
| self.env() | ||
| .extension() | ||
| .decrease_allowance(asset_id, spender, value) | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.