From 34a42ccbb8d6542201098acf403a8f3c5a9b06e8 Mon Sep 17 00:00:00 2001 From: Ovidiu Stinga Date: Mon, 24 Jul 2023 18:45:39 +0300 Subject: [PATCH 1/8] initial setup --- .../tests/price_aggregator_blackbox_test.rs | 216 ++++++++++++++++++ .../tests/price_aggregator_whitebox_test.rs | 196 ++++++++++++++++ 2 files changed, 412 insertions(+) create mode 100644 contracts/core/price-aggregator/tests/price_aggregator_blackbox_test.rs create mode 100644 contracts/core/price-aggregator/tests/price_aggregator_whitebox_test.rs diff --git a/contracts/core/price-aggregator/tests/price_aggregator_blackbox_test.rs b/contracts/core/price-aggregator/tests/price_aggregator_blackbox_test.rs new file mode 100644 index 0000000000..6b70b4a15b --- /dev/null +++ b/contracts/core/price-aggregator/tests/price_aggregator_blackbox_test.rs @@ -0,0 +1,216 @@ +use multiversx_price_aggregator_sc::PriceAggregator; +use multiversx_sc::types::{Address, EgldOrEsdtTokenIdentifier, MultiValueEncoded}; +use multiversx_sc_modules::pause::EndpointWrappers; +use multiversx_sc_scenario::{managed_address, managed_biguint, managed_buffer, WhiteboxContract}; + +use multiversx_sc_scenario::{scenario_model::*, *}; + +const PRICE_AGGREGATOR_PATH_EXPR: &str = "file:output/multiversx-price-aggregator-sc.wasm"; + +pub const NR_ORACLES: usize = 4; +pub const SUBMISSION_COUNT: usize = 3; +pub const DECIMALS: u8 = 0; +pub static EGLD_TICKER: &[u8] = b"EGLD"; +pub static USD_TICKER: &[u8] = b"USDC"; + +pub const STAKE_AMOUNT: u64 = 20; +pub const SLASH_AMOUNT: u64 = 10; +pub const SLASH_QUORUM: usize = 2; + +fn world() -> ScenarioWorld { + let mut blockchain = ScenarioWorld::new(); + blockchain.set_current_dir_from_workspace("contracts/core/price-aggregator"); + + blockchain.register_contract( + PRICE_AGGREGATOR_PATH_EXPR, + multiversx_price_aggregator_sc::ContractBuilder, + ); + blockchain +} + +#[test] +fn price_agg_submit_test() { + let mut world = world(); + let price_aggregator_whitebox = WhiteboxContract::new( + "sc:price-aggregator", + multiversx_price_aggregator_sc::contract_obj, + ); + let price_aggregator_code = world.code_expression(PRICE_AGGREGATOR_PATH_EXPR); + + let mut oracles = Vec::new(); + for i in 0..NR_ORACLES { + let oracle_address_expr = format!("address::oracle{i}"); + let oracle_address = Address::from_slice(oracle_address_expr.as_bytes()); + oracles.push(oracle_address); + } + + + // let mut oracle_args = MultiValueEncoded::new(); + // for oracle in &oracles { + // oracle_args.push(managed_address!(oracle)); + // } + // sc.init( + // EgldOrEsdtTokenIdentifier::egld(), + // managed_biguint!(STAKE_AMOUNT), + // managed_biguint!(SLASH_AMOUNT), + // SLASH_QUORUM, + // SUBMISSION_COUNT, + // oracle_args, + // ) + + world + .set_state_step( + SetStateStep::new() + .put_account("address:owner", Account::new().nonce(1)) + .new_address("address:owner", 1, "sc:price-aggregator") + .block_timestamp(100) + .put_account( + "address:oracle0", + Account::new().nonce(1).balance(STAKE_AMOUNT), + ), + ) + .sc_deploy( + ScDeployStep::new() + .from("address:owner") + .code(price_aggregator_code) + .argument() + }, + // .whitebox_deploy( + // &price_aggregator_whitebox, + // ScDeployStep::new() + // .from("address:owner") + // .code(price_aggregator_code), + // |sc| { + // let mut oracle_args = MultiValueEncoded::new(); + // for oracle in &oracles { + // oracle_args.push(managed_address!(oracle)); + // } + // sc.init( + // EgldOrEsdtTokenIdentifier::egld(), + // managed_biguint!(STAKE_AMOUNT), + // managed_biguint!(SLASH_AMOUNT), + // SLASH_QUORUM, + // SUBMISSION_COUNT, + // oracle_args, + // ) + // }, + // ) + // .whitebox_call( + // &price_aggregator_whitebox, + // ScCallStep::new().from("address:owner"), + // |sc| { + // sc.set_pair_decimals( + // managed_buffer!(EGLD_TICKER), + // managed_buffer!(USD_TICKER), + // DECIMALS, + // ) + // }, + // ) + // .whitebox_call_check( + // &price_aggregator_whitebox, + // ScCallStep::new().from("address:oracle0"), + // |sc| { + // sc.submit( + // managed_buffer!(EGLD_TICKER), + // managed_buffer!(USD_TICKER), + // 99, + // managed_biguint!(100), + // DECIMALS, + // ) + // }, + // |r| { + // r.assert_user_error("Contract is paused"); + // }, + // ) + // .whitebox_call( + // &price_aggregator_whitebox, + // ScCallStep::new().from("address:owner"), + // |sc| sc.call_unpause_endpoint(), + // ) + // .whitebox_call_check( + // &price_aggregator_whitebox, + // ScCallStep::new().from("address:oracle0"), + // |sc| { + // sc.submit( + // managed_buffer!(EGLD_TICKER), + // managed_buffer!(USD_TICKER), + // 10, + // managed_biguint!(100), + // DECIMALS, + // ) + // }, + // |r| { + // r.assert_user_error("First submission too old"); + // }, + // ) + // .whitebox_call_check( + // &price_aggregator_whitebox, + // ScCallStep::new().from("address:oracle0"), + // |sc| { + // sc.call_unpause_endpoint(); + // sc.submit( + // managed_buffer!(EGLD_TICKER), + // managed_buffer!(USD_TICKER), + // 95, + // managed_biguint!(100), + // DECIMALS, + // ) + // }, + // |r| { + // r.assert_ok(); + // }, + // ); + + // pa_setup + // .b_mock + // .execute_query(&pa_setup.price_agg, |sc| { + // let token_pair = TokenPair { + // from: managed_buffer!(EGLD_TICKER), + // to: managed_buffer!(USD_TICKER), + // }; + // 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(), 1); + // assert_eq!( + // submissions.get(&managed_address!(&oracles[0])).unwrap(), + // managed_biguint!(100) + // ); + + // assert_eq!( + // sc.oracle_status() + // .get(&managed_address!(&oracles[0])) + // .unwrap(), + // OracleStatus { + // total_submissions: 1, + // accepted_submissions: 1 + // } + // ); + // }) + // .assert_ok(); + + // // first oracle submit again - submission not accepted + // pa_setup.submit(&oracles[0], 95, 100).assert_ok(); + + // pa_setup + // .b_mock + // .execute_query(&pa_setup.price_agg, |sc| { + // assert_eq!( + // sc.oracle_status() + // .get(&managed_address!(&oracles[0])) + // .unwrap(), + // OracleStatus { + // total_submissions: 2, + // accepted_submissions: 1 + // } + // ); + // }) + // .assert_ok(); +} diff --git a/contracts/core/price-aggregator/tests/price_aggregator_whitebox_test.rs b/contracts/core/price-aggregator/tests/price_aggregator_whitebox_test.rs new file mode 100644 index 0000000000..3737fbf458 --- /dev/null +++ b/contracts/core/price-aggregator/tests/price_aggregator_whitebox_test.rs @@ -0,0 +1,196 @@ +use multiversx_price_aggregator_sc::PriceAggregator; +use multiversx_sc::types::{Address, EgldOrEsdtTokenIdentifier, MultiValueEncoded}; +use multiversx_sc_modules::pause::EndpointWrappers; +use multiversx_sc_scenario::{managed_address, managed_biguint, managed_buffer, WhiteboxContract}; + +use multiversx_sc_scenario::{scenario_model::*, *}; + +const PRICE_AGGREGATOR_PATH_EXPR: &str = "file:output/multiversx-price-aggregator-sc.wasm"; + +pub const NR_ORACLES: usize = 4; +pub const SUBMISSION_COUNT: usize = 3; +pub const DECIMALS: u8 = 0; +pub static EGLD_TICKER: &[u8] = b"EGLD"; +pub static USD_TICKER: &[u8] = b"USDC"; + +pub const STAKE_AMOUNT: u64 = 20; +pub const SLASH_AMOUNT: u64 = 10; +pub const SLASH_QUORUM: usize = 2; + +fn world() -> ScenarioWorld { + let mut blockchain = ScenarioWorld::new(); + blockchain.set_current_dir_from_workspace("contracts/core/price-aggregator"); + + blockchain.register_contract( + PRICE_AGGREGATOR_PATH_EXPR, + multiversx_price_aggregator_sc::ContractBuilder, + ); + blockchain +} + +#[test] +fn price_agg_submit_test() { + let mut world = world(); + let price_aggregator_whitebox = WhiteboxContract::new( + "sc:price-aggregator", + multiversx_price_aggregator_sc::contract_obj, + ); + let price_aggregator_code = world.code_expression(PRICE_AGGREGATOR_PATH_EXPR); + + let mut oracles = Vec::new(); + for i in 0..NR_ORACLES { + let oracle_address_expr = format!("address::oracle{i}"); + let oracle_address = Address::from_slice(oracle_address_expr.as_bytes()); + oracles.push(oracle_address); + } + + world + .set_state_step( + SetStateStep::new() + .put_account("address:owner", Account::new().nonce(1)) + .new_address("address:owner", 1, "sc:price-aggregator") + .block_timestamp(100) + .put_account( + "address:oracle0", + Account::new().nonce(1).balance(STAKE_AMOUNT), + ), + ) + .whitebox_deploy( + &price_aggregator_whitebox, + ScDeployStep::new() + .from("address:owner") + .code(price_aggregator_code), + |sc| { + let mut oracle_args = MultiValueEncoded::new(); + for oracle in &oracles { + oracle_args.push(managed_address!(oracle)); + } + sc.init( + EgldOrEsdtTokenIdentifier::egld(), + managed_biguint!(STAKE_AMOUNT), + managed_biguint!(SLASH_AMOUNT), + SLASH_QUORUM, + SUBMISSION_COUNT, + oracle_args, + ) + }, + ) + .whitebox_call( + &price_aggregator_whitebox, + ScCallStep::new().from("address:owner"), + |sc| { + sc.set_pair_decimals( + managed_buffer!(EGLD_TICKER), + managed_buffer!(USD_TICKER), + DECIMALS, + ) + }, + ) + .whitebox_call_check( + &price_aggregator_whitebox, + ScCallStep::new().from("address:oracle0"), + |sc| { + sc.submit( + managed_buffer!(EGLD_TICKER), + managed_buffer!(USD_TICKER), + 99, + managed_biguint!(100), + DECIMALS, + ) + }, + |r| { + r.assert_user_error("Contract is paused"); + }, + ) + .whitebox_call( + &price_aggregator_whitebox, + ScCallStep::new().from("address:owner"), + |sc| sc.call_unpause_endpoint(), + ) + .whitebox_call_check( + &price_aggregator_whitebox, + ScCallStep::new().from("address:oracle0"), + |sc| { + sc.submit( + managed_buffer!(EGLD_TICKER), + managed_buffer!(USD_TICKER), + 10, + managed_biguint!(100), + DECIMALS, + ) + }, + |r| { + r.assert_user_error("First submission too old"); + }, + ) + .whitebox_call_check( + &price_aggregator_whitebox, + ScCallStep::new().from("address:oracle0"), + |sc| { + sc.call_unpause_endpoint(); + sc.submit( + managed_buffer!(EGLD_TICKER), + managed_buffer!(USD_TICKER), + 95, + managed_biguint!(100), + DECIMALS, + ) + }, + |r| { + r.assert_ok(); + }, + ); + + // pa_setup + // .b_mock + // .execute_query(&pa_setup.price_agg, |sc| { + // let token_pair = TokenPair { + // from: managed_buffer!(EGLD_TICKER), + // to: managed_buffer!(USD_TICKER), + // }; + // 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(), 1); + // assert_eq!( + // submissions.get(&managed_address!(&oracles[0])).unwrap(), + // managed_biguint!(100) + // ); + + // assert_eq!( + // sc.oracle_status() + // .get(&managed_address!(&oracles[0])) + // .unwrap(), + // OracleStatus { + // total_submissions: 1, + // accepted_submissions: 1 + // } + // ); + // }) + // .assert_ok(); + + // // first oracle submit again - submission not accepted + // pa_setup.submit(&oracles[0], 95, 100).assert_ok(); + + // pa_setup + // .b_mock + // .execute_query(&pa_setup.price_agg, |sc| { + // assert_eq!( + // sc.oracle_status() + // .get(&managed_address!(&oracles[0])) + // .unwrap(), + // OracleStatus { + // total_submissions: 2, + // accepted_submissions: 1 + // } + // ); + // }) + // .assert_ok(); +} From ee949c3567c2238c0bb845cf3a783e9909d4ca78 Mon Sep 17 00:00:00 2001 From: Ovidiu Stinga Date: Tue, 25 Jul 2023 13:20:00 +0300 Subject: [PATCH 2/8] playground with new whitebox framework - wip --- .../tests/price_aggregator_blackbox_test.rs | 216 ------------------ .../tests/price_aggregator_whitebox_test.rs | 192 +++++++--------- .../scenario/model/transaction/tx_expect.rs | 13 +- 3 files changed, 88 insertions(+), 333 deletions(-) delete mode 100644 contracts/core/price-aggregator/tests/price_aggregator_blackbox_test.rs diff --git a/contracts/core/price-aggregator/tests/price_aggregator_blackbox_test.rs b/contracts/core/price-aggregator/tests/price_aggregator_blackbox_test.rs deleted file mode 100644 index 6b70b4a15b..0000000000 --- a/contracts/core/price-aggregator/tests/price_aggregator_blackbox_test.rs +++ /dev/null @@ -1,216 +0,0 @@ -use multiversx_price_aggregator_sc::PriceAggregator; -use multiversx_sc::types::{Address, EgldOrEsdtTokenIdentifier, MultiValueEncoded}; -use multiversx_sc_modules::pause::EndpointWrappers; -use multiversx_sc_scenario::{managed_address, managed_biguint, managed_buffer, WhiteboxContract}; - -use multiversx_sc_scenario::{scenario_model::*, *}; - -const PRICE_AGGREGATOR_PATH_EXPR: &str = "file:output/multiversx-price-aggregator-sc.wasm"; - -pub const NR_ORACLES: usize = 4; -pub const SUBMISSION_COUNT: usize = 3; -pub const DECIMALS: u8 = 0; -pub static EGLD_TICKER: &[u8] = b"EGLD"; -pub static USD_TICKER: &[u8] = b"USDC"; - -pub const STAKE_AMOUNT: u64 = 20; -pub const SLASH_AMOUNT: u64 = 10; -pub const SLASH_QUORUM: usize = 2; - -fn world() -> ScenarioWorld { - let mut blockchain = ScenarioWorld::new(); - blockchain.set_current_dir_from_workspace("contracts/core/price-aggregator"); - - blockchain.register_contract( - PRICE_AGGREGATOR_PATH_EXPR, - multiversx_price_aggregator_sc::ContractBuilder, - ); - blockchain -} - -#[test] -fn price_agg_submit_test() { - let mut world = world(); - let price_aggregator_whitebox = WhiteboxContract::new( - "sc:price-aggregator", - multiversx_price_aggregator_sc::contract_obj, - ); - let price_aggregator_code = world.code_expression(PRICE_AGGREGATOR_PATH_EXPR); - - let mut oracles = Vec::new(); - for i in 0..NR_ORACLES { - let oracle_address_expr = format!("address::oracle{i}"); - let oracle_address = Address::from_slice(oracle_address_expr.as_bytes()); - oracles.push(oracle_address); - } - - - // let mut oracle_args = MultiValueEncoded::new(); - // for oracle in &oracles { - // oracle_args.push(managed_address!(oracle)); - // } - // sc.init( - // EgldOrEsdtTokenIdentifier::egld(), - // managed_biguint!(STAKE_AMOUNT), - // managed_biguint!(SLASH_AMOUNT), - // SLASH_QUORUM, - // SUBMISSION_COUNT, - // oracle_args, - // ) - - world - .set_state_step( - SetStateStep::new() - .put_account("address:owner", Account::new().nonce(1)) - .new_address("address:owner", 1, "sc:price-aggregator") - .block_timestamp(100) - .put_account( - "address:oracle0", - Account::new().nonce(1).balance(STAKE_AMOUNT), - ), - ) - .sc_deploy( - ScDeployStep::new() - .from("address:owner") - .code(price_aggregator_code) - .argument() - }, - // .whitebox_deploy( - // &price_aggregator_whitebox, - // ScDeployStep::new() - // .from("address:owner") - // .code(price_aggregator_code), - // |sc| { - // let mut oracle_args = MultiValueEncoded::new(); - // for oracle in &oracles { - // oracle_args.push(managed_address!(oracle)); - // } - // sc.init( - // EgldOrEsdtTokenIdentifier::egld(), - // managed_biguint!(STAKE_AMOUNT), - // managed_biguint!(SLASH_AMOUNT), - // SLASH_QUORUM, - // SUBMISSION_COUNT, - // oracle_args, - // ) - // }, - // ) - // .whitebox_call( - // &price_aggregator_whitebox, - // ScCallStep::new().from("address:owner"), - // |sc| { - // sc.set_pair_decimals( - // managed_buffer!(EGLD_TICKER), - // managed_buffer!(USD_TICKER), - // DECIMALS, - // ) - // }, - // ) - // .whitebox_call_check( - // &price_aggregator_whitebox, - // ScCallStep::new().from("address:oracle0"), - // |sc| { - // sc.submit( - // managed_buffer!(EGLD_TICKER), - // managed_buffer!(USD_TICKER), - // 99, - // managed_biguint!(100), - // DECIMALS, - // ) - // }, - // |r| { - // r.assert_user_error("Contract is paused"); - // }, - // ) - // .whitebox_call( - // &price_aggregator_whitebox, - // ScCallStep::new().from("address:owner"), - // |sc| sc.call_unpause_endpoint(), - // ) - // .whitebox_call_check( - // &price_aggregator_whitebox, - // ScCallStep::new().from("address:oracle0"), - // |sc| { - // sc.submit( - // managed_buffer!(EGLD_TICKER), - // managed_buffer!(USD_TICKER), - // 10, - // managed_biguint!(100), - // DECIMALS, - // ) - // }, - // |r| { - // r.assert_user_error("First submission too old"); - // }, - // ) - // .whitebox_call_check( - // &price_aggregator_whitebox, - // ScCallStep::new().from("address:oracle0"), - // |sc| { - // sc.call_unpause_endpoint(); - // sc.submit( - // managed_buffer!(EGLD_TICKER), - // managed_buffer!(USD_TICKER), - // 95, - // managed_biguint!(100), - // DECIMALS, - // ) - // }, - // |r| { - // r.assert_ok(); - // }, - // ); - - // pa_setup - // .b_mock - // .execute_query(&pa_setup.price_agg, |sc| { - // let token_pair = TokenPair { - // from: managed_buffer!(EGLD_TICKER), - // to: managed_buffer!(USD_TICKER), - // }; - // 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(), 1); - // assert_eq!( - // submissions.get(&managed_address!(&oracles[0])).unwrap(), - // managed_biguint!(100) - // ); - - // assert_eq!( - // sc.oracle_status() - // .get(&managed_address!(&oracles[0])) - // .unwrap(), - // OracleStatus { - // total_submissions: 1, - // accepted_submissions: 1 - // } - // ); - // }) - // .assert_ok(); - - // // first oracle submit again - submission not accepted - // pa_setup.submit(&oracles[0], 95, 100).assert_ok(); - - // pa_setup - // .b_mock - // .execute_query(&pa_setup.price_agg, |sc| { - // assert_eq!( - // sc.oracle_status() - // .get(&managed_address!(&oracles[0])) - // .unwrap(), - // OracleStatus { - // total_submissions: 2, - // accepted_submissions: 1 - // } - // ); - // }) - // .assert_ok(); -} diff --git a/contracts/core/price-aggregator/tests/price_aggregator_whitebox_test.rs b/contracts/core/price-aggregator/tests/price_aggregator_whitebox_test.rs index 3737fbf458..64a307cf51 100644 --- a/contracts/core/price-aggregator/tests/price_aggregator_whitebox_test.rs +++ b/contracts/core/price-aggregator/tests/price_aggregator_whitebox_test.rs @@ -1,83 +1,101 @@ -use multiversx_price_aggregator_sc::PriceAggregator; +#[allow(unused_imports)] +use multiversx_price_aggregator_sc::{ + staking::EndpointWrappers as StakingEndpointWrappers, PriceAggregator, +}; use multiversx_sc::types::{Address, EgldOrEsdtTokenIdentifier, MultiValueEncoded}; -use multiversx_sc_modules::pause::EndpointWrappers; -use multiversx_sc_scenario::{managed_address, managed_biguint, managed_buffer, WhiteboxContract}; +use multiversx_sc_modules::pause::EndpointWrappers as PauseEndpointWrappers; +use multiversx_sc_scenario::{ + managed_address, managed_biguint, managed_buffer, scenario_model::*, WhiteboxContract, *, +}; -use multiversx_sc_scenario::{scenario_model::*, *}; - -const PRICE_AGGREGATOR_PATH_EXPR: &str = "file:output/multiversx-price-aggregator-sc.wasm"; - -pub const NR_ORACLES: usize = 4; -pub const SUBMISSION_COUNT: usize = 3; pub const DECIMALS: u8 = 0; -pub static EGLD_TICKER: &[u8] = b"EGLD"; -pub static USD_TICKER: &[u8] = b"USDC"; - -pub const STAKE_AMOUNT: u64 = 20; +pub const EGLD_TICKER: &[u8] = b"EGLD"; +pub const NR_ORACLES: usize = 4; pub const SLASH_AMOUNT: u64 = 10; pub const SLASH_QUORUM: usize = 2; +pub const STAKE_AMOUNT: u64 = 20; +pub const SUBMISSION_COUNT: usize = 3; +pub const USD_TICKER: &[u8] = b"USDC"; + +const OWNER_ADDRESS_EXPR: &str = "address:owner"; +const PRICE_AGGREGATOR_ADDRESS_EXPR: &str = "sc:price-aggregator"; +const PRICE_AGGREGATOR_PATH_EXPR: &str = "file:output/multiversx-price-aggregator-sc.wasm"; fn world() -> ScenarioWorld { let mut blockchain = ScenarioWorld::new(); - blockchain.set_current_dir_from_workspace("contracts/core/price-aggregator"); + blockchain.set_current_dir_from_workspace("contracts/core/price-aggregator"); blockchain.register_contract( PRICE_AGGREGATOR_PATH_EXPR, multiversx_price_aggregator_sc::ContractBuilder, ); + blockchain } +#[ignore = "work in progress"] #[test] -fn price_agg_submit_test() { +fn test_price_aggregator_submit() { let mut world = world(); let price_aggregator_whitebox = WhiteboxContract::new( - "sc:price-aggregator", + PRICE_AGGREGATOR_ADDRESS_EXPR, multiversx_price_aggregator_sc::contract_obj, ); let price_aggregator_code = world.code_expression(PRICE_AGGREGATOR_PATH_EXPR); + let mut set_state_step = SetStateStep::new() + .put_account(OWNER_ADDRESS_EXPR, Account::new().nonce(1)) + .new_address(OWNER_ADDRESS_EXPR, 1, PRICE_AGGREGATOR_ADDRESS_EXPR) + .block_timestamp(100); + let mut oracles = Vec::new(); - for i in 0..NR_ORACLES { + for i in 1..=NR_ORACLES { let oracle_address_expr = format!("address::oracle{i}"); let oracle_address = Address::from_slice(oracle_address_expr.as_bytes()); - oracles.push(oracle_address); + + set_state_step = set_state_step.put_account( + oracle_address_expr.as_str(), + Account::new().nonce(1).balance(STAKE_AMOUNT), + ); + oracles.push((oracle_address_expr, oracle_address)); } + world.set_state_step(set_state_step).whitebox_deploy( + &price_aggregator_whitebox, + ScDeployStep::new() + .from(OWNER_ADDRESS_EXPR) + .code(price_aggregator_code), + |sc| { + let mut oracle_args = MultiValueEncoded::new(); + for (_, oracle_address) in &oracles { + oracle_args.push(managed_address!(oracle_address)); + } + + sc.init( + EgldOrEsdtTokenIdentifier::egld(), + managed_biguint!(STAKE_AMOUNT), + managed_biguint!(SLASH_AMOUNT), + SLASH_QUORUM, + SUBMISSION_COUNT, + oracle_args, + ) + }, + ); + + // for (oracle_address_expr, _) in &oracles { + // world.whitebox_call( + // &price_aggregator_whitebox, + // ScCallStep::new() + // .from(oracle_address_expr.as_str()) + // .egld_value(STAKE_AMOUNT), + // |sc| sc.call_stake(), + // ); + // } + world - .set_state_step( - SetStateStep::new() - .put_account("address:owner", Account::new().nonce(1)) - .new_address("address:owner", 1, "sc:price-aggregator") - .block_timestamp(100) - .put_account( - "address:oracle0", - Account::new().nonce(1).balance(STAKE_AMOUNT), - ), - ) - .whitebox_deploy( - &price_aggregator_whitebox, - ScDeployStep::new() - .from("address:owner") - .code(price_aggregator_code), - |sc| { - let mut oracle_args = MultiValueEncoded::new(); - for oracle in &oracles { - oracle_args.push(managed_address!(oracle)); - } - sc.init( - EgldOrEsdtTokenIdentifier::egld(), - managed_biguint!(STAKE_AMOUNT), - managed_biguint!(SLASH_AMOUNT), - SLASH_QUORUM, - SUBMISSION_COUNT, - oracle_args, - ) - }, - ) .whitebox_call( &price_aggregator_whitebox, - ScCallStep::new().from("address:owner"), + ScCallStep::new().from(OWNER_ADDRESS_EXPR), |sc| { sc.set_pair_decimals( managed_buffer!(EGLD_TICKER), @@ -88,7 +106,9 @@ fn price_agg_submit_test() { ) .whitebox_call_check( &price_aggregator_whitebox, - ScCallStep::new().from("address:oracle0"), + ScCallStep::new() + .from(oracles[0].0.as_str()) + .expect(TxExpect::user_error("str:Contract is paused")), |sc| { sc.submit( managed_buffer!(EGLD_TICKER), @@ -98,18 +118,16 @@ fn price_agg_submit_test() { DECIMALS, ) }, - |r| { - r.assert_user_error("Contract is paused"); - }, + |r| r.assert_user_error("Contract is paused"), ) .whitebox_call( &price_aggregator_whitebox, - ScCallStep::new().from("address:owner"), + ScCallStep::new().from(OWNER_ADDRESS_EXPR), |sc| sc.call_unpause_endpoint(), ) .whitebox_call_check( &price_aggregator_whitebox, - ScCallStep::new().from("address:oracle0"), + ScCallStep::new().from(oracles[0].0.as_str()).no_expect(), |sc| { sc.submit( managed_buffer!(EGLD_TICKER), @@ -119,15 +137,14 @@ fn price_agg_submit_test() { DECIMALS, ) }, - |r| { - r.assert_user_error("First submission too old"); + |_r| { + // r.assert_user_error("First submission too old"); }, ) .whitebox_call_check( &price_aggregator_whitebox, - ScCallStep::new().from("address:oracle0"), + ScCallStep::new().from(oracles[0].0.as_str()).no_expect(), |sc| { - sc.call_unpause_endpoint(); sc.submit( managed_buffer!(EGLD_TICKER), managed_buffer!(USD_TICKER), @@ -136,61 +153,8 @@ fn price_agg_submit_test() { DECIMALS, ) }, - |r| { - r.assert_ok(); + |_r| { + // r.assert_ok(); }, ); - - // pa_setup - // .b_mock - // .execute_query(&pa_setup.price_agg, |sc| { - // let token_pair = TokenPair { - // from: managed_buffer!(EGLD_TICKER), - // to: managed_buffer!(USD_TICKER), - // }; - // 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(), 1); - // assert_eq!( - // submissions.get(&managed_address!(&oracles[0])).unwrap(), - // managed_biguint!(100) - // ); - - // assert_eq!( - // sc.oracle_status() - // .get(&managed_address!(&oracles[0])) - // .unwrap(), - // OracleStatus { - // total_submissions: 1, - // accepted_submissions: 1 - // } - // ); - // }) - // .assert_ok(); - - // // first oracle submit again - submission not accepted - // pa_setup.submit(&oracles[0], 95, 100).assert_ok(); - - // pa_setup - // .b_mock - // .execute_query(&pa_setup.price_agg, |sc| { - // assert_eq!( - // sc.oracle_status() - // .get(&managed_address!(&oracles[0])) - // .unwrap(), - // OracleStatus { - // total_submissions: 2, - // accepted_submissions: 1 - // } - // ); - // }) - // .assert_ok(); } diff --git a/framework/scenario/src/scenario/model/transaction/tx_expect.rs b/framework/scenario/src/scenario/model/transaction/tx_expect.rs index 9a49b6cc51..6f64a471f0 100644 --- a/framework/scenario/src/scenario/model/transaction/tx_expect.rs +++ b/framework/scenario/src/scenario/model/transaction/tx_expect.rs @@ -1,5 +1,4 @@ -use multiversx_chain_vm::tx_mock::result_values_to_string; - +use super::TxResponse; use crate::{ scenario::model::{BytesValue, CheckLogs, CheckValue, CheckValueList, U64Value}, scenario_format::{ @@ -8,8 +7,9 @@ use crate::{ }, scenario_model::Checkable, }; +use multiversx_chain_vm::tx_mock::result_values_to_string; -use super::TxResponse; +const USER_ERROR_CODE: u64 = 4; #[derive(Debug, Clone)] pub struct TxExpect { @@ -57,6 +57,13 @@ impl TxExpect { } } + pub fn user_error(err_msg_expr: E) -> Self + where + BytesValue: From, + { + Self::err(USER_ERROR_CODE, err_msg_expr) + } + pub fn no_result(mut self) -> Self { self.out = CheckValue::Equal(Vec::new()); self.build_from_response = false; From 2e422daf7208114bc958a76b2db803609daffee9 Mon Sep 17 00:00:00 2001 From: Ovidiu Stinga Date: Wed, 26 Jul 2023 19:10:55 +0300 Subject: [PATCH 3/8] first full working test with new whitebox syntax --- .../tests/price_aggregator_whitebox_test.rs | 120 ++++++++++++++---- 1 file changed, 95 insertions(+), 25 deletions(-) diff --git a/contracts/core/price-aggregator/tests/price_aggregator_whitebox_test.rs b/contracts/core/price-aggregator/tests/price_aggregator_whitebox_test.rs index 64a307cf51..907175da7d 100644 --- a/contracts/core/price-aggregator/tests/price_aggregator_whitebox_test.rs +++ b/contracts/core/price-aggregator/tests/price_aggregator_whitebox_test.rs @@ -1,8 +1,9 @@ -#[allow(unused_imports)] use multiversx_price_aggregator_sc::{ - staking::EndpointWrappers as StakingEndpointWrappers, PriceAggregator, + price_aggregator_data::{OracleStatus, TokenPair}, + staking::EndpointWrappers as StakingEndpointWrappers, + PriceAggregator, }; -use multiversx_sc::types::{Address, EgldOrEsdtTokenIdentifier, MultiValueEncoded}; +use multiversx_sc::types::{EgldOrEsdtTokenIdentifier, MultiValueEncoded}; use multiversx_sc_modules::pause::EndpointWrappers as PauseEndpointWrappers; use multiversx_sc_scenario::{ managed_address, managed_biguint, managed_buffer, scenario_model::*, WhiteboxContract, *, @@ -33,10 +34,10 @@ fn world() -> ScenarioWorld { blockchain } -#[ignore = "work in progress"] #[test] fn test_price_aggregator_submit() { let mut world = world(); + let current_timestamp = 100; let price_aggregator_whitebox = WhiteboxContract::new( PRICE_AGGREGATOR_ADDRESS_EXPR, multiversx_price_aggregator_sc::contract_obj, @@ -50,14 +51,14 @@ fn test_price_aggregator_submit() { let mut oracles = Vec::new(); for i in 1..=NR_ORACLES { - let oracle_address_expr = format!("address::oracle{i}"); - let oracle_address = Address::from_slice(oracle_address_expr.as_bytes()); + let oracle_address_expr = format!("address:oracle{i}"); + let oracle_address = AddressValue::from(oracle_address_expr.as_str()); set_state_step = set_state_step.put_account( oracle_address_expr.as_str(), Account::new().nonce(1).balance(STAKE_AMOUNT), ); - oracles.push((oracle_address_expr, oracle_address)); + oracles.push(oracle_address); } world.set_state_step(set_state_step).whitebox_deploy( @@ -67,8 +68,8 @@ fn test_price_aggregator_submit() { .code(price_aggregator_code), |sc| { let mut oracle_args = MultiValueEncoded::new(); - for (_, oracle_address) in &oracles { - oracle_args.push(managed_address!(oracle_address)); + for oracle_address in &oracles { + oracle_args.push(managed_address!(&oracle_address.to_address())); } sc.init( @@ -82,15 +83,15 @@ fn test_price_aggregator_submit() { }, ); - // for (oracle_address_expr, _) in &oracles { - // world.whitebox_call( - // &price_aggregator_whitebox, - // ScCallStep::new() - // .from(oracle_address_expr.as_str()) - // .egld_value(STAKE_AMOUNT), - // |sc| sc.call_stake(), - // ); - // } + for oracle_address in &oracles { + world.whitebox_call( + &price_aggregator_whitebox, + ScCallStep::new() + .from(oracle_address) + .egld_value(STAKE_AMOUNT), + |sc| sc.call_stake(), + ); + } world .whitebox_call( @@ -107,7 +108,7 @@ fn test_price_aggregator_submit() { .whitebox_call_check( &price_aggregator_whitebox, ScCallStep::new() - .from(oracles[0].0.as_str()) + .from(&oracles[0]) .expect(TxExpect::user_error("str:Contract is paused")), |sc| { sc.submit( @@ -127,7 +128,7 @@ fn test_price_aggregator_submit() { ) .whitebox_call_check( &price_aggregator_whitebox, - ScCallStep::new().from(oracles[0].0.as_str()).no_expect(), + ScCallStep::new().from(&oracles[0]).no_expect(), |sc| { sc.submit( managed_buffer!(EGLD_TICKER), @@ -137,13 +138,13 @@ fn test_price_aggregator_submit() { DECIMALS, ) }, - |_r| { - // r.assert_user_error("First submission too old"); + |r| { + r.assert_user_error("First submission too old"); }, ) .whitebox_call_check( &price_aggregator_whitebox, - ScCallStep::new().from(oracles[0].0.as_str()).no_expect(), + ScCallStep::new().from(&oracles[0]), |sc| { sc.submit( managed_buffer!(EGLD_TICKER), @@ -153,8 +154,77 @@ fn test_price_aggregator_submit() { DECIMALS, ) }, - |_r| { - // r.assert_ok(); + |r| { + r.assert_ok(); }, ); + + world.whitebox_query_check( + &price_aggregator_whitebox, + |sc| { + let token_pair = TokenPair { + from: managed_buffer!(EGLD_TICKER), + to: managed_buffer!(USD_TICKER), + }; + 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(), 1); + assert_eq!( + submissions + .get(&managed_address!(&oracles[0].to_address())) + .unwrap(), + managed_biguint!(100) + ); + + assert_eq!( + sc.oracle_status() + .get(&managed_address!(&oracles[0].to_address())) + .unwrap(), + OracleStatus { + total_submissions: 1, + accepted_submissions: 1 + } + ); + }, + |r| r.assert_ok(), + ); + + world.whitebox_call_check( + &price_aggregator_whitebox, + ScCallStep::new().from(&oracles[0]), + |sc| { + sc.submit( + managed_buffer!(EGLD_TICKER), + managed_buffer!(USD_TICKER), + 95, + managed_biguint!(100), + DECIMALS, + ) + }, + |r| r.assert_ok(), + ); + + world.whitebox_query_check( + &price_aggregator_whitebox, + |sc| { + assert_eq!( + sc.oracle_status() + .get(&managed_address!(&oracles[0].to_address())) + .unwrap(), + OracleStatus { + total_submissions: 2, + accepted_submissions: 1 + } + ); + }, + |r| r.assert_ok(), + ); } From ed02c5999b72760ec0449113f0f6217e13cd3343 Mon Sep 17 00:00:00 2001 From: Ovidiu Stinga Date: Wed, 26 Jul 2023 19:30:44 +0300 Subject: [PATCH 4/8] migrate another test --- .../tests/price_aggregator_whitebox_test.rs | 378 +++++++++++++----- 1 file changed, 268 insertions(+), 110 deletions(-) diff --git a/contracts/core/price-aggregator/tests/price_aggregator_whitebox_test.rs b/contracts/core/price-aggregator/tests/price_aggregator_whitebox_test.rs index 907175da7d..78ac2bbc17 100644 --- a/contracts/core/price-aggregator/tests/price_aggregator_whitebox_test.rs +++ b/contracts/core/price-aggregator/tests/price_aggregator_whitebox_test.rs @@ -1,5 +1,5 @@ use multiversx_price_aggregator_sc::{ - price_aggregator_data::{OracleStatus, TokenPair}, + price_aggregator_data::{OracleStatus, TimestampedPrice, TokenPair}, staking::EndpointWrappers as StakingEndpointWrappers, PriceAggregator, }; @@ -36,6 +36,7 @@ fn world() -> ScenarioWorld { #[test] fn test_price_aggregator_submit() { + // setup let mut world = world(); let current_timestamp = 100; let price_aggregator_whitebox = WhiteboxContract::new( @@ -61,6 +62,7 @@ fn test_price_aggregator_submit() { oracles.push(oracle_address); } + // init price aggregator world.set_state_step(set_state_step).whitebox_deploy( &price_aggregator_whitebox, ScDeployStep::new() @@ -93,111 +95,221 @@ fn test_price_aggregator_submit() { ); } - world - .whitebox_call( - &price_aggregator_whitebox, - ScCallStep::new().from(OWNER_ADDRESS_EXPR), - |sc| { - sc.set_pair_decimals( - managed_buffer!(EGLD_TICKER), - managed_buffer!(USD_TICKER), - DECIMALS, - ) - }, - ) - .whitebox_call_check( + // configure the number of decimals + world.whitebox_call( + &price_aggregator_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.set_pair_decimals( + managed_buffer!(EGLD_TICKER), + managed_buffer!(USD_TICKER), + DECIMALS, + ) + }, + ); + + // try submit while paused + world.whitebox_call_check( + &price_aggregator_whitebox, + ScCallStep::new() + .from(&oracles[0]) + .expect(TxExpect::user_error("str:Contract is paused")), + |sc| { + sc.submit( + managed_buffer!(EGLD_TICKER), + managed_buffer!(USD_TICKER), + 99, + managed_biguint!(100), + DECIMALS, + ) + }, + |r| r.assert_user_error("Contract is paused"), + ); + + // unpause + world.whitebox_call( + &price_aggregator_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| sc.call_unpause_endpoint(), + ); + + // submit first timestamp too old + world.whitebox_call_check( + &price_aggregator_whitebox, + ScCallStep::new().from(&oracles[0]).no_expect(), + |sc| { + sc.submit( + managed_buffer!(EGLD_TICKER), + managed_buffer!(USD_TICKER), + 10, + managed_biguint!(100), + DECIMALS, + ) + }, + |r| { + r.assert_user_error("First submission too old"); + }, + ); + + // submit ok + world.whitebox_call( + &price_aggregator_whitebox, + ScCallStep::new().from(&oracles[0]), + |sc| { + sc.submit( + managed_buffer!(EGLD_TICKER), + managed_buffer!(USD_TICKER), + 95, + managed_biguint!(100), + DECIMALS, + ) + }, + ); + + world.whitebox_query(&price_aggregator_whitebox, |sc| { + let token_pair = TokenPair { + from: managed_buffer!(EGLD_TICKER), + to: managed_buffer!(USD_TICKER), + }; + 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(), 1); + assert_eq!( + submissions + .get(&managed_address!(&oracles[0].to_address())) + .unwrap(), + managed_biguint!(100) + ); + + assert_eq!( + sc.oracle_status() + .get(&managed_address!(&oracles[0].to_address())) + .unwrap(), + OracleStatus { + total_submissions: 1, + accepted_submissions: 1 + } + ); + }); + + // first oracle submit again - submission not accepted + world.whitebox_call( + &price_aggregator_whitebox, + ScCallStep::new().from(&oracles[0]), + |sc| { + sc.submit( + managed_buffer!(EGLD_TICKER), + managed_buffer!(USD_TICKER), + 95, + managed_biguint!(100), + DECIMALS, + ) + }, + ); + + world.whitebox_query(&price_aggregator_whitebox, |sc| { + assert_eq!( + sc.oracle_status() + .get(&managed_address!(&oracles[0].to_address())) + .unwrap(), + OracleStatus { + total_submissions: 2, + accepted_submissions: 1 + } + ); + }); +} + +#[test] +fn test_price_aggregator_submit_round_ok() { + // setup + let mut world = world(); + let price_aggregator_whitebox = WhiteboxContract::new( + PRICE_AGGREGATOR_ADDRESS_EXPR, + multiversx_price_aggregator_sc::contract_obj, + ); + let price_aggregator_code = world.code_expression(PRICE_AGGREGATOR_PATH_EXPR); + + let mut set_state_step = SetStateStep::new() + .put_account(OWNER_ADDRESS_EXPR, Account::new().nonce(1)) + .new_address(OWNER_ADDRESS_EXPR, 1, PRICE_AGGREGATOR_ADDRESS_EXPR) + .block_timestamp(100); + + let mut oracles = Vec::new(); + for i in 1..=NR_ORACLES { + let oracle_address_expr = format!("address:oracle{i}"); + let oracle_address = AddressValue::from(oracle_address_expr.as_str()); + + set_state_step = set_state_step.put_account( + oracle_address_expr.as_str(), + Account::new().nonce(1).balance(STAKE_AMOUNT), + ); + oracles.push(oracle_address); + } + + // init price aggregator + world.set_state_step(set_state_step).whitebox_deploy( + &price_aggregator_whitebox, + ScDeployStep::new() + .from(OWNER_ADDRESS_EXPR) + .code(price_aggregator_code), + |sc| { + let mut oracle_args = MultiValueEncoded::new(); + for oracle_address in &oracles { + oracle_args.push(managed_address!(&oracle_address.to_address())); + } + + sc.init( + EgldOrEsdtTokenIdentifier::egld(), + managed_biguint!(STAKE_AMOUNT), + managed_biguint!(SLASH_AMOUNT), + SLASH_QUORUM, + SUBMISSION_COUNT, + oracle_args, + ) + }, + ); + + for oracle_address in &oracles { + world.whitebox_call( &price_aggregator_whitebox, ScCallStep::new() - .from(&oracles[0]) - .expect(TxExpect::user_error("str:Contract is paused")), - |sc| { - sc.submit( - managed_buffer!(EGLD_TICKER), - managed_buffer!(USD_TICKER), - 99, - managed_biguint!(100), - DECIMALS, - ) - }, - |r| r.assert_user_error("Contract is paused"), - ) - .whitebox_call( - &price_aggregator_whitebox, - ScCallStep::new().from(OWNER_ADDRESS_EXPR), - |sc| sc.call_unpause_endpoint(), - ) - .whitebox_call_check( - &price_aggregator_whitebox, - ScCallStep::new().from(&oracles[0]).no_expect(), - |sc| { - sc.submit( - managed_buffer!(EGLD_TICKER), - managed_buffer!(USD_TICKER), - 10, - managed_biguint!(100), - DECIMALS, - ) - }, - |r| { - r.assert_user_error("First submission too old"); - }, - ) - .whitebox_call_check( - &price_aggregator_whitebox, - ScCallStep::new().from(&oracles[0]), - |sc| { - sc.submit( - managed_buffer!(EGLD_TICKER), - managed_buffer!(USD_TICKER), - 95, - managed_biguint!(100), - DECIMALS, - ) - }, - |r| { - r.assert_ok(); - }, + .from(oracle_address) + .egld_value(STAKE_AMOUNT), + |sc| sc.call_stake(), ); + } - world.whitebox_query_check( + // configure the number of decimals + world.whitebox_call( &price_aggregator_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), |sc| { - let token_pair = TokenPair { - from: managed_buffer!(EGLD_TICKER), - to: managed_buffer!(USD_TICKER), - }; - 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(), 1); - assert_eq!( - submissions - .get(&managed_address!(&oracles[0].to_address())) - .unwrap(), - managed_biguint!(100) - ); - - assert_eq!( - sc.oracle_status() - .get(&managed_address!(&oracles[0].to_address())) - .unwrap(), - OracleStatus { - total_submissions: 1, - accepted_submissions: 1 - } - ); + sc.set_pair_decimals( + managed_buffer!(EGLD_TICKER), + managed_buffer!(USD_TICKER), + DECIMALS, + ) }, - |r| r.assert_ok(), ); - world.whitebox_call_check( + // unpause + world.whitebox_call( + &price_aggregator_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| sc.call_unpause_endpoint(), + ); + + // submit first + world.whitebox_call( &price_aggregator_whitebox, ScCallStep::new().from(&oracles[0]), |sc| { @@ -205,26 +317,72 @@ fn test_price_aggregator_submit() { managed_buffer!(EGLD_TICKER), managed_buffer!(USD_TICKER), 95, - managed_biguint!(100), + managed_biguint!(10_000), + DECIMALS, + ) + }, + ); + + let current_timestamp = 110; + world.set_state_step(SetStateStep::new().block_timestamp(current_timestamp)); + + // submit second + world.whitebox_call( + &price_aggregator_whitebox, + ScCallStep::new().from(&oracles[1]), + |sc| { + sc.submit( + managed_buffer!(EGLD_TICKER), + managed_buffer!(USD_TICKER), + 101, + managed_biguint!(11_000), DECIMALS, ) }, - |r| r.assert_ok(), ); - world.whitebox_query_check( + // submit third + world.whitebox_call( &price_aggregator_whitebox, + ScCallStep::new().from(&oracles[2]), |sc| { - assert_eq!( - sc.oracle_status() - .get(&managed_address!(&oracles[0].to_address())) - .unwrap(), - OracleStatus { - total_submissions: 2, - accepted_submissions: 1 - } - ); + sc.submit( + managed_buffer!(EGLD_TICKER), + managed_buffer!(USD_TICKER), + 105, + managed_biguint!(12_000), + DECIMALS, + ) }, - |r| r.assert_ok(), ); + + world.whitebox_query(&price_aggregator_whitebox, |sc| { + let result = sc + .latest_price_feed(managed_buffer!(EGLD_TICKER), managed_buffer!(USD_TICKER)) + .unwrap(); + + let (round_id, from, to, timestamp, price, decimals) = result.into_tuple(); + assert_eq!(round_id, 1); + assert_eq!(from, managed_buffer!(EGLD_TICKER)); + assert_eq!(to, managed_buffer!(USD_TICKER)); + assert_eq!(timestamp, current_timestamp); + assert_eq!(price, managed_biguint!(11_000)); + assert_eq!(decimals, DECIMALS); + + // submissions are deleted after round is created + let token_pair = TokenPair { from, to }; + let submissions = sc.submissions().get(&token_pair).unwrap(); + assert_eq!(submissions.len(), 0); + + let rounds = sc.rounds().get(&token_pair).unwrap(); + assert_eq!(rounds.len(), 1); + assert_eq!( + rounds.get(1), + TimestampedPrice { + timestamp, + price, + decimals + } + ); + }); } From 1291bea8b243fe23f1ba01f0af8a7d1fed0bf1d2 Mon Sep 17 00:00:00 2001 From: Ovidiu Stinga Date: Wed, 26 Jul 2023 19:35:39 +0300 Subject: [PATCH 5/8] and another test --- .../tests/price_aggregator_whitebox_test.rs | 134 +++++++++++++++++- 1 file changed, 133 insertions(+), 1 deletion(-) diff --git a/contracts/core/price-aggregator/tests/price_aggregator_whitebox_test.rs b/contracts/core/price-aggregator/tests/price_aggregator_whitebox_test.rs index 78ac2bbc17..a9f8772abb 100644 --- a/contracts/core/price-aggregator/tests/price_aggregator_whitebox_test.rs +++ b/contracts/core/price-aggregator/tests/price_aggregator_whitebox_test.rs @@ -1,7 +1,7 @@ use multiversx_price_aggregator_sc::{ price_aggregator_data::{OracleStatus, TimestampedPrice, TokenPair}, staking::EndpointWrappers as StakingEndpointWrappers, - PriceAggregator, + PriceAggregator, MAX_ROUND_DURATION_SECONDS, }; use multiversx_sc::types::{EgldOrEsdtTokenIdentifier, MultiValueEncoded}; use multiversx_sc_modules::pause::EndpointWrappers as PauseEndpointWrappers; @@ -34,6 +34,138 @@ fn world() -> ScenarioWorld { blockchain } +#[test] +fn test_price_aggregator_discarded_round() { + // setup + let mut world = world(); + let price_aggregator_whitebox = WhiteboxContract::new( + PRICE_AGGREGATOR_ADDRESS_EXPR, + multiversx_price_aggregator_sc::contract_obj, + ); + let price_aggregator_code = world.code_expression(PRICE_AGGREGATOR_PATH_EXPR); + + let mut set_state_step = SetStateStep::new() + .put_account(OWNER_ADDRESS_EXPR, Account::new().nonce(1)) + .new_address(OWNER_ADDRESS_EXPR, 1, PRICE_AGGREGATOR_ADDRESS_EXPR) + .block_timestamp(100); + + let mut oracles = Vec::new(); + for i in 1..=NR_ORACLES { + let oracle_address_expr = format!("address:oracle{i}"); + let oracle_address = AddressValue::from(oracle_address_expr.as_str()); + + set_state_step = set_state_step.put_account( + oracle_address_expr.as_str(), + Account::new().nonce(1).balance(STAKE_AMOUNT), + ); + oracles.push(oracle_address); + } + + // init price aggregator + world.set_state_step(set_state_step).whitebox_deploy( + &price_aggregator_whitebox, + ScDeployStep::new() + .from(OWNER_ADDRESS_EXPR) + .code(price_aggregator_code), + |sc| { + let mut oracle_args = MultiValueEncoded::new(); + for oracle_address in &oracles { + oracle_args.push(managed_address!(&oracle_address.to_address())); + } + + sc.init( + EgldOrEsdtTokenIdentifier::egld(), + managed_biguint!(STAKE_AMOUNT), + managed_biguint!(SLASH_AMOUNT), + SLASH_QUORUM, + SUBMISSION_COUNT, + oracle_args, + ) + }, + ); + + for oracle_address in &oracles { + world.whitebox_call( + &price_aggregator_whitebox, + ScCallStep::new() + .from(oracle_address) + .egld_value(STAKE_AMOUNT), + |sc| sc.call_stake(), + ); + } + + // configure the number of decimals + world.whitebox_call( + &price_aggregator_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.set_pair_decimals( + managed_buffer!(EGLD_TICKER), + managed_buffer!(USD_TICKER), + DECIMALS, + ) + }, + ); + + // unpause + world.whitebox_call( + &price_aggregator_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| sc.call_unpause_endpoint(), + ); + + // submit first + world.whitebox_call( + &price_aggregator_whitebox, + ScCallStep::new().from(&oracles[0]), + |sc| { + sc.submit( + managed_buffer!(EGLD_TICKER), + managed_buffer!(USD_TICKER), + 95, + managed_biguint!(10_000), + DECIMALS, + ) + }, + ); + + let current_timestamp = 100 + MAX_ROUND_DURATION_SECONDS + 1; + world.set_state_step(SetStateStep::new().block_timestamp(current_timestamp)); + + // submit second - this will discard the previous submission + world.whitebox_call( + &price_aggregator_whitebox, + ScCallStep::new().from(&oracles[1]), + |sc| { + sc.submit( + managed_buffer!(EGLD_TICKER), + managed_buffer!(USD_TICKER), + current_timestamp - 1, + managed_biguint!(11_000), + DECIMALS, + ) + }, + ); + + world.whitebox_query(&price_aggregator_whitebox, |sc| { + let token_pair = TokenPair { + from: managed_buffer!(EGLD_TICKER), + to: managed_buffer!(USD_TICKER), + }; + let submissions = sc.submissions().get(&token_pair).unwrap(); + assert_eq!(submissions.len(), 1); + assert_eq!( + submissions + .get(&managed_address!(&oracles[1].to_address())) + .unwrap(), + managed_biguint!(11_000) + ); + }); +} + +#[test] +fn test_price_aggregator_slashing() {} + #[test] fn test_price_aggregator_submit() { // setup From f7ce64c27130528cc0c66a65771647dbc6840511 Mon Sep 17 00:00:00 2001 From: Ovidiu Stinga Date: Wed, 26 Jul 2023 20:11:59 +0300 Subject: [PATCH 6/8] fully migrate price aggregation with nnew whitebox syntax --- .../tests/price_agg_setup/mod.rs | 121 ----- .../price-aggregator/tests/price_agg_tests.rs | 222 --------- .../tests/price_aggregator_whitebox_test.rs | 439 ++++++++---------- 3 files changed, 200 insertions(+), 582 deletions(-) delete mode 100644 contracts/core/price-aggregator/tests/price_agg_setup/mod.rs delete mode 100644 contracts/core/price-aggregator/tests/price_agg_tests.rs diff --git a/contracts/core/price-aggregator/tests/price_agg_setup/mod.rs b/contracts/core/price-aggregator/tests/price_agg_setup/mod.rs deleted file mode 100644 index 819baa5fc4..0000000000 --- a/contracts/core/price-aggregator/tests/price_agg_setup/mod.rs +++ /dev/null @@ -1,121 +0,0 @@ -#![allow(deprecated)] // TODO: migrate tests - -use multiversx_price_aggregator_sc::{staking::StakingModule, PriceAggregator}; -use multiversx_sc::types::{Address, EgldOrEsdtTokenIdentifier, MultiValueEncoded}; -use multiversx_sc_modules::pause::PauseModule; -use multiversx_sc_scenario::{ - managed_address, managed_biguint, managed_buffer, rust_biguint, - testing_framework::{BlockchainStateWrapper, ContractObjWrapper, TxResult}, - DebugApi, -}; - -pub const NR_ORACLES: usize = 4; -pub const SUBMISSION_COUNT: usize = 3; -pub const DECIMALS: u8 = 0; -pub static EGLD_TICKER: &[u8] = b"EGLD"; -pub static USD_TICKER: &[u8] = b"USDC"; - -pub const STAKE_AMOUNT: u64 = 20; -pub const SLASH_AMOUNT: u64 = 10; -pub const SLASH_QUORUM: usize = 2; - -pub struct PriceAggSetup -where - PriceAggObjBuilder: - 'static + Copy + Fn() -> multiversx_price_aggregator_sc::ContractObj, -{ - pub b_mock: BlockchainStateWrapper, - pub owner: Address, - pub oracles: Vec
, - pub price_agg: ContractObjWrapper< - multiversx_price_aggregator_sc::ContractObj, - PriceAggObjBuilder, - >, -} - -impl PriceAggSetup -where - PriceAggObjBuilder: - 'static + Copy + Fn() -> multiversx_price_aggregator_sc::ContractObj, -{ - pub fn new(builder: PriceAggObjBuilder) -> Self { - let rust_zero = rust_biguint!(0); - let mut b_mock = BlockchainStateWrapper::new(); - let owner = b_mock.create_user_account(&rust_zero); - - let mut oracles = Vec::new(); - for _ in 0..NR_ORACLES { - let oracle = b_mock.create_user_account(&rust_biguint!(STAKE_AMOUNT)); - oracles.push(oracle); - } - - let price_agg = - b_mock.create_sc_account(&rust_zero, Some(&owner), builder, "price_agg_path"); - - let current_timestamp = 100; - b_mock.set_block_timestamp(current_timestamp); - - // init price aggregator - b_mock - .execute_tx(&owner, &price_agg, &rust_zero, |sc| { - let mut oracle_args = MultiValueEncoded::new(); - for oracle in &oracles { - oracle_args.push(managed_address!(oracle)); - } - - sc.init( - EgldOrEsdtTokenIdentifier::egld(), - managed_biguint!(STAKE_AMOUNT), - managed_biguint!(SLASH_AMOUNT), - SLASH_QUORUM, - SUBMISSION_COUNT, - oracle_args, - ); - }) - .assert_ok(); - - for oracle in &oracles { - b_mock - .execute_tx(oracle, &price_agg, &rust_biguint!(STAKE_AMOUNT), |sc| { - sc.stake(); - }) - .assert_ok(); - } - - Self { - b_mock, - oracles, - owner, - price_agg, - } - } - - pub fn set_pair_decimals(&mut self, from: &[u8], to: &[u8], decimals: u8) { - self.b_mock - .execute_tx(&self.owner, &self.price_agg, &rust_biguint!(0), |sc| { - sc.set_pair_decimals(managed_buffer!(from), managed_buffer!(to), decimals); - }) - .assert_ok(); - } - - pub fn unpause(&mut self) { - self.b_mock - .execute_tx(&self.owner, &self.price_agg, &rust_biguint!(0), |sc| { - sc.unpause_endpoint(); - }) - .assert_ok(); - } - - pub fn submit(&mut self, oracle: &Address, timestamp: u64, price: u64) -> TxResult { - self.b_mock - .execute_tx(oracle, &self.price_agg, &rust_biguint!(0), |sc| { - sc.submit( - managed_buffer!(EGLD_TICKER), - managed_buffer!(USD_TICKER), - timestamp, - managed_biguint!(price), - DECIMALS, - ); - }) - } -} diff --git a/contracts/core/price-aggregator/tests/price_agg_tests.rs b/contracts/core/price-aggregator/tests/price_agg_tests.rs deleted file mode 100644 index 3a40450903..0000000000 --- a/contracts/core/price-aggregator/tests/price_agg_tests.rs +++ /dev/null @@ -1,222 +0,0 @@ -#![allow(deprecated)] // TODO: migrate tests - -use multiversx_price_aggregator_sc::{ - price_aggregator_data::{OracleStatus, TimestampedPrice, TokenPair}, - staking::StakingModule, - PriceAggregator, MAX_ROUND_DURATION_SECONDS, -}; -use multiversx_sc_scenario::{managed_address, managed_biguint, managed_buffer, rust_biguint}; - -mod price_agg_setup; -use price_agg_setup::*; - -#[test] -fn price_agg_submit_test() { - let mut pa_setup = PriceAggSetup::new(multiversx_price_aggregator_sc::contract_obj); - let current_timestamp = 100; - let oracles = pa_setup.oracles.clone(); - - // configure the number of decimals - pa_setup.set_pair_decimals(EGLD_TICKER, USD_TICKER, DECIMALS); - - // try submit while paused - pa_setup - .submit(&oracles[0], 99, 100) - .assert_user_error("Contract is paused"); - - // unpause - pa_setup.unpause(); - - // submit first timestamp too old - pa_setup - .submit(&oracles[0], 10, 100) - .assert_user_error("First submission too old"); - - // submit ok - pa_setup.submit(&oracles[0], 95, 100).assert_ok(); - - pa_setup - .b_mock - .execute_query(&pa_setup.price_agg, |sc| { - let token_pair = TokenPair { - from: managed_buffer!(EGLD_TICKER), - to: managed_buffer!(USD_TICKER), - }; - 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(), 1); - assert_eq!( - submissions.get(&managed_address!(&oracles[0])).unwrap(), - managed_biguint!(100) - ); - - assert_eq!( - sc.oracle_status() - .get(&managed_address!(&oracles[0])) - .unwrap(), - OracleStatus { - total_submissions: 1, - accepted_submissions: 1 - } - ); - }) - .assert_ok(); - - // first oracle submit again - submission not accepted - pa_setup.submit(&oracles[0], 95, 100).assert_ok(); - - pa_setup - .b_mock - .execute_query(&pa_setup.price_agg, |sc| { - assert_eq!( - sc.oracle_status() - .get(&managed_address!(&oracles[0])) - .unwrap(), - OracleStatus { - total_submissions: 2, - accepted_submissions: 1 - } - ); - }) - .assert_ok(); -} - -#[test] -fn price_agg_submit_round_ok_test() { - let mut pa_setup = PriceAggSetup::new(multiversx_price_aggregator_sc::contract_obj); - let oracles = pa_setup.oracles.clone(); - - // configure the number of decimals - pa_setup.set_pair_decimals(EGLD_TICKER, USD_TICKER, DECIMALS); - - // unpause - pa_setup.unpause(); - - // submit first - pa_setup.submit(&oracles[0], 95, 10_000).assert_ok(); - - let current_timestamp = 110; - pa_setup.b_mock.set_block_timestamp(current_timestamp); - - // submit second - pa_setup.submit(&oracles[1], 101, 11_000).assert_ok(); - - // submit third - pa_setup.submit(&oracles[2], 105, 12_000).assert_ok(); - - pa_setup - .b_mock - .execute_query(&pa_setup.price_agg, |sc| { - let result = sc - .latest_price_feed(managed_buffer!(EGLD_TICKER), managed_buffer!(USD_TICKER)) - .unwrap(); - - let (round_id, from, to, timestamp, price, decimals) = result.into_tuple(); - assert_eq!(round_id, 1); - assert_eq!(from, managed_buffer!(EGLD_TICKER)); - assert_eq!(to, managed_buffer!(USD_TICKER)); - assert_eq!(timestamp, current_timestamp); - assert_eq!(price, managed_biguint!(11_000)); - assert_eq!(decimals, DECIMALS); - - // submissions are deleted after round is created - let token_pair = TokenPair { from, to }; - let submissions = sc.submissions().get(&token_pair).unwrap(); - assert_eq!(submissions.len(), 0); - - let rounds = sc.rounds().get(&token_pair).unwrap(); - assert_eq!(rounds.len(), 1); - assert_eq!( - rounds.get(1), - TimestampedPrice { - timestamp, - price, - decimals - } - ); - }) - .assert_ok(); -} - -#[test] -fn price_agg_discarded_round_test() { - let mut pa_setup = PriceAggSetup::new(multiversx_price_aggregator_sc::contract_obj); - let oracles = pa_setup.oracles.clone(); - - // configure the number of decimals - pa_setup.set_pair_decimals(EGLD_TICKER, USD_TICKER, DECIMALS); - - // unpause - pa_setup.unpause(); - - // submit first - pa_setup.submit(&oracles[0], 95, 10_000).assert_ok(); - - let current_timestamp = 100 + MAX_ROUND_DURATION_SECONDS + 1; - pa_setup.b_mock.set_block_timestamp(current_timestamp); - - // submit second - this will discard the previous submission - pa_setup - .submit(&oracles[1], current_timestamp - 1, 11_000) - .assert_ok(); - - pa_setup - .b_mock - .execute_query(&pa_setup.price_agg, |sc| { - let token_pair = TokenPair { - from: managed_buffer!(EGLD_TICKER), - to: managed_buffer!(USD_TICKER), - }; - let submissions = sc.submissions().get(&token_pair).unwrap(); - assert_eq!(submissions.len(), 1); - assert_eq!( - submissions.get(&managed_address!(&oracles[1])).unwrap(), - managed_biguint!(11_000) - ); - }) - .assert_ok(); -} - -#[test] -fn price_agg_slashing_test() { - let rust_zero = rust_biguint!(0); - let mut pa_setup = PriceAggSetup::new(multiversx_price_aggregator_sc::contract_obj); - let oracles = pa_setup.oracles.clone(); - - // unpause - pa_setup.unpause(); - - pa_setup - .b_mock - .execute_tx(&oracles[0], &pa_setup.price_agg, &rust_zero, |sc| { - sc.vote_slash_member(managed_address!(&oracles[1])); - }) - .assert_ok(); - - pa_setup - .b_mock - .execute_tx(&oracles[2], &pa_setup.price_agg, &rust_zero, |sc| { - sc.vote_slash_member(managed_address!(&oracles[1])); - }) - .assert_ok(); - - pa_setup - .b_mock - .execute_tx(&oracles[0], &pa_setup.price_agg, &rust_zero, |sc| { - sc.slash_member(managed_address!(&oracles[1])); - }) - .assert_ok(); - - // oracle 1 try submit after slashing - pa_setup - .submit(&oracles[1], 95, 10_000) - .assert_user_error("only oracles allowed"); -} diff --git a/contracts/core/price-aggregator/tests/price_aggregator_whitebox_test.rs b/contracts/core/price-aggregator/tests/price_aggregator_whitebox_test.rs index a9f8772abb..a2ab56a22a 100644 --- a/contracts/core/price-aggregator/tests/price_aggregator_whitebox_test.rs +++ b/contracts/core/price-aggregator/tests/price_aggregator_whitebox_test.rs @@ -34,198 +34,13 @@ fn world() -> ScenarioWorld { blockchain } -#[test] -fn test_price_aggregator_discarded_round() { - // setup - let mut world = world(); - let price_aggregator_whitebox = WhiteboxContract::new( - PRICE_AGGREGATOR_ADDRESS_EXPR, - multiversx_price_aggregator_sc::contract_obj, - ); - let price_aggregator_code = world.code_expression(PRICE_AGGREGATOR_PATH_EXPR); - - let mut set_state_step = SetStateStep::new() - .put_account(OWNER_ADDRESS_EXPR, Account::new().nonce(1)) - .new_address(OWNER_ADDRESS_EXPR, 1, PRICE_AGGREGATOR_ADDRESS_EXPR) - .block_timestamp(100); - - let mut oracles = Vec::new(); - for i in 1..=NR_ORACLES { - let oracle_address_expr = format!("address:oracle{i}"); - let oracle_address = AddressValue::from(oracle_address_expr.as_str()); - - set_state_step = set_state_step.put_account( - oracle_address_expr.as_str(), - Account::new().nonce(1).balance(STAKE_AMOUNT), - ); - oracles.push(oracle_address); - } - - // init price aggregator - world.set_state_step(set_state_step).whitebox_deploy( - &price_aggregator_whitebox, - ScDeployStep::new() - .from(OWNER_ADDRESS_EXPR) - .code(price_aggregator_code), - |sc| { - let mut oracle_args = MultiValueEncoded::new(); - for oracle_address in &oracles { - oracle_args.push(managed_address!(&oracle_address.to_address())); - } - - sc.init( - EgldOrEsdtTokenIdentifier::egld(), - managed_biguint!(STAKE_AMOUNT), - managed_biguint!(SLASH_AMOUNT), - SLASH_QUORUM, - SUBMISSION_COUNT, - oracle_args, - ) - }, - ); - - for oracle_address in &oracles { - world.whitebox_call( - &price_aggregator_whitebox, - ScCallStep::new() - .from(oracle_address) - .egld_value(STAKE_AMOUNT), - |sc| sc.call_stake(), - ); - } - - // configure the number of decimals - world.whitebox_call( - &price_aggregator_whitebox, - ScCallStep::new().from(OWNER_ADDRESS_EXPR), - |sc| { - sc.set_pair_decimals( - managed_buffer!(EGLD_TICKER), - managed_buffer!(USD_TICKER), - DECIMALS, - ) - }, - ); - - // unpause - world.whitebox_call( - &price_aggregator_whitebox, - ScCallStep::new().from(OWNER_ADDRESS_EXPR), - |sc| sc.call_unpause_endpoint(), - ); - - // submit first - world.whitebox_call( - &price_aggregator_whitebox, - ScCallStep::new().from(&oracles[0]), - |sc| { - sc.submit( - managed_buffer!(EGLD_TICKER), - managed_buffer!(USD_TICKER), - 95, - managed_biguint!(10_000), - DECIMALS, - ) - }, - ); - - let current_timestamp = 100 + MAX_ROUND_DURATION_SECONDS + 1; - world.set_state_step(SetStateStep::new().block_timestamp(current_timestamp)); - - // submit second - this will discard the previous submission - world.whitebox_call( - &price_aggregator_whitebox, - ScCallStep::new().from(&oracles[1]), - |sc| { - sc.submit( - managed_buffer!(EGLD_TICKER), - managed_buffer!(USD_TICKER), - current_timestamp - 1, - managed_biguint!(11_000), - DECIMALS, - ) - }, - ); - - world.whitebox_query(&price_aggregator_whitebox, |sc| { - let token_pair = TokenPair { - from: managed_buffer!(EGLD_TICKER), - to: managed_buffer!(USD_TICKER), - }; - let submissions = sc.submissions().get(&token_pair).unwrap(); - assert_eq!(submissions.len(), 1); - assert_eq!( - submissions - .get(&managed_address!(&oracles[1].to_address())) - .unwrap(), - managed_biguint!(11_000) - ); - }); -} - -#[test] -fn test_price_aggregator_slashing() {} - #[test] fn test_price_aggregator_submit() { - // setup - let mut world = world(); - let current_timestamp = 100; + let (mut world, oracles) = setup(); let price_aggregator_whitebox = WhiteboxContract::new( PRICE_AGGREGATOR_ADDRESS_EXPR, multiversx_price_aggregator_sc::contract_obj, ); - let price_aggregator_code = world.code_expression(PRICE_AGGREGATOR_PATH_EXPR); - - let mut set_state_step = SetStateStep::new() - .put_account(OWNER_ADDRESS_EXPR, Account::new().nonce(1)) - .new_address(OWNER_ADDRESS_EXPR, 1, PRICE_AGGREGATOR_ADDRESS_EXPR) - .block_timestamp(100); - - let mut oracles = Vec::new(); - for i in 1..=NR_ORACLES { - let oracle_address_expr = format!("address:oracle{i}"); - let oracle_address = AddressValue::from(oracle_address_expr.as_str()); - - set_state_step = set_state_step.put_account( - oracle_address_expr.as_str(), - Account::new().nonce(1).balance(STAKE_AMOUNT), - ); - oracles.push(oracle_address); - } - - // init price aggregator - world.set_state_step(set_state_step).whitebox_deploy( - &price_aggregator_whitebox, - ScDeployStep::new() - .from(OWNER_ADDRESS_EXPR) - .code(price_aggregator_code), - |sc| { - let mut oracle_args = MultiValueEncoded::new(); - for oracle_address in &oracles { - oracle_args.push(managed_address!(&oracle_address.to_address())); - } - - sc.init( - EgldOrEsdtTokenIdentifier::egld(), - managed_biguint!(STAKE_AMOUNT), - managed_biguint!(SLASH_AMOUNT), - SLASH_QUORUM, - SUBMISSION_COUNT, - oracle_args, - ) - }, - ); - - for oracle_address in &oracles { - world.whitebox_call( - &price_aggregator_whitebox, - ScCallStep::new() - .from(oracle_address) - .egld_value(STAKE_AMOUNT), - |sc| sc.call_stake(), - ); - } // configure the number of decimals world.whitebox_call( @@ -298,6 +113,7 @@ fn test_price_aggregator_submit() { }, ); + let current_timestamp = 100; world.whitebox_query(&price_aggregator_whitebox, |sc| { let token_pair = TokenPair { from: managed_buffer!(EGLD_TICKER), @@ -362,63 +178,11 @@ fn test_price_aggregator_submit() { #[test] fn test_price_aggregator_submit_round_ok() { - // setup - let mut world = world(); + let (mut world, oracles) = setup(); let price_aggregator_whitebox = WhiteboxContract::new( PRICE_AGGREGATOR_ADDRESS_EXPR, multiversx_price_aggregator_sc::contract_obj, ); - let price_aggregator_code = world.code_expression(PRICE_AGGREGATOR_PATH_EXPR); - - let mut set_state_step = SetStateStep::new() - .put_account(OWNER_ADDRESS_EXPR, Account::new().nonce(1)) - .new_address(OWNER_ADDRESS_EXPR, 1, PRICE_AGGREGATOR_ADDRESS_EXPR) - .block_timestamp(100); - - let mut oracles = Vec::new(); - for i in 1..=NR_ORACLES { - let oracle_address_expr = format!("address:oracle{i}"); - let oracle_address = AddressValue::from(oracle_address_expr.as_str()); - - set_state_step = set_state_step.put_account( - oracle_address_expr.as_str(), - Account::new().nonce(1).balance(STAKE_AMOUNT), - ); - oracles.push(oracle_address); - } - - // init price aggregator - world.set_state_step(set_state_step).whitebox_deploy( - &price_aggregator_whitebox, - ScDeployStep::new() - .from(OWNER_ADDRESS_EXPR) - .code(price_aggregator_code), - |sc| { - let mut oracle_args = MultiValueEncoded::new(); - for oracle_address in &oracles { - oracle_args.push(managed_address!(&oracle_address.to_address())); - } - - sc.init( - EgldOrEsdtTokenIdentifier::egld(), - managed_biguint!(STAKE_AMOUNT), - managed_biguint!(SLASH_AMOUNT), - SLASH_QUORUM, - SUBMISSION_COUNT, - oracle_args, - ) - }, - ); - - for oracle_address in &oracles { - world.whitebox_call( - &price_aggregator_whitebox, - ScCallStep::new() - .from(oracle_address) - .egld_value(STAKE_AMOUNT), - |sc| sc.call_stake(), - ); - } // configure the number of decimals world.whitebox_call( @@ -518,3 +282,200 @@ fn test_price_aggregator_submit_round_ok() { ); }); } + +#[test] +fn test_price_aggregator_discarded_round() { + let (mut world, oracles) = setup(); + let price_aggregator_whitebox = WhiteboxContract::new( + PRICE_AGGREGATOR_ADDRESS_EXPR, + multiversx_price_aggregator_sc::contract_obj, + ); + + // configure the number of decimals + world.whitebox_call( + &price_aggregator_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.set_pair_decimals( + managed_buffer!(EGLD_TICKER), + managed_buffer!(USD_TICKER), + DECIMALS, + ) + }, + ); + + // unpause + world.whitebox_call( + &price_aggregator_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| sc.call_unpause_endpoint(), + ); + + // submit first + world.whitebox_call( + &price_aggregator_whitebox, + ScCallStep::new().from(&oracles[0]), + |sc| { + sc.submit( + managed_buffer!(EGLD_TICKER), + managed_buffer!(USD_TICKER), + 95, + managed_biguint!(10_000), + DECIMALS, + ) + }, + ); + + let current_timestamp = 100 + MAX_ROUND_DURATION_SECONDS + 1; + world.set_state_step(SetStateStep::new().block_timestamp(current_timestamp)); + + // submit second - this will discard the previous submission + world.whitebox_call( + &price_aggregator_whitebox, + ScCallStep::new().from(&oracles[1]), + |sc| { + sc.submit( + managed_buffer!(EGLD_TICKER), + managed_buffer!(USD_TICKER), + current_timestamp - 1, + managed_biguint!(11_000), + DECIMALS, + ) + }, + ); + + world.whitebox_query(&price_aggregator_whitebox, |sc| { + let token_pair = TokenPair { + from: managed_buffer!(EGLD_TICKER), + to: managed_buffer!(USD_TICKER), + }; + let submissions = sc.submissions().get(&token_pair).unwrap(); + assert_eq!(submissions.len(), 1); + assert_eq!( + submissions + .get(&managed_address!(&oracles[1].to_address())) + .unwrap(), + managed_biguint!(11_000) + ); + }); +} + +#[test] +fn test_price_aggregator_slashing() { + let (mut world, oracles) = setup(); + let price_aggregator_whitebox = WhiteboxContract::new( + PRICE_AGGREGATOR_ADDRESS_EXPR, + multiversx_price_aggregator_sc::contract_obj, + ); + + // unpause + world.whitebox_call( + &price_aggregator_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| sc.call_unpause_endpoint(), + ); + + world.whitebox_call( + &price_aggregator_whitebox, + ScCallStep::new() + .from(&oracles[0]) + .argument(BytesValue::from(oracles[1].to_address().as_bytes())), + |sc| sc.call_vote_slash_member(), + ); + + world.whitebox_call( + &price_aggregator_whitebox, + ScCallStep::new() + .from(&oracles[2]) + .argument(BytesValue::from(oracles[1].to_address().as_bytes())), + |sc| sc.call_vote_slash_member(), + ); + + world.whitebox_call( + &price_aggregator_whitebox, + ScCallStep::new() + .from(&oracles[0]) + .argument(BytesValue::from(oracles[1].to_address().as_bytes())), + |sc| sc.call_slash_member(), + ); + + // oracle 1 try submit after slashing + world.whitebox_call_check( + &price_aggregator_whitebox, + ScCallStep::new().from(&oracles[1]).no_expect(), + |sc| { + sc.submit( + managed_buffer!(EGLD_TICKER), + managed_buffer!(USD_TICKER), + 95, + managed_biguint!(10_000), + DECIMALS, + ) + }, + |r| { + r.assert_user_error("only oracles allowed"); + }, + ); +} + +fn setup() -> (ScenarioWorld, Vec) { + // setup + let mut world = world(); + let price_aggregator_whitebox = WhiteboxContract::new( + PRICE_AGGREGATOR_ADDRESS_EXPR, + multiversx_price_aggregator_sc::contract_obj, + ); + let price_aggregator_code = world.code_expression(PRICE_AGGREGATOR_PATH_EXPR); + + let mut set_state_step = SetStateStep::new() + .put_account(OWNER_ADDRESS_EXPR, Account::new().nonce(1)) + .new_address(OWNER_ADDRESS_EXPR, 1, PRICE_AGGREGATOR_ADDRESS_EXPR) + .block_timestamp(100); + + let mut oracles = Vec::new(); + for i in 1..=NR_ORACLES { + let oracle_address_expr = format!("address:oracle{i}"); + let oracle_address = AddressValue::from(oracle_address_expr.as_str()); + + set_state_step = set_state_step.put_account( + oracle_address_expr.as_str(), + Account::new().nonce(1).balance(STAKE_AMOUNT), + ); + oracles.push(oracle_address); + } + + // init price aggregator + world.set_state_step(set_state_step).whitebox_deploy( + &price_aggregator_whitebox, + ScDeployStep::new() + .from(OWNER_ADDRESS_EXPR) + .code(price_aggregator_code), + |sc| { + let mut oracle_args = MultiValueEncoded::new(); + for oracle_address in &oracles { + oracle_args.push(managed_address!(&oracle_address.to_address())); + } + + sc.init( + EgldOrEsdtTokenIdentifier::egld(), + managed_biguint!(STAKE_AMOUNT), + managed_biguint!(SLASH_AMOUNT), + SLASH_QUORUM, + SUBMISSION_COUNT, + oracle_args, + ) + }, + ); + + for oracle_address in &oracles { + world.whitebox_call( + &price_aggregator_whitebox, + ScCallStep::new() + .from(oracle_address) + .egld_value(STAKE_AMOUNT), + |sc| sc.call_stake(), + ); + } + + (world, oracles) +} From a381cdeea9e374bf608d1a51b2a5820886e173d8 Mon Sep 17 00:00:00 2001 From: Ovidiu Stinga Date: Thu, 7 Sep 2023 20:56:00 +0300 Subject: [PATCH 7/8] add blackbox tests full --- .../tests/price_aggregator_blackbox_test.rs | 388 ++++++++++++++++++ 1 file changed, 388 insertions(+) create mode 100644 contracts/core/price-aggregator/tests/price_aggregator_blackbox_test.rs diff --git a/contracts/core/price-aggregator/tests/price_aggregator_blackbox_test.rs b/contracts/core/price-aggregator/tests/price_aggregator_blackbox_test.rs new file mode 100644 index 0000000000..bc1078bb06 --- /dev/null +++ b/contracts/core/price-aggregator/tests/price_aggregator_blackbox_test.rs @@ -0,0 +1,388 @@ +use multiversx_price_aggregator_sc::{ + price_aggregator_data::{OracleStatus, TimestampedPrice, TokenPair}, + staking::ProxyTrait as _, + ContractObj, PriceAggregator, ProxyTrait as _, MAX_ROUND_DURATION_SECONDS, +}; +use multiversx_sc::{ + codec::multi_types::MultiValueVec, + types::{Address, EgldOrEsdtTokenIdentifier}, +}; +use multiversx_sc_modules::pause::ProxyTrait; +use multiversx_sc_scenario::{ + api::StaticApi, + managed_address, managed_biguint, managed_buffer, + scenario_model::{Account, AddressValue, ScCallStep, ScDeployStep, SetStateStep, TxExpect}, + ContractInfo, DebugApi, ScenarioWorld, WhiteboxContract, +}; + +const DECIMALS: u8 = 0; +const EGLD_TICKER: &[u8] = b"EGLD"; +const NR_ORACLES: usize = 4; +const OWNER_ADDRESS_EXPR: &str = "address:owner"; +const PRICE_AGGREGATOR_ADDRESS_EXPR: &str = "sc:price-aggregator"; +const PRICE_AGGREGATOR_PATH_EXPR: &str = "file:output/multiversx-price-aggregator-sc.wasm"; +const SLASH_AMOUNT: u64 = 10; +const SLASH_QUORUM: usize = 2; +const STAKE_AMOUNT: u64 = 20; +const SUBMISSION_COUNT: usize = 3; +const USD_TICKER: &[u8] = b"USDC"; + +type PriceAggregatorContract = ContractInfo>; + +fn world() -> ScenarioWorld { + let mut blockchain = ScenarioWorld::new(); + + blockchain.set_current_dir_from_workspace("contracts/core/price-aggregator"); + blockchain.register_contract( + PRICE_AGGREGATOR_PATH_EXPR, + multiversx_price_aggregator_sc::ContractBuilder, + ); + + blockchain +} + +struct PriceAggregatorTestState { + world: ScenarioWorld, + oracles: Vec, + price_aggregator_contract: PriceAggregatorContract, + price_aggregator_whitebox: WhiteboxContract>, +} + +impl PriceAggregatorTestState { + fn new() -> Self { + let mut world = world(); + + let mut set_state_step = SetStateStep::new() + .put_account(OWNER_ADDRESS_EXPR, Account::new().nonce(1)) + .new_address(OWNER_ADDRESS_EXPR, 1, PRICE_AGGREGATOR_ADDRESS_EXPR) + .block_timestamp(100); + + let mut oracles = Vec::new(); + for i in 1..=NR_ORACLES { + let address_expr = format!("address:oracle{}", i); + let address_value = AddressValue::from(address_expr.as_str()); + + set_state_step = set_state_step.put_account( + address_expr.as_str(), + Account::new().nonce(1).balance(STAKE_AMOUNT), + ); + + oracles.push(address_value); + } + world.set_state_step(set_state_step); + + let price_aggregator_contract = PriceAggregatorContract::new(PRICE_AGGREGATOR_ADDRESS_EXPR); + let price_aggregator_whitebox = WhiteboxContract::new( + PRICE_AGGREGATOR_ADDRESS_EXPR, + multiversx_price_aggregator_sc::contract_obj, + ); + + Self { + world, + oracles, + price_aggregator_contract, + price_aggregator_whitebox, + } + } + + fn deploy(&mut self) -> &mut Self { + let price_aggregator_code = self.world.code_expression(PRICE_AGGREGATOR_PATH_EXPR); + + let oracles = MultiValueVec::from( + self.oracles + .iter() + .map(|oracle| oracle.to_address()) + .collect::>(), + ); + + self.world.sc_deploy( + ScDeployStep::new() + .from(OWNER_ADDRESS_EXPR) + .code(price_aggregator_code) + .call(self.price_aggregator_contract.init( + EgldOrEsdtTokenIdentifier::egld(), + STAKE_AMOUNT, + SLASH_AMOUNT, + SLASH_QUORUM, + SUBMISSION_COUNT, + oracles, + )), + ); + + for address in self.oracles.iter() { + self.world.sc_call( + ScCallStep::new() + .from(address) + .egld_value(STAKE_AMOUNT) + .call(self.price_aggregator_contract.stake()), + ); + } + + self + } + + fn set_pair_decimals(&mut self) { + self.world.sc_call( + ScCallStep::new().from(OWNER_ADDRESS_EXPR).call( + self.price_aggregator_contract + .set_pair_decimals(EGLD_TICKER, USD_TICKER, DECIMALS), + ), + ); + } + + fn unpause_endpoint(&mut self) { + self.world.sc_call( + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .call(self.price_aggregator_contract.unpause_endpoint()), + ); + } + + fn submit(&mut self, from: &AddressValue, submission_timestamp: u64, price: u64) { + self.world.sc_call(ScCallStep::new().from(from).call( + self.price_aggregator_contract.submit( + EGLD_TICKER, + USD_TICKER, + submission_timestamp, + price, + DECIMALS, + ), + )); + } + + fn submit_and_expect_err( + &mut self, + from: &AddressValue, + submission_timestamp: u64, + price: u64, + err_message: &str, + ) { + self.world.sc_call( + ScCallStep::new() + .from(from) + .call(self.price_aggregator_contract.submit( + EGLD_TICKER, + USD_TICKER, + submission_timestamp, + price, + DECIMALS, + )) + .expect(TxExpect::user_error("str:".to_string() + err_message)), + ); + } + + fn vote_slash_member(&mut self, from: &AddressValue, member_to_slash: Address) { + self.world.sc_call( + ScCallStep::new().from(from).call( + self.price_aggregator_contract + .vote_slash_member(member_to_slash), + ), + ); + } +} + +#[test] +fn test_price_aggregator_submit() { + let mut state = PriceAggregatorTestState::new(); + state.deploy(); + + // configure the number of decimals + state.set_pair_decimals(); + + // try submit while paused + state.submit_and_expect_err(&state.oracles[0].clone(), 99, 100, "Contract is paused"); + + // unpause + state.unpause_endpoint(); + + // submit first timestamp too old + state.submit_and_expect_err( + &state.oracles[0].clone(), + 10, + 100, + "First submission too old", + ); + + // submit ok + state.submit(&state.oracles[0].clone(), 95, 100); + + let current_timestamp = 100; + state + .world + .whitebox_query(&state.price_aggregator_whitebox, |sc| { + let token_pair = TokenPair { + from: managed_buffer!(EGLD_TICKER), + to: managed_buffer!(USD_TICKER), + }; + 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(), 1); + assert_eq!( + submissions + .get(&managed_address!(&state.oracles[0].to_address())) + .unwrap(), + managed_biguint!(100) + ); + + assert_eq!( + sc.oracle_status() + .get(&managed_address!(&state.oracles[0].to_address())) + .unwrap(), + OracleStatus { + total_submissions: 1, + accepted_submissions: 1 + } + ); + }); + + // first oracle submit again - submission not accepted + state.submit(&state.oracles[0].clone(), 95, 100); + + state + .world + .whitebox_query(&state.price_aggregator_whitebox, |sc| { + assert_eq!( + sc.oracle_status() + .get(&managed_address!(&state.oracles[0].to_address())) + .unwrap(), + OracleStatus { + total_submissions: 2, + accepted_submissions: 1 + } + ); + }); +} + +#[test] +fn test_price_aggregator_submit_round_ok() { + 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, 10_000); + + let current_timestamp = 110; + state + .world + .set_state_step(SetStateStep::new().block_timestamp(current_timestamp)); + + // submit second + state.submit(&state.oracles[1].clone(), 101, 11_000); + + // submit third + state.submit(&state.oracles[2].clone(), 105, 12_000); + + state + .world + .whitebox_query(&state.price_aggregator_whitebox, |sc| { + let result = sc + .latest_price_feed(managed_buffer!(EGLD_TICKER), managed_buffer!(USD_TICKER)) + .unwrap(); + + let (round_id, from, to, timestamp, price, decimals) = result.into_tuple(); + assert_eq!(round_id, 1); + assert_eq!(from, managed_buffer!(EGLD_TICKER)); + assert_eq!(to, managed_buffer!(USD_TICKER)); + assert_eq!(timestamp, current_timestamp); + assert_eq!(price, managed_biguint!(11_000)); + assert_eq!(decimals, DECIMALS); + + // submissions are deleted after round is created + let token_pair = TokenPair { from, to }; + let submissions = sc.submissions().get(&token_pair).unwrap(); + assert_eq!(submissions.len(), 0); + + let rounds = sc.rounds().get(&token_pair).unwrap(); + assert_eq!(rounds.len(), 1); + assert_eq!( + rounds.get(1), + TimestampedPrice { + timestamp, + price, + decimals + } + ); + }); +} + +#[test] +fn test_price_aggregator_discarded_round() { + 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, 10_000); + + let current_timestamp = 100 + MAX_ROUND_DURATION_SECONDS + 1; + state + .world + .set_state_step(SetStateStep::new().block_timestamp(current_timestamp)); + + // submit second - this will discard the previous submission + state.submit(&state.oracles[1].clone(), current_timestamp - 1, 11_000); + + state + .world + .whitebox_query(&state.price_aggregator_whitebox, |sc| { + let token_pair = TokenPair { + from: managed_buffer!(EGLD_TICKER), + to: managed_buffer!(USD_TICKER), + }; + let submissions = sc.submissions().get(&token_pair).unwrap(); + assert_eq!(submissions.len(), 1); + assert_eq!( + submissions + .get(&managed_address!(&state.oracles[1].to_address())) + .unwrap(), + managed_biguint!(11_000) + ); + }); +} + +#[test] +fn test_price_aggregator_slashing() { + let mut state = PriceAggregatorTestState::new(); + state.deploy(); + + // unpause + state.unpause_endpoint(); + + state.vote_slash_member(&state.oracles[0].clone(), state.oracles[1].to_address()); + state.vote_slash_member(&state.oracles[2].clone(), state.oracles[1].to_address()); + + state.world.sc_call( + ScCallStep::new().from(&state.oracles[0]).call( + state + .price_aggregator_contract + .slash_member(state.oracles[1].to_address()), + ), + ); + + // oracle 1 try submit after slashing + state.submit_and_expect_err( + &state.oracles[1].clone(), + 95, + 10_000, + "only oracles allowed", + ); +} From fde63c269e9f3c7d550881ffdc0eda2177e99393 Mon Sep 17 00:00:00 2001 From: Ovidiu Stinga Date: Thu, 7 Sep 2023 20:59:09 +0300 Subject: [PATCH 8/8] small refactor on multisig blackbox --- .../multisig/tests/multisig_blackbox_test.rs | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/contracts/examples/multisig/tests/multisig_blackbox_test.rs b/contracts/examples/multisig/tests/multisig_blackbox_test.rs index 1ff87d1d37..b4e8765e3f 100644 --- a/contracts/examples/multisig/tests/multisig_blackbox_test.rs +++ b/contracts/examples/multisig/tests/multisig_blackbox_test.rs @@ -31,7 +31,6 @@ const OWNER_ADDRESS_EXPR: &str = "address:owner"; const PROPOSER_ADDRESS_EXPR: &str = "address:proposer"; const PROPOSER_BALANCE_EXPR: &str = "100,000,000"; const QUORUM_SIZE: usize = 1; -const STATUS_ERR_CODE_EXPR: u64 = 4; type MultisigContract = ContractInfo>; type AdderContract = ContractInfo>; @@ -243,10 +242,7 @@ impl MultisigTestState { ScCallStep::new() .from(BOARD_MEMBER_ADDRESS_EXPR) .call(self.multisig_contract.perform_action_endpoint(action_id)) - .expect(TxExpect::err( - STATUS_ERR_CODE_EXPR, - "str:".to_string() + err_message, - )), + .expect(TxExpect::user_error("str:".to_string() + err_message)), ); } @@ -370,8 +366,7 @@ fn test_change_quorum() { ScCallStep::new() .from(BOARD_MEMBER_ADDRESS_EXPR) .call(state.multisig_contract.discard_action(action_id)) - .expect(TxExpect::err( - STATUS_ERR_CODE_EXPR, + .expect(TxExpect::user_error( "str:cannot discard action with valid signatures", )), ); @@ -394,10 +389,7 @@ fn test_change_quorum() { ScCallStep::new() .from(BOARD_MEMBER_ADDRESS_EXPR) .call(state.multisig_contract.sign(action_id)) - .expect(TxExpect::err( - STATUS_ERR_CODE_EXPR, - "str:action does not exist", - )), + .expect(TxExpect::user_error("str:action does not exist")), ); // add another board member @@ -454,10 +446,7 @@ fn test_transfer_execute_to_user() { OptionalValue::::None, MultiValueVec::>::new(), )) - .expect(TxExpect::err( - STATUS_ERR_CODE_EXPR, - "str:proposed action has no effect", - )), + .expect(TxExpect::user_error("str:proposed action has no effect")), ); // propose