1use crate::error::SignerError;
28use crate::ethereum::keccak256;
29
30pub const PERMIT2_ADDRESS: [u8; 20] = [
32 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xd4, 0x73, 0x03, 0x0f, 0x11, 0x6d, 0xde, 0xe9, 0xf6, 0xb4,
33 0x3a, 0xc7, 0x8b, 0xa3,
34];
35
36pub const MAX_U48: u64 = (1u64 << 48) - 1;
38
39pub type Uint160 = [u8; 20];
41pub type Uint256 = [u8; 32];
43
44fn token_permissions_typehash() -> [u8; 32] {
50 keccak256(b"TokenPermissions(address token,uint256 amount)")
51}
52
53fn permit_details_typehash() -> [u8; 32] {
55 keccak256(b"PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)")
56}
57
58fn permit_single_typehash() -> [u8; 32] {
60 keccak256(b"PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)")
61}
62
63fn permit_batch_typehash() -> [u8; 32] {
65 keccak256(b"PermitBatch(PermitDetails[] details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)")
66}
67
68fn permit_transfer_from_typehash() -> [u8; 32] {
70 keccak256(b"PermitTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline)TokenPermissions(address token,uint256 amount)")
71}
72
73fn permit_batch_transfer_from_typehash() -> [u8; 32] {
75 keccak256(b"PermitBatchTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline)TokenPermissions(address token,uint256 amount)")
76}
77
78#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct PermitSingle {
85 pub token: [u8; 20],
87 pub amount: Uint160,
89 pub expiration: u64,
91 pub nonce: u64,
93 pub spender: [u8; 20],
95 pub sig_deadline: Uint256,
97}
98
99impl PermitSingle {
100 fn details_hash(&self) -> Result<[u8; 32], SignerError> {
102 let mut data = Vec::with_capacity(160);
103 data.extend_from_slice(&permit_details_typehash());
104 data.extend_from_slice(&pad_address(&self.token));
105 data.extend_from_slice(&pad_uint160(&self.amount));
106 data.extend_from_slice(&pad_u48(self.expiration, "expiration")?);
107 data.extend_from_slice(&pad_u48(self.nonce, "nonce")?);
108 Ok(keccak256(&data))
109 }
110
111 pub fn struct_hash(&self) -> Result<[u8; 32], SignerError> {
113 let mut data = Vec::with_capacity(128);
114 data.extend_from_slice(&permit_single_typehash());
115 data.extend_from_slice(&self.details_hash()?);
116 data.extend_from_slice(&pad_address(&self.spender));
117 data.extend_from_slice(&self.sig_deadline);
118 Ok(keccak256(&data))
119 }
120
121 pub fn signing_hash(&self, domain_separator: &[u8; 32]) -> Result<[u8; 32], SignerError> {
125 Ok(eip712_hash(domain_separator, &self.struct_hash()?))
126 }
127}
128
129#[derive(Debug, Clone, PartialEq, Eq)]
135pub struct PermitDetails {
136 pub token: [u8; 20],
138 pub amount: Uint160,
140 pub expiration: u64,
142 pub nonce: u64,
144}
145
146#[derive(Debug, Clone, PartialEq, Eq)]
148pub struct PermitBatch {
149 pub details: Vec<PermitDetails>,
151 pub spender: [u8; 20],
153 pub sig_deadline: Uint256,
155}
156
157impl PermitBatch {
158 pub fn struct_hash(&self) -> Result<[u8; 32], SignerError> {
160 let mut details_hashes = Vec::with_capacity(self.details.len() * 32);
161 for d in &self.details {
162 let mut h = Vec::with_capacity(160);
163 h.extend_from_slice(&permit_details_typehash());
164 h.extend_from_slice(&pad_address(&d.token));
165 h.extend_from_slice(&pad_uint160(&d.amount));
166 h.extend_from_slice(&pad_u48(d.expiration, "expiration")?);
167 h.extend_from_slice(&pad_u48(d.nonce, "nonce")?);
168 details_hashes.extend_from_slice(&keccak256(&h));
169 }
170 let details_array_hash = keccak256(&details_hashes);
171
172 let mut data = Vec::with_capacity(128);
173 data.extend_from_slice(&permit_batch_typehash());
174 data.extend_from_slice(&details_array_hash);
175 data.extend_from_slice(&pad_address(&self.spender));
176 data.extend_from_slice(&self.sig_deadline);
177 Ok(keccak256(&data))
178 }
179
180 pub fn signing_hash(&self, domain_separator: &[u8; 32]) -> Result<[u8; 32], SignerError> {
182 Ok(eip712_hash(domain_separator, &self.struct_hash()?))
183 }
184}
185
186#[derive(Debug, Clone, PartialEq, Eq)]
192pub struct PermitTransferFrom {
193 pub token: [u8; 20],
195 pub amount: Uint256,
197 pub nonce: Uint256,
199 pub deadline: Uint256,
201 pub spender: [u8; 20],
203}
204
205impl PermitTransferFrom {
206 fn token_permissions_hash(&self) -> [u8; 32] {
208 let mut data = Vec::with_capacity(96);
209 data.extend_from_slice(&token_permissions_typehash());
210 data.extend_from_slice(&pad_address(&self.token));
211 data.extend_from_slice(&self.amount);
212 keccak256(&data)
213 }
214
215 #[must_use]
217 pub fn struct_hash(&self) -> [u8; 32] {
218 let mut data = Vec::with_capacity(160);
219 data.extend_from_slice(&permit_transfer_from_typehash());
220 data.extend_from_slice(&self.token_permissions_hash());
221 data.extend_from_slice(&pad_address(&self.spender));
222 data.extend_from_slice(&self.nonce);
223 data.extend_from_slice(&self.deadline);
224 keccak256(&data)
225 }
226
227 #[must_use]
229 pub fn signing_hash(&self, domain_separator: &[u8; 32]) -> [u8; 32] {
230 eip712_hash(domain_separator, &self.struct_hash())
231 }
232}
233
234#[derive(Debug, Clone, PartialEq, Eq)]
236pub struct PermitBatchTransferFrom {
237 pub permitted: Vec<TokenPermissions>,
239 pub nonce: Uint256,
241 pub deadline: Uint256,
243 pub spender: [u8; 20],
245}
246
247#[derive(Debug, Clone, PartialEq, Eq)]
249pub struct TokenPermissions {
250 pub token: [u8; 20],
252 pub amount: Uint256,
254}
255
256impl PermitBatchTransferFrom {
257 #[must_use]
259 pub fn struct_hash(&self) -> [u8; 32] {
260 let mut perms_hashes = Vec::with_capacity(self.permitted.len() * 32);
261 for p in &self.permitted {
262 let mut h = Vec::with_capacity(96);
263 h.extend_from_slice(&token_permissions_typehash());
264 h.extend_from_slice(&pad_address(&p.token));
265 h.extend_from_slice(&p.amount);
266 perms_hashes.extend_from_slice(&keccak256(&h));
267 }
268 let perms_array_hash = keccak256(&perms_hashes);
269
270 let mut data = Vec::with_capacity(160);
271 data.extend_from_slice(&permit_batch_transfer_from_typehash());
272 data.extend_from_slice(&perms_array_hash);
273 data.extend_from_slice(&pad_address(&self.spender));
274 data.extend_from_slice(&self.nonce);
275 data.extend_from_slice(&self.deadline);
276 keccak256(&data)
277 }
278
279 #[must_use]
281 pub fn signing_hash(&self, domain_separator: &[u8; 32]) -> [u8; 32] {
282 eip712_hash(domain_separator, &self.struct_hash())
283 }
284}
285
286#[must_use]
294pub fn permit2_domain_separator(chain_id: Uint256) -> [u8; 32] {
295 let type_hash =
296 keccak256(b"EIP712Domain(string name,uint256 chainId,address verifyingContract)");
297 let name_hash = keccak256(b"Permit2");
298
299 let mut data = Vec::with_capacity(128);
300 data.extend_from_slice(&type_hash);
301 data.extend_from_slice(&name_hash);
302 data.extend_from_slice(&chain_id);
303 data.extend_from_slice(&pad_address(&PERMIT2_ADDRESS));
304 keccak256(&data)
305}
306
307pub fn encode_permit_single_call(
315 owner: &[u8; 20],
316 permit: &PermitSingle,
317 signature: &[u8],
318) -> Result<Vec<u8>, SignerError> {
319 use crate::ethereum::abi::{AbiValue, Function};
320
321 let func =
322 Function::new("permit(address,((address,uint160,uint48,uint48),address,uint256),bytes)");
323 Ok(func.encode(&[
324 AbiValue::Address(*owner),
325 AbiValue::Tuple(vec![
326 AbiValue::Tuple(vec![
327 AbiValue::Address(permit.token),
328 AbiValue::Uint256(pad_uint160(&permit.amount)),
329 AbiValue::Uint256(pad_u48(permit.expiration, "expiration")?),
330 AbiValue::Uint256(pad_u48(permit.nonce, "nonce")?),
331 ]),
332 AbiValue::Address(permit.spender),
333 AbiValue::Uint256(permit.sig_deadline),
334 ]),
335 AbiValue::Bytes(signature.to_vec()),
336 ]))
337}
338
339#[must_use]
341pub fn encode_transfer_from(
342 from: &[u8; 20],
343 to: &[u8; 20],
344 amount: Uint160,
345 token: &[u8; 20],
346) -> Vec<u8> {
347 use crate::ethereum::abi::{AbiValue, Function};
348 let func = Function::new("transferFrom(address,address,uint160,address)");
349 func.encode(&[
350 AbiValue::Address(*from),
351 AbiValue::Address(*to),
352 AbiValue::Uint256(pad_uint160(&amount)),
353 AbiValue::Address(*token),
354 ])
355}
356
357#[must_use]
363pub fn uint160_from_u128(value: u128) -> Uint160 {
364 let mut out = [0u8; 20];
365 out[4..].copy_from_slice(&value.to_be_bytes());
366 out
367}
368
369#[must_use]
371pub fn uint256_from_u64(value: u64) -> Uint256 {
372 let mut out = [0u8; 32];
373 out[24..].copy_from_slice(&value.to_be_bytes());
374 out
375}
376
377fn pad_address(addr: &[u8; 20]) -> [u8; 32] {
378 let mut buf = [0u8; 32];
379 buf[12..32].copy_from_slice(addr);
380 buf
381}
382
383fn pad_uint160(val: &Uint160) -> [u8; 32] {
384 let mut buf = [0u8; 32];
385 buf[12..32].copy_from_slice(val);
386 buf
387}
388
389fn pad_u48(val: u64, field: &str) -> Result<[u8; 32], SignerError> {
390 if val > MAX_U48 {
391 return Err(SignerError::ParseError(format!(
392 "Permit2 {field} exceeds uint48 range"
393 )));
394 }
395 Ok(uint256_from_u64(val))
396}
397
398fn eip712_hash(domain_separator: &[u8; 32], struct_hash: &[u8; 32]) -> [u8; 32] {
399 let mut data = Vec::with_capacity(66);
400 data.push(0x19);
401 data.push(0x01);
402 data.extend_from_slice(domain_separator);
403 data.extend_from_slice(struct_hash);
404 keccak256(&data)
405}
406
407#[cfg(test)]
412#[allow(clippy::unwrap_used, clippy::expect_used)]
413mod tests {
414 use super::*;
415 use crate::ethereum::abi;
416
417 const TOKEN_A: [u8; 20] = [0xAA; 20];
418 const TOKEN_B: [u8; 20] = [0xBB; 20];
419 const SPENDER: [u8; 20] = [0xCC; 20];
420 const OWNER: [u8; 20] = [0xDD; 20];
421 const DEADLINE: u64 = 1_700_000_000;
422
423 fn amount160(v: u128) -> Uint160 {
424 uint160_from_u128(v)
425 }
426
427 fn amount256(v: u64) -> Uint256 {
428 uint256_from_u64(v)
429 }
430
431 #[test]
432 fn test_permit_single_struct_hash_deterministic() {
433 let p = PermitSingle {
434 token: TOKEN_A,
435 amount: amount160(1000),
436 expiration: DEADLINE,
437 nonce: 0,
438 spender: SPENDER,
439 sig_deadline: amount256(DEADLINE),
440 };
441 assert_eq!(p.struct_hash().unwrap(), p.struct_hash().unwrap());
442 }
443
444 #[test]
445 fn test_permit_single_rejects_u48_overflow() {
446 let p = PermitSingle {
447 token: TOKEN_A,
448 amount: amount160(1000),
449 expiration: MAX_U48 + 1,
450 nonce: 0,
451 spender: SPENDER,
452 sig_deadline: amount256(DEADLINE),
453 };
454 assert!(p.struct_hash().is_err());
455 }
456
457 #[test]
458 fn test_permit_single_different_amounts() {
459 let p1 = PermitSingle {
460 token: TOKEN_A,
461 amount: amount160(1000),
462 expiration: DEADLINE,
463 nonce: 0,
464 spender: SPENDER,
465 sig_deadline: amount256(DEADLINE),
466 };
467 let p2 = PermitSingle {
468 token: TOKEN_A,
469 amount: amount160(2000),
470 expiration: DEADLINE,
471 nonce: 0,
472 spender: SPENDER,
473 sig_deadline: amount256(DEADLINE),
474 };
475 assert_ne!(p1.struct_hash().unwrap(), p2.struct_hash().unwrap());
476 }
477
478 #[test]
479 fn test_permit_single_signing_hash() {
480 let p = PermitSingle {
481 token: TOKEN_A,
482 amount: amount160(1000),
483 expiration: DEADLINE,
484 nonce: 0,
485 spender: SPENDER,
486 sig_deadline: amount256(DEADLINE),
487 };
488 let ds = permit2_domain_separator(amount256(1));
489 let hash = p.signing_hash(&ds).unwrap();
490 assert_ne!(hash, [0u8; 32]);
491 }
492
493 #[test]
494 fn test_permit_batch_struct_hash() {
495 let p = PermitBatch {
496 details: vec![
497 PermitDetails {
498 token: TOKEN_A,
499 amount: amount160(100),
500 expiration: DEADLINE,
501 nonce: 0,
502 },
503 PermitDetails {
504 token: TOKEN_B,
505 amount: amount160(200),
506 expiration: DEADLINE,
507 nonce: 1,
508 },
509 ],
510 spender: SPENDER,
511 sig_deadline: amount256(DEADLINE),
512 };
513 assert_ne!(p.struct_hash().unwrap(), [0u8; 32]);
514 }
515
516 #[test]
517 fn test_permit_transfer_struct_hash() {
518 let p = PermitTransferFrom {
519 token: TOKEN_A,
520 amount: amount256(5000),
521 nonce: amount256(42),
522 deadline: amount256(DEADLINE),
523 spender: SPENDER,
524 };
525 assert_ne!(p.struct_hash(), [0u8; 32]);
526 }
527
528 #[test]
529 fn test_permit_batch_transfer_struct_hash() {
530 let p = PermitBatchTransferFrom {
531 permitted: vec![
532 TokenPermissions {
533 token: TOKEN_A,
534 amount: amount256(100),
535 },
536 TokenPermissions {
537 token: TOKEN_B,
538 amount: amount256(200),
539 },
540 ],
541 nonce: amount256(0),
542 deadline: amount256(DEADLINE),
543 spender: SPENDER,
544 };
545 assert_ne!(p.struct_hash(), [0u8; 32]);
546 }
547
548 #[test]
549 fn test_domain_separator_different_chains() {
550 assert_ne!(
551 permit2_domain_separator(amount256(1)),
552 permit2_domain_separator(amount256(137))
553 );
554 }
555
556 #[test]
557 fn test_encode_permit_single_call_selector() {
558 let p = PermitSingle {
559 token: TOKEN_A,
560 amount: amount160(1000),
561 expiration: DEADLINE,
562 nonce: 0,
563 spender: SPENDER,
564 sig_deadline: amount256(DEADLINE),
565 };
566 let data = encode_permit_single_call(&OWNER, &p, &[0xAA; 65]).unwrap();
567 assert!(data.len() > 4);
568 }
569
570 #[test]
571 fn test_encode_transfer_from_selector() {
572 let data = encode_transfer_from(&OWNER, &SPENDER, amount160(1000), &TOKEN_A);
573 let expected = abi::function_selector("transferFrom(address,address,uint160,address)");
574 assert_eq!(&data[..4], &expected);
575 }
576
577 #[test]
578 fn test_pad_address() {
579 let addr = [0xAA; 20];
580 let padded = pad_address(&addr);
581 assert!(padded[..12].iter().all(|b| *b == 0));
582 assert_eq!(&padded[12..], &addr);
583 }
584
585 #[test]
586 fn test_pad_u48() {
587 let padded = pad_u48(42, "test").unwrap();
588 assert_eq!(padded[31], 42);
589 assert!(padded[..24].iter().all(|b| *b == 0));
590 }
591
592 #[test]
593 fn test_permit2_address_hex() {
594 let hex = PERMIT2_ADDRESS
595 .iter()
596 .map(|b| format!("{b:02x}"))
597 .collect::<String>();
598 assert_eq!(hex, "000000000022d473030f116ddee9f6b43ac78ba3");
599 }
600}