[Feat] Automate smart contract docgen and PR branch creation into Linea docs repo (#446)

* did poc for autogenerated *.mdx into docs.linea.build

* changed sparseMerkleProof

* first draft of contracts-docgen

* fix typos

* try different github token

* cleanup

* created create-docs-website-pr-branch

* cleanup for doc website repo scripts

* created first docs-repo pr using create-docs-website-pr-branch.sh

* improve comments

* added bash script segment to change filename to lowercase

* fix *.mdx headers to make more docusarus friendly

* update scripts for updated docs pr

* added comments to updateSidebar.js

* fix scripts after local test

* added installation checks

* Update contracts/docs/scripts/create-docs-website-pr-branch.sh

Co-authored-by: The Dark Jester <thedarkjester@users.noreply.github.com>
Signed-off-by: kyzooghost <73516204+kyzooghost@users.noreply.github.com>

---------

Signed-off-by: kyzooghost <73516204+kyzooghost@users.noreply.github.com>
Co-authored-by: The Dark Jester <thedarkjester@users.noreply.github.com>
This commit is contained in:
kyzooghost
2024-12-20 20:45:36 +11:00
committed by GitHub
parent 6ad7ba225a
commit ea79b2d2be
55 changed files with 405 additions and 139 deletions

View File

@@ -18,6 +18,9 @@ on:
# '!**.md' will not work by itself to exclude *.md files. See https://github.com/orgs/community/discussions/25369
- '**'
- '!**.md'
- '!**.mdx'
- '!**/docs/**'
- '!docs/**'
jobs:
analyze:

View File

@@ -122,6 +122,8 @@ jobs:
filters: |
has-changes-requiring-build:
- '!**/*.md'
- '!**/*.mdx'
- '!**/docs/**'
- '!docs/**'
# Enables us to exclude changes in multiple file types if required
predicate-quantifier: 'every'

View File

@@ -7,8 +7,10 @@ on:
- 'testdata/**'
- 'prover/**'
- '!contracts/**/*.md'
- '!contracts/**/*.mdx'
- '!testdata/**/*.md'
- '!prover/**/*.md'
- '!**/docs/**'
push:
branches:
- main
@@ -17,8 +19,10 @@ on:
- 'testdata/**'
- 'prover/**'
- '!contracts/**/*.md'
- '!contracts/**/*.mdx'
- '!testdata/**/*.md'
- '!prover/**/*.md'
- '!**/docs/**'
env:
GOPROXY: "https://proxy.golang.org"

View File

@@ -79,7 +79,7 @@ library SparseMerkleProof {
/**
* @notice Hash a value using MIMC hash
* @param _input Value to hash
* @return {bytes32} Mimc hash
* @return bytes32 Mimc hash
*/
function mimcHash(bytes calldata _input) external pure returns (bytes32) {
return Mimc.hash(_input);
@@ -106,7 +106,7 @@ library SparseMerkleProof {
/**
* @notice Hash account value
* @param _value Encoded account value bytes (nonce, balance, storageRoot, mimcCodeHash, keccakCodeHash, codeSize)
* @return {bytes32} Account value hash
* @return bytes32 Account value hash
*/
function hashAccountValue(bytes calldata _value) external pure returns (bytes32) {
Account memory account = _parseAccount(_value);
@@ -128,7 +128,7 @@ library SparseMerkleProof {
/**
* @notice Hash storage value
* @param _value Encoded storage value bytes
* @return {bytes32} Storage value hash
* @return bytes32 Storage value hash
*/
function hashStorageValue(bytes32 _value) external pure returns (bytes32) {
(bytes32 msb, bytes32 lsb) = _splitBytes32(_value);

View File

@@ -1,6 +1,4 @@
# Solidity API
## LineaRollup
# `LineaRollup`
### CONTRACT_VERSION

View File

@@ -1,6 +1,4 @@
# Solidity API
## ZkEvmV2
# `ZkEvmV2`
### MODULO_R

View File

@@ -1,6 +1,4 @@
# Solidity API
## IGenericErrors
# `IGenericErrors`
### ZeroAddressNotAllowed

View File

@@ -1,6 +1,4 @@
# Solidity API
## IMessageService
# `IMessageService`
### MessageSent

View File

@@ -1,6 +1,4 @@
# Solidity API
## IPauseManager
# `IPauseManager`
### PauseTypeRole

View File

@@ -1,6 +1,4 @@
# Solidity API
## IPermissionsManager
# `IPermissionsManager`
### RoleAddress

View File

@@ -1,6 +1,4 @@
# Solidity API
## IRateLimiter
# `IRateLimiter`
### RateLimitInitialized

View File

@@ -1,6 +1,4 @@
# Solidity API
## IL1MessageManager
# `IL1MessageManager`
### RollingHashUpdated

View File

@@ -1,6 +1,4 @@
# Solidity API
## IL1MessageManagerV1
# `IL1MessageManagerV1`
### MessageDoesNotExistOrHasAlreadyBeenClaimed

View File

@@ -1,6 +1,4 @@
# Solidity API
## IL1MessageService
# `IL1MessageService`
### ClaimMessageWithProofParams

View File

@@ -1,6 +1,4 @@
# Solidity API
## ILineaRollup
# `ILineaRollup`
### InitializationData

View File

@@ -1,6 +1,4 @@
# Solidity API
## IPlonkVerifier
# `IPlonkVerifier`
### Verify

View File

@@ -1,6 +1,4 @@
# Solidity API
## IZkEvmV2
# `IZkEvmV2`
### StartingRootHashDoesNotMatch

View File

@@ -1,6 +1,4 @@
# Solidity API
## IL2MessageManager
# `IL2MessageManager`
### RollingHashUpdated

View File

@@ -1,6 +1,4 @@
# Solidity API
## IL2MessageManagerV1
# `IL2MessageManagerV1`
### MinimumFeeChanged

View File

@@ -1,6 +1,4 @@
# Solidity API
## IL2MessageServiceV1
# `IL2MessageServiceV1`
### setMinimumFee

View File

@@ -0,0 +1,24 @@
# `CallForwardingProxy`
### target
```solidity
address target
```
The underlying target address that is called.
### constructor
```solidity
constructor(address _target) public
```
### fallback
```solidity
fallback() external payable
```
Defaults to, and forwards all calls to the target address.

View File

@@ -1,6 +1,4 @@
# Solidity API
## L2MessageServicePauseManager
# `L2MessageServicePauseManager`
### PAUSE_L1_L2_ROLE

View File

@@ -1,6 +1,4 @@
# Solidity API
## LineaRollupPauseManager
# `LineaRollupPauseManager`
### PAUSE_L1_L2_ROLE

View File

@@ -1,6 +1,4 @@
# Solidity API
## Mimc
# `Mimc`
### DataMissing

View File

@@ -1,6 +1,4 @@
# Solidity API
## PauseManager
# `PauseManager`
### PAUSE_ALL_ROLE

View File

@@ -1,6 +1,4 @@
# Solidity API
## PermissionsManager
# `PermissionsManager`
### __Permissions_init

View File

@@ -1,6 +1,4 @@
# Solidity API
## SparseMerkleProof
# `SparseMerkleProof`
### Account
@@ -130,7 +128,7 @@ Hash a value using MIMC hash
| Name | Type | Description |
| ---- | ---- | ----------- |
| [0] | bytes32 | {bytes32} Mimc hash |
| [0] | bytes32 | bytes32 Mimc hash |
### getLeaf
@@ -190,7 +188,7 @@ Hash account value
| Name | Type | Description |
| ---- | ---- | ----------- |
| [0] | bytes32 | {bytes32} Account value hash |
| [0] | bytes32 | bytes32 Account value hash |
### hashStorageValue
@@ -210,5 +208,5 @@ Hash storage value
| Name | Type | Description |
| ---- | ---- | ----------- |
| [0] | bytes32 | {bytes32} Storage value hash |
| [0] | bytes32 | bytes32 Storage value hash |

View File

@@ -1,6 +1,4 @@
# Solidity API
## TokenBridgePauseManager
# `TokenBridgePauseManager`
### PAUSE_INITIATE_TOKEN_BRIDGING_ROLE

View File

@@ -1,6 +1,4 @@
# Solidity API
## Utils
# `Utils`
### _efficientKeccak

View File

@@ -1,6 +1,4 @@
# Solidity API
## MessageServiceBase
# `MessageServiceBase`
### messageService

View File

@@ -1,6 +1,4 @@
# Solidity API
## L1MessageManager
# `L1MessageManager`
### rollingHashes

View File

@@ -1,6 +1,4 @@
# Solidity API
## L1MessageService
# `L1MessageService`
### systemMigrationBlock

View File

@@ -1,6 +1,4 @@
# Solidity API
## TransientStorageReentrancyGuardUpgradeable
# `TransientStorageReentrancyGuardUpgradeable`
### ReentrantCall

View File

@@ -1,6 +1,4 @@
# Solidity API
## L1MessageManagerV1
# `L1MessageManagerV1`
### INBOX_STATUS_UNKNOWN

View File

@@ -1,6 +1,4 @@
# Solidity API
## L1MessageServiceV1
# `L1MessageServiceV1`
### nextMessageNumber

View File

@@ -1,6 +1,4 @@
# Solidity API
## L2MessageManager
# `L2MessageManager`
### L1_L2_MESSAGE_SETTER_ROLE

View File

@@ -1,6 +1,4 @@
# Solidity API
## L2MessageService
# `L2MessageService`
### CONTRACT_VERSION

View File

@@ -1,6 +1,4 @@
# Solidity API
## L2MessageManagerV1
# `L2MessageManagerV1`
### INBOX_STATUS_UNKNOWN

View File

@@ -1,6 +1,4 @@
# Solidity API
## L2MessageServiceV1
# `L2MessageServiceV1`
### MINIMUM_FEE_SETTER_ROLE

View File

@@ -1,6 +1,4 @@
# Solidity API
## MessageHashing
# `MessageHashing`
### _hashMessage

View File

@@ -1,6 +1,4 @@
# Solidity API
## RateLimiter
# `RateLimiter`
You can use this control numeric limits over a period using timestamp.

View File

@@ -1,6 +1,4 @@
# Solidity API
## SparseMerkleTreeVerifier
# `SparseMerkleTreeVerifier`
### SafeCastOverflowedUintDowncast

View File

@@ -1,6 +1,4 @@
# Solidity API
## TimeLock
# `TimeLock`
This timelock contract will be the owner of all upgrades that gives users confidence and an ability to exit should they want to before an upgrade takes place

View File

@@ -1,6 +1,4 @@
# Solidity API
## TransientStorageHelpers
# `TransientStorageHelpers`
### tstoreUint256

View File

@@ -1,6 +1,4 @@
# Solidity API
## BridgedToken
# `BridgedToken`
ERC20 token created when a native token is bridged to a target chain.

View File

@@ -1,6 +1,4 @@
# Solidity API
## CustomBridgedToken
# `CustomBridgedToken`
Custom ERC20 token manually deployed for the Linea TokenBridge.

View File

@@ -1,6 +1,4 @@
# Solidity API
## TokenBridge
# `TokenBridge`
Contract to manage cross-chain ERC20 bridging.

View File

@@ -1,6 +1,4 @@
# Solidity API
## ITokenBridge
# `ITokenBridge`
### InitializationData

View File

@@ -1,4 +0,0 @@
# Solidity API
## StorageFiller39

View File

@@ -0,0 +1,2 @@
# `StorageFiller39`

View File

@@ -0,0 +1,10 @@
# `{{name}}`
{{{natspec.notice}}}
{{#each items}}
{{#hsection}}
{{>item}}
{{/hsection}}
{{/each}}

View File

@@ -0,0 +1,8 @@
{{! This template is from the solidity-docgen repository - https://github.com/OpenZeppelin/solidity-docgen/blob/f51c722278bef58847214a4d6b0ffd60dcdfa719/src/themes/markdown/page.hbs }}
{{! We delete the top line `# Solidity API` to make the generated documentation more Docusarus-friendly }}
{{#each items}}
{{#hsection}}
{{>item}}
{{/hsection}}
{{/each}}

View File

@@ -0,0 +1,113 @@
#!/bin/bash
### PURPOSE
# This script will automate pushing a PR branch into the docs.linea.build repo
# The new PR branch will contain autogenerated smart contract documentation for the Linea monorepo
# You must manually create the Github PR yourself (from the new branch)
### ASSUMPTIONS
# - Requires permissions to create a branch on docs.linea.build repo
# - Must execute this script from within linea-monorepo (can be anywhere)
# - Hardhat must be installed in the local project, Foundry must be installed globally
### CONSTANTS
DOCS_WEBSITE_REPO_NAME=doc.linea
DOCS_WEBSITE_REPO_GIT_LINK=https://github.com/Consensys/$DOCS_WEBSITE_REPO_NAME.git
DOCS_REPO_PR_BRANCH_NAME=update-linea-smart-contract-docs
DOCS_REPO_PR_COMMIT_MESSAGE="Update Linea smart contract docs"
UPDATE_SIDEBAR_SCRIPT_NAME=updateSidebar.js
UPDATE_SIDEBAR_SCRIPT_PATH=contracts/docs/scripts/$UPDATE_SIDEBAR_SCRIPT_NAME
MONOREPO_SMART_CONTRACT_DOCS_DIRECTORY=contracts/docs/api
DOCS_REPO_SMART_CONTRACT_DOC_DIRECTORY=docs/api/linea-smart-contracts
MAX_FOLDER_DEPTH=3
### SCRIPT START
# Ensure that `contracts` is the starting working directory
MONOREPO_ROOT_PATH=$(git rev-parse --show-toplevel)
cd $MONOREPO_ROOT_PATH
cd contracts
# Check required installations
if ! command -v forge &> /dev/null; then
echo "Please install Foundry - https://book.getfoundry.sh/getting-started/installation"
exit 1
fi
if ! command -v pnpm &> /dev/null; then
echo "Please install pnpm - https://pnpm.io/installation"
exit 1
fi
if [ -z "$(pnpm -F contracts list hardhat)" ]; then
echo "Please install Hardhat - \`pnpm i\`"
exit 1
fi
# Docgen
npx hardhat docgen
# Git clone docs website repo and create a new branch
cd $MONOREPO_ROOT_PATH
cd ..
git clone $DOCS_WEBSITE_REPO_GIT_LINK --depth 1
cd $DOCS_WEBSITE_REPO_NAME
DOCS_WEBSITE_REPO_PATH=$(pwd)
git checkout -b $DOCS_REPO_PR_BRANCH_NAME
# Copy autogenerated smart contract docs + UPDATE_SIDEBAR_SCRIPT to docs website repo
cd $MONOREPO_ROOT_PATH
rm -rf "$DOCS_WEBSITE_REPO_PATH/$DOCS_REPO_SMART_CONTRACT_DOC_DIRECTORY"
cp -r "$MONOREPO_ROOT_PATH/$MONOREPO_SMART_CONTRACT_DOCS_DIRECTORY" "$DOCS_WEBSITE_REPO_PATH/$DOCS_REPO_SMART_CONTRACT_DOC_DIRECTORY"
cp "$MONOREPO_ROOT_PATH/$UPDATE_SIDEBAR_SCRIPT_PATH" "$DOCS_WEBSITE_REPO_PATH/$UPDATE_SIDEBAR_SCRIPT_NAME"
# Ensure directories is entirely lowercase
# To pass Github Action enforcing no uppercase for file name
cd $DOCS_WEBSITE_REPO_PATH
# Purpose: Change folder names to lowercase
# Parameters:
# $1 - max folder depth
function directory_to_lowercase_at_depth() {
local DEPTH=$1
for FOLDER in `find -d "$DOCS_REPO_SMART_CONTRACT_DOC_DIRECTORY" -type d -maxdepth $DEPTH`
do
NEW_FOLDER=$(echo $FOLDER | tr 'A-Z' 'a-z')
if [[ "$FOLDER" != "$NEW_FOLDER" ]]; then
mv $FOLDER $NEW_FOLDER
fi
done;
}
# Clumsy way of forcing `find` to do breadth-first search (BFS)
# Not a true BFS currently - as we 'descend' we are redundantly repeating BFS of previously explored folder depths
# Can tolerate inefficient implementation for readability
#
# The issue with default depth-first search (DFS) algorithm of `find` is that we mutate the folder list as iterate through it
# So the `mv` command spits errors because it is acting on stale folder names
# Seems that `find` first obtains the complete folder list, then iterates through the list to apply our for-loop
# What we want is for `find` to apply our for-loop body as it adds each item to the folder list
for ((i = 1; i <= MAX_FOLDER_DEPTH; i++)); do
directory_to_lowercase_at_depth $i
done
# Ensure filenames is entirely lowercase
# To pass Github Action enforcing no uppercase for file name
for FILENAME in `find "$DOCS_REPO_SMART_CONTRACT_DOC_DIRECTORY" -type f \( -iname \*.md -o -iname \*.mdx \)`
do
NEW_FILENAME=$(echo $FILENAME | tr 'A-Z' 'a-z')
mv $FILENAME $NEW_FILENAME
done;
# Execute UPDATE_SIDEBAR_SCRIPT then delete the script (so it is not in the commit)
cd $DOCS_WEBSITE_REPO_PATH
node $UPDATE_SIDEBAR_SCRIPT_NAME
rm $UPDATE_SIDEBAR_SCRIPT_NAME
# Git commit and push
git add .
git commit -m "$DOCS_REPO_PR_COMMIT_MESSAGE"
git push --set-upstream origin $DOCS_REPO_PR_BRANCH_NAME

View File

@@ -0,0 +1,187 @@
/* eslint-disable */
// This script is meant to be executed in the root directory of https://github.com/Consensys/doc.linea, which has different linting rules
// The purpose of this script is to modify the sidebars.js file to correctly include the autogenerated smart contract documentation
/**
* PURPOSE
*
* Modifies sidebars.js in the root directory of https://github.com/Consensys/doc.linea to correctly include smart contract documentation pages.
* The sidebars.js file configures the sidebar for the documentation website @ https://docs.linea.build/
*/
const fs = require("fs");
const path = require("path");
const { execSync } = require("child_process");
/**
* TYPES
*/
/**
* Represents a sidebar section - see https://docusaurus.io/docs/sidebar/items#sidebar-item-category
* @property {'category'} type - Type of sidebar item
* @property {number} label - Sidebar label text
* @property {null} link - Sidebar item link, set to null so only collapsing/expanding the sidebar occurs on click
* @property {boolean} collapsible - If true, sidebar item can be collapsed. If false, sidebar item is permanently expanded
* @property {string | FolderSidebar} items - Items contained in this sidebar section
*/
class FolderSidebar {
type = "category";
label = "";
link = null;
collapsible = true;
items = [];
constructor(label = "", collapsible = true) {
this.label = label;
this.collapsible = collapsible;
}
}
/**
* CONSTANTS
*/
const SIDEBAR_FILE_PATH = "sidebars.js";
const SMART_CONTRACT_SIDEBAR_LABEL = "Linea smart contracts";
// Import the sidebar JS object from sidebars.js
const sidebarObject = require(path.join(__dirname, SIDEBAR_FILE_PATH));
/**
* MAIN FUNCTION
*/
main();
function main() {
removeExistingSmartContractSidebar(sidebarObject);
const smartContractSidebarNode = getSmartContractSidebar();
sidebarObject?.apiSidebar?.push(smartContractSidebarNode);
createNewSidebarFile(sidebarObject);
}
/**
* HELPER FUNCTIONS
*/
/**
* Remove existing smart contract section from sidebar object
* @param {FolderSidebar} sidebarObject
*/
function removeExistingSmartContractSidebar(sidebarObject) {
if (sidebarObject?.apiSidebar) {
sidebarObject.apiSidebar = sidebarObject?.apiSidebar.filter(sidebarSection => sidebarSection?.label !== SMART_CONTRACT_SIDEBAR_LABEL);
}
}
/**
* Creates FolderSidebar representing folder structure of smart contract documentation pages
* @returns {FolderSidebar}
*/
function getSmartContractSidebar() {
// Create and populate smart contract sidebar
const smartContractsPath = path.join(
__dirname,
"docs",
"api",
"linea-smart-contracts",
);
let smartContractSidebar = new FolderSidebar(
SMART_CONTRACT_SIDEBAR_LABEL,
// Prefer having the smart contract sidebar section permanently expanded to fill out the main sidebar
false,
);
populateFolderSidebar(
smartContractSidebar,
smartContractsPath,
".mdx",
);
return smartContractSidebar;
}
/**
* Recursive function to populate FolderSidebar for a given folder
* Performs depth-first search (DFS) of the folder tree
* @param {FolderSidebar} folderSidebar - Mutated throughout the function body. Mutated object is then returned by the function.
* @param {string} subdirectoryPath - folder path
* @param {string} fileExtension
* @return {FolderSidebar}
*/
function populateFolderSidebar(folderSidebar, subdirectoryPath, fileExtension) {
const folderFileList = fs.readdirSync(subdirectoryPath);
// Ensure *.mdx files at the top of the sidebar section
for (const fileNode of folderFileList) {
const filePath = path.join(subdirectoryPath, fileNode);
const fileMetadata = fs.statSync(filePath);
// Base case => *.mdx file => Add relative path to sidebar
if (fileMetadata.isFile() && fileNode.endsWith(fileExtension)) {
const relativePath = path.relative(
path.join(__dirname, "docs"),
filePath.split(fileExtension)[0],
);
folderSidebar?.items.push(relativePath);
}
}
// Ensure directories are below *.mdx files in the sidebar section
for (const fileNode of folderFileList) {
const filePath = path.join(subdirectoryPath, fileNode);
const fileMetadata = fs.statSync(filePath);
// Directory => Create new child FolderSidebar, then make recursive call to populate new child FolderSidebar
if (fileMetadata.isDirectory()) {
let newFolderNode = new FolderSidebar(fileNode);
populateFolderSidebar(newFolderNode, filePath, fileExtension);
// Add populated child FolderSidebar to current sidebar section
folderSidebar?.items.push(newFolderNode);
}
}
// Not a *.mdx file or directory => Do nothing
return folderSidebar;
}
/**
* Overwrites existing sidebars.js file with modified sidebar object
* @param {FolderSidebar} sidebarObject - Entire sidebar object to save
*/
function createNewSidebarFile(sidebarObject) {
// Create new sidebars.js file content
const sidebarFileLine1 =
"/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */";
const sidebarFileLine2 = "const sidebars =";
const sidebarFileLineFinal = "module.exports = sidebars;";
const newSidebarFileContent = `${sidebarFileLine1}\n${sidebarFileLine2}\n${JSON.stringify(sidebarObject, null, 2)}\n\n${sidebarFileLineFinal}`;
// Overwrite existing sidebars.js
const newSidebarFilePath = path.join(__dirname, SIDEBAR_FILE_PATH);
fs.writeFileSync(newSidebarFilePath, newSidebarFileContent);
// Do linting
lintJSFile(newSidebarFilePath);
}
/**
* Execute linting of Javascript file
* @param {string} filePath
*/
function lintJSFile(filePath) {
try {
const installCmd = `npm install --save-dev --no-save eslint`;
execSync(installCmd, { stdio: "inherit" });
const lintCmd = `npx eslint --fix --no-ignore ${filePath}`;
// Execute command synchronously and route output directly to the current stdout
execSync(lintCmd, { stdio: "inherit" });
} catch (error) {
console.error(`Error:`, error.message);
console.error(`Exiting...`);
process.exit(1);
}
}

View File

@@ -157,6 +157,9 @@ const config: HardhatUserConfig = {
exclude: ["token", "test-contracts", "proxies", "tools", "interfaces/tools", "tokenBridge/mocks", "verifiers"],
pages: "files",
outputDir: "docs/api/",
// For compatibility with docs.linea.build
pageExtension: ".mdx",
templates: "docs/docgen-templates",
},
};