diff --git a/contracts/core/price-aggregator/src/events.rs b/contracts/core/price-aggregator/src/events.rs index d485e622db..ee80b25594 100644 --- a/contracts/core/price-aggregator/src/events.rs +++ b/contracts/core/price-aggregator/src/events.rs @@ -2,15 +2,28 @@ multiversx_sc::imports!(); multiversx_sc::derive_imports!(); use crate::price_aggregator_data::{TimestampedPrice, TokenPair}; +pub type RoundId = u32; +pub type Round = usize; +pub type Block = u64; +pub type Epoch = u64; +pub type Timestamp = u64; #[type_abi] #[derive(TopEncode)] pub struct NewRoundEvent { price: BigUint, - timestamp: u64, + timestamp: Timestamp, decimals: u8, - block: u64, - epoch: u64, + block: Block, + epoch: Epoch, +} + +#[type_abi] +#[derive(TopEncode)] +pub struct DiscardSubmissionEvent { + submission_timestamp: Timestamp, + first_submission_timestamp: Timestamp, + has_caller_already_submitted: bool, } #[multiversx_sc::module] @@ -18,13 +31,14 @@ pub trait EventsModule { fn emit_new_round_event( &self, token_pair: &TokenPair, + round: Round, price_feed: &TimestampedPrice, ) { let epoch = self.blockchain().get_block_epoch(); self.new_round_event( &token_pair.from.clone(), &token_pair.to.clone(), - epoch, + round, &NewRoundEvent { price: price_feed.price.clone(), timestamp: price_feed.timestamp, @@ -40,7 +54,53 @@ pub trait EventsModule { &self, #[indexed] from: &ManagedBuffer, #[indexed] to: &ManagedBuffer, - #[indexed] epoch: u64, + #[indexed] round: Round, new_round_event: &NewRoundEvent, ); + + fn emit_discard_submission_event( + &self, + token_pair: &TokenPair, + round: Round, + submission_timestamp: Timestamp, + first_submission_timestamp: Timestamp, + has_caller_already_submitted: bool, + ) { + self.discard_submission_event( + &token_pair.from.clone(), + &token_pair.to.clone(), + round, + &DiscardSubmissionEvent { + submission_timestamp, + first_submission_timestamp, + has_caller_already_submitted, + }, + ) + } + + #[event("discard_submission")] + fn discard_submission_event( + &self, + #[indexed] from: &ManagedBuffer, + #[indexed] to: &ManagedBuffer, + #[indexed] round: Round, + discard_submission_event: &DiscardSubmissionEvent, + ); + + #[event("discard_round")] + fn discard_round_event( + &self, + #[indexed] from: &ManagedBuffer, + #[indexed] to: &ManagedBuffer, + #[indexed] round: Round, + ); + + #[event("add_submission")] + fn add_submission_event( + &self, + #[indexed] from: &ManagedBuffer, + #[indexed] to: &ManagedBuffer, + #[indexed] round: Round, + price: &BigUint, + ); } diff --git a/contracts/core/price-aggregator/src/lib.rs b/contracts/core/price-aggregator/src/lib.rs index 1dbed54848..b9e5187580 100644 --- a/contracts/core/price-aggregator/src/lib.rs +++ b/contracts/core/price-aggregator/src/lib.rs @@ -6,6 +6,7 @@ mod events; pub mod median; pub mod price_aggregator_data; +use events::{Round, Timestamp}; use multiversx_sc_modules::staking; use price_aggregator_data::{OracleStatus, PriceFeed, TimestampedPrice, TokenPair}; @@ -47,6 +48,11 @@ pub trait PriceAggregator: self.set_paused(true); } + #[upgrade] + fn upgrade(&self) { + self.set_paused(true); + } + #[only_owner] #[endpoint(changeAmounts)] fn change_amounts(&self, staking_amount: BigUint, slash_amount: BigUint) { @@ -118,29 +124,24 @@ pub trait PriceAggregator: &self, from: ManagedBuffer, to: ManagedBuffer, - submission_timestamp: u64, + submission_timestamp: Timestamp, price: BigUint, decimals: u8, ) { self.require_not_paused(); self.require_is_oracle(); - let current_timestamp = self.blockchain().get_block_timestamp(); - require!( - submission_timestamp <= current_timestamp, - "Timestamp is from the future" - ); + self.require_valid_submission_timestamp(submission_timestamp); self.check_decimals(&from, &to, decimals); - self.submit_unchecked(from, to, submission_timestamp, price, decimals); + self.submit_unchecked(from, to, price, decimals); } fn submit_unchecked( &self, from: ManagedBuffer, to: ManagedBuffer, - submission_timestamp: u64, price: BigUint, decimals: u8, ) { @@ -154,11 +155,15 @@ pub trait PriceAggregator: let first_sub_time_mapper = self.first_submission_timestamp(&token_pair); let last_sub_time_mapper = self.last_submission_timestamp(&token_pair); + let mut round_id = 0; + let wrapped_rounds = self.rounds().get(&token_pair); + if wrapped_rounds.is_some() { + round_id = wrapped_rounds.unwrap().len() + 1; + } + let current_timestamp = self.blockchain().get_block_timestamp(); let mut is_first_submission = false; let mut first_submission_timestamp = if submissions.is_empty() { - self.require_valid_first_submission(submission_timestamp, current_timestamp); - first_sub_time_mapper.set(current_timestamp); is_first_submission = true; @@ -169,24 +174,38 @@ pub trait PriceAggregator: // round was not completed in time, so it's discarded if current_timestamp > first_submission_timestamp + MAX_ROUND_DURATION_SECONDS { - self.require_valid_first_submission(submission_timestamp, current_timestamp); - submissions.clear(); first_sub_time_mapper.set(current_timestamp); last_sub_time_mapper.set(current_timestamp); first_submission_timestamp = current_timestamp; is_first_submission = true; + self.discard_round_event(&token_pair.from.clone(), &token_pair.to.clone(), round_id) } let caller = self.blockchain().get_caller(); - let accepted = !submissions.contains_key(&caller) - && (is_first_submission || submission_timestamp >= first_submission_timestamp); + let has_caller_already_submitted = submissions.contains_key(&caller); + let accepted = !has_caller_already_submitted + && (is_first_submission || current_timestamp >= first_submission_timestamp); if accepted { - submissions.insert(caller, price); + submissions.insert(caller.clone(), price.clone()); last_sub_time_mapper.set(current_timestamp); - self.create_new_round(token_pair, submissions, decimals); + self.create_new_round(token_pair.clone(), round_id, submissions, decimals); + self.add_submission_event( + &token_pair.from.clone(), + &token_pair.to.clone(), + round_id, + &price, + ); + } else { + self.emit_discard_submission_event( + &token_pair, + round_id, + current_timestamp, + first_submission_timestamp, + has_caller_already_submitted, + ); } self.oracle_status() @@ -197,7 +216,12 @@ pub trait PriceAggregator: }); } - fn require_valid_first_submission(&self, submission_timestamp: u64, current_timestamp: u64) { + fn require_valid_submission_timestamp(&self, submission_timestamp: u64) { + let current_timestamp = self.blockchain().get_block_timestamp(); + require!( + submission_timestamp <= current_timestamp, + "Timestamp is from the future" + ); require!( current_timestamp - submission_timestamp <= FIRST_SUBMISSION_TIMESTAMP_MAX_DIFF_SECONDS, "First submission too old" @@ -207,24 +231,22 @@ pub trait PriceAggregator: #[endpoint(submitBatch)] fn submit_batch( &self, - submissions: MultiValueEncoded>, + submissions: MultiValueEncoded< + MultiValue5, + >, ) { self.require_not_paused(); self.require_is_oracle(); - let current_timestamp = self.blockchain().get_block_timestamp(); for (from, to, submission_timestamp, price, decimals) in submissions .into_iter() .map(|submission| submission.into_tuple()) { - require!( - submission_timestamp <= current_timestamp, - "Timestamp is from the future" - ); + self.require_valid_submission_timestamp(submission_timestamp); self.check_decimals(&from, &to, decimals); - self.submit_unchecked(from, to, submission_timestamp, price, decimals); + self.submit_unchecked(from, to, price, decimals); } } @@ -248,6 +270,7 @@ pub trait PriceAggregator: fn create_new_round( &self, token_pair: TokenPair, + round: Round, mut submissions: MapMapper, decimals: u8, ) { @@ -281,7 +304,7 @@ pub trait PriceAggregator: .or_default() .get() .push(&price_feed); - self.emit_new_round_event(&token_pair, &price_feed); + self.emit_new_round_event(&token_pair, round, &price_feed); } } @@ -377,9 +400,11 @@ pub trait PriceAggregator: #[only_owner] #[endpoint(setPairDecimals)] fn set_pair_decimals(&self, from: ManagedBuffer, to: ManagedBuffer, decimals: u8) { - self.require_paused(); - - self.pair_decimals(&from, &to).set(Some(decimals)); + let pair_decimals_mapper = self.pair_decimals(&from, &to); + if !pair_decimals_mapper.is_empty() { + self.require_paused(); + } + pair_decimals_mapper.set(Some(decimals)); let pair = TokenPair { from, to }; self.clear_submissions(&pair); } @@ -422,13 +447,13 @@ pub trait PriceAggregator: fn first_submission_timestamp( &self, token_pair: &TokenPair, - ) -> SingleValueMapper; + ) -> SingleValueMapper; #[storage_mapper("last_submission_timestamp")] fn last_submission_timestamp( &self, token_pair: &TokenPair, - ) -> SingleValueMapper; + ) -> SingleValueMapper; #[storage_mapper("submissions")] fn submissions( diff --git a/contracts/core/price-aggregator/src/median.rs b/contracts/core/price-aggregator/src/median.rs index 714acf9632..53227bc9d7 100644 --- a/contracts/core/price-aggregator/src/median.rs +++ b/contracts/core/price-aggregator/src/median.rs @@ -1,5 +1,4 @@ multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); /// Returns the sorted middle, or the average of the two middle indexed items if the /// vector has an even number of elements. diff --git a/contracts/core/price-aggregator/src/price_aggregator_data.rs b/contracts/core/price-aggregator/src/price_aggregator_data.rs index c348b67d22..e5fd64d4eb 100644 --- a/contracts/core/price-aggregator/src/price_aggregator_data.rs +++ b/contracts/core/price-aggregator/src/price_aggregator_data.rs @@ -1,3 +1,5 @@ +use crate::events::{RoundId, Timestamp}; + multiversx_sc::imports!(); multiversx_sc::derive_imports!(); @@ -11,10 +13,10 @@ pub struct TokenPair { #[type_abi] #[derive(NestedEncode, NestedDecode, TopEncode, TopDecode)] pub struct PriceFeed { - pub round_id: u32, + pub round_id: RoundId, pub from: ManagedBuffer, pub to: ManagedBuffer, - pub timestamp: u64, + pub timestamp: Timestamp, pub price: BigUint, pub decimals: u8, } @@ -23,7 +25,7 @@ pub struct PriceFeed { #[derive(TopEncode, TopDecode, Debug, PartialEq, Eq)] pub struct TimestampedPrice { pub price: BigUint, - pub timestamp: u64, + pub timestamp: Timestamp, pub decimals: u8, } diff --git a/contracts/core/price-aggregator/tests/price_aggregator_proxy.rs b/contracts/core/price-aggregator/tests/price_aggregator_proxy.rs index e96244d668..7a7e2aaa74 100644 --- a/contracts/core/price-aggregator/tests/price_aggregator_proxy.rs +++ b/contracts/core/price-aggregator/tests/price_aggregator_proxy.rs @@ -72,6 +72,25 @@ where } } +#[rustfmt::skip] +impl PriceAggregatorProxyMethods +where + Env: TxEnv, + Env::Api: VMApi, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + pub fn upgrade( + self, + ) -> TxTypedUpgrade { + self.wrapped_tx + .payment(NotPayable) + .raw_upgrade() + .original_result() + } +} + #[rustfmt::skip] impl PriceAggregatorProxyMethods where @@ -387,3 +406,11 @@ where pub block: u64, pub epoch: u64, } + +#[type_abi] +#[derive(TopEncode)] +pub struct DiscardSubmissionEvent { + pub submission_timestamp: u64, + pub first_submission_timestamp: u64, + pub has_caller_already_submitted: bool, +} diff --git a/contracts/core/price-aggregator/wasm/src/lib.rs b/contracts/core/price-aggregator/wasm/src/lib.rs index 667c425afd..a8e75f4942 100644 --- a/contracts/core/price-aggregator/wasm/src/lib.rs +++ b/contracts/core/price-aggregator/wasm/src/lib.rs @@ -5,9 +5,10 @@ //////////////////////////////////////////////////// // Init: 1 +// Upgrade: 1 // Endpoints: 21 // Async Callback (empty): 1 -// Total number of exported functions: 23 +// Total number of exported functions: 24 #![no_std] @@ -18,6 +19,7 @@ multiversx_sc_wasm_adapter::endpoints! { multiversx_price_aggregator_sc ( init => init + upgrade => upgrade changeAmounts => change_amounts addOracles => add_oracles removeOracles => remove_oracles