1use crate::OrdersAndFills;
11use alloy::{
12 primitives::{Address, U256},
13 providers::Provider,
14};
15use futures_util::future::{try_join, try_join3, try_join_all};
16use signet_types::{SignedOrder, UnsignedOrder};
17use signet_zenith::{IPermit2, IERC20, PERMIT2_ADDRESS};
18use std::future::Future;
19use thiserror::Error;
20
21#[derive(Debug, Error)]
23#[non_exhaustive]
24pub enum PreflightError {
25 #[error("provider error: {0}")]
27 Provider(#[from] alloy::contract::Error),
28 #[error("insufficient balance: have {have}, need {need}")]
30 InsufficientBalance {
31 have: U256,
33 need: U256,
35 },
36 #[error("insufficient allowance: have {have}, need {need}")]
38 InsufficientAllowance {
39 have: U256,
41 need: U256,
43 },
44 #[error("nonce already consumed: word_pos={word_pos}, bit_pos={bit_pos}")]
46 NonceConsumed {
47 word_pos: U256,
49 bit_pos: u8,
51 },
52}
53
54pub trait Permit2Ext: Sync {
76 fn sufficient_balance(
78 &self,
79 token: Address,
80 user: Address,
81 amount: U256,
82 ) -> impl Future<Output = Result<(), PreflightError>> + Send;
83
84 fn token_approved(
86 &self,
87 token: Address,
88 user: Address,
89 amount: U256,
90 ) -> impl Future<Output = Result<(), PreflightError>> + Send;
91
92 fn nonce_available(
94 &self,
95 user: Address,
96 nonce: U256,
97 ) -> impl Future<Output = Result<(), PreflightError>> + Send;
98
99 fn check_signed_order(
104 &self,
105 order: &SignedOrder,
106 ) -> impl Future<Output = Result<(), PreflightError>> + Send {
107 async move {
108 let permit = order.permit();
109 let owner = permit.owner;
110
111 let balance_checks = permit
112 .permit
113 .permitted
114 .iter()
115 .map(|tp| self.sufficient_balance(tp.token, owner, tp.amount));
116 let approval_checks = permit
117 .permit
118 .permitted
119 .iter()
120 .map(|tp| self.token_approved(tp.token, owner, tp.amount));
121
122 try_join3(
123 try_join_all(balance_checks),
124 try_join_all(approval_checks),
125 self.nonce_available(owner, permit.permit.nonce),
126 )
127 .await
128 .map(|_| ())
129 }
130 }
131
132 fn check_unsigned_order(
137 &self,
138 order: &UnsignedOrder<'_>,
139 user: Address,
140 ) -> impl Future<Output = Result<(), PreflightError>> + Send {
141 async move {
142 let balance_checks = order
143 .inputs()
144 .iter()
145 .map(|input| self.sufficient_balance(input.token, user, input.amount));
146 let approval_checks = order
147 .inputs()
148 .iter()
149 .map(|input| self.token_approved(input.token, user, input.amount));
150
151 try_join(try_join_all(balance_checks), try_join_all(approval_checks)).await.map(|_| ())
152 }
153 }
154
155 fn check_orders_and_fills(
161 &self,
162 orders_and_fills: &OrdersAndFills,
163 ) -> impl Future<Output = Result<(), PreflightError>> + Send {
164 async move {
165 try_join_all(
166 orders_and_fills.orders().iter().map(|order| self.check_signed_order(order)),
167 )
168 .await
169 .map(|_| ())
170 }
171 }
172}
173
174impl<P: Provider> Permit2Ext for P {
175 async fn sufficient_balance(
176 &self,
177 token: Address,
178 user: Address,
179 amount: U256,
180 ) -> Result<(), PreflightError> {
181 let balance = IERC20::new(token, self).balanceOf(user).call().await?;
182 (balance >= amount)
183 .then_some(())
184 .ok_or(PreflightError::InsufficientBalance { have: balance, need: amount })
185 }
186
187 async fn token_approved(
188 &self,
189 token: Address,
190 user: Address,
191 amount: U256,
192 ) -> Result<(), PreflightError> {
193 let allowance = IERC20::new(token, self).allowance(user, PERMIT2_ADDRESS).call().await?;
194 (allowance >= amount)
195 .then_some(())
196 .ok_or(PreflightError::InsufficientAllowance { have: allowance, need: amount })
197 }
198
199 async fn nonce_available(&self, user: Address, nonce: U256) -> Result<(), PreflightError> {
200 let permit2 = IPermit2::new(PERMIT2_ADDRESS, self);
201 let (word_pos, bit_pos) = permit2.nonce_to_bitmap_position(nonce);
202 let bitmap = permit2.nonceBitmap(user, word_pos).call().await?;
203 (bitmap & (U256::from(1) << bit_pos) == U256::ZERO)
204 .then_some(())
205 .ok_or(PreflightError::NonceConsumed { word_pos, bit_pos })
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::PreflightError;
212 use alloy::primitives::uint;
213 use signet_zenith::PERMIT2_ADDRESS;
214
215 #[test]
216 fn test_preflight_errors() {
217 let insufficient_balance =
218 PreflightError::InsufficientBalance { have: uint!(100_U256), need: uint!(200_U256) };
219 assert!(insufficient_balance.to_string().contains("insufficient balance"));
220 assert!(insufficient_balance.to_string().contains("100"));
221 assert!(insufficient_balance.to_string().contains("200"));
222
223 let insufficient_allowance =
224 PreflightError::InsufficientAllowance { have: uint!(50_U256), need: uint!(100_U256) };
225 assert!(insufficient_allowance.to_string().contains("insufficient allowance"));
226
227 let nonce_consumed = PreflightError::NonceConsumed { word_pos: uint!(1_U256), bit_pos: 42 };
228 assert!(nonce_consumed.to_string().contains("nonce already consumed"));
229 assert!(nonce_consumed.to_string().contains("word_pos=1"));
230 assert!(nonce_consumed.to_string().contains("bit_pos=42"));
231 }
232
233 #[test]
234 fn test_permit2_address_matches_types() {
235 assert_eq!(
236 PERMIT2_ADDRESS,
237 alloy::primitives::address!("0x000000000022D473030F116dDEE9F6B43aC78BA3")
238 );
239 }
240}