A tool for exploring the abstract syntax tree generated by solc.


Keywords
ethereum, solidity, solc, ast, py-solc, web3py
License
MIT
Install
pip install py-solc-ast==1.2.7

Documentation

solc-ast

Pypi Status Build Status Coverage Status

A tool for exploring the Solidity abstrax syntrax tree as generated by the solc compiler.

Installation

You can install the latest release via pip:

$ pip install py-solc-ast

Or clone the repo and use setuptools:

$ python setup.py install

Usage

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'>

Interacting with Nodes

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:

  • Most nodes have a baseNodeType field as defined in grammar.py
  • ContractDefinition nodes have dependencies and libraries fields that point to related ContractDefition nodes

Some 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>]

Exploring the Tree

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'>]

Tests

To run the test suite:

$ tox

Development

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.

License

This project is licensed under the MIT license.