From d2cbdd8f283bd94c6dc4f4f21994f74519473cb1 Mon Sep 17 00:00:00 2001 From: higherordertech Date: Tue, 9 Jul 2024 11:42:24 +1000 Subject: [PATCH] feat: P-914 implemented NFT token holder VC for MFAN on Polygon --- primitives/core/src/assertion/web3_nft.rs | 3 + tee-worker/cli/lit_test_dr_vc.sh | 1 + .../commands/litentry/request_vc.rs | 2 + .../interfaces/vc/definitions.ts | 2 +- .../assertion-build-v2/src/nft_holder/mod.rs | 82 ++++++++++++++ .../litentry/core/common/src/web3_nft/mod.rs | 4 + .../service/src/web3_nft/nft_holder/common.rs | 105 +++++++++++++----- .../service/src/web3_nft/nft_holder/mod.rs | 1 + .../common/utils/vc-helper.ts | 6 + 9 files changed, 175 insertions(+), 31 deletions(-) diff --git a/primitives/core/src/assertion/web3_nft.rs b/primitives/core/src/assertion/web3_nft.rs index d51b30ec3b..495cf10e7a 100644 --- a/primitives/core/src/assertion/web3_nft.rs +++ b/primitives/core/src/assertion/web3_nft.rs @@ -26,6 +26,8 @@ pub enum Web3NftType { WeirdoGhostGang, #[codec(index = 1)] Club3Sbt, + #[codec(index = 2)] + MFan, } impl Web3NftType { @@ -33,6 +35,7 @@ impl Web3NftType { match self { Self::WeirdoGhostGang => vec![Web3Network::Ethereum], Self::Club3Sbt => vec![Web3Network::Bsc, Web3Network::Polygon, Web3Network::Arbitrum], + Self::MFan => vec![Web3Network::Polygon], } } } diff --git a/tee-worker/cli/lit_test_dr_vc.sh b/tee-worker/cli/lit_test_dr_vc.sh index bcabc56b55..c64039f6ea 100755 --- a/tee-worker/cli/lit_test_dr_vc.sh +++ b/tee-worker/cli/lit_test_dr_vc.sh @@ -195,6 +195,7 @@ assertion_vec=( "platform-user karat-dao-user" "nft-holder weirdo-ghost-gang" "nft-holder club3-sbt" + "nft-holder mfan" ) assertions=() 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 8f387c722d..250fbac1ad 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 @@ -290,6 +290,7 @@ pub enum PlatformUserCommand { pub enum NftHolderCommand { WeirdoGhostGang, Club3Sbt, + MFan, } // positional args (to vec) + required arg + optional arg is a nightmare combination for clap parser, @@ -640,6 +641,7 @@ impl Command { Command::NftHolder(arg) => Ok(match arg { NftHolderCommand::WeirdoGhostGang => NftHolder(Web3NftType::WeirdoGhostGang), NftHolderCommand::Club3Sbt => NftHolder(Web3NftType::Club3Sbt), + NftHolderCommand::MFan => NftHolder(Web3NftType::MFan), }), Command::Dynamic(arg) => { let decoded_id = hex::decode(&arg.smart_contract_id.clone()).unwrap(); 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 05cbfcd70e..f55d13e022 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 @@ -244,7 +244,7 @@ export default { }, // Web3NftType Web3NftType: { - _enum: ["WeirdoGhostGang", "Club3Sbt"], + _enum: ["WeirdoGhostGang", "Club3Sbt", "MFan"], }, }, }; diff --git a/tee-worker/litentry/core/assertion-build-v2/src/nft_holder/mod.rs b/tee-worker/litentry/core/assertion-build-v2/src/nft_holder/mod.rs index e503f17721..7056f076dd 100644 --- a/tee-worker/litentry/core/assertion-build-v2/src/nft_holder/mod.rs +++ b/tee-worker/litentry/core/assertion-build-v2/src/nft_holder/mod.rs @@ -197,6 +197,28 @@ mod tests { }) } + fn create_mfan_assertion_logic() -> Box { + Box::new(AssertionLogic::Or { + items: vec![Box::new(AssertionLogic::And { + items: vec![ + Box::new(AssertionLogic::Item { + src: "$network".into(), + op: Op::Equal, + dst: "polygon".into(), + }), + Box::new(AssertionLogic::Item { + src: "$address".into(), + op: Op::Equal, + dst: Web3NftType::MFan + .get_nft_address(Web3Network::Polygon) + .unwrap() + .into(), + }), + ], + })], + }) + } + fn init() -> DataProviderConfig { let _ = env_logger::builder().is_test(true).try_init(); let url = run(0).unwrap(); @@ -307,4 +329,64 @@ mod tests { }, } } + + #[test] + fn build_mfan_holder_works() { + let data_provider_config = init(); + let mut address = + decode_hex("0x49ad262c49c7aa708cc2df262ed53b64a17dd5ee".as_bytes().to_vec()) + .unwrap() + .as_slice() + .try_into() + .unwrap(); + let mut identities: Vec = + vec![(Identity::Evm(address), vec![Web3Network::Polygon])]; + + let mut req = crate_assertion_build_request(Web3NftType::MFan, identities); + match build(&req, Web3NftType::MFan, &data_provider_config) { + Ok(credential) => { + log::info!("build MFan holder done"); + assert_eq!( + *(credential.credential_subject.assertions.first().unwrap()), + AssertionLogic::And { + items: vec![ + create_token_assertion_logic(Web3NftType::MFan), + create_mfan_assertion_logic(), + ] + } + ); + assert_eq!(*(credential.credential_subject.values.first().unwrap()), true); + }, + Err(e) => { + panic!("build MFan holder failed with error {:?}", e); + }, + } + + address = decode_hex("0x45cdb67696802b9d01ed156b883269dbdb9c6239".as_bytes().to_vec()) + .unwrap() + .as_slice() + .try_into() + .unwrap(); + identities = vec![(Identity::Evm(address), vec![Web3Network::Polygon])]; + + req = crate_assertion_build_request(Web3NftType::MFan, identities); + match build(&req, Web3NftType::MFan, &data_provider_config) { + Ok(credential) => { + log::info!("build MFan holder done"); + assert_eq!( + *(credential.credential_subject.assertions.first().unwrap()), + AssertionLogic::And { + items: vec![ + create_token_assertion_logic(Web3NftType::MFan), + create_mfan_assertion_logic(), + ] + } + ); + assert_eq!(*(credential.credential_subject.values.first().unwrap()), false); + }, + Err(e) => { + panic!("build MFan holder failed with error {:?}", e); + }, + } + } } diff --git a/tee-worker/litentry/core/common/src/web3_nft/mod.rs b/tee-worker/litentry/core/common/src/web3_nft/mod.rs index 3d48ae84e4..79c235fe4c 100644 --- a/tee-worker/litentry/core/common/src/web3_nft/mod.rs +++ b/tee-worker/litentry/core/common/src/web3_nft/mod.rs @@ -33,6 +33,7 @@ impl NftName for Web3NftType { match self { Self::WeirdoGhostGang => "Weirdo Ghost Gang", Self::Club3Sbt => "Club3 SBT", + Self::MFan => "MFAN", } } } @@ -54,6 +55,9 @@ impl NftAddress for Web3NftType { Some("0xAc2e4e67cffa5E82bfA1e169e5F9aa405114C982"), (Self::Club3Sbt, Web3Network::Arbitrum) => Some("0xcccFF19FB8a4a2A206d07842b8F8c8c0A11904C2"), + // MFan + (Self::MFan, Web3Network::Polygon) => + Some("0x9aBc7C604C27622f9CD56bd1628F6321c32bBBf6"), _ => None, } } diff --git a/tee-worker/litentry/core/service/src/web3_nft/nft_holder/common.rs b/tee-worker/litentry/core/service/src/web3_nft/nft_holder/common.rs index 055626652a..b2e730cfb0 100644 --- a/tee-worker/litentry/core/service/src/web3_nft/nft_holder/common.rs +++ b/tee-worker/litentry/core/service/src/web3_nft/nft_holder/common.rs @@ -75,6 +75,23 @@ pub fn has_nft_721( None => Ok(LoopControls::Continue), } }, + Web3Network::Polygon => { + match check_nft_via_moralis( + network, + address.1.clone(), + token_address.into(), + data_provider_config, + ) { + Ok(r) => { + if r { + result = true; + return Ok(LoopControls::Break) + } + Ok(LoopControls::Continue) + }, + Err(err) => Err(err), + } + }, _ => Ok(LoopControls::Continue), } }, @@ -85,6 +102,50 @@ pub fn has_nft_721( Ok(result) } +pub fn check_nft_via_moralis( + network: Web3Network, + address: String, + token_address: String, + data_provider_config: &DataProviderConfig, +) -> Result { + let mut client = MoralisClient::new(data_provider_config); + + match network { + Web3Network::Bsc | Web3Network::Ethereum | Web3Network::Polygon | Web3Network::Arbitrum => { + let mut cursor: Option = None; + 'inner: loop { + let param = GetNftsByWalletParam { + address: address.clone(), + chain: MoralisChainParam::new(&network), + token_addresses: Some(vec![token_address.clone()]), + limit: None, + cursor, + }; + match client.get_nfts_by_wallet(¶m, false) { + Ok(resp) => { + cursor = resp.cursor; + for item in &resp.result { + match item.amount.parse::() { + Ok(balance) => + if balance > 0 { + return Ok(true) + }, + Err(_) => return Err(ErrorDetail::ParseError), + } + } + }, + Err(err) => return Err(err.into_error_detail()), + } + if cursor.is_none() { + break 'inner + } + } + Ok(false) + }, + _ => Ok(false), + } +} + // support ERC1155/BEP1155 nft token pub fn has_nft_1155( addresses: Vec<(Web3Network, String)>, @@ -92,7 +153,6 @@ pub fn has_nft_1155( data_provider_config: &DataProviderConfig, ) -> Result { let mut result = false; - let mut client = MoralisClient::new(data_provider_config); loop_with_abort_strategy( addresses, @@ -105,36 +165,21 @@ pub fn has_nft_1155( | Web3Network::Ethereum | Web3Network::Polygon | Web3Network::Arbitrum => { - let mut cursor: Option = None; - 'inner: loop { - let param = GetNftsByWalletParam { - address: address.1.clone(), - chain: MoralisChainParam::new(&network), - token_addresses: Some(vec![token_address.into()]), - limit: None, - cursor, - }; - match client.get_nfts_by_wallet(¶m, false) { - Ok(resp) => { - cursor = resp.cursor; - for item in &resp.result { - match item.amount.parse::() { - Ok(balance) => - if balance > 0 { - result = true; - return Ok(LoopControls::Break) - }, - Err(_) => return Err(ErrorDetail::ParseError), - } - } - }, - Err(err) => return Err(err.into_error_detail()), - } - if cursor.is_none() { - break 'inner - } + match check_nft_via_moralis( + network, + address.1.clone(), + token_address.into(), + data_provider_config, + ) { + Ok(r) => { + if r { + result = true; + return Ok(LoopControls::Break) + } + Ok(LoopControls::Continue) + }, + Err(err) => Err(err), } - Ok(LoopControls::Continue) }, _ => Ok(LoopControls::Continue), } diff --git a/tee-worker/litentry/core/service/src/web3_nft/nft_holder/mod.rs b/tee-worker/litentry/core/service/src/web3_nft/nft_holder/mod.rs index 8f24d928ef..3ecba5e2ac 100644 --- a/tee-worker/litentry/core/service/src/web3_nft/nft_holder/mod.rs +++ b/tee-worker/litentry/core/service/src/web3_nft/nft_holder/mod.rs @@ -35,5 +35,6 @@ pub fn has_nft( Web3NftType::WeirdoGhostGang => common::has_nft_721(addresses, nft_type, data_provider_config), Web3NftType::Club3Sbt => common::has_nft_1155(addresses, nft_type, data_provider_config), + Web3NftType::MFan => common::has_nft_721(addresses, nft_type, data_provider_config), } } diff --git a/tee-worker/ts-tests/integration-tests/common/utils/vc-helper.ts b/tee-worker/ts-tests/integration-tests/common/utils/vc-helper.ts index e0b4a6c562..7e80cfede5 100644 --- a/tee-worker/ts-tests/integration-tests/common/utils/vc-helper.ts +++ b/tee-worker/ts-tests/integration-tests/common/utils/vc-helper.ts @@ -216,6 +216,12 @@ export const mockAssertions = [ NftHolder: 'Club3Sbt', }, }, + { + description: 'You are a holder of a certain kind of NFT', + assertion: { + NftHolder: 'MFan', + }, + }, // TokenHoldingAmount {