From 993bc0eef55ae5cbcb384f83d82d98c239b23b4f Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Mon, 23 Sep 2024 15:03:50 -0400 Subject: [PATCH] add the oracle get earliest observation method (#9) --- .tool-versions | 2 +- Scarb.toml | 2 +- src/oracle.cairo | 41 +++++++++++++++++++++++++++++++++++++++++ src/oracle_test.cairo | 29 +++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 2 deletions(-) diff --git a/.tool-versions b/.tool-versions index 5e90c60..cc60fd6 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -scarb 2.8.1 +scarb 2.8.2 diff --git a/Scarb.toml b/Scarb.toml index 8d0476c..6f005ee 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -27,4 +27,4 @@ block_id.number = "677957" [dev-dependencies] snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.30.0" } -assert_macros = "2.8.0" \ No newline at end of file +assert_macros = "2.8.2" \ No newline at end of file diff --git a/src/oracle.cairo b/src/oracle.cairo index 7b618b3..6272dec 100644 --- a/src/oracle.cairo +++ b/src/oracle.cairo @@ -8,6 +8,12 @@ use starknet::{ContractAddress}; #[starknet::interface] pub trait IOracle { + // Returns the timestamp of the earliest observation for a given pair, or Option::None if the + // pair has no observations + fn get_earliest_observation_time( + self: @TContractState, token_a: ContractAddress, token_b: ContractAddress + ) -> Option; + // Returns the time weighted average tick between the given start and end time fn get_average_tick_over_period( self: @TContractState, @@ -102,6 +108,7 @@ pub trait IOracle { #[starknet::contract] pub mod Oracle { + use core::cmp::{max}; use core::num::traits::{Zero, Sqrt, WideMul}; use core::traits::{Into}; use ekubo::components::owned::{Owned as owned_component}; @@ -291,6 +298,40 @@ pub mod Oracle { #[abi(embed_v0)] impl OracleImpl of IOracle { + fn get_earliest_observation_time( + self: @ContractState, token_a: ContractAddress, token_b: ContractAddress + ) -> Option { + let oracle_token = self.oracle_token.read(); + + if token_a == oracle_token || token_b == oracle_token { + let (token0, token1) = if token_a < token_b { + (token_a, token_b) + } else { + (token_b, token_a) + }; + let entry = self.pool_state.entry((token0, token1)); + let count = entry.count.read(); + if count.is_zero() { + Option::None + } else { + let first = entry.snapshots.entry(0).read(); + Option::Some(first.block_timestamp) + } + } else { + if let Option::Some(time_a) = self + .get_earliest_observation_time(oracle_token, token_a) { + if let Option::Some(time_b) = self + .get_earliest_observation_time(oracle_token, token_b) { + Option::Some(max(time_a, time_b)) + } else { + Option::None + } + } else { + Option::None + } + } + } + fn get_average_tick_over_period( self: @ContractState, base_token: ContractAddress, diff --git a/src/oracle_test.cairo b/src/oracle_test.cairo index a9449a0..2012e62 100644 --- a/src/oracle_test.cairo +++ b/src/oracle_test.cairo @@ -220,6 +220,35 @@ fn test_get_tick_cumulative_at_future() { oracle.get_average_tick_over_period(pool_key.token0, pool_key.token1, start, start + 1); } +#[test] +#[fork("mainnet")] +fn test_get_earliest_observation_time() { + let (pool_key_0, pool_key_1) = setup(); + let oracle = IOracleDispatcher { contract_address: pool_key_0.extension }; + + let start = get_block_timestamp() + 10; + cheat_block_timestamp(oracle.contract_address, start, CheatSpan::Indefinite); + ekubo_core().initialize_pool(pool_key_0, Zero::zero()); + cheat_block_timestamp(oracle.contract_address, start + 15, CheatSpan::Indefinite); + ekubo_core().initialize_pool(pool_key_1, Zero::zero()); + + assert_eq!( + oracle.get_earliest_observation_time(pool_key_0.token0, pool_key_0.token1).unwrap(), start + ); + assert_eq!( + oracle.get_earliest_observation_time(pool_key_1.token0, pool_key_1.token1).unwrap(), + start + 15 + ); + assert_eq!( + oracle.get_earliest_observation_time(pool_key_0.token0, pool_key_0.token1).unwrap(), start + ); + // token0, token2 + assert_eq!( + oracle.get_earliest_observation_time(pool_key_0.token0, pool_key_1.token1).unwrap(), + start + 15 + ); +} + // assumes there is 0 liquidity so swaps are free fn move_price_to_tick(pool_key: PoolKey, tick: i129) { let tick_current = ekubo_core().get_pool_price(pool_key).tick;