use super::*;
use frame_support::{assert_err, assert_ok};
use mock::{
    ExtBuilder, GhostNetworks, MaxNetworks, RandomAccount, RegistererAccount, RemoverAccount,
    RewardCurve, RuntimeEvent, RuntimeOrigin, System, Test, UpdaterAccount,
};
use pallet_staking::EraPayout;
use sp_runtime::DispatchError;

fn prepare_network_data() -> (u32, NetworkData) {
    (
        1u32,
        NetworkData {
            chain_name: "Ethereum".into(),
            default_endpoints: vec![
                "https:://some-endpoint.my-server.com/v1/my-super-secret-key".into(),
                "https:://another-endpoint.my-server.com/v1/my-super-secret-key".into(),
            ],
            finality_delay: 69,
            rate_limit_delay: 69,
            block_distance: 69,
            avg_block_speed: 12_000,
            network_type: NetworkType::Evm,
            gatekeeper: b"0x1234567891234567891234567891234567891234".to_vec(),
            topic_name: b"0x12345678912345678912345678912345678912345678912345678912345678"
                .to_vec(),
            incoming_fee: 0,
            outgoing_fee: 0,
        },
    )
}

fn register_and_check_network(chain_id: u32, network: NetworkData) {
    assert_ok!(GhostNetworks::register_network(
        RuntimeOrigin::signed(RegistererAccount::get()),
        chain_id,
        network.clone()
    ));
    assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    assert_eq!(NetworkIndexes::<Test>::get(), vec![chain_id]);
}

#[test]
fn could_add_network_from_authority() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data();
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
        assert_ok!(GhostNetworks::register_network(
            RuntimeOrigin::signed(RegistererAccount::get()),
            chain_id,
            network.clone(),
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkRegistered {
                chain_id,
                network: network.clone(),
            },
        ));
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
        assert_eq!(NetworkIndexes::<Test>::get(), vec![chain_id]);
    });
}

#[test]
fn could_not_add_network_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data();
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
        assert_err!(
            GhostNetworks::register_network(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                network.clone(),
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::register_network(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                network.clone(),
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::register_network(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                network,
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
    });
}

#[test]
fn could_update_network_name_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let new_name = b"Polygon".to_vec();
        let (chain_id, network) = prepare_network_data();
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::update_network_name(
            RuntimeOrigin::signed(UpdaterAccount::get()),
            chain_id,
            new_name.clone()
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkNameUpdated {
                chain_id,
                chain_name: new_name.clone(),
            },
        ));
        let mut final_network = network.clone();
        final_network.chain_name = new_name;
        assert_eq!(Networks::<Test>::get(chain_id), Some(final_network.clone()));
        assert_ne!(network, final_network);
    });
}

#[test]
fn could_add_network_endpoint_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let raw_endpoint: Vec<u8> =
            "https:://new-endpoint.my-server.com/v1/my-super-secret-key".into();
        let (chain_id, network) = prepare_network_data();
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::update_network_endpoint(
            RuntimeOrigin::signed(UpdaterAccount::get()),
            chain_id,
            None,
            Some(raw_endpoint.clone()),
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkEndpointAdded {
                chain_id,
                endpoint: raw_endpoint.clone(),
            },
        ));
        let current_network = Networks::<Test>::get(chain_id).unwrap();
        assert_eq!(
            current_network.default_endpoints.len(),
            network.default_endpoints.len() + 1
        );
        assert_eq!(
            current_network
                .default_endpoints
                .last()
                .cloned()
                .unwrap_or_default(),
            raw_endpoint
        );
        assert_ne!(&network, &current_network);
    });
}

#[test]
fn could_remove_network_endpoint_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let index_to_remove = 0u32;
        let (chain_id, network) = prepare_network_data();
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::update_network_endpoint(
            RuntimeOrigin::signed(UpdaterAccount::get()),
            chain_id,
            Some(index_to_remove),
            None,
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkEndpointRemoved {
                chain_id,
                index: index_to_remove,
            },
        ));
        let current_network = Networks::<Test>::get(chain_id).unwrap();
        assert_eq!(
            current_network.default_endpoints.len(),
            network.default_endpoints.len() - 1
        );
        assert_ne!(
            current_network
                .default_endpoints
                .get(index_to_remove as usize),
            network.default_endpoints.get(index_to_remove as usize)
        );
        assert_ne!(&network, &current_network);
    });
}

#[test]
fn could_update_network_endpoint_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let index_to_update = 0u32;
        let raw_endpoint: Vec<u8> =
            "https:://new-endpoint.my-server.com/v1/my-super-secret-key".into();
        let (chain_id, network) = prepare_network_data();
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::update_network_endpoint(
            RuntimeOrigin::signed(UpdaterAccount::get()),
            chain_id,
            Some(index_to_update),
            Some(raw_endpoint.clone()),
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkEndpointUpdated {
                chain_id,
                index: index_to_update,
                endpoint: raw_endpoint.clone(),
            },
        ));
        let previous_endpoints_len = network.default_endpoints.len();
        let mut final_network = network.clone();
        if let Some(endpoint_by_index) = final_network
            .default_endpoints
            .get_mut(index_to_update as usize)
        {
            *endpoint_by_index = raw_endpoint.clone();
        }
        let current_network = Networks::<Test>::get(chain_id).unwrap();
        assert_eq!(
            current_network.default_endpoints.len(),
            previous_endpoints_len
        );
        assert_eq!(
            current_network
                .default_endpoints
                .get(index_to_update as usize)
                .cloned()
                .unwrap_or_default(),
            raw_endpoint
        );
        assert_ne!(&network, &final_network);
    });
}

#[test]
fn could_update_network_finality_delay_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let new_finality_delay = 1337;
        let (chain_id, network) = prepare_network_data();
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::update_network_finality_delay(
            RuntimeOrigin::signed(UpdaterAccount::get()),
            chain_id,
            new_finality_delay
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkFinalityDelayUpdated {
                chain_id,
                finality_delay: new_finality_delay,
            },
        ));
        let mut final_network = network.clone();
        final_network.finality_delay = new_finality_delay;
        assert_eq!(Networks::<Test>::get(chain_id), Some(final_network.clone()));
        assert_ne!(network, final_network);
    });
}

#[test]
fn could_update_network_rate_limit_delay_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let new_rate_limit_delay = 1337;
        let (chain_id, network) = prepare_network_data();
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::update_network_rate_limit_delay(
            RuntimeOrigin::signed(UpdaterAccount::get()),
            chain_id,
            new_rate_limit_delay
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkRateLimitDelayUpdated {
                chain_id,
                rate_limit_delay: new_rate_limit_delay,
            },
        ));
        let mut final_network = network.clone();
        final_network.rate_limit_delay = new_rate_limit_delay;
        assert_eq!(Networks::<Test>::get(chain_id), Some(final_network.clone()));
        assert_ne!(network, final_network);
    });
}

#[test]
fn could_update_network_block_distance_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let new_block_distance = 1337;
        let (chain_id, network) = prepare_network_data();
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::update_network_block_distance(
            RuntimeOrigin::signed(UpdaterAccount::get()),
            chain_id,
            new_block_distance
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkBlockDistanceUpdated {
                chain_id,
                block_distance: new_block_distance,
            },
        ));
        let mut final_network = network.clone();
        final_network.block_distance = new_block_distance;
        assert_eq!(Networks::<Test>::get(chain_id), Some(final_network.clone()));
        assert_ne!(network, final_network);
    });
}

#[test]
fn could_update_network_type_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let new_type = NetworkType::Utxo;
        let (chain_id, network) = prepare_network_data();
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::update_network_type(
            RuntimeOrigin::signed(UpdaterAccount::get()),
            chain_id,
            new_type.clone()
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkTypeUpdated {
                chain_id,
                network_type: new_type.clone(),
            },
        ));
        let mut final_network = network.clone();
        final_network.network_type = new_type;
        assert_eq!(Networks::<Test>::get(chain_id), Some(final_network.clone()));
        assert_ne!(network, final_network);
    });
}

#[test]
fn could_update_network_gatekeeper_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let new_gatekeeper = b"0x9876543219876543219876543219876543219876".to_vec();
        let (chain_id, network) = prepare_network_data();
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::update_network_gatekeeper(
            RuntimeOrigin::signed(UpdaterAccount::get()),
            chain_id,
            new_gatekeeper.clone()
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkGatekeeperUpdated {
                chain_id,
                gatekeeper: new_gatekeeper.clone(),
            },
        ));
        let mut final_network = network.clone();
        final_network.gatekeeper = new_gatekeeper;
        assert_eq!(Networks::<Test>::get(chain_id), Some(final_network.clone()));
        assert_ne!(network, final_network);
    });
}

#[test]
fn could_update_network_topic_name_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let new_topic_name =
            b"0x9876543219876543219876543219876543219876543219876543219876543219".to_vec();
        let (chain_id, network) = prepare_network_data();
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::update_network_topic_name(
            RuntimeOrigin::signed(UpdaterAccount::get()),
            chain_id,
            new_topic_name.clone()
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkTopicNameUpdated {
                chain_id,
                topic_name: new_topic_name.clone(),
            },
        ));
        let mut final_network = network.clone();
        final_network.topic_name = new_topic_name;
        assert_eq!(Networks::<Test>::get(chain_id), Some(final_network.clone()));
        assert_ne!(network, final_network);
    });
}

#[test]
fn could_update_incoming_network_fee_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let new_incoming_fee = 69;
        let (chain_id, network) = prepare_network_data();
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::update_incoming_network_fee(
            RuntimeOrigin::signed(UpdaterAccount::get()),
            chain_id,
            new_incoming_fee
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkIncomingFeeUpdated {
                chain_id,
                incoming_fee: new_incoming_fee,
            },
        ));
        let mut final_network = network.clone();
        final_network.incoming_fee = new_incoming_fee;
        assert_eq!(Networks::<Test>::get(chain_id), Some(final_network.clone()));
        assert_ne!(network, final_network);
    });
}

#[test]
fn could_update_outgoing_network_fee_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let new_outgoing_fee = 69;
        let (chain_id, network) = prepare_network_data();
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::update_outgoing_network_fee(
            RuntimeOrigin::signed(UpdaterAccount::get()),
            chain_id,
            new_outgoing_fee
        ));
        System::assert_last_event(RuntimeEvent::GhostNetworks(
            crate::Event::NetworkOutgoingFeeUpdated {
                chain_id,
                outgoing_fee: new_outgoing_fee,
            },
        ));
        let mut final_network = network.clone();
        final_network.outgoing_fee = new_outgoing_fee;
        assert_eq!(Networks::<Test>::get(chain_id), Some(final_network.clone()));
        assert_ne!(network, final_network);
    });
}

#[test]
fn could_not_update_network_name_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data();
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::update_network_name(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id,
                "Binance".into()
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_name(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                "Binance".into()
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_name(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                "Binance".into()
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    });
}

#[test]
fn could_not_update_network_endpoint_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let index_to_update = 0u32;
        let raw_endpoint: Vec<u8> =
            "https:://new-endpoint.my-server.com/v1/my-super-secret-key".into();
        let (chain_id, network) = prepare_network_data();
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::update_network_endpoint(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id,
                Some(index_to_update),
                Some(raw_endpoint.clone()),
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_endpoint(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                Some(index_to_update),
                Some(raw_endpoint.clone()),
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_endpoint(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                Some(index_to_update),
                Some(raw_endpoint.clone()),
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    });
}

#[test]
fn could_not_add_network_endpoint_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let raw_endpoint: Vec<u8> =
            "https:://new-endpoint.my-server.com/v1/my-super-secret-key".into();
        let (chain_id, network) = prepare_network_data();
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::update_network_endpoint(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id,
                None,
                Some(raw_endpoint.clone()),
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_endpoint(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                None,
                Some(raw_endpoint.clone()),
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_endpoint(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                None,
                Some(raw_endpoint.clone()),
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    });
}

#[test]
fn could_not_remove_network_endpoint_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let index_to_remove = 0u32;
        let (chain_id, network) = prepare_network_data();
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::update_network_endpoint(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id,
                Some(index_to_remove),
                None,
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_endpoint(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                Some(index_to_remove),
                None,
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_endpoint(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                Some(index_to_remove),
                None,
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    });
}

#[test]
fn could_not_update_network_finality_delay_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data();
        let finality_delay = 1337;
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::update_network_finality_delay(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id,
                finality_delay
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_finality_delay(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                finality_delay
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_finality_delay(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                finality_delay
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    });
}

#[test]
fn could_not_update_network_rate_limit_delay_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data();
        let rate_limit_delay = 1337;
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::update_network_rate_limit_delay(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id,
                rate_limit_delay
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_rate_limit_delay(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                rate_limit_delay
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_rate_limit_delay(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                rate_limit_delay
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    });
}

#[test]
fn could_not_update_network_release_delay_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data();
        let block_distance = 1337;
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::update_network_block_distance(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id,
                block_distance
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_block_distance(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                block_distance
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_block_distance(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                block_distance
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    });
}

#[test]
fn could_not_update_network_type_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data();
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::update_network_type(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id,
                NetworkType::Utxo
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_type(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                NetworkType::Utxo
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_type(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                NetworkType::Utxo
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    });
}

#[test]
fn could_not_update_network_gatekeeper_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data();
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::update_network_gatekeeper(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id,
                b"0x9876543219876543219876543219876543219876".to_vec()
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_gatekeeper(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                b"0x9876543219876543219876543219876543219876".to_vec()
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_gatekeeper(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                b"0x9876543219876543219876543219876543219876".to_vec()
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    });
}

#[test]
fn could_not_update_network_topic_name_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data();
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::update_network_topic_name(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id,
                b"0x9876543219876543219876543219876543219876543219876543219876543219".to_vec()
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_topic_name(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                b"0x9876543219876543219876543219876543219876543219876543219876543219".to_vec()
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_network_topic_name(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                b"0x9876543219876543219876543219876543219876543219876543219876543219".to_vec()
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    });
}

#[test]
fn could_not_update_network_incoming_fee_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data();
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::update_incoming_network_fee(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id,
                69
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_incoming_network_fee(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                69
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_incoming_network_fee(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                69
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    });
}

#[test]
fn could_not_update_network_outgoing_fee_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data();
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::update_outgoing_network_fee(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id,
                69
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_outgoing_network_fee(
                RuntimeOrigin::signed(RemoverAccount::get()),
                chain_id,
                69
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::update_outgoing_network_fee(
                RuntimeOrigin::signed(RandomAccount::get()),
                chain_id,
                69
            ),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
    });
}

#[test]
fn could_not_update_name_for_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::update_network_name(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                "Binance".into()
            ),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
    });
}

#[test]
fn could_not_update_endpoint_for_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::update_network_endpoint(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                Some(0u32),
                Some("https:://new-endpoint.my-server.com/v1/my-super-secret-key".into()),
            ),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
    });
}

#[test]
fn could_not_add_endpoint_for_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::update_network_endpoint(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                None,
                Some("https:://new-endpoint.my-server.com/v1/my-super-secret-key".into()),
            ),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
    });
}

#[test]
fn could_not_remove_endpoint_for_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::update_network_endpoint(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                Some(0u32),
                None,
            ),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
    });
}

#[test]
fn could_not_update_finality_delay_for_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::update_network_finality_delay(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                1337
            ),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
    });
}

#[test]
fn could_not_update_rate_limit_delay_for_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::update_network_rate_limit_delay(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                1337
            ),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
    });
}

#[test]
fn could_not_update_release_delay_for_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::update_network_block_distance(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                1337
            ),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
    });
}

#[test]
fn could_not_update_type_for_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::update_network_type(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                NetworkType::Utxo
            ),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
    });
}

#[test]
fn could_not_update_gatekeeper_for_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::update_network_gatekeeper(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                b"0x9876543219876543219876543219876543219876".to_vec()
            ),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
    });
}

#[test]
fn could_not_update_topic_name_for_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::update_network_topic_name(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                b"0x9876543219876543219876543219876543219876543219876543219876543219".to_vec()
            ),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
    });
}

#[test]
fn could_not_update_incoming_fee_for_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::update_incoming_network_fee(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                1337
            ),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
    });
}

#[test]
fn could_not_update_outgoing_fee_for_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::update_outgoing_network_fee(
                RuntimeOrigin::signed(UpdaterAccount::get()),
                chain_id,
                1337
            ),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
    });
}

#[test]
fn could_remove_network_from_authority_account() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data();
        register_and_check_network(chain_id, network.clone());
        assert_ok!(GhostNetworks::remove_network(
            RuntimeOrigin::signed(RemoverAccount::get()),
            chain_id,
        ));
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
    });
}

#[test]
fn could_not_remove_network_from_random_account() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data();
        register_and_check_network(chain_id, network.clone());
        assert_err!(
            GhostNetworks::remove_network(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id
            ),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::remove_network(RuntimeOrigin::signed(UpdaterAccount::get()), chain_id),
            DispatchError::BadOrigin
        );
        assert_err!(
            GhostNetworks::remove_network(RuntimeOrigin::signed(RandomAccount::get()), chain_id),
            DispatchError::BadOrigin
        );
        assert_eq!(Networks::<Test>::get(chain_id), Some(network));
        assert_eq!(NetworkIndexes::<Test>::get(), vec![chain_id]);
    });
}

#[test]
fn could_not_remove_non_existent_network() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_err!(
            GhostNetworks::remove_network(RuntimeOrigin::signed(RemoverAccount::get()), chain_id),
            crate::Error::<Test>::NetworkDoesNotExist
        );
        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
    });
}

#[test]
fn bridge_storage_is_empty_by_default() {
    ExtBuilder::build().execute_with(|| {
        assert_eq!(AccumulatedCommission::<Test>::get(), 0);
        assert_eq!(BridgedImbalance::<Test>::get(), BridgeAdjustment::default());
    });
}

#[test]
fn gatekeeper_amount_changes_correctly() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        let amount_in: u128 = 420;
        let amount_out: u128 = 69;
        let result = amount_in - 3 * amount_out;

        assert_eq!(GatekeeperAmount::<Test>::get(&chain_id), 0);

        assert_ok!(GhostNetworks::increase_gatekeeper_amount(
            &chain_id, &amount_in
        ));
        assert_ok!(GhostNetworks::decrease_gatekeeper_amount(
            &chain_id,
            &amount_out
        ));
        assert_ok!(GhostNetworks::decrease_gatekeeper_amount(
            &chain_id,
            &amount_out
        ));
        assert_ok!(GhostNetworks::decrease_gatekeeper_amount(
            &chain_id,
            &amount_out
        ));

        assert_eq!(GatekeeperAmount::<Test>::get(&chain_id), result);
    });
}

#[test]
fn bridged_imbalance_accumulated_correctly() {
    ExtBuilder::build().execute_with(|| {
        let amount_in: u128 = 420;
        let amount_out: u128 = 69;

        let imbalance_before = BridgedImbalance::<Test>::get();
        assert_eq!(imbalance_before.bridged_in, 0);
        assert_eq!(imbalance_before.bridged_out, 0);

        assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount_in));
        assert_ok!(GhostNetworks::accumulate_outgoing_imbalance(&amount_out));

        let imbalance_after = BridgedImbalance::<Test>::get();
        assert_eq!(imbalance_after.bridged_in, amount_in);
        assert_eq!(imbalance_after.bridged_out, amount_out);
    });
}

#[test]
fn commission_accumulation_is_correct() {
    ExtBuilder::build().execute_with(|| {
        let commission_first: u128 = 420;
        let commission_second: u128 = 69;
        let result = commission_first + commission_second;

        assert_eq!(AccumulatedCommission::<Test>::get(), 0);
        assert_ok!(GhostNetworks::accumulate_commission(&commission_first));
        assert_ok!(GhostNetworks::accumulate_commission(&commission_second));
        assert_eq!(AccumulatedCommission::<Test>::get(), result);
    });
}

#[test]
fn commission_overflow_and_underflow_emits_error() {
    ExtBuilder::build().execute_with(|| {
        let commission: u128 = u128::MAX - 69;
        assert_eq!(AccumulatedCommission::<Test>::get(), 0);
        assert_ok!(GhostNetworks::accumulate_commission(&commission));
        assert_err!(GhostNetworks::accumulate_commission(&commission), ());
        assert_eq!(AccumulatedCommission::<Test>::get(), commission);
    });
}

#[test]
fn gatekeeper_amount_overflow_and_underflow_emits_error() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        let commission: u128 = u128::MAX - 69;
        assert_eq!(GatekeeperAmount::<Test>::get(&chain_id), 0);
        assert_ok!(
            GhostNetworks::increase_gatekeeper_amount(&chain_id, &commission,),
            commission
        );
        assert_err!(
            GhostNetworks::increase_gatekeeper_amount(&chain_id, &commission,),
            ()
        );
        assert_eq!(GatekeeperAmount::<Test>::get(&chain_id), commission);
        assert_ok!(
            GhostNetworks::decrease_gatekeeper_amount(&chain_id, &commission,),
            0
        );
        assert_err!(
            GhostNetworks::decrease_gatekeeper_amount(&chain_id, &commission,),
            ()
        );
        assert_eq!(GatekeeperAmount::<Test>::get(&chain_id), 0);
    });
}

#[test]
fn bridged_imbalance_overflow_emits_error() {
    ExtBuilder::build().execute_with(|| {
        let chain_id: u32 = 1;
        let amount: u128 = u128::MAX - 69;
        assert_eq!(GatekeeperAmount::<Test>::get(&chain_id), 0);
        assert_ok!(
            GhostNetworks::accumulate_outgoing_imbalance(&amount),
            amount
        );
        assert_ok!(
            GhostNetworks::accumulate_incoming_imbalance(&amount),
            amount
        );

        assert_err!(GhostNetworks::accumulate_outgoing_imbalance(&amount), ());
        assert_err!(GhostNetworks::accumulate_incoming_imbalance(&amount), ());
        assert_err!(GhostNetworks::accumulate_outgoing_imbalance(&u128::MAX), ());
        assert_err!(GhostNetworks::accumulate_incoming_imbalance(&u128::MAX), ());

        let bridged_imbalance = BridgedImbalance::<Test>::get();
        assert_eq!(bridged_imbalance.bridged_out, amount);
        assert_eq!(bridged_imbalance.bridged_in, amount);
    });
}

#[test]
fn bridged_amount_overflow_and_underflow_emits_error() {
    ExtBuilder::build().execute_with(|| {
        let chain_id_first: u32 = 1;
        let chain_id_second: u32 = 2;
        let commission: u128 = u128::MAX - 69;

        assert_ok!(
            GhostNetworks::increase_gatekeeper_amount(&chain_id_first, &commission,),
            commission
        );
        assert_ok!(
            GhostNetworks::increase_gatekeeper_amount(&chain_id_second, &commission,),
            commission
        );

        assert_err!(
            GhostNetworks::increase_gatekeeper_amount(&chain_id_first, &u128::MAX,),
            ()
        );
        assert_err!(
            GhostNetworks::increase_gatekeeper_amount(&chain_id_first, &commission,),
            ()
        );

        assert_err!(
            GhostNetworks::decrease_gatekeeper_amount(&chain_id_second, &u128::MAX,),
            ()
        );
        assert_ok!(
            GhostNetworks::decrease_gatekeeper_amount(&chain_id_second, &commission,),
            0
        );

        assert_eq!(GatekeeperAmount::<Test>::get(&chain_id_first), commission);
        assert_eq!(GatekeeperAmount::<Test>::get(&chain_id_second), 0);
    });
}

#[test]
fn accumulated_commission_nullified_after_era_payout() {
    ExtBuilder::build().execute_with(|| {
        let commission_first: u128 = 420;
        let commission_second: u128 = 69;

        assert_eq!(AccumulatedCommission::<Test>::get(), 0);
        assert_ok!(
            GhostNetworks::accumulate_commission(&commission_first),
            commission_first
        );
        assert_ok!(
            GhostNetworks::accumulate_commission(&commission_second,),
            commission_first + commission_second
        );

        assert_eq!(
            AccumulatedCommission::<Test>::get(),
            commission_first + commission_second
        );
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(1, 1, 1),
            (0, commission_first + commission_second)
        );
        assert_eq!(AccumulatedCommission::<Test>::get(), 0);
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(1, 1, 1),
            (0, 0)
        );
    });
}

#[test]
fn bridged_inlation_reward_works() {
    ExtBuilder::build().execute_with(|| {
        let amount_full: u128 = 1337 * 1_000_000_000;
        let commission: u128 = amount_full / 100; // 1% commission
        let amount: u128 = amount_full - commission;

        let total_staked_ideal: u128 = 69;
        let total_staked_not_ideal: u128 = 68;
        let total_issuance: u128 = 100;

        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                total_staked_ideal * 1_000,
                total_issuance * 1_000,
                0
            ),
            (0, 0)
        );
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                total_staked_ideal * 1_000_000_000_000,
                total_issuance * 1_000_000_000_000,
                0
            ),
            (0, 0)
        );
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                total_staked_ideal * 1_000_000_000_000_000_000_000_000,
                total_issuance * 1_000_000_000_000_000_000_000_000,
                0
            ),
            (0, 0)
        );

        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                total_staked_not_ideal * 1_000,
                total_issuance * 1_000,
                0
            ),
            (0, 0)
        );
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                total_staked_not_ideal * 1_000_000_000_000,
                total_issuance * 1_000_000_000_000,
                0
            ),
            (0, 0)
        );
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                total_staked_not_ideal * 1_000_000_000_000_000_000_000_000,
                total_issuance * 1_000_000_000_000_000_000_000_000,
                0
            ),
            (0, 0)
        );

        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(1, total_issuance * 1_000, 0),
            (0, 0)
        );
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                1,
                total_issuance * 1_000_000_000_000,
                0
            ),
            (0, 0)
        );
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                1,
                total_issuance * 1_000_000_000_000_000_000_000_000,
                0
            ),
            (0, 0)
        );

        assert_ok!(GhostNetworks::accumulate_commission(&commission));
        assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount));
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                total_staked_ideal * 1_000,
                total_issuance * 1_000 + amount,
                0
            ),
            (commission, 0)
        );

        assert_ok!(GhostNetworks::accumulate_commission(&commission));
        assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount));
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                total_staked_ideal * 1_000_000_000_000,
                total_issuance * 1_000_000_000_000 + amount,
                0
            ),
            (commission, 0)
        );

        assert_ok!(GhostNetworks::accumulate_commission(&commission));
        assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount));
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                total_staked_ideal * 1_000_000_000_000_000_000_000_000,
                total_issuance * 1_000_000_000_000_000_000_000_000 + amount,
                0
            ),
            (commission, 0)
        );

        assert_ok!(GhostNetworks::accumulate_commission(&commission));
        assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount));
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                total_staked_not_ideal * 1_000,
                total_issuance * 1_000 + amount,
                0
            ),
            (13177472000, 192528000)
        );

        assert_ok!(GhostNetworks::accumulate_commission(&commission));
        assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount));
        assert_eq!(13177472000 + 192528000, commission);
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                total_staked_not_ideal * 1_000_000_000_000,
                total_issuance * 1_000_000_000_000 + amount,
                0
            ),
            (13177568884, 192431116)
        );

        assert_ok!(GhostNetworks::accumulate_commission(&commission));
        assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount));
        assert_eq!(13177568884 + 192431116, commission);
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                total_staked_not_ideal * 1_000_000_000_000_000_000_000_000,
                total_issuance * 1_000_000_000_000_000_000_000_000 + amount,
                0
            ),
            (13177568884, 192431116)
        );

        assert_ok!(GhostNetworks::accumulate_commission(&commission));
        assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount));
        assert_eq!(13177568884 + 192431116, commission);
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                1,
                total_issuance * 1_000 + amount,
                0
            ),
            (92386700, 13277613300)
        );

        assert_ok!(GhostNetworks::accumulate_commission(&commission));
        assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount));
        assert_eq!(92386700 + 13277613300, commission);
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                1,
                total_issuance * 1_000_000_000_000 + amount,
                0
            ),
            (92253000, 13277747000)
        );

        assert_ok!(GhostNetworks::accumulate_commission(&commission));
        assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount));
        assert_eq!(92253000 + 13277747000, commission);
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                1,
                total_issuance * 1_000_000_000_000_000_000_000_000 + amount,
                0
            ),
            (92253000, 13277747000)
        );

        assert_eq!(92253000 + 13277747000, commission);
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                total_staked_ideal * 1_000,
                total_issuance * 1_000 + amount,
                0
            ),
            (0, 0)
        );
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                total_staked_ideal * 1_000_000_000_000,
                total_issuance * 1_000_000_000_000 + amount,
                0
            ),
            (0, 0)
        );
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                total_staked_ideal * 1_000_000_000_000_000_000_000_000,
                total_issuance * 1_000_000_000_000_000_000_000_000 + amount,
                0
            ),
            (0, 0)
        );

        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                total_staked_not_ideal * 1_000,
                total_issuance * 1_000 + amount,
                0
            ),
            (0, 0)
        );
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                total_staked_not_ideal * 1_000_000_000_000,
                total_issuance * 1_000_000_000_000 + amount,
                0
            ),
            (0, 0)
        );
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                total_staked_not_ideal * 1_000_000_000_000_000_000_000_000,
                total_issuance * 1_000_000_000_000_000_000_000_000 + amount,
                0
            ),
            (0, 0)
        );

        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                1,
                total_issuance * 1_000 + amount,
                0
            ),
            (0, 0)
        );
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                1,
                total_issuance * 1_000_000_000_000 + amount,
                0
            ),
            (0, 0)
        );
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                1,
                total_issuance * 1_000_000_000_000_000_000_000_000 + amount,
                0
            ),
            (0, 0)
        );
    });
}

#[test]
fn bridged_inflation_era_payout_clears_storage() {
    ExtBuilder::build().execute_with(|| {
        let amount_full: u128 = 1337 * 1_000_000_000;
        let commission: u128 = amount_full / 100; // 1% commission
        let amount: u128 = amount_full - commission;

        let total_staked_ideal: u128 = 69;
        let total_issuance: u128 = 100;

        assert_ok!(GhostNetworks::accumulate_commission(&commission));
        assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount));

        let bridged_adjustment = BridgeAdjustment {
            bridged_in: amount,
            bridged_out: 0,
        };
        assert_eq!(BridgedImbalance::<Test>::get(), bridged_adjustment);
        assert_eq!(AccumulatedCommission::<Test>::get(), commission);
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                total_staked_ideal * 1_000,
                total_issuance * 1_000 + amount,
                0
            ),
            (commission, 0)
        );
        assert_eq!(BridgedImbalance::<Test>::get(), Default::default());
        assert_eq!(AccumulatedCommission::<Test>::get(), Default::default());
    });
}

#[test]
fn check_substrate_guarantees_not_to_overflow_u128() {
    ExtBuilder::build().execute_with(|| {
        let reward_curve = RewardCurve::get();
        let mut n: u128 = 69;
        let mut d: u128 = 100;

        loop {
            n = match n.checked_mul(1_000) {
                Some(value) => value,
                None => break,
            };
            d = match d.checked_mul(1_000) {
                Some(value) => value,
                None => break,
            };
            assert_eq!(
                reward_curve.calculate_for_fraction_times_denominator(n, d),
                d
            );
        }
    });
}

#[test]
fn check_substrate_guarantees_not_to_overflow_u64() {
    ExtBuilder::build().execute_with(|| {
        let reward_curve = RewardCurve::get();
        let mut n: u64 = 69;
        let mut d: u64 = 100;

        loop {
            n = match n.checked_mul(1_000) {
                Some(value) => value,
                None => break,
            };
            d = match d.checked_mul(1_000) {
                Some(value) => value,
                None => break,
            };
            assert_eq!(
                reward_curve.calculate_for_fraction_times_denominator(n, d),
                d
            );
        }
    });
}

#[test]
fn check_substrate_guarantees_not_to_overflow_u32() {
    ExtBuilder::build().execute_with(|| {
        let reward_curve = RewardCurve::get();
        let mut n: u32 = 69;
        let mut d: u32 = 100;

        loop {
            n = match n.checked_mul(1_000) {
                Some(value) => value,
                None => break,
            };
            d = match d.checked_mul(1_000) {
                Some(value) => value,
                None => break,
            };
            assert_eq!(
                reward_curve.calculate_for_fraction_times_denominator(n, d),
                d
            );
        }
    });
}

#[test]
fn check_bridged_inflation_curve_for_overflow() {
    ExtBuilder::build().execute_with(|| {
        let amount_full: u128 = 1337 * 1_000_000_000;
        let commission: u128 = amount_full / 100; // 1% commission
        let amount: u128 = amount_full - commission;

        let precomputed_payout: u128 = 13177568884;
        let precomputed_rest: u128 = 192431116;
        assert_eq!(precomputed_payout + precomputed_rest, commission);

        let mut total_staked_ideal: u128 = 69_000;
        let mut total_staked_not_ideal: u128 = 68_000;
        let mut total_issuance: u128 = 100_000;

        loop {
            assert_ok!(GhostNetworks::accumulate_commission(&commission));
            assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount));

            total_staked_ideal = match total_staked_ideal.checked_mul(1_000) {
                Some(value) => value,
                None => break,
            };
            total_staked_not_ideal = match total_staked_not_ideal.checked_mul(1_000) {
                Some(value) => value,
                None => break,
            };
            total_issuance = match total_issuance.checked_mul(1_000) {
                Some(value) => value,
                None => break,
            };
            assert_eq!(
                BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                    total_staked_ideal,
                    total_issuance + amount,
                    0
                ),
                (commission, 0)
            );
        }
    });
}

#[test]
fn check_bridged_inflation_curve_for_big_commissions() {
    ExtBuilder::build().execute_with(|| {
        let mut amount_full: u128 = 1337;

        let total_staked_ideal: u128 = 69_000_000;
        let total_staked_gt_ideal: u128 = 100_000_000;
        let total_staked_lt_ideal: u128 = 3_000_000;
        let total_issuance: u128 = 100_000_000;

        loop {
            amount_full = match amount_full.checked_mul(1_000) {
                Some(value) => value,
                None => break,
            };
            let commission: u128 = amount_full / 100; // 1% commission
            let amount: u128 = amount_full - commission;

            AccumulatedCommission::<Test>::set(commission);
            BridgedImbalance::<Test>::set(BridgeAdjustment {
                bridged_in: amount,
                bridged_out: 0,
            });

            assert_eq!(
                BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                    total_staked_ideal,
                    total_issuance + amount,
                    0
                ),
                (commission, 0)
            );

            let (payout, rest) = BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                total_staked_gt_ideal,
                total_issuance + amount,
                0,
            );
            assert!(payout < commission);
            assert!(rest < commission);

            let (payout, rest) = BridgedInflationCurve::<RewardCurve, Test>::era_payout(
                total_staked_lt_ideal,
                total_issuance + amount,
                0,
            );
            assert!(payout < commission);
            assert!(rest < commission);
        }
    });
}

#[test]
fn migration_from_v0_to_v1_works() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data();
        let other_chain_id = chain_id.saturating_add(1);

        let removed_pallet_name = <Pallet<Test> as PalletInfoAccess>::name();
        let removed_storage = "NullifyNeeded";

        let key = [
            sp_io::hashing::twox_128(removed_pallet_name.as_bytes()).to_vec(),
            sp_io::hashing::twox_128(removed_storage.as_bytes()).to_vec(),
        ]
        .concat();

        frame_support::storage::unhashed::put_raw(&key, &true.encode());

        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_ok!(GhostNetworks::register_network(
            RuntimeOrigin::signed(RegistererAccount::get()),
            chain_id,
            network.clone(),
        ));
        assert_ok!(GhostNetworks::register_network(
            RuntimeOrigin::signed(RegistererAccount::get()),
            other_chain_id,
            network.clone(),
        ));
        NetworkIndexes::<Test>::kill();

        assert!(frame_support::storage::unhashed::exists(&key));
        assert_eq!(Networks::<Test>::get(chain_id), Some(network.clone()));
        assert_eq!(Networks::<Test>::get(other_chain_id), Some(network));
        assert_eq!(NetworkIndexes::<Test>::get(), vec![]);

        type Migrate = crate::migrations::MigrateV0ToV1<Test>;
        <Migrate as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade();

        assert!(!frame_support::storage::unhashed::exists(&key));
        assert_eq!(
            NetworkIndexes::<Test>::get(),
            vec![chain_id, other_chain_id]
        );
    });
}

#[test]
fn error_on_max_networks_overflow() {
    ExtBuilder::build().execute_with(|| {
        let (chain_id, network) = prepare_network_data();

        let max_networks = MaxNetworks::get();
        for index in 0..max_networks {
            let other_chain_id = chain_id.saturating_add(index);
            assert_ok!(GhostNetworks::register_network(
                RuntimeOrigin::signed(RegistererAccount::get()),
                other_chain_id,
                network.clone(),
            ));
        }

        assert_err!(
            GhostNetworks::register_network(
                RuntimeOrigin::signed(RegistererAccount::get()),
                chain_id.saturating_add(max_networks),
                network.clone(),
            ),
            crate::Error::<Test>::TooManyNetworks,
        );
    });
}

#[test]
fn migration_from_v0_to_v1_does_not_run_twice() {
    ExtBuilder::build().execute_with(|| {
        StorageVersion::new(1).put::<GhostNetworks>();

        let (chain_id, network) = prepare_network_data();
        let other_chain_id = chain_id.saturating_add(1);

        assert_eq!(Networks::<Test>::get(chain_id), None);
        assert_ok!(GhostNetworks::register_network(
            RuntimeOrigin::signed(RegistererAccount::get()),
            chain_id,
            network.clone(),
        ));
        assert_ok!(GhostNetworks::register_network(
            RuntimeOrigin::signed(RegistererAccount::get()),
            other_chain_id,
            network.clone(),
        ));
        NetworkIndexes::<Test>::kill();

        assert_eq!(Networks::<Test>::get(chain_id), Some(network.clone()));
        assert_eq!(Networks::<Test>::get(other_chain_id), Some(network));
        assert_eq!(NetworkIndexes::<Test>::get(), vec![]);

        type Migrate = crate::migrations::MigrateV0ToV1<Test>;
        <Migrate as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade();
        assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
    });
}
