Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to compile from json mappings #6

Merged
merged 3 commits into from
Aug 28, 2023
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
150 changes: 122 additions & 28 deletions compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"fmt"
"os/exec"
"strings"

"go.uber.org/zap"
)

// Compiler represents a Solidity compiler instance.
Expand All @@ -18,6 +20,7 @@ type Compiler struct {
}

// NewCompiler creates a new Compiler instance with the given context, configuration, and source.
// It returns an error if the provided configuration, solc instance, or source is invalid.
func NewCompiler(ctx context.Context, solc *Solc, config *CompilerConfig, source string) (*Compiler, error) {
if config == nil {
return nil, fmt.Errorf("config must be provided to create new compiler")
Expand All @@ -31,8 +34,10 @@ func NewCompiler(ctx context.Context, solc *Solc, config *CompilerConfig, source
return nil, fmt.Errorf("source code must be provided to create new compiler")
}

if err := config.Validate(); err != nil {
return nil, fmt.Errorf("invalid compiler configuration: %w", err)
if config.JsonConfig == nil {
if err := config.Validate(); err != nil {
return nil, fmt.Errorf("invalid compiler configuration: %w", err)
}
}

return &Compiler{
Expand Down Expand Up @@ -64,7 +69,8 @@ func (v *Compiler) GetSources() string {
}

// Compile compiles the Solidity sources using the configured compiler version and arguments.
func (v *Compiler) Compile() (*CompilerResults, error) {
// It returns the compilation results or an error if the compilation fails.
func (v *Compiler) Compile() ([]*CompilerResults, error) {
compilerVersion := v.GetCompilerVersion()
if compilerVersion == "" {
return nil, fmt.Errorf("no compiler version specified")
Expand All @@ -82,8 +88,10 @@ func (v *Compiler) Compile() (*CompilerResults, error) {
}
args = append(args, sanitizedArgs...)

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

// #nosec G204
Expand All @@ -100,6 +108,11 @@ func (v *Compiler) Compile() (*CompilerResults, error) {
cmd.Stderr = &stderr

if err := cmd.Run(); err != nil {
zap.L().Error(
"Failed to compile Solidity sources",
zap.String("version", compilerVersion),
zap.String("stderr", stderr.String()),
)
var errors []string
var warnings []string

Expand All @@ -117,9 +130,20 @@ func (v *Compiler) Compile() (*CompilerResults, error) {
Errors: errors,
Warnings: warnings,
}
return results, err
return []*CompilerResults{results}, err
}

if v.config.JsonConfig != nil {
return v.resultsFromJson(compilerVersion, out)
}

return v.resultsFromSimple(compilerVersion, out)
}

// resultsFromSimple parses the output from the solc compiler when the output is in a simple format.
// It extracts the compilation details such as bytecode, ABI, and any errors or warnings.
// The method returns a slice of CompilerResults or an error if the output cannot be parsed.
func (v *Compiler) resultsFromSimple(compilerVersion string, out bytes.Buffer) ([]*CompilerResults, error) {
// Parse the output
var compilationOutput struct {
Contracts map[string]struct {
Expand All @@ -130,20 +154,74 @@ func (v *Compiler) Compile() (*CompilerResults, error) {
Version string `json:"version"`
}

err = json.Unmarshal(out.Bytes(), &compilationOutput)
if err != nil {
if err := json.Unmarshal(out.Bytes(), &compilationOutput); 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
// 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)
}
}

if firstContractKey == "" {
return nil, fmt.Errorf("no contracts found")
var results []*CompilerResults

for key, output := range compilationOutput.Contracts {
isEntryContract := false
if v.config.GetEntrySourceName() != "" && key == "<stdin>:"+v.config.GetEntrySourceName() {
isEntryContract = true
}

abi, err := json.Marshal(output.Abi)
if err != nil {
return nil, err
}

results = append(results, &CompilerResults{
IsEntryContract: isEntryContract,
RequestedVersion: compilerVersion,
CompilerVersion: compilationOutput.Version,
Bytecode: output.Bin,
ABI: string(abi),
ContractName: strings.TrimLeft(key, "<stdin>:"),
Errors: errors,
Warnings: warnings,
})
}

return results, nil
}

// resultsFromJson parses the output from the solc compiler when the output is in a JSON format.
// It extracts detailed compilation information including bytecode, ABI, opcodes, and metadata.
// Additionally, it separates any errors and warnings from the compilation process.
// The method returns a slice of CompilerResults or an error if the output cannot be parsed.
func (v *Compiler) resultsFromJson(compilerVersion string, out bytes.Buffer) ([]*CompilerResults, error) {
// Parse the output
var compilationOutput struct {
Contracts map[string]map[string]struct {
Abi interface{} `json:"abi"`
Evm struct {
Bytecode struct {
GeneratedSources []interface{} `json:"generatedSources"`
LinkReferences map[string]interface{} `json:"linkReferences"`
Object string `json:"object"`
Opcodes string `json:"opcodes"`
SourceMap string `json:"sourceMap"`
} `json:"bytecode"`
} `json:"evm"`
Metadata string `json:"metadata"`
} `json:"contracts"`
Errors []string `json:"errors"`
Version string `json:"version"`
}

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

// Separate errors and warnings
Expand All @@ -156,31 +234,47 @@ func (v *Compiler) Compile() (*CompilerResults, error) {
}
}

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

results := &CompilerResults{
RequestedVersion: compilerVersion,
CompilerVersion: compilationOutput.Version,
Bytecode: compilationOutput.Contracts[firstContractKey].Bin,
ABI: string(abi),
ContractName: strings.ReplaceAll(firstContractKey, "<stdin>:", ""),
Errors: errors,
Warnings: warnings,
for key := range compilationOutput.Contracts {
for key, output := range compilationOutput.Contracts[key] {
isEntryContract := false
if v.config.GetEntrySourceName() != "" && key == v.config.GetEntrySourceName() {
isEntryContract = true
}

abi, err := json.Marshal(output.Abi)
if err != nil {
return nil, err
}

results = append(results, &CompilerResults{
IsEntryContract: isEntryContract,
RequestedVersion: compilerVersion,
Bytecode: output.Evm.Bytecode.Object,
ABI: string(abi),
Opcodes: output.Evm.Bytecode.Opcodes,
ContractName: key,
Errors: errors,
Warnings: warnings,
Metadata: output.Metadata,
})
}
}

return results, nil
}

// CompilerResults represents the results of a solc compilation.
type CompilerResults struct {
IsEntryContract bool `json:"is_entry_contract"`
RequestedVersion string `json:"requested_version"`
CompilerVersion string `json:"compiler_version"`
ContractName string `json:"contract_name"`
Bytecode string `json:"bytecode"`
ABI string `json:"abi"`
ContractName string `json:"contract_name"`
Opcodes string `json:"opcodes"`
Metadata string `json:"metadata"`
Errors []string `json:"errors"`
Warnings []string `json:"warnings"`
}
Expand Down
49 changes: 47 additions & 2 deletions compiler_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var allowedArgs = map[string]bool{
"--evm-version": true,
"--overwrite": true,
"--libraries": true,
"--standard-json": true,
}

// requiredArgs defines a list of required arguments for solc.
Expand All @@ -26,8 +27,10 @@ var requiredArgs = map[string]bool{

// CompilerConfig represents the compiler configuration for the solc binaries.
type CompilerConfig struct {
CompilerVersion string // The version of the compiler to use.
Arguments []string // Arguments to pass to the solc tool.
CompilerVersion string // The version of the compiler to use.
EntrySourceName string // The name of the entry source file.
Arguments []string // Arguments to pass to the solc tool.
JsonConfig *CompilerJsonConfig // The json config to pass to the solc tool.
}

// NewDefaultCompilerConfig creates and returns a default CompilerConfiguration for compiler to use.
Expand All @@ -50,6 +53,48 @@ func NewDefaultCompilerConfig(compilerVersion string) (*CompilerConfig, error) {
return toReturn, nil
}

// NewDefaultCompilerConfig creates and returns a default CompilerConfiguration for compiler to use with provided JSON settings.
func NewCompilerConfigFromJSON(compilerVersion string, entrySourceName string, config *CompilerJsonConfig) (*CompilerConfig, error) {
toReturn := &CompilerConfig{
EntrySourceName: entrySourceName,
CompilerVersion: compilerVersion,
Arguments: []string{
"--standard-json", // Output to stdout.
},
JsonConfig: config,
}

if _, err := toReturn.SanitizeArguments(toReturn.Arguments); err != nil {
return nil, err
}

/* if err := toReturn.Validate(); err != nil {
return nil, err
} */

return toReturn, nil
}

// SetJsonConfig sets the json config to pass to the solc tool.
func (c *CompilerConfig) SetJsonConfig(config *CompilerJsonConfig) {
c.JsonConfig = config
}

// GetJsonConfig returns the json config to pass to the solc tool.
func (c *CompilerConfig) GetJsonConfig() *CompilerJsonConfig {
return c.JsonConfig
}

// SetEntrySourceName sets the name of the entry source file.
func (c *CompilerConfig) SetEntrySourceName(name string) {
c.EntrySourceName = name
}

// GetEntrySourceName returns the name of the entry source file.
func (c *CompilerConfig) GetEntrySourceName() string {
return c.EntrySourceName
}

// SetCompilerVersion sets the version of the solc compiler to use.
func (c *CompilerConfig) SetCompilerVersion(version string) {
c.CompilerVersion = version
Expand Down
35 changes: 35 additions & 0 deletions compiler_json_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package solc

import "encoding/json"

// Source represents the content of a Solidity source file.
type Source struct {
Content string `json:"content"` // The content of the Solidity source file.
}

// Settings defines the configuration settings for the Solidity compiler.
type Settings struct {
Optimizer Optimizer `json:"optimizer"` // Configuration for the optimizer.
EVMVersion string `json:"evmVersion,omitempty"` // The version of the Ethereum Virtual Machine to target. Optional.
Remappings []string `json:"remappings,omitempty"` // List of remappings for library addresses. Optional.
OutputSelection map[string]map[string][]string `json:"outputSelection"` // Specifies the type of information to output (e.g., ABI, AST).
}

// Optimizer represents the configuration for the Solidity compiler's optimizer.
type Optimizer struct {
Enabled bool `json:"enabled"` // Indicates whether the optimizer is enabled.
Runs int `json:"runs"` // Specifies the number of optimization runs.
}

// CompilerJsonConfig represents the JSON configuration for the Solidity compiler.
type CompilerJsonConfig struct {
Language string `json:"language"` // Specifies the language version (e.g., "Solidity").
Sources map[string]Source `json:"sources"` // Map of source file names to their content.
Settings Settings `json:"settings"` // Compiler settings.
}

// ToJSON converts the CompilerJsonConfig to its JSON representation.
// It returns the JSON byte array or an error if the conversion fails.
func (c *CompilerJsonConfig) ToJSON() ([]byte, error) {
return json.Marshal(c)
}
Loading