From 22f354796903b97fc29e3b205f0a798876b375d5 Mon Sep 17 00:00:00 2001 From: "will.li" <120463031+higherordertech@users.noreply.github.com> Date: Wed, 24 Apr 2024 20:44:55 +1000 Subject: [PATCH] feat: P-693 add MCRT token holding amount VC, refactored test cases and supported customized assertion ranges (#2673) Co-authored-by: higherordertech --- primitives/core/src/assertion/web3_token.rs | 5 +- .../commands/litentry/request_vc.rs | 2 + .../interfaces/vc/definitions.ts | 1 + .../src/token_holding_amount/mod.rs | 44 +++++++++ .../core/common/src/web3_token/mod.rs | 30 ++++++- .../src/token_holding_amount/mod.rs | 15 ++-- .../core/data-providers/src/moralis.rs | 80 +++++++++++++++-- .../litentry/core/mock-server/src/moralis.rs | 52 +++++++---- .../src/web3_token/token_balance/common.rs | 90 +++++++++++++------ .../src/web3_token/token_balance/mod.rs | 2 +- 10 files changed, 261 insertions(+), 60 deletions(-) diff --git a/primitives/core/src/assertion/web3_token.rs b/primitives/core/src/assertion/web3_token.rs index 653fcf8d58..05cf45a96d 100644 --- a/primitives/core/src/assertion/web3_token.rs +++ b/primitives/core/src/assertion/web3_token.rs @@ -70,6 +70,8 @@ pub enum Web3TokenType { Nfp, #[codec(index = 23)] Sol, + #[codec(index = 24)] + Mcrt, } impl Web3TokenType { @@ -84,7 +86,8 @@ impl Web3TokenType { Web3Network::Litmus, ], Self::Nfp => vec![Web3Network::Bsc], - Self::Sol => vec![Web3Network::Bsc, Web3Network::Ethereum, Web3Network::Solana], + Self::Sol | Self::Mcrt => + vec![Web3Network::Bsc, Web3Network::Ethereum, Web3Network::Solana], _ => vec![Web3Network::Ethereum], } } diff --git a/tee-worker/cli/src/trusted_base_cli/commands/litentry/request_vc.rs b/tee-worker/cli/src/trusted_base_cli/commands/litentry/request_vc.rs index d019854fd7..1e0a15d7b3 100644 --- a/tee-worker/cli/src/trusted_base_cli/commands/litentry/request_vc.rs +++ b/tee-worker/cli/src/trusted_base_cli/commands/litentry/request_vc.rs @@ -264,6 +264,7 @@ pub enum TokenHoldingAmountCommand { Trx, Nfp, Sol, + Mcrt, } #[derive(Subcommand, Debug)] @@ -604,6 +605,7 @@ impl Command { TokenHoldingAmountCommand::Trx => TokenHoldingAmount(Web3TokenType::Trx), TokenHoldingAmountCommand::Nfp => TokenHoldingAmount(Web3TokenType::Nfp), TokenHoldingAmountCommand::Sol => TokenHoldingAmount(Web3TokenType::Sol), + TokenHoldingAmountCommand::Mcrt => TokenHoldingAmount(Web3TokenType::Mcrt), }, Command::PlatformUser(arg) => match arg { PlatformUserCommand::KaratDaoUser => PlatformUser(PlatformUserType::KaratDaoUser), diff --git a/tee-worker/client-api/parachain-api/prepare-build/interfaces/vc/definitions.ts b/tee-worker/client-api/parachain-api/prepare-build/interfaces/vc/definitions.ts index ee83f793b8..eb20aa6b49 100644 --- a/tee-worker/client-api/parachain-api/prepare-build/interfaces/vc/definitions.ts +++ b/tee-worker/client-api/parachain-api/prepare-build/interfaces/vc/definitions.ts @@ -183,6 +183,7 @@ export default { "Trx", "Nfp", "Sol", + "Mcrt", ], }, // PlatformUserType diff --git a/tee-worker/litentry/core/assertion-build-v2/src/token_holding_amount/mod.rs b/tee-worker/litentry/core/assertion-build-v2/src/token_holding_amount/mod.rs index 254992322f..c81d76ad0d 100644 --- a/tee-worker/litentry/core/assertion-build-v2/src/token_holding_amount/mod.rs +++ b/tee-worker/litentry/core/assertion-build-v2/src/token_holding_amount/mod.rs @@ -564,4 +564,48 @@ mod tests { }, }; } + + #[test] + fn build_mcrt_holding_amount_works() { + let data_provider_config = init(); + let address = "EJpLyTeE8XHG9CeREeHd6pr6hNhaRnTRJx4Z5DPhEJJ6" + .from_base58() + .unwrap() + .as_slice() + .try_into() + .unwrap(); + let identities: Vec = + vec![(Identity::Solana(address), vec![Web3Network::Solana])]; + + let req = crate_assertion_build_request(Web3TokenType::Mcrt, identities); + + match build(&req, Web3TokenType::Mcrt, &data_provider_config) { + Ok(credential) => { + log::info!("build mcrt TokenHoldingAmount done"); + assert_eq!( + *(credential.credential_subject.assertions.first().unwrap()), + AssertionLogic::And { + items: vec![ + create_token_assertion_logic(Web3TokenType::Mcrt), + create_network_address_assertion_logics(Web3TokenType::Mcrt), + Box::new(AssertionLogic::Item { + src: "$holding_amount".into(), + op: Op::GreaterEq, + dst: "150000".into() + }), + Box::new(AssertionLogic::Item { + src: "$holding_amount".into(), + op: Op::LessThan, + dst: "500000".into() + }) + ] + } + ); + assert_eq!(*(credential.credential_subject.values.first().unwrap()), true); + }, + Err(e) => { + panic!("build mcrt TokenHoldingAmount failed with error {:?}", e); + }, + } + } } diff --git a/tee-worker/litentry/core/common/src/web3_token/mod.rs b/tee-worker/litentry/core/common/src/web3_token/mod.rs index 1443fe6fbf..ec0612436b 100644 --- a/tee-worker/litentry/core/common/src/web3_token/mod.rs +++ b/tee-worker/litentry/core/common/src/web3_token/mod.rs @@ -20,6 +20,8 @@ compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the sam #[cfg(all(not(feature = "std"), feature = "sgx"))] extern crate sgx_tstd as std; +use std::{vec, vec::Vec}; + use litentry_primitives::Web3TokenType; use crate::Web3Network; @@ -55,6 +57,7 @@ impl TokenName for Web3TokenType { Self::Trx => "TRX", Self::Nfp => "NFP", Self::Sol => "SOL", + Self::Mcrt => "MCRT", } } } @@ -146,6 +149,12 @@ impl TokenAddress for Web3TokenType { (Self::Sol, Web3Network::Bsc) => Some("0x570a5d26f7765ecb712c0924e4de545b89fd43df"), (Self::Sol, Web3Network::Ethereum) => Some("0x5288738df1aeb0894713de903e1d0c001eeb7644"), + // Mcrt + (Self::Mcrt, Web3Network::Bsc) => Some("0x4b8285aB433D8f69CB48d5Ad62b415ed1a221e4f"), + (Self::Mcrt, Web3Network::Ethereum) => + Some("0xde16ce60804a881e9f8c4ebb3824646edecd478d"), + (Self::Mcrt, Web3Network::Solana) => + Some("FADm4QuSUF1K526LvTjvbJjKzeeipP6bj5bSzp3r6ipq"), _ => None, } } @@ -199,9 +208,13 @@ impl TokenDecimals for Web3TokenType { // Sol (Self::Sol, Web3Network::Bsc) | (Self::Sol, Web3Network::Ethereum) => 18, // Ton - (Self::Ton, Web3Network::Bsc) | (Self::Ton, Web3Network::Ethereum) => 9, + (Self::Ton, Web3Network::Bsc) | (Self::Ton, Web3Network::Ethereum) | + // Mcrt + (Self::Mcrt, Web3Network::Bsc) | (Self::Mcrt, Web3Network::Ethereum) => 9, // Wbtc - (Self::Wbtc, Web3Network::Bsc) | (Self::Wbtc, Web3Network::Ethereum) => 8, + (Self::Wbtc, Web3Network::Bsc) | (Self::Wbtc, Web3Network::Ethereum) | + // Mcrt + (Self::Mcrt, Web3Network::Solana) => 8, // Usdc (Self::Usdc, Web3Network::Ethereum) | // Usdt @@ -216,3 +229,16 @@ impl TokenDecimals for Web3TokenType { 10_u64.pow(decimals) } } + +pub trait TokenHoldingAmountRange { + fn get_token_holding_amount_range(&self) -> Vec; +} + +impl TokenHoldingAmountRange for Web3TokenType { + fn get_token_holding_amount_range(&self) -> Vec { + match self { + Self::Mcrt => vec![0.0, 2000.0, 10000.0, 50000.0, 150000.0, 500000.0], + _ => vec![0.0, 1.0, 50.0, 100.0, 200.0, 500.0, 800.0, 1200.0, 1600.0, 3000.0], + } + } +} diff --git a/tee-worker/litentry/core/credentials-v2/src/token_holding_amount/mod.rs b/tee-worker/litentry/core/credentials-v2/src/token_holding_amount/mod.rs index 5d40073d75..24451ba6bb 100644 --- a/tee-worker/litentry/core/credentials-v2/src/token_holding_amount/mod.rs +++ b/tee-worker/litentry/core/credentials-v2/src/token_holding_amount/mod.rs @@ -22,7 +22,7 @@ extern crate sgx_tstd as std; use lc_common::{ web3_network_to_chain, - web3_token::{TokenAddress, TokenName}, + web3_token::{TokenAddress, TokenHoldingAmountRange, TokenName}, }; use litentry_primitives::{Web3Network, Web3TokenType}; @@ -33,9 +33,6 @@ use lc_credentials::{ Credential, }; -const TOKEN_HOLDING_AMOUNT_RANGE: [f64; 10] = - [0.0, 1.0, 50.0, 100.0, 200.0, 500.0, 800.0, 1200.0, 1600.0, 3000.0]; - const TYPE: &str = "Token Holding Amount"; const DESCRIPTION: &str = "The amount of a particular token you are holding"; @@ -82,11 +79,13 @@ fn update_assertion(token_type: Web3TokenType, balance: f64, credential: &mut Cr assertion = assertion.add_item(network_assertion); - let index = BalanceRange::index(&TOKEN_HOLDING_AMOUNT_RANGE, balance); + let token_holding_amount_range_vec = token_type.get_token_holding_amount_range(); + let token_holding_amount_range = token_holding_amount_range_vec.as_slice(); + let index = BalanceRange::index(token_holding_amount_range, balance); match index { Some(index) => { - let min = format!("{}", &TOKEN_HOLDING_AMOUNT_RANGE[index]); - let max = format!("{}", &TOKEN_HOLDING_AMOUNT_RANGE[index + 1]); + let min = format!("{}", token_holding_amount_range[index]); + let max = format!("{}", token_holding_amount_range[index + 1]); let min_item = AssertionLogic::new_item(ASSERTION_KEYS.holding_amount, Op::GreaterEq, &min); let max_item = @@ -101,7 +100,7 @@ fn update_assertion(token_type: Web3TokenType, balance: f64, credential: &mut Cr let min_item = AssertionLogic::new_item( ASSERTION_KEYS.holding_amount, Op::GreaterEq, - &format!("{}", &TOKEN_HOLDING_AMOUNT_RANGE.last().unwrap()), + &format!("{}", token_holding_amount_range.last().unwrap()), ); assertion = assertion.add_item(min_item); diff --git a/tee-worker/litentry/core/data-providers/src/moralis.rs b/tee-worker/litentry/core/data-providers/src/moralis.rs index 4735b7c90c..47ade5a1ab 100644 --- a/tee-worker/litentry/core/data-providers/src/moralis.rs +++ b/tee-worker/litentry/core/data-providers/src/moralis.rs @@ -215,12 +215,25 @@ impl NftApiList for MoralisClient { } #[derive(Serialize, Deserialize, Debug)] -pub struct GetSolanaNativeBalanceBalanceByWalletResponse { +pub struct GetSolanaNativeBalanceByWalletResponse { pub lamports: String, pub solana: String, } -impl<'a> RestPath> for GetSolanaNativeBalanceBalanceByWalletResponse { +impl<'a> RestPath> for GetSolanaNativeBalanceByWalletResponse { + fn get_path(path: ReqPath) -> Result { + Ok(path.path.into()) + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct GetSolanaTokenBalanceByWalletResponse { + // token address + pub mint: String, + pub amount: String, +} + +impl<'a> RestPath> for Vec { fn get_path(path: ReqPath) -> Result { Ok(path.path.into()) } @@ -231,7 +244,13 @@ pub trait BalanceApiList { &mut self, address: String, fast_fail: bool, - ) -> Result; + ) -> Result; + + fn get_solana_tokens_balance_by_wallet( + &mut self, + address: String, + fast_fail: bool, + ) -> Result, Error>; } impl BalanceApiList for MoralisClient { @@ -240,13 +259,13 @@ impl BalanceApiList for MoralisClient { &mut self, address: String, fast_fail: bool, - ) -> Result { + ) -> Result { let params = MoralisRequest { path: format!("account/mainnet/{}/balance", address), query: None }; debug!("get_solana_native_balance_by_wallet, address: {:?}", address); - match self.get::( + match self.get::( ClientType::Solana, params, fast_fail, @@ -261,6 +280,33 @@ impl BalanceApiList for MoralisClient { }, } } + + // https://docs.moralis.io/web3-data-api/solana/reference/get-spl + fn get_solana_tokens_balance_by_wallet( + &mut self, + address: String, + fast_fail: bool, + ) -> Result, Error> { + let params = + MoralisRequest { path: format!("account/mainnet/{}/tokens", address), query: None }; + + debug!("get_solana_tokens_balance_by_wallet, address: {:?}", address); + + match self.get::>( + ClientType::Solana, + params, + fast_fail, + ) { + Ok(resp) => { + debug!("get_solana_tokens_balance_by_wallet, response: {:?}", resp); + Ok(resp) + }, + Err(e) => { + debug!("get_solana_tokens_balance_by_wallet, error: {:?}", e); + Err(e) + }, + } + } } #[cfg(test)] @@ -323,4 +369,28 @@ mod tests { assert_eq!(result.lamports, "0"); assert_eq!(result.solana, "0"); } + + #[test] + fn does_get_solana_tokens_balance_by_wallet_works() { + let config = init(); + let mut client = MoralisClient::new(&config); + let mut result = client + .get_solana_tokens_balance_by_wallet( + "EJpLyTeE8XHG9CeREeHd6pr6hNhaRnTRJx4Z5DPhEJJ6".into(), + true, + ) + .unwrap(); + assert_eq!(result.len(), 2); + assert_eq!(result[0].mint, "FADm4QuSUF1K526LvTjvbJjKzeeipP6bj5bSzp3r6ipq"); + assert_eq!(result[0].amount, "405219.979008"); + assert_eq!(result[1].mint, "BNrgKeLwMUwWQYovZpANYQNCC7Aw8FgvFL3GQut1gL6B"); + assert_eq!(result[1].amount, "31"); + result = client + .get_solana_tokens_balance_by_wallet( + "EJpLyTeE8XHG9CeREeHd6pr6hNhaRnTRJx4Z5DPhEJJ1".into(), + true, + ) + .unwrap(); + assert_eq!(result.len(), 0); + } } diff --git a/tee-worker/litentry/core/mock-server/src/moralis.rs b/tee-worker/litentry/core/mock-server/src/moralis.rs index 02ad953f1e..b8c62d8b87 100644 --- a/tee-worker/litentry/core/mock-server/src/moralis.rs +++ b/tee-worker/litentry/core/mock-server/src/moralis.rs @@ -18,7 +18,8 @@ use std::collections::HashMap; use lc_data_providers::moralis::{ - GetNftsByWalletResult, GetSolanaNativeBalanceBalanceByWalletResponse, MoralisPageResponse, + GetNftsByWalletResult, GetSolanaNativeBalanceByWalletResponse, + GetSolanaTokenBalanceByWalletResponse, MoralisPageResponse, }; use warp::{http::Response, Filter}; @@ -59,20 +60,39 @@ pub(crate) fn query() -> impl Filter impl Filter + Clone { warp::get() - .and(warp::path!("moralis_solana" / "account" / "mainnet" / String / "balance")) - .map(move |address| { - if address == "EJpLyTeE8XHG9CeREeHd6pr6hNhaRnTRJx4Z5DPhEJJ6" { - let body = GetSolanaNativeBalanceBalanceByWalletResponse { - lamports: "5903457912".into(), - solana: "5.903457912".into(), - }; - Response::builder().body(serde_json::to_string(&body).unwrap()) - } else { - let body = GetSolanaNativeBalanceBalanceByWalletResponse { - lamports: "0".into(), - solana: "0".into(), - }; - Response::builder().body(serde_json::to_string(&body).unwrap()) - } + .and(warp::path!("moralis_solana" / "account" / "mainnet" / String / String)) + .map(move |address: String, api: String| match api.as_str() { + "balance" => + if address == "EJpLyTeE8XHG9CeREeHd6pr6hNhaRnTRJx4Z5DPhEJJ6" { + let body = GetSolanaNativeBalanceByWalletResponse { + lamports: "5903457912".into(), + solana: "5.903457912".into(), + }; + Response::builder().body(serde_json::to_string(&body).unwrap()) + } else { + let body = GetSolanaNativeBalanceByWalletResponse { + lamports: "0".into(), + solana: "0".into(), + }; + Response::builder().body(serde_json::to_string(&body).unwrap()) + }, + "tokens" => + if address == "EJpLyTeE8XHG9CeREeHd6pr6hNhaRnTRJx4Z5DPhEJJ6" { + let body = vec![ + GetSolanaTokenBalanceByWalletResponse { + mint: "FADm4QuSUF1K526LvTjvbJjKzeeipP6bj5bSzp3r6ipq".into(), + amount: "405219.979008".into(), + }, + GetSolanaTokenBalanceByWalletResponse { + mint: "BNrgKeLwMUwWQYovZpANYQNCC7Aw8FgvFL3GQut1gL6B".into(), + amount: "31".into(), + }, + ]; + Response::builder().body(serde_json::to_string(&body).unwrap()) + } else { + let body: Vec = vec![]; + Response::builder().body(serde_json::to_string(&body).unwrap()) + }, + _ => Response::builder().status(404).body(String::from("Error query")), }) } diff --git a/tee-worker/litentry/core/service/src/web3_token/token_balance/common.rs b/tee-worker/litentry/core/service/src/web3_token/token_balance/common.rs index 21a8244e71..dfb1f13437 100644 --- a/tee-worker/litentry/core/service/src/web3_token/token_balance/common.rs +++ b/tee-worker/litentry/core/service/src/web3_token/token_balance/common.rs @@ -22,46 +22,82 @@ extern crate sgx_tstd as std; use core::result::Result; -use lc_common::web3_token::{TokenAddress, TokenDecimals}; -use lc_data_providers::nodereal_jsonrpc::{ - FungibleApiList, GetTokenBalance20Param, Web3NetworkNoderealJsonrpcClient, +use lc_common::{ + abort_strategy::{loop_with_abort_strategy, AbortStrategy, LoopControls}, + web3_token::{TokenAddress, TokenDecimals}, }; +use lc_data_providers::{ + moralis::{BalanceApiList, MoralisClient}, + nodereal_jsonrpc::{FungibleApiList, GetTokenBalance20Param, Web3NetworkNoderealJsonrpcClient}, +}; +use log::error; use crate::*; // only support to get balance for non-native token -pub fn get_balance_from_evm( +pub fn get_balance( addresses: Vec<(Web3Network, String)>, token_type: Web3TokenType, data_provider_config: &DataProviderConfig, ) -> Result { let mut total_balance = 0_f64; - for address in addresses.iter() { - let network = address.0; - let decimals = token_type.get_decimals(network); - let param = GetTokenBalance20Param { - contract_address: token_type.get_token_address(network).unwrap_or_default().into(), - address: address.1.clone(), - block_number: "latest".into(), - }; - - match network { - Web3Network::Bsc | Web3Network::Ethereum => { - if let Some(mut client) = - network.create_nodereal_jsonrpc_client(data_provider_config) - { - match client.get_token_balance_20(¶m, false) { - Ok(balance) => { - total_balance += calculate_balance_with_decimals(balance, decimals); + loop_with_abort_strategy( + addresses, + |address| { + let network = address.0; + let token_address = token_type.get_token_address(network).unwrap_or_default(); + + match network { + Web3Network::Bsc | Web3Network::Ethereum => { + let decimals = token_type.get_decimals(network); + match network.create_nodereal_jsonrpc_client(data_provider_config) { + Some(mut client) => { + let param = GetTokenBalance20Param { + contract_address: token_address.into(), + address: address.1.clone(), + block_number: "latest".into(), + }; + let result = client.get_token_balance_20(¶m, false); + match result { + Ok(balance) => { + total_balance += + calculate_balance_with_decimals(balance, decimals); + Ok(LoopControls::Continue) + }, + Err(err) => Err(err.into_error_detail()), + } }, - Err(err) => return Err(err.into_error_detail()), + None => Ok(LoopControls::Continue), } - } - }, - _ => {}, - } - } + }, + Web3Network::Solana => { + let mut client = MoralisClient::new(data_provider_config); + let result = + client.get_solana_tokens_balance_by_wallet(address.1.clone(), false); + + match result { + Ok(items) => match items.iter().find(|&item| item.mint == token_address) { + Some(item) => match item.amount.parse::() { + Ok(balance) => { + total_balance += balance; + Ok(LoopControls::Continue) + }, + Err(err) => { + error!("Failed to parse {} to f64: {}", item.amount, err); + Err(Error::ParseError) + }, + }, + None => Ok(LoopControls::Continue), + }, + Err(err) => Err(err.into_error_detail()), + } + }, + _ => Ok(LoopControls::Continue), + } + }, + AbortStrategy::FailFast:: bool>, + )?; Ok(total_balance) } diff --git a/tee-worker/litentry/core/service/src/web3_token/token_balance/mod.rs b/tee-worker/litentry/core/service/src/web3_token/token_balance/mod.rs index dd489e1ded..b82c8932cb 100644 --- a/tee-worker/litentry/core/service/src/web3_token/token_balance/mod.rs +++ b/tee-worker/litentry/core/service/src/web3_token/token_balance/mod.rs @@ -40,6 +40,6 @@ pub fn get_token_balance( Web3TokenType::Eth => eth_balance::get_balance(addresses, data_provider_config), Web3TokenType::Lit => lit_balance::get_balance(addresses, data_provider_config), Web3TokenType::Sol => sol_balance::get_balance(addresses, data_provider_config), - _ => common::get_balance_from_evm(addresses, token_type, data_provider_config), + _ => common::get_balance(addresses, token_type, data_provider_config), } }