Skip to content

Commit

Permalink
Merge pull request #6 from 0x19/path-fix
Browse files Browse the repository at this point in the history
Ability to compile from json mappings
  • Loading branch information
0x19 committed Aug 28, 2023
2 parents 34a069a + f9c51f5 commit 79c6534
Show file tree
Hide file tree
Showing 7 changed files with 375 additions and 57 deletions.
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)
}

0 comments on commit 79c6534

Please sign in to comment.