diff --git a/Cargo.toml b/Cargo.toml index 47f7e56c6..6ad147f7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,7 @@ governance = ["openbrush_contracts/governance", "openbrush_contracts/checkpoints crypto = ["openbrush_contracts/crypto"] nonces = ["openbrush_contracts/nonces"] checkpoints = ["openbrush_contracts/checkpoints"] +psp61 = ["openbrush_contracts/psp61"] test-all = [ "psp22", diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index ff456408e..2f00de381 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -49,6 +49,7 @@ ownable = [] payment_splitter = [] reentrancy_guard = [] pausable = [] +psp61 = [] timelock_controller = [ "access_control", ] @@ -72,6 +73,7 @@ test-all = [ "psp22", "psp34", "psp37", + "psp61", "access_control", "ownable", "payment_splitter", diff --git a/contracts/src/lib.rs b/contracts/src/lib.rs index 62a5b4ee8..3ea097f9b 100644 --- a/contracts/src/lib.rs +++ b/contracts/src/lib.rs @@ -63,3 +63,5 @@ pub use upgradeability::proxy; pub use upgradeability::upgradeable; #[cfg(feature = "nonces")] pub use utils::nonces; +#[cfg(feature = "psp61")] +pub use utils::psp61; diff --git a/contracts/src/traits/mod.rs b/contracts/src/traits/mod.rs index 81884a985..10e90dc3b 100644 --- a/contracts/src/traits/mod.rs +++ b/contracts/src/traits/mod.rs @@ -33,6 +33,7 @@ pub mod proxy; pub mod psp22; pub mod psp34; pub mod psp37; +pub mod psp61; pub mod upgradeable; pub mod types; diff --git a/contracts/src/traits/psp61/mod.rs b/contracts/src/traits/psp61/mod.rs new file mode 100644 index 000000000..7b3fb0020 --- /dev/null +++ b/contracts/src/traits/psp61/mod.rs @@ -0,0 +1,10 @@ +use ink::prelude::vec::Vec; + +#[openbrush::trait_definition] +pub trait PSP61 { + #[ink(message)] + fn supports_interface(&self, interface_id: u32) -> bool; + + #[ink(message)] + fn supported_interfaces(&self) -> Vec; +} diff --git a/contracts/src/utils/mod.rs b/contracts/src/utils/mod.rs index 2fe214417..ba79439f0 100644 --- a/contracts/src/utils/mod.rs +++ b/contracts/src/utils/mod.rs @@ -22,3 +22,6 @@ #[cfg(feature = "nonces")] pub mod nonces; + +#[cfg(feature = "psp61")] +pub mod psp61; diff --git a/contracts/src/utils/psp61/mod.rs b/contracts/src/utils/psp61/mod.rs new file mode 100644 index 000000000..c142bbc94 --- /dev/null +++ b/contracts/src/utils/psp61/mod.rs @@ -0,0 +1,67 @@ +// Copyright (c) 2012-2023 727-ventures +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the"Software"), +// to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +pub use crate::{ + psp61, + traits::psp61::*, +}; +use ink::prelude::{ + vec, + vec::Vec, +}; + +pub trait PSP61Internal { + fn _interfaces(&self) -> Vec { + vec![] + } +} + +pub trait PSP61InternalOB { + fn _interfaces_ob(&self) -> Vec { + vec![] + } +} + +pub trait PSP61Impl: PSP61Internal + PSP61InternalOB { + fn supports_interface(&self, interface_id: u32) -> bool { + self._interfaces().contains(&interface_id) || self._interfaces_ob().contains(&interface_id) + } + + fn supported_interfaces(&self) -> Vec { + let mut interfaces = self._interfaces(); + interfaces.append(&mut self._interfaces_ob()); + interfaces + } +} + +#[macro_export] +macro_rules! supported_interfaces { + ($contract:ident => $($interface_id:expr),*) => { + impl ::openbrush::contracts::psp61::PSP61Internal for $contract { + fn _interfaces(&self) -> ::ink::prelude::vec::Vec { + ::ink::prelude::vec![$($interface_id),*] + } + } + }; + ($contract:ident) => { + impl ::openbrush::contracts::psp61::PSP61Internal for $contract {} + }; +} diff --git a/examples/payment_splitter/lib.rs b/examples/payment_splitter/lib.rs index 3a6549935..a308e9ef2 100644 --- a/examples/payment_splitter/lib.rs +++ b/examples/payment_splitter/lib.rs @@ -38,11 +38,7 @@ pub mod my_payment_splitter { #[rustfmt::skip] use ink_e2e::{build_message, PolkadotConfig}; - use test_helpers::{ - address_of, - get_shares, - method_call, - }; + use test_helpers::{address_of, get_shares, method_call}; type E2EResult = Result>; diff --git a/examples/psp61/Cargo.toml b/examples/psp61/Cargo.toml new file mode 100644 index 000000000..6a7e4ffa8 --- /dev/null +++ b/examples/psp61/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "my_psp61" +version = "3.1.1" +authors = ["Brushfam "] +edition = "2021" + +[dependencies] +ink = { version = "4.2.1", default-features = false} + +scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } +scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true } + +# These dependencies +openbrush = { path = "../..", default-features = false, features = [ + "psp61", + "ownable", + "access_control", + "pausable", + "reentrancy_guard", + "upgradeable", + "payment_splitter" +] } + +[dev-dependencies] +ink_e2e = "4.2.1" +test_helpers = { path = "../test_helpers", default-features = false } + +[lib] +name = "my_psp61" +path = "lib.rs" + + +[features] +default = ["std"] +std = [ + "ink/std", + "scale/std", + "scale-info/std", + # These dependencies + "openbrush/std", +] +ink-as-dependency = [] +e2e-tests = [] + +[profile.dev] +codegen-units = 16 \ No newline at end of file diff --git a/examples/psp61/lib.rs b/examples/psp61/lib.rs new file mode 100644 index 000000000..9263d9477 --- /dev/null +++ b/examples/psp61/lib.rs @@ -0,0 +1,154 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[openbrush::implementation(PSP61, Ownable, AccessControl, Pausable, Upgradeable)] +#[openbrush::contract] +pub mod my_psp61 { + use ink::prelude::{ + vec, + vec::Vec, + }; + use openbrush::{ + contracts::supported_interfaces, + traits::{ + Storage, + String, + }, + }; + + #[ink(storage)] + #[derive(Default, Storage)] + pub struct Contract { + #[storage_field] + pub ownable: ownable::Data, + #[storage_field] + pub access_control: access_control::Data, + #[storage_field] + pub pausable: pausable::Data, + } + + supported_interfaces!(Contract); + + impl Contract { + #[ink(constructor)] + pub fn new() -> Self { + Self::default() + } + + #[ink(message)] + pub fn ownable_id(&self) -> u32 { + ownable::ownable_external::TRAIT_ID + } + + #[ink(message)] + pub fn access_control_id(&self) -> u32 { + access_control::accesscontrol_external::TRAIT_ID + } + + #[ink(message)] + pub fn pausable_id(&self) -> u32 { + pausable::pausable_external::TRAIT_ID + } + + #[ink(message)] + pub fn upgradeable_id(&self) -> u32 { + upgradeable::upgradeable_external::TRAIT_ID + } + + #[ink(message)] + pub fn psp61_id(&self) -> u32 { + psp61::psp61_external::TRAIT_ID + } + + #[ink(message)] + pub fn id_batch(&self) -> Vec<(String, u32)> { + vec![ + (String::from("ownable"), ownable::ownable_external::TRAIT_ID), + ( + String::from("access_control"), + access_control::accesscontrol_external::TRAIT_ID, + ), + (String::from("pausable"), pausable::pausable_external::TRAIT_ID), + (String::from("upgradeable"), upgradeable::upgradeable_external::TRAIT_ID), + (String::from("psp61"), psp61::psp61_external::TRAIT_ID), + ] + } + } + + #[cfg(test)] + mod tests { + use super::Contract; + use openbrush::contracts::{ + access_control, + ownable, + pausable, + psp61, + psp61::PSP61, + upgradeable, + }; + + #[ink::test] + fn assure_ids_are_proper() { + let contract = Contract::new(); + + assert_eq!(contract.ownable_id(), ownable::ownable_external::TRAIT_ID); + assert_eq!( + contract.access_control_id(), + access_control::accesscontrol_external::TRAIT_ID + ); + assert_eq!(contract.pausable_id(), pausable::pausable_external::TRAIT_ID); + assert_eq!(contract.upgradeable_id(), upgradeable::upgradeable_external::TRAIT_ID); + assert_eq!(contract.psp61_id(), psp61::psp61_external::TRAIT_ID); + } + + #[ink::test] + fn check_for_interfaces() { + let contract = Contract::new(); + + assert_eq!(contract.supports_interface(ownable::ownable_external::TRAIT_ID), true); + assert_eq!( + contract.supports_interface(access_control::accesscontrol_external::TRAIT_ID), + true + ); + assert_eq!(contract.supports_interface(pausable::pausable_external::TRAIT_ID), true); + assert_eq!( + contract.supports_interface(upgradeable::upgradeable_external::TRAIT_ID), + true + ); + assert_eq!(contract.supports_interface(psp61::psp61_external::TRAIT_ID), true); + } + + #[ink::test] + fn check_for_interfaces_batch() { + let contract = Contract::new(); + + let ids = contract.id_batch(); + let mut interfaces = contract.supported_interfaces(); + + let mut ids: Vec<_> = ids + .into_iter() + .map(|(_, id)| { + assert_eq!(contract.supports_interface(id), true); + id + }) + .collect(); + + ids.sort_unstable(); + interfaces.sort_unstable(); + + assert_eq!(ids, interfaces); + } + + #[ink::test] + fn check_for_non_existing_interface() { + let contract = Contract::new(); + + assert_eq!(contract.supports_interface(0), false); + + let interfaces = contract.supported_interfaces(); + + interfaces.into_iter().for_each(|id| { + assert_eq!(contract.supports_interface(id + 1), false); + }); + } + } +} diff --git a/lang/codegen/src/implementation.rs b/lang/codegen/src/implementation.rs index 6371bba60..b4187d83b 100644 --- a/lang/codegen/src/implementation.rs +++ b/lang/codegen/src/implementation.rs @@ -76,7 +76,7 @@ pub fn generate(attrs: TokenStream, ink_module: TokenStream) -> TokenStream { let mut impl_args = ImplArgs::new(&map, &mut items, &mut imports, &mut overriden_traits, ident); - for to_implement in args { + for to_implement in &args { match to_implement.as_str() { "PSP22" => impl_psp22(&mut impl_args), "PSP22Mintable" => impl_psp22_mintable(&mut impl_args), @@ -119,6 +119,7 @@ pub fn generate(attrs: TokenStream, ink_module: TokenStream) -> TokenStream { "GovernorQuorum" => impl_governor_quorum(&mut impl_args), "GovernorCounting" => impl_governor_counting(&mut impl_args), "Nonces" => impl_nonces(&mut impl_args), + "PSP61" => impl_psp61(&mut impl_args, args.clone()), _ => panic!("openbrush::implementation({to_implement}) not implemented!"), } } diff --git a/lang/codegen/src/implementations.rs b/lang/codegen/src/implementations.rs index 05aa3b9d5..ece13375d 100644 --- a/lang/codegen/src/implementations.rs +++ b/lang/codegen/src/implementations.rs @@ -2810,6 +2810,71 @@ pub(crate) fn impl_upgradeable(impl_args: &mut ImplArgs) { impl_args.items.push(syn::Item::Impl(upgradeable_impl)); } +pub(crate) fn impl_psp61(impl_args: &mut ImplArgs, impls: Vec) { + let storage_struct_name = impl_args.contract_name(); + let psp61_impl = syn::parse2::(quote!( + impl PSP61Impl for #storage_struct_name {} + )) + .expect("Should parse"); + + let psp61 = syn::parse2::(quote!( + impl PSP61 for #storage_struct_name { + #[ink(message)] + fn supports_interface(&self, interface_id: u32) -> bool { + PSP61Impl::supports_interface(self, interface_id) + } + + #[ink(message)] + fn supported_interfaces(&self) -> ::ink::prelude::vec::Vec { + PSP61Impl::supported_interfaces(self) + } + } + )) + .expect("Should parse"); + + let traits_implemented: Vec<_> = impls + .into_iter() + .map(|args| { + let mut trait_name_lower = args.to_lowercase(); + + trait_name_lower = match trait_name_lower.clone().as_str() { + "flashmint" => String::from("flashlender"), + _ => trait_name_lower, + }; + + let trait_name = format!("{}_external", trait_name_lower); + + let ident = format_ident!("{}", trait_name); + + let tokens = quote!(#ident::TRAIT_ID); + + tokens + }) + .collect(); + + let psp61_internal_ob = syn::parse2::(quote!( + impl PSP61InternalOB for #storage_struct_name { + fn _interfaces_ob(&self) -> ::ink::prelude::vec::Vec { + ::ink::prelude::vec![ + #(#traits_implemented),* + ] + } + } + )) + .expect("Should parse"); + + let import = syn::parse2::(quote!( + use openbrush::contracts::psp61::*; + )) + .expect("Should parse"); + + impl_args.imports.insert("PSP61", import); + + impl_args.items.push(syn::Item::Impl(psp61)); + impl_args.items.push(syn::Item::Impl(psp61_impl)); + impl_args.items.push(syn::Item::Impl(psp61_internal_ob)); +} + pub(crate) fn impl_governor_settings(impl_args: &mut ImplArgs) { let storage_struct_name = impl_args.contract_name(); let governor_settings_events = syn::parse2::(quote!( diff --git a/lang/codegen/src/trait_definition.rs b/lang/codegen/src/trait_definition.rs index d1c284e08..7841be04c 100644 --- a/lang/codegen/src/trait_definition.rs +++ b/lang/codegen/src/trait_definition.rs @@ -171,6 +171,8 @@ fn generate_wrapper(ink_trait: ItemTrait) -> proc_macro2::TokenStream { let trait_wrapper_ident = format_ident!("{}Wrapper", ink_trait.ident); let mut def_messages = vec![]; let mut impl_messages = vec![]; + let mut message_selectors = vec![]; + ink_trait .items .into_iter() @@ -190,7 +192,7 @@ fn generate_wrapper(ink_trait: ItemTrait) -> proc_macro2::TokenStream { }; let selector_string = format!("{}::{}", trait_ident, message_ident); - let selector_bytes = ::ink_ir::Selector::compute(&selector_string.into_bytes()).hex_lits(); + let selector_bytes = ::ink_ir::Selector::compute(&selector_string.clone().into_bytes()).hex_lits(); let input_bindings = method .sig .inputs @@ -284,10 +286,17 @@ fn generate_wrapper(ink_trait: ItemTrait) -> proc_macro2::TokenStream { .returns::<#output_ty>() } }); + + message_selectors.push(selector_string); }); + let impl_messages = impl_messages.iter(); let def_messages = def_messages.iter(); + message_selectors.sort_unstable(); + + let trait_id = ::ink_ir::Selector::compute(&message_selectors.join("").into_bytes()).into_be_u32(); + quote! { pub trait #trait_wrapper_ident { #( #def_messages )* @@ -296,6 +305,8 @@ fn generate_wrapper(ink_trait: ItemTrait) -> proc_macro2::TokenStream { impl #trait_wrapper_ident for ::openbrush::traits::AccountId { #( #impl_messages )* } + + pub const TRAIT_ID: u32 = #trait_id; } }