Skip to content

Commit 6b4d8ff

Browse files
Merge pull request from GHSA-4v9q-cgpw-cf38
in external call codegen, when `extcodesize` is called on the target address, the IR for evaluating the target address can be evaluated twice. this can result in any side effects (embedded in the evaluation of the target address) being executed twice.
1 parent 90e3d20 commit 6b4d8ff

File tree

2 files changed

+56
-9
lines changed

2 files changed

+56
-9
lines changed

Diff for: tests/parser/features/external_contracts/test_external_contract_calls.py

+41
Original file line numberDiff line numberDiff line change
@@ -2411,3 +2411,44 @@ def bar(foo: Foo):
24112411
# fails due to returndatasize being nonzero but also lt 64
24122412
assert_tx_failed(lambda: c.bar(bad_1.address))
24132413
c.bar(bad_2.address)
2414+
2415+
2416+
def test_contract_address_evaluation(get_contract):
2417+
callee_code = """
2418+
# implements: Foo
2419+
2420+
interface Counter:
2421+
def increment_counter(): nonpayable
2422+
2423+
@external
2424+
def foo():
2425+
pass
2426+
2427+
@external
2428+
def bar() -> address:
2429+
Counter(msg.sender).increment_counter()
2430+
return self
2431+
"""
2432+
code = """
2433+
# implements: Counter
2434+
2435+
interface Foo:
2436+
def foo(): nonpayable
2437+
def bar() -> address: nonpayable
2438+
2439+
counter: uint256
2440+
2441+
@external
2442+
def increment_counter():
2443+
self.counter += 1
2444+
2445+
@external
2446+
def do_stuff(f: Foo) -> uint256:
2447+
Foo(f.bar()).foo()
2448+
return self.counter
2449+
"""
2450+
2451+
c1 = get_contract(code)
2452+
c2 = get_contract(callee_code)
2453+
2454+
assert c1.do_stuff(c2.address) == 1

Diff for: vyper/codegen/external_call.py

+15-9
Original file line numberDiff line numberDiff line change
@@ -168,15 +168,7 @@ def _extcodesize_check(address):
168168
return ["assert", ["extcodesize", address]]
169169

170170

171-
def ir_for_external_call(call_expr, context):
172-
from vyper.codegen.expr import Expr # TODO rethink this circular import
173-
174-
contract_address = Expr.parse_value_expr(call_expr.func.value, context)
175-
call_kwargs = _parse_kwargs(call_expr, context)
176-
args_ir = [Expr(x, context).ir_node for x in call_expr.args]
177-
178-
assert isinstance(contract_address.typ, InterfaceType)
179-
171+
def _external_call_helper(contract_address, args_ir, call_kwargs, call_expr, context):
180172
# expr.func._metadata["type"].return_type is more accurate
181173
# than fn_sig.return_type in the case of JSON interfaces.
182174
fn_type = call_expr.func._metadata["type"]
@@ -223,3 +215,17 @@ def ir_for_external_call(call_expr, context):
223215
ret.append(ret_unpacker)
224216

225217
return IRnode.from_list(ret, typ=return_t, location=MEMORY)
218+
219+
220+
def ir_for_external_call(call_expr, context):
221+
from vyper.codegen.expr import Expr # TODO rethink this circular import
222+
223+
contract_address = Expr.parse_value_expr(call_expr.func.value, context)
224+
assert isinstance(contract_address.typ, InterfaceType)
225+
args_ir = [Expr(x, context).ir_node for x in call_expr.args]
226+
call_kwargs = _parse_kwargs(call_expr, context)
227+
228+
with contract_address.cache_when_complex("external_contract") as (b1, contract_address):
229+
return b1.resolve(
230+
_external_call_helper(contract_address, args_ir, call_kwargs, call_expr, context)
231+
)

0 commit comments

Comments
 (0)