Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add more events to PriceAggregator #1804

Merged
merged 15 commits into from
Oct 9, 2024
70 changes: 65 additions & 5 deletions contracts/core/price-aggregator/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,43 @@ 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<M: ManagedTypeApi> {
price: BigUint<M>,
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]
pub trait EventsModule {
fn emit_new_round_event(
&self,
token_pair: &TokenPair<Self::Api>,
round: Round,
price_feed: &TimestampedPrice<Self::Api>,
) {
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,
Expand All @@ -40,7 +54,53 @@ pub trait EventsModule {
&self,
#[indexed] from: &ManagedBuffer,
#[indexed] to: &ManagedBuffer,
#[indexed] epoch: u64,
#[indexed] round: Round,
new_round_event: &NewRoundEvent<Self::Api>,
);

fn emit_discard_submission_event(
&self,
token_pair: &TokenPair<Self::Api>,
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,
);
}
85 changes: 55 additions & 30 deletions contracts/core/price-aggregator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
) {
Expand All @@ -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;

Expand All @@ -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()
Expand All @@ -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"
Expand All @@ -207,24 +231,22 @@ pub trait PriceAggregator:
#[endpoint(submitBatch)]
fn submit_batch(
&self,
submissions: MultiValueEncoded<MultiValue5<ManagedBuffer, ManagedBuffer, u64, BigUint, u8>>,
submissions: MultiValueEncoded<
MultiValue5<ManagedBuffer, ManagedBuffer, Timestamp, BigUint, u8>,
>,
) {
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);
}
}

Expand All @@ -248,6 +270,7 @@ pub trait PriceAggregator:
fn create_new_round(
&self,
token_pair: TokenPair<Self::Api>,
round: Round,
mut submissions: MapMapper<ManagedAddress, BigUint>,
decimals: u8,
) {
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -422,13 +447,13 @@ pub trait PriceAggregator:
fn first_submission_timestamp(
&self,
token_pair: &TokenPair<Self::Api>,
) -> SingleValueMapper<u64>;
) -> SingleValueMapper<Timestamp>;

#[storage_mapper("last_submission_timestamp")]
fn last_submission_timestamp(
&self,
token_pair: &TokenPair<Self::Api>,
) -> SingleValueMapper<u64>;
) -> SingleValueMapper<Timestamp>;

#[storage_mapper("submissions")]
fn submissions(
Expand Down
1 change: 0 additions & 1 deletion contracts/core/price-aggregator/src/median.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
8 changes: 5 additions & 3 deletions contracts/core/price-aggregator/src/price_aggregator_data.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::events::{RoundId, Timestamp};

multiversx_sc::imports!();
multiversx_sc::derive_imports!();

Expand All @@ -11,10 +13,10 @@ pub struct TokenPair<M: ManagedTypeApi> {
#[type_abi]
#[derive(NestedEncode, NestedDecode, TopEncode, TopDecode)]
pub struct PriceFeed<M: ManagedTypeApi> {
pub round_id: u32,
pub round_id: RoundId,
pub from: ManagedBuffer<M>,
pub to: ManagedBuffer<M>,
pub timestamp: u64,
pub timestamp: Timestamp,
pub price: BigUint<M>,
pub decimals: u8,
}
Expand All @@ -23,7 +25,7 @@ pub struct PriceFeed<M: ManagedTypeApi> {
#[derive(TopEncode, TopDecode, Debug, PartialEq, Eq)]
pub struct TimestampedPrice<M: ManagedTypeApi> {
pub price: BigUint<M>,
pub timestamp: u64,
pub timestamp: Timestamp,
pub decimals: u8,
}

Expand Down
27 changes: 27 additions & 0 deletions contracts/core/price-aggregator/tests/price_aggregator_proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,25 @@ where
}
}

#[rustfmt::skip]
impl<Env, From, To, Gas> PriceAggregatorProxyMethods<Env, From, To, Gas>
where
Env: TxEnv,
Env::Api: VMApi,
From: TxFrom<Env>,
To: TxTo<Env>,
Gas: TxGas<Env>,
{
pub fn upgrade(
self,
) -> TxTypedUpgrade<Env, From, To, NotPayable, Gas, ()> {
self.wrapped_tx
.payment(NotPayable)
.raw_upgrade()
.original_result()
}
}

#[rustfmt::skip]
impl<Env, From, To, Gas> PriceAggregatorProxyMethods<Env, From, To, Gas>
where
Expand Down Expand Up @@ -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,
}
Loading
Loading