-
Notifications
You must be signed in to change notification settings - Fork 615
feat(docs): Function transforms (hidden macros) #7784
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 22 commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
285edeb
some fixes
catmcgee 5d9478e
getting started & simple tutorial fixes
catmcgee f6b57b6
token bridge tutorial
catmcgee 4813413
tutorials
catmcgee d0326ea
aztec-nr -> Aztec.nr
catmcgee f9c7a8c
updating
catmcgee 45cd111
small fixes
catmcgee 60eabe0
Merge branch 'master' into docs/small-fixes
catmcgee 9e37aa7
format
catmcgee fcedc9e
Merge branch 'master' into docs/small-fixes
catmcgee a5a9ae3
Apply suggestions from code review
catmcgee f50f3d2
finish a sentence
catmcgee d0af4fc
nargo fmt
catmcgee cd86d7c
docs build error
catmcgee e3fff64
function hidden mcros
catmcgee a8777eb
build
catmcgee 464e9cf
buld errors
catmcgee fb4b5d9
Merge branch 'master' into docs/function-transforms
critesjosh 307001f
change name of file
catmcgee 0b31872
Merge branch 'master' into docs/function-transforms
catmcgee bbafe57
Merge branch 'master' into docs/function-transforms
catmcgee 72bab18
Merge branch 'master' into docs/function-transforms
catmcgee 6aa47b9
review suggestions
catmcgee 7ceb4f8
Merge remote-tracking branch 'origin/master' into docs/function-trans…
catmcgee 25d43f9
links
catmcgee d89a36e
noir > rust
catmcgee 8c27f07
Merge branch 'master' into docs/function-transforms
catmcgee c704235
Merge branch 'master' into docs/function-transforms
catmcgee cea5e73
build errors
catmcgee 14f7370
build errors
catmcgee 0a8a99e
broken link
catmcgee c5f214d
Merge remote-tracking branch 'origin/master' into docs/function-trans…
catmcgee 5267779
Merge branch 'master' into docs/function-transforms
catmcgee 69f9c6c
Merge branch 'master' into docs/function-transforms
catmcgee ebd45a2
pls
catmcgee 65b02f1
Merge branch 'master' into docs/function-transforms
catmcgee a023a3f
Merge branch 'master' into docs/function-transforms
catmcgee File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
284 changes: 284 additions & 0 deletions
284
docs/docs/aztec/concepts/smart_contracts/functions/function_transforms.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,284 @@ | ||
| --- | ||
| title: Inner Workings of Functions | ||
| sidebar_position: 3 | ||
| tags: [functions] | ||
| --- | ||
|
|
||
| Below, we go more into depth of what is happening under the hood when you create a function in an Aztec contract. The [next page](./attributes.md) will give you more information about what the attributes are really doing. | ||
|
|
||
|
|
||
| ## Function transformation | ||
|
|
||
| When you define a function in an Aztec contract, it undergoes several transformations under the hood. These transformations prepare the function for execution. These transformations include: | ||
|
|
||
| - [Creating a context for the function](#context-creation) | ||
| - [Handling function inputs](#input-handling) | ||
| - [Processing return values](#return-value-handling) | ||
| - [Computing note hashes and nullifiers](#computing-note-hash-and-nullifier) | ||
| - [Generating function signatures](#function-signature-generation) | ||
| - [Generating contract artifacts](#contract-artifacts) | ||
|
|
||
| Let's explore each of these transformations in detail. | ||
|
|
||
| ## Context creation | ||
|
|
||
| Every function in an Aztec contract operates within a specific context which provides some extra information and functionality. This is either a `PrivateContext` or `PublicContext` object, depending on whether it is a private or public function. For private functions, it creates a hash of all input parameters to ensure privacy. | ||
|
|
||
| ### Private functions | ||
|
|
||
| For private functions, the context creation involves hashing all input parameters: | ||
|
|
||
| ```rust | ||
| let mut args_hasher = ArgsHasher::new(); | ||
| // Hash each parameter | ||
| args_hasher.add(param1); | ||
| args_hasher.add(param2); | ||
| // add all parameters | ||
|
|
||
| let mut context = PrivateContext::new(inputs, args_hasher.hash()); | ||
|
catmcgee marked this conversation as resolved.
|
||
| ``` | ||
|
|
||
| This hashing process is important because it is used to verify the function's execution without exposing the input data. | ||
|
|
||
| ### Public functions | ||
|
|
||
| For public functions, context creation is simpler: | ||
|
|
||
| ```noir | ||
|
catmcgee marked this conversation as resolved.
Outdated
|
||
| let mut context = PublicContext::new(inputs); | ||
|
catmcgee marked this conversation as resolved.
|
||
| ``` | ||
|
|
||
| ### Using the context in functions | ||
|
|
||
| Once created, the context object provides various useful methods. Here are some common use cases: | ||
|
|
||
| #### Accessing storage | ||
|
|
||
| The context allows you to interact with contract storage. eg if you have a function that calls storage like this: | ||
|
|
||
| ```rust | ||
| let sender_balance = storage.balances.at(owner); | ||
| ``` | ||
|
|
||
| This calls the context to read from the appropriate storage slot. | ||
|
catmcgee marked this conversation as resolved.
|
||
|
|
||
| #### Interacting with other contracts | ||
|
|
||
| The context provides methods to call other contracts: | ||
|
|
||
| ```rust | ||
| let token_contract = TokenContract::at(token); | ||
| ``` | ||
|
|
||
| Under the hood, this creates a new instance of the contract interface with the specified address. | ||
|
|
||
| ## Private and public input injection | ||
|
|
||
| An additional parameter is automatically added to every function. | ||
|
|
||
| The injected input is always the first parameter of the transformed function and is of type `PrivateContextInputs` for private functions or `PublicContextInputs` for public functions. Here's how it works: | ||
|
|
||
| 1. Original function definition: | ||
| ```rust | ||
| fn my_function(param1: Type1, param2: Type2) { ... } | ||
| ``` | ||
|
|
||
| 2. Transformed function with injected input: | ||
| ```rust | ||
| fn my_function(inputs: PrivateContextInputs, param1: Type1, param2: Type2) { ... } | ||
| ``` | ||
|
|
||
| The `inputs` parameter is created using this code: | ||
|
|
||
| ```rust | ||
| fn create_inputs(ty: &str) -> Param { | ||
|
catmcgee marked this conversation as resolved.
Outdated
|
||
| let context_ident = ident("inputs"); | ||
| let context_pattern = Pattern::Identifier(context_ident); | ||
|
|
||
| let path_snippet = ty.to_case(Case::Snake); // e.g. private_context_inputs | ||
| let type_path = chained_dep!("aztec", "context", "inputs", &path_snippet, ty); | ||
|
|
||
| let context_type = make_type(UnresolvedTypeData::Named(type_path, vec![], true)); | ||
| let visibility = Visibility::Private; | ||
|
|
||
| Param { pattern: context_pattern, typ: context_type, visibility, span: Span::default() } | ||
| } | ||
| ``` | ||
|
|
||
| This makes these inputs such as `msg_sender()` available to be consumed within private annotated functions. | ||
|
|
||
| ## Return value handling | ||
|
|
||
| Return values in Aztec contracts are processed differently from traditional smart contracts when using private functions. | ||
|
|
||
| ### Private functions | ||
|
|
||
| - The original return value is assigned to a special variable: | ||
| ```rust | ||
| let macro__returned__values = original_return_expression; | ||
| ``` | ||
|
|
||
| - A new `ArgsHasher` is created for the return values: | ||
| ```rust | ||
| let mut returns_hasher = ArgsHasher::new(); | ||
| ``` | ||
|
|
||
| - The hash of the return value is set in the context: | ||
| ```rust | ||
| context.set_return_hash(returns_hasher); | ||
| ``` | ||
|
|
||
| - The function's return type is changed to `PrivateCircuitPublicInputs`, which is returned by calling `context.finish()` at the end of the function. | ||
|
|
||
| This process allows the return values to be included in the function's computation result while maintaining privacy. | ||
|
|
||
| ### Public functions | ||
|
|
||
| In public functions, the return value is directly used, and the function's return type remains as specified by the developer. | ||
|
|
||
| ## Computing note hash and nullifier | ||
|
|
||
| A function called `compute_note_hash_and_optionally_a_nullifier` is automatically generated and injected into all contracts that use notes. This function tells Aztec how to compute hashes and nullifiers for notes used in the contract. You can optionally write this function yourself if you want notes to be handled a specific way. | ||
|
|
||
| The function is automatically generated based on the note types defined in the contract. Here's how it works: | ||
|
|
||
| - The function takes several parameters: | ||
| ```rust | ||
| fn compute_note_hash_and_optionally_a_nullifier( | ||
|
catmcgee marked this conversation as resolved.
|
||
| contract_address: AztecAddress, | ||
| nonce: Field, | ||
| storage_slot: Field, | ||
| note_type_id: Field, | ||
| compute_nullifier: bool, | ||
| serialized_note: [Field; MAX_NOTE_FIELDS_LENGTH], | ||
| ) -> [Field; 4] | ||
| ``` | ||
|
|
||
| - It creates a `NoteHeader` using the provided args: | ||
| ```rust | ||
| let note_header = NoteHeader::new(contract_address, nonce, storage_slot); | ||
| ``` | ||
|
|
||
| - The function then checks the `note_type_id` against all note types defined in the contract. For each note type, it includes a condition like this: | ||
| ```rust | ||
| if (note_type_id == NoteType::get_note_type_id()) { | ||
| aztec::note::utils::compute_note_hash_and_optionally_a_nullifier( | ||
| NoteType::deserialize_content, | ||
| note_header, | ||
| compute_nullifier, | ||
| serialized_note | ||
| ) | ||
| } | ||
| ``` | ||
|
|
||
| - The function returns an array of 4 Field elements, which represent the note hash and, if computed, the nullifier. | ||
|
|
||
| ## Function signature generation | ||
|
|
||
| Unique function signatures are generated for each contract function. | ||
|
|
||
| The function signature is computed like this: | ||
|
|
||
| ```rust | ||
| fn compute_fn_signature_hash(fn_name: &str, parameters: &[Type]) -> u32 { | ||
|
catmcgee marked this conversation as resolved.
|
||
| let signature = format!( | ||
| "{}({})", | ||
| fn_name, | ||
| parameters.iter().map(signature_of_type).collect::<Vec<_>>().join(",") | ||
| ); | ||
| let mut keccak = Keccak::v256(); | ||
| let mut result = [0u8; 32]; | ||
| keccak.update(signature.as_bytes()); | ||
| keccak.finalize(&mut result); | ||
| // Take the first 4 bytes of the hash and convert them to an integer | ||
| // If you change the following value you have to change NUM_BYTES_PER_NOTE_TYPE_ID in l1_note_payload.ts as well | ||
| let num_bytes_per_note_type_id = 4; | ||
| u32::from_be_bytes(result[0..num_bytes_per_note_type_id].try_into().unwrap()) | ||
| } | ||
| ``` | ||
|
|
||
| - A string representation of the function is created, including the function name and parameter types | ||
| - This signature string is then hashed using Keccak-256 | ||
| - The first 4 bytes of the resulting hash are coverted to a u32 integer | ||
|
|
||
| ### Integration into contract interface | ||
|
|
||
| The computed function signatures are integrated into the contract interface like this: | ||
|
|
||
| - During contract compilation, placeholder values (0) are initially used for function selectors | ||
|
|
||
| - After type checking, the `update_fn_signatures_in_contract_interface()` function is called to replace these placeholders with the actual computed signatures | ||
|
|
||
| - For each function in the contract interface: | ||
| - The function's parameters are extracted | ||
| - The signature hash is computed using `compute_fn_signature_hash` | ||
| - The placeholder in the contract interface is replaced with the computed hash | ||
|
|
||
| This process ensures that each function in the contract has a unique, deterministic signature based on its name and parameter types. They are compatible with Ethereum's function selector mechanism. | ||
|
catmcgee marked this conversation as resolved.
Outdated
|
||
|
|
||
| ## Contract artifacts | ||
|
|
||
| Contract artifacts in Aztec are automatically generated structures that describe the contract's interface. They provide information about the contract's functions, their parameters, and return types. | ||
|
|
||
| ### Contract artifact generation process | ||
|
|
||
| For each function in the contract, an artifact is generated like this" | ||
|
catmcgee marked this conversation as resolved.
Outdated
|
||
|
|
||
| - A struct is created to represent the function's parameters: | ||
|
|
||
| ```rust | ||
| struct {function_name}_parameters { | ||
| // Function parameters are listed here | ||
| } | ||
| ``` | ||
|
|
||
| This struct is only created if the function has parameters. | ||
|
|
||
| - An ABI struct is generated for the function: | ||
|
|
||
| ```rust | ||
| let export_struct_source = format!( | ||
| " | ||
| #[abi(functions)] | ||
| struct {}_abi {{ | ||
| {}{} | ||
| }}", | ||
| func.name(), | ||
| parameters, | ||
| return_type | ||
| ); | ||
| ``` | ||
|
|
||
| - These structs are added to the contract's types. | ||
|
|
||
| ### Content of artifacts | ||
|
|
||
| The artifacts contain: | ||
|
|
||
| - Function name | ||
| - Parameters (if any), including their names and types | ||
| - Return type (if the function has returns) | ||
|
|
||
| For example, for a function `transfer(recipient: Address, amount: Field) -> bool`, the artifact would look like: | ||
|
|
||
| ```rust | ||
| struct transfer_parameters { | ||
| recipient: Address, | ||
| amount: Field, | ||
| } | ||
|
|
||
| #[abi(functions)] | ||
| struct transfer_abi { | ||
| parameters: transfer_parameters, | ||
| return_type: bool, | ||
| } | ||
| ``` | ||
|
|
||
| Contract artifacts are important because: | ||
|
|
||
| - They provide a machine-readable description of the contract | ||
| - They can be used to generate bindings for interacting with the contract (read [here](../../../../guides/smart_contracts/how_to_compile_contract.md#typescript-interfaces) to learn how to create TypeScript bindings) | ||
| - They help decode function return values in the simulator | ||
|
|
||
| ## Further reading | ||
| - [How do macros work](./attributes.md) | ||
|
catmcgee marked this conversation as resolved.
Outdated
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.