Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 215 additions & 0 deletions noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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(
Expand Down Expand Up @@ -132,6 +210,17 @@ impl<let M: u32, T> PrivateStaticCallInterface<M, T> {
}
}

/// 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,
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down
Loading