1use crate::associations::AccountId;
3use crate::scw_verifier::SmartContractSignatureVerifier;
4use alloy::network::TransactionBuilder;
5use alloy::primitives::{Address, BlockNumber, Bytes, FixedBytes};
6use alloy::providers::{DynProvider, Provider, ProviderBuilder};
7use alloy::{sol, sol_types::SolConstructor};
8use hex::FromHexError;
9use std::sync::Arc;
10
11use super::{ValidationResponse, VerifierError};
12
13const VALIDATE_SIG_OFFCHAIN_BYTECODE: &str = include_str!("signature_validation.hex");
18
19sol!(
20 contract VerifySig {
21 constructor (
22 address _signer,
23 bytes32 _hash,
24 bytes memory _signature
25 );
26 }
27);
28
29#[derive(Debug, Clone)]
30pub struct RpcSmartContractWalletVerifier {
31 provider: Arc<DynProvider>,
32}
33
34impl RpcSmartContractWalletVerifier {
35 pub fn new(provider_url: String) -> Result<Self, VerifierError> {
36 Ok(Self {
37 provider: Arc::new(
38 ProviderBuilder::new()
39 .connect_http(provider_url.parse()?)
40 .erased(),
41 ),
42 })
43 }
44
45 pub fn new_from_provider(provider: impl Provider + 'static) -> Self {
46 Self {
47 provider: Arc::new(DynProvider::new(provider)),
48 }
49 }
50}
51
52#[xmtp_common::async_trait]
53impl SmartContractSignatureVerifier for RpcSmartContractWalletVerifier {
54 async fn is_valid_signature(
55 &self,
56 signer: AccountId,
57 hash: [u8; 32],
58 signature: Bytes,
59 block_number: Option<BlockNumber>,
60 ) -> Result<ValidationResponse, VerifierError> {
61 let code = hex::decode(VALIDATE_SIG_OFFCHAIN_BYTECODE.trim())?;
62 let account_address: Address = signer
63 .account_address
64 .parse()
65 .map_err(|_| FromHexError::InvalidStringLength)?;
66 let call = VerifySig::constructorCall::new((
67 account_address,
68 FixedBytes::<32>::new(hash),
69 signature,
70 ));
71
72 let data = call.abi_encode();
73 let data = [code, data].concat();
74 let block_number = match block_number {
75 Some(bn) => bn,
76 None => self
77 .provider
78 .get_block_number()
79 .await
80 .map_err(VerifierError::Provider)?,
81 };
82 let mut tx = self.provider.transaction_request();
83 tx.set_input(data);
84 let result = self.provider.call(tx).block(block_number.into()).await?;
85
86 let expected_valid = Bytes::from_static(&[0x01]);
88 let is_valid = result == expected_valid;
89
90 Ok(ValidationResponse {
91 is_valid,
92 block_number: Some(block_number),
93 error: None,
94 })
95 }
96}
97
98#[cfg(all(test, not(target_arch = "wasm32")))]
101pub(crate) mod tests {
102 #![allow(clippy::unwrap_used)]
103 use crate::utils::test::{SignatureWithNonce, SmartWalletContext, smart_wallet};
104
105 use super::*;
106 use alloy::dyn_abi::SolType;
107 use alloy::primitives::{B256, U256};
108 use alloy::providers::ext::AnvilApi;
109 use alloy::signers::Signer;
110 use std::time::Duration;
111
112 #[rstest::rstest]
113 #[timeout(Duration::from_secs(30))]
114 #[tokio::test]
115 async fn test_coinbase_smart_wallet(#[future] smart_wallet: SmartWalletContext) {
116 let SmartWalletContext {
117 factory,
118 sw,
119 owner0,
120 owner1,
121 sw_address,
122 } = smart_wallet.await;
123 let provider = factory.provider();
124 let chain_id = provider.get_chain_id().await.unwrap();
125 let hash = B256::random();
126 let replay_safe_hash = sw.replaySafeHash(hash).call().await.unwrap();
127 let verifier = RpcSmartContractWalletVerifier::new_from_provider(provider.clone());
128 let sig0 = owner0.sign_hash(&replay_safe_hash).await.unwrap();
129 let account_id = AccountId::new_evm(chain_id, format!("{}", sw_address));
130
131 let res = verifier
132 .is_valid_signature(
133 account_id.clone(),
134 *hash,
135 SignatureWithNonce::abi_encode(&(U256::from(0), Bytes::from(sig0.as_bytes())))
136 .into(),
137 None,
138 )
139 .await
140 .unwrap();
141 assert!(res.is_valid);
142
143 let sig1 = owner1.sign_hash(&replay_safe_hash).await.unwrap();
145 let res = verifier
146 .is_valid_signature(
147 account_id.clone(),
148 *hash,
149 SignatureWithNonce::abi_encode(&(U256::from(1), Bytes::from(sig1.as_bytes())))
150 .into(),
151 None,
152 )
153 .await
154 .unwrap();
155 assert!(res.is_valid);
156
157 let res = verifier
159 .is_valid_signature(
160 account_id.clone(),
161 *hash,
162 SignatureWithNonce::abi_encode(&(U256::from(1), Bytes::from(sig0.as_bytes())))
163 .into(),
164 None,
165 )
166 .await
167 .unwrap();
168 assert!(!res.is_valid);
169 }
170
171 #[rstest::rstest]
172 #[timeout(Duration::from_secs(60))]
173 #[tokio::test]
174 async fn test_smart_wallet_time_travel(#[future] smart_wallet: SmartWalletContext) {
175 let SmartWalletContext {
176 factory,
177 sw,
178 owner1,
179 sw_address,
180 ..
181 } = smart_wallet.await;
182
183 let provider = factory.provider();
184 let verifier = RpcSmartContractWalletVerifier::new_from_provider(provider.clone());
185 let chain_id = provider.get_chain_id().await.unwrap();
186 let hash = B256::random();
187 let replay_safe_hash = sw.replaySafeHash(hash).call().await.unwrap();
188 let sig1 = owner1.sign_hash(&replay_safe_hash).await.unwrap();
189 let account_id = AccountId::new_evm(chain_id, format!("{}", sw_address));
190 let block_number = provider.get_block_number().await.unwrap();
191 println!("{}", block_number);
192 provider.anvil_mine(Some(50), None).await.unwrap();
193 println!("{}", provider.get_block_number().await.unwrap());
194 let _tx = sw
196 .removeOwnerAtIndex(U256::from(1))
197 .from(owner1.address())
198 .send()
199 .await
200 .unwrap()
201 .get_receipt()
202 .await
203 .unwrap();
204
205 let res = verifier
206 .is_valid_signature(
207 account_id.clone(),
208 *hash,
209 SignatureWithNonce::abi_encode(&(U256::from(1), sig1.as_bytes())).into(),
210 None,
211 )
212 .await;
213 assert!(res.is_err());
214 let res = verifier
218 .is_valid_signature(
219 account_id.clone(),
220 *hash,
221 SignatureWithNonce::abi_encode(&(U256::from(1), sig1.as_bytes())).into(),
222 Some(block_number),
223 )
224 .await
225 .unwrap();
226 assert!(res.is_valid);
227 }
228
229 #[rstest::rstest]
231 #[timeout(Duration::from_secs(60))]
232 #[tokio::test]
233 async fn test_is_valid_signature(#[future] smart_wallet: SmartWalletContext) {
234 let SmartWalletContext {
235 factory,
236 sw,
237 owner0: owner,
238 sw_address,
239 ..
240 } = smart_wallet.await;
241 let provider = factory.provider();
242 let chain_id = provider.get_chain_id().await.unwrap();
243 let hash = B256::random();
244 let replay_safe_hash = sw.replaySafeHash(hash).call().await.unwrap();
245 let verifier = RpcSmartContractWalletVerifier::new_from_provider(provider.clone());
246 let signature = owner.sign_hash(&replay_safe_hash).await.unwrap();
247 let signature: Bytes =
248 SignatureWithNonce::abi_encode(&(U256::from(0), signature.as_bytes())).into();
249 let account_id = AccountId::new_evm(chain_id, format!("{}", sw_address));
250
251 assert!(
253 verifier
254 .is_valid_signature(account_id.clone(), *hash, signature.clone(), None)
255 .await
256 .unwrap()
257 .is_valid
258 );
259
260 assert!(
261 !verifier
262 .is_valid_signature(account_id.clone(), *B256::random(), signature, None)
263 .await
264 .unwrap()
265 .is_valid
266 );
267
268 let signature = owner.sign_hash(&hash).await.unwrap();
270 let owner_account_id = AccountId::new_evm(chain_id, format!("{:?}", owner.address()));
271 assert!(
272 verifier
273 .is_valid_signature(
274 owner_account_id.clone(),
275 *hash,
276 signature.as_bytes().into(),
277 None
278 )
279 .await
280 .unwrap()
281 .is_valid
282 );
283
284 assert!(
285 !verifier
286 .is_valid_signature(
287 owner_account_id,
288 *B256::random(),
289 signature.as_bytes().into(),
290 None
291 )
292 .await
293 .unwrap()
294 .is_valid
295 );
296 }
297}