diff --git a/docs/docs/dev_docs/contracts/functions.md b/docs/docs/dev_docs/contracts/functions.md index ceddbeb1186e..a8cb551a3483 100644 --- a/docs/docs/dev_docs/contracts/functions.md +++ b/docs/docs/dev_docs/contracts/functions.md @@ -30,22 +30,124 @@ ## Importing Contracts ### Contract Interface + +## Mixing Constrained and Unconstrained Execution + -## Constrained --> Unconstrained E.g. `get()` ## Oracle calls +### What are oracles? +Oracles are a concept in Noir that enable developers to embed external information into their circuits at proving time. An direct comparison can be seen with Ethereum oracles that are used to get external information into a smart contract (often a price feed). Despite the same naming, oracles are not to be confused with oracles in Noir. -## Private --> Private +**What is an example of an oracle in Noir** +The `Aztec.nr` framework makes heavy use of Noir's oracles for getting information about note into a circuit, for example, when reading storage at a particular storage slot, an oracle can be used to insert the value into the circuit. +Oracles introduce `non determinism` into a circuit, and thus are `unconstrained`. It is important that any information that is injected into a circuit through an oracle is later constrained for correctness. Otherwise the circuit will be `under constrained`. -## Public --> Public +`Aztec.nr` has a module dedicated to its oracles. If you are interested, you can view them by following the link below: +#include_code oracles-module /yarn-project/noir-libs/noir-aztec/src/oracle.nr rust -## Private --> Public +## Private --> Private Function Calls +In Aztec Private to Private function calls are handled by the [private kernel circuit](../../concepts/advanced/circuits/kernels/private_kernel.md), and take place on the user's device. +Behind the scenes, the `Aztec RPC Server` (the beating heart of Aztec that runs in your wallet) will execute all of the functions in the desired order "simulating" them in sequence. For example, a very common use-case of Private to Private interaction is calling another private function from an `account contract` (Account contracts are a general concept, more information about them can be found [here](../../dev_docs/wallets/writing_an_account_contract.md)). + +Take, for example, the following call stack: +``` +AccountContract::entrypoint + |-> A::example_call + | -> B::nested_call + |-> C::example_call +``` + + +In the example above the Account Contract has been instructed to call two external functions. In the first function all, to `ContractA::example_call` a further nested call is performed to `ContractB::nested_call`. Finally the account contract makes one last call to `ContractC::example_call`. + +Lets further illustrate what these examples could look like + +```rust +// Contract A contains a singular function that returns the result of B::nested_call +contract A { + #[aztec(private)] + fn example_call(input: Field) -> pub Field { + B::at().nested_call(input) + } +} + +// Contract B contains a singular function that returns a `input + 1` +contract B { + #[aztec(private)] + fn nested_call(input: Field) -> pub Field { + input + 1 + } +} + +// Contract C contains a singular function that simply returns `10` +contract C { + #[aztec(private)] + fn example_call() -> pub Field { + 10 + } +} +``` + +When simulating the following call stack, we can execution flow to continue procedurally. The simulator will begin at the account contract's entry point, find a call to `A::example_call`, then begin to execute the code there. When the simulator executes the code in contract `A`, it will find the further nested call to contract `B::nested_call`. It will execute the code in B, bringing the return value back to contract `A`. +The same process will be followed for contract `C`. + +So far the provided example is identical to other executions. Ethereum execution occurs in a similar way, during execution the EVM will execute instructions until it reaches an external call, where it will hop into a new context and execute code there, returning back when it is complete, bringing with it return values from the foreign execution. + +Those of you who have written circuits before may see an issue here. The account contract, contract `A`, `B` and `C` are all distinct circuits, which do not know anything about each other. How is it possible to use a value from contract `B` in contract `A`? This value will not be constrained. + +This is where the `kernel` circuit comes in. Once the execution of all of our functions has completed, we can just prove the execution of each of them independently. It is the job of the `kernel` circuit to constrain that the input parameters in a cross function call are correct, as well as the return values. The kernel will constrain that the value returned from `A::example_call` is the same value that is returned from `B::nested_call`, it will also be able to constrain the value returned by `B::nested_call` is the inputs to `A::example_call` + 1. + +The orchestration of these calls has an added benefit. All of the nested calls are **recursively proven**. This means that the kernel circuit essentially gobbles up each of our function's execution proofs. Condensing the size of the final proof to just be one. + + + + +## Public --> Public Function Calls +The public execution environment in Aztec takes place on the sequencer through a [Public VM](../../concepts/advanced/public_vm.md). This execution model is conceptually much simpler than the private transaction model as code is executed and proven on the sequencer. + +Using the same example code and call stack from the section [above](#private----private-function-calls), we will walk through how it gets executed in public. + +The first key difference is that public functions are not compiled to circuits, rather they are compiled to `Aztec Bytecode`. + +This bytecode is run by the sequencer in the `Aztec VM`, which is in turn proven by the [`Aztec VM circuit`](../../concepts/advanced/public_vm.md). +The mental model for public execution carries many of the same idea as are carried by Ethereum. Programs are compiled into a series of opcodes (known as bytecode). This bytecode is then executed. The extra step for the Aztec VM is that each opcode is then proven for correctness. + +**How does a public function call another public function?** + + + +## Private --> Public Function Calls +As discussed above, private function execution and calls take place on the user's device, while public function execution and calls take place on a sequencer, in two different places at two different times, it is natural to question how we can achieve composability between the two. + +The solution is asynchonsity. Further reading can be found [here](../../concepts/foundation/communication/public_private_calls.md) + +Private function execution takes place on the users device, where it keeps track of any public function calls that have been made. Whenever private execution completes, and a kernel proof is produced, the transaction sent to the network will include all of the public calls that were dispatched. +When the sequencer receives the messages, it will take over and execute the public parts of the transaction. + +As a consequence a private function *CANNOT* accept a return value from a public function. It can only dispatch it. + + ## `internal` keyword +The internal function is a way to mark functions (that you don't want to be inlined) to only be callable from within the same contract. + +You can mark a function as internal by adding the following to a function definition. + +#include_code internal_function_call /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust ## Public --> Private + ## Recursive function calls diff --git a/yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr index cd6dc5f9b295..6bc524b9e818 100644 --- a/yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr @@ -237,4 +237,36 @@ contract DocsExample { actions::get_total_points(storage.cards, account, 0) } // docs:end:functions-UncontrainedFunction + + /// Example of how to perform an internal function + #[aztec(public)] + fn public_entrypoint(value: Field) -> Field { + internal_function() + } + + // docs:start:internal_function_call + #[aztec(public)] + internal fn internal_function(value: Field) -> Field { + value + 1 + } + // docs:end:internal_function_call +} + +struct DocsExampleInterface( + at: Field +) + +impl { + fn at(address: Field) -> Self { + Self { + address, + } + } + + fn internal_function( + context: &mut PrivateContext, + ) { + let mut args = []; + context.call_public_function(self.address, 0xbac98727, []) + } }