Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 106 additions & 1 deletion Static_agent/Slither_agent/solidity_syntax_fixer.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,109 @@ def check_compilation(self, file_path: str) -> Tuple[bool, str]:
return False, result.stderr

except Exception as e:
return False, f"Error during compilation: {str(e)}"
return False, f"Error during compilation: {str(e)}"

def detect_and_fix_syntax_issues(self, content: str, file_path: str) -> str:
"""Detect and fix common Solidity syntax issues"""
original_content = content
fixes = []

# Fix 1: Abstract contracts with unimplemented functions
pattern = r'contract\s+(\w+)\s*{'
matches = re.finditer(pattern, content)
for match in matches:
contract_name = match.group(1)
contract_start = match.start()

# Look for unimplemented functions in this contract
# Find the contract body
brace_count = 0
contract_end = contract_start
for i, char in enumerate(content[contract_start:]):
if char == '{':
brace_count += 1
elif char == '}':
brace_count -= 1
if brace_count == 0:
contract_end = contract_start + i
break

contract_body = content[contract_start:contract_end + 1]

# Check for unimplemented functions (ending with semicolon)
func_pattern = r'function\s+\w+\([^)]*\)\s*[^{]*;'
if re.search(func_pattern, contract_body):
# This contract has unimplemented functions, make it abstract
new_contract_def = f'abstract contract {contract_name} {{'
content = content.replace(match.group(0), new_contract_def)
fixes.append(f"Made contract '{contract_name}' abstract (has unimplemented functions)")

Comment on lines +54 to +82
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Potential issues with contract parsing and abstract keyword insertion.

The contract parsing logic has several concerns:

  1. The regex pattern doesn't handle inheritance (e.g., contract A is B {)
  2. The brace counting doesn't handle nested structures like structs or mappings properly
  3. Abstract contracts may not be compatible with pragma versions below 0.6.0

Consider this more robust approach:

-        # Fix 1: Abstract contracts with unimplemented functions
-        pattern = r'contract\s+(\w+)\s*{'
-        matches = re.finditer(pattern, content)
-        for match in matches:
-            contract_name = match.group(1)
-            contract_start = match.start()
-            
-            # Look for unimplemented functions in this contract
-            # Find the contract body
-            brace_count = 0
-            contract_end = contract_start
-            for i, char in enumerate(content[contract_start:]):
-                if char == '{':
-                    brace_count += 1
-                elif char == '}':
-                    brace_count -= 1
-                    if brace_count == 0:
-                        contract_end = contract_start + i
-                        break
-            
-            contract_body = content[contract_start:contract_end + 1]
-            
-            # Check for unimplemented functions (ending with semicolon)
-            func_pattern = r'function\s+\w+\([^)]*\)\s*[^{]*;'
-            if re.search(func_pattern, contract_body):
-                # This contract has unimplemented functions, make it abstract
-                new_contract_def = f'abstract contract {contract_name} {{'
-                content = content.replace(match.group(0), new_contract_def)
-                fixes.append(f"Made contract '{contract_name}' abstract (has unimplemented functions)")
+        # Fix 1: Abstract contracts with unimplemented functions
+        # Handle inheritance syntax properly
+        pattern = r'contract\s+(\w+)(\s+is\s+[^{]+)?\s*{'
+        matches = list(re.finditer(pattern, content))
+        for match in reversed(matches):  # Process in reverse to maintain positions
+            contract_name = match.group(1)
+            inheritance = match.group(2) or ""
+            
+            # Check for unimplemented functions in this contract
+            func_pattern = r'function\s+\w+\([^)]*\)\s*[^{]*;'
+            if re.search(func_pattern, content):
+                # Only apply if pragma supports abstract contracts
+                pragma_match = re.search(r'pragma\s+solidity\s+([^;]+);', content)
+                if pragma_match and not re.search(r'\^0\.[0-5]\.', pragma_match.group(1)):
+                    new_contract_def = f'abstract contract {contract_name}{inheritance} {{'
+                    content = content.replace(match.group(0), new_contract_def)
+                    fixes.append(f"Made contract '{contract_name}' abstract (has unimplemented functions)")

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In Static_agent/Slither_agent/solidity_syntax_fixer.py around lines 54-82, the
current logic that finds contracts and marks them abstract is fragile: the
contract regex only matches simple "contract Name {" and misses inheritance
clauses, the manual brace counting can be confused by nested braces inside
strings, comments, structs or mappings, and blindly inserting "abstract" can
break compatibility with older pragma versions. Fix by updating the contract
detection regex to capture optional inheritance and any modifiers (e.g.,
r'contract\s+(\w+)\s*(?:is\s+[^{]+)?\s*{'), replace the brittle brace-scan with
a parser-aware approach such as using a simple state machine that skips string
literals and comments and correctly tracks braces (or use an existing Solidity
parser library) to locate the contract body bounds, check the pragma solidity
version at the top and only insert the "abstract" keyword when pragma >= 0.6.0,
and perform the replacement using precise slicing (start/end indices) rather
than global replace to avoid accidental multiple substitutions; also add unit
tests covering inheritance, nested structs/mappings, comments, and older pragma
versions.

# Fix 2: Add virtual keyword to unimplemented functions
virtual_pattern = r'function\s+(\w+)\s*\([^)]*\)\s*([^{;]*);'
def add_virtual(match):
func_name = match.group(1)
modifiers = match.group(2).strip()
if 'virtual' not in modifiers:
if modifiers:
new_func = f"function {func_name}({match.group(0).split('(')[1].split(')')[0]}) {modifiers} virtual;"
else:
new_func = f"function {func_name}({match.group(0).split('(')[1].split(')')[0]}) public virtual;"
fixes.append(f"Added 'virtual' keyword to function '{func_name}'")
return new_func
return match.group(0)

content = re.sub(virtual_pattern, add_virtual, content)
Comment on lines +84 to +97
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Complex regex replacement with potential parsing issues.

The function parameter extraction using string splitting is fragile and may break with complex parameter lists or nested parentheses.

Apply this diff for more robust parameter handling:

-        # Fix 2: Add virtual keyword to unimplemented functions
-        virtual_pattern = r'function\s+(\w+)\s*\([^)]*\)\s*([^{;]*);'
-        def add_virtual(match):
-            func_name = match.group(1)
-            modifiers = match.group(2).strip()
-            if 'virtual' not in modifiers:
-                if modifiers:
-                    new_func = f"function {func_name}({match.group(0).split('(')[1].split(')')[0]}) {modifiers} virtual;"
-                else:
-                    new_func = f"function {func_name}({match.group(0).split('(')[1].split(')')[0]}) public virtual;"
-                fixes.append(f"Added 'virtual' keyword to function '{func_name}'")
-                return new_func
-            return match.group(0)
-        
-        content = re.sub(virtual_pattern, add_virtual, content)
+        # Fix 2: Add virtual keyword to unimplemented functions
+        virtual_pattern = r'function\s+(\w+)\s*\(([^)]*)\)\s*([^{;]*);'
+        def add_virtual(match):
+            func_name = match.group(1)
+            params = match.group(2)
+            modifiers = match.group(3).strip()
+            if 'virtual' not in modifiers:
+                if modifiers:
+                    new_func = f"function {func_name}({params}) {modifiers} virtual;"
+                else:
+                    new_func = f"function {func_name}({params}) public virtual;"
+                fixes.append(f"Added 'virtual' keyword to function '{func_name}'")
+                return new_func
+            return match.group(0)
+        
+        content = re.sub(virtual_pattern, add_virtual, content)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
virtual_pattern = r'function\s+(\w+)\s*\([^)]*\)\s*([^{;]*);'
def add_virtual(match):
func_name = match.group(1)
modifiers = match.group(2).strip()
if 'virtual' not in modifiers:
if modifiers:
new_func = f"function {func_name}({match.group(0).split('(')[1].split(')')[0]}) {modifiers} virtual;"
else:
new_func = f"function {func_name}({match.group(0).split('(')[1].split(')')[0]}) public virtual;"
fixes.append(f"Added 'virtual' keyword to function '{func_name}'")
return new_func
return match.group(0)
content = re.sub(virtual_pattern, add_virtual, content)
# Fix 2: Add virtual keyword to unimplemented functions
virtual_pattern = r'function\s+(\w+)\s*\(([^)]*)\)\s*([^{;]*);'
def add_virtual(match):
func_name = match.group(1)
params = match.group(2)
modifiers = match.group(3).strip()
if 'virtual' not in modifiers:
if modifiers:
new_func = f"function {func_name}({params}) {modifiers} virtual;"
else:
new_func = f"function {func_name}({params}) public virtual;"
fixes.append(f"Added 'virtual' keyword to function '{func_name}'")
return new_func
return match.group(0)
content = re.sub(virtual_pattern, add_virtual, content)
🤖 Prompt for AI Agents
In Static_agent/Slither_agent/solidity_syntax_fixer.py around lines 84 to 97,
the current replacement extracts the function parameter list by splitting the
full match string which is fragile for complex or nested parameter lists; update
the regex to capture the parameter list and modifiers explicitly (e.g. pattern
with groups for name, params, and modifiers), use match.group(...) to get the
params and modifiers instead of string splitting, preserve existing visibility
or insert "public" only when visibility is missing, append the same fixes
message, and return the rebuilt function string using the captured groups so
nested/complex parameter lists are handled robustly.


# Fix 3: Update deprecated .value() syntax to {value: ...}
value_pattern = r'(\w+)\.call\.value\(([^)]+)\)\(\)'
def fix_call_value(match):
address = match.group(1)
value = match.group(2)
fixes.append(f"Updated deprecated .value() syntax to modern {{value: ...}} format")
return f'{address}.call{{value: {value}}}("")'

content = re.sub(value_pattern, fix_call_value, content)

# Fix 4: Add success handling for call operations
call_pattern = r'require\s*\(\s*(\w+)\.call\{value:\s*([^}]+)\}\s*\(""\)\s*\)'
def fix_call_handling(match):
address = match.group(1)
value = match.group(2)
fixes.append(f"Added proper success handling for call operation")
return f'(bool success, ) = {address}.call{{value: {value}}}("");\n require(success)'

content = re.sub(call_pattern, fix_call_handling, content)
Comment on lines +109 to +117
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Multiple issues with call pattern handling.

  1. The regex pattern may not handle all call variations
  2. The f-string at line 114 has no placeholders
  3. The replacement introduces formatting inconsistencies

Apply this diff to fix the issues:

-        # Fix 4: Add success handling for call operations
-        call_pattern = r'require\s*\(\s*(\w+)\.call\{value:\s*([^}]+)\}\s*\(""\)\s*\)'
-        def fix_call_handling(match):
-            address = match.group(1)
-            value = match.group(2)
-            fixes.append(f"Added proper success handling for call operation")
-            return f'(bool success, ) = {address}.call{{value: {value}}}("");\n        require(success)'
-        
-        content = re.sub(call_pattern, fix_call_handling, content)
+        # Fix 4: Add success handling for call operations
+        call_pattern = r'require\s*\(\s*(\w+)\.call\{value:\s*([^}]+)\}\s*\(""\)\s*\)'
+        def fix_call_handling(match):
+            address = match.group(1)
+            value = match.group(2)
+            fixes.append("Added proper success handling for call operation")
+            return f'(bool success, ) = {address}.call{{value: {value}}}("");\n        require(success)'
+        
+        content = re.sub(call_pattern, fix_call_handling, content)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Fix 4: Add success handling for call operations
call_pattern = r'require\s*\(\s*(\w+)\.call\{value:\s*([^}]+)\}\s*\(""\)\s*\)'
def fix_call_handling(match):
address = match.group(1)
value = match.group(2)
fixes.append(f"Added proper success handling for call operation")
return f'(bool success, ) = {address}.call{{value: {value}}}("");\n require(success)'
content = re.sub(call_pattern, fix_call_handling, content)
# Fix 4: Add success handling for call operations
call_pattern = r'require\s*\(\s*(\w+)\.call\{value:\s*([^}]+)\}\s*\(""\)\s*\)'
def fix_call_handling(match):
address = match.group(1)
value = match.group(2)
fixes.append("Added proper success handling for call operation")
return f'(bool success, ) = {address}.call{{value: {value}}}("");\n require(success)'
content = re.sub(call_pattern, fix_call_handling, content)
🧰 Tools
🪛 Ruff (0.13.1)

114-114: f-string without any placeholders

Remove extraneous f prefix

(F541)

🤖 Prompt for AI Agents
In Static_agent/Slither_agent/solidity_syntax_fixer.py around lines 109 to 117,
the current call-fix logic uses a fragile regex, an unnecessary f-string with no
placeholders, and produces inconsistent indentation; replace it by expanding the
regex to match optional whitespace, optional value clause, and optional calldata
(e.g., .call{value: ...}("...") or .call("...") ), capture leading indentation,
address, value (if present) and calldata, stop using an f-string when not
interpolating, and build the replacement string using the captured groups so it
produces a properly indented assignment like "(bool success, ) =
ADDRESS.call{value: VALUE}(CALLDATA);" or without the value clause when absent,
followed by "require(success);" on the next indented line to preserve formatting
and semantics.


# Fix 5: Update pragma version if it's too old and causing issues
pragma_pattern = r'pragma\s+solidity\s+([^;]+);'
pragma_match = re.search(pragma_pattern, content)
if pragma_match:
version = pragma_match.group(1)
# If version is older than 0.6.0 and we have modern syntax, update it
if '^0.5' in version or '^0.4' in version:
if 'abstract' in content or 'virtual' in content or '{value:' in content:
content = re.sub(pragma_pattern, 'pragma solidity ^0.8.0;', content)
fixes.append(f"Updated pragma version from '{version}' to '^0.8.0' for compatibility")

# Fix 6: Add SPDX license if missing
if 'SPDX-License-Identifier' not in content:
lines = content.split('\n')
lines.insert(0, '// SPDX-License-Identifier: MIT')
content = '\n'.join(lines)
fixes.append("Added missing SPDX license identifier")

# Fix 7: Fix require statements that use old call patterns
old_require_pattern = r'require\s*\(\s*address\([^)]+\)\.call\.value\([^)]+\)\(\)\s*\)'
if re.search(old_require_pattern, content):
# This is handled by the earlier fixes, but let's ensure consistency
fixes.append("Fixed deprecated require statement with old call pattern")

# Store fixes for this file
if fixes:
self.fixes_applied.append({
"file": file_path,
"fixes": fixes,
"timestamp": datetime.now().isoformat()
})

return content