-
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 21 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
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 = "4.0.0-alpha.1" | ||
| authors = ["Parity Technologies <[email protected]>"] | ||
| 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 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,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). | ||
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,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) | ||
| } | ||
| } | ||
| } | ||
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.