use snforge_std::{
    declare, ContractClassTrait, start_prank, stop_prank, CheatTarget, spy_events, SpyOn, EventSpy,
    EventFetcher, event_name_hash, Event, start_warp, stop_warp, ContractClass,
};
use pyth::pyth::{
    IPythDispatcher, IPythDispatcherTrait, DataSource, Event as PythEvent, PriceFeedUpdated,
    WormholeAddressSet, GovernanceDataSourceSet, ContractUpgraded, DataSourcesSet, FeeSet,
    PriceFeedPublishTime, GetPriceNoOlderThanError, Price, PriceFeed, GetPriceUnsafeError,
};
use pyth::byte_buffer::{ByteBuffer, ByteBufferImpl};
use pyth::util::{array_try_into, UnwrapWithFelt252};
use pyth::wormhole::{IWormholeDispatcher, IWormholeDispatcherTrait};
use core::starknet::ContractAddress;
use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDispatcherTrait};
use super::wormhole::corrupted_vm;
use super::data;

#[generate_trait]
impl DecodeEventHelpers of DecodeEventHelpersTrait {
    fn pop<T, +TryInto<felt252, T>>(ref self: Array<felt252>) -> T {
        self.pop_front().unwrap().try_into().unwrap()
    }

    fn pop_u256(ref self: Array<felt252>) -> u256 {
        u256 { low: self.pop(), high: self.pop(), }
    }

    fn pop_data_source(ref self: Array<felt252>) -> DataSource {
        DataSource { emitter_chain_id: self.pop(), emitter_address: self.pop_u256(), }
    }

    fn pop_data_sources(ref self: Array<felt252>) -> Array<DataSource> {
        let count: usize = self.pop();
        let mut i = 0;
        let mut output = array![];
        while i < count {
            output.append(self.pop_data_source());
            i += 1;
        };
        output
    }
}

fn decode_event(mut event: Event) -> PythEvent {
    let key0: felt252 = event.keys.pop();
    let output = if key0 == event_name_hash('PriceFeedUpdated') {
        let event = PriceFeedUpdated {
            price_id: event.keys.pop_u256(),
            price: event.data.pop(),
            conf: event.data.pop(),
            publish_time: event.data.pop(),
        };
        PythEvent::PriceFeedUpdated(event)
    } else if key0 == event_name_hash('FeeSet') {
        let event = FeeSet {
            old_fee: event.data.pop_u256(), new_fee: event.data.pop_u256(), token: event.data.pop(),
        };
        PythEvent::FeeSet(event)
    } else if key0 == event_name_hash('DataSourcesSet') {
        let event = DataSourcesSet {
            old_data_sources: event.data.pop_data_sources(),
            new_data_sources: event.data.pop_data_sources(),
        };
        PythEvent::DataSourcesSet(event)
    } else if key0 == event_name_hash('WormholeAddressSet') {
        let event = WormholeAddressSet {
            old_address: event.data.pop(), new_address: event.data.pop(),
        };
        PythEvent::WormholeAddressSet(event)
    } else if key0 == event_name_hash('GovernanceDataSourceSet') {
        let event = GovernanceDataSourceSet {
            old_data_source: event.data.pop_data_source(),
            new_data_source: event.data.pop_data_source(),
            last_executed_governance_sequence: event.data.pop(),
        };
        PythEvent::GovernanceDataSourceSet(event)
    } else if key0 == event_name_hash('ContractUpgraded') {
        let event = ContractUpgraded { new_class_hash: event.data.pop() };
        PythEvent::ContractUpgraded(event)
    } else {
        panic!("unrecognized event")
    };
    assert!(event.keys.len() == 0);
    assert!(event.data.len() == 0);
    output
}

#[test]
fn test_getters_work() {
    let ctx = deploy_mainnet();
    let pyth = ctx.pyth;

    assert!(pyth.wormhole_address() == ctx.wormhole.contract_address);
    assert!(
        pyth
            .fee_token_addresses() == array![
                ctx.fee_contract.contract_address, ctx.fee_contract2.contract_address,
            ]
    );
    assert!(pyth.get_single_update_fee(ctx.fee_contract.contract_address) == 1000);
    assert!(pyth.get_single_update_fee(ctx.fee_contract2.contract_address) == 2000);
    assert!(
        pyth
            .valid_data_sources() == array![
                DataSource {
                    emitter_chain_id: 26,
                    emitter_address: 0xe101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71,
                }
            ]
    );
    assert!(
        pyth
            .is_valid_data_source(
                DataSource {
                    emitter_chain_id: 26,
                    emitter_address: 0xe101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71,
                }
            )
    );
    assert!(
        !pyth.is_valid_data_source(DataSource { emitter_chain_id: 26, emitter_address: 0xbad, })
    );
    assert!(
        !pyth
            .is_valid_data_source(
                DataSource {
                    emitter_chain_id: 27,
                    emitter_address: 0xe101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71,
                }
            )
    );
    assert!(
        pyth.governance_data_source() == DataSource { emitter_chain_id: 1, emitter_address: 41, }
    );
    assert!(
        pyth
            .is_valid_governance_data_source(
                DataSource { emitter_chain_id: 1, emitter_address: 41, }
            )
    );
    assert!(
        !pyth
            .is_valid_governance_data_source(
                DataSource { emitter_chain_id: 1, emitter_address: 42, }
            )
    );
    assert!(pyth.last_executed_governance_sequence() == 0);
    assert!(pyth.governance_data_source_index() == 0);
    assert!(pyth.chain_id() == 60051);
}

#[test]
fn update_price_feeds_works() {
    let ctx = deploy_mainnet();
    let pyth = ctx.pyth;

    assert!(
        !pyth.price_feed_exists(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
    );
    assert!(
        pyth
            .latest_price_info_publish_time(
                0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43
            ) == 0
    );

    let fee = pyth.get_update_fee(data::good_update1(), ctx.fee_contract.contract_address);
    assert!(fee == 1000);
    ctx.approve_fee(fee);

    let mut spy = spy_events(SpyOn::One(pyth.contract_address));

    start_prank(CheatTarget::One(pyth.contract_address), ctx.user.try_into().unwrap());
    pyth.update_price_feeds(data::good_update1());
    stop_prank(CheatTarget::One(pyth.contract_address));

    spy.fetch_events();
    assert!(spy.events.len() == 1);
    let (from, event) = spy.events.pop_front().unwrap();
    assert!(from == pyth.contract_address);
    let event = decode_event(event);
    let expected = PriceFeedUpdated {
        price_id: 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43,
        publish_time: 1712589206,
        price: 7192002930010,
        conf: 3596501465,
    };
    assert!(event == PythEvent::PriceFeedUpdated(expected));

    let last_price = pyth
        .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
        .unwrap_with_felt252();
    assert!(last_price.price == 7192002930010);
    assert!(last_price.conf == 3596501465);
    assert!(last_price.expo == -8);
    assert!(last_price.publish_time == 1712589206);

    let last_ema_price = pyth
        .get_ema_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
        .unwrap_with_felt252();
    assert!(last_ema_price.price == 7181868900000);
    assert!(last_ema_price.conf == 4096812700);
    assert!(last_ema_price.expo == -8);
    assert!(last_ema_price.publish_time == 1712589206);

    let feed = pyth
        .query_price_feed_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
        .unwrap_with_felt252();
    assert!(feed.id == 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43);
    assert!(feed.price.price == 7192002930010);
    assert!(feed.price.conf == 3596501465);
    assert!(feed.price.expo == -8);
    assert!(feed.price.publish_time == 1712589206);
    assert!(feed.ema_price.price == 7181868900000);
    assert!(feed.ema_price.conf == 4096812700);
    assert!(feed.ema_price.expo == -8);
    assert!(feed.ema_price.publish_time == 1712589206);

    assert!(
        pyth.price_feed_exists(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
    );
    assert!(
        pyth
            .latest_price_info_publish_time(
                0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43
            ) == 1712589206
    );
}

#[test]
fn update_price_feeds_works2() {
    let ctx = deploy_mainnet();
    let pyth = ctx.pyth;
    let fee = pyth.get_update_fee(data::good_update2(), ctx.fee_contract.contract_address);
    assert!(fee == 1000);
    ctx.approve_fee(fee);
    start_prank(CheatTarget::One(pyth.contract_address), ctx.user.try_into().unwrap());
    pyth.update_price_feeds(data::good_update2());
    stop_prank(CheatTarget::One(pyth.contract_address));
}

#[test]
fn test_accepts_secondary_fee() {
    let ctx = deploy_mainnet();
    let pyth = ctx.pyth;

    assert!(
        !pyth.price_feed_exists(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
    );
    assert!(
        pyth
            .latest_price_info_publish_time(
                0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43
            ) == 0
    );

    let fee2 = pyth.get_update_fee(data::good_update1(), ctx.fee_contract2.contract_address);
    assert!(fee2 == 2000);
    ctx.approve_fee2(fee2);

    let balance1 = ctx.fee_contract.balanceOf(ctx.user);
    let balance2 = ctx.fee_contract2.balanceOf(ctx.user);
    start_prank(CheatTarget::One(pyth.contract_address), ctx.user.try_into().unwrap());
    pyth.update_price_feeds(data::good_update1());
    stop_prank(CheatTarget::One(pyth.contract_address));
    assert!(balance1 - ctx.fee_contract.balanceOf(ctx.user) == 0);
    assert!(balance2 - ctx.fee_contract2.balanceOf(ctx.user) == 2000);
}

#[test]
fn test_accepts_secondary_fee_if_first_allowance_insufficient() {
    let ctx = deploy_mainnet();
    let pyth = ctx.pyth;

    assert!(
        !pyth.price_feed_exists(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
    );
    assert!(
        pyth
            .latest_price_info_publish_time(
                0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43
            ) == 0
    );

    let fee = pyth.get_update_fee(data::good_update1(), ctx.fee_contract.contract_address);
    assert!(fee == 1000);
    let fee2 = pyth.get_update_fee(data::good_update1(), ctx.fee_contract2.contract_address);
    assert!(fee2 == 2000);
    ctx.approve_fee(500);
    ctx.approve_fee2(fee2);

    let balance1 = ctx.fee_contract.balanceOf(ctx.user);
    let balance2 = ctx.fee_contract2.balanceOf(ctx.user);
    start_prank(CheatTarget::One(pyth.contract_address), ctx.user.try_into().unwrap());
    pyth.update_price_feeds(data::good_update1());
    stop_prank(CheatTarget::One(pyth.contract_address));
    assert!(balance1 - ctx.fee_contract.balanceOf(ctx.user) == 0);
    assert!(balance2 - ctx.fee_contract2.balanceOf(ctx.user) == 2000);
}

#[test]
fn test_accepts_secondary_fee_if_first_balance_insufficient() {
    let ctx = deploy_mainnet();
    let pyth = ctx.pyth;
    let user2 = 'user2'.try_into().unwrap();

    start_prank(CheatTarget::One(ctx.fee_contract.contract_address), ctx.user);
    ctx.fee_contract.transfer(user2, 500);
    stop_prank(CheatTarget::One(ctx.fee_contract.contract_address));

    start_prank(CheatTarget::One(ctx.fee_contract2.contract_address), ctx.user);
    ctx.fee_contract2.transfer(user2, 2000);
    stop_prank(CheatTarget::One(ctx.fee_contract2.contract_address));

    assert!(
        !pyth.price_feed_exists(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
    );
    assert!(
        pyth
            .latest_price_info_publish_time(
                0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43
            ) == 0
    );

    let fee = pyth.get_update_fee(data::good_update1(), ctx.fee_contract.contract_address);
    assert!(fee == 1000);
    let fee2 = pyth.get_update_fee(data::good_update1(), ctx.fee_contract2.contract_address);
    assert!(fee2 == 2000);
    start_prank(CheatTarget::One(ctx.fee_contract.contract_address), user2);
    ctx.fee_contract.approve(ctx.pyth.contract_address, fee);
    stop_prank(CheatTarget::One(ctx.fee_contract.contract_address));

    start_prank(CheatTarget::One(ctx.fee_contract2.contract_address), user2);
    ctx.fee_contract2.approve(ctx.pyth.contract_address, fee2);
    stop_prank(CheatTarget::One(ctx.fee_contract2.contract_address));

    let balance1 = ctx.fee_contract.balanceOf(user2);
    let balance2 = ctx.fee_contract2.balanceOf(user2);
    start_prank(CheatTarget::One(pyth.contract_address), user2.try_into().unwrap());
    pyth.update_price_feeds(data::good_update1());
    stop_prank(CheatTarget::One(pyth.contract_address));
    assert!(balance1 - ctx.fee_contract.balanceOf(user2) == 0);
    assert!(balance2 - ctx.fee_contract2.balanceOf(user2) == 2000);
}

#[test]
#[should_panic(expected: ('insufficient fee allowance',))]
fn test_rejects_if_both_fees_insufficient() {
    let ctx = deploy_mainnet();
    let pyth = ctx.pyth;

    assert!(
        !pyth.price_feed_exists(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
    );
    assert!(
        pyth
            .latest_price_info_publish_time(
                0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43
            ) == 0
    );

    let fee = pyth.get_update_fee(data::good_update1(), ctx.fee_contract.contract_address);
    assert!(fee == 1000);
    let fee2 = pyth.get_update_fee(data::good_update1(), ctx.fee_contract2.contract_address);
    assert!(fee2 == 2000);
    ctx.approve_fee(500);
    ctx.approve_fee2(1500);

    start_prank(CheatTarget::One(pyth.contract_address), ctx.user.try_into().unwrap());
    pyth.update_price_feeds(data::good_update1());
    stop_prank(CheatTarget::One(pyth.contract_address));
}

#[test]
#[should_panic(expected: ('unsupported token',))]
fn test_get_update_fee_rejects_unsupported_token() {
    let ctx = deploy_mainnet();
    let pyth = ctx.pyth;

    pyth.get_update_fee(data::good_update1(), ctx.pyth.contract_address);
}

#[test]
#[should_panic(expected: ('unsupported token',))]
fn test_get_single_update_fee_rejects_unsupported_token() {
    let ctx = deploy_mainnet();
    let pyth = ctx.pyth;

    pyth.get_single_update_fee(ctx.pyth.contract_address);
}

#[test]
fn test_update_if_necessary_works() {
    let ctx = deploy_test();
    let pyth = ctx.pyth;
    ctx.approve_fee(10000);

    let mut spy = spy_events(SpyOn::One(pyth.contract_address));

    let price_id = 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43;
    assert!(pyth.get_price_unsafe(price_id).is_err());

    start_prank(CheatTarget::One(pyth.contract_address), ctx.user);
    let times = array![PriceFeedPublishTime { price_id, publish_time: 1715769470 }];
    pyth.update_price_feeds_if_necessary(data::test_price_update1(), times);

    let last_price = pyth.get_price_unsafe(price_id).unwrap_with_felt252();
    assert!(last_price.price == 6281060000000);
    assert!(last_price.publish_time == 1715769470);

    spy.fetch_events();
    assert!(spy.events.len() == 1);

    let times = array![PriceFeedPublishTime { price_id, publish_time: 1715769475 }];
    pyth.update_price_feeds_if_necessary(data::test_price_update2(), times);

    let last_price = pyth.get_price_unsafe(price_id).unwrap_with_felt252();
    assert!(last_price.price == 6281522520745);
    assert!(last_price.publish_time == 1715769475);

    spy.fetch_events();
    assert!(spy.events.len() == 2);

    stop_prank(CheatTarget::One(pyth.contract_address));
}

#[test]
fn test_parse_price_feed_updates_works() {
    let ctx = deploy_mainnet();
    let pyth = ctx.pyth;

    let fee = pyth.get_update_fee(data::good_update1(), ctx.fee_contract.contract_address);
    assert!(fee == 1000);
    ctx.approve_fee(1000);

    let mut spy = spy_events(SpyOn::One(pyth.contract_address));

    start_prank(CheatTarget::One(pyth.contract_address), ctx.user.try_into().unwrap());
    let output = pyth
        .parse_price_feed_updates(
            data::good_update1(),
            array![0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43],
            0,
            1712589208
        );
    stop_prank(CheatTarget::One(pyth.contract_address));
    assert!(output.len() == 1);
    let output = output.at(0).clone();
    let expected = PriceFeed {
        id: 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43,
        price: Price {
            price: 7192002930010, conf: 3596501465, expo: -8, publish_time: 1712589206,
        },
        ema_price: Price {
            price: 7181868900000, conf: 4096812700, expo: -8, publish_time: 1712589206,
        },
    };
    assert!(output == expected);

    spy.fetch_events();
    assert!(spy.events.len() == 1);
    let (from, event) = spy.events.pop_front().unwrap();
    assert!(from == pyth.contract_address);
    let event = decode_event(event);
    let expected = PriceFeedUpdated {
        price_id: 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43,
        publish_time: 1712589206,
        price: 7192002930010,
        conf: 3596501465,
    };
    assert!(event == PythEvent::PriceFeedUpdated(expected));

    let last_price = pyth
        .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
        .unwrap_with_felt252();
    assert!(last_price.price == 7192002930010);
    assert!(last_price.conf == 3596501465);
    assert!(last_price.expo == -8);
    assert!(last_price.publish_time == 1712589206);

    let last_ema_price = pyth
        .get_ema_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
        .unwrap_with_felt252();
    assert!(last_ema_price.price == 7181868900000);
    assert!(last_ema_price.conf == 4096812700);
    assert!(last_ema_price.expo == -8);
    assert!(last_ema_price.publish_time == 1712589206);
}

#[test]
#[should_panic(expected: ('price feed not found',))]
fn test_parse_price_feed_updates_rejects_bad_price_id() {
    let ctx = deploy_mainnet();
    let pyth = ctx.pyth;

    let fee = pyth.get_update_fee(data::good_update1(), ctx.fee_contract.contract_address);
    assert!(fee == 1000);
    ctx.approve_fee(fee);

    start_prank(CheatTarget::One(pyth.contract_address), ctx.user.try_into().unwrap());
    pyth.parse_price_feed_updates(data::good_update1(), array![0x14], 0, 1712589208);
}

#[test]
#[should_panic(expected: ('price feed not found',))]
fn test_parse_price_feed_updates_rejects_out_of_range() {
    let ctx = deploy_mainnet();
    let pyth = ctx.pyth;

    let fee = pyth.get_update_fee(data::good_update1(), ctx.fee_contract.contract_address);
    assert!(fee == 1000);
    ctx.approve_fee(fee);

    start_prank(CheatTarget::One(pyth.contract_address), ctx.user.try_into().unwrap());
    pyth
        .parse_price_feed_updates(
            data::good_update1(),
            array![0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43],
            0,
            1712589000
        );
}

#[test]
fn test_parse_price_feed_updates_unique_works() {
    let ctx = deploy_mainnet();
    let pyth = ctx.pyth;

    let fee1 = pyth.get_update_fee(data::test_price_update2(), ctx.fee_contract.contract_address);
    assert!(fee1 == 1000);
    ctx.approve_fee(10000);

    start_prank(CheatTarget::One(pyth.contract_address), ctx.user);
    let output = pyth
        .parse_unique_price_feed_updates(
            data::unique_update1(),
            array![0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43],
            1716904943,
            2,
        );
    stop_prank(CheatTarget::One(pyth.contract_address));
    assert!(output.len() == 1);
    let output = output.at(0).clone();
    let expected = PriceFeed {
        id: 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43,
        price: Price {
            price: 6751021151231, conf: 7471389383, expo: -8, publish_time: 1716904943,
        },
        ema_price: Price {
            price: 6815630100000, conf: 6236878200, expo: -8, publish_time: 1716904943,
        },
    };
    assert!(output == expected);
}

#[test]
#[should_panic(expected: ('price feed not found',))]
fn test_parse_price_feed_updates_unique_rejects_non_unique() {
    let ctx = deploy_mainnet();
    let pyth = ctx.pyth;

    let fee1 = pyth.get_update_fee(data::test_price_update2(), ctx.fee_contract.contract_address);
    assert!(fee1 == 1000);
    ctx.approve_fee(10000);

    start_prank(CheatTarget::One(pyth.contract_address), ctx.user);
    pyth
        .parse_unique_price_feed_updates(
            data::good_update1(),
            array![0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43],
            1712589206,
            2,
        );
}

#[test]
#[should_panic(expected: ('no fresh update',))]
fn test_update_if_necessary_rejects_empty() {
    let ctx = deploy_test();
    let pyth = ctx.pyth;
    ctx.approve_fee(10000);

    start_prank(CheatTarget::One(pyth.contract_address), ctx.user);
    pyth.update_price_feeds_if_necessary(data::test_price_update1(), array![]);
    stop_prank(CheatTarget::One(pyth.contract_address));
}

#[test]
#[should_panic(expected: ('no fresh update',))]
fn test_update_if_necessary_rejects_no_fresh() {
    let ctx = deploy_test();
    let pyth = ctx.pyth;
    ctx.approve_fee(10000);

    let mut spy = spy_events(SpyOn::One(pyth.contract_address));

    start_prank(CheatTarget::One(pyth.contract_address), ctx.user);
    pyth.update_price_feeds_if_necessary(data::test_price_update1(), array![]);

    let price_id = 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43;
    assert!(pyth.get_price_unsafe(price_id).is_err());
    spy.fetch_events();
    assert!(spy.events.len() == 0);

    let times = array![PriceFeedPublishTime { price_id, publish_time: 1715769470 }];
    pyth.update_price_feeds_if_necessary(data::test_price_update1(), times);

    let last_price = pyth.get_price_unsafe(price_id).unwrap_with_felt252();
    assert!(last_price.price == 6281060000000);
    assert!(last_price.publish_time == 1715769470);

    spy.fetch_events();
    assert!(spy.events.len() == 1);

    let times = array![PriceFeedPublishTime { price_id, publish_time: 1715769470 }];
    pyth.update_price_feeds_if_necessary(data::test_price_update2(), times);
}

#[test]
fn test_get_no_older_works() {
    let ctx = deploy_mainnet();
    let pyth = ctx.pyth;

    let price_id = 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43;
    let err = pyth.get_price_unsafe(price_id).unwrap_err();
    assert!(err == GetPriceUnsafeError::PriceFeedNotFound);
    let err = pyth.get_ema_price_unsafe(price_id).unwrap_err();
    assert!(err == GetPriceUnsafeError::PriceFeedNotFound);
    let err = pyth.query_price_feed_unsafe(price_id).unwrap_err();
    assert!(err == GetPriceUnsafeError::PriceFeedNotFound);
    let err = pyth.get_price_no_older_than(price_id, 100).unwrap_err();
    assert!(err == GetPriceNoOlderThanError::PriceFeedNotFound);
    let err = pyth.get_ema_price_no_older_than(price_id, 100).unwrap_err();
    assert!(err == GetPriceNoOlderThanError::PriceFeedNotFound);
    let err = pyth.query_price_feed_no_older_than(price_id, 100).unwrap_err();
    assert!(err == GetPriceNoOlderThanError::PriceFeedNotFound);

    ctx.approve_fee(10000);

    start_prank(CheatTarget::One(pyth.contract_address), ctx.user.try_into().unwrap());
    pyth.update_price_feeds(data::good_update1());
    stop_prank(CheatTarget::One(pyth.contract_address));

    start_warp(CheatTarget::One(pyth.contract_address), 1712589210);
    let err = pyth.get_price_no_older_than(price_id, 3).unwrap_err();
    assert!(err == GetPriceNoOlderThanError::StalePrice);
    let err = pyth.get_ema_price_no_older_than(price_id, 3).unwrap_err();
    assert!(err == GetPriceNoOlderThanError::StalePrice);
    let err = pyth.query_price_feed_no_older_than(price_id, 3).unwrap_err();
    assert!(err == GetPriceNoOlderThanError::StalePrice);

    start_warp(CheatTarget::One(pyth.contract_address), 1712589208);
    let val = pyth.get_price_no_older_than(price_id, 3).unwrap_with_felt252();
    assert!(val.publish_time == 1712589206);
    assert!(val.price == 7192002930010);
    let val = pyth.get_ema_price_no_older_than(price_id, 3).unwrap_with_felt252();
    assert!(val.publish_time == 1712589206);
    assert!(val.price == 7181868900000);
    let val = pyth.query_price_feed_no_older_than(price_id, 3).unwrap_with_felt252();
    assert!(val.price.publish_time == 1712589206);
    assert!(val.price.price == 7192002930010);

    start_warp(CheatTarget::One(pyth.contract_address), 1712589204);
    let val = pyth.get_price_no_older_than(price_id, 3).unwrap_with_felt252();
    assert!(val.publish_time == 1712589206);
    assert!(val.price == 7192002930010);
    let val = pyth.get_ema_price_no_older_than(price_id, 3).unwrap_with_felt252();
    assert!(val.publish_time == 1712589206);
    assert!(val.price == 7181868900000);
    let val = pyth.query_price_feed_no_older_than(price_id, 3).unwrap_with_felt252();
    assert!(val.price.publish_time == 1712589206);
    assert!(val.price.price == 7192002930010);

    stop_warp(CheatTarget::One(pyth.contract_address));
}

#[test]
fn test_governance_set_fee_works() {
    let ctx = deploy_test();
    let pyth = ctx.pyth;
    let fee_contract = ctx.fee_contract;
    let user = ctx.user;

    let fee1 = pyth.get_update_fee(data::test_price_update1(), ctx.fee_contract.contract_address);
    assert!(fee1 == 1000);
    ctx.approve_fee(10000);

    let mut balance = fee_contract.balanceOf(user);
    start_prank(CheatTarget::One(pyth.contract_address), user);
    pyth.update_price_feeds(data::test_price_update1());
    stop_prank(CheatTarget::One(pyth.contract_address));
    let new_balance = fee_contract.balanceOf(user);
    assert!(balance - new_balance == 1000);
    balance = new_balance;
    let last_price = pyth
        .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
        .unwrap_with_felt252();
    assert!(last_price.price == 6281060000000);

    let mut spy = spy_events(SpyOn::One(pyth.contract_address));

    pyth.execute_governance_instruction(data::pyth_set_fee());

    spy.fetch_events();
    assert!(spy.events.len() == 1);
    let (from, event) = spy.events.pop_front().unwrap();
    assert!(from == pyth.contract_address);
    let event = decode_event(event);
    let expected = FeeSet {
        old_fee: 1000, new_fee: 4200, token: ctx.fee_contract.contract_address
    };
    assert!(event == PythEvent::FeeSet(expected));

    let fee2 = pyth.get_update_fee(data::test_price_update2(), ctx.fee_contract.contract_address);
    assert!(fee2 == 4200);
    let fee2_alt = pyth
        .get_update_fee(data::test_price_update2(), ctx.fee_contract2.contract_address);
    assert!(fee2_alt == 2000);

    start_prank(CheatTarget::One(pyth.contract_address), user);
    pyth.update_price_feeds(data::test_price_update2());
    stop_prank(CheatTarget::One(pyth.contract_address));
    let new_balance = fee_contract.balanceOf(user);
    assert!(balance - new_balance == 4200);
    let last_price = pyth
        .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
        .unwrap_with_felt252();
    assert!(last_price.price == 6281522520745);
}

#[test]
fn test_governance_set_fee_in_token_works() {
    let ctx = deploy_test();
    let pyth = ctx.pyth;
    let fee_contract = ctx.fee_contract;
    let user = ctx.user;

    let fee1 = pyth.get_update_fee(data::test_price_update1(), ctx.fee_contract.contract_address);
    assert!(fee1 == 1000);
    ctx.approve_fee(1000);

    let mut balance = fee_contract.balanceOf(user);
    start_prank(CheatTarget::One(pyth.contract_address), user);
    pyth.update_price_feeds(data::test_price_update1());
    stop_prank(CheatTarget::One(pyth.contract_address));
    let new_balance = fee_contract.balanceOf(user);
    assert!(balance - new_balance == 1000);
    balance = new_balance;
    let last_price = pyth
        .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
        .unwrap_with_felt252();
    assert!(last_price.price == 6281060000000);

    let mut spy = spy_events(SpyOn::One(pyth.contract_address));

    pyth.execute_governance_instruction(data::pyth_set_fee_in_token());

    spy.fetch_events();
    assert!(spy.events.len() == 1);
    let (from, event) = spy.events.pop_front().unwrap();
    assert!(from == pyth.contract_address);
    let event = decode_event(event);
    let expected = FeeSet {
        old_fee: 2000, new_fee: 4200, token: ctx.fee_contract2.contract_address
    };
    assert!(event == PythEvent::FeeSet(expected));

    let fee2 = pyth.get_update_fee(data::test_price_update2(), ctx.fee_contract.contract_address);
    assert!(fee2 == 1000);
    let fee2_alt = pyth
        .get_update_fee(data::test_price_update2(), ctx.fee_contract2.contract_address);
    assert!(fee2_alt == 4200);
    ctx.approve_fee2(4200);

    let balance2 = ctx.fee_contract2.balanceOf(user);
    start_prank(CheatTarget::One(pyth.contract_address), user);
    pyth.update_price_feeds(data::test_price_update2());
    stop_prank(CheatTarget::One(pyth.contract_address));
    let new_balance2 = ctx.fee_contract2.balanceOf(user);
    assert!(balance2 - new_balance2 == 4200);
    let last_price = pyth
        .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
        .unwrap_with_felt252();
    assert!(last_price.price == 6281522520745);
}

#[test]
#[fuzzer(runs: 100, seed: 0)]
#[should_panic]
fn test_rejects_corrupted_governance_instruction(pos: usize, random1: usize, random2: usize) {
    let ctx = deploy_test();
    let pyth = ctx.pyth;

    let input = corrupted_vm(data::pyth_set_fee(), pos, random1, random2);
    pyth.execute_governance_instruction(input);
}

#[test]
fn test_governance_set_data_sources_works() {
    let ctx = deploy_test();
    let pyth = ctx.pyth;
    ctx.approve_fee(10000);

    start_prank(CheatTarget::One(pyth.contract_address), ctx.user);
    pyth.update_price_feeds(data::test_price_update1());
    stop_prank(CheatTarget::One(pyth.contract_address));
    let last_price = pyth
        .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
        .unwrap_with_felt252();
    assert!(last_price.price == 6281060000000);

    let mut spy = spy_events(SpyOn::One(pyth.contract_address));

    pyth.execute_governance_instruction(data::pyth_set_data_sources());

    spy.fetch_events();
    assert!(spy.events.len() == 1);
    let (from, event) = spy.events.pop_front().unwrap();
    assert!(from == pyth.contract_address);
    let event = decode_event(event);
    let expected = DataSourcesSet {
        old_data_sources: array![
            DataSource {
                emitter_chain_id: 26,
                emitter_address: 0xe101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71,
            }
        ],
        new_data_sources: array![
            DataSource {
                emitter_chain_id: 1,
                emitter_address: 0x6bb14509a612f01fbbc4cffeebd4bbfb492a86df717ebe92eb6df432a3f00a25,
            },
            DataSource { emitter_chain_id: 3, emitter_address: 0x12d, },
        ],
    };
    assert!(event == PythEvent::DataSourcesSet(expected));

    start_prank(CheatTarget::One(pyth.contract_address), ctx.user);
    pyth.update_price_feeds(data::test_update2_alt_emitter());
    stop_prank(CheatTarget::One(pyth.contract_address));
    let last_price = pyth
        .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
        .unwrap_with_felt252();
    assert!(last_price.price == 6281522520745);
}

#[test]
#[should_panic(expected: ('invalid update data source',))]
fn test_rejects_update_after_data_source_changed() {
    let ctx = deploy_test();
    let pyth = ctx.pyth;
    ctx.approve_fee(10000);

    start_prank(CheatTarget::One(pyth.contract_address), ctx.user);
    pyth.update_price_feeds(data::test_price_update1());
    stop_prank(CheatTarget::One(pyth.contract_address));
    let last_price = pyth
        .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
        .unwrap_with_felt252();
    assert!(last_price.price == 6281060000000);

    pyth.execute_governance_instruction(data::pyth_set_data_sources());

    start_prank(CheatTarget::One(pyth.contract_address), ctx.user);
    pyth.update_price_feeds(data::test_price_update2());
    stop_prank(CheatTarget::One(pyth.contract_address));
    let last_price = pyth
        .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
        .unwrap_with_felt252();
    assert!(last_price.price == 6281522520745);
}

#[test]
fn test_governance_set_wormhole_works() {
    let wormhole_class = declare("wormhole");
    // Arbitrary
    let wormhole_address = 0x42.try_into().unwrap();
    let wormhole = super::wormhole::deploy_declared_with_test_guardian_at(
        @wormhole_class, wormhole_address
    );

    let user = 'user'.try_into().unwrap();
    let fee_class = declare("ERC20");
    let fee_contract = deploy_fee_contract(fee_class, fee_address1(), user);
    let fee_contract2 = deploy_fee_contract(fee_class, fee_address2(), user);
    let pyth = deploy_pyth_default(
        wormhole.contract_address, fee_contract.contract_address, fee_contract2.contract_address
    );

    start_prank(CheatTarget::One(fee_contract.contract_address), user);
    fee_contract.approve(pyth.contract_address, 10000);
    stop_prank(CheatTarget::One(fee_contract.contract_address));

    start_prank(CheatTarget::One(pyth.contract_address), user);
    pyth.update_price_feeds(data::test_price_update1());
    stop_prank(CheatTarget::One(pyth.contract_address));
    let last_price = pyth
        .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
        .unwrap_with_felt252();
    assert!(last_price.price == 6281060000000);

    // Address used in the governance instruction
    let wormhole2_address = 0x05033f06d5c47bcce7960ea703b04a0bf64bf33f6f2eb5613496da747522d9c2
        .try_into()
        .unwrap();
    let wormhole2 = super::wormhole::deploy_declared_with_test_guardian_at(
        @wormhole_class, wormhole2_address
    );
    wormhole2.submit_new_guardian_set(data::upgrade_to_test2());

    let mut spy = spy_events(SpyOn::One(pyth.contract_address));

    pyth.execute_governance_instruction(data::pyth_set_wormhole());

    spy.fetch_events();
    assert!(spy.events.len() == 1);
    let (from, event) = spy.events.pop_front().unwrap();
    assert!(from == pyth.contract_address);
    let event = decode_event(event);
    let expected = WormholeAddressSet {
        old_address: wormhole_address, new_address: wormhole2_address,
    };
    assert!(event == PythEvent::WormholeAddressSet(expected));

    start_prank(CheatTarget::One(pyth.contract_address), user);
    pyth.update_price_feeds(data::test_update2_set2());
    stop_prank(CheatTarget::One(pyth.contract_address));
    let last_price = pyth
        .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
        .unwrap_with_felt252();
    assert!(last_price.price == 6281522520745);
}

#[test]
#[should_panic(expected: ('invalid guardian set index',))]
fn test_rejects_price_update_without_setting_wormhole() {
    let ctx = deploy_test();
    let pyth = ctx.pyth;

    ctx.approve_fee(10000);

    start_prank(CheatTarget::One(pyth.contract_address), ctx.user);
    pyth.update_price_feeds(data::test_price_update1());
    stop_prank(CheatTarget::One(pyth.contract_address));
    let last_price = pyth
        .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
        .unwrap_with_felt252();
    assert!(last_price.price == 6281060000000);

    start_prank(CheatTarget::One(pyth.contract_address), ctx.user);
    pyth.update_price_feeds(data::test_update2_set2());
}

// This test doesn't pass because of an snforge bug.
// See https://github.com/foundry-rs/starknet-foundry/issues/2096
// TODO: update snforge and unignore when the next release is available
#[test]
#[should_panic]
#[ignore]
fn test_rejects_set_wormhole_without_deploying() {
    let wormhole_class = declare("wormhole");
    // Arbitrary
    let wormhole_address = 0x42.try_into().unwrap();
    let wormhole = super::wormhole::deploy_declared_with_test_guardian_at(
        @wormhole_class, wormhole_address
    );

    let user = 'user'.try_into().unwrap();
    let fee_class = declare("ERC20");
    let fee_contract = deploy_fee_contract(fee_class, fee_address1(), user);
    let fee_contract2 = deploy_fee_contract(fee_class, fee_address2(), user);
    let pyth = deploy_pyth_default(
        wormhole.contract_address, fee_contract.contract_address, fee_contract2.contract_address
    );
    pyth.execute_governance_instruction(data::pyth_set_wormhole());
}

#[test]
#[should_panic(expected: ('Invalid signature',))]
fn test_rejects_set_wormhole_with_incompatible_guardians() {
    let wormhole_class = declare("wormhole");
    // Arbitrary
    let wormhole_address = 0x42.try_into().unwrap();
    let wormhole = super::wormhole::deploy_declared_with_test_guardian_at(
        @wormhole_class, wormhole_address
    );

    let user = 'user'.try_into().unwrap();
    let fee_class = declare("ERC20");
    let fee_contract = deploy_fee_contract(fee_class, fee_address1(), user);
    let fee_contract2 = deploy_fee_contract(fee_class, fee_address2(), user);
    let pyth = deploy_pyth_default(
        wormhole.contract_address, fee_contract.contract_address, fee_contract2.contract_address
    );

    // Address used in the governance instruction
    let wormhole2_address = 0x05033f06d5c47bcce7960ea703b04a0bf64bf33f6f2eb5613496da747522d9c2
        .try_into()
        .unwrap();
    super::wormhole::deploy_declared_at(
        @wormhole_class,
        0,
        array_try_into(array![0x301]),
        super::wormhole::CHAIN_ID,
        super::wormhole::GOVERNANCE_CHAIN_ID,
        super::wormhole::GOVERNANCE_CONTRACT,
        Option::Some(wormhole2_address),
    );
    pyth.execute_governance_instruction(data::pyth_set_wormhole());
}

#[test]
fn test_governance_transfer_works() {
    let ctx = deploy_test();
    let pyth = ctx.pyth;

    let mut spy = spy_events(SpyOn::One(pyth.contract_address));

    pyth.execute_governance_instruction(data::pyth_auth_transfer());

    spy.fetch_events();
    assert!(spy.events.len() == 1);
    let (from, event) = spy.events.pop_front().unwrap();
    assert!(from == pyth.contract_address);
    let event = decode_event(event);
    let expected = GovernanceDataSourceSet {
        old_data_source: DataSource { emitter_chain_id: 1, emitter_address: 41, },
        new_data_source: DataSource { emitter_chain_id: 2, emitter_address: 43, },
        last_executed_governance_sequence: 1,
    };
    assert!(event == PythEvent::GovernanceDataSourceSet(expected));

    pyth.execute_governance_instruction(data::pyth_set_fee_alt_emitter());
}

#[test]
#[should_panic(expected: ('invalid governance data source',))]
fn test_set_fee_rejects_wrong_emitter() {
    let ctx = deploy_test();
    let pyth = ctx.pyth;

    pyth.execute_governance_instruction(data::pyth_set_fee_alt_emitter());
}

#[test]
#[should_panic(expected: ('invalid governance data source',))]
fn test_rejects_old_emitter_after_transfer() {
    let ctx = deploy_test();
    let pyth = ctx.pyth;

    pyth.execute_governance_instruction(data::pyth_auth_transfer());
    pyth.execute_governance_instruction(data::pyth_set_fee());
}

#[test]
fn test_upgrade_works() {
    let ctx = deploy_test();
    let pyth = ctx.pyth;

    let class = declare("pyth_fake_upgrade1");

    let mut spy = spy_events(SpyOn::One(pyth.contract_address));

    pyth.execute_governance_instruction(data::pyth_upgrade_fake1());

    spy.fetch_events();
    assert!(spy.events.len() == 1);
    let (from, event) = spy.events.pop_front().unwrap();
    assert!(from == pyth.contract_address);
    let event = decode_event(event);
    let expected = ContractUpgraded { new_class_hash: class.class_hash };
    assert!(event == PythEvent::ContractUpgraded(expected));

    let last_price = pyth.get_price_unsafe(1234).unwrap_with_felt252();
    assert!(last_price.price == 42);
}

#[test]
#[should_panic]
#[ignore] // TODO: unignore when snforge is updated
fn test_upgrade_rejects_invalid_hash() {
    let ctx = deploy_test();
    let pyth = ctx.pyth;

    pyth.execute_governance_instruction(data::pyth_upgrade_invalid_hash());
}

#[test]
#[should_panic]
#[ignore] // TODO: unignore when snforge is updated
fn test_upgrade_rejects_not_pyth() {
    let ctx = deploy_test();
    let pyth = ctx.pyth;

    declare("pyth_fake_upgrade_not_pyth");
    pyth.execute_governance_instruction(data::pyth_upgrade_not_pyth());
}

#[test]
#[should_panic(expected: ('invalid governance message',))]
fn test_upgrade_rejects_wrong_magic() {
    let ctx = deploy_test();
    let pyth = ctx.pyth;

    declare("pyth_fake_upgrade_wrong_magic");
    pyth.execute_governance_instruction(data::pyth_upgrade_wrong_magic());
}

#[test]
#[should_panic(expected: ('invalid guardian set index',))]
fn update_price_feeds_with_set3_rejects_on_guardian_set4() {
    let wormhole = super::wormhole::deploy_with_mainnet_guardian_set4();
    let ctx = deploy_with_wormhole(wormhole);
    let pyth = ctx.pyth;
    ctx.approve_fee(1000);
    start_prank(CheatTarget::One(pyth.contract_address), ctx.user.try_into().unwrap());
    pyth.update_price_feeds(data::good_update1());
    stop_prank(CheatTarget::One(pyth.contract_address));
}

#[test]
fn update_price_feeds_works_with_guardian_set4() {
    let wormhole = super::wormhole::deploy_with_mainnet_guardian_set4();
    let ctx = deploy_with_wormhole(wormhole);
    let pyth = ctx.pyth;
    ctx.approve_fee(1000);
    start_prank(CheatTarget::One(pyth.contract_address), ctx.user.try_into().unwrap());
    pyth.update_price_feeds(data::unique_update1());
    stop_prank(CheatTarget::One(pyth.contract_address));
}

#[test]
fn update_price_feeds_works_with_guardian_sets_3_4() {
    let wormhole = super::wormhole::deploy_with_mainnet_guardian_sets_3_4();
    let ctx = deploy_with_wormhole(wormhole);
    let pyth = ctx.pyth;
    ctx.approve_fee(2000);
    start_prank(CheatTarget::One(pyth.contract_address), ctx.user.try_into().unwrap());
    pyth.update_price_feeds(data::good_update1());
    pyth.update_price_feeds(data::unique_update1());
    stop_prank(CheatTarget::One(pyth.contract_address));
}

#[derive(Drop, Copy)]
struct Context {
    user: ContractAddress,
    wormhole: IWormholeDispatcher,
    fee_contract: IERC20CamelDispatcher,
    fee_contract2: IERC20CamelDispatcher,
    pyth: IPythDispatcher,
}

#[generate_trait]
impl ContextImpl of ContextTrait {
    fn approve_fee(self: Context, amount: u256) {
        start_prank(CheatTarget::One(self.fee_contract.contract_address), self.user);
        self.fee_contract.approve(self.pyth.contract_address, amount);
        stop_prank(CheatTarget::One(self.fee_contract.contract_address));
    }

    fn approve_fee2(self: Context, amount: u256) {
        start_prank(CheatTarget::One(self.fee_contract2.contract_address), self.user);
        self.fee_contract2.approve(self.pyth.contract_address, amount);
        stop_prank(CheatTarget::One(self.fee_contract2.contract_address));
    }
}

fn deploy_test() -> Context {
    deploy_with_wormhole(super::wormhole::deploy_with_test_guardian())
}

fn deploy_mainnet() -> Context {
    deploy_with_wormhole(super::wormhole::deploy_with_mainnet_guardians())
}

fn deploy_with_wormhole(wormhole: IWormholeDispatcher) -> Context {
    let user = 'user'.try_into().unwrap();
    let fee_class = declare("ERC20");
    let fee_contract = deploy_fee_contract(fee_class, fee_address1(), user);
    let fee_contract2 = deploy_fee_contract(fee_class, fee_address2(), user);
    let pyth = deploy_pyth_default(
        wormhole.contract_address, fee_contract.contract_address, fee_contract2.contract_address
    );
    Context { user, wormhole, fee_contract, fee_contract2, pyth }
}

fn deploy_pyth_default(
    wormhole_address: ContractAddress,
    fee_token_address1: ContractAddress,
    fee_token_address2: ContractAddress
) -> IPythDispatcher {
    deploy_pyth(
        wormhole_address,
        fee_token_address1,
        1000,
        fee_token_address2,
        2000,
        array![
            DataSource {
                emitter_chain_id: 26,
                emitter_address: 0xe101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71,
            }
        ],
        1,
        41,
        0,
    )
}

fn deploy_pyth(
    wormhole_address: ContractAddress,
    fee_token_address1: ContractAddress,
    single_update_fee1: u256,
    fee_token_address2: ContractAddress,
    single_update_fee2: u256,
    data_sources: Array<DataSource>,
    governance_emitter_chain_id: u16,
    governance_emitter_address: u256,
    governance_initial_sequence: u64,
) -> IPythDispatcher {
    let mut args = array![];
    (wormhole_address, fee_token_address1, single_update_fee1).serialize(ref args);
    (fee_token_address2, single_update_fee2).serialize(ref args);
    (data_sources, governance_emitter_chain_id).serialize(ref args);
    (governance_emitter_address, governance_initial_sequence).serialize(ref args);
    let contract = declare("pyth");
    let contract_address = match contract.deploy(@args) {
        Result::Ok(v) => { v },
        Result::Err(err) => { panic(err.panic_data) },
    };
    IPythDispatcher { contract_address }
}

fn fee_address1() -> ContractAddress {
    0x1010.try_into().unwrap()
}
fn fee_address2() -> ContractAddress {
    0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7.try_into().unwrap()
}

fn deploy_fee_contract(
    class: ContractClass, at: ContractAddress, recipient: ContractAddress
) -> IERC20CamelDispatcher {
    let mut args = array![];
    let name: ByteArray = "eth";
    let symbol: ByteArray = "eth";
    (name, symbol, 100000_u256, recipient).serialize(ref args);
    let contract_address = match class.deploy_at(@args, at) {
        Result::Ok(v) => { v },
        Result::Err(err) => { panic(err.panic_data) },
    };
    IERC20CamelDispatcher { contract_address }
}
