|
1 | 1 | mod test_receptor {
|
2 |
| - use opus::constants::{DAI_DECIMALS, USDC_DECIMALS, USDT_DECIMALS}; |
| 2 | + use access_control::{IAccessControlDispatcher, IAccessControlDispatcherTrait}; |
3 | 3 | use opus::core::receptor::receptor as receptor_contract;
|
| 4 | + use opus::core::roles::receptor_roles; |
| 5 | + use opus::core::shrine::shrine as shrine_contract; |
| 6 | + use opus::external::interfaces::{ITaskDispatcher, ITaskDispatcherTrait}; |
| 7 | + use opus::interfaces::IReceptor::{IReceptorDispatcher, IReceptorDispatcherTrait}; |
4 | 8 | use opus::interfaces::IShrine::{IShrineDispatcher, IShrineDispatcherTrait};
|
| 9 | + use opus::tests::common; |
5 | 10 | use opus::tests::receptor::utils::receptor_utils;
|
6 | 11 | use opus::tests::shrine::utils::shrine_utils;
|
7 | 12 | use opus::types::QuoteTokenInfo;
|
| 13 | + use snforge_std::{start_warp, start_prank, stop_prank, CheatTarget, spy_events, SpyOn, EventSpy, EventAssertions}; |
| 14 | + use starknet::{ContractAddress, get_block_timestamp}; |
| 15 | + use wadray::Wad; |
8 | 16 |
|
9 | 17 |
|
10 | 18 | #[test]
|
11 | 19 | fn test_receptor_deploy() {
|
12 |
| - let shrine: IShrineDispatcher = shrine_utils::shrine_deploy_and_setup(Option::None); |
| 20 | + let (_, receptor, mock_ekubo_oracle_extension_addr) = receptor_utils::receptor_deploy(Option::None); |
13 | 21 |
|
14 |
| - let quote_tokens: Span<QuoteTokenInfo> = array![ |
15 |
| - QuoteTokenInfo { address: receptor_utils::mock_dai(), decimals: DAI_DECIMALS }, |
16 |
| - QuoteTokenInfo { address: receptor_utils::mock_usdc(), decimals: USDC_DECIMALS }, |
17 |
| - QuoteTokenInfo { address: receptor_utils::mock_usdt(), decimals: USDT_DECIMALS }, |
| 22 | + let receptor_ac = IAccessControlDispatcher { contract_address: receptor.contract_address }; |
| 23 | + let admin = shrine_utils::admin(); |
| 24 | + assert(receptor_ac.get_admin() == admin, 'wrong admin'); |
| 25 | + assert(receptor_ac.get_roles(admin) == receptor_roles::default_admin_role(), 'wrong role'); |
| 26 | + |
| 27 | + assert_eq!(receptor.get_oracle_extension(), mock_ekubo_oracle_extension_addr, "wrong extension addr"); |
| 28 | + assert_eq!(receptor.get_twap_duration(), receptor_utils::INITIAL_TWAP_DURATION, "wrong twap duration"); |
| 29 | + assert_eq!(receptor.get_quote_tokens(), receptor_utils::quote_tokens(), "wrong quote tokens"); |
| 30 | + } |
| 31 | + |
| 32 | + // Parameters |
| 33 | + |
| 34 | + #[test] |
| 35 | + fn test_set_oracle_extension() { |
| 36 | + let (_, receptor, _) = receptor_utils::receptor_deploy(Option::None); |
| 37 | + |
| 38 | + start_prank(CheatTarget::One(receptor.contract_address), shrine_utils::admin()); |
| 39 | + let new_addr: ContractAddress = receptor_utils::mock_oracle_extension(); |
| 40 | + receptor.set_oracle_extension(new_addr); |
| 41 | + |
| 42 | + assert_eq!(receptor.get_oracle_extension(), new_addr, "wrong extension addr"); |
| 43 | + } |
| 44 | + |
| 45 | + #[test] |
| 46 | + #[should_panic(expected: ('Caller missing role',))] |
| 47 | + fn test_set_oracle_extension_unauthorized() { |
| 48 | + let (_, receptor, _) = receptor_utils::receptor_deploy(Option::None); |
| 49 | + |
| 50 | + start_prank(CheatTarget::One(receptor.contract_address), common::badguy()); |
| 51 | + receptor.set_oracle_extension(receptor_utils::mock_oracle_extension()); |
| 52 | + } |
| 53 | + |
| 54 | + #[test] |
| 55 | + fn test_set_twap_duration_pass() { |
| 56 | + let (_, receptor, _) = receptor_utils::receptor_deploy(Option::None); |
| 57 | + let mut spy = spy_events(SpyOn::One(receptor.contract_address)); |
| 58 | + |
| 59 | + start_prank(CheatTarget::One(receptor.contract_address), shrine_utils::admin().into()); |
| 60 | + let old_duration: u64 = receptor_utils::INITIAL_TWAP_DURATION; |
| 61 | + let new_duration: u64 = old_duration + 1; |
| 62 | + receptor.set_twap_duration(new_duration); |
| 63 | + |
| 64 | + let expected_events = array![ |
| 65 | + ( |
| 66 | + receptor.contract_address, |
| 67 | + receptor_contract::Event::TwapDurationUpdated( |
| 68 | + receptor_contract::TwapDurationUpdated { old_duration, new_duration } |
| 69 | + ) |
| 70 | + ) |
| 71 | + ]; |
| 72 | + |
| 73 | + spy.assert_emitted(@expected_events); |
| 74 | + } |
| 75 | + |
| 76 | + #[test] |
| 77 | + #[should_panic(expected: ('REC: TWAP duration is 0',))] |
| 78 | + fn test_set_twap_duration_zero_fail() { |
| 79 | + let (_, receptor, _) = receptor_utils::receptor_deploy(Option::None); |
| 80 | + |
| 81 | + start_prank(CheatTarget::One(receptor.contract_address), shrine_utils::admin().into()); |
| 82 | + receptor.set_twap_duration(0); |
| 83 | + } |
| 84 | + |
| 85 | + #[test] |
| 86 | + #[should_panic(expected: ('Caller missing role',))] |
| 87 | + fn test_set_twap_duration_unauthorized_fail() { |
| 88 | + let (_, receptor, _) = receptor_utils::receptor_deploy(Option::None); |
| 89 | + |
| 90 | + start_prank(CheatTarget::One(receptor.contract_address), common::badguy()); |
| 91 | + receptor.set_twap_duration(receptor_utils::INITIAL_TWAP_DURATION + 1); |
| 92 | + } |
| 93 | + |
| 94 | + #[test] |
| 95 | + fn test_set_update_frequency_pass() { |
| 96 | + let (_, receptor, _) = receptor_utils::receptor_deploy(Option::None); |
| 97 | + let mut spy = spy_events(SpyOn::One(receptor.contract_address)); |
| 98 | + |
| 99 | + let old_frequency: u64 = receptor_utils::INITIAL_UPDATE_FREQUENCY; |
| 100 | + let new_frequency: u64 = old_frequency + 1; |
| 101 | + start_prank(CheatTarget::One(receptor.contract_address), shrine_utils::admin()); |
| 102 | + receptor.set_update_frequency(new_frequency); |
| 103 | + |
| 104 | + assert_eq!(receptor.get_update_frequency(), new_frequency, "wrong update frequency"); |
| 105 | + |
| 106 | + let expected_events = array![ |
| 107 | + ( |
| 108 | + receptor.contract_address, |
| 109 | + receptor_contract::Event::UpdateFrequencyUpdated( |
| 110 | + receptor_contract::UpdateFrequencyUpdated { old_frequency, new_frequency } |
| 111 | + ) |
| 112 | + ) |
| 113 | + ]; |
| 114 | + |
| 115 | + spy.assert_emitted(@expected_events); |
| 116 | + } |
| 117 | + |
| 118 | + #[test] |
| 119 | + #[should_panic(expected: ('Caller missing role',))] |
| 120 | + fn test_set_update_frequency_unauthorized() { |
| 121 | + let (_, receptor, _) = receptor_utils::receptor_deploy(Option::None); |
| 122 | + start_prank(CheatTarget::One(receptor.contract_address), common::badguy()); |
| 123 | + receptor.set_update_frequency(receptor_utils::INITIAL_UPDATE_FREQUENCY - 1); |
| 124 | + } |
| 125 | + |
| 126 | + #[test] |
| 127 | + #[should_panic(expected: ('REC: Frequency out of bounds',))] |
| 128 | + fn test_set_update_frequency_oob_lower() { |
| 129 | + let (_, receptor, _) = receptor_utils::receptor_deploy(Option::None); |
| 130 | + |
| 131 | + let new_frequency: u64 = receptor_contract::LOWER_UPDATE_FREQUENCY_BOUND - 1; |
| 132 | + start_prank(CheatTarget::One(receptor.contract_address), shrine_utils::admin()); |
| 133 | + receptor.set_update_frequency(new_frequency); |
| 134 | + } |
| 135 | + |
| 136 | + #[test] |
| 137 | + #[should_panic(expected: ('REC: Frequency out of bounds',))] |
| 138 | + fn test_set_update_frequency_oob_higher() { |
| 139 | + let (_, receptor, _) = receptor_utils::receptor_deploy(Option::None); |
| 140 | + |
| 141 | + let new_frequency: u64 = receptor_contract::UPPER_UPDATE_FREQUENCY_BOUND + 1; |
| 142 | + start_prank(CheatTarget::One(receptor.contract_address), shrine_utils::admin()); |
| 143 | + receptor.set_update_frequency(new_frequency); |
| 144 | + } |
| 145 | + |
| 146 | + // Core functionality |
| 147 | + |
| 148 | + #[test] |
| 149 | + fn test_update_yin_price() { |
| 150 | + let (shrine, receptor, mock_ekubo_oracle_extension_addr) = receptor_utils::receptor_deploy(Option::None); |
| 151 | + let mut shrine_spy = spy_events(SpyOn::One(shrine.contract_address)); |
| 152 | + let mut receptor_spy = spy_events(SpyOn::One(receptor.contract_address)); |
| 153 | + |
| 154 | + let before_yin_spot_price: Wad = shrine.get_yin_spot_price(); |
| 155 | + |
| 156 | + let quote_tokens: Span<QuoteTokenInfo> = receptor_utils::quote_tokens(); |
| 157 | + // actual mainnet values from 1727418625 start time to 1727429425 end time |
| 158 | + // converted in python |
| 159 | + let prices: Span<u256> = array![ |
| 160 | + 340309250276362099785975626643777172060, // 1.000158012403645039602034587 DAI / CASH |
| 161 | + 340527434977254803682969657, // 1.001440899252887204535902704 USDC / CASH |
| 162 | + 340328625112763872478829777, // 1.000271899695698999556601210 USDT / CASH |
| 163 | + ] |
| 164 | + .span(); |
| 165 | + receptor_utils::set_next_prices( |
| 166 | + shrine.contract_address, mock_ekubo_oracle_extension_addr, quote_tokens, prices, |
| 167 | + ); |
| 168 | + |
| 169 | + let next_ts = get_block_timestamp() + receptor_utils::INITIAL_UPDATE_FREQUENCY; |
| 170 | + start_warp(CheatTarget::All, next_ts); |
| 171 | + |
| 172 | + let quotes: Span<Wad> = receptor.get_quotes(); |
| 173 | + let expected_yin_spot_price: Wad = *quotes[2]; |
| 174 | + let mut expected_prices: Span<Wad> = array![ |
| 175 | + 1000158012403645039_u128.into(), // DAI |
| 176 | + 1001440899252887204_u128.into(), // USDC |
| 177 | + 1000271899695698999_u128.into(), // USDT |
| 178 | + ] |
| 179 | + .span(); |
| 180 | + let error_margin: Wad = 200_u128.into(); |
| 181 | + |
| 182 | + let mut quotes_copy = quotes; |
| 183 | + loop { |
| 184 | + match quotes_copy.pop_front() { |
| 185 | + Option::Some(quote) => { |
| 186 | + let expected: Wad = *expected_prices.pop_front().unwrap(); |
| 187 | + common::assert_equalish(*quote, expected, error_margin, 'wrong quote'); |
| 188 | + }, |
| 189 | + Option::None => { break; }, |
| 190 | + }; |
| 191 | + }; |
| 192 | + |
| 193 | + start_prank(CheatTarget::One(receptor.contract_address), shrine_utils::admin()); |
| 194 | + receptor.update_yin_price(); |
| 195 | + |
| 196 | + let after_yin_spot_price: Wad = shrine.get_yin_spot_price(); |
| 197 | + assert_eq!(after_yin_spot_price, expected_yin_spot_price, "wrong yin price in shrine #1"); |
| 198 | + |
| 199 | + let expected_receptor_events = array![ |
| 200 | + ( |
| 201 | + receptor.contract_address, |
| 202 | + receptor_contract::Event::ValidQuotes(receptor_contract::ValidQuotes { quotes }) |
| 203 | + ) |
| 204 | + ]; |
| 205 | + receptor_spy.assert_emitted(@expected_receptor_events); |
| 206 | + |
| 207 | + let expected_shrine_events = array![ |
| 208 | + ( |
| 209 | + shrine.contract_address, |
| 210 | + shrine_contract::Event::YinPriceUpdated( |
| 211 | + shrine_contract::YinPriceUpdated { |
| 212 | + old_price: before_yin_spot_price, new_price: after_yin_spot_price |
| 213 | + } |
| 214 | + ) |
| 215 | + ) |
| 216 | + ]; |
| 217 | + shrine_spy.assert_emitted(@expected_shrine_events); |
| 218 | + |
| 219 | + // test unsuccessful update due to a zero price quote |
| 220 | + let prices: Span<u256> = array![ |
| 221 | + 340309250276362099785975626643777172060, // 1.000158012403645039602034587 DAI / CASH |
| 222 | + 0, // 1.001440899252887204535902704 USDC / CASH |
| 223 | + 340328625112763872478829777, // 1.000271899695698999556601210 USDT / CASH |
| 224 | + ] |
| 225 | + .span(); |
| 226 | + receptor_utils::set_next_prices( |
| 227 | + shrine.contract_address, mock_ekubo_oracle_extension_addr, quote_tokens, prices, |
| 228 | + ); |
| 229 | + |
| 230 | + let next_ts = get_block_timestamp() + receptor_utils::INITIAL_UPDATE_FREQUENCY; |
| 231 | + start_warp(CheatTarget::All, next_ts); |
| 232 | + |
| 233 | + let quotes: Span<Wad> = receptor.get_quotes(); |
| 234 | + |
| 235 | + receptor.update_yin_price(); |
| 236 | + |
| 237 | + assert_eq!(shrine.get_yin_spot_price(), expected_yin_spot_price, "wrong yin price in shrine #2"); |
| 238 | + |
| 239 | + let expected_receptor_events = array![ |
| 240 | + ( |
| 241 | + receptor.contract_address, |
| 242 | + receptor_contract::Event::InvalidQuotes(receptor_contract::InvalidQuotes { quotes }) |
| 243 | + ) |
| 244 | + ]; |
| 245 | + receptor_spy.assert_emitted(@expected_receptor_events); |
| 246 | + } |
| 247 | + |
| 248 | + #[test] |
| 249 | + fn test_update_yin_price_via_execute_task() { |
| 250 | + let (shrine, receptor, mock_ekubo_oracle_extension_addr) = receptor_utils::receptor_deploy(Option::None); |
| 251 | + |
| 252 | + let quote_tokens: Span<QuoteTokenInfo> = receptor_utils::quote_tokens(); |
| 253 | + // actual mainnet values from 1727418625 start time to 1727429425 end time |
| 254 | + // converted in python |
| 255 | + let prices: Span<u256> = array![ |
| 256 | + 340309250276362099785975626643777172060, // 1.000158012403645039602034587 DAI / CASH |
| 257 | + 340527434977254803682969657, // 1.001440899252887204535902704 USDC / CASH |
| 258 | + 340328625112763872478829777, // 1.000271899695698999556601210 USDT / CASH |
18 | 259 | ]
|
19 | 260 | .span();
|
| 261 | + receptor_utils::set_next_prices( |
| 262 | + shrine.contract_address, mock_ekubo_oracle_extension_addr, quote_tokens, prices, |
| 263 | + ); |
| 264 | + |
| 265 | + let next_ts = get_block_timestamp() + receptor_utils::INITIAL_UPDATE_FREQUENCY; |
| 266 | + start_warp(CheatTarget::All, next_ts); |
| 267 | + |
| 268 | + ITaskDispatcher { contract_address: receptor.contract_address }.execute_task(); |
20 | 269 |
|
21 |
| - let receptor = receptor_utils::receptor_deploy( |
22 |
| - shrine.contract_address, |
23 |
| - receptor_utils::mock_oracle_extension(), |
24 |
| - receptor_utils::INITIAL_TWAP_DURATION, |
25 |
| - quote_tokens, |
26 |
| - Option::None |
| 270 | + let quotes: Span<Wad> = receptor.get_quotes(); |
| 271 | + let expected_yin_spot_price: Wad = *quotes[2]; |
| 272 | + |
| 273 | + let after_yin_spot_price: Wad = shrine.get_yin_spot_price(); |
| 274 | + assert_eq!(after_yin_spot_price, expected_yin_spot_price, "wrong yin price in shrine #1"); |
| 275 | + } |
| 276 | + |
| 277 | + #[test] |
| 278 | + fn test_probe_task() { |
| 279 | + let (shrine, receptor, mock_ekubo_oracle_extension_addr) = receptor_utils::receptor_deploy(Option::None); |
| 280 | + |
| 281 | + let quote_tokens: Span<QuoteTokenInfo> = receptor_utils::quote_tokens(); |
| 282 | + // actual mainnet values from 1727418625 start time to 1727429425 end time |
| 283 | + // converted in python |
| 284 | + let prices: Span<u256> = array![ |
| 285 | + 340309250276362099785975626643777172060, // 1.000158012403645039602034587 DAI / CASH |
| 286 | + 340527434977254803682969657, // 1.001440899252887204535902704 USDC / CASH |
| 287 | + 340328625112763872478829777, // 1.000271899695698999556601210 USDT / CASH |
| 288 | + ] |
| 289 | + .span(); |
| 290 | + receptor_utils::set_next_prices( |
| 291 | + shrine.contract_address, mock_ekubo_oracle_extension_addr, quote_tokens, prices, |
27 | 292 | );
|
28 | 293 |
|
29 |
| - println!("Receptor deployed"); |
| 294 | + let task = ITaskDispatcher { contract_address: receptor.contract_address }; |
| 295 | + assert(task.probe_task(), 'should be ready 1'); |
| 296 | + |
| 297 | + task.execute_task(); |
| 298 | + |
| 299 | + assert(!task.probe_task(), 'should not be ready 1'); |
| 300 | + |
| 301 | + start_warp(CheatTarget::All, get_block_timestamp() + receptor.get_update_frequency() - 1); |
| 302 | + assert(!task.probe_task(), 'should not be ready 2'); |
| 303 | + |
| 304 | + start_warp(CheatTarget::All, get_block_timestamp() + 1); |
| 305 | + assert(task.probe_task(), 'should be ready 2'); |
30 | 306 | }
|
31 | 307 | }
|
0 commit comments