123456789101112131415161718use 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 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 50 #[method(name = "unique_collectionTokens")]51 fn collection_tokens(52 &self,53 collection: CollectionId,54 at: Option<BlockHash>,55 ) -> Result<Vec<TokenId>>;5657 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 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 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 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 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 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 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 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 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 141 #[method(name = "unique_totalSupply")]142 fn total_supply(&self, collection: CollectionId, at: Option<BlockHash>) -> Result<u32>;143144 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 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 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 175 #[method(name = "unique_adminlist")]176 fn adminlist(177 &self,178 collection: CollectionId,179 at: Option<BlockHash>,180 ) -> Result<Vec<CrossAccountId>>;181182 183 #[method(name = "unique_allowlist")]184 fn allowlist(185 &self,186 collection: CollectionId,187 at: Option<BlockHash>,188 ) -> Result<Vec<CrossAccountId>>;189190 191 #[method(name = "unique_allowed")]192 fn allowed(193 &self,194 collection: CollectionId,195 user: CrossAccountId,196 at: Option<BlockHash>,197 ) -> Result<bool>;198199 200 #[method(name = "unique_lastTokenId")]201 fn last_token_id(&self, collection: CollectionId, at: Option<BlockHash>) -> Result<TokenId>;202203 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 212 #[method(name = "unique_collectionStats")]213 fn collection_stats(&self, at: Option<BlockHash>) -> Result<CollectionStats>;214215 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 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 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 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 260 #[method(name = "appPromotion_totalStaked")]261 fn total_staked(262 &self,263 staker: Option<CrossAccountId>,264 at: Option<BlockHash>,265 ) -> Result<String>;266267 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 276 #[method(name = "appPromotion_pendingUnstake")]277 fn pending_unstake(278 &self,279 staker: Option<CrossAccountId>,280 at: Option<BlockHash>,281 ) -> Result<String>;282283 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 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 348 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}