solgo

package module
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 2, 2023 License: Apache-2.0 Imports: 6 Imported by: 0

README

Build Status Coverage Status Go Report Card License PkgGoDev

Solidity Parser in Golang

SolGo contains a Solidity parser written in Golang, using Antlr and AntlrGo for grammar parsing. The aim of this project is to provide a tool to parse Solidity source code into a structured format, enabling further analysis.

The parser is generated from a Solidity grammar file using Antlr, producing a lexer, parser, and listener using AntlrGo. This allows for the syntactic analysis of Solidity code, transforming it into a parse tree that can be traversed and manipulated.

This project is ideal for developers working with Solidity smart contracts who wish to leverage the power and efficiency of Golang for their analysis tools.

Note: This project is still in development and is not yet ready for use in production.

Why?

In my projects, I have a strong preference for using Golang or Rust over Javascript. I found myself needing to parse Solidity code and conduct some analysis on it. During my research, I discovered several projects that aimed to do this. However, they were either incomplete, not maintained, or not written in Go at all. This led me to the decision to create this project. The goal is to provide a Solidity parser in Golang that is as "complete as possible" and well-maintained. I'm excited to see how this project will develop and evolve.

This project will be integrated within unpack. I've found that for many deployed contracts, the source code is available, but the ABIs are not. This project will help bridge that gap.

Solidity Language Grammar

Latest Solidity language grammar higher overview and detailed description can be found here.

ANTLR Grammar

We are using grammar files that are maintained by the Solidity team. Link to the grammar files can be found here.

ANTLR Go

We are using the ANTLR4 Go runtime library to generate the parser. Repository can be found here.

Features

  • Syntactic Analysis: SolGo transforms Solidity code into a parse tree that can be traversed and manipulated, providing a detailed syntactic analysis of the code.
  • Listener Registration: SolGo allows for the registration of custom listeners that can be invoked as the parser walks the parse tree, enabling custom handling of parse events.
  • Error Handling: SolGo includes a SyntaxErrorListener which collects syntax errors encountered during parsing, providing detailed error information including line, column, message, severity, and context.
  • Contextual Parsing: SolGo provides a ContextualSolidityParser that maintains a stack of contexts, allowing for context-specific parsing rules.
  • ABI Generation and Interaction: SolGo can parse contract definitions to generate Ethereum contract ABIs (Application Binary Interfaces), providing a structured representation of the contract's functions, events, and variables. It also includes functionality for normalizing type names and handling complex types like mappings, enabling more effective interaction with contracts on the Ethereum network.

Getting Started

To use SolGo, you will need to have Golang installed on your machine. You can then import SolGo into your Go programs like this:

import "github.com/txpull/solgo"

Logger setup

SolGo uses zap logger. To setup logger you can use following code:

import (
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

// ....

config := zap.NewDevelopmentConfig()
config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
logger, err := config.Build()
if err != nil {
	panic(err)
}

zap.ReplaceGlobals(logger)

I've deliberately not to pass logger as a reference to each struct, because I am lazy and in my eyes, it's not a good practice. Instead, I'm using zap.L() function to get logger instance in each struct.

Usage

Parse Solidity Code and Retrieve Contract Information
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"

	"github.com/txpull/solgo"
	"github.com/txpull/solgo/contracts"
)

var contract = `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// Some additional comments that can be extracted

import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";

contract MyToken is Initializable, ERC20Upgradeable, AccessControlUpgradeable, PausableUpgradeable {
    using SafeERC20Upgradeable for IERC20Upgradeable;
}
`

func main() {
	parser, err := solgo.New(context.Background(), strings.NewReader(contract))
	if err != nil {
		panic(err)
	}

	// Register the contract information listener
	contractListener := contracts.NewContractListener(parser.GetParser())
	if err := parser.RegisterListener(solgo.ListenerContractInfo, contractListener); err != nil {
		panic(err)
	}

	if errs := parser.Parse(); len(errs) > 0 {
		for _, err := range errs {
			fmt.Println(err)
		}
		return
	}

	jsonResponse, _ := json.MarshalIndent(contractListener.ToStruct(), "", "  ")
	fmt.Println(string(jsonResponse))
}

Response from the example above:

{
  "comments": [
    "// Some additional comments that can be extracted"
  ],
  "license": "MIT",
  "pragmas": [
    "solidity ^0.8.0"
  ],
  "imports": [
    "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol",
    "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol",
    "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol",
    "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol",
    "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"
  ],
  "name": "MyToken",
  "implements": [
    "Initializable",
    "ERC20Upgradeable",
    "AccessControlUpgradeable",
    "PausableUpgradeable",
    "SafeERC20Upgradeable"
  ]
}
Parse Solidity Code and Retrieve ABI information
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"

	"github.com/txpull/solgo"
	"github.com/txpull/solgo/abis"
)

var contract = `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract MyContract {
	uint256 public myUint;
	address public myAddress;
	string public myString = "Hello, World!";
	bytes32 public myBytes32 = "Hello, World!";
	bool public myBool = true;
	uint256[] public myUintArr = [1,2,3];
}`

func main() {
	parser, err := solgo.New(context.Background(), strings.NewReader(contract))
	if err != nil {
		panic(err)
	}

	// Register the abi listener
	abiListener := abis.NewAbiListener()
	if err := parser.RegisterListener(solgo.ListenerAbi, abiListener); err != nil {
		panic(err)
	}

	if errs := parser.Parse(); len(errs) > 0 {
		for _, err := range errs {
			fmt.Println(err)
		}
		return
	}

	// Grab the abi parser from the listener
	abiParser := abiListener.GetParser()

	// Get JSON representation of the ABI
	_, err = abiParser.ToJSON()
	if err != nil {
		panic(err)
	}

	// Get go-ethereum ABI representation
	_, err = abiParser.ToABI()
	if err != nil {
		panic(err)
	}

	jsonResponse, _ := json.MarshalIndent(abiParser.ToStruct(), "", "  ")
	fmt.Println(string(jsonResponse))
}

Development Setup

To set up the development environment for this project, follow these steps:

  1. Go 1.19+: Make sure you have Go version 1.19 or higher installed. You can find installation instructions here.

  2. Java: You need Java version 11 or higher installed to generate the parser. You can download Java from here. After installing Java, make sure to set the JAVA_HOME environment variable to the location of your Java installation.

  3. ANTLR4: The ANTLR4 is already included in this repository as .jar, which can be found in the antlr directory. You don't need to build it separately. The current version used is 4.13.0.

Once you have completed these steps, you can start developing and testing your changes. To run the tests, use the command make test from the root directory of the repository.

The parser files are already generated and can be found in the parser directory. If you want to regenerate the parser, use the command make generate from the root directory of the repository. This will regenerate the parser files and place them in the parser directory.

Contributing

Contributions to SolGo are always welcome! If you have a feature request, bug report, or proposal for improvement, please open an issue. If you wish to contribute code, please fork the repository, make your changes, and submit a pull request.

License

SolGo is licensed under the Apache 2.0. See LICENSE for the full license text.

Acknowledgements

We would like to express our gratitude to the Solidity team for maintaining the Solidity grammar files, and to the Antlr and AntlrGo team for providing the powerful Antlr tool that makes this project possible.

Documentation

Overview

Package solgo provides a suite of tools for parsing, analyzing, and interacting with Solidity contracts. It includes a contextual parser that maintains a stack of contexts as it parses a contract, allowing it to keep track of the current context (e.g., within a contract definition, function definition, etc.). It also includes a contract listener that extracts information about contracts as they are parsed, including the contract name, implemented interfaces, imported contracts, pragmas, and comments. Additionally, it includes a syntax error listener that listens for syntax errors in contracts and categorizes them by severity. The package also provides functionality for generating and working with Ethereum contract ABIs (Application Binary Interfaces). This includes parsing contract definitions to extract ABI information, normalizing type names, and handling complex types like mappings. These tools can be used together to provide a comprehensive interface for working with Solidity contracts, making it easier to understand their structure, identify potential issues, and interact with them on the Ethereum network.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ContextualSolidityParser

type ContextualSolidityParser struct {
	*parser.SolidityParser // SolidityParser is the base parser from the Solidity parser.
	// contains filtered or unexported fields
}

ContextualSolidityParser is a wrapper around the SolidityParser that maintains a stack of contexts. This allows the parser to keep track of the current context (e.g., within a contract definition, function definition, etc.) as it parses a Solidity contract.

func (*ContextualSolidityParser) ContractDefinition

ContractDefinition is called when the parser enters a contract definition. It pushes "ContractDefinition" onto the context stack, calls the original ContractDefinition method, and then pops the context from the stack before returning.

func (*ContextualSolidityParser) CurrentContext

func (p *ContextualSolidityParser) CurrentContext() string

CurrentContext returns the current context, i.e., the context at the top of the stack. If the stack is empty, it returns an empty string.

func (*ContextualSolidityParser) PopContext

func (p *ContextualSolidityParser) PopContext()

PopContext pops the current context from the context stack. This should be called when the parser exits a rule.

func (*ContextualSolidityParser) PushContext

func (p *ContextualSolidityParser) PushContext(context string)

PushContext pushes a new context onto the context stack. This should be called when the parser enters a new rule.

type ListenerName

type ListenerName string

ListenerName represents the name of a listener.

const (
	ListenerAbi          ListenerName = "abi"
	ListenerContractInfo ListenerName = "contract_info"
	ListenerAst          ListenerName = "ast"
)

Predefined listener names.

type SeverityLevel

type SeverityLevel int

SeverityLevel represents the severity of a syntax error.

const (
	// SeverityHigh represents a high severity error.
	SeverityHigh SeverityLevel = iota
	// SeverityMedium represents a medium severity error.
	SeverityMedium
	// SeverityLow represents a low severity error.
	SeverityLow
)

type SolGo

type SolGo struct {
	// contains filtered or unexported fields
}

SolGo is a struct that encapsulates the functionality for parsing and analyzing Solidity contracts.

func New

func New(ctx context.Context, input io.Reader) (*SolGo, error)

New creates a new instance of SolGo. It takes a context and an io.Reader from which the Solidity contract is read. It initializes an input stream, lexer, token stream, and parser, and sets up error listeners.

func (*SolGo) GetAllListeners

func (s *SolGo) GetAllListeners() map[ListenerName]antlr.ParseTreeListener

func (*SolGo) GetInput

func (s *SolGo) GetInput() io.Reader

GetInput returns the raw input reader from which the Solidity contract is read.

func (*SolGo) GetInputStream

func (s *SolGo) GetInputStream() *antlr.InputStream

GetInputStream returns the ANTLR input stream which is used by the lexer.

func (*SolGo) GetLexer

func (s *SolGo) GetLexer() *parser.SolidityLexer

GetLexer returns the Solidity lexer which tokenizes the input stream.

func (*SolGo) GetListener

func (s *SolGo) GetListener(name ListenerName) (antlr.ParseTreeListener, error)

func (*SolGo) GetParser

func (s *SolGo) GetParser() *parser.SolidityParser

GetParser returns the Solidity parser which parses the token stream.

func (*SolGo) GetTokenStream

func (s *SolGo) GetTokenStream() *antlr.CommonTokenStream

GetTokenStream returns the stream of tokens produced by the lexer.

func (*SolGo) GetTree

func (s *SolGo) GetTree() antlr.ParseTree

GetTree returns the root of the parse tree that results from parsing the Solidity contract.

func (*SolGo) IsListenerRegistered

func (s *SolGo) IsListenerRegistered(name ListenerName) bool

func (*SolGo) Parse

func (s *SolGo) Parse() []SyntaxError

Parse initiates the parsing process. It walks the parse tree with all registered listeners and returns any syntax errors that were encountered during parsing.

func (*SolGo) RegisterListener

func (s *SolGo) RegisterListener(name ListenerName, listener antlr.ParseTreeListener) error

type SyntaxError

type SyntaxError struct {
	// Line is the line number where the error occurred.
	Line int
	// Column is the column number where the error occurred.
	Column int
	// Message is the error message.
	Message string
	// Severity is the severity level of the error.
	Severity SeverityLevel
	// Context is the context in which the error occurred.
	Context string
}

SyntaxError represents a syntax error in a Solidity contract.

type SyntaxErrorListener

type SyntaxErrorListener struct {
	// DefaultErrorListener is the base error listener from the ANTLR4 parser.
	*antlr.DefaultErrorListener
	// Errors is a slice of SyntaxErrors.
	Errors []SyntaxError
}

SyntaxErrorListener is a listener for syntax errors in Solidity contracts. It extends the DefaultErrorListener from the ANTLR4 parser.

func NewSyntaxErrorListener

func NewSyntaxErrorListener() *SyntaxErrorListener

NewSyntaxErrorListener creates a new SyntaxErrorListener.

func (*SyntaxErrorListener) SyntaxError

func (s *SyntaxErrorListener) SyntaxError(recognizer antlr.Recognizer, offendingSymbol interface{}, line, column int, msg string, e antlr.RecognitionException)

SyntaxError is called when a syntax error is encountered. It creates a SyntaxError with the line number, column number, error message, severity level, and context, and adds it to the Errors slice.

Directories

Path Synopsis
Package abis provides functionality for parsing and manipulating Solidity contract ABIs (Application Binary Interfaces).
Package abis provides functionality for parsing and manipulating Solidity contract ABIs (Application Binary Interfaces).
Package contracts provides a set of utilities and listeners for working with Solidity contracts.
Package contracts provides a set of utilities and listeners for working with Solidity contracts.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL