git.delta.rocks / unique-network / refs/commits / 2df3f6b6e145

difftreelog

source

client/rpc/src/lib.rs20.1 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617// Original License18use std::sync::Arc;1920use anyhow::anyhow;21use app_promotion_rpc::AppPromotionApi as AppPromotionRuntimeApi;22pub use app_promotion_unique_rpc::AppPromotionApiServer;23use jsonrpsee::{core::RpcResult as Result, proc_macros::rpc};24use parity_scale_codec::Decode;25use sp_api::{ApiExt, BlockT, ProvideRuntimeApi};26use sp_blockchain::HeaderBackend;27use sp_runtime::traits::{AtLeast32BitUnsigned, Member};28use up_data_structs::{29	CollectionId, CollectionLimits, CollectionStats, Property, PropertyKeyPermission,30	RpcCollection, TokenChild, TokenData, TokenId,31};32use up_rpc::UniqueApi as UniqueRuntimeApi;3334#[cfg(feature = "pov-estimate")]35pub mod pov_estimate;3637#[rpc(server)]38#[async_trait]39pub trait UniqueApi<BlockHash, CrossAccountId, AccountId> {40	/// Get tokens owned by account.41	#[method(name = "unique_accountTokens")]42	fn account_tokens(43		&self,44		collection: CollectionId,45		account: CrossAccountId,46		at: Option<BlockHash>,47	) -> Result<Vec<TokenId>>;4849	/// Get tokens contained within a collection.50	#[method(name = "unique_collectionTokens")]51	fn collection_tokens(52		&self,53		collection: CollectionId,54		at: Option<BlockHash>,55	) -> Result<Vec<TokenId>>;5657	/// Check if the token exists.58	#[method(name = "unique_tokenExists")]59	fn token_exists(60		&self,61		collection: CollectionId,62		token: TokenId,63		at: Option<BlockHash>,64	) -> Result<bool>;6566	/// Get the token owner.67	#[method(name = "unique_tokenOwner")]68	fn token_owner(69		&self,70		collection: CollectionId,71		token: TokenId,72		at: Option<BlockHash>,73	) -> Result<Option<CrossAccountId>>;7475	/// Returns 10 tokens owners in no particular order.76	#[method(name = "unique_tokenOwners")]77	fn token_owners(78		&self,79		collection: CollectionId,80		token: TokenId,81		at: Option<BlockHash>,82	) -> Result<Vec<CrossAccountId>>;8384	/// Get the topmost token owner in the hierarchy of a possibly nested token.85	#[method(name = "unique_topmostTokenOwner")]86	fn topmost_token_owner(87		&self,88		collection: CollectionId,89		token: TokenId,90		at: Option<BlockHash>,91	) -> Result<Option<CrossAccountId>>;9293	/// Get tokens nested directly into the token.94	#[method(name = "unique_tokenChildren")]95	fn token_children(96		&self,97		collection: CollectionId,98		token: TokenId,99		at: Option<BlockHash>,100	) -> Result<Vec<TokenChild>>;101102	/// Get collection properties, optionally limited to the provided keys.103	#[method(name = "unique_collectionProperties")]104	fn collection_properties(105		&self,106		collection: CollectionId,107		keys: Option<Vec<String>>,108		at: Option<BlockHash>,109	) -> Result<Vec<Property>>;110111	/// Get token properties, optionally limited to the provided keys.112	#[method(name = "unique_tokenProperties")]113	fn token_properties(114		&self,115		collection: CollectionId,116		token_id: TokenId,117		keys: Option<Vec<String>>,118		at: Option<BlockHash>,119	) -> Result<Vec<Property>>;120121	/// Get property permissions, optionally limited to the provided keys.122	#[method(name = "unique_propertyPermissions")]123	fn property_permissions(124		&self,125		collection: CollectionId,126		keys: Option<Vec<String>>,127		at: Option<BlockHash>,128	) -> Result<Vec<PropertyKeyPermission>>;129130	/// Get token data, including properties, optionally limited to the provided keys, and total pieces for an RFT.131	#[method(name = "unique_tokenData")]132	fn token_data(133		&self,134		collection: CollectionId,135		token_id: TokenId,136		keys: Option<Vec<String>>,137		at: Option<BlockHash>,138	) -> Result<TokenData<CrossAccountId>>;139140	/// Get the amount of distinctive tokens present in a collection.141	#[method(name = "unique_totalSupply")]142	fn total_supply(&self, collection: CollectionId, at: Option<BlockHash>) -> Result<u32>;143144	/// Get the amount of any user tokens owned by an account.145	#[method(name = "unique_accountBalance")]146	fn account_balance(147		&self,148		collection: CollectionId,149		account: CrossAccountId,150		at: Option<BlockHash>,151	) -> Result<u32>;152153	/// Get the amount of a specific token owned by an account.154	#[method(name = "unique_balance")]155	fn balance(156		&self,157		collection: CollectionId,158		account: CrossAccountId,159		token: TokenId,160		at: Option<BlockHash>,161	) -> Result<String>;162163	/// Get the amount of currently possible sponsored transactions on a token for the fee to be taken off a sponsor.164	#[method(name = "unique_allowance")]165	fn allowance(166		&self,167		collection: CollectionId,168		sender: CrossAccountId,169		spender: CrossAccountId,170		token: TokenId,171		at: Option<BlockHash>,172	) -> Result<String>;173174	/// Get the list of admin accounts of a collection.175	#[method(name = "unique_adminlist")]176	fn adminlist(177		&self,178		collection: CollectionId,179		at: Option<BlockHash>,180	) -> Result<Vec<CrossAccountId>>;181182	/// Get the list of accounts allowed to operate within a collection.183	#[method(name = "unique_allowlist")]184	fn allowlist(185		&self,186		collection: CollectionId,187		at: Option<BlockHash>,188	) -> Result<Vec<CrossAccountId>>;189190	/// Check if a user is allowed to operate within a collection.191	#[method(name = "unique_allowed")]192	fn allowed(193		&self,194		collection: CollectionId,195		user: CrossAccountId,196		at: Option<BlockHash>,197	) -> Result<bool>;198199	/// Get the last token ID created in a collection.200	#[method(name = "unique_lastTokenId")]201	fn last_token_id(&self, collection: CollectionId, at: Option<BlockHash>) -> Result<TokenId>;202203	/// Get collection info by the specified ID.204	#[method(name = "unique_collectionById")]205	fn collection_by_id(206		&self,207		collection: CollectionId,208		at: Option<BlockHash>,209	) -> Result<Option<RpcCollection<AccountId>>>;210211	/// Get chain stats about collections.212	#[method(name = "unique_collectionStats")]213	fn collection_stats(&self, at: Option<BlockHash>) -> Result<CollectionStats>;214215	/// Get the number of blocks until sponsoring a transaction is available.216	#[method(name = "unique_nextSponsored")]217	fn next_sponsored(218		&self,219		collection: CollectionId,220		account: CrossAccountId,221		token: TokenId,222		at: Option<BlockHash>,223	) -> Result<Option<u64>>;224225	/// Get effective collection limits. If not explicitly set, get the chain defaults.226	#[method(name = "unique_effectiveCollectionLimits")]227	fn effective_collection_limits(228		&self,229		collection_id: CollectionId,230		at: Option<BlockHash>,231	) -> Result<Option<CollectionLimits>>;232233	/// Get the total amount of pieces of an RFT.234	#[method(name = "unique_totalPieces")]235	fn total_pieces(236		&self,237		collection_id: CollectionId,238		token_id: TokenId,239		at: Option<BlockHash>,240	) -> Result<Option<String>>;241242	/// Get whether an operator is approved by a given owner.243	#[method(name = "unique_allowanceForAll")]244	fn allowance_for_all(245		&self,246		collection: CollectionId,247		owner: CrossAccountId,248		operator: CrossAccountId,249		at: Option<BlockHash>,250	) -> Result<bool>;251}252253mod app_promotion_unique_rpc {254	use super::*;255256	#[rpc(server)]257	#[async_trait]258	pub trait AppPromotionApi<BlockHash, BlockNumber, CrossAccountId, AccountId> {259		/// Returns the total amount of staked tokens.260		#[method(name = "appPromotion_totalStaked")]261		fn total_staked(262			&self,263			staker: Option<CrossAccountId>,264			at: Option<BlockHash>,265		) -> Result<String>;266267		///Returns the total amount of staked tokens per block when staked.268		#[method(name = "appPromotion_totalStakedPerBlock")]269		fn total_staked_per_block(270			&self,271			staker: CrossAccountId,272			at: Option<BlockHash>,273		) -> Result<Vec<(BlockNumber, String)>>;274275		/// Returns the total amount of tokens pending withdrawal from staking.276		#[method(name = "appPromotion_pendingUnstake")]277		fn pending_unstake(278			&self,279			staker: Option<CrossAccountId>,280			at: Option<BlockHash>,281		) -> Result<String>;282283		/// Returns the total amount of tokens pending withdrawal from staking per block.284		#[method(name = "appPromotion_pendingUnstakePerBlock")]285		fn pending_unstake_per_block(286			&self,287			staker: CrossAccountId,288			at: Option<BlockHash>,289		) -> Result<Vec<(BlockNumber, String)>>;290	}291}292293#[macro_export]294macro_rules! define_struct_for_server_api {295	($name:ident { $($arg:ident: $arg_ty:ty),+ $(,)? }) => {296		pub struct $name<Client, Block: BlockT> {297			$($arg: $arg_ty),+,298			_marker: std::marker::PhantomData<Block>,299		}300301		impl<Client, Block: BlockT> $name<Client, Block> {302			pub fn new($($arg: $arg_ty),+) -> Self {303				Self {304					$($arg),+,305					_marker: Default::default(),306				}307			}308		}309	};310}311312define_struct_for_server_api! {313	Unique {314		client: Arc<Client>315	}316}317318define_struct_for_server_api! {319	AppPromotion {320		client: Arc<Client>321	}322}323324macro_rules! pass_method {325	(326		$method_name:ident(327			$($(#[map = $map:expr])? $name:ident: $ty:ty),* $(,)?328		) -> $result:ty $(=> $mapper:expr)?,329		//$runtime_name:ident $(<$($lt: tt),+>)*330		$runtime_api_macro:ident331		$(; changed_in $ver:expr, $changed_method_name:ident ($($changed_name:expr), * $(,)?) => $fixer:expr)*332	) => {333		fn $method_name(334			&self,335			$(336				$name: $ty,337			)*338			at: Option<<Block as BlockT>::Hash>,339		) -> Result<$result> {340			let api = self.client.runtime_api();341			let at = at.unwrap_or_else(|| self.client.info().best_hash);342			let _api_version = if let Ok(Some(api_version)) =343				api.api_version::<$runtime_api_macro!()>(at)344			{345				api_version346			} else {347				// unreachable for our runtime348				return Err(anyhow!("api is not available").into())349			};350351			let result = $(if _api_version < $ver {352				api.$changed_method_name(at, $($changed_name),*).map(|r| r.and_then($fixer))353			} else)*354			{ api.$method_name(at, $($($map)? ($name)),*) };355356			Ok(result357				.map_err(|e| anyhow!("unable to query: {e}"))?358				.map_err(|e| anyhow!("runtime error: {e:?}"))$(.map($mapper))??)359		}360	};361}362363macro_rules! unique_api {364	() => {365		dyn UniqueRuntimeApi<Block, CrossAccountId, AccountId>366	};367}368369macro_rules! app_promotion_api {370	() => {371		dyn AppPromotionRuntimeApi<Block, BlockNumber, CrossAccountId, AccountId>372	};373}374375#[allow(deprecated)]376impl<C, Block, CrossAccountId, AccountId>377	UniqueApiServer<<Block as BlockT>::Hash, CrossAccountId, AccountId> for Unique<C, Block>378where379	Block: BlockT,380	AccountId: Decode,381	C: 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>,382	C::Api: UniqueRuntimeApi<Block, CrossAccountId, AccountId>,383	CrossAccountId: pallet_evm::account::CrossAccountId<AccountId>,384{385	pass_method!(386		account_tokens(collection: CollectionId, account: CrossAccountId) -> Vec<TokenId>, unique_api387	);388	pass_method!(389		collection_tokens(collection: CollectionId) -> Vec<TokenId>, unique_api390	);391	pass_method!(392		token_exists(collection: CollectionId, token: TokenId) -> bool, unique_api393	);394	pass_method!(395		token_owner(collection: CollectionId, token: TokenId) -> Option<CrossAccountId>, unique_api396	);397	pass_method!(398		topmost_token_owner(collection: CollectionId, token: TokenId) -> Option<CrossAccountId>, unique_api399	);400	pass_method!(token_children(collection: CollectionId, token: TokenId) -> Vec<TokenChild>, unique_api);401	pass_method!(total_supply(collection: CollectionId) -> u32, unique_api);402	pass_method!(account_balance(collection: CollectionId, account: CrossAccountId) -> u32, unique_api);403	pass_method!(balance(collection: CollectionId, account: CrossAccountId, token: TokenId) -> String => |v| v.to_string(), unique_api);404	pass_method!(405		allowance(collection: CollectionId, sender: CrossAccountId, spender: CrossAccountId, token: TokenId) -> String => |v| v.to_string(),406		unique_api407	);408409	pass_method!(collection_properties(410		collection: CollectionId,411412		#[map = string_keys_to_bytes_keys]413		keys: Option<Vec<String>>414	) -> Vec<Property>, unique_api);415416	pass_method!(token_properties(417		collection: CollectionId,418		token_id: TokenId,419420		#[map = string_keys_to_bytes_keys]421		keys: Option<Vec<String>>422	) -> Vec<Property>, unique_api);423424	pass_method!(property_permissions(425		collection: CollectionId,426427		#[map = string_keys_to_bytes_keys]428		keys: Option<Vec<String>>429	) -> Vec<PropertyKeyPermission>, unique_api);430431	fn token_data(432		&self,433		collection: CollectionId,434		token_id: TokenId,435		keys: Option<Vec<String>>,436		at: Option<<Block as BlockT>::Hash>,437	) -> Result<TokenData<CrossAccountId>> {438		token_data_internal(self.client.clone(), collection, token_id, keys, at)439	}440441	pass_method!(adminlist(collection: CollectionId) -> Vec<CrossAccountId>, unique_api);442	pass_method!(allowlist(collection: CollectionId) -> Vec<CrossAccountId>, unique_api);443	pass_method!(allowed(collection: CollectionId, user: CrossAccountId) -> bool, unique_api);444	pass_method!(last_token_id(collection: CollectionId) -> TokenId, unique_api);445	pass_method!(446		collection_by_id(collection: CollectionId) -> Option<RpcCollection<AccountId>>, unique_api;447		changed_in 3, collection_by_id_before_version_3(collection) => |value| {448			use sp_runtime::DispatchError;449450			if let Some(bytes) = value {451				Ok(Some(detect_type_and_decode_collection(bytes.as_slice())452				.map_err(|_| DispatchError::Other("API Error: UniqueApi_collection_by_id"))?))453			} else {454				Ok(None)455			}456		}457	);458	pass_method!(collection_stats() -> CollectionStats, unique_api);459	pass_method!(next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Option<u64>, unique_api);460	pass_method!(effective_collection_limits(collection_id: CollectionId) -> Option<CollectionLimits>, unique_api);461	pass_method!(total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<String> => |o| o.map(|number| number.to_string()) , unique_api);462	pass_method!(token_owners(collection: CollectionId, token: TokenId) -> Vec<CrossAccountId>, unique_api);463	pass_method!(allowance_for_all(collection: CollectionId, owner: CrossAccountId, operator: CrossAccountId) -> bool, unique_api);464}465466impl<C, Block, BlockNumber, CrossAccountId, AccountId>467	app_promotion_unique_rpc::AppPromotionApiServer<468		<Block as BlockT>::Hash,469		BlockNumber,470		CrossAccountId,471		AccountId,472	> for AppPromotion<C, Block>473where474	Block: BlockT,475	BlockNumber: Decode + Member + AtLeast32BitUnsigned,476	AccountId: Decode,477	C: 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>,478	CrossAccountId: pallet_evm::account::CrossAccountId<AccountId>,479	C::Api: AppPromotionRuntimeApi<Block, BlockNumber, CrossAccountId, AccountId>,480{481	pass_method!(total_staked(staker: Option<CrossAccountId>) -> String => |v| v.to_string(), app_promotion_api);482	pass_method!(total_staked_per_block(staker: CrossAccountId) -> Vec<(BlockNumber, String)> =>483		|v| v484		.into_iter()485		.map(|(b, a)| (b, a.to_string()))486		.collect::<Vec<_>>(), app_promotion_api);487	pass_method!(pending_unstake(staker: Option<CrossAccountId>) -> String => |v| v.to_string(), app_promotion_api);488	pass_method!(pending_unstake_per_block(staker: CrossAccountId) -> Vec<(BlockNumber, String)> =>489		|v| v490		.into_iter()491		.map(|(b, a)| (b, a.to_string()))492		.collect::<Vec<_>>(), app_promotion_api);493}494495fn token_data_internal<Block, Client, AccountId, CrossAccountId>(496	client: Arc<Client>,497	collection: CollectionId,498	token_id: TokenId,499	keys: Option<Vec<String>>,500	at: Option<<Block as BlockT>::Hash>,501) -> Result<TokenData<CrossAccountId>>502where503	AccountId: Decode,504	Block: BlockT,505	Client: ProvideRuntimeApi<Block> + HeaderBackend<Block>,506	Client::Api: UniqueRuntimeApi<Block, CrossAccountId, AccountId>,507	CrossAccountId: pallet_evm::account::CrossAccountId<AccountId>,508{509	let api = client.runtime_api();510	let at = at.unwrap_or_else(|| client.info().best_hash);511	let api_version = if let Ok(Some(api_version)) =512		api.api_version::<dyn UniqueRuntimeApi<Block, CrossAccountId, AccountId>>(at)513	{514		api_version515	} else {516		return Err(anyhow!("api is not available").into());517	};518	let result = if api_version >= 3 {519		api.token_data(at, collection, token_id, string_keys_to_bytes_keys(keys))520	} else {521		#[allow(deprecated)]522		api.token_data_before_version_3(at, collection, token_id, string_keys_to_bytes_keys(keys))523			.map(524				|r: sc_service::Result<525					up_data_structs::TokenDataVersion1<CrossAccountId>,526					sp_runtime::DispatchError,527				>| r.map(|value| value.into()),528			)529			.or_else(|_| {530				Ok(api531					.token_owner(at, collection, token_id)?532					.map(|owner| TokenData {533						properties: Vec::new(),534						owner,535						pieces: 0,536					}))537			})538	};539	Ok(result540		.map_err(|e| anyhow!("unable to query: {e}"))?541		.map_err(|e| anyhow!("runtime error: {e:?}"))?)542}543544fn string_keys_to_bytes_keys(keys: Option<Vec<String>>) -> Option<Vec<Vec<u8>>> {545	keys.map(|keys| keys.into_iter().map(|key| key.into_bytes()).collect())546}547548fn decode_collection_from_bytes<T: parity_scale_codec::Decode>(549	bytes: &[u8],550) -> core::result::Result<T, parity_scale_codec::Error> {551	let mut reader = parity_scale_codec::IoReader(bytes);552	T::decode(&mut reader)553}554555fn detect_type_and_decode_collection<AccountId: Decode>(556	bytes: &[u8],557) -> core::result::Result<RpcCollection<AccountId>, parity_scale_codec::Error> {558	use up_data_structs::{CollectionVersion1, RpcCollectionVersion1};559560	decode_collection_from_bytes::<RpcCollection<AccountId>>(bytes)561		.or_else(|_| {562			decode_collection_from_bytes::<RpcCollectionVersion1<AccountId>>(bytes)563				.map(|col| col.into())564		})565		.or_else(|_| {566			decode_collection_from_bytes::<CollectionVersion1<AccountId>>(bytes)567				.map(|col| col.into())568		})569}570571#[cfg(test)]572mod tests {573	use hex_literal::hex;574	use parity_scale_codec::IoReader;575	use up_data_structs::{CollectionVersion1, RawEncoded};576577	use super::*;578579	const ENCODED_COLLECTION_V1: [u8; 180] = hex!("aab94a1ee784bc17f68d76d4d48d736916ca6ff6315b8c1fa1175726c8345a390000285000720069006d00610020004c00690076006500d04500730065006d00700069006f00200064006900200063007200650061007a0069006f006e006500200064006900200075006e00610020006e0075006f0076006100200063006f006c006c0065007a0069006f006e00650020006400690020004e004600540021000c464e5400000000000000000000000000000000");580	const ENCODED_RPC_COLLECTION_V2: [u8; 618] = hex!("d00dcc24bf66750d3809aa26884b930ec8a3094d6f6f19fdc62020b2fbec013400604d0069006e007400460065007300740020002d002000460075006e006e007900200061006e0069006d0061006c0073008c430072006f00730073006f0076006500720020006200650074007700650065006e00200061006e0069006d0061006c00730020002d00200066006f0072002000660075006e00104d46464100000000000000000000010001000100000004385f6f6c645f636f6e7374446174610001000c5c5f6f6c645f636f6e73744f6e436861696e536368656d6139047b226e6573746564223a7b226f6e436861696e4d65746144617461223a7b226e6573746564223a7b224e46544d657461223a7b226669656c6473223a7b22697066734a736f6e223a7b226964223a312c2272756c65223a227265717569726564222c2274797065223a22737472696e67227d2c2248656164223a7b226964223a322c2272756c65223a227265717569726564222c2274797065223a22737472696e67227d2c22426f6479223a7b226964223a332c2272756c65223a227265717569726564222c2274797065223a22737472696e67227d2c225461696c223a7b226964223a342c2272756c65223a227265717569726564222c2274797065223a22737472696e67227d7d7d7d7d7d7d485f6f6c645f736368656d6156657273696f6e18556e69717565685f6f6c645f7661726961626c654f6e436861696e536368656d6111017b22636f6c6c656374696f6e436f766572223a22516d53557a7139354c357a556777795a584d3731576a3762786b36557048515468633162536965347766706e5435227d000000");581582	#[test]583	fn decoding_collection_v1() {584		decode_collection_from_bytes::<CollectionVersion1<[u8; 32]>>(585			ENCODED_COLLECTION_V1.as_slice(),586		)587		.unwrap();588	}589590	#[test]591	fn detecting_and_decoding_collection_v1() {592		detect_type_and_decode_collection::<[u8; 32]>(ENCODED_COLLECTION_V1.as_slice()).unwrap();593	}594595	#[test]596	fn decoding_rpc_collection_v2() {597		decode_collection_from_bytes::<RpcCollection<[u8; 32]>>(598			ENCODED_RPC_COLLECTION_V2.as_slice(),599		)600		.unwrap();601	}602603	#[test]604	fn detecting_decoding_rpc_collection_v2() {605		detect_type_and_decode_collection::<[u8; 32]>(ENCODED_RPC_COLLECTION_V2.as_slice())606			.unwrap();607	}608609	#[test]610	fn rpc_collection_supports_decoding_through_vec() {611		let mut bytes = IoReader(ENCODED_RPC_COLLECTION_V2.as_slice());612		let vec = RawEncoded::decode(&mut bytes).unwrap();613		println!("{:?}", vec.len());614		let mut bytes = IoReader(vec.as_slice());615		RpcCollection::<[u8; 32]>::decode(&mut bytes).unwrap();616	}617}