builder_relayer_client_rust/encode/
safe.rs

1use crate::types::{OperationType, SafeTransaction};
2use ethers::abi::{Token, encode};
3use hex::ToHex;
4
5// pack single transaction like Solidity struct used by Gnosis Safe MultiSend
6fn pack_tx(tx: &SafeTransaction) -> Vec<u8> {
7    // bytes: operation (1) + to (20) + value (32) + data length (32) + data
8    let mut out = Vec::new();
9    out.push(tx.operation as u8);
10    // address
11    let addr_bytes = hex::decode(tx.to.trim_start_matches("0x")).unwrap_or_default();
12    let mut addr_fixed = vec![0u8; 20];
13    if addr_bytes.len() == 20 {
14        addr_fixed.copy_from_slice(&addr_bytes);
15    } else if addr_bytes.len() > 20 {
16        addr_fixed.copy_from_slice(&addr_bytes[addr_bytes.len() - 20..]);
17    }
18    out.extend_from_slice(&addr_fixed);
19    // value (uint256)
20    let value = ethers::types::U256::from_dec_str(&tx.value).unwrap_or_default();
21    let mut value_be = [0u8; 32];
22    value.to_big_endian(&mut value_be);
23    out.extend_from_slice(&value_be);
24    // data length (uint256)
25    let data_bytes = hex::decode(tx.data.trim_start_matches("0x")).unwrap_or_default();
26    let mut len_be = [0u8; 32];
27    ethers::types::U256::from(data_bytes.len()).to_big_endian(&mut len_be);
28    out.extend_from_slice(&len_be);
29    // data
30    out.extend_from_slice(&data_bytes);
31    out
32}
33
34pub fn create_safe_multisend_transaction(
35    txns: &[SafeTransaction],
36    safe_multisend_address: &str,
37) -> SafeTransaction {
38    if txns.len() == 1 {
39        return txns[0].clone();
40    }
41    let mut packed: Vec<u8> = Vec::new();
42    for t in txns {
43        packed.extend_from_slice(&pack_tx(t));
44    }
45    // encode function selector + encoded bytes via ethers abi
46    // multiSend(bytes)
47    let calldata = encode(&[Token::Bytes(packed)]);
48    let selector = &keccak256(b"multiSend(bytes)")[..4];
49    let mut final_data = Vec::from(selector);
50    final_data.extend_from_slice(&calldata);
51    SafeTransaction {
52        to: safe_multisend_address.to_string(),
53        value: "0".to_string(),
54        data: format!("0x{}", final_data.encode_hex::<String>()),
55        operation: OperationType::DelegateCall,
56    }
57}
58
59fn keccak256(data: &[u8]) -> [u8; 32] {
60    use sha3::{Digest, Keccak256};
61    let mut h = Keccak256::new();
62    h.update(data);
63    let out = h.finalize();
64    let mut arr = [0u8; 32];
65    arr.copy_from_slice(&out);
66    arr
67}