diff --git a/docs/.gitignore b/docs/.gitignore index 462844ee8bd..858ab1e91b4 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -21,3 +21,8 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +docs/developers/contracts/references/aztec-nr +src/preprocess/developers + +/src/preprocess/AztecnrReferenceAutogenStructure.json diff --git a/docs/docs/developers/contracts/references/storage/private_state.md b/docs/docs/developers/contracts/references/storage/private_state.md index 4b8d6addec1..61d3046a67c 100644 --- a/docs/docs/developers/contracts/references/storage/private_state.md +++ b/docs/docs/developers/contracts/references/storage/private_state.md @@ -202,7 +202,7 @@ Allows us to modify the storage by inserting a note into the set. A hash of the note will be generated, and inserted into the note hash tree, allowing us to later use in contract interactions. Recall that the content of the note should be shared with the owner to allow them to use it, as mentioned this can be done via an [encrypted log](../../writing_contracts/events/emit_event.md#encrypted-events), or offchain via web2, or completely offline. -#include_code insert /noir-projects/aztec-nr/easy-private-state/src/easy_private_state.nr rust +#include_code insert /noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr rust ### `insert_from_public` @@ -220,7 +220,7 @@ Nullifiers are emitted when reading values to make sure that they are up to date An example of how to use this operation is visible in the `easy_private_state`: -#include_code remove /noir-projects/aztec-nr/easy-private-state/src/easy_private_state.nr rust +#include_code remove /noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr rust ### `get_notes` @@ -232,7 +232,7 @@ Because of this limit, we should always consider using the second argument `Note An example of such options is using the [filter_notes_min_sum](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/value-note/src/filter.nr) to get "enough" notes to cover a given value. Essentially, this function will return just enough notes to cover the amount specified such that we don't need to read all our notes. For users with a lot of notes, this becomes increasingly important. -#include_code get_notes /noir-projects/aztec-nr/easy-private-state/src/easy_private_state.nr rust +#include_code get_notes /noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr rust ### `view_notes` diff --git a/docs/docs/developers/contracts/writing_contracts/functions/context.md b/docs/docs/developers/contracts/writing_contracts/functions/context.md index e80d34e4ec2..99b692ffe25 100644 --- a/docs/docs/developers/contracts/writing_contracts/functions/context.md +++ b/docs/docs/developers/contracts/writing_contracts/functions/context.md @@ -81,7 +81,7 @@ In the public context this header is set by sequencer (sequencer executes public Just like with the `is_contract_deployment` flag mentioned earlier. This data will only be set to true when the current transaction is one in which a contract is being deployed. -#include_code contract-deployment-data /noir-projects/noir-protocol-circuits/src/crates/types/src/contrakt/deployment_data.nr rust +#include_code contract-deployment-data /noir-projects/noir-protocol-circuits/src/crates/types/src/contrakt/contract_deployment_data.nr rust ### Private Global Variables diff --git a/docs/package.json b/docs/package.json index d226d8bae82..67a53d63e89 100644 --- a/docs/package.json +++ b/docs/package.json @@ -5,15 +5,15 @@ "scripts": { "docusaurus": "docusaurus", "start": "yarn preprocess && yarn typedoc && docusaurus start --host 0.0.0.0", - "start:dev": "concurrently \"yarn preprocess:dev\" \"yarn typedoc:dev\" \"sleep 2 && docusaurus start --host 0.0.0.0\"", - "start:dev:local": "concurrently \"yarn preprocess:dev\" \"yarn typedoc:dev\" \"sleep 2 && docusaurus start\"", + "start:dev": "yarn start", + "start:dev:local": "yarn preprocess && yarn typedoc && docusaurus start", "build": "./scripts/build.sh", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", - "clear": "rm -rf 'processed-docs' 'processed-docs-cache' docs/apis && docusaurus clear", + "clear": "rm -rf 'processed-docs' 'processed-docs-cache' docs/apis && docusaurus clear && rm 'src/preprocess/AztecnrReferenceAutogenStructure.json' && rm -rf 'docs/developers/references/aztec-nr'", "serve": "docusaurus serve", - "preprocess": "yarn node -r dotenv/config ./src/preprocess/index.js", - "preprocess:dev": "nodemon --config nodemon.json ./src/preprocess/index.js", + "preprocess": "yarn node -r dotenv/config ./src/preprocess/index.js && node src/preprocess/generate_aztecnr_reference.js", + "preprocess:dev": "nodemon --config nodemon.json ./src/preprocess/index.js && nodemon --config nodemon.json src/preprocess/generate_aztecnr_reference.js ", "typedoc": "rm -rf docs/apis && docusaurus generate-typedoc && cp -a docs/apis processed-docs/", "typedoc:dev": "nodemon -w ../yarn-project -e '*.js,*.ts,*.nr,*.md' --exec \"rm -rf docs/apis && yarn docusaurus generate-typedoc && cp -a docs/apis processed-docs/\"", "write-translations": "docusaurus write-translations", diff --git a/docs/scripts/build.sh b/docs/scripts/build.sh index eb63a05887d..0755b2f3515 100755 --- a/docs/scripts/build.sh +++ b/docs/scripts/build.sh @@ -45,4 +45,7 @@ fi # Now build the docsite echo Building docsite... +echo "Generating Aztec.nr reference docs..." +node ./src/preprocess/generate_aztecnr_reference.js +echo "Generated Aztec.nr reference docs" yarn preprocess && yarn typedoc && yarn docusaurus build diff --git a/docs/sidebars.js b/docs/sidebars.js index 50335fcddb1..9fe832ff0d3 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -11,6 +11,47 @@ // @ts-check +const fs = require("fs"); +const path = require("path"); +// Load the structured documentation paths +const docsStructurePath = path.join( + __dirname, + "/src/preprocess/AztecnrReferenceAutogenStructure.json" +); +const docsStructure = JSON.parse(fs.readFileSync(docsStructurePath, "utf8")); + +// Function to recursively build sidebar items from the structured documentation +function buildSidebarItemsFromStructure(structure, basePath = "") { + const items = []; + for (const key in structure) { + if (key === "_docs") { + // Base case: add the docs + structure[key].forEach((doc) => { + items.push(`${basePath}/${doc}`); + }); + } else { + // Recursive case: process a subdirectory + const subItems = buildSidebarItemsFromStructure( + structure[key], + `${basePath}/${key}` + ); + items.push({ + type: "category", + label: key.charAt(0).toUpperCase() + key.slice(1), // Capitalize the label + items: subItems, + }); + } + } + return items; +} + +// Build sidebar for AztecNR documentation +const aztecNRSidebar = buildSidebarItemsFromStructure( + docsStructure.AztecNR, + "developers/contracts/references/aztec-nr" +); + +console.log(aztecNRSidebar); /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ const sidebars = { docsSidebar: [ @@ -22,7 +63,7 @@ const sidebars = { // ABOUT AZTEC { - type: "html", + type: "html", className: "sidebar-title", value: "LEARN", defaultStyle: true, @@ -278,9 +319,7 @@ const sidebars = { id: "apis/pxe/interfaces/PXE", }, ], - }, - ], }, { @@ -331,7 +370,6 @@ const sidebars = { type: "category", items: [ "developers/contracts/writing_contracts/accounts/write_accounts_contract", - ], }, { @@ -383,7 +421,8 @@ const sidebars = { ], }, { - label: "Access public data from private state (Slow Updates Tree)", + label: + "Access public data from private state (Slow Updates Tree)", type: "category", link: { type: "doc", @@ -393,7 +432,6 @@ const sidebars = { "developers/contracts/writing_contracts/historical_data/slow_updates_tree/implement_slow_updates", ], }, - ], }, { @@ -402,16 +440,16 @@ const sidebars = { items: [ "developers/contracts/compiling_contracts/how_to_compile_contract", "developers/contracts/compiling_contracts/artifacts", - ], + ], }, { label: "Deploying Contracts", type: "category", items: [ "developers/contracts/deploying_contracts/how_to_deploy_contract", - ], + ], }, - "developers/contracts/testing_contracts/main", + "developers/contracts/testing_contracts/main", { label: "References", type: "category", @@ -426,7 +464,7 @@ const sidebars = { }, items: [ "developers/contracts/references/storage/private_state", - "developers/contracts/references/storage/public_state" + "developers/contracts/references/storage/public_state", ], }, { @@ -439,6 +477,11 @@ const sidebars = { "developers/contracts/references/portals/registry", ], }, + { + label: "Aztec.nr Reference", + type: "category", + items: aztecNRSidebar, + }, "developers/contracts/references/history_lib_reference", "developers/contracts/references/slow_updates_tree", ], @@ -502,7 +545,7 @@ const sidebars = { "developers/aztecjs/guides/call_view_function", ], }, - { + { label: "References", type: "category", items: [ @@ -516,9 +559,9 @@ const sidebars = { type: "category", items: [{ dirName: "apis/accounts", type: "autogenerated" }], }, - ], - }, - ], + ], + }, + ], }, { label: "Debugging", @@ -544,9 +587,7 @@ const sidebars = { type: "doc", id: "developers/wallets/main", }, - items: [ - "developers/wallets/architecture", - ], + items: ["developers/wallets/architecture"], }, /* { diff --git a/docs/src/preprocess/generate_aztecnr_reference.js b/docs/src/preprocess/generate_aztecnr_reference.js new file mode 100644 index 00000000000..0d06932453c --- /dev/null +++ b/docs/src/preprocess/generate_aztecnr_reference.js @@ -0,0 +1,300 @@ +const fs = require('fs'); +const path = require('path'); + +function listNrFiles(dir, fileList = []) { + const files = fs.readdirSync(dir); + files.forEach(file => { + const filePath = path.join(dir, file); + const stat = fs.statSync(filePath); + if (stat.isDirectory()) { + listNrFiles(filePath, fileList); + } else if (filePath.endsWith('.nr') && !file.endsWith('lib.nr')) { + fileList.push(filePath); + } + }); + return fileList; +} + +function escapeHtml(unsafeText) { + if (!unsafeText) { + // Return an empty string or some default value if unsafeText is undefined or null + return ''; + } + return unsafeText.replace(//g, ">"); +} + + +function parseParameters(paramString) { + if (!paramString.trim()) { + return []; + } + + return paramString.split(',').map(param => { + param = param.trim().replace(/[\[:;,.]$/g, '').replace(/^[\[:;,.]/g, ''); // Clean up start and end + let [paramName, type] = param.split(':').map(p => p.trim()); + return { name: paramName, type: escapeHtml(type) }; + }); +} + + +function parseStruct(content) { + const structRegex = /struct (\w+)\s*{([\s\S]*?)}/g; + let match; + const structs = []; + + while ((match = structRegex.exec(content)) !== null) { + const structName = match[1]; + const fields = match[2].trim().split('\n').map(fieldLine => { + fieldLine = fieldLine.trim().replace(/,$/, ''); + // Skip lines that are comments or do not contain a colon (indicating they are not field definitions) + if (!fieldLine.startsWith('//') && fieldLine.includes(':')) { + let [name, type] = fieldLine.split(/:\s*/); + return { name, type }; + } + }).filter(field => field !== undefined); // Filter out undefined entries resulting from comments or invalid lines + + let descriptionLines = []; + let lineIndex = content.lastIndexOf('\n', match.index - 1); + while (lineIndex >= 0) { + let endOfPreviousLine = content.lastIndexOf('\n', lineIndex - 1); + let line = content.substring(endOfPreviousLine + 1, lineIndex).trim(); + + if (line.startsWith('//') && !line.includes('docs:start:') && !line.includes('docs:end:')) { + descriptionLines.unshift(line.replace('//', '').trim()); + } else if (!line.startsWith('//')) { + break; + } + + lineIndex = endOfPreviousLine; + } + + let description = descriptionLines.join(' '); + structs.push({ structName, fields, description }); + } + + return structs; +} + +function parseFunctions(content) { + const functions = []; + const implRegex = /impl\s+(\w+)\s*{/g; + let implMatch; + + while ((implMatch = implRegex.exec(content)) !== null) { + const structName = implMatch[1]; + let braceDepth = 1; + let currentPos = implMatch.index + implMatch[0].length; + + while (braceDepth > 0 && currentPos < content.length) { + if (content[currentPos] === '{') { + braceDepth++; + } else if (content[currentPos] === '}') { + braceDepth--; + } + currentPos++; + } + + const implBlockContent = content.substring(implMatch.index, currentPos); + const methodRegex = /(?:pub )?fn (\w+)\((.*?)\)(?: -> (.*?))? {/g; + let methodMatch; + + while ((methodMatch = methodRegex.exec(implBlockContent)) !== null) { + const name = methodMatch[1]; + const params = parseParameters(methodMatch[2]); + const returnType = (methodMatch[3] || '').replace(/[\[:;,.]$/g, '').replace(/^[\[:;,.]/g, ''); + + let description = ''; + let commentIndex = methodMatch.index; + while (commentIndex >= 0) { + const commentMatch = implBlockContent.substring(0, commentIndex).match(/\/\/\s*(.*)\n\s*$/); + if (commentMatch && !commentMatch[1].includes('docs:start:') && !commentMatch[1].includes('docs:end:')) { + description = commentMatch[1] + (description ? ' ' + description : ''); + commentIndex = commentMatch.index - 1; + } else { + break; + } + } + + functions.push({ structName, name, params, returnType, description, isMethod: true }); + } + } + + const standaloneFunctionRegex = /(?:pub\s+)?fn\s+(\w+)(?:<.*?>)?\s*\((.*?)\)\s*(?:->\s*(.*?))?\s*{/g; + let standaloneFunctionMatch; + while ((standaloneFunctionMatch = standaloneFunctionRegex.exec(content)) !== null) { + const name = standaloneFunctionMatch[1]; + + if (!functions.some(f => f.name === name && f.isMethod)) { + const params = parseParameters(standaloneFunctionMatch[2]); + const returnType = (standaloneFunctionMatch[3] || '').replace(/[\[:;,.]$/g, '').replace(/^[\[:;,.]/g, ''); + + let description = ''; + const descriptionMatch = content.substring(0, standaloneFunctionMatch.index).match(/\/\/\s*(.*)\n\s*$/); + if (descriptionMatch) { + const precedingText = content.substring(0, descriptionMatch.index); + if (!precedingText.includes('docs:start:') && !precedingText.includes('docs:end:')) { + description = descriptionMatch[1]; + } + } + + functions.push({ name, params, returnType, description, isMethod: false }); + } + } + + return functions; +} + +function generateMarkdown(structs, functions) { + let markdown = ''; + + structs.forEach(structInfo => { + if (structInfo) { + markdown += `# ${escapeHtml(structInfo.structName)}\n\n`; + + if (structInfo.description) { + markdown += `${escapeHtml(structInfo.description)}\n\n`; + } + + if (structInfo.fields.length > 0) { + markdown += `## Fields\n`; + markdown += `| Field | Type |\n| --- | --- |\n`; + structInfo.fields.forEach(field => { + const cleanType = escapeHtml(field.type.replace(/[\[:;,]$/g, '').replace(/^[\[:;,]/g, '')); + const fieldName = escapeHtml(field.name.replace(/[:;]/g, '')); + markdown += `| ${fieldName} | ${cleanType} |\n`; + }); + markdown += '\n'; + } + + // Filter methods for this struct + const methods = functions.filter(f => f.isMethod && f.structName === escapeHtml(structInfo.structName)); + if (methods.length > 0) { + markdown += `## Methods\n\n`; + methods.forEach(func => { + markdown += `### ${escapeHtml(func.name)}\n\n`; + + + // Description taken from a comment above the function decalaration + // If the comment is docs:, looks at the comment above + if (func.description) { + markdown += `${escapeHtml(func.description)}\n\n`; + } + + // Codeblock for example usage + const usageParams = func.params.map(param => param.name).join(', '); + markdown += "```rust\n" + `${func.structName}::${func.name}(${usageParams});` + "\n```\n\n"; + + // Parameters + if (func.params.length > 0) { + markdown += `#### Parameters\n`; + markdown += `| Name | Type |\n| --- | --- |\n`; + func.params.forEach(({ name, type }) => { + markdown += `| ${escapeHtml(name)} | ${escapeHtml(type)} |\n`; + }); + markdown += '\n'; + } else { + markdown += 'Takes no parameters.\n\n'; + } + + // Returns + if (func.returnType) { + markdown += `#### Returns\n`; + markdown += `| Type |\n| --- |\n`; + markdown += `| ${escapeHtml(func.returnType)} |\n\n`; + } + }); + } + } + }); + + // Generate markdown for standalone functions + const standaloneFunctions = functions.filter(f => !f.isMethod); + if (standaloneFunctions.length > 0) { + markdown += `## Standalone Functions\n\n`; + standaloneFunctions.forEach(func => { + markdown += `### ${escapeHtml(func.name)}\n\n`; + + // Insert usage code block + const usageParams = func.params.map(param => param.name).join(', '); + markdown += "```rust\n" + `${func.name}(${usageParams});` + "\n```\n\n"; + + if (func.description) { + markdown += `${escapeHtml(func.description)}\n\n`; + } + + if (func.params.length > 0) { + markdown += `#### Parameters\n`; + markdown += `| Name | Type |\n| --- | --- |\n`; + func.params.forEach(({ name, type }) => { + markdown += `| ${escapeHtml(name)} | ${escapeHtml(type)} |\n`; + }); + markdown += '\n'; + } else { + markdown += 'Takes no parameters.\n\n'; + } + + if (func.returnType) { + markdown += `#### Returns\n`; + markdown += `| Type |\n| --- |\n`; + markdown += `| ${escapeHtml(func.returnType)} |\n\n`; + } + }); + } + + return markdown; +} + + +function processFiles(baseDir, outputBaseDir) { + const nrFiles = listNrFiles(baseDir); + let docStructure = {}; // To hold structured documentation paths + + nrFiles.forEach(filePath => { + + const content = fs.readFileSync(filePath, 'utf8'); + const structs = parseStruct(content); + const functions = parseFunctions(content); + + if (structs.length === 0 && functions.length === 0) { + return; + } + + const markdown = generateMarkdown(structs, functions); + + const relativePath = path.relative(baseDir, filePath); + const adjustedPath = relativePath.replace('/src', '').replace(/\.nr$/, '.md'); + const outputFilePath = path.join(outputBaseDir, adjustedPath); + + fs.mkdirSync(path.dirname(outputFilePath), { recursive: true }); + fs.writeFileSync(outputFilePath, markdown); + + // Adjusted to populate docStructure for JSON + const docPathForJson = adjustedPath.replace(/\\/g, '/').replace('.md', ''); + const parts = docPathForJson.split('/'); + let current = docStructure; + + for (let i = 0; i < parts.length - 1; i++) { + current[parts[i]] = current[parts[i]] || {}; + current = current[parts[i]]; + } + + current._docs = current._docs || []; + current._docs.push(parts[parts.length - 1]); + }); + + // Write structured documentation paths to JSON + const outputPath = path.join(__dirname, 'AztecnrReferenceAutogenStructure.json'); + fs.writeFileSync(outputPath, JSON.stringify({ AztecNR: docStructure }, null, 2)); +} + + +let baseDir = path.resolve(__dirname, '../../../noir-projects/aztec-nr'); +let outputBaseDir = path.resolve(__dirname, '../../docs/developers/contracts/references/aztec-nr'); + +// if (process.env.CI === 'true') { +// baseDir = path.resolve(__dirname, '../noir-projects/aztec-nr'); +// outputBaseDir = path.resolve(__dirname, '../../docs/developers/contracts/references/aztec-nr'); +// } + +processFiles(baseDir, outputBaseDir); + diff --git a/noir-projects/aztec-nr/aztec/src/avm/context.nr b/noir-projects/aztec-nr/aztec/src/avm/context.nr index e247f60db89..076ff17f51e 100644 --- a/noir-projects/aztec-nr/aztec/src/avm/context.nr +++ b/noir-projects/aztec-nr/aztec/src/avm/context.nr @@ -2,9 +2,10 @@ use dep::protocol_types::address::{AztecAddress, EthAddress}; // Getters that will be converted by the transpiler into their // own opcodes +// No new function as this struct is entirely static getters + struct AvmContext {} -// No new function as this struct is entirely static getters impl AvmContext { #[oracle(address)] pub fn address() -> AztecAddress {} diff --git a/noir-projects/aztec-nr/aztec/src/context/globals/private_global_variables.nr b/noir-projects/aztec-nr/aztec/src/context/globals/private_global_variables.nr index 012db96d33c..038273862fb 100644 --- a/noir-projects/aztec-nr/aztec/src/context/globals/private_global_variables.nr +++ b/noir-projects/aztec-nr/aztec/src/context/globals/private_global_variables.nr @@ -9,6 +9,7 @@ struct PrivateGlobalVariables { } // docs:end:private-global-variables +// Note: public global vars are equal to the private ones impl Serialize for PrivateGlobalVariables { fn serialize(self) -> [Field; PRIVATE_GLOBAL_VARIABLES_LENGTH] { [self.chain_id, self.version] diff --git a/noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr b/noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr index d6b6fbf650f..06664d9ccb7 100644 --- a/noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr +++ b/noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr @@ -15,6 +15,7 @@ struct EasyPrivateUint { storage_slot: Field, } +// Holds a note that can act similarly to an int. impl EasyPrivateUint { pub fn new( context: Context, diff --git a/noir-projects/aztec-nr/safe-math/src/safe_u120.nr b/noir-projects/aztec-nr/safe-math/src/safe_u120.nr index 74acd0db57d..fabd9d53a60 100644 --- a/noir-projects/aztec-nr/safe-math/src/safe_u120.nr +++ b/noir-projects/aztec-nr/safe-math/src/safe_u120.nr @@ -30,6 +30,7 @@ impl Deserialize for SafeU120 { } } +// Holds an integer in public storage impl SafeU120 { pub fn min() -> Self { Self {