|
|
@@ -11,10 +11,10 @@ pub use pyth::{
|
|
|
};
|
|
|
pub use errors::{
|
|
|
GetPriceUnsafeError, GovernanceActionError, UpdatePriceFeedsError, GetPriceNoOlderThanError,
|
|
|
- UpdatePriceFeedsIfNecessaryError,
|
|
|
+ UpdatePriceFeedsIfNecessaryError, ParsePriceFeedsError,
|
|
|
};
|
|
|
pub use interface::{
|
|
|
- IPyth, IPythDispatcher, IPythDispatcherTrait, DataSource, Price, PriceFeedPublishTime
|
|
|
+ IPyth, IPythDispatcher, IPythDispatcherTrait, DataSource, Price, PriceFeedPublishTime, PriceFeed
|
|
|
};
|
|
|
|
|
|
#[starknet::contract]
|
|
|
@@ -36,12 +36,13 @@ mod pyth {
|
|
|
use super::{
|
|
|
DataSource, UpdatePriceFeedsError, GovernanceActionError, Price, GetPriceUnsafeError,
|
|
|
IPythDispatcher, IPythDispatcherTrait, PriceFeedPublishTime, GetPriceNoOlderThanError,
|
|
|
- UpdatePriceFeedsIfNecessaryError,
|
|
|
+ UpdatePriceFeedsIfNecessaryError, PriceFeed, ParsePriceFeedsError,
|
|
|
};
|
|
|
use super::governance;
|
|
|
use super::governance::GovernancePayload;
|
|
|
use openzeppelin::token::erc20::interface::{IERC20CamelDispatcherTrait, IERC20CamelDispatcher};
|
|
|
use pyth::util::ResultMapErrInto;
|
|
|
+ use core::nullable::{NullableTrait, match_nullable, FromNullableResult};
|
|
|
|
|
|
#[event]
|
|
|
#[derive(Drop, PartialEq, starknet::Event)]
|
|
|
@@ -204,48 +205,7 @@ mod pyth {
|
|
|
}
|
|
|
|
|
|
fn update_price_feeds(ref self: ContractState, data: ByteArray) {
|
|
|
- let mut reader = ReaderImpl::new(data);
|
|
|
- read_and_verify_header(ref reader);
|
|
|
- let wormhole_proof_size = reader.read_u16();
|
|
|
- let wormhole_proof = reader.read_byte_array(wormhole_proof_size.into());
|
|
|
-
|
|
|
- let wormhole = IWormholeDispatcher { contract_address: self.wormhole_address.read() };
|
|
|
- let vm = wormhole.parse_and_verify_vm(wormhole_proof);
|
|
|
-
|
|
|
- let source = DataSource {
|
|
|
- emitter_chain_id: vm.emitter_chain_id, emitter_address: vm.emitter_address
|
|
|
- };
|
|
|
- if !self.is_valid_data_source.read(source) {
|
|
|
- panic_with_felt252(UpdatePriceFeedsError::InvalidUpdateDataSource.into());
|
|
|
- }
|
|
|
-
|
|
|
- let root_digest = parse_wormhole_proof(vm.payload);
|
|
|
-
|
|
|
- let num_updates = reader.read_u8();
|
|
|
- let total_fee = self.get_total_fee(num_updates);
|
|
|
- let fee_contract = IERC20CamelDispatcher {
|
|
|
- contract_address: self.fee_contract_address.read()
|
|
|
- };
|
|
|
- let execution_info = get_execution_info().unbox();
|
|
|
- let caller = execution_info.caller_address;
|
|
|
- let contract = execution_info.contract_address;
|
|
|
- if fee_contract.allowance(caller, contract) < total_fee {
|
|
|
- panic_with_felt252(UpdatePriceFeedsError::InsufficientFeeAllowance.into());
|
|
|
- }
|
|
|
- if !fee_contract.transferFrom(caller, contract, total_fee) {
|
|
|
- panic_with_felt252(UpdatePriceFeedsError::InsufficientFeeAllowance.into());
|
|
|
- }
|
|
|
-
|
|
|
- let mut i = 0;
|
|
|
- while i < num_updates {
|
|
|
- let message = read_and_verify_message(ref reader, root_digest);
|
|
|
- self.update_latest_price_if_necessary(message);
|
|
|
- i += 1;
|
|
|
- };
|
|
|
-
|
|
|
- if reader.len() != 0 {
|
|
|
- panic_with_felt252(UpdatePriceFeedsError::InvalidUpdateData.into());
|
|
|
- }
|
|
|
+ self.update_price_feeds_internal(data, array![], 0, 0, false);
|
|
|
}
|
|
|
|
|
|
fn get_update_fee(self: @ContractState, data: ByteArray) -> u256 {
|
|
|
@@ -279,6 +239,32 @@ mod pyth {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ fn parse_price_feed_updates(
|
|
|
+ ref self: ContractState,
|
|
|
+ data: ByteArray,
|
|
|
+ price_ids: Array<u256>,
|
|
|
+ min_publish_time: u64,
|
|
|
+ max_publish_time: u64
|
|
|
+ ) -> Array<PriceFeed> {
|
|
|
+ self
|
|
|
+ .update_price_feeds_internal(
|
|
|
+ data, price_ids, min_publish_time, max_publish_time, false
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ fn parse_unique_price_feed_updates(
|
|
|
+ ref self: ContractState,
|
|
|
+ data: ByteArray,
|
|
|
+ price_ids: Array<u256>,
|
|
|
+ publish_time: u64,
|
|
|
+ max_staleness: u64,
|
|
|
+ ) -> Array<PriceFeed> {
|
|
|
+ self
|
|
|
+ .update_price_feeds_internal(
|
|
|
+ data, price_ids, publish_time, publish_time + max_staleness, true
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
fn execute_governance_instruction(ref self: ContractState, data: ByteArray) {
|
|
|
let wormhole = IWormholeDispatcher { contract_address: self.wormhole_address.read() };
|
|
|
let vm = wormhole.parse_and_verify_vm(data.clone());
|
|
|
@@ -362,24 +348,24 @@ mod pyth {
|
|
|
old_data_sources
|
|
|
}
|
|
|
|
|
|
- fn update_latest_price_if_necessary(ref self: ContractState, message: PriceFeedMessage) {
|
|
|
- let latest_publish_time = self.latest_price_info.read(message.price_id).publish_time;
|
|
|
- if message.publish_time > latest_publish_time {
|
|
|
+ fn update_latest_price_if_necessary(ref self: ContractState, message: @PriceFeedMessage) {
|
|
|
+ let latest_publish_time = self.latest_price_info.read(*message.price_id).publish_time;
|
|
|
+ if *message.publish_time > latest_publish_time {
|
|
|
let info = PriceInfo {
|
|
|
- price: message.price,
|
|
|
- conf: message.conf,
|
|
|
- expo: message.expo,
|
|
|
- publish_time: message.publish_time,
|
|
|
- ema_price: message.ema_price,
|
|
|
- ema_conf: message.ema_conf,
|
|
|
+ price: *message.price,
|
|
|
+ conf: *message.conf,
|
|
|
+ expo: *message.expo,
|
|
|
+ publish_time: *message.publish_time,
|
|
|
+ ema_price: *message.ema_price,
|
|
|
+ ema_conf: *message.ema_conf,
|
|
|
};
|
|
|
- self.latest_price_info.write(message.price_id, info);
|
|
|
+ self.latest_price_info.write(*message.price_id, info);
|
|
|
|
|
|
let event = PriceFeedUpdated {
|
|
|
- price_id: message.price_id,
|
|
|
- publish_time: message.publish_time,
|
|
|
- price: message.price,
|
|
|
- conf: message.conf,
|
|
|
+ price_id: *message.price_id,
|
|
|
+ publish_time: *message.publish_time,
|
|
|
+ price: *message.price,
|
|
|
+ conf: *message.conf,
|
|
|
};
|
|
|
self.emit(event);
|
|
|
}
|
|
|
@@ -490,6 +476,105 @@ mod pyth {
|
|
|
let event = ContractUpgraded { new_class_hash: new_implementation };
|
|
|
self.emit(event);
|
|
|
}
|
|
|
+
|
|
|
+ // Applies all price feed updates encoded in `data` and extracts requested information
|
|
|
+ // about the new updates. `price_ids` specifies price feeds of interest. The output will
|
|
|
+ // contain as many items as `price_ids`, with price feeds returned in the same order as
|
|
|
+ // specified in `price_ids`.
|
|
|
+ //
|
|
|
+ // If `unique == false`, for each price feed, the first encountered update
|
|
|
+ // in the specified time interval (both timestamps inclusive) will be returned.
|
|
|
+ // If `unique == true`, the globally unique first update will be returned, as verified by
|
|
|
+ // the `prev_publish_time` value of the update. Panics if a matching update was not found
|
|
|
+ // for any of the specified feeds.
|
|
|
+ fn update_price_feeds_internal(
|
|
|
+ ref self: ContractState,
|
|
|
+ data: ByteArray,
|
|
|
+ price_ids: Array<u256>,
|
|
|
+ min_publish_time: u64,
|
|
|
+ max_publish_time: u64,
|
|
|
+ unique: bool,
|
|
|
+ ) -> Array<PriceFeed> {
|
|
|
+ let mut output: Felt252Dict<Nullable<PriceFeed>> = Default::default();
|
|
|
+ let mut reader = ReaderImpl::new(data);
|
|
|
+ read_and_verify_header(ref reader);
|
|
|
+ let wormhole_proof_size = reader.read_u16();
|
|
|
+ let wormhole_proof = reader.read_byte_array(wormhole_proof_size.into());
|
|
|
+
|
|
|
+ let wormhole = IWormholeDispatcher { contract_address: self.wormhole_address.read() };
|
|
|
+ let vm = wormhole.parse_and_verify_vm(wormhole_proof);
|
|
|
+
|
|
|
+ let source = DataSource {
|
|
|
+ emitter_chain_id: vm.emitter_chain_id, emitter_address: vm.emitter_address
|
|
|
+ };
|
|
|
+ if !self.is_valid_data_source.read(source) {
|
|
|
+ panic_with_felt252(UpdatePriceFeedsError::InvalidUpdateDataSource.into());
|
|
|
+ }
|
|
|
+
|
|
|
+ let root_digest = parse_wormhole_proof(vm.payload);
|
|
|
+
|
|
|
+ let num_updates = reader.read_u8();
|
|
|
+ let total_fee = self.get_total_fee(num_updates);
|
|
|
+ let fee_contract = IERC20CamelDispatcher {
|
|
|
+ contract_address: self.fee_contract_address.read()
|
|
|
+ };
|
|
|
+ let execution_info = get_execution_info().unbox();
|
|
|
+ let caller = execution_info.caller_address;
|
|
|
+ let contract = execution_info.contract_address;
|
|
|
+ if fee_contract.allowance(caller, contract) < total_fee {
|
|
|
+ panic_with_felt252(UpdatePriceFeedsError::InsufficientFeeAllowance.into());
|
|
|
+ }
|
|
|
+ if !fee_contract.transferFrom(caller, contract, total_fee) {
|
|
|
+ panic_with_felt252(UpdatePriceFeedsError::InsufficientFeeAllowance.into());
|
|
|
+ }
|
|
|
+
|
|
|
+ let mut i = 0;
|
|
|
+ let price_ids2 = @price_ids;
|
|
|
+ while i < num_updates {
|
|
|
+ let message = read_and_verify_message(ref reader, root_digest);
|
|
|
+ self.update_latest_price_if_necessary(@message);
|
|
|
+
|
|
|
+ let output_index = find_index_of_price_id(price_ids2, message.price_id);
|
|
|
+ match output_index {
|
|
|
+ Option::Some(output_index) => {
|
|
|
+ if output.get(output_index.into()).is_null() {
|
|
|
+ let should_output = message.publish_time >= min_publish_time
|
|
|
+ && message.publish_time <= max_publish_time
|
|
|
+ && (!unique || min_publish_time > message.prev_publish_time);
|
|
|
+ if should_output {
|
|
|
+ output
|
|
|
+ .insert(
|
|
|
+ output_index.into(), NullableTrait::new(message.into())
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ Option::None => {}
|
|
|
+ }
|
|
|
+
|
|
|
+ i += 1;
|
|
|
+ };
|
|
|
+
|
|
|
+ if reader.len() != 0 {
|
|
|
+ panic_with_felt252(UpdatePriceFeedsError::InvalidUpdateData.into());
|
|
|
+ }
|
|
|
+
|
|
|
+ let mut output_array = array![];
|
|
|
+ let mut i = 0;
|
|
|
+ while i < price_ids.len() {
|
|
|
+ let value = output.get(i.into());
|
|
|
+ match match_nullable(value) {
|
|
|
+ FromNullableResult::Null => {
|
|
|
+ panic_with_felt252(
|
|
|
+ ParsePriceFeedsError::PriceFeedNotFoundWithinRange.into()
|
|
|
+ )
|
|
|
+ },
|
|
|
+ FromNullableResult::NotNull(value) => { output_array.append(value.unbox()); }
|
|
|
+ }
|
|
|
+ i += 1;
|
|
|
+ };
|
|
|
+ output_array
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
fn apply_decimal_expo(value: u64, expo: u64) -> u256 {
|
|
|
@@ -511,4 +596,19 @@ mod pyth {
|
|
|
};
|
|
|
actual_age <= age
|
|
|
}
|
|
|
+
|
|
|
+ fn find_index_of_price_id(ids: @Array<u256>, value: u256) -> Option<usize> {
|
|
|
+ let mut i = 0;
|
|
|
+ while i < ids.len() {
|
|
|
+ if ids.at(i) == @value {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ i += 1;
|
|
|
+ };
|
|
|
+ if i == ids.len() {
|
|
|
+ Option::None
|
|
|
+ } else {
|
|
|
+ Option::Some(i)
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|