1use ethabi::{decode, ParamType, Token, Uint};
7use thiserror::Error;
8
9use super::{bundle_function_selector, BundleActionArgs, BundleCalldataArgs, EthEncodeError};
10
11#[derive(Debug, Error)]
12pub enum BundleDecodeError {
13 #[error("calldata too short")]
14 TooShort,
15 #[error("wrong function selector")]
16 BadSelector,
17 #[error("ABI decode failed: {0}")]
18 Abi(String),
19 #[error("unexpected token layout")]
20 Layout,
21 #[error("{0}")]
22 Encode(#[from] EthEncodeError),
23}
24
25fn bundle_action_param() -> ParamType {
26 ParamType::Tuple(vec![
27 ParamType::FixedBytes(32),
28 ParamType::Bytes,
29 ParamType::Bytes,
30 ParamType::FixedBytes(32),
31 ParamType::FixedBytes(32),
32 ParamType::FixedBytes(32),
33 ParamType::Bytes,
34 ParamType::FixedArray(Box::new(ParamType::Uint(256)), 8),
35 ParamType::FixedArray(Box::new(ParamType::Uint(256)), 3),
36 ])
37}
38
39fn bundle_top_params() -> Vec<ParamType> {
40 vec![
41 ParamType::Array(Box::new(bundle_action_param())),
42 ParamType::Uint(256),
43 ParamType::Uint(256),
44 ParamType::FixedBytes(32),
45 ParamType::FixedArray(Box::new(ParamType::Uint(256)), 3),
46 ]
47}
48
49fn token_bytes32(t: &Token) -> Result<[u8; 32], BundleDecodeError> {
50 match t {
51 Token::FixedBytes(b) if b.len() == 32 => {
52 let mut out = [0u8; 32];
53 out.copy_from_slice(b);
54 Ok(out)
55 }
56 Token::Bytes(b) if b.len() == 32 => {
57 let mut out = [0u8; 32];
58 out.copy_from_slice(b);
59 Ok(out)
60 }
61 Token::Uint(u) => Ok(uint_to_be32(u)),
62 _ => Err(BundleDecodeError::Layout),
63 }
64}
65
66fn uint_to_be32(u: &Uint) -> [u8; 32] {
67 let mut out = [0u8; 32];
68 u.to_big_endian(&mut out);
69 out
70}
71
72fn parse_action(token: &Token) -> Result<BundleActionArgs, BundleDecodeError> {
73 let fields = match token {
74 Token::Tuple(v) if v.len() == 9 => v,
75 _ => return Err(BundleDecodeError::Layout),
76 };
77 let cmx = token_bytes32(&fields[0])?;
78 let enc_ciphertext = match &fields[1] {
79 Token::Bytes(b) => b.clone(),
80 _ => return Err(BundleDecodeError::Layout),
81 };
82 let out_ciphertext = match &fields[2] {
83 Token::Bytes(b) => b.clone(),
84 _ => return Err(BundleDecodeError::Layout),
85 };
86 let epk = token_bytes32(&fields[3])?;
87 let nf_old = token_bytes32(&fields[4])?;
88 let anchor = token_bytes32(&fields[5])?;
89 let proof = match &fields[6] {
90 Token::Bytes(b) => b.clone(),
91 _ => return Err(BundleDecodeError::Layout),
92 };
93 let pub_fields = match &fields[7] {
94 Token::FixedArray(v) if v.len() == 8 => {
95 let mut out = [[0u8; 32]; 8];
96 for (i, t) in v.iter().enumerate() {
97 out[i] = token_bytes32(t)?;
98 }
99 out
100 }
101 _ => return Err(BundleDecodeError::Layout),
102 };
103 let spend_auth_sig = match &fields[8] {
104 Token::FixedArray(v) if v.len() == 3 => {
105 let mut out = [[0u8; 32]; 3];
106 for (i, t) in v.iter().enumerate() {
107 out[i] = token_bytes32(t)?;
108 }
109 out
110 }
111 _ => return Err(BundleDecodeError::Layout),
112 };
113 Ok(BundleActionArgs {
114 cmx,
115 enc_ciphertext,
116 out_ciphertext,
117 epk,
118 nf_old,
119 anchor,
120 proof,
121 pub_fields,
122 spend_auth_sig,
123 })
124}
125
126pub fn decode_bundle_calldata(calldata: &[u8]) -> Result<BundleCalldataArgs, BundleDecodeError> {
128 if calldata.len() < 4 {
129 return Err(BundleDecodeError::TooShort);
130 }
131 if calldata[..4] != bundle_function_selector() {
132 return Err(BundleDecodeError::BadSelector);
133 }
134 let tokens = decode(&bundle_top_params(), &calldata[4..])
135 .map_err(|e| BundleDecodeError::Abi(e.to_string()))?;
136 if tokens.len() != 5 {
137 return Err(BundleDecodeError::Layout);
138 }
139 let actions: Vec<BundleActionArgs> = match &tokens[0] {
140 Token::Array(items) => items.iter().map(parse_action).collect::<Result<_, _>>()?,
141 _ => return Err(BundleDecodeError::Layout),
142 };
143 let value_balance = token_bytes32(&tokens[1])?;
144 let amount = match &tokens[2] {
145 Token::Uint(u) => u.as_u64(),
146 _ => return Err(BundleDecodeError::Layout),
147 };
148 let recipient_meta = token_bytes32(&tokens[3])?;
149 let binding_sig = match &tokens[4] {
150 Token::FixedArray(v) if v.len() == 3 => {
151 let mut out = [[0u8; 32]; 3];
152 for (i, t) in v.iter().enumerate() {
153 out[i] = token_bytes32(t)?;
154 }
155 out
156 }
157 _ => return Err(BundleDecodeError::Layout),
158 };
159 Ok(BundleCalldataArgs {
160 actions,
161 value_balance,
162 amount,
163 recipient_meta,
164 binding_sig,
165 })
166}
167
168#[derive(Debug, Clone)]
170pub struct BundleActionCiphertexts {
171 pub out_ciphertext: Vec<u8>,
172 pub cv_net_x: [u8; 32],
174}
175
176pub fn bundle_actions_by_cmx(
178 calldata: &[u8],
179) -> Result<std::collections::HashMap<[u8; 32], BundleActionCiphertexts>, BundleDecodeError> {
180 let bundle = decode_bundle_calldata(calldata)?;
181 let mut map = std::collections::HashMap::new();
182 for a in bundle.actions {
183 map.insert(
184 a.cmx,
185 BundleActionCiphertexts {
186 out_ciphertext: a.out_ciphertext,
187 cv_net_x: a.pub_fields[1],
188 },
189 );
190 }
191 Ok(map)
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197 use crate::ethereum::{encode_bundle_calldata, BundleCalldataArgs};
198
199 #[test]
200 fn bundle_calldata_roundtrip_decode() {
201 let proof_bytes = vec![0xabu8; 256];
202 let enc = vec![0xCCu8; 580];
203 let out = vec![0xDDu8; 80];
204 let args = BundleCalldataArgs {
205 actions: vec![BundleActionArgs {
206 cmx: [1u8; 32],
207 enc_ciphertext: enc.clone(),
208 out_ciphertext: out.clone(),
209 epk: [2u8; 32],
210 nf_old: [3u8; 32],
211 anchor: [4u8; 32],
212 proof: proof_bytes,
213 pub_fields: [
214 [4u8; 32],
215 [5u8; 32],
216 [0u8; 32],
217 [3u8; 32],
218 [0u8; 32],
219 [0u8; 32],
220 [1u8; 32],
221 [0u8; 32],
222 ],
223 spend_auth_sig: [[6u8; 32]; 3],
224 }],
225 value_balance: [0u8; 32],
226 amount: 0,
227 recipient_meta: [0u8; 32],
228 binding_sig: [[7u8; 32]; 3],
229 };
230 let cd = encode_bundle_calldata(&args).expect("encode");
231 let decoded = decode_bundle_calldata(&cd).expect("decode");
232 assert_eq!(decoded.actions.len(), 1);
233 assert_eq!(decoded.actions[0].enc_ciphertext, enc);
234 assert_eq!(decoded.actions[0].out_ciphertext, out);
235 assert_eq!(decoded.actions[0].pub_fields[1], [5u8; 32]);
236 let map = bundle_actions_by_cmx(&cd).expect("map");
237 assert_eq!(map.get(&[1u8; 32]).unwrap().out_ciphertext, out);
238 }
239}