diff --git a/Cargo.lock b/Cargo.lock index c5bbf335..26c55acf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9034,6 +9034,7 @@ name = "pop-api" version = "0.0.0" dependencies = [ "ink", + "ink_env", "parity-scale-codec", "scale-info", "sp-runtime 24.0.0", @@ -9108,6 +9109,7 @@ dependencies = [ "cumulus-pallet-xcmp-queue", "cumulus-primitives-core", "cumulus-primitives-utility", + "enumflags2", "env_logger 0.11.2", "frame-benchmarking", "frame-executive", diff --git a/contracts/pop-api-examples/nfts/Cargo.toml b/contracts/pop-api-examples/nfts/Cargo.toml index 5af5a189..282d9a82 100755 --- a/contracts/pop-api-examples/nfts/Cargo.toml +++ b/contracts/pop-api-examples/nfts/Cargo.toml @@ -6,12 +6,9 @@ edition = "2021" [dependencies] ink = { version = "4.3.0", default-features = false } - -pop-api = { path = "../../../pop-api", default-features = false} - +pop-api = { path = "../../../pop-api", 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 } - sp-io = { version = "23.0.0", default-features = false, features = ["disable_panic_handler", "disable_oom", "disable_allocator"] } sp-runtime = { version = "24.0.0", default-features = false } diff --git a/contracts/pop-api-examples/nfts/lib.rs b/contracts/pop-api-examples/nfts/lib.rs index 91d0996a..df8bb9d1 100755 --- a/contracts/pop-api-examples/nfts/lib.rs +++ b/contracts/pop-api-examples/nfts/lib.rs @@ -1,21 +1,22 @@ #![cfg_attr(not(feature = "std"), no_std, no_main)] -use ink::{ - env::Environment, - prelude::vec::Vec, -}; +use pop_api::PopApiError; -use ink::primitives::AccountId; -use sp_runtime::MultiAddress; -use pop_api::impls::pop_network::PopEnv; -use pop_api::impls::pop_network::PopApiError; +#[derive(Debug, Copy, Clone, PartialEq, Eq, scale::Encode, scale::Decode)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] +pub enum ContractError { + SomeError, +} -#[ink::contract(env = crate::PopEnv)] -mod pop_api_extension_demo { +impl From for ContractError { + fn from(_value: PopApiError) -> Self { + ContractError::SomeError + } +} - use super::PopApiError; - use scale::Encode; - use ink::env::Error as EnvError; +#[ink::contract(env = pop_api::PopEnv)] +mod pop_api_extension_demo { + use super::ContractError; #[ink(storage)] #[derive(Default)] @@ -34,14 +35,24 @@ mod pop_api_extension_demo { collection_id: u32, item_id: u32, receiver: AccountId, - ) { + ) -> Result<(), ContractError> { ink::env::debug_println!("PopApiExtensionDemo::mint_through_runtime: collection_id: {:?} \nitem_id {:?} \nreceiver: {:?}, ", collection_id, item_id, receiver); - let call = pop_api::impls::pop_network::Nfts::mint(collection_id, item_id, receiver); - self.env().extension().dispatch(call); + // simplified API call + pop_api::nfts::mint(collection_id, item_id, receiver)?; ink::env::debug_println!("PopApiExtensionDemo::mint_through_runtime end"); - + Ok(()) + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[ink::test] + fn default_works() { + PopApiExtensionDemo::new(); } } -} \ No newline at end of file +} diff --git a/pop-api/Cargo.toml b/pop-api/Cargo.toml index 0b9d6d9e..ee581ba6 100644 --- a/pop-api/Cargo.toml +++ b/pop-api/Cargo.toml @@ -7,9 +7,8 @@ edition = "2021" [dependencies] ink = { version = "4.3.0", default-features = false } -# pallet-nfts = {version = "22.0.0", default-features = false} +ink_env = { version = "4.3.0", default-features = false } sp-runtime = { version = "24.0.0", 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 } @@ -20,12 +19,10 @@ crate-type = [ "rlib", ] - [features] default = ["std"] std = [ "ink/std", - # "pallet-nfts/std", "sp-runtime/std", "scale/std", "scale-info/std", diff --git a/pop-api/src/impls/mod.rs b/pop-api/src/impls/mod.rs deleted file mode 100644 index e5dd5f52..00000000 --- a/pop-api/src/impls/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ - -pub mod pop_network; \ No newline at end of file diff --git a/pop-api/src/impls/pop_network.rs b/pop-api/src/impls/pop_network.rs deleted file mode 100644 index 6d5931e8..00000000 --- a/pop-api/src/impls/pop_network.rs +++ /dev/null @@ -1,100 +0,0 @@ -use crate::interfaces::nfts::NftCalls; -use sp_runtime::{MultiAddress, MultiSignature}; -use ink::prelude::vec::Vec; -use scale; - -use ink::env::Environment; - -// Id used for identifying non-fungible collections. -pub type CollectionId = u32; - -// Id used for identifying non-fungible items. -pub type ItemId = u32; - -type AccountId = ::AccountId; -type Balance = ::Balance; -type BlockNumber = ::BlockNumber; - -type Signature = MultiSignature; - -type StringLimit = u32; -type KeyLimit = u32; -type MaxTips = u32; - - -pub struct Pop { - pub api: Api -} - -pub struct Api { - nft: Nfts -} - -pub type NftCallsOf = NftCalls; - -pub struct Nfts; - -impl Nfts { - // pub fn create(&self, admin: MultiAddress, config: CollectionConfig) -> RuntimeCall { - // RuntimeCall::Nfts(NftCallsOf::Create { admin, config }) - // } - - pub fn mint(collection: CollectionId, item: ItemId, mint_to: impl Into< MultiAddress >) -> RuntimeCall { - RuntimeCall::Nfts(NftCallsOf::Mint { collection, item, mint_to: mint_to.into(), witness_data: None}) - } -} - -#[derive(scale::Encode)] -pub enum RuntimeCall { - #[codec(index = 50)] - Nfts(NftCalls) -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, scale::Encode, scale::Decode)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] -pub enum PopApiError { - PlaceholderError, -} - -pub type Result = core::result::Result; - -#[ink::chain_extension] -pub trait PopApi { - type ErrorCode = PopApiError; - - #[ink(extension = 0xfecb)] - fn dispatch(call: RuntimeCall) -> Result>; -} - -impl ink::env::chain_extension::FromStatusCode for PopApiError { - fn from_status_code(status_code: u32) -> core::result::Result<(), Self> { - match status_code { - 0 => Ok(()), - 1 => Err(Self::PlaceholderError), - _ => panic!("encountered unknown status code"), - } - } -} - -impl From for PopApiError { - fn from(_: scale::Error) -> Self { - panic!("encountered unexpected invalid SCALE encoding") - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] -pub enum PopEnv {} - -impl Environment for PopEnv { - const MAX_EVENT_TOPICS: usize = - ::MAX_EVENT_TOPICS; - - type AccountId = ::AccountId; - type Balance = ::Balance; - type Hash = ::Hash; - type BlockNumber = ::BlockNumber; - type Timestamp = ::Timestamp; - - type ChainExtension = PopApi; -} \ No newline at end of file diff --git a/pop-api/src/interfaces/mod.rs b/pop-api/src/interfaces/mod.rs deleted file mode 100644 index b0a3d1e9..00000000 --- a/pop-api/src/interfaces/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod nfts; \ No newline at end of file diff --git a/pop-api/src/lib.rs b/pop-api/src/lib.rs index fe1fe378..0f9f7f70 100644 --- a/pop-api/src/lib.rs +++ b/pop-api/src/lib.rs @@ -1,4 +1,77 @@ #![cfg_attr(not(feature = "std"), no_std, no_main)] -pub mod interfaces; -pub mod impls; \ No newline at end of file +pub mod v0; + +use ink::{env::Environment, prelude::vec::Vec, ChainExtensionInstance}; +use scale; +use sp_runtime::MultiSignature; +pub use v0::nfts; +use v0::RuntimeCall; + +// Id used for identifying non-fungible collections. +pub type CollectionId = u32; + +// Id used for identifying non-fungible items. +pub type ItemId = u32; + +type AccountId = ::AccountId; +type Balance = ::Balance; +type BlockNumber = ::BlockNumber; +type Signature = MultiSignature; +type StringLimit = u32; +type KeyLimit = u32; +type MaxTips = u32; + +pub type Result = core::result::Result; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, scale::Encode, scale::Decode)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] +pub enum PopApiError { + PlaceholderError, +} + +impl ink::env::chain_extension::FromStatusCode for PopApiError { + fn from_status_code(status_code: u32) -> core::result::Result<(), Self> { + match status_code { + 0 => Ok(()), + 1 => Err(Self::PlaceholderError), + _ => panic!("encountered unknown status code"), + } + } +} + +impl From for PopApiError { + fn from(_: scale::Error) -> Self { + panic!("encountered unexpected invalid SCALE encoding") + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] +pub enum PopEnv {} + +impl Environment for PopEnv { + const MAX_EVENT_TOPICS: usize = ::MAX_EVENT_TOPICS; + + type AccountId = ::AccountId; + type Balance = ::Balance; + type Hash = ::Hash; + type BlockNumber = ::BlockNumber; + type Timestamp = ::Timestamp; + + type ChainExtension = PopApi; +} + +#[ink::chain_extension] +pub trait PopApi { + type ErrorCode = PopApiError; + + #[ink(extension = 0xfecb)] + #[allow(private_interfaces)] + fn dispatch(call: RuntimeCall) -> crate::Result>; +} + +fn call_runtime(call: RuntimeCall) -> Result> { + <::ChainExtension as ChainExtensionInstance>::instantiate() + .dispatch(call) +} diff --git a/pop-api/src/v0/balances.rs b/pop-api/src/v0/balances.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/pop-api/src/v0/balances.rs @@ -0,0 +1 @@ + diff --git a/pop-api/src/v0/mod.rs b/pop-api/src/v0/mod.rs new file mode 100644 index 00000000..9692c233 --- /dev/null +++ b/pop-api/src/v0/mod.rs @@ -0,0 +1,8 @@ +pub mod balances; +pub mod nfts; + +#[derive(scale::Encode)] +pub(crate) enum RuntimeCall { + #[codec(index = 50)] + Nfts(nfts::NftCalls), +} diff --git a/pop-api/src/interfaces/nfts.rs b/pop-api/src/v0/nfts.rs similarity index 89% rename from pop-api/src/interfaces/nfts.rs rename to pop-api/src/v0/nfts.rs index 358e6e05..127f393b 100644 --- a/pop-api/src/interfaces/nfts.rs +++ b/pop-api/src/v0/nfts.rs @@ -1,23 +1,37 @@ -use sp_runtime::{BoundedVec, MultiAddress}; +use super::RuntimeCall; +use crate::*; use ink::prelude::vec::Vec; +use sp_runtime::{BoundedVec, MultiAddress}; + +pub fn mint( + collection: CollectionId, + item: ItemId, + mint_to: impl Into>, +) -> Result<()> { + crate::call_runtime(RuntimeCall::Nfts(NftCalls::Mint { + collection, + item, + mint_to: mint_to.into(), + witness_data: None, + }))?; + Ok(()) +} #[derive(scale::Encode)] -pub enum NftCalls { +pub(crate) enum NftCalls { // #[codec(index = 0)] // Create { // admin: MultiAddress, // config: CollectionConfig // }, #[codec(index = 2)] - Destroy { - collection: CollectionId - }, + Destroy { collection: CollectionId }, #[codec(index = 3)] Mint { collection: CollectionId, item: ItemId, mint_to: MultiAddress, - witness_data: Option<()> + witness_data: Option<()>, }, #[codec(index = 5)] Burn { @@ -28,12 +42,12 @@ pub enum NftCalls + dest: MultiAddress, }, #[codec(index = 7)] Redeposit { collection: CollectionId, - items: Vec + items: Vec, }, #[codec(index = 8)] LockItemTransfer { @@ -53,7 +67,7 @@ pub enum NftCalls + new_owner: MultiAddress, }, #[codec(index = 12)] SetTeam { @@ -131,9 +145,7 @@ pub enum NftCalls, }, #[codec(index = 27)] - ClearCollectionMetadata { - collection: CollectionId, - }, + ClearCollectionMetadata { collection: CollectionId }, #[codec(index = 28)] SetAcceptOwnership { collection: CollectionId, @@ -198,4 +210,4 @@ pub enum NftCalls(path: &str) -> std::io::Result<(Vec, ::Output)> + pub fn load_wasm_module( + path: &str, + ) -> std::io::Result<(Vec, ::Output)> where T: frame_system::Config, { @@ -161,6 +166,21 @@ mod tests { [hash[0..4].to_vec()].concat() } + // NFT helper functions + fn collection_config_from_disabled_settings( + settings: BitFlags, + ) -> CollectionConfig { + CollectionConfig { + settings: CollectionSettings::from_disabled(settings), + max_supply: None, + mint_settings: MintSettings::default(), + } + } + + fn default_collection_config() -> CollectionConfig { + collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into()) + } + #[test] fn test_dispatch() { new_test_ext().execute_with(|| { @@ -226,4 +246,92 @@ mod tests { assert_eq!(bob_balance_before + value_to_send, bob_balance_after); }); } + + #[test] + fn test_nfts_mint() { + new_test_ext().execute_with(|| { + let _ = env_logger::try_init(); + + let (wasm_binary, _) = load_wasm_module::( + "../contracts/pop-api-examples/nfts/target/ink/pop_api_nft_example.wasm", + ) + .unwrap(); + + let init_value = 100; + + let result = Contracts::bare_instantiate( + ALICE, + init_value, + GAS_LIMIT, + None, + Code::Upload(wasm_binary), + function_selector("new"), + vec![], + DEBUG_OUTPUT, + pallet_contracts::CollectEvents::Skip, + ) + .result + .unwrap(); + + assert!( + !result.result.did_revert(), + "deploying contract reverted {:?}", + result + ); + + let addr = result.account_id; + + let collection_id: u32 = 0; + let item_id: u32 = 1; + + // create nft collection + assert_eq!( + Nfts::force_create( + RuntimeOrigin::root(), + ALICE.into(), + default_collection_config() + ), + Ok(()) + ); + + assert_eq!(Nfts::collection_owner(collection_id), Some(ALICE.into())); + // assert that the item does not exist yet + assert_eq!(Nfts::owner(collection_id, item_id), None); + + let function = function_selector("mint_through_runtime"); + + let params = [ + function, + collection_id.encode(), + item_id.encode(), + BOB.encode(), + ] + .concat(); + + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + Weight::from_parts(100_000_000_000, 3 * 1024 * 1024), + None, + params, + DEBUG_OUTPUT, + pallet_contracts::CollectEvents::Skip, + pallet_contracts::Determinism::Enforced, + ); + + if DEBUG_OUTPUT == pallet_contracts::DebugInfo::UnsafeDebug { + log::debug!( + "Contract debug buffer - {:?}", + String::from_utf8(result.debug_message.clone()) + ); + log::debug!("result: {:?}", result); + } + + // check for revert + assert!(!result.result.unwrap().did_revert(), "Contract reverted!"); + + assert_eq!(Nfts::owner(collection_id, item_id), Some(BOB.into())); + }); + } }