use multiversx_price_aggregator_sc::{ price_aggregator_data::{OracleStatus, TokenPair}, PriceAggregator, }; use multiversx_sc_scenario::imports::*; const DECIMALS: u8 = 0; const EGLD_TICKER: &[u8] = b"EGLD"; const NR_ORACLES: usize = 50; const OWNER: TestAddress = TestAddress::new("owner"); const PRICE_AGGREGATOR_ADDRESS: TestSCAddress = TestSCAddress::new("price-aggregator"); const PRICE_AGGREGATOR_PATH: MxscPath = MxscPath::new("../output/multiversx-price-aggregator-sc.mxsc.json"); const SLASH_AMOUNT: u64 = 10; const SLASH_QUORUM: usize = 3; const STAKE_AMOUNT: u64 = 20; const SUBMISSION_COUNT: usize = 50; const USD_TICKER: &[u8] = b"USDC"; mod price_aggregator_proxy; fn world() -> ScenarioWorld { let mut blockchain = ScenarioWorld::new(); blockchain.set_current_dir_from_workspace("contracts/core/price-aggregator"); blockchain.register_contract( PRICE_AGGREGATOR_PATH, multiversx_price_aggregator_sc::ContractBuilder, ); blockchain } struct PriceAggregatorTestState { world: ScenarioWorld, oracles: Vec, } impl PriceAggregatorTestState { fn new() -> Self { let mut world = world(); world.start_trace(); world .account(OWNER) .nonce(1) .new_address(OWNER, 1, PRICE_AGGREGATOR_ADDRESS) .current_block() .block_timestamp(100); let mut oracles = Vec::new(); for i in 1..=NR_ORACLES { let address_expr = format!("oracle{}", i); let address: TestAddress = TestAddress::new(address_expr.as_str()); let address_value = AddressValue::from(address.eval_to_expr().as_str()); world.account(address).nonce(1).balance(STAKE_AMOUNT); oracles.push(address_value); } Self { world, oracles } } fn deploy(&mut self) -> &mut Self { let oracles = MultiValueVec::from( self.oracles .iter() .map(|oracle| oracle.to_address()) .collect::>(), ); self.world .tx() .from(OWNER) .gas(120_000_000u64) .typed(price_aggregator_proxy::PriceAggregatorProxy) .init( EgldOrEsdtTokenIdentifier::egld(), STAKE_AMOUNT, SLASH_AMOUNT, SLASH_QUORUM, SUBMISSION_COUNT, oracles, ) .code(PRICE_AGGREGATOR_PATH) .run(); for address in self.oracles.iter() { self.world .tx() .from(&address.to_address()) .to(PRICE_AGGREGATOR_ADDRESS) .gas(5_000_000u64) .typed(price_aggregator_proxy::PriceAggregatorProxy) .stake() .egld(STAKE_AMOUNT) .run(); } self } fn set_pair_decimals(&mut self) { self.world .tx() .from(OWNER) .to(PRICE_AGGREGATOR_ADDRESS) .typed(price_aggregator_proxy::PriceAggregatorProxy) .set_pair_decimals(EGLD_TICKER, USD_TICKER, DECIMALS) .run(); } fn unpause_endpoint(&mut self) { self.world .tx() .from(OWNER) .to(PRICE_AGGREGATOR_ADDRESS) .gas(5_000_000u64) .typed(price_aggregator_proxy::PriceAggregatorProxy) .unpause_endpoint() .run(); } fn submit(&mut self, from: &AddressValue, submission_timestamp: u64, price: u64) { self.world .tx() .from(&from.to_address()) .to(PRICE_AGGREGATOR_ADDRESS) .gas(7_000_000u64) .typed(price_aggregator_proxy::PriceAggregatorProxy) .submit( EGLD_TICKER, USD_TICKER, submission_timestamp, price, DECIMALS, ) .run(); } } #[test] fn test_price_aggregator_submit() { let mut state = PriceAggregatorTestState::new(); state.deploy(); // configure the number of decimals state.set_pair_decimals(); // unpause state.unpause_endpoint(); // submit first state.submit(&state.oracles[0].clone(), 95, rand::random::()); // submit ok for index in 1..SUBMISSION_COUNT - 1 { state.submit(&state.oracles[index].clone(), 100, rand::random::()); } let current_timestamp = 100; state.world.query().to(PRICE_AGGREGATOR_ADDRESS).whitebox( multiversx_price_aggregator_sc::contract_obj, |sc| { let blockchain_timestamp = sc.blockchain().get_block_timestamp(); let token_pair = TokenPair { from: managed_buffer!(EGLD_TICKER), to: managed_buffer!(USD_TICKER), }; assert_eq!(blockchain_timestamp, current_timestamp); let submission_count = sc.submission_count().get(); assert_eq!(submission_count, SUBMISSION_COUNT); assert_eq!( sc.first_submission_timestamp(&token_pair).get(), current_timestamp ); assert_eq!( sc.last_submission_timestamp(&token_pair).get(), current_timestamp ); let submissions = sc.submissions().get(&token_pair).unwrap(); assert_eq!(submissions.len(), SUBMISSION_COUNT - 1); for index in 0..SUBMISSION_COUNT - 1 { assert_eq!( sc.oracle_status() .get(&managed_address!(&state.oracles[index].to_address())) .unwrap(), OracleStatus { total_submissions: 1, accepted_submissions: 1 } ); } }, ); // submit last that resets the round state.submit( &state.oracles[SUBMISSION_COUNT - 1].clone(), 100, rand::random::(), ); state .world .write_scenario_trace("scenarios/stress_submit_test.scen.json"); }