123456789101112131415161718use 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 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 49 #[method(name = "unique_collectionTokens")]50 fn collection_tokens(51 &self,52 collection: CollectionId,53 at: Option<BlockHash>,54 ) -> Result<Vec<TokenId>>;5556 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 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 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 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 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 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 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 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 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 140 #[method(name = "unique_totalSupply")]141 fn total_supply(&self, collection: CollectionId, at: Option<BlockHash>) -> Result<u32>;142143 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 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 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 174 #[method(name = "unique_adminlist")]175 fn adminlist(176 &self,177 collection: CollectionId,178 at: Option<BlockHash>,179 ) -> Result<Vec<CrossAccountId>>;180181 182 #[method(name = "unique_allowlist")]183 fn allowlist(184 &self,185 collection: CollectionId,186 at: Option<BlockHash>,187 ) -> Result<Vec<CrossAccountId>>;188189 190 #[method(name = "unique_allowed")]191 fn allowed(192 &self,193 collection: CollectionId,194 user: CrossAccountId,195 at: Option<BlockHash>,196 ) -> Result<bool>;197198 199 #[method(name = "unique_lastTokenId")]200 fn last_token_id(&self, collection: CollectionId, at: Option<BlockHash>) -> Result<TokenId>;201202 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 211 #[method(name = "unique_collectionStats")]212 fn collection_stats(&self, at: Option<BlockHash>) -> Result<CollectionStats>;213214 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 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 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 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 259 #[method(name = "appPromotion_totalStaked")]260 fn total_staked(261 &self,262 staker: Option<CrossAccountId>,263 at: Option<BlockHash>,264 ) -> Result<String>;265266 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 275 #[method(name = "appPromotion_pendingUnstake")]276 fn pending_unstake(277 &self,278 staker: Option<CrossAccountId>,279 at: Option<BlockHash>,280 ) -> Result<String>;281282 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 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 347 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 )?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 )?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}