Skip to content

Commit 3ea5b85

Browse files
Th0rgalclaude
andcommitted
fix: block-scope internal temps and include target in collectStmtNames
Address bugbot findings: 1. Wrap call/revert/sizeCheck in a YulStmt.block so __ecwr_success is block-scoped. This prevents duplicate let declarations when multiple externalCallWithReturn statements appear in the same function body. The resultVar binding remains flat-scoped (outside the block) so subsequent statements can reference it. 2. Include the target expression in collectStmtNames for externalCallWithReturn, matching all other handler functions. Adds test: multiple externalCallWithReturn in same function compiles without collision. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c5bfef0 commit 3ea5b85

2 files changed

Lines changed: 36 additions & 5 deletions

File tree

Compiler/ContractSpec.lean

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -604,8 +604,8 @@ private partial def collectStmtNames : Stmt → List String
604604
| Stmt.internalCall functionName args => functionName :: collectExprListNames args
605605
| Stmt.internalCallAssign names functionName args =>
606606
names ++ functionName :: collectExprListNames args
607-
| Stmt.externalCallWithReturn resultVar _ _ args _ =>
608-
resultVar :: collectExprListNames args
607+
| Stmt.externalCallWithReturn resultVar target _ args _ =>
608+
resultVar :: collectExprNames target ++ collectExprListNames args
609609

610610
private partial def collectStmtListNames : List Stmt → List String
611611
| [] => []
@@ -3607,10 +3607,14 @@ def compileStmt (fields : List Field) (events : List EventDef := [])
36073607
let sizeCheck := YulStmt.if_ (YulExpr.call "lt" [YulExpr.call "returndatasize" [], YulExpr.lit 32]) [
36083608
YulStmt.expr (YulExpr.call "revert" [YulExpr.lit 0, YulExpr.lit 0])
36093609
]
3610-
-- Step 6: extract return value (call/staticcall already copied returndata to memory[0..32])
3610+
-- Wrap call + checks in a block so __ecwr_success is block-scoped.
3611+
-- This avoids duplicate let declarations when multiple externalCallWithReturn
3612+
-- statements appear in the same function body.
3613+
let callBlock := YulStmt.block ([storeSelector] ++ storeArgs ++ [letSuccess, revertBlock, sizeCheck])
3614+
-- Step 6: extract return value outside the block (call already copied returndata to memory[0..32])
3615+
-- resultVar is flat-scoped so subsequent statements can reference it.
36113616
let bindResult := YulStmt.let_ resultVar (YulExpr.call "mload" [YulExpr.lit 0])
3612-
-- Emit statements flat (not in a block) so resultVar is visible to subsequent statements
3613-
pure ([storeSelector] ++ storeArgs ++ [letSuccess, revertBlock, sizeCheck, bindResult])
3617+
pure [callBlock, bindResult]
36143618
| Stmt.returnValues values => do
36153619
if isInternal then
36163620
if values.length != internalRetNames.length then

Compiler/ContractSpecFeatureTest.lean

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4619,4 +4619,31 @@ private def externalCallWithReturnSpec : ContractSpec := {
46194619
| .ok _ =>
46204620
IO.println "✓ externalCallWithReturn staticcall accepted for view function"
46214621

4622+
-- Test: multiple externalCallWithReturn in same function (no duplicate let collision)
4623+
#eval! do
4624+
let multiCallSpec : ContractSpec := {
4625+
name := "MultiExternalCall"
4626+
fields := []
4627+
constructor := none
4628+
functions := [
4629+
{ name := "getPrices"
4630+
params := [{ name := "oracle1", ty := ParamType.address }, { name := "oracle2", ty := ParamType.address }]
4631+
returnType := none
4632+
body := [
4633+
Stmt.externalCallWithReturn "price1" (Expr.param "oracle1") 0xa035b1fe [] (isStatic := true),
4634+
Stmt.externalCallWithReturn "price2" (Expr.param "oracle2") 0xa035b1fe [] (isStatic := true),
4635+
Stmt.stop
4636+
]
4637+
}
4638+
]
4639+
}
4640+
match compile multiCallSpec [1] with
4641+
| .error err =>
4642+
throw (IO.userError s!"✗ multiple externalCallWithReturn should compile: {err}")
4643+
| .ok ir =>
4644+
let rendered := Yul.render (emitYul ir)
4645+
assertContains "multi externalCallWithReturn both bindings" rendered
4646+
["let price1 := mload(0)", "let price2 := mload(0)"]
4647+
IO.println "✓ multiple externalCallWithReturn in same function compiles without collision"
4648+
46224649
end Compiler.ContractSpecFeatureTest

0 commit comments

Comments
 (0)