1use crate::error::SignerError;
36use crate::ethereum::abi::{self, AbiValue};
37use sha3::{Digest, Keccak256};
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum Operation {
44 Call = 0,
46 DelegateCall = 1,
48}
49
50#[derive(Debug, Clone)]
55pub struct SafeTransaction {
56 pub to: [u8; 20],
58 pub value: [u8; 32],
60 pub data: Vec<u8>,
62 pub operation: Operation,
64 pub safe_tx_gas: [u8; 32],
66 pub base_gas: [u8; 32],
68 pub gas_price: [u8; 32],
70 pub gas_token: [u8; 20],
72 pub refund_receiver: [u8; 20],
74 pub nonce: [u8; 32],
76}
77
78impl SafeTransaction {
79 #[must_use]
83 pub fn type_hash() -> [u8; 32] {
84 keccak256(
85 b"SafeTx(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,address refundReceiver,uint256 nonce)",
86 )
87 }
88
89 #[must_use]
93 pub fn struct_hash(&self) -> [u8; 32] {
94 let data_hash = keccak256(&self.data);
95
96 let mut buf = Vec::with_capacity(11 * 32);
97 buf.extend_from_slice(&Self::type_hash());
98 buf.extend_from_slice(&pad_address(&self.to));
99 buf.extend_from_slice(&self.value);
100 buf.extend_from_slice(&data_hash);
101 buf.extend_from_slice(&pad_u8(self.operation as u8));
102 buf.extend_from_slice(&self.safe_tx_gas);
103 buf.extend_from_slice(&self.base_gas);
104 buf.extend_from_slice(&self.gas_price);
105 buf.extend_from_slice(&pad_address(&self.gas_token));
106 buf.extend_from_slice(&pad_address(&self.refund_receiver));
107 buf.extend_from_slice(&self.nonce);
108
109 keccak256(&buf)
110 }
111
112 #[must_use]
116 pub fn signing_hash(&self, domain_separator: &[u8; 32]) -> [u8; 32] {
117 let mut buf = Vec::with_capacity(2 + 32 + 32);
118 buf.push(0x19);
119 buf.push(0x01);
120 buf.extend_from_slice(domain_separator);
121 buf.extend_from_slice(&self.struct_hash());
122 keccak256(&buf)
123 }
124
125 pub fn sign(
129 &self,
130 signer: &super::EthereumSigner,
131 domain_separator: &[u8; 32],
132 ) -> Result<super::EthereumSignature, SignerError> {
133 let hash = self.signing_hash(domain_separator);
134 signer.sign_digest(&hash)
135 }
136
137 #[must_use]
142 pub fn encode_exec_transaction(&self, signatures: &[super::EthereumSignature]) -> Vec<u8> {
143 let packed_sigs = encode_signatures(signatures);
144 let func = abi::Function::new(
145 "execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)",
146 );
147 func.encode(&[
148 AbiValue::Address(self.to),
149 AbiValue::Uint256(self.value),
150 AbiValue::Bytes(self.data.clone()),
151 AbiValue::Uint256(pad_u8(self.operation as u8)),
152 AbiValue::Uint256(self.safe_tx_gas),
153 AbiValue::Uint256(self.base_gas),
154 AbiValue::Uint256(self.gas_price),
155 AbiValue::Address(self.gas_token),
156 AbiValue::Address(self.refund_receiver),
157 AbiValue::Bytes(packed_sigs),
158 ])
159 }
160}
161
162#[must_use]
171pub fn safe_domain_separator(chain_id: u64, safe_address: &[u8; 20]) -> [u8; 32] {
172 let domain_type_hash =
173 keccak256(b"EIP712Domain(uint256 chainId,address verifyingContract)");
174 let mut buf = Vec::with_capacity(3 * 32);
175 buf.extend_from_slice(&domain_type_hash);
176 buf.extend_from_slice(&pad_u64(chain_id));
177 buf.extend_from_slice(&pad_address(safe_address));
178 keccak256(&buf)
179}
180
181#[must_use]
189pub fn encode_signatures(signatures: &[super::EthereumSignature]) -> Vec<u8> {
190 let mut packed = Vec::with_capacity(signatures.len() * 65);
191 for sig in signatures {
192 packed.extend_from_slice(&sig.r);
193 packed.extend_from_slice(&sig.s);
194 packed.push(sig.v as u8);
195 }
196 packed
197}
198
199pub fn decode_signatures(data: &[u8]) -> Result<Vec<super::EthereumSignature>, SignerError> {
204 if data.len() % 65 != 0 {
205 return Err(SignerError::EncodingError(format!(
206 "signature data length {} is not a multiple of 65",
207 data.len()
208 )));
209 }
210 let count = data.len() / 65;
211 let mut sigs = Vec::with_capacity(count);
212 for i in 0..count {
213 let offset = i * 65;
214 let mut r = [0u8; 32];
215 let mut s = [0u8; 32];
216 r.copy_from_slice(&data[offset..offset + 32]);
217 s.copy_from_slice(&data[offset + 32..offset + 64]);
218 let v = u64::from(data[offset + 64]);
219 sigs.push(super::EthereumSignature { r, s, v });
220 }
221 Ok(sigs)
222}
223
224#[must_use]
228pub fn encode_add_owner(owner: [u8; 20], threshold: u64) -> Vec<u8> {
229 let func = abi::Function::new("addOwnerWithThreshold(address,uint256)");
230 func.encode(&[
231 AbiValue::Address(owner),
232 AbiValue::from_u64(threshold),
233 ])
234}
235
236#[must_use]
241pub fn encode_remove_owner(prev_owner: [u8; 20], owner: [u8; 20], threshold: u64) -> Vec<u8> {
242 let func = abi::Function::new("removeOwner(address,address,uint256)");
243 func.encode(&[
244 AbiValue::Address(prev_owner),
245 AbiValue::Address(owner),
246 AbiValue::from_u64(threshold),
247 ])
248}
249
250#[must_use]
252pub fn encode_change_threshold(threshold: u64) -> Vec<u8> {
253 let func = abi::Function::new("changeThreshold(uint256)");
254 func.encode(&[AbiValue::from_u64(threshold)])
255}
256
257#[must_use]
259pub fn encode_swap_owner(prev_owner: [u8; 20], old_owner: [u8; 20], new_owner: [u8; 20]) -> Vec<u8> {
260 let func = abi::Function::new("swapOwner(address,address,address)");
261 func.encode(&[
262 AbiValue::Address(prev_owner),
263 AbiValue::Address(old_owner),
264 AbiValue::Address(new_owner),
265 ])
266}
267
268#[must_use]
270pub fn encode_enable_module(module: [u8; 20]) -> Vec<u8> {
271 let func = abi::Function::new("enableModule(address)");
272 func.encode(&[AbiValue::Address(module)])
273}
274
275#[must_use]
277pub fn encode_disable_module(prev_module: [u8; 20], module: [u8; 20]) -> Vec<u8> {
278 let func = abi::Function::new("disableModule(address,address)");
279 func.encode(&[
280 AbiValue::Address(prev_module),
281 AbiValue::Address(module),
282 ])
283}
284
285#[must_use]
287pub fn encode_set_guard(guard: [u8; 20]) -> Vec<u8> {
288 let func = abi::Function::new("setGuard(address)");
289 func.encode(&[AbiValue::Address(guard)])
290}
291
292pub const SENTINEL_OWNERS: [u8; 20] = {
294 let mut a = [0u8; 20];
295 a[19] = 1;
296 a
297};
298
299#[must_use]
301pub fn encode_get_owners() -> Vec<u8> {
302 let func = abi::Function::new("getOwners()");
303 func.encode(&[])
304}
305
306#[must_use]
308pub fn encode_get_threshold() -> Vec<u8> {
309 let func = abi::Function::new("getThreshold()");
310 func.encode(&[])
311}
312
313#[must_use]
315pub fn encode_nonce() -> Vec<u8> {
316 let func = abi::Function::new("nonce()");
317 func.encode(&[])
318}
319
320#[must_use]
322pub fn encode_get_transaction_hash(tx: &SafeTransaction) -> Vec<u8> {
323 let func = abi::Function::new(
324 "getTransactionHash(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,uint256)",
325 );
326 func.encode(&[
327 AbiValue::Address(tx.to),
328 AbiValue::Uint256(tx.value),
329 AbiValue::Bytes(tx.data.clone()),
330 AbiValue::Uint256(pad_u8(tx.operation as u8)),
331 AbiValue::Uint256(tx.safe_tx_gas),
332 AbiValue::Uint256(tx.base_gas),
333 AbiValue::Uint256(tx.gas_price),
334 AbiValue::Address(tx.gas_token),
335 AbiValue::Address(tx.refund_receiver),
336 AbiValue::Uint256(tx.nonce),
337 ])
338}
339
340fn keccak256(data: &[u8]) -> [u8; 32] {
343 let mut hasher = Keccak256::new();
344 hasher.update(data);
345 hasher.finalize().into()
346}
347
348fn pad_address(addr: &[u8; 20]) -> [u8; 32] {
349 let mut buf = [0u8; 32];
350 buf[12..32].copy_from_slice(addr);
351 buf
352}
353
354fn pad_u8(val: u8) -> [u8; 32] {
355 let mut buf = [0u8; 32];
356 buf[31] = val;
357 buf
358}
359
360fn pad_u64(val: u64) -> [u8; 32] {
361 let mut buf = [0u8; 32];
362 buf[24..32].copy_from_slice(&val.to_be_bytes());
363 buf
364}
365
366#[cfg(test)]
369#[allow(clippy::unwrap_used, clippy::expect_used)]
370mod tests {
371 use super::*;
372 use crate::traits::KeyPair;
373
374 fn zero_tx() -> SafeTransaction {
375 SafeTransaction {
376 to: [0xBB; 20],
377 value: [0u8; 32],
378 data: vec![],
379 operation: Operation::Call,
380 safe_tx_gas: [0u8; 32],
381 base_gas: [0u8; 32],
382 gas_price: [0u8; 32],
383 gas_token: [0u8; 20],
384 refund_receiver: [0u8; 20],
385 nonce: [0u8; 32],
386 }
387 }
388
389 #[test]
392 fn test_type_hash_matches_safe_contract() {
393 let th = SafeTransaction::type_hash();
394 let expected = keccak256(
396 b"SafeTx(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,address refundReceiver,uint256 nonce)",
397 );
398 assert_eq!(th, expected);
399 }
400
401 #[test]
404 fn test_struct_hash_deterministic() {
405 let tx = zero_tx();
406 assert_eq!(tx.struct_hash(), tx.struct_hash());
407 }
408
409 #[test]
410 fn test_struct_hash_changes_with_to() {
411 let tx1 = zero_tx();
412 let mut tx2 = zero_tx();
413 tx2.to = [0xCC; 20];
414 assert_ne!(tx1.struct_hash(), tx2.struct_hash());
415 }
416
417 #[test]
418 fn test_struct_hash_changes_with_data() {
419 let tx1 = zero_tx();
420 let mut tx2 = zero_tx();
421 tx2.data = vec![0xDE, 0xAD];
422 assert_ne!(tx1.struct_hash(), tx2.struct_hash());
423 }
424
425 #[test]
426 fn test_struct_hash_changes_with_operation() {
427 let tx1 = zero_tx();
428 let mut tx2 = zero_tx();
429 tx2.operation = Operation::DelegateCall;
430 assert_ne!(tx1.struct_hash(), tx2.struct_hash());
431 }
432
433 #[test]
434 fn test_struct_hash_changes_with_nonce() {
435 let tx1 = zero_tx();
436 let mut tx2 = zero_tx();
437 tx2.nonce[31] = 1;
438 assert_ne!(tx1.struct_hash(), tx2.struct_hash());
439 }
440
441 #[test]
442 fn test_struct_hash_changes_with_value() {
443 let tx1 = zero_tx();
444 let mut tx2 = zero_tx();
445 tx2.value[31] = 1;
446 assert_ne!(tx1.struct_hash(), tx2.struct_hash());
447 }
448
449 #[test]
450 fn test_struct_hash_changes_with_gas_fields() {
451 let tx1 = zero_tx();
452 let mut tx2 = zero_tx();
453 tx2.safe_tx_gas[31] = 100;
454 assert_ne!(tx1.struct_hash(), tx2.struct_hash());
455
456 let mut tx3 = zero_tx();
457 tx3.base_gas[31] = 50;
458 assert_ne!(tx1.struct_hash(), tx3.struct_hash());
459
460 let mut tx4 = zero_tx();
461 tx4.gas_price[31] = 10;
462 assert_ne!(tx1.struct_hash(), tx4.struct_hash());
463 }
464
465 #[test]
466 fn test_struct_hash_changes_with_gas_token() {
467 let tx1 = zero_tx();
468 let mut tx2 = zero_tx();
469 tx2.gas_token = [0xFF; 20];
470 assert_ne!(tx1.struct_hash(), tx2.struct_hash());
471 }
472
473 #[test]
474 fn test_struct_hash_changes_with_refund_receiver() {
475 let tx1 = zero_tx();
476 let mut tx2 = zero_tx();
477 tx2.refund_receiver = [0xFF; 20];
478 assert_ne!(tx1.struct_hash(), tx2.struct_hash());
479 }
480
481 #[test]
484 fn test_domain_separator_deterministic() {
485 let ds1 = safe_domain_separator(1, &[0xAA; 20]);
486 let ds2 = safe_domain_separator(1, &[0xAA; 20]);
487 assert_eq!(ds1, ds2);
488 }
489
490 #[test]
491 fn test_domain_separator_changes_with_chain_id() {
492 let ds1 = safe_domain_separator(1, &[0xAA; 20]);
493 let ds2 = safe_domain_separator(137, &[0xAA; 20]);
494 assert_ne!(ds1, ds2);
495 }
496
497 #[test]
498 fn test_domain_separator_changes_with_address() {
499 let ds1 = safe_domain_separator(1, &[0xAA; 20]);
500 let ds2 = safe_domain_separator(1, &[0xBB; 20]);
501 assert_ne!(ds1, ds2);
502 }
503
504 #[test]
507 fn test_signing_hash_starts_with_eip712_prefix() {
508 let tx = zero_tx();
509 let domain = safe_domain_separator(1, &[0xAA; 20]);
510 let h1 = tx.signing_hash(&domain);
513 let h2 = tx.signing_hash(&domain);
514 assert_eq!(h1, h2);
515 }
516
517 #[test]
518 fn test_signing_hash_changes_with_domain() {
519 let tx = zero_tx();
520 let d1 = safe_domain_separator(1, &[0xAA; 20]);
521 let d2 = safe_domain_separator(5, &[0xAA; 20]);
522 assert_ne!(tx.signing_hash(&d1), tx.signing_hash(&d2));
523 }
524
525 #[test]
528 fn test_sign_produces_valid_signature() {
529 let signer = super::super::EthereumSigner::generate().unwrap();
530 let tx = zero_tx();
531 let domain = safe_domain_separator(1, &[0xAA; 20]);
532 let sig = tx.sign(&signer, &domain).unwrap();
533 assert!(sig.v == 27 || sig.v == 28);
535 assert_ne!(sig.r, [0u8; 32]);
536 assert_ne!(sig.s, [0u8; 32]);
537 }
538
539 #[test]
540 fn test_sign_recovers_correct_address() {
541 let signer = super::super::EthereumSigner::generate().unwrap();
542 let tx = zero_tx();
543 let domain = safe_domain_separator(1, &[0xAA; 20]);
544 let sig = tx.sign(&signer, &domain).unwrap();
545 let hash = tx.signing_hash(&domain);
546 let recovered = super::super::ecrecover_digest(&hash, &sig).unwrap();
547 assert_eq!(recovered, signer.address());
548 }
549
550 #[test]
553 fn test_encode_signatures_empty() {
554 let packed = encode_signatures(&[]);
555 assert!(packed.is_empty());
556 }
557
558 #[test]
559 fn test_encode_signatures_single() {
560 let sig = super::super::EthereumSignature {
561 r: [0xAA; 32],
562 s: [0xBB; 32],
563 v: 27,
564 };
565 let packed = encode_signatures(&[sig]);
566 assert_eq!(packed.len(), 65);
567 assert_eq!(&packed[..32], &[0xAA; 32]);
568 assert_eq!(&packed[32..64], &[0xBB; 32]);
569 assert_eq!(packed[64], 27);
570 }
571
572 #[test]
573 fn test_encode_signatures_multiple() {
574 let sig1 = super::super::EthereumSignature {
575 r: [0x11; 32], s: [0x22; 32], v: 27,
576 };
577 let sig2 = super::super::EthereumSignature {
578 r: [0x33; 32], s: [0x44; 32], v: 28,
579 };
580 let packed = encode_signatures(&[sig1, sig2]);
581 assert_eq!(packed.len(), 130);
582 assert_eq!(packed[64], 27);
583 assert_eq!(packed[129], 28);
584 }
585
586 #[test]
589 fn test_decode_signatures_roundtrip() {
590 let sig1 = super::super::EthereumSignature {
591 r: [0xAA; 32], s: [0xBB; 32], v: 27,
592 };
593 let sig2 = super::super::EthereumSignature {
594 r: [0xCC; 32], s: [0xDD; 32], v: 28,
595 };
596 let packed = encode_signatures(&[sig1.clone(), sig2.clone()]);
597 let decoded = decode_signatures(&packed).unwrap();
598 assert_eq!(decoded.len(), 2);
599 assert_eq!(decoded[0], sig1);
600 assert_eq!(decoded[1], sig2);
601 }
602
603 #[test]
604 fn test_decode_signatures_empty() {
605 let decoded = decode_signatures(&[]).unwrap();
606 assert!(decoded.is_empty());
607 }
608
609 #[test]
610 fn test_decode_signatures_invalid_length() {
611 assert!(decode_signatures(&[0u8; 64]).is_err());
612 assert!(decode_signatures(&[0u8; 66]).is_err());
613 }
614
615 #[test]
618 fn test_exec_transaction_has_correct_selector() {
619 let tx = zero_tx();
620 let sig = super::super::EthereumSignature {
621 r: [0xAA; 32], s: [0xBB; 32], v: 27,
622 };
623 let calldata = tx.encode_exec_transaction(&[sig]);
624 let expected_selector = abi::function_selector(
626 "execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)",
627 );
628 assert_eq!(&calldata[..4], &expected_selector);
629 }
630
631 #[test]
632 fn test_exec_transaction_includes_signature_data() {
633 let tx = zero_tx();
634 let sig = super::super::EthereumSignature {
635 r: [0xAA; 32], s: [0xBB; 32], v: 27,
636 };
637 let calldata = tx.encode_exec_transaction(&[sig]);
638 assert!(calldata.len() > 4 + 10 * 32); }
641
642 #[test]
645 fn test_encode_add_owner_selector() {
646 let calldata = encode_add_owner([0xAA; 20], 2);
647 let expected = abi::function_selector("addOwnerWithThreshold(address,uint256)");
648 assert_eq!(&calldata[..4], &expected);
649 assert_eq!(calldata.len(), 4 + 2 * 32);
650 }
651
652 #[test]
653 fn test_encode_remove_owner_selector() {
654 let calldata = encode_remove_owner(SENTINEL_OWNERS, [0xAA; 20], 1);
655 let expected = abi::function_selector("removeOwner(address,address,uint256)");
656 assert_eq!(&calldata[..4], &expected);
657 assert_eq!(calldata.len(), 4 + 3 * 32);
658 }
659
660 #[test]
661 fn test_encode_change_threshold_selector() {
662 let calldata = encode_change_threshold(3);
663 let expected = abi::function_selector("changeThreshold(uint256)");
664 assert_eq!(&calldata[..4], &expected);
665 assert_eq!(calldata.len(), 4 + 32);
666 }
667
668 #[test]
669 fn test_encode_swap_owner_selector() {
670 let calldata = encode_swap_owner(SENTINEL_OWNERS, [0xAA; 20], [0xBB; 20]);
671 let expected = abi::function_selector("swapOwner(address,address,address)");
672 assert_eq!(&calldata[..4], &expected);
673 assert_eq!(calldata.len(), 4 + 3 * 32);
674 }
675
676 #[test]
677 fn test_encode_enable_module_selector() {
678 let calldata = encode_enable_module([0xAA; 20]);
679 let expected = abi::function_selector("enableModule(address)");
680 assert_eq!(&calldata[..4], &expected);
681 }
682
683 #[test]
684 fn test_encode_disable_module_selector() {
685 let calldata = encode_disable_module(SENTINEL_OWNERS, [0xAA; 20]);
686 let expected = abi::function_selector("disableModule(address,address)");
687 assert_eq!(&calldata[..4], &expected);
688 }
689
690 #[test]
691 fn test_encode_set_guard_selector() {
692 let calldata = encode_set_guard([0xAA; 20]);
693 let expected = abi::function_selector("setGuard(address)");
694 assert_eq!(&calldata[..4], &expected);
695 }
696
697 #[test]
700 fn test_encode_get_owners_selector() {
701 let calldata = encode_get_owners();
702 let expected = abi::function_selector("getOwners()");
703 assert_eq!(&calldata[..4], &expected);
704 }
705
706 #[test]
707 fn test_encode_get_threshold_selector() {
708 let calldata = encode_get_threshold();
709 let expected = abi::function_selector("getThreshold()");
710 assert_eq!(&calldata[..4], &expected);
711 }
712
713 #[test]
714 fn test_encode_nonce_selector() {
715 let calldata = encode_nonce();
716 let expected = abi::function_selector("nonce()");
717 assert_eq!(&calldata[..4], &expected);
718 }
719
720 #[test]
721 fn test_encode_get_transaction_hash_selector() {
722 let tx = zero_tx();
723 let calldata = encode_get_transaction_hash(&tx);
724 let expected = abi::function_selector(
725 "getTransactionHash(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,uint256)",
726 );
727 assert_eq!(&calldata[..4], &expected);
728 }
729
730 #[test]
733 fn test_sentinel_owners() {
734 assert_eq!(SENTINEL_OWNERS[19], 1);
735 assert_eq!(SENTINEL_OWNERS[..19], [0u8; 19]);
736 }
737
738 #[test]
741 fn test_operation_values() {
742 assert_eq!(Operation::Call as u8, 0);
743 assert_eq!(Operation::DelegateCall as u8, 1);
744 }
745
746 #[test]
747 fn test_operation_eq() {
748 assert_eq!(Operation::Call, Operation::Call);
749 assert_ne!(Operation::Call, Operation::DelegateCall);
750 }
751
752 #[test]
755 fn test_pad_address() {
756 let addr = [0xAA; 20];
757 let padded = pad_address(&addr);
758 assert_eq!(&padded[..12], &[0u8; 12]);
759 assert_eq!(&padded[12..], &[0xAA; 20]);
760 }
761
762 #[test]
763 fn test_pad_u8() {
764 let padded = pad_u8(42);
765 assert_eq!(&padded[..31], &[0u8; 31]);
766 assert_eq!(padded[31], 42);
767 }
768
769 #[test]
770 fn test_pad_u64() {
771 let padded = pad_u64(256);
772 assert_eq!(&padded[..24], &[0u8; 24]);
773 assert_eq!(&padded[24..], &256u64.to_be_bytes());
774 }
775
776 #[test]
779 fn test_delegate_call_transaction() {
780 let mut tx = zero_tx();
781 tx.operation = Operation::DelegateCall;
782 tx.data = vec![0xDE, 0xAD, 0xBE, 0xEF];
783 let domain = safe_domain_separator(1, &[0xAA; 20]);
784 let hash = tx.signing_hash(&domain);
785 assert_ne!(hash, [0u8; 32]);
786 }
787}