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        let fee_converter = num::fee_converter();
136        legacy
137            .setTakerFee(U256::from(self.id), fee_converter.to_unsigned(taker_fee))
138            .gas(500000)
139            .send()
140            .await
141            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
142            .unwrap()
143            .get_receipt()
144            .await
145            .unwrap();
146        legacy
147            .setMakerFee(U256::from(self.id), fee_converter.to_unsigned(maker_fee))
148            .gas(500000)
149            .send()
150            .await
151            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
152            .unwrap()
153            .get_receipt()
154            .await
155            .unwrap();
156        self
157    }
158
159    /// Sets a custom fee schedule for this perpetual contract, eight
160    /// `(taker, maker)` rates indexed by an account's fee tier.
161    ///
162    /// v1.1.7.4 split the one-shot custom-schedule setter into value-set +
163    /// repoint, done here back to back. Use
164    /// [`Self::set_own_fee_schedule_values`] and [`Self::use_own_fee_schedule`]
165    /// to exercise the two halves apart.
166    pub async fn set_fee_schedule(
167        &self,
168        taker_fees: [UD64; state::FEE_TIERS],
169        maker_fees: [UD64; state::FEE_TIERS],
170    ) -> PendingTransactionBuilder<Ethereum> {
171        self.set_own_fee_schedule_values(taker_fees, maker_fees)
172            .await;
173        self.use_own_fee_schedule().await
174    }
175
176    /// Writes the rates of the schedule keyed by this contract's id, without
177    /// pointing the contract at it - schedule values and the pointer into them
178    /// are independent.
179    pub async fn set_own_fee_schedule_values(
180        &self,
181        taker_fees: [UD64; state::FEE_TIERS],
182        maker_fees: [UD64; state::FEE_TIERS],
183    ) {
184        let fee_converter = num::fee_converter();
185        self.exchange
186            .exchange
187            .setFeeSchedValues(
188                U256::from(self.id),
189                taker_fees.map(|fee| fee_converter.to_unsigned(fee)),
190                maker_fees.map(|fee| fee_converter.to_unsigned(fee)),
191            )
192            .gas(500000)
193            .send()
194            .await
195            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
196            .unwrap()
197            .get_receipt()
198            .await
199            .unwrap();
200    }
201
202    /// Points this perpetual contract at the schedule keyed by its own id, see
203    /// [`Self::set_own_fee_schedule_values`].
204    pub async fn use_own_fee_schedule(&self) -> PendingTransactionBuilder<Ethereum> {
205        self.exchange
206            .exchange
207            .setPerpToFeeSchedule(U256::from(self.id), U256::from(self.id))
208            .gas(500000)
209            .send()
210            .await
211            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
212            .unwrap()
213    }
214
215    /// Repoints this perpetual contract at the exchange-wide RWA default fee
216    /// schedule.
217    pub async fn set_rwa_default_fee(&self) -> PendingTransactionBuilder<Ethereum> {
218        self.exchange
219            .exchange
220            .setPerpToDefaultRwaFeeSched(U256::from(self.id))
221            .gas(500000)
222            .send()
223            .await
224            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
225            .unwrap()
226    }
227
228    /// Posts an order via the V1 entrypoint, which carries no builder
229    /// attribution.
230    pub async fn order(
231        &self,
232        account_id: types::AccountId,
233        request: types::OrderRequest,
234    ) -> PendingTransactionBuilder<Ethereum> {
235        self.exchange
236            .exchange
237            .execOrder(request.to_order_desc(
238                self.price_converter,
239                self.size_converter,
240                self.leverage_converter,
241                Some(self.exchange.collateral_converter),
242            ))
243            .from(self.account_address(account_id))
244            .gas(5000000)
245            .send()
246            .await
247            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
248            .unwrap()
249    }
250
251    /// Posts an order via the V2 entrypoint, carrying the request's builder
252    /// attribution if any.
253    pub async fn order_v2(
254        &self,
255        account_id: types::AccountId,
256        request: types::OrderRequest,
257    ) -> PendingTransactionBuilder<Ethereum> {
258        self.exchange
259            .exchange
260            .execOrderV2(
261                request.to_order_desc(
262                    self.price_converter,
263                    self.size_converter,
264                    self.leverage_converter,
265                    Some(self.exchange.collateral_converter),
266                ),
267                request.to_order_extension().unwrap(),
268            )
269            .from(self.account_address(account_id))
270            .gas(5000000)
271            .send()
272            .await
273            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
274            .unwrap()
275    }
276
277    /// Posts a batch of orders via the V2 entrypoint, carrying each request's
278    /// builder attribution if any.
279    pub async fn orders_v2(
280        &self,
281        account_id: types::AccountId,
282        requests: Vec<types::OrderRequest>,
283        revert_on_fail: bool,
284    ) -> PendingTransactionBuilder<Ethereum> {
285        self.exchange
286            .exchange
287            .execOrdersV2(
288                requests
289                    .iter()
290                    .map(|req| {
291                        req.to_order_desc(
292                            self.price_converter,
293                            self.size_converter,
294                            self.leverage_converter,
295                            Some(self.exchange.collateral_converter),
296                        )
297                    })
298                    .collect(),
299                revert_on_fail,
300                requests
301                    .iter()
302                    .map(|req| req.to_order_extension().unwrap())
303                    .collect(),
304            )
305            .from(self.account_address(account_id))
306            .gas(150000000)
307            .send()
308            .await
309            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
310            .unwrap()
311    }
312
313    fn account_address(&self, account_id: types::AccountId) -> Address {
314        *self
315            .exchange
316            .account_address
317            .get(&account_id)
318            .unwrap()
319            .value()
320    }
321
322    pub async fn orders(
323        &self,
324        account_id: types::AccountId,
325        requests: Vec<types::OrderRequest>,
326    ) -> PendingTransactionBuilder<Ethereum> {
327        self.exchange
328            .exchange
329            .execOrders(
330                requests
331                    .iter()
332                    .map(|req| {
333                        req.to_order_desc(
334                            self.price_converter,
335                            self.size_converter,
336                            self.leverage_converter,
337                            Some(self.exchange.collateral_converter),
338                        )
339                    })
340                    .collect(),
341                true,
342            )
343            .from(self.account_address(account_id))
344            .gas(150000000)
345            .send()
346            .await
347            .map_err::<DexError, _>(|err| DexError::Provider(err.into()))
348            .unwrap()
349    }
350}