Skip to content

Commit

Permalink
Adding support for solc.compile
Browse files Browse the repository at this point in the history
  • Loading branch information
0x19 committed Aug 17, 2023
1 parent 06ee446 commit bb25c8d
Show file tree
Hide file tree
Showing 6 changed files with 611 additions and 9 deletions.
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,15 @@ Makes no sense to rewrite all of that hard work just to be written in Go. Theref

## Features

- **Protocol Buffers**: SolGo uses [Protocol Buffers](https://github.com/txpull/protos) to provide a structured format for the data, enabling more effective analysis and a way to build a common interface for other tools. Supported languages: Go and Javascript. In the future, will be adding Rust and Python.
- **Abstract Syntax Tree (AST) Generation:** SolGo includes an builder that constructs an Abstract Syntax Tree for Solidity code.
- **Intermediate Representation (IR) Generation**: SolGo can generate an Intermediate Representation (IR) from the AST. The IR provides a language-agnostic representation of the contract, capturing key elements such as functions, state variables, events, and more. This enables more advanced analysis and manipulation of the contract.
- **Application Binary Interface (ABI) Generation:** SolGo includes an builder that can parse contract definitions to generate ABI for group of contracts or each contract individually.
- **Opcode Decompilation and Execution Trees:** The opcode package facilitates the decompilation of bytecode into opcodes, and offers tools for constructing and visualizing opcode execution trees. This provides a comprehensive view of opcode sequences in smart contracts, aiding in deeper analysis and understanding.
- **Syntax Error Handling**: SolGo includes listener which collects syntax errors encountered during parsing, providing detailed error information including line, column, message, severity, and context.
- **Automatic Source Detection**: SolGo automatically loads and integrates Solidity contracts from well-known libraries such as [OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts).
- **Ethereum Improvement Proposals (EIP) Registry**: A package designed to provide a structured representation of Ethereum Improvement Proposals (EIPs) and Ethereum Request for Comments (ERCs). It simplifies the interaction with various contract standards by including functions, events, and a registry mechanism crafted for efficient management.
- **Security Audits**: SolGo now includes an Audit package that is specifically designed to detect security vulnerabilities in Solidity smart contracts. This package uses [Slither](https://github.com/crytic/slither) advanced algorithms and patterns to scan and identify potential threats and weaknesses in the codebase, ensuring that contracts are secure and robust against malicious attacks.
- **Protocol Buffers**: Utilizing [Protocol Buffers](https://github.com/txpull/protos), SolGo offers a structured data format, paving the way for enhanced analysis and facilitating a unified interface for diverse tools. Currently, it supports Go and Javascript, with plans to incorporate Rust and Python in upcoming versions.
- **Abstract Syntax Tree (AST) Generation:** Package `ast` is equipped with a dedicated builder that crafts an Abstract Syntax Tree (AST) tailored for Solidity code.
- **Intermediate Representation (IR) Generation**: From the AST, SolGo is adept at generating an Intermediate Representation (IR). `ir` package serves as a language-neutral depiction of the contract, encapsulating pivotal components like functions, state variables, and events, thus broadening the scope for intricate analysis and contract manipulation.
- **Application Binary Interface (ABI) Generation:** SolGo's in-built `abi` package can interpret contract definitions, enabling the generation of ABI for a collective group of contracts or individual ones.
- **Opcode Tools**: The `opcode` package in SolGo demystifies bytecode by decompiling it into opcodes. Additionally, it provides tools for the creation and visualization of opcode execution trees, granting a holistic perspective of opcode sequences in smart contracts.
- **Library Integration**: SolGo is programmed to autonomously source and assimilate Solidity contracts from renowned libraries, notably [OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts). This feature enables users to seamlessly import and utilize contracts from these libraries without the need for manual integration.
- **EIP & ERC Registry**: SolGo introduces a package `eip` exclusively for Ethereum Improvement Proposals (EIPs) and Ethereum Request for Comments (ERCs). This package streamlines interactions with diverse contract standards by encompassing functions, events, and a registry system optimized for proficient management.
- **Solidity Compiler Detection & Compilation:** SolGo intelligently identifies the Solidity version employed for contract compilation. This not only streamlines the process of determining the compiler version but also equips users with the capability to seamlessly compile contracts.
- **Security Audit Package**: Prioritizing security, SolGo has incorporated an `audit` package. This specialized package leverages [Slither](https://github.com/crytic/slither)'s sophisticated algorithms to scrutinize and pinpoint potential vulnerabilities in Solidity smart contracts, ensuring robust protection against adversarial threats.

## Contributing

Expand Down
23 changes: 23 additions & 0 deletions data/tests/audits/CorruptedContract.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract CorruptedContract {
mapping(address => uint256) public balances;

function deposit() external payable {
require(msg.value > 0, "Deposit amount should be greater than 0");
balances[msg.sender] += msg.value;
bug();
}

function withdraw() external {
uint256 amount = balances[msg.sender];
require(amount > 0, "Insufficient balance");

// This call can be exploited for reentrancy
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");

balances[msg.sender] = 0;
}
}
250 changes: 250 additions & 0 deletions solc/compiler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
package solc

import (
"bytes"
"context"
"encoding/json"
"fmt"
"os/exec"
"strings"

"github.com/txpull/solgo"
)

// Compiler represents a Solidity compiler instance.
type Compiler struct {
ctx context.Context // The context for the compiler.
solc *Select // The solc selector.
sources *solgo.Sources // The Solidity sources to compile.
config *Config // The configuration for the compiler.
compilerVersion string // The version of the compiler to use.
}

// NewCompiler creates a new Compiler instance with the given context, configuration, and sources.
func NewCompiler(ctx context.Context, config *Config, sources *solgo.Sources) (*Compiler, error) {
solc, err := NewSelect()
if err != nil {
return nil, err
}

// Ensure that the sources are prepared for future consumption in case they are not already.
if !sources.ArePrepared() {
if err := sources.Prepare(); err != nil {
return nil, err
}
}

return &Compiler{
ctx: ctx,
solc: solc,
sources: sources,
config: config,
}, nil
}

// SetCompilerVersion sets the version of the solc compiler to use.
func (v *Compiler) SetCompilerVersion(version string) {
v.compilerVersion = version
}

// GetCompilerVersion returns the currently set version of the solc compiler.
func (v *Compiler) GetCompilerVersion() string {
return v.compilerVersion
}

// GetContext returns the context associated with the compiler.
func (v *Compiler) GetContext() context.Context {
return v.ctx
}

// GetSources returns the Solidity sources associated with the compiler.
func (v *Compiler) GetSources() *solgo.Sources {
return v.sources
}

// GetSolc returns the solc selector associated with the compiler.
func (v *Compiler) GetSolc() *Select {
return v.solc
}

// Compile compiles the Solidity sources using the configured compiler version and arguments.
func (v *Compiler) Compile() (*CompilerResults, error) {
combinedSource := v.sources.GetCombinedSource()

if v.compilerVersion == "" {
sv, err := v.sources.GetSolidityVersion()

Check failure on line 75 in solc/compiler.go

View workflow job for this annotation

GitHub Actions / test (1.19)

v.sources.GetSolidityVersion undefined (type *solgo.Sources has no field or method GetSolidityVersion)

Check failure on line 75 in solc/compiler.go

View workflow job for this annotation

GitHub Actions / tests

v.sources.GetSolidityVersion undefined (type *solgo.Sources has no field or method GetSolidityVersion)
if err != nil {
return nil, err
}
v.compilerVersion = sv
}

if v.compilerVersion == "" {
return nil, fmt.Errorf("no compiler version specified")
}

if _, _, _, err := v.solc.Use(v.compilerVersion); err != nil {
return nil, err
}

args := []string{}
sanitizedArgs, err := v.config.SanitizeArguments(v.config.Arguments)
if err != nil {
return nil, err
}
args = append(args, sanitizedArgs...)

if err := v.config.Validate(); err != nil {
return nil, err
}

// Prepare the command
cmd := exec.Command("solc", args...)

// Set the combined source as input
cmd.Stdin = strings.NewReader(combinedSource)

// Capture the output
var out bytes.Buffer
cmd.Stdout = &out
var stderr bytes.Buffer
cmd.Stderr = &stderr

if err := cmd.Run(); err != nil {
var errors []string
var warnings []string

// Parsing the error message to extract line and column information.
errorMessage := stderr.String()
if strings.Contains(errorMessage, "Error:") {
errors = append(errors, errorMessage)
} else if strings.HasPrefix(errorMessage, "Warning:") {
warnings = append(warnings, errorMessage)
}

// Construct the CompilerResults structure with errors and warnings.
results := &CompilerResults{
RequestedVersion: v.compilerVersion,
Errors: errors,
Warnings: warnings,
}
return results, err
}

// Parse the output
var compilationOutput struct {
Contracts map[string]struct {
Bin string `json:"bin"`
Abi interface{} `json:"abi"`
} `json:"contracts"`
Errors []string `json:"errors"`
Version string `json:"version"`
}

err = json.Unmarshal(out.Bytes(), &compilationOutput)
if err != nil {
return nil, err
}

// Extract the first contract's results (assuming one contract for simplicity)
var firstContractKey string
for key := range compilationOutput.Contracts {
firstContractKey = key
break
}

if firstContractKey == "" {
return nil, fmt.Errorf("no contracts found")
}

// Separate errors and warnings
var errors, warnings []string
for _, msg := range compilationOutput.Errors {
if strings.Contains(msg, "Warning:") {
warnings = append(warnings, msg)
} else {
errors = append(errors, msg)
}
}

abi, err := json.Marshal(compilationOutput.Contracts[firstContractKey].Abi)
if err != nil {
return nil, err
}

results := &CompilerResults{
RequestedVersion: v.compilerVersion,
CompilerVersion: compilationOutput.Version,
Bytecode: compilationOutput.Contracts[firstContractKey].Bin,
ABI: string(abi),
ContractName: strings.ReplaceAll(firstContractKey, "<stdin>:", ""),
Errors: errors,
Warnings: warnings,
}

return results, nil
}

// CompilerResults represents the results of a solc compilation.
type CompilerResults struct {
RequestedVersion string `json:"requested_version"`
CompilerVersion string `json:"compiler_version"`
Bytecode string `json:"bytecode"`
ABI string `json:"abi"`
ContractName string `json:"contract_name"`
Errors []string `json:"errors"`
Warnings []string `json:"warnings"`
}

// HasErrors returns true if there are compilation errors.
func (v *CompilerResults) HasErrors() bool {
if v == nil {
return false
}

return len(v.Errors) > 0
}

// HasWarnings returns true if there are compilation warnings.
func (v *CompilerResults) HasWarnings() bool {
if v == nil {
return false
}

return len(v.Warnings) > 0
}

// GetErrors returns the compilation errors.
func (v *CompilerResults) GetErrors() []string {
return v.Errors
}

// GetWarnings returns the compilation warnings.
func (v *CompilerResults) GetWarnings() []string {
return v.Warnings
}

// GetABI returns the compiled contract's ABI (Application Binary Interface) in JSON format.
func (v *CompilerResults) GetABI() string {
return v.ABI
}

// GetBytecode returns the compiled contract's bytecode.
func (v *CompilerResults) GetBytecode() string {
return v.Bytecode
}

// GetContractName returns the name of the compiled contract.
func (v *CompilerResults) GetContractName() string {
return v.ContractName
}

// GetRequestedVersion returns the requested compiler version used for compilation.
func (v *CompilerResults) GetRequestedVersion() string {
return v.RequestedVersion
}

// GetCompilerVersion returns the actual compiler version used for compilation.
func (v *CompilerResults) GetCompilerVersion() string {
return v.CompilerVersion
}
Loading

0 comments on commit bb25c8d

Please sign in to comment.