1use alloy_primitives::{Address, B256, Bytes, U256, keccak256};
4use alloy_sol_types::{SolCall, sol};
5use std::sync::LazyLock;
6
7use crate::chain::environment::{
8 POLYESTER_TESTNET_ENVIRONMENT, PolyesterChainEnvironment, SafeDeploymentConfig,
9};
10use crate::errors::{Error, Result};
11
12static SAFE_PROXY_CREATION_CODE: LazyLock<Vec<u8>> = LazyLock::new(|| {
15 hex::decode(concat!(
16 "608060405234801561001057600080fd5b506040516101e63803806101e68339818101604052602081101561003357600080fd5b",
17 "8101908080519060200190929190505050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffff",
18 "ffffffffffffffffffffffff1614156100ca576040517f08c379a0000000000000000000000000000000000000000000000000",
19 "0000000081526004018080602001828103825260228152602001806101c46022913960400191505060405180910390fd5b8060",
20 "00806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffff",
21 "ffffffffff1602179055505060ab806101196000396000f3fe608060405273ffffffffffffffffffffffffffffffffffffffff",
22 "600054167fa619486e000000000000000000000000000000000000000000000000000000006000351415605057806000526020",
23 "6000f35b3660008037600080366000845af43d6000803e60008114156070573d6000fd5b3d6000f3fea2646970667358221220",
24 "03d1488ee65e08fa41e58e888a9865554c535f2c77126a82cb4c0f917f31441364736f6c63430007060033496e76616c696420",
25 "73696e676c65746f6e20616464726573732070726f7669646564",
26 ))
27 .expect("SAFE_PROXY_CREATION_CODE hex")
28});
29
30sol! {
31 function setup(
32 address[] owners,
33 uint256 threshold,
34 address to,
35 bytes data,
36 address fallbackHandler,
37 address paymentToken,
38 uint256 payment,
39 address paymentReceiver
40 );
41 function enableModules(address[] modules);
42 function multiSend(bytes transactions);
43 function createProxyWithNonce(address singleton, bytes initializer, uint256 saltNonce);
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct PredictedSafe {
49 pub address: String,
50 pub initializer: Vec<u8>,
51 pub factory_calldata: Vec<u8>,
52}
53
54fn parse_address(value: &str) -> Result<Address> {
55 let text = value.trim();
56 let hex = text.strip_prefix("0x").unwrap_or(text);
57 if hex.len() != 40 {
58 return Err(Error::validation(
59 "address must be a 20-byte 0x-prefixed hex string",
60 ));
61 }
62 let raw = hex::decode(hex).map_err(|_| Error::validation("address is not valid hex"))?;
63 Ok(Address::from_slice(&raw))
64}
65
66fn encode_internal_tx(to: Address, data: &[u8], value: U256, operation: u8) -> Vec<u8> {
67 let mut out = Vec::with_capacity(1 + 20 + 32 + 32 + data.len());
68 out.push(operation);
69 out.extend_from_slice(to.as_slice());
70 out.extend_from_slice(&value.to_be_bytes::<32>());
71 out.extend_from_slice(&U256::from(data.len()).to_be_bytes::<32>());
72 out.extend_from_slice(data);
73 out
74}
75
76fn get_initializer(
77 owners: &[Address],
78 threshold: u64,
79 safe: &SafeDeploymentConfig,
80) -> Result<Vec<u8>> {
81 let module = parse_address(safe.safe_4337_module_address)?;
82 let setup_addr = parse_address(safe.safe_module_setup_address)?;
83 let multi_send = parse_address(safe.multi_send_address)?;
84
85 let enable_modules = enableModulesCall {
86 modules: vec![module],
87 }
88 .abi_encode();
89
90 let multi_calls = [encode_internal_tx(
91 setup_addr,
92 &enable_modules,
93 U256::ZERO,
94 1,
95 )];
96 let packed: Vec<u8> = multi_calls.into_iter().flatten().collect();
97 let multi_send_calldata = multiSendCall {
98 transactions: Bytes::from(packed),
99 }
100 .abi_encode();
101
102 Ok(setupCall {
103 owners: owners.to_vec(),
104 threshold: U256::from(threshold),
105 to: multi_send,
106 data: Bytes::from(multi_send_calldata),
107 fallbackHandler: module,
108 paymentToken: Address::ZERO,
109 payment: U256::ZERO,
110 paymentReceiver: Address::ZERO,
111 }
112 .abi_encode())
113}
114
115pub fn predict_safe_address_with_data(
117 owners: &[&str],
118 salt_nonce: u64,
119 threshold: Option<u64>,
120 safe: Option<&SafeDeploymentConfig>,
121 environment: Option<&PolyesterChainEnvironment>,
122) -> Result<PredictedSafe> {
123 if owners.is_empty() {
124 return Err(Error::validation("owners must be non-empty"));
125 }
126 let env = environment.unwrap_or(&POLYESTER_TESTNET_ENVIRONMENT);
127 let cfg = safe.unwrap_or(&env.account_abstraction.safe);
128 let owner_addrs: Result<Vec<Address>> = owners.iter().map(|o| parse_address(o)).collect();
129 let owner_addrs = owner_addrs?;
130 let thresh = threshold.unwrap_or(owner_addrs.len() as u64);
131 let initializer = get_initializer(&owner_addrs, thresh, cfg)?;
132 let singleton = parse_address(cfg.safe_singleton_address)?;
133 let factory = parse_address(cfg.safe_proxy_factory_address)?;
134
135 let factory_calldata = createProxyWithNonceCall {
136 singleton,
137 initializer: Bytes::copy_from_slice(&initializer),
138 saltNonce: U256::from(salt_nonce),
139 }
140 .abi_encode();
141
142 let mut deployment_code = SAFE_PROXY_CREATION_CODE.clone();
143 let mut singleton_word = [0u8; 32];
144 singleton_word[12..].copy_from_slice(singleton.as_slice());
145 deployment_code.extend_from_slice(&singleton_word);
146
147 let mut salt_material = [0u8; 64];
148 salt_material[..32].copy_from_slice(keccak256(&initializer).as_slice());
149 salt_material[32..].copy_from_slice(&U256::from(salt_nonce).to_be_bytes::<32>());
150 let salt = B256::from(keccak256(salt_material));
151 let init_code_hash = B256::from(keccak256(&deployment_code));
152 let address = factory.create2(salt, init_code_hash);
153
154 Ok(PredictedSafe {
155 address: address.to_checksum(None),
156 initializer,
157 factory_calldata,
158 })
159}
160
161pub fn predict_safe_address(
163 owner_address: &str,
164 salt_nonce: u64,
165 environment: Option<&PolyesterChainEnvironment>,
166) -> Result<String> {
167 Ok(
168 predict_safe_address_with_data(&[owner_address], salt_nonce, None, None, environment)?
169 .address,
170 )
171}
172
173pub fn predict_polyester_smart_account_address(
175 owner_address: &str,
176 salt_nonce: u64,
177 environment: Option<&PolyesterChainEnvironment>,
178) -> Result<String> {
179 predict_safe_address(owner_address, salt_nonce, environment)
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 const OWNER: &str = "0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf";
187 const SALT0: &str = "0xA244Ed1dc6B46C75F37E0119054fFa45E76c9B6f";
188 const SALT7: &str = "0x4AEdcc90537f9fb3828E6b431E5A16Cdc473D6f0";
189
190 #[test]
191 fn predict_safe_address_salt_zero_matches_typescript() {
192 assert_eq!(predict_safe_address(OWNER, 0, None).unwrap(), SALT0);
193 }
194
195 #[test]
196 fn predict_safe_address_salt_seven_matches_typescript() {
197 assert_eq!(predict_safe_address(OWNER, 7, None).unwrap(), SALT7);
198 }
199
200 #[test]
201 fn predict_safe_address_with_data_includes_factory_calldata() {
202 let predicted = predict_safe_address_with_data(&[OWNER], 0, None, None, None).unwrap();
203 assert_eq!(predicted.address, SALT0);
204 assert!(hex::encode(&predicted.initializer).starts_with("b63e800d"));
205 assert!(hex::encode(&predicted.factory_calldata).starts_with("1688f0b9"));
206 }
207
208 #[test]
209 fn proxy_creation_code_length() {
210 assert_eq!(SAFE_PROXY_CREATION_CODE.len(), 486);
212 }
213}