#![cfg(test)] use std::time::{SystemTime, UNIX_EPOCH}; use super::*; use crate::mock::*; use frame_support::{assert_err, assert_ok, dispatch}; use sp_core::offchain::{ testing, testing::{TestOffchainExt, TestTransactionPoolExt}, OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, }; use sp_runtime::testing::UintAuthorityId; use ghost_networks::BridgedInflationCurve; use pallet_staking::EraPayout; #[test] fn should_calculate_throttling_slash_function_correctly() { let dummy_offence = SlowClapOffence { session_index: 0, validator_set_count: 69, offenders: vec![()], offence_type: OffenceType::ThrottlingOffence(Perbill::from_percent(50)), }; assert_eq!(dummy_offence.slash_fraction(1), Perbill::from_percent(50)); assert_eq!(dummy_offence.slash_fraction(5), Perbill::from_percent(10)); assert_eq!(dummy_offence.slash_fraction(10), Perbill::from_percent(5)); let dummy_offence = SlowClapOffence { session_index: 0, validator_set_count: 50, offenders: vec![()], offence_type: OffenceType::CommitmentOffence, }; assert_eq!(dummy_offence.slash_fraction(1), Perbill::zero()); assert_eq!(dummy_offence.slash_fraction(5), Perbill::zero()); assert_eq!( dummy_offence.slash_fraction(7), Perbill::from_parts(4_200_000) ); assert_eq!( dummy_offence.slash_fraction(17), Perbill::from_parts(46_200_000) ); } #[test] fn bitmap_operations_correct() { let max_value = 440; let sixty_nine = 69; let four_twenty = 420; let mut bitmap = BitMap::new(max_value); assert_eq!(bitmap.max_value(), max_value); assert!(!bitmap.exists(&sixty_nine)); bitmap.set(sixty_nine); assert!(bitmap.exists(&sixty_nine)); assert!(!bitmap.exists(&four_twenty)); bitmap.set(four_twenty); assert!(bitmap.exists(&four_twenty)); let bitmap_cloned = bitmap.clone(); let mut bucket_iter = bitmap_cloned.iter_buckets(); let empty_result = 0; assert_eq!(bucket_iter.next(), Some((&0u32, &empty_result))); // index = 69 / 64 = 1; bit_pos = 69 % 64 = 5 let first_result = 1 << 5; assert_eq!(bucket_iter.next(), Some((&1u32, &first_result))); assert_eq!(bucket_iter.next(), Some((&2u32, &empty_result))); assert_eq!(bucket_iter.next(), Some((&3u32, &empty_result))); assert_eq!(bucket_iter.next(), Some((&4u32, &empty_result))); assert_eq!(bucket_iter.next(), Some((&5u32, &empty_result))); // index = 420 / 64 = 6; bit_pos = 420 % 64 = 36 let second_result = 1 << 36; assert_eq!(bucket_iter.next(), Some((&6u32, &second_result))); assert_eq!(bucket_iter.next(), None); assert_eq!(bitmap.get_bucket(&0), empty_result); assert_eq!(bitmap.get_bucket(&1), first_result); assert_eq!(bitmap.get_bucket(&6), second_result); bitmap.unset(sixty_nine); bitmap.unset(four_twenty); assert!(!bitmap.exists(&sixty_nine)); assert!(!bitmap.exists(&four_twenty)); assert_eq!(BitMap::size_of_bucket(), 6); // for u64 let mut bitmap1 = BitMap::new(max_value); let mut bitmap2 = BitMap::new(max_value); assert_eq!(bitmap1.max_value(), max_value); assert_eq!(bitmap2.max_value(), max_value); bitmap1.set(1); bitmap1.set(12); bitmap1.set(44); bitmap1.set(80); bitmap2.set(1); bitmap2.set(2); bitmap2.set(15); bitmap2.set(36); bitmap2.set(130); let ones = bitmap1.clone().bitor(bitmap2.clone()).count_ones(); assert_eq!(ones, 8); bitmap2.set(80); bitmap2.set(420); let ones = bitmap1.clone().bitor(bitmap2.clone()).count_ones(); assert_eq!(ones, 9); bitmap1.set(15); bitmap1.set(69); bitmap1.set(33); bitmap2.set(15); bitmap2.set(69); bitmap2.set(33); let ones = bitmap1.clone().bitor(bitmap2.clone()).count_ones(); assert_eq!(ones, 11); } #[test] fn should_correctly_adjust_to_block() { assert_eq!(SlowClap::adjust_to_block(420, 69, 1337), 420); assert_eq!(SlowClap::adjust_to_block(420, 1337, 69), 420); assert_eq!(SlowClap::adjust_to_block(1337, 420, 69), 489); assert_eq!(SlowClap::adjust_to_block(1337, 69, 420), 489); assert_eq!(SlowClap::adjust_to_block(69, 1337, 420), 69); assert_eq!(SlowClap::adjust_to_block(69, 420, 1337), 69); } #[test] fn request_body_is_correct_for_get_block_number() { let (offchain, _) = TestOffchainExt::new(); let mut t = sp_io::TestExternalities::default(); t.register_extension(OffchainWorkerExt::new(offchain)); t.execute_with(|| { let network_data = prepare_evm_network(Some(1), None); let request_body = SlowClap::prepare_request_body_for_latest_block(&network_data); assert_eq!( core::str::from_utf8(&request_body).unwrap(), r#"{"id":0,"jsonrpc":"2.0","method":"eth_blockNumber"}"# ); }); } #[test] fn request_body_is_correct_for_get_logs() { let (offchain, _) = TestOffchainExt::new(); let mut t = sp_io::TestExternalities::default(); t.register_extension(OffchainWorkerExt::new(offchain)); t.execute_with(|| { let from_block: u64 = 420; let to_block: u64 = 1337; let network_data = prepare_evm_network(Some(1), None); let request_body = SlowClap::prepare_request_body_for_latest_transfers( from_block, to_block, &network_data); assert_eq!(core::str::from_utf8(&request_body).unwrap(), r#"{"id":0,"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":"0x1a4","toBlock":"0x539","address":"0x4d224452801ACEd8B2F0aebE155379bb5D594381","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]}]}"#, ); }); } #[test] fn should_make_http_call_for_block_number() { let (offchain, state) = TestOffchainExt::new(); let mut t = sp_io::TestExternalities::default(); t.register_extension(OffchainWorkerExt::new(offchain)); evm_block_response(&mut state.write()); let _: Result<(), OffchainErr> = t.execute_with(|| { let rpc_endpoint = get_rpc_endpoint(); let network_data = prepare_evm_network(Some(1), None); let request_body = SlowClap::prepare_request_body_for_latest_block(&network_data); let raw_response = SlowClap::fetch_from_remote(&rpc_endpoint, &request_body)?; assert_eq!(raw_response.len(), 45usize); // precalculated Ok(()) }); } #[test] fn should_make_http_call_for_logs() { let (offchain, state) = TestOffchainExt::new(); let mut t = sp_io::TestExternalities::default(); t.register_extension(OffchainWorkerExt::new(offchain)); evm_logs_response(&mut state.write()); let _: Result<(), OffchainErr> = t.execute_with(|| { let from_block: u64 = 20335770; let to_block: u64 = 20335858; let rpc_endpoint = get_rpc_endpoint(); let network_data = prepare_evm_network(Some(1), None); let request_body = SlowClap::prepare_request_body_for_latest_transfers( from_block, to_block, &network_data, ); let raw_response = SlowClap::fetch_from_remote(&rpc_endpoint, &request_body)?; assert_eq!(raw_response.len(), 1805); // precalculated Ok(()) }); } #[test] fn should_make_http_call_and_parse_block_number() { let (offchain, state) = TestOffchainExt::new(); let mut t = sp_io::TestExternalities::default(); t.register_extension(OffchainWorkerExt::new(offchain)); evm_block_response(&mut state.write()); let _: Result<(), OffchainErr> = t.execute_with(|| { let rpc_endpoint = get_rpc_endpoint(); let network_data = prepare_evm_network(Some(1), None); let request_body = SlowClap::prepare_request_body_for_latest_block(&network_data); let raw_response = SlowClap::fetch_from_remote(&rpc_endpoint, &request_body)?; let evm_block_number = SlowClap::parse_evm_response(&raw_response).map( |parsed_response| match parsed_response { EvmResponseType::BlockNumber(block_number) => block_number, EvmResponseType::TransactionLogs(_) => 0, }, )?; assert_eq!(evm_block_number, 20335745); Ok(()) }); } #[test] fn should_make_http_call_and_parse_logs() { let (offchain, state) = TestOffchainExt::new(); let (pool, _) = TestTransactionPoolExt::new(); let mut t = sp_io::TestExternalities::default(); t.register_extension(OffchainDbExt::new(offchain.clone())); t.register_extension(OffchainWorkerExt::new(offchain)); t.register_extension(TransactionPoolExt::new(pool)); evm_logs_response(&mut state.write()); let _: Result<(), OffchainErr> = t.execute_with(|| { let rpc_endpoint = get_rpc_endpoint(); let from_block: u64 = 20335770; let to_block: u64 = 20335858; let network_data = prepare_evm_network(None, None); let request_body = SlowClap::prepare_request_body_for_latest_transfers( from_block, to_block, &network_data, ); let raw_response = SlowClap::fetch_from_remote(&rpc_endpoint, &request_body)?; match SlowClap::parse_evm_response(&raw_response)? { EvmResponseType::BlockNumber(_) => assert_eq!(1, 0), // force break EvmResponseType::TransactionLogs(evm_logs) => assert_eq!(evm_logs.len(), 2), } Ok(()) }); } #[test] fn should_emit_black_swan_if_not_enough_authorities_left() { let (network_id, _, _) = generate_unique_hash(None, None, None, None, None); new_test_ext().execute_with(|| { let session_index = advance_session_and_get_index(); prepare_evm_network(None, None); assert_ok!(do_clap_from(session_index, network_id, 0, false)); assert_ok!(do_clap_from(session_index, network_id, 1, false)); Session::disable_index(2); Session::disable_index(3); advance_session(); advance_session(); System::assert_has_event(RuntimeEvent::SlowClap(crate::Event::BlackSwan)); }); } #[test] fn should_emit_offence_on_missed_clap() { let (network_id, _, _) = generate_unique_hash(None, None, None, None, None); new_test_ext().execute_with(|| { let session_index = advance_session_and_get_index(); prepare_evm_network(None, None); assert_ok!(do_clap_from(session_index, network_id, 1, false)); assert_ok!(do_clap_from(session_index, network_id, 2, false)); assert_ok!(do_clap_from(session_index, network_id, 3, false)); advance_session(); advance_session(); System::assert_has_event(RuntimeEvent::SlowClap( crate::Event::SomeAuthoritiesTrottling { throttling: vec![(0, 0)], }, )); }); } #[test] fn should_emit_offence_on_fake_clap() { let (network_id, _, _) = generate_unique_hash(None, None, None, None, None); new_test_ext().execute_with(|| { let session_index = advance_session_and_get_index(); prepare_evm_network(None, None); assert_ok!(do_clap_from(session_index, network_id, 0, false)); assert_ok!(do_clap_from(session_index, network_id, 1, false)); advance_session(); advance_session(); System::assert_has_event(RuntimeEvent::SlowClap( crate::Event::SomeAuthoritiesTrottling { throttling: vec![(0, 0), (1, 1)], }, )); }); } #[test] fn should_not_emit_offence_if_already_disabled() { let (network_id, _, _) = generate_unique_hash(None, None, None, None, None); new_test_ext().execute_with(|| { let session_index = advance_session_and_get_index(); prepare_evm_network(None, None); assert_ok!(do_clap_from(session_index, network_id, 0, false)); assert_ok!(do_clap_from(session_index, network_id, 1, false)); assert_eq!(Session::disable_index(0), true); advance_session(); advance_session(); System::assert_has_event(RuntimeEvent::SlowClap( crate::Event::SomeAuthoritiesTrottling { throttling: vec![(1, 1)], }, )); }); } #[test] fn should_emit_authorities_equilibrium_if_no_deviation() { let (network_id, _, _) = generate_unique_hash(None, None, None, None, None); new_test_ext().execute_with(|| { let session_index = advance_session_and_get_index(); prepare_evm_network(None, None); assert_ok!(do_clap_from(session_index, network_id, 0, false)); assert_ok!(do_clap_from(session_index, network_id, 1, false)); assert_ok!(do_clap_from(session_index, network_id, 2, false)); assert_ok!(do_clap_from(session_index, network_id, 3, false)); advance_session(); advance_session(); let session_index = Session::session_index(); for index in 0..10 { let transaction_hash = H256::repeat_byte(index as u8); let receiver: u64 = index + 1; let amount: u64 = 1_000_000_000_000 * receiver; for authority_index in 0..=3 { let clap = Clap { block_number: 1337u64, removed: false, transaction_hash, authority_index, session_index, network_id, receiver, amount, }; let unique_hash = SlowClap::generate_unique_hash(&clap); let authority = UintAuthorityId::from(index as u64); let signature = authority.sign(&clap.encode()).unwrap(); assert_ok!(SlowClap::slow_clap(RuntimeOrigin::none(), clap, signature)); assert_clapped(&session_index, &unique_hash, authority_index, true); } } advance_session(); advance_session(); let binding = System::events(); let number_of_events = binding .iter() .filter(|x| { x.event == RuntimeEvent::SlowClap(crate::Event::AuthoritiesApplauseEquilibrium) }) .count(); assert_eq!(number_of_events, 6); }); } #[test] fn should_clear_sesions_based_on_history_depth() { let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None); new_test_ext().execute_with(|| { let history_depth = HistoryDepth::get(); let session_index = advance_session_and_get_index(); prepare_evm_network(None, None); assert_eq!( ApplauseDetails::::get(&session_index, &unique_hash), None ); assert_ok!(do_clap_from(session_index, network_id, 0, false)); assert_ok!(do_clap_from(session_index, network_id, 1, false)); assert_ok!(do_clap_from(session_index, network_id, 2, false)); assert_ok!(do_clap_from(session_index, network_id, 3, false)); assert_eq!( ApplauseDetails::::get(&session_index, &unique_hash).is_some(), true ); for _ in 0..history_depth { advance_session(); } assert_eq!( ApplauseDetails::::get(&session_index, &unique_hash), None ); }); } #[test] fn should_collect_commission_accordingly() { let (network_id, _, _) = generate_unique_hash(None, None, None, None, None); let (_, _, amount, _) = get_mocked_metadata(); new_test_ext().execute_with(|| { let _ = prepare_evm_network(None, Some(500_000_000)); let session_index = advance_session_and_get_index(); assert_eq!(Networks::accumulated_commission(), 0); assert_ok!(do_clap_from(session_index, network_id, 0, false)); assert_ok!(do_clap_from(session_index, network_id, 1, false)); assert_ok!(do_clap_from(session_index, network_id, 2, false)); assert_eq!(Networks::accumulated_commission(), amount.saturating_div(2)); }); } #[test] fn should_increase_gatkeeper_amount_accordingly() { let (network_id, _, _) = generate_unique_hash(None, None, None, None, None); let (_, _, amount, _) = get_mocked_metadata(); new_test_ext().execute_with(|| { let _ = prepare_evm_network(None, Some(500_000_000)); let session_index = advance_session_and_get_index(); assert_eq!(Networks::gatekeeper_amount(network_id), 0); assert_eq!(Networks::bridged_imbalance().bridged_in, 0); assert_eq!(Networks::bridged_imbalance().bridged_out, 0); assert_ok!(do_clap_from(session_index, network_id, 0, false)); assert_ok!(do_clap_from(session_index, network_id, 1, false)); assert_ok!(do_clap_from(session_index, network_id, 2, false)); assert_eq!(Networks::gatekeeper_amount(network_id), amount); assert_eq!( Networks::bridged_imbalance().bridged_in, amount.saturating_div(2) ); assert_eq!(Networks::bridged_imbalance().bridged_out, 0); }); } #[test] fn should_mark_clapped_transaction_when_clap_received() { let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None); new_test_ext().execute_with(|| { let session_index = advance_session_and_get_index(); prepare_evm_network(None, None); assert_ok!(do_clap_from(session_index, network_id, 0, false)); assert_clapped(&session_index, &unique_hash, 0, true); assert_ok!(do_clap_from(session_index, network_id, 1, false)); assert_clapped(&session_index, &unique_hash, 1, true); assert_ok!(do_clap_from(session_index, network_id, 2, false)); assert_clapped(&session_index, &unique_hash, 2, true); assert_ok!(do_clap_from(session_index, network_id, 3, false)); assert_clapped(&session_index, &unique_hash, 3, true); }); } #[test] fn should_applause_and_take_next_claps() { let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None); let (_, receiver, amount, _) = get_mocked_metadata(); new_test_ext().execute_with(|| { let mut clapped_amount = Default::default(); let session_index = advance_session_and_get_index(); prepare_evm_network(None, None); assert_eq!(Balances::total_balance(&receiver), 0); for i in 0..=3 { assert_ok!(do_clap_from(session_index, network_id, i, false)); let account_id = TestExposureListener::get_account_by_index(i as usize) .unwrap_or_default(); clapped_amount = TestExposureListener::get_validator_exposure(&account_id) .saturating_add(clapped_amount); assert_clapped_amount(&session_index, &unique_hash, clapped_amount); } assert_applaused(&session_index, &unique_hash); assert_eq!(Balances::total_balance(&receiver), amount); }); } #[test] fn should_throw_error_on_clap_duplication() { let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None); new_test_ext().execute_with(|| { let session_index = advance_session_and_get_index(); prepare_evm_network(None, None); assert_ok!(do_clap_from(session_index, network_id, 0, false)); assert_clapped(&session_index, &unique_hash, 0, true); assert_err!( do_clap_from(session_index, network_id, 0, false), Error::::AlreadyClapped ); assert_clapped(&session_index, &unique_hash, 0, true); }); } #[test] fn should_remove_clap_by_authority() { let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None); new_test_ext().execute_with(|| { let session_index = advance_session_and_get_index(); prepare_evm_network(None, None); assert_ok!(do_clap_from(session_index, network_id, 0, false)); assert_ok!(do_clap_from(session_index, network_id, 1, false)); assert_ok!(do_clap_from(session_index, network_id, 1, true)); assert_clapped(&session_index, &unique_hash, 0, true); assert_clapped(&session_index, &unique_hash, 1, false); }); } #[test] fn should_throw_error_if_executed_block_number_is_higher() { let (network_id, block_number, unique_hash) = generate_unique_hash(None, None, None, None, None); new_test_ext().execute_with(|| { let session_index = advance_session_and_get_index(); prepare_evm_network(None, None); assert_eq!( ApplauseDetails::::get(session_index, unique_hash), None ); assert_ok!(do_clap_from(session_index, network_id, 0, false)); assert_ok!(do_clap_from(session_index, network_id, 1, false)); assert_ok!(do_clap_from(session_index, network_id, 2, false)); assert_ok!(do_clap_from(session_index, network_id, 3, false)); assert_clapped(&session_index, &unique_hash, 0, true); assert_applaused(&session_index, &unique_hash); assert_err!( do_clap_from_at_block(session_index, network_id, 0, block_number - 1), Error::::ExecutedBlockIsHigher, ); }); } #[test] fn should_throw_error_if_validator_disabled_and_ignore_later() { let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None); new_test_ext().execute_with(|| { let session_index = advance_session_and_get_index(); prepare_evm_network(None, None); assert_eq!( ApplauseDetails::::get(&session_index, &unique_hash), None ); assert_eq!(Session::disable_index(0), true); assert_err!( do_clap_from(session_index, network_id, 0, false), Error::::DisabledAuthority, ); assert_eq!( ApplauseDetails::::get(&session_index, &unique_hash), None ); advance_session(); let session_index = session_index + 1; assert_eq!(Session::disable_index(0), true); assert_err!( do_clap_from(session_index, network_id, 0, false), Error::::DisabledAuthority, ); assert_eq!( ApplauseDetails::::get(&session_index, &unique_hash), None ); }); } #[test] fn should_clap_without_applause_on_gatekeeper_amount_overflow() { let big_amount: u64 = u64::MAX; let first_receiver: u64 = 1337; let second_receiver: u64 = 420; let (network_id, block_number, unique_hash) = generate_unique_hash(None, None, Some(first_receiver), Some(big_amount), None); new_test_ext().execute_with(|| { let session_index = advance_session_and_get_index(); prepare_evm_network(None, None); for authority_index in 0..=3 { let clap = Clap { removed: false, block_number, transaction_hash: H256::repeat_byte(1u8), session_index, authority_index, network_id, receiver: first_receiver, amount: big_amount, }; let authority = UintAuthorityId::from(authority_index as u64); let signature = authority.sign(&clap.encode()).unwrap(); assert_ok!(SlowClap::slow_clap(RuntimeOrigin::none(), clap, signature)); assert_clapped(&session_index, &unique_hash, authority_index, true); } assert_applaused(&session_index, &unique_hash); assert_eq!(Balances::total_balance(&first_receiver), big_amount); assert_eq!(Balances::total_balance(&second_receiver), 0); for authority_index in 0..=3 { let clap = Clap { block_number, removed: false, transaction_hash: H256::repeat_byte(3u8), session_index, authority_index, network_id, receiver: second_receiver, amount: second_receiver, }; let authority = UintAuthorityId::from(authority_index as u64); let signature = authority.sign(&clap.encode()).unwrap(); assert_ok!(SlowClap::slow_clap(RuntimeOrigin::none(), clap, signature)); } assert_eq!(Balances::total_balance(&first_receiver), big_amount); assert_eq!(Balances::total_balance(&second_receiver), 0); assert_eq!(Networks::gatekeeper_amount(network_id), big_amount); assert_eq!(Networks::bridged_imbalance().bridged_in, big_amount); assert_eq!(Networks::bridged_imbalance().bridged_out, 0); }); } #[test] fn should_nullify_commission_on_finalize() { let total_staked = 69_000_000; let total_issuance = 100_000_000; let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None); let (_, _, amount, _) = get_mocked_metadata(); new_test_ext().execute_with(|| { let _ = prepare_evm_network(None, Some(1_000_000_000)); let session_index = advance_session_and_get_index(); assert_eq!(Networks::accumulated_commission(), 0); assert_ok!(do_clap_from(session_index, network_id, 0, false)); assert_ok!(do_clap_from(session_index, network_id, 1, false)); assert_ok!(do_clap_from(session_index, network_id, 2, false)); assert_eq!(Networks::accumulated_commission(), amount); assert_eq!(Networks::is_nullification_period(), false); assert_applaused(&session_index, &unique_hash); assert_eq!( BridgedInflationCurve::::era_payout( total_staked, total_issuance, 0u64 ), (amount, 0u64) ); // precomputed values assert_eq!(Networks::is_nullification_period(), true); Networks::on_finalize(System::block_number()); assert_eq!(Networks::is_nullification_period(), false); assert_eq!(Networks::accumulated_commission(), 0); assert_ok!(do_clap_from(session_index, network_id, 3, false)); assert_eq!(Networks::accumulated_commission(), 0); }); } #[test] fn should_avoid_applause_during_nullification_period() { let zero: u64 = 0u64; let total_staked = 69_000_000; let total_issuance = 100_000_000; let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None); let (_, receiver, amount, _) = get_mocked_metadata(); new_test_ext().execute_with(|| { let _ = prepare_evm_network(None, None); let session_index = advance_session_and_get_index(); assert_eq!(Networks::is_nullification_period(), false); assert_eq!( BridgedInflationCurve::::era_payout( total_staked, total_issuance, zero ), (zero, zero) ); assert_eq!(Networks::is_nullification_period(), true); assert_ok!(do_clap_from(session_index, network_id, 0, false)); assert_ok!(do_clap_from(session_index, network_id, 1, false)); assert_eq!(Balances::total_balance(&receiver), 0); assert_clapped(&session_index, &unique_hash, 0, true); assert_clapped(&session_index, &unique_hash, 1, true); assert_clapped(&session_index, &unique_hash, 2, false); assert_clapped(&session_index, &unique_hash, 3, false); Networks::on_finalize(System::block_number()); assert_eq!(Networks::is_nullification_period(), false); assert_ok!(do_clap_from(session_index, network_id, 2, false)); assert_ok!(do_clap_from(session_index, network_id, 3, false)); assert_eq!(Balances::total_balance(&receiver), amount); assert_applaused(&session_index, &unique_hash); assert_clapped(&session_index, &unique_hash, 0, true); assert_clapped(&session_index, &unique_hash, 1, true); assert_clapped(&session_index, &unique_hash, 2, true); assert_clapped(&session_index, &unique_hash, 3, true); }); } #[test] fn should_avoid_session_overlap_on_mended_session_index() { let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None); let (_, receiver, amount, _) = get_mocked_metadata(); new_test_ext().execute_with(|| { let _ = prepare_evm_network(None, None); let session_index = advance_session_and_get_index(); assert_ok!(do_clap_from(session_index, network_id, 0, false)); assert_ok!(do_clap_from(session_index, network_id, 1, false)); assert_clapped(&session_index, &unique_hash, 0, true); assert_clapped(&session_index, &unique_hash, 1, true); assert_clapped(&session_index, &unique_hash, 2, false); assert_clapped(&session_index, &unique_hash, 3, false); advance_session(); let new_session_index = session_index + 1; assert_ok!(do_clap_from(new_session_index, network_id, 2, false)); assert_eq!(Balances::total_balance(&receiver), amount); assert_eq!( ApplauseDetails::::get(new_session_index, unique_hash), None ); assert_applaused(&session_index, &unique_hash); assert_clapped(&session_index, &unique_hash, 0, true); assert_clapped(&session_index, &unique_hash, 1, true); assert_clapped(&session_index, &unique_hash, 2, true); assert_clapped(&session_index, &unique_hash, 3, false); advance_session(); let new_session_index = new_session_index + 1; assert_ok!(do_clap_from(new_session_index, network_id, 3, false)); assert_clapped(&new_session_index, &unique_hash, 0, false); assert_clapped(&new_session_index, &unique_hash, 1, false); assert_clapped(&new_session_index, &unique_hash, 2, false); assert_clapped(&new_session_index, &unique_hash, 3, true); }); } #[test] fn should_emit_event_on_each_clap_and_on_applause() { let (network_id, block_number, unique_hash) = generate_unique_hash(None, None, None, None, None); let (_, receiver, amount, _) = get_mocked_metadata(); new_test_ext().execute_with(|| { let commission = Perbill::from_parts(100_000_000); // 10% let commission_amount = commission.mul_ceil(amount); let amount_after_commission = amount.saturating_sub(commission_amount); let _ = prepare_evm_network(None, Some(100_000_000)); let session_index = advance_session_and_get_index(); assert_eq!( ApplauseDetails::::get(&session_index, &unique_hash), None ); for index in 0..2 { assert_ok!(do_clap_from(session_index, network_id, index, false)); System::assert_last_event(RuntimeEvent::SlowClap(crate::Event::Clapped { clap_unique_hash: unique_hash, receiver: receiver.clone(), authority_id: index, removed: false, network_id, amount, })); } assert_ok!(do_clap_from(session_index, network_id, 2, false)); let binding = System::events(); let last_five_events = binding.iter().rev().take(5).collect::>(); assert_eq!( last_five_events[0].event, RuntimeEvent::SlowClap(crate::Event::Applaused { received_amount: amount_after_commission, receiver: receiver.clone(), block_number, network_id, }) ); assert_eq!( last_five_events[4].event, RuntimeEvent::SlowClap(crate::Event::Clapped { receiver: receiver.clone(), clap_unique_hash: unique_hash, authority_id: 2, removed: false, network_id, amount, }) ); assert_applaused(&session_index, &unique_hash); assert_ok!(do_clap_from(session_index, network_id, 3, false)); System::assert_last_event(RuntimeEvent::SlowClap(crate::Event::Clapped { receiver: receiver.clone(), clap_unique_hash: unique_hash, authority_id: 3, removed: false, network_id, amount, })); }); } #[test] fn should_not_fail_on_sub_existential_balance() { let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None); let (_, receiver, amount, _) = get_mocked_metadata(); new_test_ext().execute_with(|| { let _ = prepare_evm_network(None, Some(1_000_000_000)); // 100% let session_index = advance_session_and_get_index(); assert_eq!(Networks::accumulated_commission(), 0); assert_eq!(Networks::gatekeeper_amount(network_id), 0); assert_eq!(Networks::bridged_imbalance().bridged_in, 0); assert_eq!(Networks::bridged_imbalance().bridged_out, 0); assert_eq!(Balances::total_balance(&receiver), 0); assert_eq!( ApplauseDetails::::get(session_index, unique_hash), None ); assert_ok!(do_clap_from(session_index, network_id, 0, false)); assert_ok!(do_clap_from(session_index, network_id, 1, false)); assert_ok!(do_clap_from(session_index, network_id, 2, false)); assert_eq!(Networks::accumulated_commission(), amount); assert_eq!(Networks::gatekeeper_amount(network_id), amount); assert_eq!(Networks::bridged_imbalance().bridged_in, 0); assert_eq!(Networks::bridged_imbalance().bridged_out, 0); assert_eq!(Balances::total_balance(&receiver), 0); assert_applaused(&session_index, &unique_hash); }); } #[test] fn should_register_block_commitments() { let (network_id, _, _) = generate_unique_hash(None, None, None, None, None); new_test_ext().execute_with(|| { let session_index = advance_session_and_get_index(); prepare_evm_network(None, None); for commitment_details in BlockCommitments::::get(network_id).values() { assert_eq!(commitment_details.last_stored_block, 0); assert_eq!(commitment_details.last_updated, 0); assert_eq!(commitment_details.commits, 0); } let mut block_commitment = CommitmentDetails { last_stored_block: 69, commits: 420, last_updated: 1337, }; for i in 0..=3 { assert_ok!(do_block_commitment( session_index, network_id, i, &block_commitment )); } block_commitment.last_updated = 1000; for i in 0..=3 { assert_err!( do_block_commitment(session_index, network_id, i, &block_commitment), Error::::TimeWentBackwards, ); } for commitment_details in BlockCommitments::::get(network_id).values() { assert_eq!(commitment_details.last_stored_block, 69); assert_eq!(commitment_details.last_updated, 1337); assert_eq!(commitment_details.commits, 1); } advance_session(); for commitment_details in BlockCommitments::::get(network_id).values() { assert_eq!(commitment_details.last_stored_block, 0); assert_eq!(commitment_details.last_updated, 0); assert_eq!(commitment_details.commits, 0); } }); } #[test] fn should_disable_on_commitment_inactivity() { let (network_id, _, _) = generate_unique_hash(None, None, None, None, None); new_test_ext().execute_with(|| { let session_index = advance_session_and_get_index(); prepare_evm_network(None, None); for commitment_details in BlockCommitments::::get(network_id).values() { assert_eq!(commitment_details.last_stored_block, 0); assert_eq!(commitment_details.last_updated, 0); assert_eq!(commitment_details.commits, 0); } let timestamp = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("Time went backwards") .as_millis() as u64; let mut block_commitment = CommitmentDetails { last_stored_block: 69, commits: 420, last_updated: timestamp, }; // two block commitments for extra_time in 0..=2 { block_commitment.last_updated += extra_time; for i in 0..3 { assert_ok!(do_block_commitment( session_index, network_id, i, &block_commitment )); } } System::assert_has_event(RuntimeEvent::SlowClap( crate::Event::SomeAuthoritiesDelayed { delayed: vec![(3, 3)], }, )); for commitment_details in BlockCommitments::::get(network_id) .values() .take(2) { assert_eq!(commitment_details.commits, 3); } }); } #[test] fn should_disable_on_commitment_block_deviation() { let (network_id, _, _) = generate_unique_hash(None, None, None, None, None); new_test_ext().execute_with(|| { let session_index = advance_session_and_get_index(); prepare_evm_network(None, None); for commitment_details in BlockCommitments::::get(network_id).values() { assert_eq!(commitment_details.last_stored_block, 0); assert_eq!(commitment_details.last_updated, 0); } let timestamp = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("Time went backwards") .as_millis() as u64; let mut block_commitment = CommitmentDetails { last_stored_block: 9_500_000, commits: 420, last_updated: timestamp, }; let mut bad_block_commitment = CommitmentDetails { last_stored_block: 9_100_000, commits: 420, last_updated: timestamp, }; // two block commitments for extra_time in 0..=2 { block_commitment.last_updated += extra_time; bad_block_commitment.last_updated += extra_time; for i in 0..3 { assert_ok!(do_block_commitment( session_index, network_id, i, &block_commitment )); } assert_ok!(do_block_commitment( session_index, network_id, 3, &bad_block_commitment )); } System::assert_has_event(RuntimeEvent::SlowClap( crate::Event::SomeAuthoritiesDelayed { delayed: vec![(3, 3)], }, )); }); } #[test] fn should_not_offend_disabled_authorities() { let (network_id, _, _) = generate_unique_hash(None, None, None, None, None); new_test_ext().execute_with(|| { let session_index = advance_session_and_get_index(); prepare_evm_network(None, None); assert_ok!(do_clap_from(session_index, network_id, 0, false)); assert_ok!(do_clap_from(session_index, network_id, 1, false)); assert_ok!(do_clap_from(session_index, network_id, 2, false)); let timestamp = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("Time went backwards") .as_millis() as u64; let mut block_commitment = CommitmentDetails { last_stored_block: 9_500_000, commits: 420, last_updated: timestamp, }; Session::disable_index(3); // two block commitments for extra_time in 0..=2 { block_commitment.last_updated += extra_time; for i in 0..3 { assert_ok!(do_block_commitment( session_index, network_id, i, &block_commitment )); } } System::assert_has_event(RuntimeEvent::SlowClap( crate::Event::AuthoritiesCommitmentEquilibrium, )); }); } #[test] fn should_not_slash_by_applause_if_disabled_by_commitment() { let (network_id, _, _) = generate_unique_hash(None, None, None, None, None); new_test_ext().execute_with(|| { let session_index = advance_session_and_get_index(); prepare_evm_network(None, None); for commitment_details in BlockCommitments::::get(network_id).values() { assert_eq!(commitment_details.last_stored_block, 0); assert_eq!(commitment_details.last_updated, 0); } let timestamp = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("Time went backwards") .as_millis() as u64; let mut block_commitment = CommitmentDetails { last_stored_block: 9_500_000, commits: 420, last_updated: timestamp, }; Session::disable_index(3); // two block commitments for extra_time in 0..=2 { block_commitment.last_updated += extra_time; for i in 0..3 { assert_ok!(do_block_commitment( session_index, network_id, i, &block_commitment )); } } System::assert_has_event(RuntimeEvent::SlowClap( crate::Event::AuthoritiesCommitmentEquilibrium, )); }); } fn assert_clapped_amount( session_index: &SessionIndex, unique_hash: &H256, clapped_amount: BalanceOf, ) { let maybe_details = ApplauseDetails::::get(session_index, unique_hash); assert!(maybe_details.is_some()); let actual_clapped_amount = maybe_details .map(|details| details.clapped_amount) .unwrap_or_default(); assert_eq!(actual_clapped_amount, clapped_amount); } fn assert_applaused(session_index: &SessionIndex, unique_hash: &H256) { let maybe_details = ApplauseDetails::::get(session_index, unique_hash); assert!(maybe_details.is_some()); let is_applaused = maybe_details .map(|details| details.finalized) .unwrap_or_default(); assert!(is_applaused); } fn prepare_evm_network( maybe_network_id: Option, maybe_incoming_commission: Option, ) -> NetworkData { let network_data = NetworkData { chain_name: "Ethereum".into(), default_endpoints: get_rpc_endpoints(), finality_delay: 69, avg_block_speed: 69, rate_limit_delay: 69, block_distance: 69, network_type: ghost_networks::NetworkType::Evm, gatekeeper: get_gatekeeper(), topic_name: get_topic_name(), incoming_fee: maybe_incoming_commission.unwrap_or_default(), outgoing_fee: 0, }; assert_ok!(Networks::register_network( RuntimeOrigin::root(), maybe_network_id.unwrap_or(1u32), network_data.clone() )); network_data } fn do_block_commitment( session_index: u32, network_id: u32, authority_index: u32, commitment: &CommitmentDetails, ) -> dispatch::DispatchResult { let block_commitment = BlockCommitment { session_index, authority_index, network_id, commitment: *commitment, }; let authority = UintAuthorityId::from(authority_index as u64); let signature = authority.sign(&block_commitment.encode()).unwrap(); SlowClap::pre_dispatch(&crate::Call::commit_block { block_commitment: block_commitment.clone(), signature: signature.clone(), }) .map_err(|e| <&'static str>::from(e))?; SlowClap::commit_block(RuntimeOrigin::none(), block_commitment, signature) } fn do_clap_from( session_index: u32, network_id: u32, authority_index: u32, transaction_removed: bool, ) -> dispatch::DispatchResult { let (transaction_hash, receiver, amount, block_number) = get_mocked_metadata(); let clap = Clap { block_number, removed: transaction_removed, transaction_hash, session_index, authority_index, network_id, receiver, amount, }; let authority = UintAuthorityId::from(authority_index as u64); let signature = authority.sign(&clap.encode()).unwrap(); SlowClap::pre_dispatch(&crate::Call::slow_clap { clap: clap.clone(), signature: signature.clone(), }) .map_err(|e| <&'static str>::from(e))?; SlowClap::slow_clap(RuntimeOrigin::none(), clap, signature) } fn do_clap_from_at_block( session_index: u32, network_id: u32, authority_index: u32, block_number: u64, ) -> dispatch::DispatchResult { let transaction_hash = H256::repeat_byte(96u8); let receiver: u64 = 1337; let amount: u64 = 420_000_000_000_000; let clap = Clap { block_number, removed: false, transaction_hash, session_index, authority_index, network_id, receiver, amount, }; let authority = UintAuthorityId::from(authority_index as u64); let signature = authority.sign(&clap.encode()).unwrap(); SlowClap::pre_dispatch(&crate::Call::slow_clap { clap: clap.clone(), signature: signature.clone(), }) .map_err(|e| <&'static str>::from(e))?; SlowClap::slow_clap(RuntimeOrigin::none(), clap, signature) } fn assert_clapped( session_index: &SessionIndex, unique_hash: &H256, authority_index: u32, is_clapped: bool, ) { let maybe_details = ApplauseDetails::::get(session_index, unique_hash); assert!(maybe_details.is_some()); let bitmap = maybe_details.map(|details| details.authorities).unwrap(); assert_eq!(bitmap.exists(&authority_index), is_clapped); } fn advance_session_and_get_index() -> u32 { advance_session(); assert_eq!(Session::validators(), Vec::::new()); advance_session(); Session::session_index() } fn generate_unique_hash( maybe_network_id: Option, maybe_transaction_hash: Option, maybe_receiver: Option, maybe_amount: Option, maybe_block_number: Option, ) -> (NetworkIdOf, u64, H256) { let network_id = maybe_network_id.unwrap_or(1); let (transaction_hash, receiver, amount, block_number) = get_mocked_metadata(); let transaction_hash = maybe_transaction_hash.unwrap_or(transaction_hash); let receiver = maybe_receiver.unwrap_or(receiver); let amount = maybe_amount.unwrap_or(amount); let block_number = maybe_block_number.unwrap_or(block_number); let clap = Clap { session_index: 0, authority_index: 0, removed: false, block_number, transaction_hash, network_id, receiver, amount, }; let unique_hash = SlowClap::generate_unique_hash(&clap); (network_id, block_number, unique_hash) } fn get_rpc_endpoint() -> Vec { b"https://rpc.endpoint.network.com".to_vec() } fn get_rpc_endpoints() -> Vec> { vec![ get_rpc_endpoint(), b"https://other.endpoint.network.com".to_vec(), ] } fn get_gatekeeper() -> Vec { b"0x4d224452801ACEd8B2F0aebE155379bb5D594381".to_vec() } fn get_topic_name() -> Vec { b"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef".to_vec() } fn get_mocked_metadata() -> (H256, u64, u64, u64) { let transaction_hash = H256::repeat_byte(69u8); let receiver: u64 = 1337; let amount: u64 = 420_000_000_000_000; let block_number: u64 = 555; (transaction_hash, receiver, amount, block_number) } fn evm_block_response(state: &mut testing::OffchainState) { let expected_body = br#"{"id":0,"jsonrpc":"2.0","method":"eth_blockNumber"}"#.to_vec(); state.expect_request(testing::PendingRequest { method: "POST".into(), uri: "https://rpc.endpoint.network.com".into(), headers: vec![ ("Accept".to_string(), "application/json".to_string()), ("Content-Type".to_string(), "application/json".to_string()), ], response: Some(b"{\"id\":0,\"jsonrpc\":\"2.0\",\"result\":\"0x1364c81\"}".to_vec()), body: expected_body, sent: true, ..Default::default() }); } fn evm_logs_response(state: &mut testing::OffchainState) { let expected_body = br#"{"id":0,"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":"0x1364c9a","toBlock":"0x1364cf2","address":"0x4d224452801ACEd8B2F0aebE155379bb5D594381","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]}]}"#.to_vec(); let expected_response = br#"{ "id":0, "jsonrpc":"2.0", "result":[ { "address":"0x4d224452801aced8b2f0aebe155379bb5d594381", "topics":[ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x000000000000000000000000d91efec7e42f80156d1d9f660a69847188950747", "0x0000000000000000000000005e611dfbe71b988cf2a3e5fe4191b5e3d4c4212a" ], "data":"0x000000000000000000000000000000000000000000000046f0d522a71fdac000", "blockNumber":"0x1364c9d", "transactionHash":"0xa82f2fe87f4ba01ab3bd2cd4d0fe75a26f0e9a37e2badc004a0e38f9d088a758", "transactionIndex":"0x11", "blockHash":"0x4b08f82d5a948c0bc5c23c8ddce55e5b4536d7454be53668bbd985ef13dca04a", "logIndex":"0x92", "removed":false }, { "address":"0x4d224452801aced8b2f0aebe155379bb5d594381", "topics":[ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000005954ab967bc958940b7eb73ee84797dc8a2afbb9", "0x000000000000000000000000465165be7cacdbd2cbc8334f549fab9783ad6e7a" ], "data":"0x0000000000000000000000000000000000000000000000027c790defec959e75", "blockNumber":"0x1364cf1", "transactionHash":"0xd9f02b79a90db7536b0afb5e24bbc1f4319dc3d8c57af7c39941acd249ec053a", "transactionIndex":"0x2c", "blockHash":"0x6876b18f5081b5d24d5a73107a667a7713256f6e51edbbe52bf8df24e340b0f7", "logIndex":"0x14b", "removed":false } ] }"#.to_vec(); state.expect_request(testing::PendingRequest { method: "POST".into(), uri: "https://rpc.endpoint.network.com".into(), headers: vec![ ("Accept".to_string(), "application/json".to_string()), ("Content-Type".to_string(), "application/json".to_string()), ], response_headers: vec![ ("Accept".to_string(), "application/json".to_string()), ("Content-Type".to_string(), "application/json".to_string()), ], body: expected_body, response: Some(expected_response), sent: true, ..Default::default() }); }