Skip to content

Commit b23bc8a

Browse files
committed
add tests
1 parent c9b9bd7 commit b23bc8a

File tree

4 files changed

+412
-30
lines changed

4 files changed

+412
-30
lines changed

src/lib.cairo

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ pub mod mock {
7373
pub mod erc20_mintable;
7474
pub mod flash_borrower;
7575
pub mod flash_liquidator;
76+
pub mod mock_ekubo_oracle_extension;
7677
pub mod mock_pragma;
7778
pub mod mock_switchboard;
7879
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use starknet::ContractAddress;
2+
3+
#[starknet::interface]
4+
pub trait IMockEkuboOracleExtension<TContractState> {
5+
// Timestamps are ignored
6+
fn next_get_price_x128_over_period(
7+
ref self: TContractState, base_token: ContractAddress, quote_token: ContractAddress, price: u256
8+
);
9+
}
10+
11+
#[starknet::contract]
12+
pub mod mock_ekubo_oracle_extension {
13+
use opus::external::interfaces::IEkuboOracleExtension;
14+
use starknet::ContractAddress;
15+
use super::IMockEkuboOracleExtension;
16+
17+
#[storage]
18+
struct Storage {
19+
// Mapping from (base token, quote token) to x128 price
20+
price: LegacyMap::<(ContractAddress, ContractAddress), u256>,
21+
}
22+
23+
#[abi(embed_v0)]
24+
impl IMockEkuboOracleExtensionImpl of IMockEkuboOracleExtension<ContractState> {
25+
fn next_get_price_x128_over_period(
26+
ref self: ContractState, base_token: ContractAddress, quote_token: ContractAddress, price: u256
27+
) {
28+
self.price.write((base_token, quote_token), price);
29+
}
30+
}
31+
32+
#[abi(embed_v0)]
33+
impl IEkuboOracleExtensionImpl of IEkuboOracleExtension<ContractState> {
34+
fn get_price_x128_over_period(
35+
self: @ContractState,
36+
base_token: ContractAddress,
37+
quote_token: ContractAddress,
38+
start_time: u64,
39+
end_time: u64
40+
) -> u256 {
41+
self.price.read((base_token, quote_token))
42+
}
43+
}
44+
}
Lines changed: 289 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,307 @@
11
mod test_receptor {
2-
use opus::constants::{DAI_DECIMALS, USDC_DECIMALS, USDT_DECIMALS};
2+
use access_control::{IAccessControlDispatcher, IAccessControlDispatcherTrait};
33
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};
48
use opus::interfaces::IShrine::{IShrineDispatcher, IShrineDispatcherTrait};
9+
use opus::tests::common;
510
use opus::tests::receptor::utils::receptor_utils;
611
use opus::tests::shrine::utils::shrine_utils;
712
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;
816

917

1018
#[test]
1119
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);
1321

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
18259
]
19260
.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();
20269

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,
27292
);
28293

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');
30306
}
31307
}

0 commit comments

Comments
 (0)