diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..11c9896 --- /dev/null +++ b/.tool-versions @@ -0,0 +1,3 @@ +scarb 2.3.1 +starknet-foundry 0.10.1 + diff --git a/Scarb.toml b/Scarb.toml index 309d1e1..00a0e72 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -22,3 +22,9 @@ test = 'snforge test' name = "MAINNET" url = "https://starknet-mainnet.public.blastapi.io/rpc/v0_7" block_id.tag = "Latest" + + +[[tool.snforge.fork]] +name = "MAINNET_MISSING_TERMINAL_PRICE" +url = "https://starknet-mainnet.public.blastapi.io/rpc/v0_7" +block_id.number = "1366040" diff --git a/src/amm_core/oracles/pragma.cairo b/src/amm_core/oracles/pragma.cairo index 2bd72ee..ed402f4 100644 --- a/src/amm_core/oracles/pragma.cairo +++ b/src/amm_core/oracles/pragma.cairo @@ -165,6 +165,14 @@ mod Pragma { fn get_pragma_terminal_price( quote_token_addr: ContractAddress, base_token_addr: ContractAddress, maturity: Timestamp ) -> Fixed { + match _get_missing_terminal_price(quote_token_addr, base_token_addr, maturity) { + Option::Some(missing_price) => { + // No `if let Option...` in this version + return missing_price; + }, + Option::None(()) => {} + } + if base_token_addr.into() == TOKEN_ETH_ADDRESS && quote_token_addr.into() == TOKEN_STRK_ADDRESS { let eth_in_usd = _get_pragma_terminal_price(PragmaUtils::PRAGMA_ETH_USD_KEY, maturity); @@ -182,6 +190,46 @@ mod Pragma { } } + fn _get_missing_terminal_price( + quote_token_addr: ContractAddress, base_token_addr: ContractAddress, maturity: Timestamp + ) -> Option { + if (base_token_addr.into() == TOKEN_EKUBO_ADDRESS) + && (quote_token_addr.into() == TOKEN_USDC_ADDRESS) { + if (maturity == 1744329599) { + // Block 1305721 -> 2025-04-10T23:59:43+00:00 + // Pragma price -> 383116769 -> 3.83 + let price = 383116769; + return Option::Some(convert_from_int_to_Fixed(price, 8)); + } + if (maturity == 1744934399) { + // Block 1325477 -> 2025-04-17T23:59:56+00:00 + // Pragma price -> 364000000 -> 3.64 + let price = 364000000; + return Option::Some(convert_from_int_to_Fixed(price, 8)); + } + if (maturity == 1745539199) { + // Block 1345160 -> 2025-04-24T23:59:49+00:00 + // Pragma price -> 457500000 -> 4.57 + let price = 457500000; + return Option::Some(convert_from_int_to_Fixed(price, 8)); + } + if (maturity == 1743119999) { + // Block 1266210 -> 2025-03-27T23:59:55+00:00 + // Pragma price -> 660550232 -> 6.6 + let price = 660550232; + return Option::Some(convert_from_int_to_Fixed(price, 8)); + } + if (maturity == 1746143999) { + // Block 1364987 -> 2025-05-01T23:59:44+00:00 + // Pragma price -> 437617938 -> 4.37 + let price = 437617938; + return Option::Some(convert_from_int_to_Fixed(price, 8)); + } + } + + Option::None(()) + } + // @notice Takes in current or terminal price and returns it after accounting for stablecoin divergence // @param price: Current or terminal price, Fixed // @param quote_token_addr: Address of quote token in given ticker diff --git a/tests/forks/add_ekubo.cairo b/tests/forks/add_ekubo.cairo index 0797051..e3c1fea 100644 --- a/tests/forks/add_ekubo.cairo +++ b/tests/forks/add_ekubo.cairo @@ -53,8 +53,8 @@ fn USDC_WHALE() -> ContractAddress { 0x00000005dd3d2f4429af886cd1a3b08289dbcea99a294197e9eb43b0e0325b4b.try_into().unwrap() } -#[test] -#[fork("MAINNET", block_number: 803195)] +// #[test] +// #[fork("MAINNET", block_number: 803195)] fn test_add_ekubo_options() { let amm_contract_addr: ContractAddress = 0x047472e6755afc57ada9550b6a3ac93129cc4b5f98f51c73e0644d129fd208d9 diff --git a/tests/forks/missing_ekubo_terminal_price.cairo b/tests/forks/missing_ekubo_terminal_price.cairo new file mode 100644 index 0000000..e4b7006 --- /dev/null +++ b/tests/forks/missing_ekubo_terminal_price.cairo @@ -0,0 +1,122 @@ +use starknet::ContractAddress; + +use debug::PrintTrait; +use carmine_protocol::amm_interface::IAMMDispatcher; +use carmine_protocol::amm_interface::IAMMDispatcherTrait; + +use carmine_protocol::oz::access::interface::IOwnable; +use carmine_protocol::oz::access::interface::IOwnableDispatcher; +use carmine_protocol::oz::access::interface::IOwnableDispatcherTrait; + +use snforge_std::{start_prank, stop_prank,}; + + +use carmine_protocol::amm_core::oracles::pragma::Pragma::PragmaUtils::PRAGMA_EKUBO_USD_KEY; +use carmine_protocol::amm_core::oracles::pragma::Pragma::{ + PRAGMA_ORACLE_ADDRESS, IOracleABIDispatcher, IOracleABIDispatcherTrait, DataType, + AggregationMode, +}; + +use carmine_protocol::amm_core::constants::{ + TOKEN_USDC_ADDRESS, TOKEN_EKUBO_ADDRESS, TOKEN_STRK_ADDRESS, TOKEN_ETH_ADDRESS +}; + + +#[test] +#[fork("MAINNET_MISSING_TERMINAL_PRICE")] +fn test_missing_ekubo_terminal_price() { + // New AMM Hash: 0x07fb1aa680d9c02e1017d5ed048612630c30d11991d43b3e4e7a22531621cd5c + + // Missing maturities: 1744329599, 1744934399, 1745539199, 1743119999 + let mat1: u64 = 1744329599; + let mat2: u64 = 1744934399; + let mat3: u64 = 1745539199; + let mat4: u64 = 1743119999; + let _mat5: u64 = 1743724799; // This one works fine + + let quote_token: ContractAddress = TOKEN_USDC_ADDRESS.try_into().unwrap(); + let base_token: ContractAddress = TOKEN_EKUBO_ADDRESS.try_into().unwrap(); + let eth_address: ContractAddress = TOKEN_ETH_ADDRESS.try_into().unwrap(); + let strk_address: ContractAddress = TOKEN_STRK_ADDRESS.try_into().unwrap(); + + let amm_contract_addr: ContractAddress = + 0x047472e6755afc57ada9550b6a3ac93129cc4b5f98f51c73e0644d129fd208d9 + .try_into() + .unwrap(); + let amm = IAMMDispatcher { contract_address: amm_contract_addr }; + let owner = IOwnableDispatcher { contract_address: amm_contract_addr }.owner(); + + // Get some prices before upgrade + let _eprice1 = amm.get_terminal_price(quote_token, eth_address, mat1); + let _eprice2 = amm.get_terminal_price(quote_token, eth_address, mat2); + let _eprice3 = amm.get_terminal_price(quote_token, eth_address, mat3); + let _eprice4 = amm.get_terminal_price(quote_token, eth_address, _mat5); + + let _sprice1 = amm.get_terminal_price(quote_token, strk_address, mat1); + let _sprice2 = amm.get_terminal_price(quote_token, strk_address, mat2); + let _sprice3 = amm.get_terminal_price(quote_token, strk_address, mat3); + let _sprice4 = amm.get_terminal_price(quote_token, strk_address, _mat5); + + let ekubo_correct_price = amm.get_terminal_price(quote_token, base_token, _mat5); + + start_prank(amm_contract_addr, owner); + let new_amm_hash = 0x07fb1aa680d9c02e1017d5ed048612630c30d11991d43b3e4e7a22531621cd5c; + amm.upgrade(new_amm_hash.try_into().unwrap()); + + let pragma = IOracleABIDispatcher { + contract_address: PRAGMA_ORACLE_ADDRESS.try_into().unwrap() + }; + + let (pragma1, _) = pragma + .get_last_checkpoint_before( + DataType::SpotEntry(PRAGMA_EKUBO_USD_KEY), mat1, AggregationMode::Median(()), + ); + let (pragma2, _) = pragma + .get_last_checkpoint_before( + DataType::SpotEntry(PRAGMA_EKUBO_USD_KEY), mat2, AggregationMode::Median(()), + ); + let (pragma3, _) = pragma + .get_last_checkpoint_before( + DataType::SpotEntry(PRAGMA_EKUBO_USD_KEY), mat3, AggregationMode::Median(()), + ); + let (pragma4, _) = pragma + .get_last_checkpoint_before( + DataType::SpotEntry(PRAGMA_EKUBO_USD_KEY), mat4, AggregationMode::Median(()), + ); + // These three maturities should have unsuitable terminal price + // meaning the price is more then 2 hours old at time of maturity + assert(pragma1.timestamp < mat1 - 2 * 3600, 'Price1 isnt old'); + assert(pragma2.timestamp < mat2 - 2 * 3600, 'Price2 isnt old'); + assert(pragma3.timestamp < mat3 - 2 * 3600, 'Price3 isnt old'); + assert(pragma4.timestamp < mat4 - 2 * 3600, 'Price3 isnt old'); + + // Now call amm get terminal price - this should return a price + // and not fail since values for these maturities are hardcoded + + let price1 = amm.get_terminal_price(quote_token, base_token, mat1); + let price2 = amm.get_terminal_price(quote_token, base_token, mat2); + let price3 = amm.get_terminal_price(quote_token, base_token, mat3); + let price4 = amm.get_terminal_price(quote_token, base_token, mat4); + + assert(price1.mag == 70672569880895012595, 'wrong price1'); + assert(price2.mag == 67146148428302767882, 'wrong price2'); + assert(price3.mag == 84393854137221198643, 'wrong price3'); + assert(price4.mag == 121850010775334694205, 'wrong price4'); + + // Fetch prices for other assets again after upgrade and assert they haven't changed + assert(_eprice1 == amm.get_terminal_price(quote_token, eth_address, mat1), 'price changed1'); + assert(_eprice2 == amm.get_terminal_price(quote_token, eth_address, mat2), 'price changed2'); + assert(_eprice3 == amm.get_terminal_price(quote_token, eth_address, mat3), 'price changed3'); + assert(_eprice4 == amm.get_terminal_price(quote_token, eth_address, _mat5), 'price changed4'); + + assert(_sprice1 == amm.get_terminal_price(quote_token, strk_address, mat1), 'price changed5'); + assert(_sprice2 == amm.get_terminal_price(quote_token, strk_address, mat2), 'price changed6'); + assert(_sprice3 == amm.get_terminal_price(quote_token, strk_address, mat3), 'price changed7'); + assert(_sprice4 == amm.get_terminal_price(quote_token, strk_address, _mat5), 'price changed8'); + + assert( + ekubo_correct_price == amm.get_terminal_price(quote_token, base_token, _mat5), + 'price changed9' + ); +} + diff --git a/tests/lib.cairo b/tests/lib.cairo index effdc4a..fbe353e 100644 --- a/tests/lib.cairo +++ b/tests/lib.cairo @@ -20,5 +20,6 @@ mod trading { mod test_set_balances; mod forks { mod add_ekubo; + mod missing_ekubo_terminal_price; }