1use std::collections::BTreeMap;
6
7use aztec_core::abi::{
8 encode_arguments, AbiParameter, AbiType, AbiValue, FunctionArtifact, FunctionSelector,
9 FunctionType,
10};
11use aztec_core::fee::GasSettings;
12use aztec_core::hash::ChainInfo;
13use aztec_core::tx::{ExecutionPayload, FunctionCall, HashedValues, TxContext};
14use aztec_core::types::{AztecAddress, Fr};
15use aztec_core::Error;
16
17use crate::account::{AuthorizationProvider, EntrypointOptions, TxExecutionRequest};
18use crate::wallet::MessageHashOrIntent;
19
20use super::encoding::EncodedAppEntrypointCalls;
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum AccountFeePaymentMethodOptions {
25 External = 0,
27 PreexistingFeeJuice = 1,
29 FeeJuiceWithClaim = 2,
31}
32
33#[derive(Clone, Debug)]
35pub struct DefaultAccountEntrypointOptions {
36 pub cancellable: bool,
38 pub tx_nonce: Option<Fr>,
40 pub fee_payment_method_options: AccountFeePaymentMethodOptions,
42}
43
44impl Default for DefaultAccountEntrypointOptions {
45 fn default() -> Self {
46 Self {
47 cancellable: false,
48 tx_nonce: None,
49 fee_payment_method_options: AccountFeePaymentMethodOptions::External,
50 }
51 }
52}
53
54impl From<DefaultAccountEntrypointOptions> for EntrypointOptions {
55 fn from(_opts: DefaultAccountEntrypointOptions) -> Self {
56 Self {
57 fee_payer: None,
58 gas_settings: None,
59 fee_payment_method: None,
60 }
61 }
62}
63
64struct EntrypointCallData {
65 encoded_calls: EncodedAppEntrypointCalls,
66 encoded_args: Vec<Fr>,
67 function_selector: FunctionSelector,
68 payload_auth_witness: aztec_core::tx::AuthWitness,
69}
70
71pub struct DefaultAccountEntrypoint {
73 address: AztecAddress,
74 auth: Box<dyn AuthorizationProvider>,
75}
76
77impl DefaultAccountEntrypoint {
78 pub fn new(address: AztecAddress, auth: Box<dyn AuthorizationProvider>) -> Self {
80 Self { address, auth }
81 }
82
83 pub async fn create_tx_execution_request(
85 &self,
86 exec: ExecutionPayload,
87 gas_settings: GasSettings,
88 chain_info: &ChainInfo,
89 options: &DefaultAccountEntrypointOptions,
90 ) -> Result<TxExecutionRequest, Error> {
91 let call_data = self
92 .build_entrypoint_call_data(&exec, chain_info, options)
93 .await?;
94 let entrypoint_hashed_args = HashedValues::from_args(call_data.encoded_args.clone());
95
96 let mut args_of_calls = call_data.encoded_calls.hashed_args().to_vec();
97 args_of_calls.push(entrypoint_hashed_args.clone());
98 args_of_calls.extend(exec.extra_hashed_args);
99
100 let mut auth_witnesses = exec.auth_witnesses;
101 auth_witnesses.push(call_data.payload_auth_witness);
102
103 let fee_payer = exec.fee_payer.filter(|fp| *fp != self.address);
104
105 Ok(TxExecutionRequest {
106 origin: self.address,
107 function_selector: call_data.function_selector,
108 first_call_args_hash: entrypoint_hashed_args.hash,
109 tx_context: TxContext {
110 chain_id: chain_info.chain_id,
111 version: chain_info.version,
112 gas_settings,
113 },
114 args_of_calls,
115 auth_witnesses,
116 capsules: exec.capsules,
117 salt: Fr::random(),
118 fee_payer,
119 })
120 }
121
122 pub async fn wrap_execution_payload(
124 &self,
125 exec: ExecutionPayload,
126 chain_info: &ChainInfo,
127 options: &DefaultAccountEntrypointOptions,
128 ) -> Result<ExecutionPayload, Error> {
129 let call_data = self
130 .build_entrypoint_call_data(&exec, chain_info, options)
131 .await?;
132
133 let entrypoint_call = FunctionCall {
134 to: self.address,
135 selector: call_data.function_selector,
136 args: vec![
137 build_app_payload_value(&call_data.encoded_calls),
138 AbiValue::Integer(options.fee_payment_method_options as i128),
139 AbiValue::Boolean(options.cancellable),
140 ],
141 function_type: FunctionType::Private,
142 is_static: false,
143 hide_msg_sender: false,
144 };
145
146 let mut wrapped_auth_witnesses = vec![call_data.payload_auth_witness];
147 wrapped_auth_witnesses.extend(exec.auth_witnesses);
148
149 let mut wrapped_hashed_args = call_data.encoded_calls.hashed_args().to_vec();
150 wrapped_hashed_args.extend(exec.extra_hashed_args);
151
152 Ok(ExecutionPayload {
153 calls: vec![entrypoint_call],
154 auth_witnesses: wrapped_auth_witnesses,
155 capsules: exec.capsules,
156 extra_hashed_args: wrapped_hashed_args,
157 fee_payer: exec.fee_payer.or(Some(self.address)),
158 })
159 }
160
161 pub fn entrypoint_abi() -> FunctionArtifact {
166 let function_selector_struct = AbiType::Struct {
167 name: "authwit::aztec::protocol_types::abis::function_selector::FunctionSelector"
168 .to_owned(),
169 fields: vec![AbiParameter {
170 name: "inner".to_owned(),
171 typ: AbiType::Integer {
172 sign: "unsigned".to_owned(),
173 width: 32,
174 },
175 visibility: None,
176 }],
177 };
178 let address_struct = AbiType::Struct {
179 name: "authwit::aztec::protocol_types::address::AztecAddress".to_owned(),
180 fields: vec![AbiParameter {
181 name: "inner".to_owned(),
182 typ: AbiType::Field,
183 visibility: None,
184 }],
185 };
186 let function_call_struct = AbiType::Struct {
187 name: "authwit::entrypoint::function_call::FunctionCall".to_owned(),
188 fields: vec![
189 AbiParameter {
190 name: "args_hash".to_owned(),
191 typ: AbiType::Field,
192 visibility: None,
193 },
194 AbiParameter {
195 name: "function_selector".to_owned(),
196 typ: function_selector_struct,
197 visibility: None,
198 },
199 AbiParameter {
200 name: "target_address".to_owned(),
201 typ: address_struct,
202 visibility: None,
203 },
204 AbiParameter {
205 name: "is_public".to_owned(),
206 typ: AbiType::Boolean,
207 visibility: None,
208 },
209 AbiParameter {
210 name: "hide_msg_sender".to_owned(),
211 typ: AbiType::Boolean,
212 visibility: None,
213 },
214 AbiParameter {
215 name: "is_static".to_owned(),
216 typ: AbiType::Boolean,
217 visibility: None,
218 },
219 ],
220 };
221 let app_payload_struct = AbiType::Struct {
222 name: "authwit::entrypoint::app::AppPayload".to_owned(),
223 fields: vec![
224 AbiParameter {
225 name: "function_calls".to_owned(),
226 typ: AbiType::Array {
227 element: Box::new(function_call_struct),
228 length: 5,
229 },
230 visibility: None,
231 },
232 AbiParameter {
233 name: "tx_nonce".to_owned(),
234 typ: AbiType::Field,
235 visibility: None,
236 },
237 ],
238 };
239
240 FunctionArtifact {
241 name: "entrypoint".to_owned(),
242 function_type: FunctionType::Private,
243 is_initializer: false,
244 is_static: false,
245 is_only_self: Some(false),
246 parameters: vec![
247 AbiParameter {
248 name: "app_payload".to_owned(),
249 typ: app_payload_struct,
250 visibility: Some("public".to_owned()),
251 },
252 AbiParameter {
253 name: "fee_payment_method".to_owned(),
254 typ: AbiType::Integer {
255 sign: "unsigned".to_owned(),
256 width: 8,
257 },
258 visibility: None,
259 },
260 AbiParameter {
261 name: "cancellable".to_owned(),
262 typ: AbiType::Boolean,
263 visibility: None,
264 },
265 ],
266 return_types: vec![],
267 error_types: Some(serde_json::Value::Object(Default::default())),
268 selector: None,
269 bytecode: None,
270 verification_key_hash: None,
271 verification_key: None,
272 custom_attributes: None,
273 is_unconstrained: None,
274 debug_symbols: None,
275 }
276 }
277
278 async fn build_entrypoint_call_data(
279 &self,
280 exec: &ExecutionPayload,
281 chain_info: &ChainInfo,
282 options: &DefaultAccountEntrypointOptions,
283 ) -> Result<EntrypointCallData, Error> {
284 let encoded_calls = EncodedAppEntrypointCalls::create(&exec.calls, options.tx_nonce)?;
285 let abi = Self::entrypoint_abi();
286 let encoded_args = encode_arguments(
287 &abi,
288 &[
289 build_app_payload_value(&encoded_calls),
290 AbiValue::Integer(options.fee_payment_method_options as i128),
291 AbiValue::Boolean(options.cancellable),
292 ],
293 )?;
294 let function_selector =
295 FunctionSelector::from_name_and_parameters(&abi.name, &abi.parameters);
296 let payload_auth_witness = self
297 .auth
298 .create_auth_wit(
299 MessageHashOrIntent::Hash {
300 hash: encoded_calls.hash(),
301 },
302 chain_info,
303 )
304 .await?;
305
306 Ok(EntrypointCallData {
307 encoded_calls,
308 encoded_args,
309 function_selector,
310 payload_auth_witness,
311 })
312 }
313
314 pub fn address(&self) -> AztecAddress {
316 self.address
317 }
318}
319
320fn build_app_payload_value(encoded_calls: &EncodedAppEntrypointCalls) -> AbiValue {
321 let mut payload = BTreeMap::new();
322 payload.insert(
323 "function_calls".to_owned(),
324 AbiValue::Array(
325 encoded_calls
326 .encoded_calls()
327 .iter()
328 .map(build_encoded_call_value)
329 .collect(),
330 ),
331 );
332 payload.insert(
333 "tx_nonce".to_owned(),
334 AbiValue::Field(encoded_calls.tx_nonce()),
335 );
336 AbiValue::Struct(payload)
337}
338
339fn build_encoded_call_value(call: &super::encoding::EncodedCallView) -> AbiValue {
340 let mut value = BTreeMap::new();
341 value.insert("args_hash".to_owned(), AbiValue::Field(call.args_hash));
342 value.insert(
343 "function_selector".to_owned(),
344 AbiValue::Field(call.function_selector),
345 );
346 value.insert(
347 "target_address".to_owned(),
348 AbiValue::Field(call.target_address),
349 );
350 value.insert("is_public".to_owned(), AbiValue::Boolean(call.is_public));
351 value.insert(
352 "hide_msg_sender".to_owned(),
353 AbiValue::Boolean(call.hide_msg_sender),
354 );
355 value.insert("is_static".to_owned(), AbiValue::Boolean(call.is_static));
356 AbiValue::Struct(value)
357}
358
359#[cfg(test)]
360#[allow(clippy::unwrap_used, clippy::expect_used)]
361mod tests {
362 use super::*;
363 use async_trait::async_trait;
364 use aztec_core::abi::{AbiValue, FunctionSelector, FunctionType};
365 use aztec_core::tx::AuthWitness;
366
367 struct MockAuth;
368
369 #[async_trait]
370 impl AuthorizationProvider for MockAuth {
371 async fn create_auth_wit(
372 &self,
373 intent: MessageHashOrIntent,
374 _chain_info: &ChainInfo,
375 ) -> Result<AuthWitness, Error> {
376 let hash = match intent {
377 MessageHashOrIntent::Hash { hash } => hash,
378 _ => Fr::zero(),
379 };
380 Ok(AuthWitness {
381 request_hash: hash,
382 fields: vec![hash, Fr::from(1u64)],
383 })
384 }
385 }
386
387 fn sample_chain_info() -> ChainInfo {
388 ChainInfo {
389 chain_id: Fr::from(31337u64),
390 version: Fr::from(1u64),
391 }
392 }
393
394 fn sample_exec() -> ExecutionPayload {
395 ExecutionPayload {
396 calls: vec![FunctionCall {
397 to: AztecAddress::from(1u64),
398 selector: FunctionSelector::from_hex("0x11223344").expect("valid"),
399 args: vec![AbiValue::Field(Fr::from(99u64))],
400 function_type: FunctionType::Private,
401 is_static: false,
402 hide_msg_sender: false,
403 }],
404 ..Default::default()
405 }
406 }
407
408 #[tokio::test]
409 async fn wrap_creates_single_entrypoint_call() {
410 let entrypoint =
411 DefaultAccountEntrypoint::new(AztecAddress::from(42u64), Box::new(MockAuth));
412 let wrapped = entrypoint
413 .wrap_execution_payload(
414 sample_exec(),
415 &sample_chain_info(),
416 &DefaultAccountEntrypointOptions::default(),
417 )
418 .await
419 .expect("wrap");
420
421 assert_eq!(wrapped.calls.len(), 1);
422 assert_eq!(wrapped.calls[0].to, AztecAddress::from(42u64));
423 assert_eq!(wrapped.auth_witnesses.len(), 1);
424 assert!(!wrapped.extra_hashed_args.is_empty());
425 assert_eq!(wrapped.fee_payer, Some(AztecAddress::from(42u64)));
426 }
427
428 #[tokio::test]
429 async fn wrap_prepends_payload_auth_witness() {
430 let entrypoint =
431 DefaultAccountEntrypoint::new(AztecAddress::from(42u64), Box::new(MockAuth));
432 let exec = ExecutionPayload {
433 auth_witnesses: vec![AuthWitness {
434 request_hash: Fr::from(1u64),
435 fields: vec![Fr::from(999u64)],
436 }],
437 ..sample_exec()
438 };
439
440 let wrapped = entrypoint
441 .wrap_execution_payload(
442 exec,
443 &sample_chain_info(),
444 &DefaultAccountEntrypointOptions::default(),
445 )
446 .await
447 .expect("wrap");
448
449 assert_eq!(wrapped.auth_witnesses.len(), 2);
450 assert_ne!(wrapped.auth_witnesses[0].fields, vec![Fr::from(999u64)]);
451 assert_eq!(wrapped.auth_witnesses[1].fields, vec![Fr::from(999u64)]);
452 }
453
454 #[tokio::test]
455 async fn create_tx_execution_request_uses_upstream_shape() {
456 let entrypoint =
457 DefaultAccountEntrypoint::new(AztecAddress::from(42u64), Box::new(MockAuth));
458 let gas_settings = GasSettings::default();
459
460 let request = entrypoint
461 .create_tx_execution_request(
462 sample_exec(),
463 gas_settings.clone(),
464 &sample_chain_info(),
465 &DefaultAccountEntrypointOptions::default(),
466 )
467 .await
468 .expect("request");
469
470 assert_eq!(request.origin, AztecAddress::from(42u64));
471 assert_eq!(request.tx_context.gas_settings, gas_settings);
472 assert_eq!(request.args_of_calls.len(), 6);
473 assert_eq!(request.auth_witnesses.len(), 1);
474 assert_ne!(request.first_call_args_hash, Fr::zero());
475 }
476}