class FunctionCall(ExpressionAbc):
"""
Represents:
- function calls, e.g. `:::solidity address(this).call("")`,
- type conversions, e.g. `:::solidity address(this)`,
- struct constructor calls, e.g. `:::solidity MyStruct({a: 1, b: 2})`,
- contract creation calls, e.g. `:::solidity new MyContract{value: 1}()`,
- dynamic array creations calls (including `:::solidity bytes` and `:::solidity string`), e.g. `:::solidity new uint[](10)` or `:::solidity new bytes(10)`,
- event emit calls, e.g. `:::solidity MyEvent(1, 2)` in `emit MyEvent(1, 2)`,
- error revert calls, e.g. `:::solidity MyError(1, 2)` in `revert MyError(1, 2)`,
- variable getter calls.
"""
_ast_node: SolcFunctionCall
_parent: weakref.ReferenceType[SolidityAbc] # TODO: make this more specific
_arguments: List[ExpressionAbc]
_expression: ExpressionAbc
_kind: FunctionCallKind
_names: List[str]
_try_call: bool
def __init__(
self, init: IrInitTuple, function_call: SolcFunctionCall, parent: SolidityAbc
):
super().__init__(init, function_call, parent)
self._kind = function_call.kind
self._names = list(function_call.names)
self._try_call = function_call.try_call
self._expression = ExpressionAbc.from_ast(init, function_call.expression, self)
self._arguments = [
ExpressionAbc.from_ast(init, argument, self)
for argument in function_call.arguments
]
def __iter__(self) -> Iterator[IrAbc]:
yield self
for argument in self._arguments:
yield from argument
yield from self._expression
@property
def parent(self) -> SolidityAbc:
return super().parent
@property
def children(self) -> Iterator[ExpressionAbc]:
"""
Yields:
Direct children of this node.
"""
yield self._expression
yield from self._arguments
@property
def kind(self) -> FunctionCallKind:
"""
[FunctionCallKind.FUNCTION_CALL][wake.ir.enums.FunctionCallKind.FUNCTION_CALL] is except for
function calls also used for:
- contract construction using [NewExpression][wake.ir.expressions.new_expression.NewExpression]
- new dynamic array creation using [NewExpression][wake.ir.expressions.new_expression.NewExpression]
- variable getter calls
Returns:
Kind of function call.
"""
return self._kind
@property
def names(self) -> Tuple[str, ...]:
"""
Is empty if the function call does not use named arguments.
!!! example
`("to", "value")` for the following function call:
```solidity
token.transfer({to: msg.sender, value: 100});
```
Returns:
Tuple of names of the named arguments in the order they appear in the source code.
"""
return tuple(self._names)
@property
def try_call(self) -> bool:
"""
!!! example
Is `True` for `this.foo()` in the following code:
```solidity
try this.foo() {} catch {}
```
Returns:
True if the function call is a try call, False otherwise.
"""
return self._try_call
@property
def expression(self) -> ExpressionAbc:
"""
Returns:
Expression that evaluates to the function being called.
"""
return self._expression
@property
def arguments(self) -> Tuple[ExpressionAbc, ...]:
"""
Returns:
Tuple of arguments of the function call in the order they appear in the source code.
"""
return tuple(self._arguments)
@property
def function_called(
self,
) -> Optional[
Union[
ContractDefinition, # contract construction
ArrayTypeName, # new dynamic array
ElementaryTypeName, # new string or bytes
EventDefinition,
ErrorDefinition,
FunctionDefinition,
GlobalSymbol,
StructDefinition,
VariableDeclaration,
]
]:
if self.kind == FunctionCallKind.TYPE_CONVERSION:
return None
node = self.expression
while True:
if isinstance(node, Identifier):
referenced_declaration = node.referenced_declaration
if isinstance(
referenced_declaration,
(
EventDefinition,
ErrorDefinition,
FunctionDefinition,
GlobalSymbol,
StructDefinition,
VariableDeclaration,
),
):
return referenced_declaration
else:
assert (
False
), f"Unexpected function call referenced declaration type: {referenced_declaration}"
elif isinstance(node, MemberAccess):
referenced_declaration = node.referenced_declaration
if isinstance(
referenced_declaration,
(
EventDefinition,
ErrorDefinition,
FunctionDefinition,
GlobalSymbol,
StructDefinition,
VariableDeclaration,
),
):
return referenced_declaration
else:
assert (
False
), f"Unexpected function call referenced declaration type: {referenced_declaration}"
elif isinstance(node, FunctionCall):
node = node.expression
while isinstance(
node, MemberAccess
) and node.referenced_declaration in {
GlobalSymbol.FUNCTION_VALUE,
GlobalSymbol.FUNCTION_GAS,
}:
node = node.expression
elif isinstance(node, FunctionCallOptions):
node = node.expression
elif isinstance(node, NewExpression):
type_name = node.type_name
if isinstance(type_name, (ArrayTypeName, ElementaryTypeName)):
return type_name
elif isinstance(type_name, UserDefinedTypeName):
assert isinstance(
type_name.referenced_declaration, ContractDefinition
)
return type_name.referenced_declaration
else:
assert (
False
), f"Unexpected function call child node: {node}\n{self.source}"
elif isinstance(node, TupleExpression):
if len(node.components) != 1:
assert (
False
), f"Unexpected function call child node: {node}\n{self.source}"
node = node.components[0]
else:
assert (
False
), f"Unexpected function call child node: {node}\n{self.source}"
@property
@weak_self_lru_cache(maxsize=2048)
def is_ref_to_state_variable(self) -> bool:
if self.kind == FunctionCallKind.TYPE_CONVERSION:
return self.expression.is_ref_to_state_variable
return False
@property
@cached_return_on_recursion(frozenset())
def modifies_state(
self,
) -> Set[Tuple[Union[ExpressionAbc, StatementAbc, YulAbc], ModifiesStateFlag]]:
ret = self.expression.modifies_state | reduce(
or_, (arg.modifies_state for arg in self.arguments), set()
)
if self.kind == FunctionCallKind.FUNCTION_CALL:
called_function = self.function_called
if called_function in {
GlobalSymbol.SELFDESTRUCT,
GlobalSymbol.SUICIDE,
}:
ret |= {(self, ModifiesStateFlag.SELFDESTRUCTS)}
elif called_function in {
GlobalSymbol.ADDRESS_TRANSFER,
GlobalSymbol.ADDRESS_SEND,
}:
ret |= {(self, ModifiesStateFlag.SENDS_ETHER)}
elif called_function == GlobalSymbol.ADDRESS_CALL:
ret |= {(self, ModifiesStateFlag.PERFORMS_CALL)}
elif called_function == GlobalSymbol.ADDRESS_DELEGATECALL:
ret |= {(self, ModifiesStateFlag.PERFORMS_DELEGATECALL)}
elif (
called_function
in {
GlobalSymbol.ARRAY_PUSH,
GlobalSymbol.ARRAY_POP,
GlobalSymbol.BYTES_PUSH,
GlobalSymbol.BYTES_POP,
}
and self.expression.is_ref_to_state_variable
):
ret |= {(self, ModifiesStateFlag.MODIFIES_STATE_VAR)}
elif called_function == GlobalSymbol.FUNCTION_VALUE:
ret |= {(self, ModifiesStateFlag.SENDS_ETHER)}
elif isinstance(called_function, FunctionDefinition):
if called_function.state_mutability in {
StateMutability.PURE,
StateMutability.VIEW,
}:
pass
elif called_function.body is not None:
ret |= called_function.body.modifies_state
for modifier in called_function.modifiers:
modifier_def = modifier.modifier_name.referenced_declaration
assert isinstance(modifier_def, ModifierDefinition)
if modifier_def.body is not None:
ret |= modifier_def.body.modifies_state
elif called_function.state_mutability == StateMutability.NONPAYABLE:
ret |= {
(
self,
ModifiesStateFlag.CALLS_UNIMPLEMENTED_NONPAYABLE_FUNCTION,
)
}
elif called_function.state_mutability == StateMutability.PAYABLE:
ret |= {
(self, ModifiesStateFlag.CALLS_UNIMPLEMENTED_PAYABLE_FUNCTION)
}
else:
assert False
return ret