From d55622eef180ab844c13cdafba6fde20e17f88b4 Mon Sep 17 00:00:00 2001 From: Oline Tramp Date: Tue, 18 Jun 2024 23:43:35 +0200 Subject: [PATCH 1/4] feat(contracts): enrich nft 4.2.2 metadata support --- .../src/tip4_2_2/collection_contract.rs | 20 ++++++ nekoton-contracts/src/tip4_2_2/mod.rs | 11 +++- src/core/nft_wallet/mod.rs | 64 +++++++++++++++---- 3 files changed, 80 insertions(+), 15 deletions(-) diff --git a/nekoton-contracts/src/tip4_2_2/collection_contract.rs b/nekoton-contracts/src/tip4_2_2/collection_contract.rs index b75046bad..6928a2de7 100644 --- a/nekoton-contracts/src/tip4_2_2/collection_contract.rs +++ b/nekoton-contracts/src/tip4_2_2/collection_contract.rs @@ -24,3 +24,23 @@ pub fn get_nft_url() -> &'static ton_abi::Function { outputs: vec![Param::new("nftUrl", ParamType::String)] } } + +/// Return url to metadata for NFT collection. +/// +/// # Type +/// Responsible getter method +/// +/// # Inputs +/// * `answerId: uint32` - responsible answer id +/// +/// # Outputs +/// * `collectionUrl: string` - NFT collection metadata URL +pub fn get_collection_url() -> &'static ton_abi::Function { + declare_function! { + name: "get_collection_url", + inputs: vec![ + Param::new("answerId", ParamType::Uint(32)), + ], + outputs: vec![Param::new("collectionUrl", ParamType::String)] + } +} diff --git a/nekoton-contracts/src/tip4_2_2/mod.rs b/nekoton-contracts/src/tip4_2_2/mod.rs index 3c3240cd7..5c2f42930 100644 --- a/nekoton-contracts/src/tip4_2_2/mod.rs +++ b/nekoton-contracts/src/tip4_2_2/mod.rs @@ -10,7 +10,7 @@ pub mod metadata_contract; pub struct MetadataContract<'a>(pub ExecutionContext<'a>); impl MetadataContract<'_> { - pub fn get_url_parts(&self) -> Result { + pub fn get_url_parts(&self) -> Result { let inputs = [0u32.token_value().named("answerId")]; let result = self .0 @@ -35,4 +35,13 @@ impl CollectionContract<'_> { .unpack_first()?; Ok(result) } + + pub fn get_collection_url(&self) -> Result { + let inputs = [0u32.token_value().named("answerId")]; + let result = self + .0 + .run_local_responsible_simple(collection_contract::get_collection_url(), &inputs)? + .unpack_first()?; + Ok(result) + } } diff --git a/src/core/nft_wallet/mod.rs b/src/core/nft_wallet/mod.rs index 91d75fa81..955adfb87 100644 --- a/src/core/nft_wallet/mod.rs +++ b/src/core/nft_wallet/mod.rs @@ -25,7 +25,9 @@ pub struct NftCollection { collection_address: MsgAddressInt, state: ExistingContract, index_code: Cell, + interfaces: CollectionInterfaces, json_info: Option, + json_url: Option, } impl NftCollection { @@ -48,9 +50,14 @@ impl NftCollection { let index_code = contract.resolve_collection_index_code(clock)?; + let ctx = state.as_context(clock); let json_info = interfaces .tip4_2 - .then(|| tip4_2::MetadataContract(state.as_context(clock)).get_json()) + .then(|| tip4_2::MetadataContract(ctx).get_json()) + .transpose()?; + let json_url = interfaces + .tip4_2_2 + .then(|| tip4_2_2::CollectionContract(ctx).get_collection_url()) .transpose()?; Ok(NftCollection { @@ -58,7 +65,9 @@ impl NftCollection { collection_address, state, index_code, + interfaces, json_info, + json_url, }) } @@ -74,6 +83,10 @@ impl NftCollection { &self.json_info } + pub fn json_url(&self) -> &Option { + &self.json_url + } + pub fn compute_collection_code_hash(&self, owner: &MsgAddressInt) -> Result { CollectionContractState(&self.state) .get_collection_code_hash(owner, self.index_code.clone()) @@ -90,6 +103,10 @@ impl NftCollection { .get_accounts_by_code_hash(&code_hash, limit, &continuation) .await } + + pub fn interfaces(&self) -> &CollectionInterfaces { + &self.interfaces + } } pub struct Nft { @@ -100,6 +117,7 @@ pub struct Nft { manager: MsgAddressInt, interfaces: NftInterfaces, json_info: Option, + json_url: Option, contract_subscription: ContractSubscription, handler: Arc, } @@ -149,11 +167,15 @@ impl Nft { // NOTE: Assume that TIP4.1 support is mandatory let mut info = nft_state.get_info(clock.as_ref())?; - let json_info = if interfaces.tip4_2 { - Some(nft_state.get_json(clock.as_ref())?) - } else { - None - }; + let json_info = interfaces + .tip4_2 + .then(|| nft_state.get_json(clock.as_ref())) + .transpose()?; + + let json_url = interfaces + .tip4_2_2 + .then(|| nft_state.get_json_url(clock.as_ref())) + .transpose()?; let contract_subscription = ContractSubscription::subscribe( clock.clone(), @@ -180,6 +202,7 @@ impl Nft { manager: info.manager, interfaces, json_info, + json_url, contract_subscription, handler, }) @@ -213,6 +236,10 @@ impl Nft { &self.json_info } + pub fn metadata_url(&self) -> &Option { + &self.json_url + } + pub fn prepare_transfer( &self, to: MsgAddressInt, @@ -447,6 +474,8 @@ impl<'a> CollectionContractState<'a> { if tip6_interface.supports_interface(tip4_3::collection_contract::INTERFACE_ID)? { result.tip4_3 = true; + result.tip4_2_2 = + tip6_interface.supports_interface(tip4_2_2::collection_contract::INTERFACE_ID)?; result.tip4_2 = tip6_interface.supports_interface(tip4_2::metadata_contract::INTERFACE_ID)?; } @@ -487,6 +516,7 @@ impl<'a> CollectionContractState<'a> { #[derive(Copy, Clone, Default)] pub struct CollectionInterfaces { pub tip4_3: bool, + pub tip4_2_2: bool, pub tip4_2: bool, } @@ -506,15 +536,15 @@ impl<'a> NftContractState<'a> { let ctx = self.0.as_context(clock); let tip6_interface = tip6::SidContract(ctx); - let mut result = NftInterfaces::default(); - result.tip4_1 = tip6_interface.supports_interface(tip4_1::nft_contract::INTERFACE_ID)?; - result.tip4_2 = - tip6_interface.supports_interface(tip4_2::metadata_contract::INTERFACE_ID)?; - result.tip4_2_2 = - tip6_interface.supports_interface(tip4_2_2::metadata_contract::INTERFACE_ID)?; - result.tip4_3 = tip6_interface.supports_interface(tip4_3::nft_contract::INTERFACE_ID)?; + let result = NftInterfaces { + tip4_1: tip6_interface.supports_interface(tip4_1::nft_contract::INTERFACE_ID)?, + tip4_2: tip6_interface.supports_interface(tip4_2::metadata_contract::INTERFACE_ID)?, + tip4_2_2: tip6_interface + .supports_interface(tip4_2_2::metadata_contract::INTERFACE_ID)?, + tip4_3: tip6_interface.supports_interface(tip4_3::nft_contract::INTERFACE_ID)?, + }; - Ok((result.tip4_1 || result.tip4_3).then(|| result)) + Ok((result.tip4_1 || result.tip4_3).then_some(result)) } pub fn get_json(&self, clock: &dyn Clock) -> Result { @@ -523,6 +553,12 @@ impl<'a> NftContractState<'a> { tip4_2_interface.get_json() } + pub fn get_json_url(&self, clock: &dyn Clock) -> Result { + let ctx = self.0.as_context(clock); + let part = tip4_2_2::MetadataContract(ctx).get_url_parts()?; + tip4_2_2::CollectionContract(ctx).get_nft_url(part) + } + pub fn get_info(&self, clock: &dyn Clock) -> Result { let ctx = self.0.as_context(clock); let tip4_1_interface = tip4_1::NftContract(ctx); From 680111627ac6179403db20948d2faafde9df64f7 Mon Sep 17 00:00:00 2001 From: Oline Tramp Date: Wed, 19 Jun 2024 16:45:42 +0200 Subject: [PATCH 2/4] refactor(contracts): merge json_info and json_url, fix contract calls for json url --- src/core/nft_wallet/mod.rs | 67 +++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/src/core/nft_wallet/mod.rs b/src/core/nft_wallet/mod.rs index 955adfb87..b38aa43db 100644 --- a/src/core/nft_wallet/mod.rs +++ b/src/core/nft_wallet/mod.rs @@ -26,8 +26,12 @@ pub struct NftCollection { state: ExistingContract, index_code: Cell, interfaces: CollectionInterfaces, - json_info: Option, - json_url: Option, + json_info: Option, +} + +pub enum JsonInfo { + Json(String), + Url(String), } impl NftCollection { @@ -51,14 +55,19 @@ impl NftCollection { let index_code = contract.resolve_collection_index_code(clock)?; let ctx = state.as_context(clock); - let json_info = interfaces + let json_data = interfaces .tip4_2 .then(|| tip4_2::MetadataContract(ctx).get_json()) - .transpose()?; + .transpose()? + .map(JsonInfo::Json); + let json_url = interfaces .tip4_2_2 .then(|| tip4_2_2::CollectionContract(ctx).get_collection_url()) - .transpose()?; + .transpose()? + .map(JsonInfo::Url); + + let json_info = json_data.or(json_url); Ok(NftCollection { transport, @@ -67,7 +76,6 @@ impl NftCollection { index_code, interfaces, json_info, - json_url, }) } @@ -79,14 +87,10 @@ impl NftCollection { &self.index_code } - pub fn json_info(&self) -> &Option { + pub fn json_info(&self) -> &Option { &self.json_info } - pub fn json_url(&self) -> &Option { - &self.json_url - } - pub fn compute_collection_code_hash(&self, owner: &MsgAddressInt) -> Result { CollectionContractState(&self.state) .get_collection_code_hash(owner, self.index_code.clone()) @@ -116,8 +120,7 @@ pub struct Nft { owner: MsgAddressInt, manager: MsgAddressInt, interfaces: NftInterfaces, - json_info: Option, - json_url: Option, + json_info: Option, contract_subscription: ContractSubscription, handler: Arc, } @@ -167,16 +170,28 @@ impl Nft { // NOTE: Assume that TIP4.1 support is mandatory let mut info = nft_state.get_info(clock.as_ref())?; - let json_info = interfaces + + let collection_contract = match transport.get_contract_state(&info.collection).await? { + RawContractState::Exists(state) => state, + RawContractState::NotExists { .. } => return Err(NftError::ContractNotExist.into()), + }; + + let collection_state = CollectionContractState(&collection_contract); + + let json_data = interfaces .tip4_2 .then(|| nft_state.get_json(clock.as_ref())) .transpose()?; let json_url = interfaces .tip4_2_2 - .then(|| nft_state.get_json_url(clock.as_ref())) + .then(|| nft_state.get_url_parts(clock.as_ref())) + .transpose()? + .map(|part| collection_state.get_nft_url(clock.as_ref(), part)) .transpose()?; + let json_info = json_data.or(json_url); + let contract_subscription = ContractSubscription::subscribe( clock.clone(), transport, @@ -202,7 +217,6 @@ impl Nft { manager: info.manager, interfaces, json_info, - json_url, contract_subscription, handler, }) @@ -232,14 +246,10 @@ impl Nft { &self.manager } - pub fn metadata(&self) -> &Option { + pub fn metadata(&self) -> &Option { &self.json_info } - pub fn metadata_url(&self) -> &Option { - &self.json_url - } - pub fn prepare_transfer( &self, to: MsgAddressInt, @@ -511,6 +521,12 @@ impl<'a> CollectionContractState<'a> { let cell = nekoton_abi::set_code_salt(code_index, salt)?; Ok(cell.hash(0)) } + + pub fn get_nft_url(&self, clock: &dyn Clock, part: Cell) -> Result { + let ctx = self.0.as_context(clock); + let tip4_2_2_interface = tip4_2_2::CollectionContract(ctx); + tip4_2_2_interface.get_nft_url(part).map(JsonInfo::Url) + } } #[derive(Copy, Clone, Default)] @@ -547,16 +563,15 @@ impl<'a> NftContractState<'a> { Ok((result.tip4_1 || result.tip4_3).then_some(result)) } - pub fn get_json(&self, clock: &dyn Clock) -> Result { + pub fn get_json(&self, clock: &dyn Clock) -> Result { let ctx = self.0.as_context(clock); let tip4_2_interface = tip4_2::MetadataContract(ctx); - tip4_2_interface.get_json() + tip4_2_interface.get_json().map(JsonInfo::Json) } - pub fn get_json_url(&self, clock: &dyn Clock) -> Result { + pub fn get_url_parts(&self, clock: &dyn Clock) -> Result { let ctx = self.0.as_context(clock); - let part = tip4_2_2::MetadataContract(ctx).get_url_parts()?; - tip4_2_2::CollectionContract(ctx).get_nft_url(part) + tip4_2_2::MetadataContract(ctx).get_url_parts() } pub fn get_info(&self, clock: &dyn Clock) -> Result { From 20196aba5dea6a87299455990cab5320b9742df6 Mon Sep 17 00:00:00 2001 From: Oline Tramp Date: Fri, 28 Jun 2024 17:03:49 +0200 Subject: [PATCH 3/4] fix(contracts): use correct collection url function name --- nekoton-contracts/src/tip4_2_2/collection_contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nekoton-contracts/src/tip4_2_2/collection_contract.rs b/nekoton-contracts/src/tip4_2_2/collection_contract.rs index 6928a2de7..098e9deed 100644 --- a/nekoton-contracts/src/tip4_2_2/collection_contract.rs +++ b/nekoton-contracts/src/tip4_2_2/collection_contract.rs @@ -37,7 +37,7 @@ pub fn get_nft_url() -> &'static ton_abi::Function { /// * `collectionUrl: string` - NFT collection metadata URL pub fn get_collection_url() -> &'static ton_abi::Function { declare_function! { - name: "get_collection_url", + name: "getCollectionUrl", inputs: vec![ Param::new("answerId", ParamType::Uint(32)), ], From 3000393fb5dc018f2fca18e4bf41ffe4ad2eb6a7 Mon Sep 17 00:00:00 2001 From: Oline Tramp Date: Fri, 28 Jun 2024 18:30:10 +0200 Subject: [PATCH 4/4] feat(contracts): add ownable internal contract --- nekoton-contracts/src/access/mod.rs | 19 +++++++++++++++ .../src/access/ownable_internal_contract.rs | 23 +++++++++++++++++++ nekoton-contracts/src/lib.rs | 1 + src/core/nft_wallet/mod.rs | 9 ++++++++ 4 files changed, 52 insertions(+) create mode 100644 nekoton-contracts/src/access/mod.rs create mode 100644 nekoton-contracts/src/access/ownable_internal_contract.rs diff --git a/nekoton-contracts/src/access/mod.rs b/nekoton-contracts/src/access/mod.rs new file mode 100644 index 000000000..72d8c8a64 --- /dev/null +++ b/nekoton-contracts/src/access/mod.rs @@ -0,0 +1,19 @@ +use anyhow::Result; +use nekoton_abi::*; + +use super::RunLocalSimple; + +pub mod ownable_internal_contract; + +#[derive(Copy, Clone)] +pub struct OwnableInternalContract<'a>(pub ExecutionContext<'a>); + +impl OwnableInternalContract<'_> { + pub fn owner(&self) -> Result { + let result = self + .0 + .run_local_simple(ownable_internal_contract::owner(), &[])? + .unpack_first()?; + Ok(result) + } +} diff --git a/nekoton-contracts/src/access/ownable_internal_contract.rs b/nekoton-contracts/src/access/ownable_internal_contract.rs new file mode 100644 index 000000000..79179dcf0 --- /dev/null +++ b/nekoton-contracts/src/access/ownable_internal_contract.rs @@ -0,0 +1,23 @@ +use ton_abi::{Param, ParamType}; + +use crate::utils::declare_function; + +/// Returns collection owner address +/// +/// # Type +/// Simple getter method +/// +/// # Inputs +/// No inputs +/// +/// # Outputs +/// * `value0: address` - Address of NFT contract +/// +pub fn owner() -> &'static ton_abi::Function { + declare_function! { + header: [pubkey, time, expire], + name: "owner", + inputs: vec![], + outputs: vec![Param::new("value0", ParamType::Address)], + } +} diff --git a/nekoton-contracts/src/lib.rs b/nekoton-contracts/src/lib.rs index 1210886f2..4838bd13b 100644 --- a/nekoton-contracts/src/lib.rs +++ b/nekoton-contracts/src/lib.rs @@ -54,6 +54,7 @@ use anyhow::Result; use nekoton_abi::{ExecutionContext, ExecutionOutput}; +pub mod access; pub mod dens; pub mod old_tip3; pub mod tip1155; diff --git a/src/core/nft_wallet/mod.rs b/src/core/nft_wallet/mod.rs index b38aa43db..efe2bdf13 100644 --- a/src/core/nft_wallet/mod.rs +++ b/src/core/nft_wallet/mod.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use anyhow::Result; use nekoton_abi::MessageBuilder; +use nekoton_contracts::access::OwnableInternalContract; use nekoton_contracts::tip4_1::nft_contract::{self, GetInfoOutputs, NftCallbackPayload}; use nekoton_contracts::tip4_3::index_contract::IndexGetInfoOutputs; use nekoton_contracts::*; @@ -27,6 +28,7 @@ pub struct NftCollection { index_code: Cell, interfaces: CollectionInterfaces, json_info: Option, + owner: Option, } pub enum JsonInfo { @@ -69,6 +71,8 @@ impl NftCollection { let json_info = json_data.or(json_url); + let owner = OwnableInternalContract(ctx).owner().ok(); + Ok(NftCollection { transport, collection_address, @@ -76,6 +80,7 @@ impl NftCollection { index_code, interfaces, json_info, + owner, }) } @@ -91,6 +96,10 @@ impl NftCollection { &self.json_info } + pub fn owner(&self) -> &Option { + &self.owner + } + pub fn compute_collection_code_hash(&self, owner: &MsgAddressInt) -> Result { CollectionContractState(&self.state) .get_collection_code_hash(owner, self.index_code.clone())