Commit 7105bad6 authored by smartcontracts's avatar smartcontracts Committed by GitHub

feat(ci): check contract semver matches natspec (#11992)

Adds a new check to CI that verifies that the semver string
defined in the contract matches the string defined in natspec.
parent 08a43698
...@@ -133,6 +133,23 @@ commands: ...@@ -133,6 +133,23 @@ commands:
branch_pattern: develop branch_pattern: develop
mentions: "<< parameters.mentions >>" mentions: "<< parameters.mentions >>"
run-contracts-check:
parameters:
command:
description: Just command that runs the check
type: string
steps:
- run:
name: <<parameters.command>>
command: |
git reset --hard
just <<parameters.command>>
git diff --quiet --exit-code
working_directory: packages/contracts-bedrock
when: always
environment:
FOUNDRY_PROFILE: ci
jobs: jobs:
cannon-go-lint-and-test: cannon-go-lint-and-test:
docker: docker:
...@@ -658,102 +675,30 @@ jobs: ...@@ -658,102 +675,30 @@ jobs:
- setup_remote_docker: - setup_remote_docker:
docker_layer_caching: true docker_layer_caching: true
- run: - run:
name: forge version name: print forge version
command: forge --version command: forge --version
- run: - run-contracts-check:
# Semver lock must come second because one of the later steps may modify the cache & force a contracts rebuild. command: semver-lock
name: semver lock - run-contracts-check:
command: | command: semver-diff-check-no-build
just semver-lock - run-contracts-check:
git diff --exit-code semver-lock.json || echo "export SEMVER_LOCK_STATUS=1" >> "$BASH_ENV" command: semver-natspec-check-no-build
working_directory: packages/contracts-bedrock - run-contracts-check:
- run: command: validate-deploy-configs
name: check deploy configs - run-contracts-check:
command: just validate-deploy-configs || echo "export DEPLOY_CONFIGS_STATUS=1" >> "$BASH_ENV" command: lint
working_directory: packages/contracts-bedrock - run-contracts-check:
- run: command: gas-snapshot-check
name: lint - run-contracts-check:
command: | command: autogen-invariant-docs
just lint-check || echo "export LINT_STATUS=1" >> "$BASH_ENV" - run-contracts-check:
working_directory: packages/contracts-bedrock command: snapshots-check-no-build
- run: - run-contracts-check:
name: gas snapshot command: kontrol-deployment-check
command: | - run-contracts-check:
just gas-snapshot-check || echo "export GAS_SNAPSHOT_STATUS=1" >> "$BASH_ENV" command: interfaces-check-no-build
environment: - run-contracts-check:
FOUNDRY_PROFILE: ci command: size-check
working_directory: packages/contracts-bedrock
no_output_timeout: 15m
- run:
name: invariant docs
command: |
just autogen-invariant-docs
git diff --exit-code ./invariant-docs/*.md || echo "export INVARIANT_DOCS_STATUS=1" >> "$BASH_ENV"
working_directory: packages/contracts-bedrock
- run:
name: snapshots
command: |
just snapshots-check-no-build || echo "export SNAPSHOTS_STATUS=1" >> "$BASH_ENV"
working_directory: packages/contracts-bedrock
- run:
name: kontrol deployment
command: |
just kontrol-deployment-check || echo "export KONTROL_DEPLOYMENT_STATUS=1" >> "$BASH_ENV"
working_directory: packages/contracts-bedrock
- run:
name: interfaces
command: |
just interfaces-check-no-build || echo "export INTERFACES_STATUS=1" >> "$BASH_ENV"
working_directory: packages/contracts-bedrock
- run:
name: size check
command: |
forge build --sizes --skip "/**/test/**" --skip "/**/scripts/**" || echo "export SIZE_CHECK=1" >> "$BASH_ENV"
environment:
FOUNDRY_PROFILE: ci
working_directory: packages/contracts-bedrock
- run:
name: check statuses
command: |
if [[ "$LINT_STATUS" -ne 0 ]]; then
echo "Linting failed, see job output for details."
FAILED=1
fi
if [[ "$GAS_SNAPSHOT_STATUS" -ne 0 ]]; then
echo "Gas snapshot failed, see job output for details."
FAILED=1
fi
if [[ "$SEMVER_LOCK_STATUS" -ne 0 ]]; then
echo "Semver lock failed, see job output for details."
FAILED=1
fi
if [[ "$INVARIANT_DOCS_STATUS" -ne 0 ]]; then
echo "Invariant docs failed, see job output for details."
FAILED=1
fi
if [[ "$DEPLOY_CONFIGS_STATUS" -ne 0 ]]; then
echo "Deploy config check failed, see job output for details."
FAILED=1
fi
if [[ "$SNAPSHOTS_STATUS" -ne 0 ]]; then
echo "Snapshots check failed, see job output for details."
FAILED=1
fi
if [[ "$KONTROL_DEPLOYMENT_STATUS" -ne 0 ]]; then
echo "Kontrol deployment check failed, see job output for details."
FAILED=1
fi
if [[ "$INTERFACES_STATUS" -ne 0 ]]; then
echo "Interface check failed, see job output for details."
FAILED=1
fi
if [[ "$SIZE_CHECK" -ne 0 ]]; then
echo "Contract(s) exceed size limit, see job output for details."
FAILED=1
fi
if [[ "$FAILED" -ne 0 ]]; then
exit 1
fi
contracts-bedrock-validate-spacers: contracts-bedrock-validate-spacers:
docker: docker:
......
...@@ -96,6 +96,26 @@ interfaces-check-no-build: ...@@ -96,6 +96,26 @@ interfaces-check-no-build:
# artifacts can cause the script to detect issues incorrectly.2 # artifacts can cause the script to detect issues incorrectly.2
interfaces-check: clean build interfaces-check-no-build interfaces-check: clean build interfaces-check-no-build
# Checks that the size of the contracts is within the limit.
size-check:
forge build --sizes --skip "/**/test/**" --skip "/**/scripts/**"
# Checks that any contracts with a modified semver lock also have a modified semver version.
# Does not build contracts.
semver-diff-check-no-build:
./scripts/checks/check-semver-diff.sh
# Checks that any contracts with a modified semver lock also have a modified semver version.
semver-diff-check: build semver-diff-check-no-build
# Checks that semver natspec is equal to the actual semver version.
# Does not build contracts.
semver-natspec-check-no-build:
./scripts/checks/check-semver-natspec-match.sh
# Checks that semver natspec is equal to the actual semver version.
semver-natspec-check: build semver-natspec-check-no-build
semver-lock: semver-lock:
forge script scripts/autogen/SemverLock.s.sol forge script scripts/autogen/SemverLock.s.sol
......
#!/usr/bin/env bash
set -euo pipefail
# Grab the directory of the contracts-bedrock package.
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
# Load semver-utils.
# shellcheck source=/dev/null
source "$SCRIPT_DIR/utils/semver-utils.sh"
# Path to semver-lock.json.
SEMVER_LOCK="semver-lock.json"
# Create a temporary directory.
temp_dir=$(mktemp -d)
trap 'rm -rf "$temp_dir"' EXIT
# Exit early if semver-lock.json has not changed.
if ! git diff origin/develop...HEAD --name-only | grep -q "$SEMVER_LOCK"; then
echo "No changes detected in semver-lock.json"
exit 0
fi
# Get the upstream semver-lock.json.
git show origin/develop:packages/contracts-bedrock/semver-lock.json > "$temp_dir/upstream_semver_lock.json"
# Copy the local semver-lock.json.
cp "$SEMVER_LOCK" "$temp_dir/local_semver_lock.json"
# Get the changed contracts.
changed_contracts=$(jq -r '
def changes:
to_entries as $local
| input as $upstream
| $local | map(
select(
.key as $key
| .value != $upstream[$key]
)
) | map(.key);
changes[]
' "$temp_dir/local_semver_lock.json" "$temp_dir/upstream_semver_lock.json")
# Flag to track if any errors are detected.
has_errors=false
# Check each changed contract for a semver version change.
for contract in $changed_contracts; do
# Check if the contract file exists.
if [ ! -f "$contract" ]; then
echo "❌ Error: Contract file $contract not found"
has_errors=true
continue
fi
# Extract the old and new source files.
old_source_file="$temp_dir/old_${contract##*/}"
new_source_file="$temp_dir/new_${contract##*/}"
git show origin/develop:packages/contracts-bedrock/"$contract" > "$old_source_file" 2>/dev/null || true
cp "$contract" "$new_source_file"
# Extract the old and new versions.
old_version=$(extract_version "$old_source_file" 2>/dev/null || echo "N/A")
new_version=$(extract_version "$new_source_file" 2>/dev/null || echo "N/A")
# Check if the versions were extracted successfully.
if [ "$old_version" = "N/A" ] || [ "$new_version" = "N/A" ]; then
echo "❌ Error: unable to extract version for $contract"
echo " this is probably a bug in check-semver-diff.sh"
echo " please report or fix the issue if possible"
has_errors=true
fi
# Check if the version changed.
if [ "$old_version" = "$new_version" ]; then
echo "❌ Error: src/$contract has changes in semver-lock.json but no version change"
echo " Old version: $old_version"
echo " New version: $new_version"
has_errors=true
else
echo "✅ $contract: version changed from $old_version to $new_version"
fi
done
# Exit with error if any issues were found.
if [ "$has_errors" = true ]; then
exit 1
fi
#!/usr/bin/env bash
set -euo pipefail
# Grab the directory of the contracts-bedrock package
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
CONTRACTS_BASE=$(dirname "$(dirname "$SCRIPT_DIR")")
ARTIFACTS_DIR="$CONTRACTS_BASE/forge-artifacts"
CONTRACTS_DIR="$CONTRACTS_BASE/src"
# Load semver-utils
# shellcheck source=/dev/null
source "$SCRIPT_DIR/utils/semver-utils.sh"
# Flag to track if any errors are detected
has_errors=false
# Iterate through each artifact file
for artifact_file in "$ARTIFACTS_DIR"/**/*.json; do
# Get the contract name and find the corresponding source file
contract_name=$(basename "$artifact_file" .json)
contract_file=$(find "$CONTRACTS_DIR" -name "$contract_name.sol")
# Try to extract version as a constant
raw_metadata=$(jq -r '.rawMetadata' "$artifact_file")
artifact_version=$(echo "$raw_metadata" | jq -r '.output.devdoc.stateVariables.version."custom:semver"')
is_constant=true
if [ "$artifact_version" = "null" ]; then
# If not found as a constant, try to extract as a function
artifact_version=$(echo "$raw_metadata" | jq -r '.output.devdoc.methods."version()"."custom:semver"')
is_constant=false
fi
# If @custom:semver is not found in either location, skip this file
if [ "$artifact_version" = "null" ]; then
continue
fi
# If source file is not found, report an error
if [ -z "$contract_file" ]; then
echo "❌ $contract_name: Source file not found"
continue
fi
# Extract version from source based on whether it's a constant or function
if [ "$is_constant" = true ]; then
source_version=$(extract_constant_version "$contract_file")
else
source_version=$(extract_function_version "$contract_file")
fi
# If source version is not found, report an error
if [ "$source_version" = "" ]; then
echo "❌ Error: failed to find version string for $contract_name"
echo " this is probably a bug in check-contract-semver.sh"
echo " please report or fix the issue if possible"
has_errors=true
fi
# Compare versions
if [ "$source_version" != "$artifact_version" ]; then
echo "❌ Error: $contract_name has different semver in code and devdoc"
echo " Code: $source_version"
echo " Devdoc: $artifact_version"
has_errors=true
else
echo "✅ $contract_name: code: $source_version, devdoc: $artifact_version"
fi
done
# If any errors were detected, exit with a non-zero status
if [ "$has_errors" = true ]; then
exit 1
fi
#!/usr/bin/env bash
set -euo pipefail
# Function to extract version from contract source as a constant
extract_constant_version() {
local file=$1
grep -o 'string.*constant.*version.*=.*"[^"]*"' "$file" | sed 's/.*"\([^"]*\)".*/\1/' || echo ""
}
# Function to extract version from contract source as a function
extract_function_version() {
local file=$1
sed -n '/function.*version()/,/return/p' "$file" | grep -o '"[^"]*"' | sed 's/"//g' || echo ""
}
# Function to extract version from either constant or function
extract_version() {
local file=$1
version=$(extract_constant_version "$file")
if [ -z "$version" ]; then
version=$(extract_function_version "$file")
fi
echo "$version"
}
...@@ -212,16 +212,16 @@ ...@@ -212,16 +212,16 @@
"sourceCodeHash": "0x740b4043436d1b314ee3ba145acfcde60b6abd8416ea594f2b8e890b5d0bce6b" "sourceCodeHash": "0x740b4043436d1b314ee3ba145acfcde60b6abd8416ea594f2b8e890b5d0bce6b"
}, },
"src/universal/OptimismMintableERC20Factory.sol": { "src/universal/OptimismMintableERC20Factory.sol": {
"initCodeHash": "0x3ebd2297c0af2856a432daf29d186d0751f7edb1c777abbe136953038cf5d1ba", "initCodeHash": "0x9cd4102d3ca811d5dc67ae99ce7de95812264575a789f96a6057600e55dcab64",
"sourceCodeHash": "0xf8425f65eb5520d55710907d67a9d6fa277263285e1b79ba299815d1c76919a3" "sourceCodeHash": "0xc70c8c11d6e754eabe746bbee47a5e1051f71f7a83913f62ebcce8db989a1357"
}, },
"src/universal/OptimismMintableERC721.sol": { "src/universal/OptimismMintableERC721.sol": {
"initCodeHash": "0x5a995fc043f8268a6d5c6284ad85b0de21328cd47277114aeba2c03484deaf91", "initCodeHash": "0xec037be7fc28e072944b0a9e55d4278b92d6c68ccb41049ab52eafca59c6e023",
"sourceCodeHash": "0xbf186922941d49e75d25bcac2a480c3e3ffed9bdd79a3fdca5a6c79bcbe94735" "sourceCodeHash": "0x5ea7c1b0cef5609f25c4193f5795fc9ce8f3ae08dbbf2945afe38e5af58f32c3"
}, },
"src/universal/OptimismMintableERC721Factory.sol": { "src/universal/OptimismMintableERC721Factory.sol": {
"initCodeHash": "0x9c6181eff40822a78562a30eaefb338c0841284e3b1036371348d185ec5e285f", "initCodeHash": "0x1e247d46b5ac3cc8ac6b51193917cd5b88ff00fbea7bf768c65fa35a115f8607",
"sourceCodeHash": "0x8be6e661af4b8079567fb2cd368e0f6bf9c11185472c5a75bec1172820d553e0" "sourceCodeHash": "0x1c4bc4727f08d80e8364561b49397ee57bb485072cb004b7a430559cbfa019a6"
}, },
"src/universal/StorageSetter.sol": { "src/universal/StorageSetter.sol": {
"initCodeHash": "0x00b8b883597e67e5c3548e7ba4139ed720893c0acb217dd170bec520cefdfab5", "initCodeHash": "0x00b8b883597e67e5c3548e7ba4139ed720893c0acb217dd170bec520cefdfab5",
......
...@@ -48,8 +48,8 @@ contract OptimismMintableERC20Factory is ISemver, Initializable, IOptimismERC20F ...@@ -48,8 +48,8 @@ contract OptimismMintableERC20Factory is ISemver, Initializable, IOptimismERC20F
/// the OptimismMintableERC20 token contract since this contract /// the OptimismMintableERC20 token contract since this contract
/// is responsible for deploying OptimismMintableERC20 contracts. /// is responsible for deploying OptimismMintableERC20 contracts.
/// @notice Semantic version. /// @notice Semantic version.
/// @custom:semver 1.10.1-beta.1 /// @custom:semver 1.10.1-beta.3
string public constant version = "1.10.1-beta.2"; string public constant version = "1.10.1-beta.3";
/// @notice Constructs the OptimismMintableERC20Factory contract. /// @notice Constructs the OptimismMintableERC20Factory contract.
constructor() { constructor() {
......
...@@ -32,8 +32,8 @@ contract OptimismMintableERC721 is ERC721Enumerable, IOptimismMintableERC721, IS ...@@ -32,8 +32,8 @@ contract OptimismMintableERC721 is ERC721Enumerable, IOptimismMintableERC721, IS
} }
/// @notice Semantic version. /// @notice Semantic version.
/// @custom:semver 1.3.1-beta.1 /// @custom:semver 1.3.1-beta.2
string public constant version = "1.3.1-beta.1"; string public constant version = "1.3.1-beta.2";
/// @param _bridge Address of the bridge on this network. /// @param _bridge Address of the bridge on this network.
/// @param _remoteChainId Chain ID where the remote token is deployed. /// @param _remoteChainId Chain ID where the remote token is deployed.
......
...@@ -23,8 +23,8 @@ contract OptimismMintableERC721Factory is ISemver { ...@@ -23,8 +23,8 @@ contract OptimismMintableERC721Factory is ISemver {
event OptimismMintableERC721Created(address indexed localToken, address indexed remoteToken, address deployer); event OptimismMintableERC721Created(address indexed localToken, address indexed remoteToken, address deployer);
/// @notice Semantic version. /// @notice Semantic version.
/// @custom:semver 1.4.1-beta.1 /// @custom:semver 1.4.1-beta.2
string public constant version = "1.4.1-beta.1"; string public constant version = "1.4.1-beta.2";
/// @notice The semver MUST be bumped any time that there is a change in /// @notice The semver MUST be bumped any time that there is a change in
/// the OptimismMintableERC721 token contract since this contract /// the OptimismMintableERC721 token contract since this contract
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment