diff --git a/pallets/ethereum-events/src/lib.rs b/pallets/ethereum-events/src/lib.rs index cf717c899..4d32fc46b 100644 --- a/pallets/ethereum-events/src/lib.rs +++ b/pallets/ethereum-events/src/lib.rs @@ -36,8 +36,8 @@ use codec::{Decode, Encode, MaxEncodedLen}; use sp_application_crypto::RuntimeAppPublic; use sp_avn_common::{ event_types::{ - AddedValidatorData, AvtGrowthLiftedData, Challenge, ChallengeReason, CheckResult, - EthEventCheckResult, EthEventId, EventData, LiftedData, NftCancelListingData, + AddedValidatorData, AvtGrowthLiftedData, AvtLowerClaimedData, Challenge, ChallengeReason, + CheckResult, EthEventCheckResult, EthEventId, EventData, LiftedData, NftCancelListingData, NftEndBatchListingData, NftMintData, NftTransferToData, ProcessedEventHandler, ValidEvents, Validator, }, @@ -1078,6 +1078,12 @@ impl Pallet { Error::::EventParsingFailed })?; return Ok(EventData::LogAvtGrowthLifted(event_data)) + } else if event_id.signature == ValidEvents::AvtLowerClaimed.signature() { + let event_data = ::parse_bytes(data, topics).map_err(|e| { + log::warn!("Error parsing T1 LogLowerClaimed Event: {:#?}", e); + Error::::EventParsingFailed + })?; + return Ok(EventData::LogLowerClaimed(event_data)) } else { return Err(Error::::UnrecognizedEventSignature) } diff --git a/pallets/ethereum-events/src/tests/tests.rs b/pallets/ethereum-events/src/tests/tests.rs index e981eba63..ad867cf7c 100644 --- a/pallets/ethereum-events/src/tests/tests.rs +++ b/pallets/ethereum-events/src/tests/tests.rs @@ -24,6 +24,7 @@ mod test_get_contract_address_for { ValidEvents::NftCancelListing => H160::from(NFT_CONTRACT), ValidEvents::NftEndBatchListing => H160::from(NFT_CONTRACT), ValidEvents::AvtGrowthLifted => H160::from(BRIDGE_CONTRACT), + ValidEvents::AvtLowerClaimed => H160::from(BRIDGE_CONTRACT), } } diff --git a/pallets/token-manager/src/lib.rs b/pallets/token-manager/src/lib.rs index e82088e3e..ac124f411 100644 --- a/pallets/token-manager/src/lib.rs +++ b/pallets/token-manager/src/lib.rs @@ -44,7 +44,10 @@ use pallet_avn::{ LowerParams, OnGrowthLiftedHandler, ProcessedEventsChecker, PACKED_LOWER_PARAM_SIZE, }; use sp_avn_common::{ - event_types::{AvtGrowthLiftedData, EthEvent, EventData, LiftedData, ProcessedEventHandler}, + event_types::{ + AvtGrowthLiftedData, AvtLowerClaimedData, EthEvent, EventData, LiftedData, + ProcessedEventHandler, + }, verify_signature, CallDecoder, InnerCallValidator, Proof, }; use sp_core::{ConstU32, MaxEncodedLen, H160, H256}; @@ -219,6 +222,9 @@ pub mod pallet { LowerReadyToClaim { lower_id: LowerId, }, + AvtLowerClaimed { + lower_id: LowerId, + }, FailedToGenerateLowerProof { lower_id: LowerId, }, @@ -238,6 +244,7 @@ pub mod pallet { #[pallet::error] pub enum Error { NoTier1EventForLogLifted, + NoTier1EventForLogLowerClaimed, AmountOverflow, DepositFailed, LowerFailed, @@ -611,16 +618,6 @@ impl Pallet { Ok(()) } - fn lift(event: &EthEvent) -> DispatchResult { - return match &event.event_data { - EventData::LogLifted(d) => return Self::process_lift(event, d), - EventData::LogAvtGrowthLifted(d) => return Self::process_avt_growth_lift(event, d), - - // Event handled or it is not for us, in which case ignore it. - _ => Ok(()), - } - } - fn settle_lower( token_id: T::TokenId, from: &T::AccountId, @@ -915,6 +912,26 @@ impl Pallet { Ok(()) } + fn process_lower_claim(event: &EthEvent, data: &AvtLowerClaimedData) -> DispatchResult { + let event_id = &event.event_id; + let event_validity = T::ProcessedEventsChecker::check_event(event_id); + ensure!(event_validity, Error::::NoTier1EventForLogLowerClaimed); + + ensure!( + LowersReadyToClaim::::contains_key(data.lower_id) == true, + Error::::InvalidLowerId + ); + LowersReadyToClaim::::remove(data.lower_id); + ensure!( + LowersReadyToClaim::::contains_key(data.lower_id) == false, + Error::::InvalidLowerId + ); + + Self::deposit_event(Event::::AvtLowerClaimed { lower_id: data.lower_id }); + + Ok(()) + } + /// The account ID of the AvN treasury. /// This actually does computation. If you need to keep using it, then make sure you cache /// the value and only call this once. @@ -976,7 +993,14 @@ impl Pallet { impl ProcessedEventHandler for Pallet { fn on_event_processed(event: &EthEvent) -> DispatchResult { - return Self::lift(event) + return match &event.event_data { + EventData::LogLifted(d) => return Self::process_lift(event, d), + EventData::LogAvtGrowthLifted(d) => return Self::process_avt_growth_lift(event, d), + EventData::LogLowerClaimed(d) => return Self::process_lower_claim(event, d), + + // Event handled or it is not for us, in which case ignore it. + _ => Ok(()), + } } } diff --git a/pallets/token-manager/src/mock.rs b/pallets/token-manager/src/mock.rs index 7193e2ca5..2b03150cd 100644 --- a/pallets/token-manager/src/mock.rs +++ b/pallets/token-manager/src/mock.rs @@ -529,6 +529,7 @@ pub struct MockData { pub avt_token_lift_event: EthEvent, pub non_avt_token_lift_event: EthEvent, pub empty_data_lift_event: EthEvent, + pub lower_claimed_event: EthEvent, pub receiver_account_id: ::AccountId, pub token_balance_123_tokens: ::TokenBalance, } @@ -571,6 +572,13 @@ impl MockData { }, event_data: EventData::EmptyEvent, }, + lower_claimed_event: EthEvent { + event_id: EthEventId { + signature: ValidEvents::AvtLowerClaimed.signature(), + transaction_hash: H256::random(), + }, + event_data: EventData::LogLowerClaimed(AvtLowerClaimedData { lower_id: 0 }), + }, receiver_account_id, token_balance_123_tokens: Self::get_token_balance(AMOUNT_123_TOKEN), } diff --git a/pallets/token-manager/src/test_avt_tokens.rs b/pallets/token-manager/src/test_avt_tokens.rs index 65543660c..6d3153a0b 100644 --- a/pallets/token-manager/src/test_avt_tokens.rs +++ b/pallets/token-manager/src/test_avt_tokens.rs @@ -26,6 +26,61 @@ use pallet_balances::Error as BalancesError; const USE_RECEIVER_WITH_EXISTING_AMOUNT: bool = true; const USE_RECEIVER_WITH_0_AMOUNT: bool = false; +fn schedule_lower( + from: AccountId, + amount: u128, + t1_recipient: H160, + expected_lower_id: u32, + burn_acc: AccountId, +) { + assert_ok!(TokenManager::schedule_direct_lower( + RuntimeOrigin::signed(from), + from, + AVT_TOKEN_CONTRACT, + amount, + t1_recipient + )); + + // execute lower + fast_forward_to_block(get_expected_execution_block()); + + // Event emitted + assert!(System::events().iter().any(|a| a.event == + RuntimeEvent::TokenManager(crate::Event::::AvtLowered { + sender: from, + recipient: burn_acc, + amount, + t1_recipient, + lower_id: expected_lower_id + }))); +} + +fn perform_lower_setup(lower_id: u32) { + let (_, from, burn_acc, t1_recipient) = MockData::setup_lower_request_data(); + let pre_lower_balance = ::Balances::get((NON_AVT_TOKEN_ID, from)); + let amount = pre_lower_balance; + + let expected_lower_id = lower_id; + schedule_lower(from, amount, t1_recipient, expected_lower_id, burn_acc); + assert!(>::get(expected_lower_id).is_some()); + + let test_proof_data: Vec = "lowerProofReady".to_string().into(); + // Simulate the response from eth-bridge + assert_ok!(TokenManager::process_lower_proof_result( + expected_lower_id, + PALLET_ID.to_vec(), + Ok(test_proof_data.clone()) + )); + + // Generated proof should be stored in LowerReadyToClaim + assert_eq!( + >::get(expected_lower_id) + .unwrap() + .encoded_lower_data, + test_proof_data + ); +} + #[test] fn avn_test_lift_to_zero_balance_account_should_succeed() { let mut ext = ExtBuilder::build_default() @@ -42,7 +97,7 @@ fn avn_test_lift_to_zero_balance_account_should_succeed() { assert_eq!(TokenManager::balance((AVT_TOKEN_CONTRACT, mock_data.receiver_account_id)), 0); assert_eq!(Balances::free_balance(mock_data.receiver_account_id), 0); - assert_ok!(TokenManager::lift(&mock_event)); + assert_ok!(TokenManager::on_event_processed(&mock_event)); assert_eq!(Balances::free_balance(mock_data.receiver_account_id), AMOUNT_123_TOKEN); // check that TokenManager.balance for AVT contract is still 0 @@ -75,7 +130,7 @@ fn avn_test_lift_to_non_zero_balance_account_should_succeed() { assert_eq!(Balances::free_balance(mock_data.receiver_account_id), AMOUNT_100_TOKEN); let new_balance = Balances::free_balance(mock_data.receiver_account_id) + AMOUNT_123_TOKEN; - assert_ok!(TokenManager::lift(&mock_event)); + assert_ok!(TokenManager::on_event_processed(&mock_event)); assert_eq!(Balances::free_balance(mock_data.receiver_account_id), new_balance); // check that TokenManager.balance for AVT contract is still 0 @@ -104,7 +159,7 @@ fn avn_test_lift_max_balance_to_zero_balance_account_should_succeed() { insert_to_mock_processed_events(&mock_event.event_id); assert_eq!(Balances::free_balance(mock_data.receiver_account_id), 0); - assert_ok!(TokenManager::lift(&mock_event)); + assert_ok!(TokenManager::on_event_processed(&mock_event)); assert_eq!(Balances::free_balance(mock_data.receiver_account_id), u128_max_amount); assert!(System::events().iter().any(|a| a.event == @@ -130,7 +185,10 @@ fn avn_test_lift_max_balance_to_non_zero_balance_account_should_return_deposit_f insert_to_mock_processed_events(&mock_event.event_id); let balance_before = Balances::free_balance(mock_data.receiver_account_id); - assert_noop!(TokenManager::lift(&mock_event), Error::::DepositFailed); + assert_noop!( + TokenManager::on_event_processed(&mock_event), + Error::::DepositFailed + ); assert_eq!(Balances::free_balance(mock_data.receiver_account_id), balance_before); assert!(!System::events().iter().any(|a| a.event == @@ -338,3 +396,47 @@ fn avn_test_avt_token_total_lowered_amount_greater_than_balance_max_value_ok() { }))); }); } + +#[test] +fn avt_lower_claimed_succesfully() { + let mut ext = ExtBuilder::build_default() + .with_genesis_config() + .with_balances() + .as_externality(); + + ext.execute_with(|| { + let mock_data = MockData::setup(AMOUNT_123_TOKEN, USE_RECEIVER_WITH_0_AMOUNT); + let mock_event = &mock_data.lower_claimed_event; + insert_to_mock_processed_events(&mock_event.event_id); + + let lower_id = 0; + + perform_lower_setup(lower_id); + + assert_ok!(TokenManager::on_event_processed(&mock_event)); + + assert!(System::events().iter().any(|a| a.event == + RuntimeEvent::TokenManager(crate::Event::::AvtLowerClaimed { + lower_id + }))); + }); +} + +#[test] +fn avt_lower_claimed_fails_due_with_invalid_lower_id() { + let mut ext = ExtBuilder::build_default() + .with_genesis_config() + .with_balances() + .as_externality(); + + ext.execute_with(|| { + let mock_data = MockData::setup(AMOUNT_123_TOKEN, USE_RECEIVER_WITH_0_AMOUNT); + let mock_event = &mock_data.lower_claimed_event; + insert_to_mock_processed_events(&mock_event.event_id); + + assert_noop!( + TokenManager::on_event_processed(&mock_event), + Error::::InvalidLowerId + ); + }); +} diff --git a/pallets/token-manager/src/test_common_cases.rs b/pallets/token-manager/src/test_common_cases.rs index e5363bd1b..fe018eda4 100644 --- a/pallets/token-manager/src/test_common_cases.rs +++ b/pallets/token-manager/src/test_common_cases.rs @@ -43,7 +43,7 @@ fn avn_test_lift_ignored_when_event_type_does_not_match() { 0 ); - assert_ok!(TokenManager::lift(&mock_data.empty_data_lift_event)); + assert_ok!(TokenManager::on_event_processed(&mock_data.empty_data_lift_event)); assert_eq!(Balances::free_balance(mock_data.receiver_account_id), 0); assert_eq!( @@ -86,7 +86,10 @@ fn avn_test_lift_zero_amount_should_fail() { mock_data.receiver_account_id, )); - assert_noop!(TokenManager::lift(&mock_event), Error::::AmountIsZero); + assert_noop!( + TokenManager::on_event_processed(&mock_event), + Error::::AmountIsZero + ); assert_eq!(Balances::free_balance(mock_data.receiver_account_id), avt_token_balance_before); assert_eq!( ::Balances::get(( @@ -128,7 +131,7 @@ fn avn_test_lift_should_fail_when_event_is_not_in_processed_events() { )); assert_noop!( - TokenManager::lift(&mock_event), + TokenManager::on_event_processed(&mock_event), Error::::NoTier1EventForLogLifted ); assert_eq!(Balances::free_balance(mock_data.receiver_account_id), avt_token_balance_before); diff --git a/pallets/token-manager/src/test_non_avt_tokens.rs b/pallets/token-manager/src/test_non_avt_tokens.rs index ebe645f3b..0fbb9eca2 100644 --- a/pallets/token-manager/src/test_non_avt_tokens.rs +++ b/pallets/token-manager/src/test_non_avt_tokens.rs @@ -40,7 +40,7 @@ fn avn_test_lift_to_zero_balance_account_should_succeed() { )), 0 ); - assert_ok!(TokenManager::lift(&mock_event)); + assert_ok!(TokenManager::on_event_processed(&mock_event)); assert_eq!( ::Balances::get(( NON_AVT_TOKEN_ID, @@ -74,7 +74,7 @@ fn avn_test_lift_to_non_zero_balance_account_should_succeed() { )); assert_eq!(token_balance_before, AMOUNT_100_TOKEN); let expected_token_balance = token_balance_before + AMOUNT_123_TOKEN; - assert_ok!(TokenManager::lift(&mock_event)); + assert_ok!(TokenManager::on_event_processed(&mock_event)); let new_token_balance = ::Balances::get(( NON_AVT_TOKEN_ID, mock_data.receiver_account_id, @@ -108,7 +108,7 @@ fn avn_test_lift_max_balance_to_zero_balance_account_should_succeed() { )), 0 ); - assert_ok!(TokenManager::lift(&mock_event)); + assert_ok!(TokenManager::on_event_processed(&mock_event)); assert_eq!( ::Balances::get(( NON_AVT_TOKEN_ID, @@ -142,7 +142,10 @@ fn avn_test_lift_max_balance_to_non_zero_balance_account_should_fail_with_overfl mock_data.receiver_account_id, )); - assert_noop!(TokenManager::lift(&mock_event), Error::::AmountOverflow); + assert_noop!( + TokenManager::on_event_processed(&mock_event), + Error::::AmountOverflow + ); assert_eq!( ::Balances::get(( NON_AVT_TOKEN_ID, diff --git a/primitives/avn-common/src/event_types.rs b/primitives/avn-common/src/event_types.rs index aede613cd..182006504 100644 --- a/primitives/avn-common/src/event_types.rs +++ b/primitives/avn-common/src/event_types.rs @@ -2,7 +2,11 @@ use crate::bounds::NftExternalRefBound; use codec::{Decode, Encode, MaxEncodedLen}; use hex_literal::hex; use sp_core::{bounded::BoundedVec, H160, H256, H512, U256}; -use sp_runtime::{scale_info::TypeInfo, traits::Member, DispatchResult}; +use sp_runtime::{ + scale_info::TypeInfo, + traits::{Member, Zero}, + DispatchResult, +}; use sp_std::{convert::TryInto, vec::Vec}; // ================================= Events Types ==================================== @@ -54,6 +58,11 @@ pub enum Error { AvtGrowthLiftedEventBadTopicLength, AvtGrowthLiftedEventDataOverflow, AvtGrowthLiftedEventPeriodConversion, + + AvtLowerClaimedEventMissingData, + AvtLowerClaimedEventWrongTopicCount, + AvtLowerClaimedEventBadTopicLength, + AvtLowerClaimedEventIdConversion, } #[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo)] @@ -65,6 +74,7 @@ pub enum ValidEvents { NftCancelListing, NftEndBatchListing, AvtGrowthLifted, + AvtLowerClaimed, } impl ValidEvents { @@ -102,6 +112,10 @@ impl ValidEvents { // hex string of Keccak-256 for LogGrowth(uint256,uint32) ValidEvents::AvtGrowthLifted => H256(hex!("3ad58a8dc1110baa37ad88a68db14181b4ef0c69192dfa7699a9588960eca7fd")), + + // hex string of Keccak-256 for LogLowerClaimed(uint32) + ValidEvents::AvtLowerClaimed => + H256(hex!("9853e4c075911a10a89a0f7a46bac6f8a246c4e9152480d16d86aa6a2391a4f1")), } } @@ -120,6 +134,8 @@ impl ValidEvents { return Some(ValidEvents::NftEndBatchListing) } else if signature == &ValidEvents::AvtGrowthLifted.signature() { return Some(ValidEvents::AvtGrowthLifted) + } else if signature == &ValidEvents::AvtLowerClaimed.signature() { + return Some(ValidEvents::AvtLowerClaimed) } else { return None } @@ -555,6 +571,39 @@ impl AvtGrowthLiftedData { } } +// T1 Event definition: +// event LogLowerClaimed(uint32 lowerId); +#[derive(Encode, Decode, Default, Clone, PartialEq, Debug, Eq, TypeInfo, MaxEncodedLen)] +pub struct AvtLowerClaimedData { + pub lower_id: u32, +} + +impl AvtLowerClaimedData { + const TOPIC_LOWER_ID: usize = 1; + + pub fn parse_bytes(data: Option>, topics: Vec>) -> Result { + if data.is_some() { + return Err(Error::AvtLowerClaimedEventMissingData) + } + + if topics.len() != 2 { + return Err(Error::AvtLowerClaimedEventWrongTopicCount) + } + + if topics[Self::TOPIC_LOWER_ID].len() != WORD_LENGTH { + return Err(Error::AvtLowerClaimedEventBadTopicLength) + } + + let lower_id: u32 = u32::from_be_bytes( + topics[Self::TOPIC_LOWER_ID][TWENTY_EIGHT_BYTES..WORD_LENGTH] + .try_into() + .map_err(|_| Error::AvtLowerClaimedEventIdConversion)?, + ); + + return Ok(AvtLowerClaimedData { lower_id }) + } +} + #[derive(Encode, Decode, Clone, PartialEq, Debug, Eq, TypeInfo, MaxEncodedLen)] pub enum EventData { LogAddedValidator(AddedValidatorData), @@ -565,6 +614,7 @@ pub enum EventData { LogNftCancelListing(NftCancelListingData), LogNftEndBatchListing(NftEndBatchListingData), LogAvtGrowthLifted(AvtGrowthLiftedData), + LogLowerClaimed(AvtLowerClaimedData), } impl EventData { @@ -750,3 +800,7 @@ mod nft_event_tests; #[cfg(test)] #[path = "tests/test_avt_growth_event_parsing.rs"] mod test_avt_growth_event_parsing; + +#[cfg(test)] +#[path = "tests/test_lower_claim.rs"] +mod test_lower_claim; diff --git a/primitives/avn-common/src/tests/test_lower_claim.rs b/primitives/avn-common/src/tests/test_lower_claim.rs new file mode 100644 index 000000000..b7da8e8d5 --- /dev/null +++ b/primitives/avn-common/src/tests/test_lower_claim.rs @@ -0,0 +1,119 @@ +// Copyright 2024 Aventus Systems (UK) Ltd. +#[cfg(test)] +use super::*; +use sha3::{Digest, Keccak256}; +use sp_core::U256; +use sp_std::vec::Vec; + +mod end_batch_listing { + use super::*; + + struct AvtLowerClaimedConfig { + topic1: Vec, + topic2_lower_id: Vec, + data: Option>, + bad_topic_short: Vec, + bad_topic_long: Vec, + bad_data: Option>, + } + + impl AvtLowerClaimedConfig { + fn setup() -> Self { + AvtLowerClaimedConfig { + topic1: vec![1; 32], + topic2_lower_id: vec![1u8; 32], + data: None, + bad_topic_short: vec![10; 16], + bad_topic_long: vec![10; 64], + bad_data: Some(vec![2; 1]), + } + } + } + + #[test] + fn event_signature_should_match() { + let mut hasher = Keccak256::new(); + + hasher.input(b"LogLowerClaimed(uint32)"); + let result = hasher.result(); + + assert_eq!(result[..], *ValidEvents::AvtLowerClaimed.signature().as_bytes()); + assert_eq!( + result[..], + hex!("9853e4c075911a10a89a0f7a46bac6f8a246c4e9152480d16d86aa6a2391a4f1")[..] + ); + } + + mod can_successfully_be_parsed { + use super::*; + + #[test] + fn when_event_contains_correct_topics_and_data() { + let config = AvtLowerClaimedConfig::setup(); + + let expected_lower_id = u32::from_be_bytes([1u8; 4]); + + let topics = vec![config.topic1, config.topic2_lower_id]; + let result = AvtLowerClaimedData::parse_bytes(config.data, topics); + + assert!(result.is_ok()); + let result = result.unwrap(); + + assert_eq!(result.lower_id, expected_lower_id); + } + } + + mod fails_parsing_when { + use super::*; + + #[test] + fn non_topic_data_is_not_empty() { + let config = AvtLowerClaimedConfig::setup(); + + let topics = vec![config.topic1, config.topic2_lower_id]; + let result = AvtLowerClaimedData::parse_bytes(config.bad_data, topics); + + assert_eq!(result, Err(Error::AvtLowerClaimedEventMissingData)); + } + + #[test] + fn event_contains_few_topics() { + let config = AvtLowerClaimedConfig::setup(); + + let topics = vec![]; + let result = AvtLowerClaimedData::parse_bytes(config.data, topics); + + assert_eq!(result, Err(Error::AvtLowerClaimedEventWrongTopicCount)); + } + + #[test] + fn event_contains_too_many_topics() { + let config = AvtLowerClaimedConfig::setup(); + + let topics = vec![config.topic1.clone(), config.topic2_lower_id, config.topic1]; + let result = AvtLowerClaimedData::parse_bytes(config.data, topics); + + assert_eq!(result, Err(Error::AvtLowerClaimedEventWrongTopicCount)); + } + + #[test] + fn event_contains_short_topic() { + let config = AvtLowerClaimedConfig::setup(); + + let topics = vec![config.topic1, config.bad_topic_short]; + let result = AvtLowerClaimedData::parse_bytes(config.data, topics); + + assert_eq!(result, Err(Error::AvtLowerClaimedEventBadTopicLength)); + } + + #[test] + fn event_contains_long_topic() { + let config = AvtLowerClaimedConfig::setup(); + + let topics = vec![config.topic1, config.bad_topic_long]; + let result = AvtLowerClaimedData::parse_bytes(config.data, topics); + + assert_eq!(result, Err(Error::AvtLowerClaimedEventBadTopicLength)); + } + } +}