1use super::contracts::ISafe;
2use alloy_network::Ethereum;
3use alloy_primitives::{Address, B256, Bytes, U256};
4use clap::Args;
5use eyre::{Context, Result, ensure};
6use foundry_common::sh_status;
7use reqwest::{Client, Method, Url, header};
8use serde::{Deserialize, Deserializer, Serialize, de::DeserializeOwned};
9use serde_json::{Value, json};
10use std::str::FromStr;
11
12const SAFE_SIGNATURE_LENGTH: usize = 65;
13const CONTRACT_SIGNATURE_HEADER_LENGTH: usize = SAFE_SIGNATURE_LENGTH + U256::BYTES;
14const P256_SIGNATURE_DATA_LENGTH: usize = 128;
15const P256_SIGNATURE_LENGTH: usize = SAFE_SIGNATURE_LENGTH + P256_SIGNATURE_DATA_LENGTH;
16
17#[derive(Args, Clone, Debug)]
18pub struct SafeServiceOpts {
19 #[arg(long, env = "SAFE_TRANSACTION_SERVICE_URL")]
22 pub(super) service_url: Option<Url>,
23
24 #[arg(long, env = "SAFE_API_KEY")]
26 api_key: Option<String>,
27}
28
29#[derive(Debug, Deserialize)]
30#[serde(rename_all = "camelCase")]
31pub(super) struct SafeTransaction {
32 pub(super) safe: Address,
33 pub(super) to: Address,
34 #[serde(deserialize_with = "deserialize_number_string")]
35 pub(super) value: String,
36 #[serde(default, deserialize_with = "deserialize_null_default")]
37 pub(super) data: Bytes,
38 pub(super) operation: u8,
39 #[serde(deserialize_with = "deserialize_number_string")]
40 pub(super) safe_tx_gas: String,
41 #[serde(deserialize_with = "deserialize_number_string")]
42 pub(super) base_gas: String,
43 #[serde(deserialize_with = "deserialize_number_string")]
44 pub(super) gas_price: String,
45 #[serde(default, deserialize_with = "deserialize_null_default")]
46 pub(super) gas_token: Address,
47 #[serde(default, deserialize_with = "deserialize_null_default")]
48 pub(super) refund_receiver: Address,
49 #[serde(deserialize_with = "deserialize_number_string")]
50 pub(super) nonce: String,
51 #[serde(alias = "contractTransactionHash")]
52 pub(super) safe_tx_hash: B256,
53 #[serde(default, deserialize_with = "deserialize_null_default")]
54 pub(super) confirmations: Vec<SafeConfirmation>,
55 #[serde(default)]
56 pub(super) is_executed: bool,
57 #[serde(default)]
58 pub(super) transaction_hash: Option<B256>,
59}
60
61#[derive(Debug, Deserialize)]
62#[serde(rename_all = "camelCase")]
63pub(super) struct SafeConfirmation {
64 owner: Address,
65 #[serde(default)]
66 signature: Option<Bytes>,
67}
68
69#[derive(Debug, Deserialize, Serialize)]
70#[serde(rename_all = "camelCase")]
71pub(super) struct SafeDelegate {
72 safe: Option<Address>,
73 delegate: Address,
74 delegator: Address,
75 label: String,
76}
77
78#[derive(Debug, Deserialize)]
79pub(super) struct SafeDelegatesResponse {
80 #[serde(default)]
81 pub(super) next: Option<String>,
82 pub(super) results: Vec<SafeDelegate>,
83}
84
85impl SafeTransaction {
86 pub(super) fn number(value: &str, field: &str) -> Result<U256> {
87 U256::from_str(value).wrap_err_with(|| format!("invalid {field} in transaction"))
88 }
89
90 pub(super) async fn calculate_hash<P>(&self, provider: &P) -> Result<B256>
91 where
92 P: alloy_provider::Provider<Ethereum>,
93 {
94 ISafe::new(self.safe, provider)
95 .getTransactionHash(
96 self.to,
97 Self::number(&self.value, "value")?,
98 self.data.clone(),
99 self.operation,
100 Self::number(&self.safe_tx_gas, "safeTxGas")?,
101 Self::number(&self.base_gas, "baseGas")?,
102 Self::number(&self.gas_price, "gasPrice")?,
103 self.gas_token,
104 self.refund_receiver,
105 Self::number(&self.nonce, "nonce")?,
106 )
107 .call()
108 .await
109 .wrap_err("failed to calculate Safe transaction hash")
110 }
111
112 pub(super) async fn verify_hash<P>(&self, expected_safe: Address, provider: &P) -> Result<()>
113 where
114 P: alloy_provider::Provider<Ethereum>,
115 {
116 ensure!(
117 self.safe == expected_safe,
118 "Transaction Service returned Safe {}, expected {expected_safe}",
119 self.safe
120 );
121 ensure!(self.operation <= 1, "invalid Safe operation: {}", self.operation);
122 let calculated = self.calculate_hash(provider).await?;
123 ensure!(
124 calculated == self.safe_tx_hash,
125 "Safe transaction hash mismatch: service/file returned {}, calculated {calculated}",
126 self.safe_tx_hash
127 );
128 Ok(())
129 }
130
131 pub(super) fn proposal_body(
132 &self,
133 sender: Address,
134 signature: String,
135 origin: Option<String>,
136 ) -> Value {
137 let mut body = json!({
138 "to": self.to.to_checksum(None),
139 "value": self.value,
140 "data": self.data,
141 "operation": self.operation,
142 "safeTxGas": self.safe_tx_gas,
143 "baseGas": self.base_gas,
144 "gasPrice": self.gas_price,
145 "gasToken": self.gas_token.to_checksum(None),
146 "refundReceiver": self.refund_receiver.to_checksum(None),
147 "nonce": self.nonce,
148 "contractTransactionHash": self.safe_tx_hash,
149 "sender": sender.to_checksum(None),
150 "signature": signature,
151 });
152 if let Some(origin) = origin {
153 body["origin"] = Value::String(origin);
154 }
155 body
156 }
157
158 pub(super) fn packed_signatures(&self) -> Result<Bytes> {
159 let mut confirmations = self.confirmations.iter().collect::<Vec<_>>();
160 confirmations.sort_unstable_by_key(|confirmation| confirmation.owner);
161 let static_len = confirmations.len() * SAFE_SIGNATURE_LENGTH;
162 let mut signatures = Vec::with_capacity(static_len);
163 let mut dynamic = Vec::new();
164 for confirmation in confirmations {
165 let signature = confirmation.signature.as_ref().ok_or_else(|| {
166 eyre::eyre!("confirmation from {} does not contain a signature", confirmation.owner)
167 })?;
168 ensure!(
169 signature.len() >= SAFE_SIGNATURE_LENGTH,
170 "invalid signature from {}: expected at least {SAFE_SIGNATURE_LENGTH} bytes, got {}",
171 confirmation.owner,
172 signature.len()
173 );
174 let v = signature[SAFE_SIGNATURE_LENGTH - 1];
175 match v {
176 0 | 2 => {
179 let kind = if v == 0 { "contract" } else { "P-256" };
180 if v == 0 {
181 ensure!(
182 signature.len() >= CONTRACT_SIGNATURE_HEADER_LENGTH,
183 "contract signature from {} does not contain a length",
184 confirmation.owner
185 );
186 let data_len = U256::from_be_slice(
187 &signature[SAFE_SIGNATURE_LENGTH..CONTRACT_SIGNATURE_HEADER_LENGTH],
188 );
189 ensure!(
190 data_len
191 == U256::from(signature.len() - CONTRACT_SIGNATURE_HEADER_LENGTH),
192 "invalid contract signature length from {}: expected {}, got {data_len}",
193 confirmation.owner,
194 signature.len() - CONTRACT_SIGNATURE_HEADER_LENGTH
195 );
196 } else {
197 ensure!(
198 signature.len() == P256_SIGNATURE_LENGTH,
199 "invalid P-256 signature from {}: expected {P256_SIGNATURE_LENGTH} bytes, got {}",
200 confirmation.owner,
201 signature.len()
202 );
203 }
204 let offset =
205 U256::from_be_slice(&signature[U256::BYTES..SAFE_SIGNATURE_LENGTH - 1]);
206 ensure!(
207 offset == U256::from(SAFE_SIGNATURE_LENGTH),
208 "invalid {kind} signature offset from {}: expected {SAFE_SIGNATURE_LENGTH}, got {offset}",
209 confirmation.owner
210 );
211
212 signatures.extend_from_slice(&signature[..U256::BYTES]);
213 signatures.extend_from_slice(
214 &U256::from(static_len + dynamic.len()).to_be_bytes::<{ U256::BYTES }>(),
215 );
216 signatures.push(v);
217 dynamic.extend_from_slice(&signature[SAFE_SIGNATURE_LENGTH..]);
218 }
219 1 => {
220 eyre::bail!(
221 "approved-hash signatures (v = 1) are not supported by `cast safe execute`"
222 );
223 }
224 _ => {
225 ensure!(
226 signature.len() == SAFE_SIGNATURE_LENGTH,
227 "invalid signature from {}: expected {SAFE_SIGNATURE_LENGTH} bytes, got {}",
228 confirmation.owner,
229 signature.len()
230 );
231 signatures.extend_from_slice(signature);
232 }
233 }
234 }
235 ensure!(!signatures.is_empty(), "Safe transaction has no confirmations");
236 signatures.extend_from_slice(&dynamic);
237 Ok(signatures.into())
238 }
239
240 pub(super) fn show_transaction_summary(&self) -> Result<()> {
241 let operation = if self.operation == 0 { "CALL" } else { "DELEGATECALL" };
242 sh_status!("Safe transaction: {}", self.safe_tx_hash)?;
243 sh_status!(" Safe: {}", self.safe)?;
244 sh_status!(" To: {}", self.to)?;
245 sh_status!(" Value: {}", self.value)?;
246 sh_status!(" Operation: {} ({operation})", self.operation)?;
247 sh_status!(" Safe tx gas: {}", self.safe_tx_gas)?;
248 sh_status!(" Base gas: {}", self.base_gas)?;
249 sh_status!(" Gas price: {}", self.gas_price)?;
250 sh_status!(" Gas token: {}", self.gas_token)?;
251 sh_status!(" Refund receiver: {}", self.refund_receiver)?;
252 sh_status!(" Nonce: {}", self.nonce)?;
253 sh_status!(" Data: {}", self.data)?;
254 Ok(())
255 }
256}
257
258impl SafeServiceOpts {
259 pub(super) fn endpoint(&self, chain_id: u64, path: &str) -> Result<Url> {
260 let mut url = match &self.service_url {
261 Some(url) => url.clone(),
262 None => default_service_url(chain_id)?,
263 };
264 let mut endpoint = url.path().trim_end_matches('/').to_string();
265 if !endpoint.ends_with("/api") {
266 endpoint.push_str("/api");
267 }
268 endpoint.push('/');
269 endpoint.push_str(path.trim_start_matches('/'));
270 url.set_path(&endpoint);
271 url.set_query(None);
272 url.set_fragment(None);
273 Ok(url)
274 }
275
276 pub(super) fn request(&self, method: Method, url: Url) -> reqwest::RequestBuilder {
277 let request = Client::new()
278 .request(method, url)
279 .header(header::ACCEPT, "application/json")
280 .header(header::CONTENT_TYPE, "application/json");
281 if let Some(api_key) = &self.api_key { request.bearer_auth(api_key) } else { request }
282 }
283
284 async fn send(&self, request: reqwest::RequestBuilder) -> Result<String> {
286 let response = request.send().await.wrap_err("Safe Transaction Service request failed")?;
287 let status = response.status();
288 let text = response.text().await.wrap_err("failed to read Transaction Service response")?;
289 ensure!(status.is_success(), "Safe Transaction Service returned {status}: {}", text.trim());
290 Ok(text)
291 }
292
293 pub(super) async fn response<T: DeserializeOwned>(
294 &self,
295 request: reqwest::RequestBuilder,
296 ) -> Result<T> {
297 serde_json::from_str(&self.send(request).await?)
298 .wrap_err("invalid Transaction Service response")
299 }
300
301 pub(super) async fn empty_response(&self, request: reqwest::RequestBuilder) -> Result<()> {
302 self.send(request).await.map(drop)
303 }
304
305 pub(super) async fn get_transaction(
306 &self,
307 chain_id: u64,
308 api_version: &str,
309 safe_tx_hash: B256,
310 ) -> Result<SafeTransaction> {
311 let url = self
312 .endpoint(chain_id, &format!("{api_version}/multisig-transactions/{safe_tx_hash}/"))?;
313 let transaction: SafeTransaction = self.response(self.request(Method::GET, url)).await?;
314 ensure!(
315 transaction.safe_tx_hash == safe_tx_hash,
316 "Transaction Service returned a different Safe transaction hash"
317 );
318 Ok(transaction)
319 }
320
321 pub(super) async fn next_nonce(
322 &self,
323 chain_id: u64,
324 safe: Address,
325 onchain_nonce: U256,
326 ) -> Result<U256> {
327 let response: Value = self
328 .response(self.request(Method::GET, self.next_nonce_endpoint(chain_id, safe)?))
329 .await?;
330 let Some(nonce) = response
331 .get("results")
332 .and_then(Value::as_array)
333 .and_then(|results| results.first())
334 .and_then(|tx| tx.get("nonce"))
335 else {
336 return Ok(onchain_nonce);
337 };
338 let nonce = match nonce {
339 Value::String(nonce) => U256::from_str(nonce),
340 Value::Number(nonce) => U256::from_str(&nonce.to_string()),
341 _ => eyre::bail!("invalid nonce returned by Safe Transaction Service"),
342 }
343 .wrap_err("invalid nonce returned by Safe Transaction Service")?;
344 Ok(std::cmp::max(onchain_nonce, nonce.saturating_add(U256::from(1))))
345 }
346
347 fn next_nonce_endpoint(&self, chain_id: u64, safe: Address) -> Result<Url> {
348 let mut url = self.endpoint(
349 chain_id,
350 &format!("v1/safes/{}/multisig-transactions/", safe.to_checksum(None)),
351 )?;
352 url.query_pairs_mut()
353 .append_pair("executed", "false")
354 .append_pair("ordering", "-nonce")
355 .append_pair("limit", "1");
356 Ok(url)
357 }
358}
359
360fn deserialize_number_string<'de, D>(deserializer: D) -> Result<String, D::Error>
361where
362 D: Deserializer<'de>,
363{
364 match Value::deserialize(deserializer)? {
365 Value::String(value) => Ok(value),
366 Value::Number(value) => Ok(value.to_string()),
367 value => Err(serde::de::Error::custom(format!("expected number or string, got {value}"))),
368 }
369}
370
371fn deserialize_null_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
372where
373 D: Deserializer<'de>,
374 T: Deserialize<'de> + Default,
375{
376 Ok(Option::<T>::deserialize(deserializer)?.unwrap_or_default())
377}
378
379fn default_service_url(chain_id: u64) -> Result<Url> {
380 let short_name = match chain_id {
381 1 => "eth",
382 10 => "oeth",
383 56 => "bnb",
384 100 => "gno",
385 130 => "unichain",
386 137 => "pol",
387 146 => "sonic",
388 196 => "okb",
389 232 => "lens",
390 324 => "zksync",
391 480 => "wc",
392 4217 => "tempo",
393 5000 => "mantle",
394 8453 => "base",
395 9745 => "plasma",
396 10200 => "chi",
397 42161 => "arb1",
398 42220 => "celo",
399 42431 => "tempo-moderato",
400 43114 => "avax",
401 43111 => "hemi",
402 57073 => "ink",
403 59144 => "linea",
404 747474 => "katana",
405 80094 => "berachain",
406 84532 => "basesep",
407 534352 => "scr",
408 11155111 => "sep",
409 1313161554 => "aurora",
410 _ => eyre::bail!(
411 "no known Safe Transaction Service for chain ID {chain_id}; pass --service-url"
412 ),
413 };
414 format!("https://api.safe.global/tx-service/{short_name}")
415 .parse()
416 .wrap_err("invalid built-in Safe Transaction Service URL")
417}
418
419#[cfg(test)]
420mod tests {
421 use super::*;
422
423 fn transaction() -> SafeTransaction {
424 SafeTransaction {
425 safe: Address::ZERO,
426 to: Address::ZERO,
427 value: "1".to_string(),
428 data: Bytes::new(),
429 operation: 0,
430 safe_tx_gas: "0".to_string(),
431 base_gas: "0".to_string(),
432 gas_price: "0".to_string(),
433 gas_token: Address::ZERO,
434 refund_receiver: Address::ZERO,
435 nonce: "7".to_string(),
436 safe_tx_hash: B256::ZERO,
437 confirmations: Vec::new(),
438 is_executed: false,
439 transaction_hash: None,
440 }
441 }
442
443 fn eoa_signature(byte: u8) -> Bytes {
444 let mut signature = vec![byte; SAFE_SIGNATURE_LENGTH];
445 signature[SAFE_SIGNATURE_LENGTH - 1] = 27;
446 signature.into()
447 }
448
449 fn contract_signature(owner: Address, payload: &[u8]) -> Bytes {
450 let mut signature = Vec::with_capacity(CONTRACT_SIGNATURE_HEADER_LENGTH + payload.len());
451 signature.extend_from_slice(owner.into_word().as_slice());
452 signature.extend_from_slice(&U256::from(SAFE_SIGNATURE_LENGTH).to_be_bytes::<32>());
453 signature.push(0);
454 signature.extend_from_slice(&U256::from(payload.len()).to_be_bytes::<32>());
455 signature.extend_from_slice(payload);
456 signature.into()
457 }
458
459 fn p256_signature(owner: Address, payload: &[u8; 128]) -> Bytes {
460 let mut signature = Vec::with_capacity(SAFE_SIGNATURE_LENGTH + payload.len());
461 signature.extend_from_slice(owner.into_word().as_slice());
462 signature.extend_from_slice(&U256::from(SAFE_SIGNATURE_LENGTH).to_be_bytes::<32>());
463 signature.push(2);
464 signature.extend_from_slice(payload);
465 signature.into()
466 }
467
468 #[test]
469 fn normalizes_transaction_service_urls() {
470 for base in [
471 "https://api.safe.global/tx-service/tempo-moderato/",
472 "https://api.safe.global/tx-service/tempo-moderato/api",
473 ] {
474 let service =
475 SafeServiceOpts { service_url: Some(base.parse().unwrap()), api_key: None };
476 assert_eq!(
477 service.endpoint(42431, "v2/delegates/").unwrap().as_str(),
478 "https://api.safe.global/tx-service/tempo-moderato/api/v2/delegates/"
479 );
480 }
481 }
482
483 #[test]
484 fn infers_tempo_transaction_service_urls() {
485 assert_eq!(
486 default_service_url(4217).unwrap().as_str(),
487 "https://api.safe.global/tx-service/tempo"
488 );
489 assert_eq!(
490 default_service_url(42431).unwrap().as_str(),
491 "https://api.safe.global/tx-service/tempo-moderato"
492 );
493 for chain_id in [1101, 81457, 31337] {
494 assert!(default_service_url(chain_id).is_err());
495 }
496 }
497
498 #[test]
499 fn builds_pending_nonce_query_as_url_parameters() {
500 let service = SafeServiceOpts { service_url: None, api_key: None };
501 assert_eq!(
502 service.next_nonce_endpoint(42431, Address::ZERO).unwrap().as_str(),
503 "https://api.safe.global/tx-service/tempo-moderato/api/v1/safes/0x0000000000000000000000000000000000000000/multisig-transactions/?executed=false&ordering=-nonce&limit=1"
504 );
505 }
506
507 #[test]
508 fn parses_transaction_service_response() {
509 let transaction: SafeTransaction = serde_json::from_value(json!({
510 "safe": Address::ZERO,
511 "to": Address::ZERO,
512 "value": "1",
513 "data": null,
514 "operation": 0,
515 "safeTxGas": 0,
516 "baseGas": "0",
517 "gasPrice": 0,
518 "gasToken": null,
519 "refundReceiver": null,
520 "nonce": 7,
521 "safeTxHash": B256::ZERO,
522 }))
523 .unwrap();
524
525 assert_eq!(transaction.data, Bytes::new());
526 assert_eq!(transaction.nonce, "7");
527 assert_eq!(transaction.gas_token, Address::ZERO);
528 assert_eq!(transaction.refund_receiver, Address::ZERO);
529 }
530
531 #[test]
532 fn packs_confirmations_in_owner_order() {
533 let mut transaction = transaction();
534 transaction.confirmations = vec![
535 SafeConfirmation { owner: Address::repeat_byte(2), signature: Some(eoa_signature(2)) },
536 SafeConfirmation { owner: Address::repeat_byte(1), signature: Some(eoa_signature(1)) },
537 ];
538
539 let signatures = transaction.packed_signatures().unwrap();
540 assert_eq!(&signatures[..64], &[1; 64]);
541 assert_eq!(signatures[64], 27);
542 assert_eq!(&signatures[65..129], &[2; 64]);
543 assert_eq!(signatures[129], 27);
544 }
545
546 #[test]
547 fn packs_contract_signatures_after_static_signatures() {
548 let first_owner = Address::repeat_byte(1);
549 let third_owner = Address::repeat_byte(3);
550 let first_payload = [4, 5, 6];
551 let third_payload = [7, 8, 9, 10];
552
553 let mut transaction = transaction();
554 transaction.confirmations = vec![
555 SafeConfirmation {
556 owner: third_owner,
557 signature: Some(contract_signature(third_owner, &third_payload)),
558 },
559 SafeConfirmation { owner: Address::repeat_byte(2), signature: Some(eoa_signature(2)) },
560 SafeConfirmation {
561 owner: first_owner,
562 signature: Some(contract_signature(first_owner, &first_payload)),
563 },
564 ];
565
566 let signatures = transaction.packed_signatures().unwrap();
567 assert_eq!(&signatures[..32], first_owner.into_word().as_slice());
568 assert_eq!(U256::from_be_slice(&signatures[32..64]), U256::from(195));
569 assert_eq!(signatures[64], 0);
570 assert_eq!(&signatures[65..130], &eoa_signature(2));
571 assert_eq!(&signatures[130..162], third_owner.into_word().as_slice());
572 assert_eq!(U256::from_be_slice(&signatures[162..194]), U256::from(230));
573 assert_eq!(signatures[194], 0);
574 assert_eq!(U256::from_be_slice(&signatures[195..227]), U256::from(3));
575 assert_eq!(&signatures[227..230], &first_payload);
576 assert_eq!(U256::from_be_slice(&signatures[230..262]), U256::from(4));
577 assert_eq!(&signatures[262..], &third_payload);
578 }
579
580 #[test]
581 fn packs_p256_and_contract_signatures_after_static_signatures() {
582 let p256_owner = Address::repeat_byte(1);
583 let contract_owner = Address::repeat_byte(3);
584 let p256_payload = [4; 128];
585 let contract_payload = [5, 6, 7];
586 let mut transaction = transaction();
587 transaction.confirmations = vec![
588 SafeConfirmation {
589 owner: contract_owner,
590 signature: Some(contract_signature(contract_owner, &contract_payload)),
591 },
592 SafeConfirmation { owner: Address::repeat_byte(2), signature: Some(eoa_signature(2)) },
593 SafeConfirmation {
594 owner: p256_owner,
595 signature: Some(p256_signature(p256_owner, &p256_payload)),
596 },
597 ];
598
599 let signatures = transaction.packed_signatures().unwrap();
600 assert_eq!(&signatures[..32], p256_owner.into_word().as_slice());
601 assert_eq!(U256::from_be_slice(&signatures[32..64]), U256::from(195));
602 assert_eq!(signatures[64], 2);
603 assert_eq!(&signatures[65..130], &eoa_signature(2));
604 assert_eq!(&signatures[130..162], contract_owner.into_word().as_slice());
605 assert_eq!(U256::from_be_slice(&signatures[162..194]), U256::from(323));
606 assert_eq!(signatures[194], 0);
607 assert_eq!(&signatures[195..323], &p256_payload);
608 assert_eq!(U256::from_be_slice(&signatures[323..355]), U256::from(3));
609 assert_eq!(&signatures[355..], &contract_payload);
610 }
611
612 #[test]
613 fn rejects_malformed_confirmation_signatures() {
614 let owner = Address::repeat_byte(1);
615 let mut wrong_offset = vec![0; 97];
616 wrong_offset[32..64].copy_from_slice(&U256::from(66).to_be_bytes::<32>());
617 let mut wrong_length = vec![0; 97];
618 wrong_length[32..64].copy_from_slice(&U256::from(65).to_be_bytes::<32>());
619 wrong_length[65..97].copy_from_slice(&U256::from(1).to_be_bytes::<32>());
620
621 for signature in [vec![1; 66], vec![0; 65], wrong_offset, wrong_length] {
622 let mut transaction = transaction();
623 transaction.confirmations =
624 vec![SafeConfirmation { owner, signature: Some(signature.into()) }];
625 assert!(transaction.packed_signatures().is_err());
626 }
627 }
628
629 #[test]
630 fn rejects_malformed_p256_signatures() {
631 let owner = Address::repeat_byte(1);
632 let signature = |len: usize, offset: usize| {
633 let mut signature = vec![0; len];
634 signature[..U256::BYTES].copy_from_slice(owner.into_word().as_slice());
635 signature[U256::BYTES..SAFE_SIGNATURE_LENGTH - 1]
636 .copy_from_slice(&U256::from(offset).to_be_bytes::<32>());
637 signature[SAFE_SIGNATURE_LENGTH - 1] = 2;
638 signature
639 };
640
641 for signature in [
642 signature(SAFE_SIGNATURE_LENGTH, SAFE_SIGNATURE_LENGTH),
643 signature(P256_SIGNATURE_LENGTH - 1, SAFE_SIGNATURE_LENGTH),
644 signature(P256_SIGNATURE_LENGTH + 1, SAFE_SIGNATURE_LENGTH),
645 signature(P256_SIGNATURE_LENGTH, SAFE_SIGNATURE_LENGTH + 1),
646 ] {
647 let mut transaction = transaction();
648 transaction.confirmations =
649 vec![SafeConfirmation { owner, signature: Some(signature.into()) }];
650 assert!(transaction.packed_signatures().is_err());
651 }
652 }
653}