From e769241109db17a6435de3f5521ed799400a5edc Mon Sep 17 00:00:00 2001 From: Chepelau Date: Thu, 1 May 2025 12:14:01 +0200 Subject: [PATCH 1/3] Add missing terminal prices --- .tool-versions | 3 + Scarb.toml | 6 + src/amm_core/oracles/pragma.cairo | 36 ++++++ tests/forks/add_ekubo.cairo | 4 +- .../forks/missing_ekubo_terminal_price.cairo | 114 ++++++++++++++++++ tests/lib.cairo | 1 + 6 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 .tool-versions create mode 100644 tests/forks/missing_ekubo_terminal_price.cairo 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..37a80f1 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 = "1363168" diff --git a/src/amm_core/oracles/pragma.cairo b/src/amm_core/oracles/pragma.cairo index 2bd72ee..01767b2 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,34 @@ 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)); + } + } + + 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..31a0349 --- /dev/null +++ b/tests/forks/missing_ekubo_terminal_price.cairo @@ -0,0 +1,114 @@ +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: 0x01313b44618af33738511f02ce2ae9092f2bdf62528a4a7f3c5ebd8c16dac594 + + // Missing maturities: 1744329599, 1744934399, 1745539199 + let mat1: u64 = 1744329599; + let mat2: u64 = 1744934399; + let mat3: u64 = 1745539199; + let _mat4: 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, _mat4); + + 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, _mat4); + + let ekubo_correct_price = amm.get_terminal_price(quote_token, base_token, _mat4); + + start_prank(amm_contract_addr, owner); + let new_amm_hash = 0x01313b44618af33738511f02ce2ae9092f2bdf62528a4a7f3c5ebd8c16dac594; + 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(()), + ); + // 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'); + + // 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); + + assert(price1.mag == 70672569880895012595, 'wrong price1'); + assert(price2.mag == 67146148428302767882, 'wrong price2'); + assert(price3.mag == 84393854137221198643, 'wrong price3'); + + // 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, _mat4), '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, _mat4), 'price changed8'); + + assert( + ekubo_correct_price == amm.get_terminal_price(quote_token, base_token, _mat4), + '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; } From 47e33b9c4efa15bc7b7f9fd50ffea4ae75d6e4a0 Mon Sep 17 00:00:00 2001 From: Chepelau Date: Thu, 1 May 2025 19:09:41 +0200 Subject: [PATCH 2/3] Add one more missing maturity --- Scarb.toml | 2 +- src/amm_core/oracles/pragma.cairo | 6 ++++ .../forks/missing_ekubo_terminal_price.cairo | 28 ++++++++++++------- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/Scarb.toml b/Scarb.toml index 37a80f1..ea2337d 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -27,4 +27,4 @@ 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 = "1363168" +block_id.number = "1364161" diff --git a/src/amm_core/oracles/pragma.cairo b/src/amm_core/oracles/pragma.cairo index 01767b2..b755c54 100644 --- a/src/amm_core/oracles/pragma.cairo +++ b/src/amm_core/oracles/pragma.cairo @@ -213,6 +213,12 @@ mod Pragma { 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)); + } } Option::None(()) diff --git a/tests/forks/missing_ekubo_terminal_price.cairo b/tests/forks/missing_ekubo_terminal_price.cairo index 31a0349..3dbf166 100644 --- a/tests/forks/missing_ekubo_terminal_price.cairo +++ b/tests/forks/missing_ekubo_terminal_price.cairo @@ -25,13 +25,14 @@ use carmine_protocol::amm_core::constants::{ #[test] #[fork("MAINNET_MISSING_TERMINAL_PRICE")] fn test_missing_ekubo_terminal_price() { - // New AMM Hash: 0x01313b44618af33738511f02ce2ae9092f2bdf62528a4a7f3c5ebd8c16dac594 + // New AMM Hash: 0x0202e349e2cd0cdde2f38c542bc4607cfbda38799745193cac196614356f10e7 - // Missing maturities: 1744329599, 1744934399, 1745539199 + // Missing maturities: 1744329599, 1744934399, 1745539199, 1743119999 let mat1: u64 = 1744329599; let mat2: u64 = 1744934399; let mat3: u64 = 1745539199; - let _mat4: u64 = 1743724799; // This one works fine + 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(); @@ -49,17 +50,17 @@ fn test_missing_ekubo_terminal_price() { 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, _mat4); + 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, _mat4); + let _sprice4 = amm.get_terminal_price(quote_token, strk_address, _mat5); - let ekubo_correct_price = amm.get_terminal_price(quote_token, base_token, _mat4); + let ekubo_correct_price = amm.get_terminal_price(quote_token, base_token, _mat5); start_prank(amm_contract_addr, owner); - let new_amm_hash = 0x01313b44618af33738511f02ce2ae9092f2bdf62528a4a7f3c5ebd8c16dac594; + let new_amm_hash = 0x0202e349e2cd0cdde2f38c542bc4607cfbda38799745193cac196614356f10e7; amm.upgrade(new_amm_hash.try_into().unwrap()); let pragma = IOracleABIDispatcher { @@ -78,11 +79,16 @@ fn test_missing_ekubo_terminal_price() { .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 @@ -90,24 +96,26 @@ fn test_missing_ekubo_terminal_price() { 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, _mat4), 'price changed4'); + 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, _mat4), 'price changed8'); + 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, _mat4), + ekubo_correct_price == amm.get_terminal_price(quote_token, base_token, _mat5), 'price changed9' ); } From 6b1b1c4ae0c552aa832a06f573c6366885350f9f Mon Sep 17 00:00:00 2001 From: Chepelau Date: Fri, 2 May 2025 11:07:15 +0200 Subject: [PATCH 3/3] Add one more missing maturity --- Scarb.toml | 2 +- src/amm_core/oracles/pragma.cairo | 6 ++++++ tests/forks/missing_ekubo_terminal_price.cairo | 4 ++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Scarb.toml b/Scarb.toml index ea2337d..00a0e72 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -27,4 +27,4 @@ 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 = "1364161" +block_id.number = "1366040" diff --git a/src/amm_core/oracles/pragma.cairo b/src/amm_core/oracles/pragma.cairo index b755c54..ed402f4 100644 --- a/src/amm_core/oracles/pragma.cairo +++ b/src/amm_core/oracles/pragma.cairo @@ -219,6 +219,12 @@ mod Pragma { 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(()) diff --git a/tests/forks/missing_ekubo_terminal_price.cairo b/tests/forks/missing_ekubo_terminal_price.cairo index 3dbf166..e4b7006 100644 --- a/tests/forks/missing_ekubo_terminal_price.cairo +++ b/tests/forks/missing_ekubo_terminal_price.cairo @@ -25,7 +25,7 @@ use carmine_protocol::amm_core::constants::{ #[test] #[fork("MAINNET_MISSING_TERMINAL_PRICE")] fn test_missing_ekubo_terminal_price() { - // New AMM Hash: 0x0202e349e2cd0cdde2f38c542bc4607cfbda38799745193cac196614356f10e7 + // New AMM Hash: 0x07fb1aa680d9c02e1017d5ed048612630c30d11991d43b3e4e7a22531621cd5c // Missing maturities: 1744329599, 1744934399, 1745539199, 1743119999 let mat1: u64 = 1744329599; @@ -60,7 +60,7 @@ fn test_missing_ekubo_terminal_price() { let ekubo_correct_price = amm.get_terminal_price(quote_token, base_token, _mat5); start_prank(amm_contract_addr, owner); - let new_amm_hash = 0x0202e349e2cd0cdde2f38c542bc4607cfbda38799745193cac196614356f10e7; + let new_amm_hash = 0x07fb1aa680d9c02e1017d5ed048612630c30d11991d43b3e4e7a22531621cd5c; amm.upgrade(new_amm_hash.try_into().unwrap()); let pragma = IOracleABIDispatcher {