1use std::collections::BTreeMap;
6
7use aztec_core::abi::{
8 encode_arguments, AbiParameter, AbiType, AbiValue, FunctionArtifact, FunctionSelector,
9 FunctionType,
10};
11use aztec_core::constants::protocol_contract_address;
12use aztec_core::fee::GasSettings;
13use aztec_core::hash::ChainInfo;
14use aztec_core::tx::{ExecutionPayload, FunctionCall, HashedValues, TxContext};
15use aztec_core::types::{AztecAddress, Fr};
16use aztec_core::Error;
17
18use crate::account::TxExecutionRequest;
19
20use super::encoding::EncodedAppEntrypointCalls;
21
22pub struct DefaultMultiCallEntrypoint {
24 address: AztecAddress,
25}
26
27impl DefaultMultiCallEntrypoint {
28 pub fn new() -> Self {
30 Self {
31 address: protocol_contract_address::multi_call_entrypoint(),
32 }
33 }
34
35 pub fn with_address(address: AztecAddress) -> Self {
37 Self { address }
38 }
39
40 pub fn create_tx_execution_request(
42 &self,
43 exec: ExecutionPayload,
44 gas_settings: GasSettings,
45 chain_info: &ChainInfo,
46 ) -> Result<TxExecutionRequest, Error> {
47 let call_data = self.build_entrypoint_call_data(&exec)?;
48 let entrypoint_hashed_args = HashedValues::from_args(call_data.encoded_args.clone());
49
50 let mut args_of_calls = call_data.encoded_calls.hashed_args().to_vec();
51 args_of_calls.push(entrypoint_hashed_args.clone());
52 args_of_calls.extend(exec.extra_hashed_args);
53
54 Ok(TxExecutionRequest {
55 origin: self.address,
56 function_selector: call_data.function_selector,
57 first_call_args_hash: entrypoint_hashed_args.hash,
58 tx_context: TxContext {
59 chain_id: chain_info.chain_id,
60 version: chain_info.version,
61 gas_settings,
62 },
63 args_of_calls,
64 auth_witnesses: exec.auth_witnesses,
65 capsules: exec.capsules,
66 salt: Fr::random(),
67 fee_payer: exec.fee_payer.filter(|fp| *fp != self.address),
68 })
69 }
70
71 pub fn wrap_execution_payload(
73 &self,
74 exec: ExecutionPayload,
75 ) -> Result<ExecutionPayload, Error> {
76 let call_data = self.build_entrypoint_call_data(&exec)?;
77
78 let entrypoint_call = FunctionCall {
79 to: self.address,
80 selector: call_data.function_selector,
81 args: vec![build_app_payload_value(&call_data.encoded_calls)],
82 function_type: FunctionType::Private,
83 is_static: false,
84 hide_msg_sender: false,
85 };
86
87 let mut extra_hashed_args = call_data.encoded_calls.hashed_args().to_vec();
88 extra_hashed_args.extend(exec.extra_hashed_args);
89
90 Ok(ExecutionPayload {
91 calls: vec![entrypoint_call],
92 auth_witnesses: exec.auth_witnesses,
93 capsules: exec.capsules,
94 extra_hashed_args,
95 fee_payer: exec.fee_payer,
96 })
97 }
98
99 fn build_entrypoint_call_data(
100 &self,
101 exec: &ExecutionPayload,
102 ) -> Result<EntrypointCallData, Error> {
103 let encoded_calls = EncodedAppEntrypointCalls::create(&exec.calls, None)?;
104 let abi = Self::entrypoint_abi();
105 let encoded_args = encode_arguments(&abi, &[build_app_payload_value(&encoded_calls)])?;
106 let function_selector =
107 FunctionSelector::from_name_and_parameters(&abi.name, &abi.parameters);
108
109 Ok(EntrypointCallData {
110 encoded_calls,
111 encoded_args,
112 function_selector,
113 })
114 }
115
116 fn entrypoint_abi() -> FunctionArtifact {
117 let function_selector_struct = AbiType::Struct {
118 name: "authwit::aztec::protocol_types::abis::function_selector::FunctionSelector"
119 .to_owned(),
120 fields: vec![AbiParameter {
121 name: "inner".to_owned(),
122 typ: AbiType::Integer {
123 sign: "unsigned".to_owned(),
124 width: 32,
125 },
126 visibility: None,
127 }],
128 };
129 let address_struct = AbiType::Struct {
130 name: "authwit::aztec::protocol_types::address::AztecAddress".to_owned(),
131 fields: vec![AbiParameter {
132 name: "inner".to_owned(),
133 typ: AbiType::Field,
134 visibility: None,
135 }],
136 };
137 let function_call_struct = AbiType::Struct {
138 name: "authwit::entrypoint::function_call::FunctionCall".to_owned(),
139 fields: vec![
140 AbiParameter {
141 name: "args_hash".to_owned(),
142 typ: AbiType::Field,
143 visibility: None,
144 },
145 AbiParameter {
146 name: "function_selector".to_owned(),
147 typ: function_selector_struct,
148 visibility: None,
149 },
150 AbiParameter {
151 name: "target_address".to_owned(),
152 typ: address_struct,
153 visibility: None,
154 },
155 AbiParameter {
156 name: "is_public".to_owned(),
157 typ: AbiType::Boolean,
158 visibility: None,
159 },
160 AbiParameter {
161 name: "hide_msg_sender".to_owned(),
162 typ: AbiType::Boolean,
163 visibility: None,
164 },
165 AbiParameter {
166 name: "is_static".to_owned(),
167 typ: AbiType::Boolean,
168 visibility: None,
169 },
170 ],
171 };
172 let app_payload_struct = AbiType::Struct {
173 name: "authwit::entrypoint::app::AppPayload".to_owned(),
174 fields: vec![
175 AbiParameter {
176 name: "function_calls".to_owned(),
177 typ: AbiType::Array {
178 element: Box::new(function_call_struct),
179 length: 5,
180 },
181 visibility: None,
182 },
183 AbiParameter {
184 name: "tx_nonce".to_owned(),
185 typ: AbiType::Field,
186 visibility: None,
187 },
188 ],
189 };
190
191 FunctionArtifact {
192 name: "entrypoint".to_owned(),
193 function_type: FunctionType::Private,
194 is_initializer: false,
195 is_static: false,
196 is_only_self: Some(false),
197 parameters: vec![AbiParameter {
198 name: "app_payload".to_owned(),
199 typ: app_payload_struct,
200 visibility: Some("public".to_owned()),
201 }],
202 return_types: vec![],
203 error_types: Some(serde_json::Value::Object(Default::default())),
204 selector: None,
205 bytecode: None,
206 verification_key_hash: None,
207 verification_key: None,
208 custom_attributes: None,
209 is_unconstrained: None,
210 debug_symbols: None,
211 }
212 }
213
214 pub fn address(&self) -> AztecAddress {
216 self.address
217 }
218}
219
220impl Default for DefaultMultiCallEntrypoint {
221 fn default() -> Self {
222 Self::new()
223 }
224}
225
226struct EntrypointCallData {
227 encoded_calls: EncodedAppEntrypointCalls,
228 encoded_args: Vec<Fr>,
229 function_selector: FunctionSelector,
230}
231
232fn build_app_payload_value(encoded_calls: &EncodedAppEntrypointCalls) -> AbiValue {
233 let mut payload = BTreeMap::new();
234 payload.insert(
235 "function_calls".to_owned(),
236 AbiValue::Array(
237 encoded_calls
238 .encoded_calls()
239 .iter()
240 .map(|call| {
241 let mut value = BTreeMap::new();
242 value.insert("args_hash".to_owned(), AbiValue::Field(call.args_hash));
243 value.insert(
244 "function_selector".to_owned(),
245 AbiValue::Field(call.function_selector),
246 );
247 value.insert(
248 "target_address".to_owned(),
249 AbiValue::Field(call.target_address),
250 );
251 value.insert("is_public".to_owned(), AbiValue::Boolean(call.is_public));
252 value.insert(
253 "hide_msg_sender".to_owned(),
254 AbiValue::Boolean(call.hide_msg_sender),
255 );
256 value.insert("is_static".to_owned(), AbiValue::Boolean(call.is_static));
257 AbiValue::Struct(value)
258 })
259 .collect(),
260 ),
261 );
262 payload.insert(
263 "tx_nonce".to_owned(),
264 AbiValue::Field(encoded_calls.tx_nonce()),
265 );
266 AbiValue::Struct(payload)
267}
268
269#[cfg(test)]
270#[allow(clippy::unwrap_used, clippy::expect_used)]
271mod tests {
272 use super::*;
273 use aztec_core::abi::FunctionSelector;
274
275 fn sample_chain_info() -> ChainInfo {
276 ChainInfo {
277 chain_id: Fr::from(31337u64),
278 version: Fr::from(1u64),
279 }
280 }
281
282 #[test]
283 fn default_address_is_multi_call_entrypoint() {
284 let ep = DefaultMultiCallEntrypoint::new();
285 assert_eq!(
286 ep.address(),
287 protocol_contract_address::multi_call_entrypoint()
288 );
289 }
290
291 #[test]
292 fn wrap_creates_single_call_to_multi_call_address() {
293 let ep = DefaultMultiCallEntrypoint::new();
294 let exec = ExecutionPayload {
295 calls: vec![FunctionCall {
296 to: AztecAddress::from(1u64),
297 selector: FunctionSelector::from_hex("0x11223344").expect("valid"),
298 args: vec![AbiValue::Field(Fr::from(42u64))],
299 function_type: FunctionType::Private,
300 is_static: false,
301 hide_msg_sender: false,
302 }],
303 ..Default::default()
304 };
305
306 let wrapped = ep.wrap_execution_payload(exec).expect("wrap");
307
308 assert_eq!(wrapped.calls.len(), 1);
309 assert_eq!(
310 wrapped.calls[0].to,
311 protocol_contract_address::multi_call_entrypoint()
312 );
313 assert!(wrapped.auth_witnesses.is_empty());
314 assert_eq!(wrapped.extra_hashed_args.len(), 5);
315 }
316
317 #[test]
318 fn create_tx_execution_request_uses_upstream_shape() {
319 let ep = DefaultMultiCallEntrypoint::new();
320 let exec = ExecutionPayload {
321 calls: vec![FunctionCall {
322 to: AztecAddress::from(1u64),
323 selector: FunctionSelector::from_hex("0x11223344").expect("valid"),
324 args: vec![AbiValue::Field(Fr::from(42u64))],
325 function_type: FunctionType::Private,
326 is_static: false,
327 hide_msg_sender: false,
328 }],
329 ..Default::default()
330 };
331
332 let request = ep
333 .create_tx_execution_request(exec, GasSettings::default(), &sample_chain_info())
334 .expect("request");
335
336 assert_eq!(
337 request.origin,
338 protocol_contract_address::multi_call_entrypoint()
339 );
340 assert_eq!(request.args_of_calls.len(), 6);
341 assert_ne!(request.first_call_args_hash, Fr::zero());
342 }
343
344 #[test]
345 fn custom_address() {
346 let ep = DefaultMultiCallEntrypoint::with_address(AztecAddress::from(99u64));
347 assert_eq!(ep.address(), AztecAddress::from(99u64));
348 }
349}