espresso_contract_deployer/proposals/
safe_hash.rs1use alloy::primitives::{Address, B256, Bytes, U256, keccak256};
4
5const SAFE_TX_TYPEHASH_PREIMAGE: &[u8] = b"SafeTx(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,address refundReceiver,uint256 nonce)";
6
7#[derive(Debug, Clone)]
8pub struct SafeTxHashes {
9 pub domain: B256,
10 pub message: B256,
11 pub safe_tx: B256,
12}
13
14pub fn safe_tx_hashes(
19 safe: Address,
20 chain_id: u64,
21 to: Address,
22 value: U256,
23 data: &Bytes,
24 operation: u8,
25 nonce: u64,
26) -> SafeTxHashes {
27 let domain_typehash = keccak256(b"EIP712Domain(uint256 chainId,address verifyingContract)");
28 let mut domain_encoded = [0u8; 96];
29 domain_encoded[..32].copy_from_slice(domain_typehash.as_ref());
30 domain_encoded[32..64]
31 .copy_from_slice(&alloy::primitives::U256::from(chain_id).to_be_bytes::<32>());
32 domain_encoded[64 + 12..].copy_from_slice(safe.as_slice());
33 let domain_hash = keccak256(domain_encoded);
34
35 let data_hash = keccak256(data.as_ref());
36 let to_padded: [u8; 32] = {
37 let mut b = [0u8; 32];
38 b[12..].copy_from_slice(to.as_slice());
39 b
40 };
41 let safe_tx_typehash = keccak256(SAFE_TX_TYPEHASH_PREIMAGE);
42
43 let mut msg_encoded = [0u8; 32 * 11];
44 msg_encoded[0..32].copy_from_slice(safe_tx_typehash.as_ref());
45 msg_encoded[32..64].copy_from_slice(&to_padded);
46 msg_encoded[64..96].copy_from_slice(&value.to_be_bytes::<32>());
47 msg_encoded[96..128].copy_from_slice(data_hash.as_ref());
48 msg_encoded[128..160]
49 .copy_from_slice(&alloy::primitives::U256::from(operation).to_be_bytes::<32>());
50 msg_encoded[320..352]
52 .copy_from_slice(&alloy::primitives::U256::from(nonce).to_be_bytes::<32>());
53 let message_hash = keccak256(msg_encoded);
54
55 let mut final_encoded = [0u8; 66];
56 final_encoded[0] = 0x19;
57 final_encoded[1] = 0x01;
58 final_encoded[2..34].copy_from_slice(domain_hash.as_ref());
59 final_encoded[34..66].copy_from_slice(message_hash.as_ref());
60
61 SafeTxHashes {
62 domain: domain_hash,
63 message: message_hash,
64 safe_tx: keccak256(final_encoded),
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use alloy::primitives::{Address, B256, Bytes, U256};
71
72 use super::*;
73
74 #[test]
79 fn test_safe_tx_hash_known_vector() {
80 let safe: Address = "0xb76834e371b666feee48e5d7d9a97ca08b5a0620"
81 .parse()
82 .unwrap();
83 let chain_id: u64 = 11155111;
84 let timelock: Address = "0x8e3b6563d683b87964104a2c3a4bf542bb70767f"
85 .parse()
86 .unwrap();
87
88 let data = Bytes::new();
91
92 let hashes = safe_tx_hashes(safe, chain_id, timelock, U256::ZERO, &data, 0, 24);
93
94 let expected_domain: B256 =
95 "0x8f560c9d209e6d9320305560aee98fa1dea01510aa5451a9c0911401893835c6"
96 .parse()
97 .unwrap();
98 assert_eq!(hashes.domain, expected_domain);
99 }
100}