Skip to main content

perpl_sdk/testing/
perp.rs

1use alloy::{
2    network::Ethereum,
3    primitives::{Address, I256, U256},
4    providers::PendingTransactionBuilder,
5};
6use fastnum::UD64;
7
8use super::TestExchange;
9use crate::{abi::dex_legacy::LegacyExchange, error::DexError, num, state, types};
10
11#[derive(Debug)]
12pub struct TestPerp<'e> {
13    pub id: types::PerpetualId,
14    pub name: String,
15    pub price_converter: num::Converter,
16    pub size_converter: num::Converter,
17    pub leverage_converter: num::Converter,
18    pub exchange: &'e TestExchange,
19}
20
21impl<'e> TestPerp<'e> {
22    pub async fn with_mark_price(self, price: UD64) -> Self {
23        self.exchange
24            .exchange
25            .updateMarkPricePNS(U256::from(self.id), self.price_converter.to_unsigned(price).to())
26            .from(self.exchange.price_admin)
27            .gas(500000)
28            .send()
29            .await
30            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
31            .unwrap()
32            .get_receipt()
33            .await
34            .unwrap();
35        self
36    }
37
38    pub async fn with_min_post(self, min: U256) -> Self {
39        self.exchange
40            .exchange
41            .setMinPost(min)
42            .send()
43            .await
44            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
45            .unwrap()
46            .get_receipt()
47            .await
48            .unwrap();
49        self
50    }
51
52    pub async fn with_min_settle(self, min: U256) -> Self {
53        self.exchange
54            .exchange
55            .setMinSettle(min)
56            .send()
57            .await
58            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
59            .unwrap()
60            .get_receipt()
61            .await
62            .unwrap();
63        self
64    }
65
66    pub async fn unpause(self) -> Self {
67        self.exchange
68            .exchange
69            .setContractPaused(U256::from(self.id), false)
70            .gas(500000)
71            .send()
72            .await
73            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
74            .unwrap()
75            .get_receipt()
76            .await
77            .unwrap();
78        self
79    }
80
81    pub async fn set_maintenance_margin(
82        &self,
83        maintenance_margin: UD64,
84    ) -> PendingTransactionBuilder<Ethereum> {
85        self.exchange
86            .exchange
87            .setMaintenanceMarginFraction(
88                U256::from(self.id),
89                self.leverage_converter.to_unsigned(maintenance_margin),
90            )
91            .gas(500000)
92            .send()
93            .await
94            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
95            .unwrap()
96    }
97
98    pub async fn set_mark_price(&self, price: UD64) -> PendingTransactionBuilder<Ethereum> {
99        self.exchange
100            .exchange
101            .updateMarkPricePNS(U256::from(self.id), self.price_converter.to_unsigned(price).to())
102            .from(self.exchange.price_admin)
103            .gas(500000)
104            .send()
105            .await
106            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
107            .unwrap()
108    }
109
110    pub async fn set_funding_rate(
111        &self,
112        price: u32,
113        rate: i32,
114    ) -> PendingTransactionBuilder<Ethereum> {
115        self.exchange
116            .exchange
117            .setFundingSum(U256::from(self.id), I256::try_from(rate).unwrap(), price, true, true)
118            .from(self.exchange.anvil.addresses()[2]) // From Price Admin
119            .gas(500000)
120            .send()
121            .await
122            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
123            .unwrap()
124    }
125
126    /// Sets this contract's fees through the pre-v1.1.7.4 per-contract setters,
127    /// for use with [`TestExchange::new_at_previous_version`].
128    ///
129    /// The current contract has no equivalent - fees live in the keyed
130    /// schedules and these setters were removed - so this reverts against
131    /// it. Emits the deprecated `TakerFeeUpdated`/`MakerFeeUpdated` events.
132    pub async fn with_legacy_fees(self, taker_fee: UD64, maker_fee: UD64) -> Self {
133        let legacy =
134            LegacyExchange::new(*self.exchange.exchange.address(), self.exchange.provider.clone());
135        // Per100K: this reaches the PREVIOUS generation, which reads fee rates
136        // against 100,000. The current setters use ppm.
137        let fee_converter = num::fee_converter();
138        legacy
139            .setTakerFee(U256::from(self.id), fee_converter.to_unsigned(taker_fee))
140            .gas(500000)
141            .send()
142            .await
143            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
144            .unwrap()
145            .get_receipt()
146            .await
147            .unwrap();
148        legacy
149            .setMakerFee(U256::from(self.id), fee_converter.to_unsigned(maker_fee))
150            .gas(500000)
151            .send()
152            .await
153            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
154            .unwrap()
155            .get_receipt()
156            .await
157            .unwrap();
158        self
159    }
160
161    /// Sets a custom fee schedule for this perpetual contract, eight
162    /// `(taker, maker)` rates indexed by an account's fee tier.
163    ///
164    /// v1.1.7.4 split the one-shot custom-schedule setter into value-set +
165    /// repoint, done here back to back. Use
166    /// [`Self::set_own_fee_schedule_values`] and [`Self::use_own_fee_schedule`]
167    /// to exercise the two halves apart.
168    pub async fn set_fee_schedule(
169        &self,
170        taker_fees: [UD64; state::FEE_TIERS],
171        maker_fees: [UD64; state::FEE_TIERS],
172    ) -> PendingTransactionBuilder<Ethereum> {
173        self.set_own_fee_schedule_values(taker_fees, maker_fees)
174            .await;
175        self.use_own_fee_schedule().await
176    }
177
178    /// Writes the rates of the schedule keyed by this contract's id, without
179    /// pointing the contract at it - schedule values and the pointer into them
180    /// are independent.
181    pub async fn set_own_fee_schedule_values(
182        &self,
183        taker_fees: [UD64; state::FEE_TIERS],
184        maker_fees: [UD64; state::FEE_TIERS],
185    ) {
186        // ppm, like every other schedule setter here -- see
187        // `TestExchange::set_fee_schedule`.
188        let fee_converter = num::ppm_fee_converter();
189        self.exchange
190            .exchange
191            .setFeeSchedValues(
192                U256::from(self.id),
193                taker_fees.map(|fee| fee_converter.to_unsigned(fee)),
194                maker_fees.map(|fee| fee_converter.to_unsigned(fee)),
195            )
196            .gas(500000)
197            .send()
198            .await
199            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
200            .unwrap()
201            .get_receipt()
202            .await
203            .unwrap();
204    }
205
206    /// Points this perpetual contract at the schedule keyed by its own id, see
207    /// [`Self::set_own_fee_schedule_values`].
208    pub async fn use_own_fee_schedule(&self) -> PendingTransactionBuilder<Ethereum> {
209        self.exchange
210            .exchange
211            .setPerpToFeeSchedule(U256::from(self.id), U256::from(self.id))
212            .gas(500000)
213            .send()
214            .await
215            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
216            .unwrap()
217    }
218
219    /// Repoints this perpetual contract at the exchange-wide RWA default fee
220    /// schedule.
221    pub async fn set_rwa_default_fee(&self) -> PendingTransactionBuilder<Ethereum> {
222        self.exchange
223            .exchange
224            .setPerpToDefaultRwaFeeSched(U256::from(self.id))
225            .gas(500000)
226            .send()
227            .await
228            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
229            .unwrap()
230    }
231
232    /// Posts an order via the V1 entrypoint, which carries no builder
233    /// attribution.
234    pub async fn order(
235        &self,
236        account_id: types::AccountId,
237        request: types::OrderRequest,
238    ) -> PendingTransactionBuilder<Ethereum> {
239        self.exchange
240            .exchange
241            .execOrder(request.to_order_desc(
242                self.price_converter,
243                self.size_converter,
244                self.leverage_converter,
245                Some(self.exchange.collateral_converter),
246            ))
247            .from(self.account_address(account_id))
248            .gas(5000000)
249            .send()
250            .await
251            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
252            .unwrap()
253    }
254
255    /// Posts an order via the V2 entrypoint, carrying the request's builder
256    /// attribution if any.
257    pub async fn order_v2(
258        &self,
259        account_id: types::AccountId,
260        request: types::OrderRequest,
261    ) -> PendingTransactionBuilder<Ethereum> {
262        self.exchange
263            .exchange
264            .execOrderV2(
265                request.to_order_desc(
266                    self.price_converter,
267                    self.size_converter,
268                    self.leverage_converter,
269                    Some(self.exchange.collateral_converter),
270                ),
271                request.to_order_extension().unwrap(),
272            )
273            .from(self.account_address(account_id))
274            .gas(5000000)
275            .send()
276            .await
277            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
278            .unwrap()
279    }
280
281    /// Posts a batch of orders via the V2 entrypoint, carrying each request's
282    /// builder attribution if any.
283    pub async fn orders_v2(
284        &self,
285        account_id: types::AccountId,
286        requests: Vec<types::OrderRequest>,
287        revert_on_fail: bool,
288    ) -> PendingTransactionBuilder<Ethereum> {
289        self.exchange
290            .exchange
291            .execOrdersV2(
292                requests
293                    .iter()
294                    .map(|req| {
295                        req.to_order_desc(
296                            self.price_converter,
297                            self.size_converter,
298                            self.leverage_converter,
299                            Some(self.exchange.collateral_converter),
300                        )
301                    })
302                    .collect(),
303                revert_on_fail,
304                requests
305                    .iter()
306                    .map(|req| req.to_order_extension().unwrap())
307                    .collect(),
308            )
309            .from(self.account_address(account_id))
310            .gas(150000000)
311            .send()
312            .await
313            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
314            .unwrap()
315    }
316
317    fn account_address(&self, account_id: types::AccountId) -> Address {
318        *self
319            .exchange
320            .account_address
321            .get(&account_id)
322            .unwrap()
323            .value()
324    }
325
326    pub async fn orders(
327        &self,
328        account_id: types::AccountId,
329        requests: Vec<types::OrderRequest>,
330    ) -> PendingTransactionBuilder<Ethereum> {
331        self.exchange
332            .exchange
333            .execOrders(
334                requests
335                    .iter()
336                    .map(|req| {
337                        req.to_order_desc(
338                            self.price_converter,
339                            self.size_converter,
340                            self.leverage_converter,
341                            Some(self.exchange.collateral_converter),
342                        )
343                    })
344                    .collect(),
345                true,
346            )
347            .from(self.account_address(account_id))
348            .gas(150000000)
349            .send()
350            .await
351            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
352            .unwrap()
353    }
354}