#![cfg(test)] 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, DispatchError}; use ghost_networks::BridgedInflationCurve; use pallet_staking::EraPayout; const MAX_DEVIATION_DEPTH: u64 = 10; 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, 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_clap_from_first_authority( session_index: u32, network_id: u32, authority: u64, ) -> dispatch::DispatchResult { let (transaction_hash, receiver, amount) = get_mocked_metadata(); let clap = Clap { block_number: 420, removed: false, transaction_hash, session_index, authority_index: 0, network_id, receiver, amount, }; let authority = UintAuthorityId::from(authority); 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( session_index: u32, network_id: u32, authority_index: u32, transaction_removed: bool, ) -> dispatch::DispatchResult { let (transaction_hash, receiver, amount) = get_mocked_metadata(); let clap = Clap { block_number: 420, removed: transaction_removed, transaction_hash, session_index, authority_index, network_id, receiver, amount, }; let authority = UintAuthorityId::from((authority_index + 1) 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) } #[test] fn test_throttling_slash_function() { let dummy_offence = ThrottlingOffence { session_index: 0, validator_set_count: 50, offenders: vec![()], }; 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(4200000) ); assert_eq!( dummy_offence.slash_fraction(17), Perbill::from_parts(46200000) ); } #[test] fn test_median_calculations_are_correct() { new_test_ext().execute_with(|| { let data = BTreeMap::from([ ( 0u32, SessionAuthorityInfo { claps: 0, disabled: true, }, ), ( 3u32, SessionAuthorityInfo { claps: 1, disabled: false, }, ), ]); assert_eq!(SlowClap::calculate_median_claps(&data, 4), 0); let data = BTreeMap::from([ ( 0u32, SessionAuthorityInfo { claps: 0, disabled: false, }, ), ( 1u32, SessionAuthorityInfo { claps: 69, disabled: false, }, ), ( 2u32, SessionAuthorityInfo { claps: 69, disabled: false, }, ), ( 3u32, SessionAuthorityInfo { claps: 420, disabled: false, }, ), ]); assert_eq!(SlowClap::calculate_median_claps(&data, 4), 69); let data = BTreeMap::from([ ( 0u32, SessionAuthorityInfo { claps: 31, disabled: false, }, ), ( 1u32, SessionAuthorityInfo { claps: 420, disabled: true, }, ), ( 2u32, SessionAuthorityInfo { claps: 69, disabled: false, }, ), ( 3u32, SessionAuthorityInfo { claps: 156, disabled: true, }, ), ]); assert_eq!(SlowClap::calculate_median_claps(&data, 4), 50); let data = BTreeMap::from([ ( 0u32, SessionAuthorityInfo { claps: 0, disabled: true, }, ), ( 1u32, SessionAuthorityInfo { claps: 420, disabled: false, }, ), ( 2u32, SessionAuthorityInfo { claps: 0, disabled: true, }, ), ( 3u32, SessionAuthorityInfo { claps: 0, disabled: false, }, ), ]); assert_eq!(SlowClap::calculate_median_claps(&data, 4), 210); let data = BTreeMap::from([ ( 0u32, SessionAuthorityInfo { claps: 0, disabled: false, }, ), ( 1u32, SessionAuthorityInfo { claps: 420, disabled: true, }, ), ( 2u32, SessionAuthorityInfo { claps: 69, disabled: true, }, ), ( 3u32, SessionAuthorityInfo { claps: 0, disabled: false, }, ), ]); assert_eq!(SlowClap::calculate_median_claps(&data, 4), 0); }); } #[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 maybe_evm_block_number = SlowClap::apply_evm_response(&raw_response, 69, Default::default(), 420, 1)?; assert_eq!(maybe_evm_block_number, Some(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 session_index = advance_session_and_get_index(); let rpc_endpoint = get_rpc_endpoint(); let from_block: u64 = 20335770; let to_block: u64 = 20335858; let network_id: u32 = 1; let network_data = prepare_evm_network(Some(network_id), 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), } let maybe_evm_block_number = SlowClap::apply_evm_response( &raw_response, 1, UintAuthorityId::from(2), session_index, network_id, )?; assert_eq!(maybe_evm_block_number, None); Ok(()) }); } #[test] fn should_clear_sesions_based_on_history_depth() { let (network_id, transaction_hash, unique_transaction_hash) = generate_unique_hash(None, None, None, None); new_test_ext().execute_with(|| { let session_index = advance_session_and_get_index(); let history_depth = HistoryDepth::get(); let storage_key = (session_index, transaction_hash, unique_transaction_hash); assert_claps_info_correct(&storage_key, &session_index, 0); assert_eq!( pallet::ApplausesForTransaction::::get(&storage_key), false ); 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_claps_info_correct(&storage_key, &session_index, 3); assert_eq!( pallet::ApplausesForTransaction::::get(&storage_key), true ); for _ in 0..history_depth { advance_session(); } assert_claps_info_correct(&storage_key, &session_index, 0); assert_eq!( pallet::ApplausesForTransaction::::get(&storage_key), false ); }); } #[test] fn should_collect_commission_accordingly() { let (network_id, _, _) = generate_unique_hash(None, None, None, None); let (_, _, amount) = get_mocked_metadata(); new_test_ext().execute_with(|| { let _ = prepare_evm_network(Some(network_id), 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); let (_, _, amount) = get_mocked_metadata(); new_test_ext().execute_with(|| { let _ = prepare_evm_network(Some(network_id), 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, transaction_hash, unique_transaction_hash) = generate_unique_hash(None, None, None, None); new_test_ext().execute_with(|| { let session_index = advance_session_and_get_index(); let storage_key = (session_index, transaction_hash, unique_transaction_hash); assert_claps_info_correct(&storage_key, &session_index, 0); assert_ok!(do_clap_from(session_index, network_id, 0, false)); assert_claps_info_correct(&storage_key, &session_index, 1); assert_ok!(do_clap_from(session_index, network_id, 1, false)); assert_claps_info_correct(&storage_key, &session_index, 2); assert_ok!(do_clap_from(session_index, network_id, 2, false)); assert_claps_info_correct(&storage_key, &session_index, 3); }); } #[test] fn should_applause_and_take_next_claps() { let (network_id, transaction_hash, unique_transaction_hash) = generate_unique_hash(None, None, None, None); let (_, receiver, amount) = get_mocked_metadata(); new_test_ext().execute_with(|| { let session_index = advance_session_and_get_index(); let storage_key = (session_index, transaction_hash, unique_transaction_hash); assert_eq!( pallet::ApplausesForTransaction::::get(&storage_key), false ); assert_eq!(Balances::balance(&receiver), 0); assert_ok!(do_clap_from(session_index, network_id, 0, false)); assert_eq!( pallet::ApplausesForTransaction::::get(&storage_key), false ); assert_eq!(Balances::balance(&receiver), 0); assert_ok!(do_clap_from(session_index, network_id, 1, false)); assert_eq!( pallet::ApplausesForTransaction::::get(&storage_key), true ); assert_eq!(Balances::balance(&receiver), amount); assert_ok!(do_clap_from(session_index, network_id, 2, false)); assert_eq!( pallet::ApplausesForTransaction::::get(&storage_key), true ); assert_eq!(Balances::balance(&receiver), amount); }); } #[test] fn should_throw_error_on_clap_duplication() { let (network_id, transaction_hash, unique_transaction_hash) = generate_unique_hash(None, None, None, None); new_test_ext().execute_with(|| { let session_index = advance_session_and_get_index(); let storage_key = (session_index, transaction_hash, unique_transaction_hash); assert_claps_info_correct(&storage_key, &session_index, 0); assert_ok!(do_clap_from(session_index, network_id, 0, false)); assert_claps_info_correct(&storage_key, &session_index, 1); assert_err!( do_clap_from(session_index, network_id, 0, false), Error::::AlreadyClapped ); assert_claps_info_correct(&storage_key, &session_index, 1); }); } #[test] fn should_throw_error_on_removal_of_unregistered_clap() { let (network_id, transaction_hash, unique_transaction_hash) = generate_unique_hash(None, None, None, None); new_test_ext().execute_with(|| { let session_index = advance_session_and_get_index(); let storage_key = (session_index, transaction_hash, unique_transaction_hash); assert_claps_info_correct(&storage_key, &session_index, 0); assert_err!( do_clap_from(session_index, network_id, 0, true), Error::::UnregisteredClapRemove ); assert_claps_info_correct(&storage_key, &session_index, 0); }); } #[test] fn should_throw_error_if_session_index_is_not_current() { let (network_id, transaction_hash, unique_transaction_hash) = generate_unique_hash(None, None, None, None); let bad_signer = 777; let bad_signer_id = 5; new_test_ext().execute_with(|| { let mut session_and_indexes = Vec::new(); for deviation in 1..MAX_DEVIATION_DEPTH { advance_session_with_authority(deviation); session_and_indexes.push((deviation, Session::current_index())); } for chunk in session_and_indexes.chunks(3).into_iter() { let authority_curr = chunk[1].0; let authority_prev = chunk[0].0; let authority_next = chunk[2].0; let session_index_curr = chunk[1].1; let session_index_prev = chunk[0].1; let session_index_next = chunk[2].1; let storage_key_curr = ( session_index_curr, transaction_hash, unique_transaction_hash, ); let storage_key_prev = ( session_index_prev, transaction_hash, unique_transaction_hash, ); let storage_key_next = ( session_index_next, transaction_hash, unique_transaction_hash, ); assert_claps_info_correct(&storage_key_curr, &session_index_curr, 0); assert_claps_info_correct(&storage_key_prev, &session_index_prev, 0); assert_claps_info_correct(&storage_key_next, &session_index_next, 0); assert_invalid_signing_address(session_index_curr, network_id, bad_signer_id); assert_invalid_signing_address(session_index_prev, network_id, bad_signer_id); assert_invalid_signing_address(session_index_prev, network_id, bad_signer_id); assert_transaction_has_bad_signature(session_index_curr, network_id, bad_signer); assert_transaction_has_bad_signature(session_index_prev, network_id, bad_signer); assert_transaction_has_bad_signature(session_index_prev, network_id, bad_signer); assert_transaction_has_bad_signature(session_index_curr, network_id, authority_prev); assert_transaction_has_bad_signature(session_index_curr, network_id, authority_next); assert_transaction_has_bad_signature(session_index_prev, network_id, authority_curr); assert_transaction_has_bad_signature(session_index_prev, network_id, authority_next); assert_transaction_has_bad_signature(session_index_next, network_id, authority_prev); assert_transaction_has_bad_signature(session_index_next, network_id, authority_curr); assert_claps_info_correct(&storage_key_curr, &session_index_curr, 0); assert_claps_info_correct(&storage_key_prev, &session_index_prev, 0); assert_claps_info_correct(&storage_key_next, &session_index_next, 0); assert_ok!(do_clap_from_first_authority( session_index_curr, network_id, authority_curr )); assert_ok!(do_clap_from_first_authority( session_index_prev, network_id, authority_prev )); assert_ok!(do_clap_from_first_authority( session_index_next, network_id, authority_next )); assert_claps_info_correct(&storage_key_curr, &session_index_curr, 1); assert_claps_info_correct(&storage_key_prev, &session_index_prev, 1); assert_claps_info_correct(&storage_key_next, &session_index_next, 1); } }); } #[test] fn should_throw_error_if_signer_has_incorrect_index() { let (network_id, transaction_hash, unique_transaction_hash) = generate_unique_hash(None, None, None, None); new_test_ext().execute_with(|| { let session_index = advance_session_and_get_index(); let storage_key = (session_index, transaction_hash, unique_transaction_hash); assert_claps_info_correct(&storage_key, &session_index, 0); let clap = Clap { block_number: 420, removed: false, transaction_hash, session_index, authority_index: 1337, network_id, receiver: 69, amount: 420, }; let authority = UintAuthorityId::from((1) as u64); let signature = authority.sign(&clap.encode()).unwrap(); assert_err!( SlowClap::slow_clap(RuntimeOrigin::none(), clap, signature), Error::::NotAnAuthority ); assert_claps_info_correct(&storage_key, &session_index, 0); }); } #[test] fn should_throw_error_if_validator_disabled_and_ignore_later() { let (network_id, transaction_hash, unique_transaction_hash) = generate_unique_hash(None, None, None, None); new_test_ext().execute_with(|| { let session_index = advance_session_and_get_index(); let storage_key = (session_index, transaction_hash, unique_transaction_hash); assert_claps_info_correct(&storage_key, &session_index, 0); assert_eq!(Session::disable_index(0), true); assert_err!( do_clap_from(session_index, network_id, 0, false), Error::::CurrentValidatorIsDisabled ); assert_eq!(pallet::ReceivedClaps::::get(&storage_key).len(), 0); assert_eq!( pallet::ClapsInSession::::get(&session_index).len(), 1 ); assert_eq!( pallet::ClapsInSession::::get(&session_index) .into_iter() .filter(|(_, v)| !v.disabled) .collect::>() .len(), 0 ); assert_ok!(do_clap_from(session_index, network_id, 1, false)); assert_eq!(Session::disable_index(1), true); assert_eq!(pallet::ReceivedClaps::::get(&storage_key).len(), 1); assert_eq!( pallet::ClapsInSession::::get(&session_index).len(), 2 ); assert_eq!( pallet::ClapsInSession::::get(&session_index) .into_iter() .filter(|(_, v)| !v.disabled) .collect::>() .len(), 0 ); }); } #[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, transaction_hash, unique_transaction_hash) = generate_unique_hash(None, None, Some(first_receiver), Some(big_amount)); new_test_ext().execute_with(|| { let session_index = advance_session_and_get_index(); let storage_key_first = (session_index, transaction_hash, unique_transaction_hash); for authority_index in 0..=2 { let clap = Clap { block_number: 420, removed: false, transaction_hash, session_index, authority_index, network_id, receiver: first_receiver, amount: big_amount, }; let authority = UintAuthorityId::from((authority_index + 1) as u64); let signature = authority.sign(&clap.encode()).unwrap(); assert_ok!(SlowClap::slow_clap(RuntimeOrigin::none(), clap, signature)); } assert_claps_info_correct(&storage_key_first, &session_index, 3); assert_eq!( pallet::ApplausesForTransaction::::get(&storage_key_first), true ); assert_eq!(Balances::balance(&first_receiver), big_amount); assert_eq!(Balances::balance(&second_receiver), 0); for authority_index in 0..=2 { let clap = Clap { block_number: 420, removed: false, transaction_hash: H256::repeat_byte(3u8), session_index, authority_index, network_id, receiver: second_receiver, amount: 420, }; let authority = UintAuthorityId::from((authority_index + 1) as u64); let signature = authority.sign(&clap.encode()).unwrap(); assert_ok!(SlowClap::slow_clap(RuntimeOrigin::none(), clap, signature)); } assert_eq!(Balances::balance(&first_receiver), big_amount); assert_eq!(Balances::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_clap_without_applause_on_commission_overflow() { let big_amount: u64 = u64::MAX; let first_receiver: u64 = 1337; let second_receiver: u64 = 420; let network_id_other: u32 = 69; let (network_id, transaction_hash, unique_transaction_hash) = generate_unique_hash(None, None, Some(first_receiver), Some(big_amount)); new_test_ext().execute_with(|| { let _ = prepare_evm_network(Some(network_id), Some(0)); let _ = prepare_evm_network(Some(network_id_other), Some(0)); let session_index = advance_session_and_get_index(); let storage_key_first = (session_index, transaction_hash, unique_transaction_hash); for authority_index in 0..=2 { let clap = Clap { block_number: 420, removed: false, transaction_hash, session_index, authority_index, network_id, receiver: first_receiver, amount: big_amount, }; let authority = UintAuthorityId::from((authority_index + 1) as u64); let signature = authority.sign(&clap.encode()).unwrap(); assert_ok!(SlowClap::slow_clap(RuntimeOrigin::none(), clap, signature)); } assert_claps_info_correct(&storage_key_first, &session_index, 3); assert_eq!( pallet::ApplausesForTransaction::::get(&storage_key_first), true ); assert_eq!(Balances::balance(&first_receiver), big_amount); assert_eq!(Balances::balance(&second_receiver), 0); for authority_index in 0..=2 { let clap = Clap { block_number: 420, removed: false, transaction_hash: H256::repeat_byte(3u8), session_index, authority_index, network_id: network_id_other, receiver: 420, amount: big_amount, }; let authority = UintAuthorityId::from((authority_index + 1) as u64); let signature = authority.sign(&clap.encode()).unwrap(); assert_ok!(SlowClap::slow_clap(RuntimeOrigin::none(), clap, signature)); } assert_eq!(Balances::balance(&first_receiver), big_amount); assert_eq!(Balances::balance(&second_receiver), 0); assert_eq!(Networks::gatekeeper_amount(network_id), big_amount); assert_eq!(Networks::gatekeeper_amount(network_id_other), 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, _, _) = generate_unique_hash(None, None, None, None); let (_, _, amount) = get_mocked_metadata(); new_test_ext().execute_with(|| { let _ = prepare_evm_network(Some(network_id), 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_eq!(Networks::accumulated_commission(), amount); assert_eq!(Networks::is_nullification_period(), false); assert_eq!( BridgedInflationCurve::::era_payout( total_staked, total_issuance, 0 ), (420000000000000, 0) ); // 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, 2, false)); assert_eq!(Networks::accumulated_commission(), 0); }); } #[test] fn should_avoid_applause_during_nullification_period() { let total_staked = 69_000_000; let total_issuance = 100_000_000; let (network_id, _, _) = generate_unique_hash(None, None, None, None); let (_, receiver, amount) = get_mocked_metadata(); new_test_ext().execute_with(|| { let _ = prepare_evm_network(Some(network_id), Some(0)); let session_index = advance_session_and_get_index(); assert_eq!(Networks::is_nullification_period(), false); assert_eq!( BridgedInflationCurve::::era_payout( total_staked, total_issuance, 0 ), (0, 0) ); 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::balance(&receiver), 0); 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_eq!(Balances::balance(&receiver), amount); }); } #[test] fn should_self_applause_if_enough_received_claps() { let (network_id, transaction_hash, unique_transaction_hash) = generate_unique_hash(None, None, None, None); let (_, receiver, amount) = get_mocked_metadata(); new_test_ext().execute_with(|| { let _ = prepare_evm_network(Some(network_id), Some(0)); let session_index = advance_session_and_get_index(); let storage_key = (session_index, transaction_hash, unique_transaction_hash); assert_err!( SlowClap::self_applause( RuntimeOrigin::signed(receiver), network_id, session_index, transaction_hash, receiver, amount, ), Error::::NotEnoughClaps ); assert_eq!( pallet::ApplausesForTransaction::::get(&storage_key), false ); assert_eq!(Balances::balance(&receiver), 0); assert_eq!( BridgedInflationCurve::::era_payout(0, 0, 0), (0, 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!( pallet::ApplausesForTransaction::::get(&storage_key), false ); assert_eq!(Balances::balance(&receiver), 0); assert_ok!(SlowClap::self_applause( RuntimeOrigin::signed(receiver), network_id, session_index, transaction_hash, receiver, amount, )); assert_eq!( pallet::ApplausesForTransaction::::get(&storage_key), false ); assert_eq!(Balances::balance(&receiver), 0); Networks::on_finalize(System::block_number()); assert_ok!(SlowClap::self_applause( RuntimeOrigin::signed(receiver), network_id, session_index, transaction_hash, receiver, amount, )); assert_eq!( pallet::ApplausesForTransaction::::get(&storage_key), true ); assert_eq!(Balances::balance(&receiver), amount); }); } #[test] fn should_emit_event_on_each_clap_and_on_applause() { let (network_id, transaction_hash, unique_transaction_hash) = generate_unique_hash(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(Some(network_id), Some(100_000_000)); let session_index = advance_session_and_get_index(); let storage_key = (session_index, transaction_hash, unique_transaction_hash); assert_eq!( pallet::ApplausesForTransaction::::get(&storage_key), false ); assert_claps_info_correct(&storage_key, &session_index, 0); assert_ok!(do_clap_from(session_index, network_id, 0, false)); System::assert_last_event(RuntimeEvent::SlowClap(crate::Event::Clapped { receiver: receiver.clone(), authority_id: 0, network_id, transaction_hash, amount, })); assert_eq!( pallet::ApplausesForTransaction::::get(&storage_key), false ); assert_claps_info_correct(&storage_key, &session_index, 1); assert_ok!(do_clap_from(session_index, network_id, 1, false)); let binding = System::events(); let last_two_events = binding.iter().rev().take(5).collect::>(); assert_eq!( last_two_events[0].event, RuntimeEvent::SlowClap(crate::Event::Applaused { network_id, receiver: receiver.clone(), received_amount: amount_after_commission, }) ); assert_eq!( last_two_events[4].event, RuntimeEvent::SlowClap(crate::Event::Clapped { receiver: receiver.clone(), authority_id: 1, network_id, transaction_hash, amount, }) ); assert_eq!( pallet::ApplausesForTransaction::::get(&storage_key), true ); assert_claps_info_correct(&storage_key, &session_index, 2); assert_ok!(do_clap_from(session_index, network_id, 2, false)); System::assert_last_event(RuntimeEvent::SlowClap(crate::Event::Clapped { receiver: receiver.clone(), authority_id: 2, network_id, transaction_hash, amount, })); }); } #[test] fn should_not_fail_on_sub_existential_balance() { let (network_id, transaction_hash, unique_transaction_hash) = generate_unique_hash(None, None, None, None); let (_, receiver, amount) = get_mocked_metadata(); new_test_ext().execute_with(|| { let _ = prepare_evm_network(Some(network_id), Some(1_000_000_000)); // 100% let session_index = advance_session_and_get_index(); let received_claps_key = (session_index, transaction_hash, unique_transaction_hash); 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::balance(&receiver), 0); assert_eq!( SlowClap::applauses_for_transaction(&received_claps_key), false ); 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::balance(&receiver), 0); assert_eq!( SlowClap::applauses_for_transaction(&received_claps_key), true ); }); } 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, ) -> (u32, H256, H256) { let network_id = maybe_network_id.unwrap_or(1); let (transaction_hash, receiver, amount) = 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 unique_transaction_hash = SlowClap::generate_unique_hash(&receiver, &amount, &network_id); (network_id, transaction_hash, unique_transaction_hash) } fn assert_claps_info_correct(storage_key: &(u32, H256, H256), session_index: &u32, index: usize) { assert_eq!( pallet::ReceivedClaps::::get(storage_key).len(), index ); assert_eq!( pallet::ClapsInSession::::get(session_index).len(), index ); } fn assert_transaction_has_bad_signature(session_index: u32, network_id: u32, authority: u64) { assert_err!( do_clap_from_first_authority(session_index, network_id, authority), DispatchError::Other("Transaction has a bad signature") ); } fn assert_invalid_signing_address(session_index: u32, network_id: u32, authority_index: u32) { assert_err!( do_clap_from(session_index, network_id, authority_index, false), DispatchError::Other("Invalid signing address") ); } 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) { let transaction_hash = H256::repeat_byte(69u8); let receiver: u64 = 1337; let amount: u64 = 420_000_000_000_000; (transaction_hash, receiver, amount) } 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() }); }