Skip to content
Merged
Show file tree
Hide file tree
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
16 changes: 16 additions & 0 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

### [0.1.23] - 2025-05-19
- Added `FunctionParameter` for input parameters of `FunctionType`s (required for named argument support)
- Changed `FunctionType` field `inputs` to `input_params`
- Added `block.blobbasefee` builtin variable
- Added `blobhash` builtin function
- Added new `require` function that takes an `ErrorType`
- Added `Type.is_user_error`
- Added `ErrorType` to make allow class error objects
- Added `UserDefinedErrorType` for solidity `error` 'function' types
- Added `CreateError` AST2 node for first class error initialisation
- Changed AST2 `RevertWithError` to take a `CreateError` object instead of a type and list of args
- Changed AST1 `EventParameter` and `ErrorParameter` to use `var_name` instead of `name` to be consistent with Parameter
- Changed AST1 `Parameter` to use `soltypes.Type` for `var_type` instead of `Ident`
- Fixed hashing issue with `NodeList`
- Fixed some issues with function type widening with
-
### [0.1.22] - 2024-08-29
- Fixed issues with external function call options(gas/value) being confused with name function arguments
- Renamed `solnodes.CallFunction.modifiers` to `special_call_options`
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def gen(self):

setup(
name='solidity-parser',
version='0.1.22',
version='0.1.23',

install_requires=[
"antlr4-python3-runtime==4.11.1",
Expand Down
185 changes: 138 additions & 47 deletions src/solidity_parser/ast/ast2builder.py

Large diffs are not rendered by default.

13 changes: 11 additions & 2 deletions src/solidity_parser/ast/nodebase.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,15 @@ def __init__(self, parent: T, seq=()):
super().__init__(seq)
self.parent = parent

# shims so the NodeDataclass decorator can generate a hash for this list
def get_children(self) -> Generator['Node', None, None]:
yield from list(self)

def get_all_children(self) -> Generator['Node', None, None]:
for direct_child in self.get_children():
yield direct_child
yield from direct_child.get_all_children()

def __str__(self):
return list.__str__(self)

Expand All @@ -143,12 +152,12 @@ def __delitem__(self, key):
self._set_dirty(key, None)

def __setslice__(self, i, j, sequence):
raise NotImplemented
raise NotImplementedError()

def __eq__(self, other):
if isinstance(other, list):
return super().__eq__(other)
raise NotImplemented
raise NotImplementedError(f"Can't compare NodeList to {other}")

def append(self, __object):
ret = super().append(__object)
Expand Down
19 changes: 5 additions & 14 deletions src/solidity_parser/ast/parsers/parsers060.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,24 +264,15 @@ def _mapping_type(parser, mapping_type: SolidityParser.MappingContext):
)


def params_to_types(params: solnodes.Parameter):
# TODO: add name + storage to FunctionType. Is it even necessary?
# old solnodes1.FunctionType had this but solnodes2.FunctionType didn't and now types.FunctionType doesn't either
# so this informtion isn't stored.
# Idea: create a new mixin that can be added to all the Type subclasses OR create a new NamedType that has the
# actual type as an attr and redirects Type calls to the attr. e.g.
# class NamedType(Type):
# name: Ident
# ttype: Type
# storage: ...
# def __getattr__(self, name):
# # checks...
# return getattr(self.ttype, name)
def params_to_types(params: list[solnodes.Parameter]):
return [p.var_type for p in params]

def input_params_to_types(params: list[solnodes.Parameter]):
return [soltypes.FunctionParameter(p.var_name, p.var_type) for p in params]

def _function_type_name(parser, function_type: SolidityParser.FunctionTypeNameContext):
return soltypes.FunctionType(
params_to_types(parser.make(function_type.parameterList(), default=[])),
input_params_to_types(parser.make(function_type.parameterList(), default=[])),
params_to_types(parser.make(function_type.returnParameters(), default=[])),
parser.make(function_type.modifierList(), default=[])
)
Expand Down
2 changes: 1 addition & 1 deletion src/solidity_parser/ast/parsers/parsers080.py
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ def _type_name(parser, type_name: SolidityParser.TypeNameContext):

def _function_type_name(parser, function_type: SolidityParser.FunctionTypeNameContext):
return soltypes.FunctionType(
parsers060.params_to_types(parser.make(function_type.arguments, default=[])),
parsers060.input_params_to_types(parser.make(function_type.arguments, default=[])),
parsers060.params_to_types(parser.make(function_type.returnParameters, default=[])),
parser.make_all_rules(function_type.visibility()) + parser.make_all_rules(function_type.stateMutability())
)
Expand Down
9 changes: 5 additions & 4 deletions src/solidity_parser/ast/solnodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ class VarDecl(Stmt):

@dataclass
class Parameter(AST1Node):
var_type: Ident
var_type: soltypes.Type
var_loc: Location
var_name: Ident

Expand Down Expand Up @@ -511,7 +511,7 @@ class UserValueType(SourceUnit, ContractPart):
@dataclass
class EventParameter(AST1Node):
var_type: soltypes.Type
name: Ident
var_name: Ident
is_indexed: bool


Expand All @@ -525,7 +525,7 @@ class EventDefinition(ContractPart):
@dataclass
class ErrorParameter(AST1Node):
var_type: soltypes.Type
name: Ident
var_name: Ident


@dataclass
Expand Down Expand Up @@ -601,5 +601,6 @@ def has_modifier_kind(node, *kinds: VisibilityModifierKind | MutabilityModifierK
Types: typing.TypeAlias = (soltypes.VariableLengthArrayType | soltypes.VoidType | soltypes.IntType
| soltypes.FunctionType | soltypes.ArrayType | soltypes.BytesType | soltypes.BoolType
| soltypes.AnyType | soltypes.MappingType | soltypes.UserType | soltypes.StringType
| soltypes.FixedLengthArrayType | soltypes.AddressType | soltypes.ByteType)
| soltypes.FixedLengthArrayType | soltypes.AddressType | soltypes.ByteType
| soltypes.ErrorType )

73 changes: 66 additions & 7 deletions src/solidity_parser/ast/solnodes2.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,35 @@ def type_of(self) -> soltypes.Type:
class Modifier(AST2Node, ABC):
pass

@nodebase.NodeDataclass
class UserDefinedErrorType(soltypes.Type):
# Use a ref because this type doesn't "own" the ErrorDefinition node instance
value: nodebase.Ref['ErrorDefinition'] = field(repr=False)

def is_user_error(self) -> bool:
return True

def is_user_type(self) -> bool:
return True

def __str__(self):
return f'error({self.value.x.name.text})'

def __repr__(self):
return self.__str__()

def code_str(self):
return self.value.x.name.text

def type_key(self, *args, **kwargs):
return self.value.x.name.text

def can_implicitly_cast_from(self, actual_type: 'Type') -> bool:
if super().can_implicitly_cast_from(actual_type):
return True
if actual_type.is_user_error():
return actual_type.value.x == self.value.x
return False

@nodebase.NodeDataclass
class ResolvedUserType(soltypes.Type):
Expand Down Expand Up @@ -1058,6 +1087,18 @@ def code_str(self):
return f'{self.ttype.code_str()}({", ".join(e.code_str() for e in self.args)})'


@nodebase.NodeDataclass
class CreateError(Expr):
ttype: UserDefinedErrorType
args: list[Expr]

def type_of(self) -> soltypes.Type:
return self.ttype

def code_str(self):
return f'{self.ttype.code_str()}({", ".join(e.code_str() for e in self.args)})'


@nodebase.NodeDataclass
class CreateAndDeployContract(Expr):
ttype: ResolvedUserType
Expand All @@ -1072,16 +1113,30 @@ def code_str(self):


def check_arg_types(args: list[Expr], f: FunctionDefinition) -> bool:
named_args = {a.name.text: a.expr.type_of() for a in args if isinstance(a, NamedArgument)}
# weird edge case: imagine a call x.abc({a:1,b:2}) where we have a "using statement" for x. The statement gets
# converted to a DirectCall to X.abc, with the args (x, {a:1}, {b:2}), i.e.a mix of 1 positional argument the rest
# named
possible_direct_call = len(args) > 0 and not isinstance(args[0], NamedArgument) and all([isinstance(a, NamedArgument) for a in args[1:]])

named_args = {
a.name.text: a.expr.type_of()
for a in (args[1:] if possible_direct_call else args) if isinstance(a, NamedArgument)
}

if len(named_args) > 0:
func_params = {p.var.name.text: p.var.ttype for p in f.inputs}
func_params = {p.var.name.text: p.var.ttype for p in (f.inputs[1:] if possible_direct_call else f.inputs)}

if set(func_params.keys()) != set(named_args.keys()):
return False

f_types, c_types = [], []

if possible_direct_call:
# want to match first arg by direct
f_types.append(f.inputs[0].var.ttype)
c_types.append(args[0].type_of())

# f_types[i] and c_types[i] are expected and provided types of an arg 'x'
for k, v in named_args.items():
f_types.append(func_params[k])
c_types.append(v)
Expand Down Expand Up @@ -1117,6 +1172,8 @@ def resolve_call(self) -> FunctionDefinition:
unit = self.ttype.value.x
matching_name_funcs = [p for p in unit.parts if isinstance(p, FunctionDefinition) and p.name.text == self.name.text]
matching_param_types = [f for f in matching_name_funcs if self.check_arg_types(f)]
if len(matching_name_funcs) > 0:
self.check_arg_types(matching_name_funcs[0])
assert len(matching_param_types) == 1
return matching_param_types[0]

Expand Down Expand Up @@ -1259,9 +1316,11 @@ class GetFunctionPointer(Expr):
def type_of(self) -> soltypes.Type:
def ts(params):
return [p.var.ttype for p in params]
def its(params: list[ErrorParameter | Parameter | EventParameter]):
return [soltypes.FunctionParameter(p.var.name, p.var.ttype) for p in params]
f = self.func.x
modifiers = f.modifiers if hasattr(f, 'modifiers') else []
return soltypes.FunctionType(ts(f.inputs), ts(f.outputs), modifiers)
return soltypes.FunctionType(its(f.inputs), ts(f.outputs), modifiers)

def code_str(self):
return f'fptr({self.func.x.parent.descriptor()}::{self.func.x.name.text})'
Expand All @@ -1283,11 +1342,10 @@ class Revert(Stmt):

@nodebase.NodeDataclass
class RevertWithError(Revert):
error: nodebase.Ref[ErrorDefinition]
args: list[Expr]
error: CreateError

def code_str(self):
return f'revert {self.error.x.name.text}({", ".join(e.code_str() for e in self.args)});'
return f'revert {self.error.code_str()};'


@nodebase.NodeDataclass
Expand Down Expand Up @@ -1352,4 +1410,5 @@ class UnprocessedCode(Stmt):
| soltypes.AnyType | soltypes.MappingType | soltypes.StringType | soltypes.AddressType
| soltypes.FixedLengthArrayType | soltypes.ByteType | soltypes.MetaTypeType
| soltypes.TupleType | soltypes.PreciseIntType | soltypes.PreciseIntType
| soltypes.BuiltinType | ResolvedUserType | SuperType | soltypes.FloatType)
| soltypes.BuiltinType | ResolvedUserType | SuperType | soltypes.FloatType
| soltypes.ErrorType | UserDefinedErrorType)
14 changes: 11 additions & 3 deletions src/solidity_parser/ast/symtab.py
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,9 @@ def __init__(self, parser_version: version_util.Version):
block_object.add(BuiltinValue('difficulty', uint()))
block_object.add(BuiltinValue('gaslimit', uint()))
block_object.add(BuiltinValue('number', uint()))
# Add blobbasefee only for Solidity 0.8.24 and above
if parser_version and parser_version.is_enforced_in(version_util.Version(0, 8, 24)):
block_object.add(BuiltinValue('blobbasefee', uint()))
block_object.add(now_symbol := BuiltinValue('timestamp', uint())) # now was used pre solidity 0.7 for block.timestamp
self.add(block_object)

Expand Down Expand Up @@ -768,11 +771,16 @@ def address_object(payable):

self.add(BuiltinFunction('gasleft', [], [uint()]))

self.add(BuiltinFunction('blobhash', [uint()], [bytes32()]))
# Add blobhash only for Solidity 0.8.24 and above
if parser_version and parser_version.is_enforced_in(version_util.Version(0, 8, 24)):
self.add(BuiltinFunction('blobhash', [uint()], [bytes32()]))
self.add(BuiltinFunction('blockhash', [uint()], [bytes32()]))

self.add(BuiltinFunction('require', [soltypes.BoolType(), soltypes.StringType()], []))
self.add(BuiltinFunction('require', [soltypes.BoolType()], []))
# Add require(bool, error) overload for Solidity 0.8.26 and above
if parser_version and parser_version.is_enforced_in(version_util.Version(0, 8, 26)):
self.add(BuiltinFunction('require', [soltypes.BoolType(), soltypes.ErrorType()], []))

self.add(BuiltinFunction('assert', [soltypes.BoolType()], []))

Expand Down Expand Up @@ -1642,10 +1650,10 @@ def make_symbols_for_node(self, node, context: Context, build_skeletons: bool, v
else:
return self.make_symbol(node, name=node.var_name.text)
elif isinstance(node, solnodes.EventParameter):
if node.name is None:
if node.var_name is None:
name = f'<unnamed_paramter:{visit_index}'
else:
name = node.name.text
name = node.var_name.text
return self.make_symbol(node, name=name)
elif isinstance(node, solnodes.VarDecl):
return None # TODO: this is a sentinel
Expand Down
Loading