git.delta.rocks / unique-network / refs/commits / 6616dda5f558

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 app_promotion_rpc::AppPromotionApi as AppPromotionRuntimeApi;21pub use app_promotion_unique_rpc::AppPromotionApiServer;22use jsonrpsee::{core::RpcResult as Result, proc_macros::rpc, types::ErrorCode};23use parity_scale_codec::Decode;24use sp_api::{ApiExt, ProvideRuntimeApi};25use sp_blockchain::HeaderBackend;26use sp_runtime::traits::{AtLeast32BitUnsigned, Block as BlockT, Member};27use up_data_structs::{28	CollectionId, CollectionLimits, CollectionStats, Property, PropertyKeyPermission,29	RpcCollection, TokenChild, TokenData, TokenId,30};31use up_rpc::UniqueApi as UniqueRuntimeApi;3233#[cfg(feature = "pov-estimate")]34pub mod pov_estimate;3536#[rpc(server)]37#[async_trait]38pub trait UniqueApi<BlockHash, CrossAccountId, AccountId> {39	/// Get tokens owned by account.40	#[method(name = "unique_accountTokens")]41	fn account_tokens(42		&self,43		collection: CollectionId,44		account: CrossAccountId,45		at: Option<BlockHash>,46	) -> Result<Vec<TokenId>>;4748	/// Get tokens contained within a collection.49	#[method(name = "unique_collectionTokens")]50	fn collection_tokens(51		&self,52		collection: CollectionId,53		at: Option<BlockHash>,54	) -> Result<Vec<TokenId>>;5556	/// Check if the token exists.57	#[method(name = "unique_tokenExists")]58	fn token_exists(59		&self,60		collection: CollectionId,61		token: TokenId,62		at: Option<BlockHash>,63	) -> Result<bool>;6465	/// Get the token owner.66	#[method(name = "unique_tokenOwner")]67	fn token_owner(68		&self,69		collection: CollectionId,70		token: TokenId,71		at: Option<BlockHash>,72	) -> Result<Option<CrossAccountId>>;7374	/// Returns 10 tokens owners in no particular order.75	#[method(name = "unique_tokenOwners")]76	fn token_owners(77		&self,78		collection: CollectionId,79		token: TokenId,80		at: Option<BlockHash>,81	) -> Result<Vec<CrossAccountId>>;8283	/// Get the topmost token owner in the hierarchy of a possibly nested token.84	#[method(name = "unique_topmostTokenOwner")]85	fn topmost_token_owner(86		&self,87		collection: CollectionId,88		token: TokenId,89		at: Option<BlockHash>,90	) -> Result<Option<CrossAccountId>>;9192	/// Get tokens nested directly into the token.93	#[method(name = "unique_tokenChildren")]94	fn token_children(95		&self,96		collection: CollectionId,97		token: TokenId,98		at: Option<BlockHash>,99	) -> Result<Vec<TokenChild>>;100101	/// Get collection properties, optionally limited to the provided keys.102	#[method(name = "unique_collectionProperties")]103	fn collection_properties(104		&self,105		collection: CollectionId,106		keys: Option<Vec<String>>,107		at: Option<BlockHash>,108	) -> Result<Vec<Property>>;109110	/// Get token properties, optionally limited to the provided keys.111	#[method(name = "unique_tokenProperties")]112	fn token_properties(113		&self,114		collection: CollectionId,115		token_id: TokenId,116		keys: Option<Vec<String>>,117		at: Option<BlockHash>,118	) -> Result<Vec<Property>>;119120	/// Get property permissions, optionally limited to the provided keys.121	#[method(name = "unique_propertyPermissions")]122	fn property_permissions(123		&self,124		collection: CollectionId,125		keys: Option<Vec<String>>,126		at: Option<BlockHash>,127	) -> Result<Vec<PropertyKeyPermission>>;128129	/// Get token data, including properties, optionally limited to the provided keys, and total pieces for an RFT.130	#[method(name = "unique_tokenData")]131	fn token_data(132		&self,133		collection: CollectionId,134		token_id: TokenId,135		keys: Option<Vec<String>>,136		at: Option<BlockHash>,137	) -> Result<TokenData<CrossAccountId>>;138139	/// Get the amount of distinctive tokens present in a collection.140	#[method(name = "unique_totalSupply")]141	fn total_supply(&self, collection: CollectionId, at: Option<BlockHash>) -> Result<u32>;142143	/// Get the amount of any user tokens owned by an account.144	#[method(name = "unique_accountBalance")]145	fn account_balance(146		&self,147		collection: CollectionId,148		account: CrossAccountId,149		at: Option<BlockHash>,150	) -> Result<u32>;151152	/// Get the amount of a specific token owned by an account.153	#[method(name = "unique_balance")]154	fn balance(155		&self,156		collection: CollectionId,157		account: CrossAccountId,158		token: TokenId,159		at: Option<BlockHash>,160	) -> Result<String>;161162	/// Get the amount of currently possible sponsored transactions on a token for the fee to be taken off a sponsor.163	#[method(name = "unique_allowance")]164	fn allowance(165		&self,166		collection: CollectionId,167		sender: CrossAccountId,168		spender: CrossAccountId,169		token: TokenId,170		at: Option<BlockHash>,171	) -> Result<String>;172173	/// Get the list of admin accounts of a collection.174	#[method(name = "unique_adminlist")]175	fn adminlist(176		&self,177		collection: CollectionId,178		at: Option<BlockHash>,179	) -> Result<Vec<CrossAccountId>>;180181	/// Get the list of accounts allowed to operate within a collection.182	#[method(name = "unique_allowlist")]183	fn allowlist(184		&self,185		collection: CollectionId,186		at: Option<BlockHash>,187	) -> Result<Vec<CrossAccountId>>;188189	/// Check if a user is allowed to operate within a collection.190	#[method(name = "unique_allowed")]191	fn allowed(192		&self,193		collection: CollectionId,194		user: CrossAccountId,195		at: Option<BlockHash>,196	) -> Result<bool>;197198	/// Get the last token ID created in a collection.199	#[method(name = "unique_lastTokenId")]200	fn last_token_id(&self, collection: CollectionId, at: Option<BlockHash>) -> Result<TokenId>;201202	/// Get collection info by the specified ID.203	#[method(name = "unique_collectionById")]204	fn collection_by_id(205		&self,206		collection: CollectionId,207		at: Option<BlockHash>,208	) -> Result<Option<RpcCollection<AccountId>>>;209210	/// Get chain stats about collections.211	#[method(name = "unique_collectionStats")]212	fn collection_stats(&self, at: Option<BlockHash>) -> Result<CollectionStats>;213214	/// Get the number of blocks until sponsoring a transaction is available.215	#[method(name = "unique_nextSponsored")]216	fn next_sponsored(217		&self,218		collection: CollectionId,219		account: CrossAccountId,220		token: TokenId,221		at: Option<BlockHash>,222	) -> Result<Option<u64>>;223224	/// Get effective collection limits. If not explicitly set, get the chain defaults.225	#[method(name = "unique_effectiveCollectionLimits")]226	fn effective_collection_limits(227		&self,228		collection_id: CollectionId,229		at: Option<BlockHash>,230	) -> Result<Option<CollectionLimits>>;231232	/// Get the total amount of pieces of an RFT.233	#[method(name = "unique_totalPieces")]234	fn total_pieces(235		&self,236		collection_id: CollectionId,237		token_id: TokenId,238		at: Option<BlockHash>,239	) -> Result<Option<String>>;240241	/// Get whether an operator is approved by a given owner.242	#[method(name = "unique_allowanceForAll")]243	fn allowance_for_all(244		&self,245		collection: CollectionId,246		owner: CrossAccountId,247		operator: CrossAccountId,248		at: Option<BlockHash>,249	) -> Result<bool>;250}251252mod app_promotion_unique_rpc {253	use super::*;254255	#[rpc(server)]256	#[async_trait]257	pub trait AppPromotionApi<BlockHash, BlockNumber, CrossAccountId, AccountId> {258		/// Returns the total amount of staked tokens.259		#[method(name = "appPromotion_totalStaked")]260		fn total_staked(261			&self,262			staker: Option<CrossAccountId>,263			at: Option<BlockHash>,264		) -> Result<String>;265266		///Returns the total amount of staked tokens per block when staked.267		#[method(name = "appPromotion_totalStakedPerBlock")]268		fn total_staked_per_block(269			&self,270			staker: CrossAccountId,271			at: Option<BlockHash>,272		) -> Result<Vec<(BlockNumber, String)>>;273274		/// Returns the total amount of tokens pending withdrawal from staking.275		#[method(name = "appPromotion_pendingUnstake")]276		fn pending_unstake(277			&self,278			staker: Option<CrossAccountId>,279			at: Option<BlockHash>,280		) -> Result<String>;281282		/// Returns the total amount of tokens pending withdrawal from staking per block.283		#[method(name = "appPromotion_pendingUnstakePerBlock")]284		fn pending_unstake_per_block(285			&self,286			staker: CrossAccountId,287			at: Option<BlockHash>,288		) -> Result<Vec<(BlockNumber, String)>>;289	}290}291292#[macro_export]293macro_rules! define_struct_for_server_api {294	($name:ident { $($arg:ident: $arg_ty:ty),+ $(,)? }) => {295		pub struct $name<Client, Block: BlockT> {296			$($arg: $arg_ty),+,297			_marker: std::marker::PhantomData<Block>,298		}299300		impl<Client, Block: BlockT> $name<Client, Block> {301			pub fn new($($arg: $arg_ty),+) -> Self {302				Self {303					$($arg),+,304					_marker: Default::default(),305				}306			}307		}308	};309}310311define_struct_for_server_api! {312	Unique {313		client: Arc<Client>314	}315}316317define_struct_for_server_api! {318	AppPromotion {319		client: Arc<Client>320	}321}322323macro_rules! pass_method {324	(325		$method_name:ident(326			$($(#[map = $map:expr])? $name:ident: $ty:ty),* $(,)?327		) -> $result:ty $(=> $mapper:expr)?,328		//$runtime_name:ident $(<$($lt: tt),+>)*329		$runtime_api_macro:ident330		$(; changed_in $ver:expr, $changed_method_name:ident ($($changed_name:expr), * $(,)?) => $fixer:expr)*331	) => {332		fn $method_name(333			&self,334			$(335				$name: $ty,336			)*337			at: Option<<Block as BlockT>::Hash>,338		) -> Result<$result> {339			let api = self.client.runtime_api();340			let at = at.unwrap_or_else(|| self.client.info().best_hash);341			let _api_version = if let Ok(Some(api_version)) =342				api.api_version::<$runtime_api_macro!()>(at)343			{344				api_version345			} else {346				// unreachable for our runtime347				return Err(ErrorCode::MethodNotFound.into())348			};349350			let result = $(if _api_version < $ver {351				api.$changed_method_name(at, $($changed_name),*).map(|r| r.and_then($fixer))352			} else)*353			{ api.$method_name(at, $($($map)? ($name)),*) };354355			Ok(result356				.map_err(|_| ErrorCode::InternalError /* TODO proper error */)?357				.map_err(|_| ErrorCode::InvalidParams)$(.map($mapper))??)358		}359	};360}361362macro_rules! unique_api {363	() => {364		dyn UniqueRuntimeApi<Block, CrossAccountId, AccountId>365	};366}367368macro_rules! app_promotion_api {369	() => {370		dyn AppPromotionRuntimeApi<Block, BlockNumber, CrossAccountId, AccountId>371	};372}373374#[allow(deprecated)]375impl<C, Block, CrossAccountId, AccountId>376	UniqueApiServer<<Block as BlockT>::Hash, CrossAccountId, AccountId> for Unique<C, Block>377where378	Block: BlockT,379	AccountId: Decode,380	C: 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>,381	C::Api: UniqueRuntimeApi<Block, CrossAccountId, AccountId>,382	CrossAccountId: pallet_evm::account::CrossAccountId<AccountId>,383{384	pass_method!(385		account_tokens(collection: CollectionId, account: CrossAccountId) -> Vec<TokenId>, unique_api386	);387	pass_method!(388		collection_tokens(collection: CollectionId) -> Vec<TokenId>, unique_api389	);390	pass_method!(391		token_exists(collection: CollectionId, token: TokenId) -> bool, unique_api392	);393	pass_method!(394		token_owner(collection: CollectionId, token: TokenId) -> Option<CrossAccountId>, unique_api395	);396	pass_method!(397		topmost_token_owner(collection: CollectionId, token: TokenId) -> Option<CrossAccountId>, unique_api398	);399	pass_method!(token_children(collection: CollectionId, token: TokenId) -> Vec<TokenChild>, unique_api);400	pass_method!(total_supply(collection: CollectionId) -> u32, unique_api);401	pass_method!(account_balance(collection: CollectionId, account: CrossAccountId) -> u32, unique_api);402	pass_method!(balance(collection: CollectionId, account: CrossAccountId, token: TokenId) -> String => |v| v.to_string(), unique_api);403	pass_method!(404		allowance(collection: CollectionId, sender: CrossAccountId, spender: CrossAccountId, token: TokenId) -> String => |v| v.to_string(),405		unique_api406	);407408	pass_method!(collection_properties(409		collection: CollectionId,410411		#[map = string_keys_to_bytes_keys]412		keys: Option<Vec<String>>413	) -> Vec<Property>, unique_api);414415	pass_method!(token_properties(416		collection: CollectionId,417		token_id: TokenId,418419		#[map = string_keys_to_bytes_keys]420		keys: Option<Vec<String>>421	) -> Vec<Property>, unique_api);422423	pass_method!(property_permissions(424		collection: CollectionId,425426		#[map = string_keys_to_bytes_keys]427		keys: Option<Vec<String>>428	) -> Vec<PropertyKeyPermission>, unique_api);429430	fn token_data(431		&self,432		collection: CollectionId,433		token_id: TokenId,434		keys: Option<Vec<String>>,435		at: Option<<Block as BlockT>::Hash>,436	) -> Result<TokenData<CrossAccountId>> {437		token_data_internal(self.client.clone(), collection, token_id, keys, at)438	}439440	pass_method!(adminlist(collection: CollectionId) -> Vec<CrossAccountId>, unique_api);441	pass_method!(allowlist(collection: CollectionId) -> Vec<CrossAccountId>, unique_api);442	pass_method!(allowed(collection: CollectionId, user: CrossAccountId) -> bool, unique_api);443	pass_method!(last_token_id(collection: CollectionId) -> TokenId, unique_api);444	pass_method!(445		collection_by_id(collection: CollectionId) -> Option<RpcCollection<AccountId>>, unique_api;446		changed_in 3, collection_by_id_before_version_3(collection) => |value| {447			use sp_runtime::DispatchError;448449			if let Some(bytes) = value {450				Ok(Some(detect_type_and_decode_collection(bytes.as_slice())451				.map_err(|_| DispatchError::Other("API Error: UniqueApi_collection_by_id"))?))452			} else {453				Ok(None)454			}455		}456	);457	pass_method!(collection_stats() -> CollectionStats, unique_api);458	pass_method!(next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Option<u64>, unique_api);459	pass_method!(effective_collection_limits(collection_id: CollectionId) -> Option<CollectionLimits>, unique_api);460	pass_method!(total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<String> => |o| o.map(|number| number.to_string()) , unique_api);461	pass_method!(token_owners(collection: CollectionId, token: TokenId) -> Vec<CrossAccountId>, unique_api);462	pass_method!(allowance_for_all(collection: CollectionId, owner: CrossAccountId, operator: CrossAccountId) -> bool, unique_api);463}464465impl<C, Block, BlockNumber, CrossAccountId, AccountId>466	app_promotion_unique_rpc::AppPromotionApiServer<467		<Block as BlockT>::Hash,468		BlockNumber,469		CrossAccountId,470		AccountId,471	> for AppPromotion<C, Block>472where473	Block: BlockT,474	BlockNumber: Decode + Member + AtLeast32BitUnsigned,475	AccountId: Decode,476	C: 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>,477	CrossAccountId: pallet_evm::account::CrossAccountId<AccountId>,478	C::Api: AppPromotionRuntimeApi<Block, BlockNumber, CrossAccountId, AccountId>,479{480	pass_method!(total_staked(staker: Option<CrossAccountId>) -> String => |v| v.to_string(), app_promotion_api);481	pass_method!(total_staked_per_block(staker: CrossAccountId) -> Vec<(BlockNumber, String)> =>482		|v| v483		.into_iter()484		.map(|(b, a)| (b, a.to_string()))485		.collect::<Vec<_>>(), app_promotion_api);486	pass_method!(pending_unstake(staker: Option<CrossAccountId>) -> String => |v| v.to_string(), app_promotion_api);487	pass_method!(pending_unstake_per_block(staker: CrossAccountId) -> Vec<(BlockNumber, String)> =>488		|v| v489		.into_iter()490		.map(|(b, a)| (b, a.to_string()))491		.collect::<Vec<_>>(), app_promotion_api);492}493494fn token_data_internal<Block, Client, AccountId, CrossAccountId>(495	client: Arc<Client>,496	collection: CollectionId,497	token_id: TokenId,498	keys: Option<Vec<String>>,499	at: Option<<Block as BlockT>::Hash>,500) -> Result<TokenData<CrossAccountId>>501where502	AccountId: Decode,503	Block: BlockT,504	Client: ProvideRuntimeApi<Block> + HeaderBackend<Block>,505	Client::Api: UniqueRuntimeApi<Block, CrossAccountId, AccountId>,506	CrossAccountId: pallet_evm::account::CrossAccountId<AccountId>,507{508	let api = client.runtime_api();509	let at = at.unwrap_or_else(|| client.info().best_hash);510	let api_version = if let Ok(Some(api_version)) =511		api.api_version::<dyn UniqueRuntimeApi<Block, CrossAccountId, AccountId>>(at)512	{513		api_version514	} else {515		return Err(ErrorCode::MethodNotFound.into());516	};517	let result = if api_version >= 3 {518		api.token_data(at, collection, token_id, string_keys_to_bytes_keys(keys))519	} else {520		#[allow(deprecated)]521		api.token_data_before_version_3(at, collection, token_id, string_keys_to_bytes_keys(keys))522			.map(523				|r: sc_service::Result<524					up_data_structs::TokenDataVersion1<CrossAccountId>,525					sp_runtime::DispatchError,526				>| r.map(|value| value.into()),527			)528			.or_else(|_| {529				Ok(api530					.token_owner(at, collection, token_id)?531					.map(|owner| TokenData {532						properties: Vec::new(),533						owner,534						pieces: 0,535					}))536			})537	};538	Ok(result539		.map_err(|_| ErrorCode::InternalError /* TODO proper error */)?540		.map_err(|_| ErrorCode::InvalidParams)?)541}542543fn string_keys_to_bytes_keys(keys: Option<Vec<String>>) -> Option<Vec<Vec<u8>>> {544	keys.map(|keys| keys.into_iter().map(|key| key.into_bytes()).collect())545}546547fn decode_collection_from_bytes<T: parity_scale_codec::Decode>(548	bytes: &[u8],549) -> core::result::Result<T, parity_scale_codec::Error> {550	let mut reader = parity_scale_codec::IoReader(bytes);551	T::decode(&mut reader)552}553554fn detect_type_and_decode_collection<AccountId: Decode>(555	bytes: &[u8],556) -> core::result::Result<RpcCollection<AccountId>, parity_scale_codec::Error> {557	use up_data_structs::{CollectionVersion1, RpcCollectionVersion1};558559	decode_collection_from_bytes::<RpcCollection<AccountId>>(bytes)560		.or_else(|_| {561			decode_collection_from_bytes::<RpcCollectionVersion1<AccountId>>(bytes)562				.map(|col| col.into())563		})564		.or_else(|_| {565			decode_collection_from_bytes::<CollectionVersion1<AccountId>>(bytes)566				.map(|col| col.into())567		})568}569570#[cfg(test)]571mod tests {572	use hex_literal::hex;573	use parity_scale_codec::IoReader;574	use up_data_structs::{CollectionVersion1, RawEncoded};575576	use super::*;577578	const ENCODED_COLLECTION_V1: [u8; 180] = hex!("aab94a1ee784bc17f68d76d4d48d736916ca6ff6315b8c1fa1175726c8345a390000285000720069006d00610020004c00690076006500d04500730065006d00700069006f00200064006900200063007200650061007a0069006f006e006500200064006900200075006e00610020006e0075006f0076006100200063006f006c006c0065007a0069006f006e00650020006400690020004e004600540021000c464e5400000000000000000000000000000000");579	const ENCODED_RPC_COLLECTION_V2: [u8; 618] = hex!("d00dcc24bf66750d3809aa26884b930ec8a3094d6f6f19fdc62020b2fbec013400604d0069006e007400460065007300740020002d002000460075006e006e007900200061006e0069006d0061006c0073008c430072006f00730073006f0076006500720020006200650074007700650065006e00200061006e0069006d0061006c00730020002d00200066006f0072002000660075006e00104d46464100000000000000000000010001000100000004385f6f6c645f636f6e7374446174610001000c5c5f6f6c645f636f6e73744f6e436861696e536368656d6139047b226e6573746564223a7b226f6e436861696e4d65746144617461223a7b226e6573746564223a7b224e46544d657461223a7b226669656c6473223a7b22697066734a736f6e223a7b226964223a312c2272756c65223a227265717569726564222c2274797065223a22737472696e67227d2c2248656164223a7b226964223a322c2272756c65223a227265717569726564222c2274797065223a22737472696e67227d2c22426f6479223a7b226964223a332c2272756c65223a227265717569726564222c2274797065223a22737472696e67227d2c225461696c223a7b226964223a342c2272756c65223a227265717569726564222c2274797065223a22737472696e67227d7d7d7d7d7d7d485f6f6c645f736368656d6156657273696f6e18556e69717565685f6f6c645f7661726961626c654f6e436861696e536368656d6111017b22636f6c6c656374696f6e436f766572223a22516d53557a7139354c357a556777795a584d3731576a3762786b36557048515468633162536965347766706e5435227d000000");580581	#[test]582	fn decoding_collection_v1() {583		decode_collection_from_bytes::<CollectionVersion1<[u8; 32]>>(584			ENCODED_COLLECTION_V1.as_slice(),585		)586		.unwrap();587	}588589	#[test]590	fn detecting_and_decoding_collection_v1() {591		detect_type_and_decode_collection::<[u8; 32]>(ENCODED_COLLECTION_V1.as_slice()).unwrap();592	}593594	#[test]595	fn decoding_rpc_collection_v2() {596		decode_collection_from_bytes::<RpcCollection<[u8; 32]>>(597			ENCODED_RPC_COLLECTION_V2.as_slice(),598		)599		.unwrap();600	}601602	#[test]603	fn detecting_decoding_rpc_collection_v2() {604		detect_type_and_decode_collection::<[u8; 32]>(ENCODED_RPC_COLLECTION_V2.as_slice())605			.unwrap();606	}607608	#[test]609	fn rpc_collection_supports_decoding_through_vec() {610		let mut bytes = IoReader(ENCODED_RPC_COLLECTION_V2.as_slice());611		let vec = RawEncoded::decode(&mut bytes).unwrap();612		println!("{:?}", vec.len());613		let mut bytes = IoReader(vec.as_slice());614		RpcCollection::<[u8; 32]>::decode(&mut bytes).unwrap();615	}616}