A tool for exploring the Solidity abstrax syntrax tree as generated by the solc compiler.
You can install the latest release via pip
:
$ pip install py-solc-ast
Or clone the repo and use setuptools
:
$ python setup.py install
First, use py-solc-x to compile your contracts to the standard JSON output format.
>>> import json
>>> import solcx
>>> input_json = json.load(open('input.json'))
>>> output_json = solcx.compile_standard(input_json)
Next, import solcast
and initialize using from_standard_output_json
or from_standard_output
. This returns a list of SourceUnit
objects, which each represent the base AST node in a Solidity source file.
>>> import solcast
>>> nodes = solcast.from_standard_output(output_json)
>>> nodes
[<SourceUnit iterable 'contracts/Token.sol'>, <SourceUnit iterable 'contracts/SafeMath.sol'>]
You can also generate a single SourceUnit
directly from that source's AST:
>>> import solcast
>>> node = solcast.from_ast(output_json["sources"]["contracts/Token.sol"]["ast"])
>>> node
<SourceUnit iterable 'contracts/Token.sol'>
Each node has the following attributes:
>>> node
<FunctionDefinition iterable 'mul'>
>>> node.depth # Number of nodes between this node and the SourceUnit
2
>>> node.offset # Absolute source offsets as a (start, stop) tuple
(1693, 2151)
>>> node.contract_id # Contract ID as given by the standard compiler JSON
2
>>> node.fields # List of fields for this node
['baseNodeType', 'documentation', 'id', 'implemented', 'kind', 'modifiers', 'name', 'nodeType', 'nodes', 'parameters', 'returnParameters', 'scope', 'src', 'stateMutability', 'superFunction', 'visibility']
Fields mostly follow the expected AST grammar. One notable difference: Block
nodes are omitted and the body of each Block
is available within it's parent as the attribute nodes
. Nodes containing a body are iterable and can be accessed with list-like syntax. Additionally, any child node with a name
field is accessible using dict-like syntax.
The following additional fields are also available:
baseNodeType
field as defined in grammar.pyContractDefinition
nodes have dependencies
and libraries
fields that point to related ContractDefition
nodesSome Examples:
>>> source_node
<SourceUnit iterable 'contracts/math/SafeMath.sol'>
>>> source_node.keys()
['absolutePath', 'children', 'contract_id', 'depth', 'exportedSymbols', 'id', 'is_child_of', 'is_parent_of', 'keys', 'nodeType', 'nodes', 'offset', 'parent', 'parents', 'src']
>>> source_node.nodes
[<PragmaDirective object>, <ContractDefinition iterable 'SafeMath'>]
>>> source_node[1]
<ContractDefinition iterable 'SafeMath'>
>>> source_node['SafeMath']
<ContractDefinition iterable 'SafeMath'>
>>> source_node['SafeMath'].keys()
['baseContracts', 'children', 'contractDependencies', 'contractKind', 'contract_id', 'dependencies', 'depth', 'documentation', 'fullyImplemented', 'id', 'is_child_of', 'is_parent_of', 'keys', 'libraries', 'linearizedBaseContracts', 'name', 'nodeType', 'nodes', 'offset', 'parent', 'parents', 'scope', 'src']
>>> source_node['SafeMath'].nodes
[<FunctionDefinition iterable 'add'>, <FunctionDefinition iterable 'sub'>, <FunctionDefinition iterable 'mul'>, <FunctionDefinition iterable 'div'>, <FunctionDefinition iterable 'mod'>]
>>> source_node['SafeMath']['mul']
<FunctionDefinition iterable 'mul'>
>>> source_node['SafeMath']['mul']
[<IfStatement object>, <VariableDeclarationStatement object>, <FunctionCall object>, <Return object>]
The Node.children()
method is used to search and filter through child nodes of a given node. It takes any of the following keyword arguments:
depth
: Number of levels of children to traverse. 0
returns only this node.include_self
: Includes this node in the results.include_parents
: Includes nodes that match in the results, when they also have child nodes that match.include_children
: If True, as soon as a match is found it's children will not be included in the search.required_offset
: Only match nodes with a source offset that contains this offset.offset_limits
: Only match nodes when their source offset is contained inside this source offset.filters
: Dictionary of {'attribute': "value"}
that children must match. Can also be given as a list of dicts, children that match any of the dicts will be returned.exclude_filter
: Dictionary of {'attribute': "value"}
that children cannot match.>>> node = s['Token']['transfer']
>>> node.children(
include_children=False,
filters={'nodeType': "FunctionCall", "expression.name": "require"}
)
[<FunctionCall>]
Node.parent()
and Node.parents()
are used to travel back up the tree. They take the following arguments:
depth
: Depth limit. If given as a negative value, it will be subtracted from this object's depth.filters
: Dictionary of {'attribute': "value"}
that parents must match.Node.parent()
returns one result, Node.parents()
returns a list of matches.
>>> node.parents()
[<ContractDefinition iterable 'Token'>, <SourceUnit iterable object 'contracts/Token.sol'>]
To run the test suite:
$ tox
Comments, questions, criticisms and pull requests are welcomed! Feel free to open an issue if you encounter a problem or would like to suggest a new feature.
This project is licensed under the MIT license.