Skip to main content

safe_rs/encoding/
multisend.rs

1//! MultiSend transaction encoding
2//!
3//! The MultiSend contract expects transactions to be encoded in a packed format:
4//! - operation: 1 byte (0 = Call, 1 = DelegateCall)
5//! - to: 20 bytes
6//! - value: 32 bytes
7//! - data length: 32 bytes
8//! - data: variable length
9
10use alloy::primitives::Bytes;
11
12use crate::types::SafeCall;
13
14/// Encodes a single transaction for MultiSend packed format
15///
16/// Format: operation (1 byte) | to (20 bytes) | value (32 bytes) | data length (32 bytes) | data
17pub fn encode_transaction(call: &impl SafeCall) -> Vec<u8> {
18    let data = call.data();
19    let data_len = data.len();
20
21    // Calculate total size: 1 + 20 + 32 + 32 + data_len
22    let mut encoded = Vec::with_capacity(85 + data_len);
23
24    // Operation (1 byte)
25    encoded.push(call.operation().as_u8());
26
27    // To address (20 bytes)
28    encoded.extend_from_slice(call.to().as_slice());
29
30    // Value (32 bytes, big-endian)
31    encoded.extend_from_slice(&call.value().to_be_bytes::<32>());
32
33    // Data length (32 bytes, big-endian)
34    let mut data_len_bytes = [0u8; 32];
35    data_len_bytes[24..].copy_from_slice(&(data_len as u64).to_be_bytes());
36    encoded.extend_from_slice(&data_len_bytes);
37
38    // Data
39    encoded.extend_from_slice(&data);
40
41    encoded
42}
43
44/// Encodes multiple transactions for MultiSend
45pub fn encode_multisend_data(calls: &[impl SafeCall]) -> Bytes {
46    let mut encoded = Vec::new();
47
48    for call in calls {
49        encoded.extend(encode_transaction(call));
50    }
51
52    Bytes::from(encoded)
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58    use crate::types::Call;
59    use alloy::primitives::{address, U256};
60
61    #[test]
62    fn test_encode_single_transaction() {
63        let call = Call::new(
64            address!("0x1234567890123456789012345678901234567890"),
65            U256::from(1000),
66            vec![0xa9, 0x05, 0x9c, 0xbb], // transfer selector
67        );
68
69        let encoded = encode_transaction(&call);
70
71        // Check operation byte
72        assert_eq!(encoded[0], 0); // Call
73
74        // Check address (bytes 1-20)
75        assert_eq!(
76            &encoded[1..21],
77            address!("0x1234567890123456789012345678901234567890").as_slice()
78        );
79
80        // Check value (bytes 21-52)
81        let value_bytes = &encoded[21..53];
82        assert_eq!(value_bytes[31], 0xe8); // 1000 = 0x3e8
83        assert_eq!(value_bytes[30], 0x03);
84
85        // Check data length (bytes 53-84)
86        let len_bytes = &encoded[53..85];
87        assert_eq!(len_bytes[31], 4); // 4 bytes of data
88
89        // Check data (bytes 85+)
90        assert_eq!(&encoded[85..], &[0xa9, 0x05, 0x9c, 0xbb]);
91    }
92
93    #[test]
94    fn test_encode_delegate_call() {
95        let call = Call::delegate_call(
96            address!("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"),
97            vec![0x01, 0x02],
98        );
99
100        let encoded = encode_transaction(&call);
101        assert_eq!(encoded[0], 1); // DelegateCall
102    }
103
104    #[test]
105    fn test_encode_multisend_data() {
106        let calls = vec![
107            Call::call(
108                address!("0x1111111111111111111111111111111111111111"),
109                vec![0x01],
110            ),
111            Call::call(
112                address!("0x2222222222222222222222222222222222222222"),
113                vec![0x02],
114            ),
115        ];
116
117        let encoded = encode_multisend_data(&calls);
118
119        // First transaction: 1 + 20 + 32 + 32 + 1 = 86 bytes
120        // Second transaction: 1 + 20 + 32 + 32 + 1 = 86 bytes
121        assert_eq!(encoded.len(), 172);
122    }
123
124    #[test]
125    fn test_encode_empty_data() {
126        let call = Call::call(
127            address!("0x1234567890123456789012345678901234567890"),
128            vec![],
129        );
130
131        let encoded = encode_transaction(&call);
132
133        // 1 + 20 + 32 + 32 + 0 = 85 bytes
134        assert_eq!(encoded.len(), 85);
135
136        // Check data length is 0
137        let len_bytes = &encoded[53..85];
138        assert!(len_bytes.iter().all(|&b| b == 0));
139    }
140}