Commit 1f0d8a8b authored by smartcontracts's avatar smartcontracts Committed by GitHub

fix: properly check all interfaces (#11734)

Interfaces check script had a bug that would cause it to use the
wrong ABI when checking an interface. Each compiler artifact only
has a single ABI in it but this same ABI was being used for all
contract definitions found within that file. Since each separate
contract definition gets its own file anyway, this script now
properly verifies by finding the specific definition that matches
the filename.
parent 2f10fb7a
...@@ -17,31 +17,22 @@ fi ...@@ -17,31 +17,22 @@ fi
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
CONTRACTS_BASE=$(dirname "$(dirname "$SCRIPT_DIR")") CONTRACTS_BASE=$(dirname "$(dirname "$SCRIPT_DIR")")
# Define the files to exclude (glob patterns can be used) # Define contracts that should be excluded from the check
EXCLUDE_FILES=( EXCLUDE_CONTRACTS=(
# External dependencies # External dependencies
"IMulticall3"
"IERC20" "IERC20"
"IERC721" "IERC721"
"IERC721Enumerable" "IERC721Enumerable"
"IERC721Metadata"
"IERC721Upgradeable" "IERC721Upgradeable"
"IERC721Receiver"
"IERC1271"
"IERC165" "IERC165"
"IVotes" "IERC165Upgradeable"
"IBeacon"
"IProxyCreationCallback"
"IAutomate"
"IGelato1Balance"
"ERC721TokenReceiver" "ERC721TokenReceiver"
"ERC1155TokenReceiver" "ERC1155TokenReceiver"
"ERC777TokensRecipient" "ERC777TokensRecipient"
"Guard" "Guard"
"GnosisSafeProxy" "IProxy"
# Foundry # Foundry
"Common"
"Vm" "Vm"
"VmSafe" "VmSafe"
...@@ -52,11 +43,6 @@ EXCLUDE_FILES=( ...@@ -52,11 +43,6 @@ EXCLUDE_FILES=(
# Kontrol # Kontrol
"KontrolCheatsBase" "KontrolCheatsBase"
"KontrolCheats"
# Error definition files
"CommonErrors"
"Errors"
# TODO: Interfaces that need to be fixed # TODO: Interfaces that need to be fixed
"IPreimageOracle" "IPreimageOracle"
...@@ -72,15 +58,16 @@ EXCLUDE_FILES=( ...@@ -72,15 +58,16 @@ EXCLUDE_FILES=(
"IDelayedWETH" "IDelayedWETH"
"IAnchorStateRegistry" "IAnchorStateRegistry"
"ICrossL2Inbox" "ICrossL2Inbox"
"IL1CrossDomainMessenger"
"IL2ToL2CrossDomainMessenger" "IL2ToL2CrossDomainMessenger"
"KontrolInterfaces" "IL1ERC721Bridge"
"IL1StandardBridge"
"ISuperchainConfig"
"IOptimismPortal"
) )
# Convert the exclude files array to a pipe-separated string
EXCLUDE_PATTERN=$( (IFS="|"; echo "${EXCLUDE_FILES[*]}") )
# Find all JSON files in the forge-artifacts folder # Find all JSON files in the forge-artifacts folder
JSON_FILES=$(find "$CONTRACTS_BASE/forge-artifacts" -type f -name "*.json" | grep -Ev "$EXCLUDE_PATTERN") JSON_FILES=$(find "$CONTRACTS_BASE/forge-artifacts" -type f -name "*.json")
# Initialize a flag to track if any issues are detected # Initialize a flag to track if any issues are detected
issues_detected=false issues_detected=false
...@@ -88,7 +75,7 @@ issues_detected=false ...@@ -88,7 +75,7 @@ issues_detected=false
# Create a temporary file to store files that have already been reported # Create a temporary file to store files that have already been reported
REPORTED_INTERFACES_FILE=$(mktemp) REPORTED_INTERFACES_FILE=$(mktemp)
# Define a cleanup function # Clean up the temporary file on exit
cleanup() { cleanup() {
rm -f "$REPORTED_INTERFACES_FILE" rm -f "$REPORTED_INTERFACES_FILE"
} }
...@@ -96,47 +83,67 @@ cleanup() { ...@@ -96,47 +83,67 @@ cleanup() {
# Trap exit and error signals and call cleanup function # Trap exit and error signals and call cleanup function
trap cleanup EXIT ERR trap cleanup EXIT ERR
# Check if a contract is excluded
is_excluded() {
for exclude in "${EXCLUDE_CONTRACTS[@]}"; do
if [[ "$exclude" == "$1" ]]; then
return 0
fi
done
return 1
}
# Iterate over all JSON files # Iterate over all JSON files
for interface_file in $JSON_FILES; do for interface_file in $JSON_FILES; do
# Extract contract kind and name in a single pass # Grab the contract name from the file name
contract_name=$(basename "$interface_file" .json | cut -d '.' -f 1)
# Extract all contract definitions in a single pass
contract_definitions=$(jq -r '.ast.nodes[] | select(.nodeType == "ContractDefinition") | "\(.contractKind),\(.name)"' "$interface_file") contract_definitions=$(jq -r '.ast.nodes[] | select(.nodeType == "ContractDefinition") | "\(.contractKind),\(.name)"' "$interface_file")
# Warn and continue if no contract definitions are found # Continue if no contract definitions are found
# Can happen in Solidity files that don't declare any contracts/interfaces
if [ -z "$contract_definitions" ]; then if [ -z "$contract_definitions" ]; then
echo "Warning: Could not extract contract definitions from $interface_file."
echo "Add this file to the EXCLUDE_FILES list if it can be ignored."
continue continue
fi fi
while IFS=',' read -r contract_kind contract_name; do # Iterate over the found contract definitions and figure out which one
# If contract kind is not "interface", skip the file # matches the file name. We do this so that we can figure out if this is an
if [ "$contract_kind" != "interface" ]; then # interface or a contract based on the contract kind.
continue found=false
fi contract_temp=""
contract_kind=""
# If contract name is in the exclude list, skip the file for definition in $contract_definitions; do
# Exclude list functions double duty as a list of files to exclude (glob patterns allowed) IFS=',' read -r contract_kind contract_temp <<< "$definition"
# and a list of interface names that shouldn't be checked. Simplifies the script a bit and if [[ "$contract_name" == "$contract_temp" ]]; then
# means we can ignore specific interfaces without ignoring the entire file if desired. found=true
exclude=false
for exclude_item in "${EXCLUDE_FILES[@]}"; do
if [[ "$exclude_item" == "$contract_name" ]]; then
exclude=true
break break
fi fi
done done
if [[ "$exclude" == true ]]; then
# Continue if no matching contract name is found. Can happen in Solidity
# files where no contracts/interfaces are defined with the same name as the
# file. Still OK because a separate artifact *will* be generated for the
# specific contract/interface.
if [ "$found" = false ]; then
continue
fi
# If contract kind is not "interface", skip the file
if [ "$contract_kind" != "interface" ]; then
continue continue
fi fi
# If contract name does not start with an "I", throw an error # If contract name does not start with an "I", throw an error
if [[ "$contract_name" != I* ]]; then if [[ "$contract_name" != I* ]]; then
if ! grep -q "^$contract_name$" "$REPORTED_INTERFACES_FILE"; then if ! grep -q "^$contract_name$" "$REPORTED_INTERFACES_FILE"; then
echo "$contract_name" >> "$REPORTED_INTERFACES_FILE"
if ! is_excluded "$contract_name"; then
echo "Issue found in ABI for interface $contract_name from file $interface_file." echo "Issue found in ABI for interface $contract_name from file $interface_file."
echo "Interface $contract_name does not start with 'I'." echo "Interface $contract_name does not start with 'I'."
echo "$contract_name" >> "$REPORTED_INTERFACES_FILE"
issues_detected=true issues_detected=true
fi fi
fi
continue continue
fi fi
...@@ -144,8 +151,11 @@ for interface_file in $JSON_FILES; do ...@@ -144,8 +151,11 @@ for interface_file in $JSON_FILES; do
contract_basename=${contract_name:1} contract_basename=${contract_name:1}
corresponding_contract_file="$CONTRACTS_BASE/forge-artifacts/$contract_basename.sol/$contract_basename.json" corresponding_contract_file="$CONTRACTS_BASE/forge-artifacts/$contract_basename.sol/$contract_basename.json"
# Check if the corresponding contract file exists # Skip the file if the corresponding contract file does not exist
if [ -f "$corresponding_contract_file" ]; then if [ ! -f "$corresponding_contract_file" ]; then
continue
fi
# Extract and compare ABIs excluding constructors # Extract and compare ABIs excluding constructors
interface_abi=$(jq '[.abi[] | select(.type != "constructor")]' < "$interface_file") interface_abi=$(jq '[.abi[] | select(.type != "constructor")]' < "$interface_file")
contract_abi=$(jq '[.abi[] | select(.type != "constructor")]' < "$corresponding_contract_file") contract_abi=$(jq '[.abi[] | select(.type != "constructor")]' < "$corresponding_contract_file")
...@@ -153,24 +163,33 @@ for interface_file in $JSON_FILES; do ...@@ -153,24 +163,33 @@ for interface_file in $JSON_FILES; do
# Use jq to compare the ABIs # Use jq to compare the ABIs
if ! diff_result=$(diff -u <(echo "$interface_abi" | jq -S .) <(echo "$contract_abi" | jq -S .)); then if ! diff_result=$(diff -u <(echo "$interface_abi" | jq -S .) <(echo "$contract_abi" | jq -S .)); then
if ! grep -q "^$contract_name$" "$REPORTED_INTERFACES_FILE"; then if ! grep -q "^$contract_name$" "$REPORTED_INTERFACES_FILE"; then
echo "$contract_name" >> "$REPORTED_INTERFACES_FILE"
if ! is_excluded "$contract_name"; then
echo "Issue found in ABI for interface $contract_name from file $interface_file." echo "Issue found in ABI for interface $contract_name from file $interface_file."
echo "Differences found in ABI between interface $contract_name and actual contract $contract_basename." echo "Differences found in ABI between interface $contract_name and actual contract $contract_basename."
if [ "$no_diff" = false ]; then if [ "$no_diff" = false ]; then
echo "$diff_result" echo "$diff_result"
fi fi
echo "$contract_name" >> "$REPORTED_INTERFACES_FILE"
issues_detected=true issues_detected=true
fi fi
fi fi
continue
fi
done
# Check for unnecessary exclusions
for exclude_item in "${EXCLUDE_CONTRACTS[@]}"; do
if ! grep -q "^$exclude_item$" "$REPORTED_INTERFACES_FILE"; then
echo "Warning: $exclude_item is in the exclude list but was not reported as an issue."
echo "Consider removing it from the EXCLUDE_CONTRACTS list in the script."
fi fi
done <<< "$contract_definitions"
done done
# Fail the script if any issues were detected # Fail the script if any issues were detected
if [ "$issues_detected" = true ]; then if [ "$issues_detected" = true ]; then
echo "Issues were detected while validating interface files." echo "Issues were detected while validating interface files."
echo "If the interface is an external dependency or should otherwise be excluded from this" echo "If the interface is an external dependency or should otherwise be excluded from this"
echo "check, add the interface name to the EXCLUDE_FILES list in the script. This will prevent" echo "check, add the interface name to the EXCLUDE_CONTRACTS list in the script. This will prevent"
echo "the script from comparing it against a corresponding contract." echo "the script from comparing it against a corresponding contract."
exit 1 exit 1
else else
......
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