diff --git a/noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr b/noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr index 86bf93064d84..f57eaafff7d2 100644 --- a/noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr +++ b/noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr @@ -51,6 +51,68 @@ where } } + /// Makes the call to this private function. + /// + /// # Arguments + /// * `context` - The PrivateContext -- made magically available to the body + /// of every #[private] function as `context`, through the + /// #[private] annotation's macro. + /// + /// # Returns + /// * `T` - Whatever data the called function has returned. + /// + /// This enables contracts to interact with each other while maintaining + /// privacy. This "composability" of private contract functions is a key + /// feature of the Aztec network. + /// + /// If a user's transaction includes multiple private function calls, then + /// by the design of Aztec, the following information will remain private[1]: + /// - The function selectors and contract addresses of all private function + /// calls will remain private, so an observer of the public mempool will + /// not be able to look at a tx and deduce which private functions have + /// been executed. + /// - The arguments and return values of all private function calls will + /// remain private. + /// - The person who initiated the tx will remain private. + /// - The notes and nullifiers and private logs that are emitted by all + /// private function calls will (if designed well) not leak any user + /// secrets, nor leak which functions have been executed. + /// + /// [1] Caveats: Some of these privacy guarantees depend on how app + /// developers design their smart contracts. Some actions _can_ leak + /// information, such as: + /// - Calling an internal public function. + /// - Calling a public function and not setting msg_sender to Option::none + /// (see https://github.com/AztecProtocol/aztec-packages/pull/16433) + /// - Calling any public function will always leak details about the nature + /// of the transaction, so devs should be careful in their contract + /// designs. If it can be done in a private function, then that will give + /// the best privacy. + /// - Not padding the side-effects of a tx to some standardised, uniform + /// size. The kernel circuits can take hints to pad side-effects, so a + /// wallet should be able to request for a particular amount of padding. + /// Wallets should ideally agree on some standard. + /// - Padding should include: + /// - Padding the lengths of note & nullifier arrays + /// - Padding private logs with random fields, up to some standardised + /// size. + /// See also: https://docs.aztec.network/developers/reference/considerations/privacy_considerations + /// + /// # Advanced + /// * The call is added to the private call stack and executed by kernel + /// circuits after this function completes + /// * The called function can modify its own contract's private state + /// * Side effects from the called function are included in this transaction + /// * The call inherits the current transaction's context and gas limits + /// + /// # Arguments + /// * `context` - The PrivateContext -- made magically available to the body + /// of every #[private] function as `context`, through the + /// #[private] annotation's macro. + /// + /// # Returns + /// * `T` - Whatever data the called function has returned. + /// pub fn call(self, context: &mut PrivateContext) -> T { execution_cache::store(self.args, self.args_hash); let returns_hash = context.call_private_function_with_args_hash( @@ -65,6 +127,22 @@ where returns_hash.get_preimage() } + /// Makes a _read-only_ call to this private function. + /// + /// This is similar to Solidity's `staticcall`. The called function + /// cannot modify state, emit L2->L1 messages, nor emit events. Any nested + /// calls are constrained to also be staticcalls. + /// + /// See `call` for more general info on private function calls. + /// + /// # Arguments + /// * `context` - The PrivateContext -- made magically available to the body + /// of every #[private] function as `context`, through the + /// #[private] annotation's macro. + /// + /// # Returns + /// * `T` - Whatever data the called function has returned. + /// pub fn view(self, context: &mut PrivateContext) -> T { execution_cache::store(self.args, self.args_hash); let returns_hash = context.call_private_function_with_args_hash( @@ -132,6 +210,17 @@ impl PrivateStaticCallInterface { } } + /// Makes a read-only call to this private function. + /// + /// This is similar to Solidity's `staticcall`. The called function + /// cannot modify state, emit L2->L1 messages, nor emit events. Any nested + /// calls are constrained to also be staticcalls. + /// + /// # Arguments + /// * `context` - The PrivateContext -- made magically available to the body + /// of every #[private] function as `context`, through the + /// #[private] annotation's macro. + /// pub fn view(self, context: &mut PrivateContext) -> T where T: Deserialize, @@ -208,6 +297,18 @@ where self } + /// Makes the call to this public function. + /// + /// Will revert if the called function reverts or runs out of gas. + /// + /// # Arguments + /// * `context` - The PublicContext -- made magically available to the body + /// of every #[public] function as `context`, through the + /// #[public] annotation's macro. + /// + /// # Returns + /// * `T` - Whatever data the called function has returned. + /// pub unconstrained fn call(self, context: &mut PublicContext) -> T { let returns = context.call_public_function( self.target_contract, @@ -220,6 +321,22 @@ where Deserialize::deserialize(returns.as_array()) } + /// Makes a read-only call to this public function. + /// + /// This is similar to Solidity's `staticcall`. The called function + /// cannot modify state or emit events. Any nested calls are constrained to + /// also be staticcalls. + /// + /// Will revert if the called function reverts or runs out of gas. + /// + /// # Arguments + /// * `context` - The PublicContext -- made magically available to the body + /// of every #[public] function as `context`, through the + /// #[public] annotation's macro. + /// + /// # Returns + /// * `T` - Whatever data the called function has returned. + /// pub unconstrained fn view(self, context: &mut PublicContext) -> T { let returns = context.static_call_public_function( self.target_contract, @@ -232,6 +349,35 @@ where Deserialize::deserialize(returns.as_array()) } + /// Enqueues a call to this public function, to be executed later. + /// + /// Unlike private functions which execute immediately on the user's device, + /// public function calls are "enqueued" and executed some time later by a + /// block proposer. + /// + /// This means a public function cannot return any values back to a private + /// function, because by the time the public function is being executed, + /// the private function which called it has already completed execution. + /// (In fact, the private function has been executed and proven, along with + /// all other private function calls of the user's tx. A single proof of the + /// tx has been submitted to the Aztec network, and some time later a + /// proposer has picked the tx up from the mempool and begun executing all + /// of the enqueued public functions). + /// + /// # Privacy warning + /// Enqueueing a public function call is an inherently leaky action. + /// Many interesting applications will require some interaction with public + /// state, but smart contract developers should try to use public function + /// calls sparingly, and carefully. + /// _Internal_ public function calls are especially leaky, because they + /// completely leak which private contract made the call. + /// See also: https://docs.aztec.network/developers/reference/considerations/privacy_considerations + /// + /// # Arguments + /// * `context` - The PrivateContext -- made magically available to the body + /// of every #[private] function as `context`, through the + /// #[private] annotation's macro. + /// pub fn enqueue(self, context: &mut PrivateContext) { let calldata = self.args.push_front(self.selector.to_field()); let calldata_hash = hash_calldata(calldata); @@ -244,6 +390,17 @@ where ) } + /// Enqueues a read-only call to this public function. + /// + /// This is similar to Solidity's `staticcall`. The called function + /// cannot modify state, emit L2->L1 messages, nor emit events. Any nested + /// calls are constrained to also be staticcalls. + /// + /// # Arguments + /// * `context` - The PrivateContext -- made magically available to the body + /// of every #[private] function as `context`, through the + /// #[private] annotation's macro. + /// pub fn enqueue_view(self, context: &mut PrivateContext) { let calldata = self.args.push_front(self.selector.to_field()); let calldata_hash = hash_calldata(calldata); @@ -256,6 +413,37 @@ where ) } + /// Enqueues a call to this public function, and designates it to be the + /// teardown function for this tx. Only one teardown function call can be + /// made by a tx. + /// + /// Niche function: Only wallet developers and paymaster contract developers + /// (aka Fee-payment contracts) will need to make use of this function. + /// + /// Aztec supports a three-phase execution model: setup, app logic, teardown. + /// The phases exist to enable a fee payer to take on the risk of paying + /// a transaction fee, safe in the knowledge that their payment (in whatever + /// token or method the user chooses) will succeed, regardless of whether + /// the app logic will succeed. The "setup" phase ensures the fee payer + /// has sufficient balance to pay the proposer their fees. + /// The teardown phase is primarily intended to: calculate exactly + /// how much the user owes, based on gas consumption, and refund the user + /// any change. + /// + /// Note: in some cases, the cost of refunding the user (i.e. DA costs of + /// tx side-effects) might exceed the refund amount. For app logic with + /// fairly stable and predictable gas consumption, a material refund amount + /// is unlikely. For app logic with unpredictable gas consumption, a + /// refund might be important to the user (e.g. if a hefty function reverts + /// very early). Wallet/FPC/Paymaster developers should be mindful of this. + /// + /// See `enqueue` for more information about enqueuing public function calls. + /// + /// # Arguments + /// * `context` - The PrivateContext -- made magically available to the body + /// of every #[private] function as `context`, through the + /// #[private] annotation's macro. + /// pub fn set_as_teardown(self, context: &mut PrivateContext) { let calldata = self.args.push_front(self.selector.to_field()); let calldata_hash = hash_calldata(calldata); @@ -328,6 +516,22 @@ where self } + /// Makes the read-only call to this public function. + /// + /// This is similar to Solidity's `staticcall`. The called function + /// cannot modify state or emit events. Any nested calls are constrained to + /// also be staticcalls. + /// + /// Will revert if the called function reverts or runs out of gas. + /// + /// # Arguments + /// * `context` - The PublicContext -- made magically available to the body + /// of every #[public] function as `context`, through the + /// #[public] annotation's macro. + /// + /// # Returns + /// * `T` - Whatever data the called function has returned. + /// pub unconstrained fn view(self, context: &mut PublicContext) -> T { let returns = context.static_call_public_function( self.target_contract, @@ -338,6 +542,17 @@ where Deserialize::deserialize(returns.as_array()) } + /// Enqueues a read-only call to this public function. + /// + /// This is similar to Solidity's `staticcall`. The called function + /// cannot modify state, emit L2->L1 messages, nor emit events. Any nested + /// calls are constrained to also be staticcalls. + /// + /// # Arguments + /// * `context` - The PrivateContext -- made magically available to the body + /// of every #[private] function as `context`, through the + /// #[private] annotation's macro. + /// pub fn enqueue_view(self, context: &mut PrivateContext) { let calldata = self.args.push_front(self.selector.to_field()); let calldata_hash = hash_calldata(calldata);