From 82e0dd79f43a5ad8aa5d3d52db0ce061b7e83dcd Mon Sep 17 00:00:00 2001 From: RJ Catalano Date: Wed, 16 Aug 2017 14:43:58 -0500 Subject: [PATCH] common/compiler: complete reworking of compilers section Signed-off-by: RJ Catalano --- common/compiler/compiler.go | 49 ++++- common/compiler/flags.go | 17 -- common/compiler/solidity.go | 348 ++++++++++++++++------------- common/compiler/solidity_test.go | 367 +++++++++++++++++++++++++------ 4 files changed, 543 insertions(+), 238 deletions(-) delete mode 100644 common/compiler/flags.go diff --git a/common/compiler/compiler.go b/common/compiler/compiler.go index e95c45ae9e..8b85f00274 100644 --- a/common/compiler/compiler.go +++ b/common/compiler/compiler.go @@ -1,6 +1,51 @@ package compiler +import ( + "fmt" + "os/exec" +) + +// An interface to denote a compiler. It implements two function. +// Compile takes in a slice of files and a series of functions made +// to represent flag options of the compiler and returns a Compile Return or an error. +// The function version() is instantiated upon the creation of the compiler to +// fill the compiler's struct with version details. It fails if there is an error in the parsing. type Compiler interface { - PrepareCommand(files ...string) error - Compile(flags ...func() string) (string, error) + Compile(files []string, flags FlagOpts) (Return, error) + version() error +} + +// Practicing inheritance, this struct gives us access to all types of returns. +// This is written to be extendable to other compilers. +type Return struct { + Error error + SolcReturn + //Enter your return struct here...e.g. + //SerpentReturn + //BambooReturn + //ViperReturn +} + +// Practicing inheritance, this struct allows us to easily create a simple +// interface for interacting with our potentially various compilers. +type FlagOpts struct { + SolcFlagOpts + //Enter your FlagOpts struct here... +} + +func InitCompiler(compilerName string) (Compiler, error) { + if _, err := exec.LookPath(compilerName); err != nil { + return nil, fmt.Errorf("compiler: could not find %v in PATH", compilerName) + } + switch compilerName { + case "solc": + s := &Solidity{} + if err := s.version(); err != nil { + return nil, err + } else { + return s, nil + } + default: + return nil, fmt.Errorf("compiler: currently does not support %v for compilation", compilerName) + } } diff --git a/common/compiler/flags.go b/common/compiler/flags.go deleted file mode 100644 index 3c587120f4..0000000000 --- a/common/compiler/flags.go +++ /dev/null @@ -1,17 +0,0 @@ -package compiler - -func addStandardAbiAndBin() string { - return "abi,bin," -} - -func addDevDoc() string { - return "devdoc" -} - -func addUserDoc() string { - return "userdoc" -} - -func addMetadata() string { - return "metadata" -} diff --git a/common/compiler/solidity.go b/common/compiler/solidity.go index 6e69136834..7170db8d0a 100644 --- a/common/compiler/solidity.go +++ b/common/compiler/solidity.go @@ -22,217 +22,253 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" "os" "os/exec" + "path" + "reflect" "regexp" "strconv" "strings" + + "github.com/ethereum/go-ethereum/common" ) var versionRegexp = regexp.MustCompile(`([0-9]+)\.([0-9]+)\.([0-9]+)`) -type Contract struct { - Code string `json:"code"` - Info ContractInfo `json:"info"` +//The following represents solidity outputs from the compiler that we're interested in +type SolcReturn struct { + Warning string + Version string `json:"version"` + Contracts map[string]SolcItems `json:"contracts"` } -type ContractInfo struct { - Source string `json:"source"` - Language string `json:"language"` - LanguageVersion string `json:"languageVersion"` - CompilerVersion string `json:"compilerVersion"` - CompilerOptions string `json:"compilerOptions"` - AbiDefinition interface{} `json:"abiDefinition"` - UserDoc interface{} `json:"userDoc"` - DeveloperDoc interface{} `json:"developerDoc"` - Metadata string `json:"metadata"` +//The key return items to enable unmarshalling from the returns from the compiler +type SolcItems struct { + Bin string `json:"bin"` + Abi string `json:"abi"` + DevDoc string `json:"devdoc"` + UserDoc string `json:"userdoc"` + Metadata string `json:"metadata"` +} + +// Custom UnmarshalJSON is needed for the sake of capturing the warnings that can pop up +// in the compiler while still maintaining the results of the compilation. +func (ret *SolcReturn) UnmarshalJSON(data []byte) (err error) { + trimmedOutput := bytes.TrimSpace(data) + jsonBeginsCertainly := bytes.Index(trimmedOutput, []byte(`{"contracts":`)) + + if jsonBeginsCertainly > 0 { + ret.Warning = string(trimmedOutput[:jsonBeginsCertainly]) + trimmedOutput = trimmedOutput[jsonBeginsCertainly:] + } + + err = json.Unmarshal(trimmedOutput, &ret) + + return +} + +func (ret SolcReturn) blend(other SolcReturn) (SolcReturn, error) { + for str, items := range other.Contracts { + if _, taken := ret.Contracts[str]; taken { + return SolcReturn{}, fmt.Errorf("solc: there was an issue in blending bin and sol files, please try them separately") + } else { + ret.Contracts[str] = items + } + } + return ret, nil } // Solidity contains information about the solidity compiler. type Solidity struct { Path, Version, FullVersion string Major, Minor, Patch int - Files []string - FlagOpts SolcFlagOpts } +//This is a template to define our inputs for the compiler flags type SolcFlagOpts struct { - Optimize bool - CombinedJson []string - ToLink []string - Version string + // (Optional) what to get in the output, can be any combination of [abi, bin, userdoc, devdoc, metadata] + // abi: application binary interface. Necessary for interaction with contracts. + // bin: binary bytecode. Necessary for creating and deploying and interacting with contracts. + // userdoc: natspec for users. + // devdoc: natspec for devs. + // metadata: contract metadata. + CombinedOutput []string + // (Optional) Direct string of library address mappings. + // Syntax: :
,:
+ // Address is interpreted as a hex string optionally prefixed by 0x. + Libraries map[string]common.Address + // (Optional) Remappings, see https://solidity.readthedocs.io/en/latest/layout-of-source-files.html#use-in-actual-compilers + // Syntax: = + Remappings []string + // (Optional) if true, enable standard library contracts + StdLib bool + // (Optional) if true, optimizes solidity code + Optimize bool + // (Optional) the number of optimization runs to run on solidity + OptimizeRuns uint64 + // (Optional) For anything else we may have missed, if filled will default override other flags. + Exec string } -// --combined-output format -type solcOutput struct { - Contracts map[string]struct { - Bin, Abi, Devdoc, Userdoc, Metadata string +func (s *Solidity) defaultFlagOpts() (f SolcFlagOpts) { + f = SolcFlagOpts{ + CombinedOutput: []string{"bin", "abi", "userdoc", "devdoc"}, + StdLib: true, + Optimize: true, } - Version string -} -func (s *Solidity) makeArgs() []string { - p := []string{ - "--combined-json", "bin,abi,userdoc,devdoc", - "--add-std", // include standard lib contracts - "--optimize", // code optimizer switched on + if s.Major >= 0 && s.Minor >= 4 && s.Patch > 6 { + f.CombinedOutput = append(f.CombinedOutput, "metadata") } - if s.Major > 0 || s.Minor > 4 || s.Patch > 6 { - p[1] += ",metadata" - } - return p + + return } // SolidityVersion runs solc and parses its version output. -func SolidityVersion(solc string) (*Solidity, error) { - if solc == "" { - solc = "solc" - } +func (s *Solidity) version() error { var out bytes.Buffer - cmd := exec.Command(solc, "--version") + cmd := exec.Command("solc", "--version") cmd.Stdout = &out err := cmd.Run() if err != nil { - return nil, err + return err } matches := versionRegexp.FindStringSubmatch(out.String()) if len(matches) != 4 { - return nil, fmt.Errorf("can't parse solc version %q", out.String()) + return fmt.Errorf("can't parse solc version %q", out.String()) } - s := &Solidity{Path: cmd.Path, FullVersion: out.String(), Version: matches[0]} + s = &Solidity{Path: cmd.Path, FullVersion: out.String(), Version: matches[0]} if s.Major, err = strconv.Atoi(matches[1]); err != nil { - return nil, err + return err } if s.Minor, err = strconv.Atoi(matches[2]); err != nil { - return nil, err + return err } if s.Patch, err = strconv.Atoi(matches[3]); err != nil { - return nil, err - } - return s, nil -} - -// CompileSolidityString builds and returns all the contracts contained within a source string. -func CompileSolidityString(solc, source string) (map[string]*Contract, error) { - if len(source) == 0 { - return nil, errors.New("solc: empty source string") - } - s, err := SolidityVersion(solc) - if err != nil { - return nil, err - } - args := append(s.makeArgs(), "--") - cmd := exec.Command(s.Path, append(args, "-")...) - cmd.Stdin = strings.NewReader(source) - return s.run(cmd, source) -} - -// CompileSolidity compiles all given Solidity source files. -func CompileSolidity(solc string, sourcefiles ...string) (map[string]*Contract, error) { - if len(sourcefiles) == 0 { - return nil, errors.New("solc: no source files") - } - source, err := slurpFiles(sourcefiles) - if err != nil { - return nil, err - } - s, err := SolidityVersion(solc) - if err != nil { - return nil, err - } - args := append(s.makeArgs(), "--") - cmd := exec.Command(s.Path, append(args, sourcefiles...)...) - return s.run(cmd, source) -} - -func (s *Solidity) GetFiles(sourcefiles ...string) error { - if len(sourcefiles) == 0 { - return errors.New("solc: no source files") - } - - for _, file := range sourcefiles { - if _, err := os.Stat(file); os.IsNotExist(err) { - return fmt.Errorf("solc: could not find file %v", file) - } - s.Files = append(s.Files, file) + return err } return nil } -/*func (s *Solidity) Compile(flags ...func() string) (string, error) { - var command []string - for _, flag := range flags { - command := append(command, flag()) - } - if len(s.FlagOpts.ToLink) > 0 { - command := append(command, s.linkLibraries()) +// Compiles a series of files using the solidity compiler +func (s *Solidity) Compile(files []string, flags FlagOpts) (Return, error) { + + if reflect.DeepEqual(flags.SolcFlagOpts, (SolcFlagOpts{})) { + flags.SolcFlagOpts = s.defaultFlagOpts() } - finalCommand := strings.Join(command, "") + //check files for .bin extension for linking addresses + //separate .sol and .bin files + //link .bins separately + solFiles, binFiles, err := s.sortAndValidateFiles(files) + if err != nil { + return Return{}, err + } - exec.Command("solc", finalCommand) + // assemble commands and execute + var binResults SolcReturn + if len(binFiles) > 0 { + solcExecute := flags.assembleSolcCommand(true, binFiles...) + binResults, err = s.executeSolc(solcExecute...) + if err != nil { + return Return{}, err + } + } -}*/ + var solResults SolcReturn + if len(solFiles) > 0 { + solcExecute := flags.assembleSolcCommand(false, solFiles...) + solResults, err = s.executeSolc(solcExecute...) + if err != nil { + return Return{}, err + } + } -func (s *Solidity) run(cmd *exec.Cmd, source string) (map[string]*Contract, error) { + // blend the two results, even if one of them is empty (more efficient this way) + ret, err := solResults.blend(binResults) + return Return{SolcReturn: ret}, err +} + +func (f SolcFlagOpts) assembleSolcCommand(binary bool, files ...string) (command []string) { + stringifyLibs := func(libs map[string]common.Address) []string { + var combinedLibs []string + for x, y := range f.Libraries { + combinedLibs = append(combinedLibs, x+":"+y.String()) + } + return combinedLibs + } + + switch { + case f.Exec != "": + command = append(command, f.Exec) + case binary: + if len(f.Libraries) > 0 { + combinedLibs := stringifyLibs(f.Libraries) + command = append(command, "--link --libraries") + command = append(command, strings.Join(combinedLibs, ",")) + } + default: + if len(f.Remappings) > 0 { + command = append(command, strings.Join(f.Remappings, " ")) + } + if len(f.CombinedOutput) > 0 { + command = append(command, "--combined-json", strings.Join(f.CombinedOutput, ",")) + } + if len(f.Libraries) > 0 { + combinedLibs := stringifyLibs(f.Libraries) + command = append(command, "--link --libraries") + command = append(command, strings.Join(combinedLibs, ",")) + } + if f.Optimize { + command = append(command, "--optimize") + } + if f.StdLib { + command = append(command, "--std-lib") + } + if f.OptimizeRuns != 0 { + command = append(command, "--optimize-runs", strconv.FormatUint(f.OptimizeRuns, 10)) + } + } + return append(command, files...) +} + +// A utility function to sort .sol and .bin files into separate slices +func (s *Solidity) sortAndValidateFiles(files []string) ([]string, []string, error) { + var solFiles []string + var binFiles []string + if len(files) == 0 { + return nil, nil, errors.New("solc: no source files") + } + for _, file := range files { + if _, err := os.Stat(file); os.IsNotExist(err) { + return nil, nil, fmt.Errorf("solc: could not find file %v", file) + } + switch path.Ext(file) { + case ".sol": + solFiles = append(solFiles, file) + case ".bin": + binFiles = append(binFiles, file) + default: + return nil, nil, fmt.Errorf("solc: unexpected file extension found during compilation: %v", file) + } + } + return solFiles, binFiles, nil +} + +func (s *Solidity) executeSolc(flagsAndFiles ...string) (output SolcReturn, err error) { var stderr, stdout bytes.Buffer + + cmd := exec.Command("solc", flagsAndFiles...) cmd.Stderr = &stderr cmd.Stdout = &stdout - if err := cmd.Run(); err != nil { - return nil, fmt.Errorf("solc: %v\n%s", err, stderr.Bytes()) - } - var output solcOutput - if err := json.Unmarshal(stdout.Bytes(), &output); err != nil { - return nil, err + + if err = cmd.Run(); err != nil { + return SolcReturn{}, fmt.Errorf("solc: %v\n%s", err, stderr.Bytes()) } - // Compilation succeeded, assemble and return the contracts. - contracts := make(map[string]*Contract) - for name, info := range output.Contracts { - // Parse the individual compilation results. - var abi interface{} - if err := json.Unmarshal([]byte(info.Abi), &abi); err != nil { - return nil, fmt.Errorf("solc: error reading abi definition (%v)", err) - } - var userdoc interface{} - if err := json.Unmarshal([]byte(info.Userdoc), &userdoc); err != nil { - return nil, fmt.Errorf("solc: error reading user doc: %v", err) - } - var devdoc interface{} - if err := json.Unmarshal([]byte(info.Devdoc), &devdoc); err != nil { - return nil, fmt.Errorf("solc: error reading dev doc: %v", err) - } - contracts[name] = &Contract{ - Code: "0x" + info.Bin, - Info: ContractInfo{ - Source: source, - Language: "Solidity", - LanguageVersion: s.Version, - CompilerVersion: s.Version, - CompilerOptions: strings.Join(s.makeArgs(), " "), - AbiDefinition: abi, - UserDoc: userdoc, - DeveloperDoc: devdoc, - Metadata: info.Metadata, - }, - } + if err = json.Unmarshal(stdout.Bytes(), &output); err != nil { + return SolcReturn{}, err } - return contracts, nil -} -func (s *Solidity) linkLibraries() string { - return "--libraries " + strings.Join(s.FlagOpts.ToLink, ",") -} - -func slurpFiles(files []string) (string, error) { - var concat bytes.Buffer - for _, file := range files { - content, err := ioutil.ReadFile(file) - if err != nil { - return "", err - } - concat.Write(content) - } - return concat.String(), nil + return } diff --git a/common/compiler/solidity_test.go b/common/compiler/solidity_test.go index d248720e0d..e34d085f3e 100644 --- a/common/compiler/solidity_test.go +++ b/common/compiler/solidity_test.go @@ -17,21 +17,38 @@ package compiler import ( - "os/exec" + "io/ioutil" + "os" + "path/filepath" "strings" "testing" + + "github.com/ethereum/go-ethereum/common" ) -const ( - testSource = ` -contract test { - /// @notice Will multiply ` + "`a`" + ` by 7. - function multiply(uint a) returns(uint d) { - return a * 7; - } -} -` - librarySource = ` +const solFile = `pragma solidity >= 0.0.0; + contract main { + uint a; + function f() { + a = 1; + } + }` + +const faultySol = `pragma solidity >= 0.0.0; + contract main { + uint a; + function f() { + a = 1; + } + ` +const solNoPragma = `contract main { + uint a; + function f() { + a = 1; + } + }` +const simplyLibrarySol = `pragma solidity >=0.0.0; + library Set { struct Data { mapping(uint => bool) flags; } function insert(Data storage self, uint value) @@ -62,69 +79,293 @@ library Set { contract C { Set.Data knownValues; function register(uint value) { - require(Set.insert(knownValues, value)); + if (!Set.insert(knownValues, value)) + throw; } }` -) -func skipWithoutSolc(t *testing.T) { - if _, err := exec.LookPath("solc"); err != nil { +const solFile1 = `pragma solidity >=0.0.0; + +library Set { + struct Data { mapping(uint => bool) flags; } + function insert(Data storage self, uint value) + returns (bool) + { + if (self.flags[value]) + return false; // already there + self.flags[value] = true; + return true; + } + + function remove(Data storage self, uint value) + returns (bool) + { + if (!self.flags[value]) + return false; // not there + self.flags[value] = false; + return true; + } + + function contains(Data storage self, uint value) + returns (bool) + { + return self.flags[value]; + } +}` + +const solFile2 = `pragma solidity >=0.0.0; +import "set.sol"; + +contract C { + Set.Data knownValues; + function register(uint value) { + if (!Set.insert(knownValues, value)) + throw; + } +}` + +func TestSolcCompilerNormal(t *testing.T) { + solc, err := InitCompiler("solc") + if err != nil { t.Skip(err) } -} - -func TestCompiler(t *testing.T) { - skipWithoutSolc(t) - - contracts, err := CompileSolidityString("", testSource) + solc = solc.(*Solidity) + file, err := os.Create("simpleContract.sol") + defer os.Remove("simpleContract.sol") if err != nil { - t.Fatalf("error compiling source. result %v: %v", contracts, err) + t.Fatal(err) } - if len(contracts) != 1 { - t.Errorf("one contract expected, got %d", len(contracts)) + file.WriteString(solFile) + flags := SolcFlagOpts{ + CombinedOutput: []string{"bin", "abi"}, } - c, ok := contracts["test"] - if !ok { - c, ok = contracts[":test"] - if !ok { - t.Fatal("info for contract 'test' not present in result") - } - } - if c.Code == "" { - t.Error("empty code") - } - if c.Info.Source != testSource { - t.Error("wrong source") - } - if c.Info.CompilerVersion == "" { - t.Error("empty version") - } -} -func TestCompileError(t *testing.T) { - skipWithoutSolc(t) - - contracts, err := CompileSolidityString("", testSource[4:]) - if err == nil { - t.Errorf("error expected compiling source. got none. result %v", contracts) - } - t.Logf("error: %v", err) -} - -func TestCompilerLinking(t *testing.T) { - skipWithoutSolc(t) - - solc, err := SolidityVersion("") + solReturn, err := solc.Compile([]string{"simpleContract.sol"}, FlagOpts{SolcFlagOpts: flags}) if err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } - solc.FlagOpts.ToLink = append(solc.FlagOpts.ToLink, "Set:0x692a70d2e424a56d2c6c27aa97d1a86395877b3a") - - linkedLibraries := solc.linkLibraries() - - testingCase1 := "--libraries Set:0x692a70d2e424a56d2c6c27aa97d1a86395877b3a" - if strings.Compare(linkedLibraries, testingCase1) != 0 { - t.Errorf("expected %v, got %v", linkedLibraries, testingCase1) + if solReturn.Error != nil || solReturn.Warning != "" || len(solReturn.Contracts) != 1 { + t.Fatalf("Expected no errors or warnings and expected contract items. Got %v for errors, %v for warnings, and %v for contract items", solReturn.Error, solReturn.Warning, solReturn.Contracts) + } +} + +func TestSolcCompilerError(t *testing.T) { + solc, err := InitCompiler("solc") + if err != nil { + t.Skip(err) + } + solc = solc.(*Solidity) + file, err := os.Create("faultyContract.sol") + defer os.Remove("faultyContract.sol") + if err != nil { + t.Fatal(err) + } + file.WriteString(faultySol) + flags := SolcFlagOpts{ + CombinedOutput: []string{"bin", "abi"}, + } + + solReturn, err := solc.Compile([]string{"faultyContract.sol"}, FlagOpts{SolcFlagOpts: flags}) + if err != nil { + t.Fatal(err) + } + if solReturn.Error == nil { + t.Fatal("Expected an error, got nil.") + } +} + +func TestSolcCompilerWarning(t *testing.T) { + solc, err := InitCompiler("solc") + if err != nil { + t.Skip(err) + } + solc = solc.(*Solidity) + file, err := os.Create("simpleContract.sol") + defer os.Remove("simpleContract.sol") + if err != nil { + t.Fatal(err) + } + file.WriteString(solNoPragma) + flags := SolcFlagOpts{ + CombinedOutput: []string{"bin", "abi"}, + } + + solReturn, err := solc.Compile([]string{"simpleContract.sol"}, FlagOpts{SolcFlagOpts: flags}) + if err != nil { + t.Fatal(err) + } + if solReturn.Warning == "" { + t.Fatal("Expected a warning.") + } +} + +func TestLinkingBinaries(t *testing.T) { + solc, err := InitCompiler("solc") + if err != nil { + t.Skip(err) + } + solc = solc.(*Solidity) + file, err := os.Create("simpleLibrary.sol") + defer os.Remove("simpleLibrary.sol") + if err != nil { + t.Fatal(err) + } + file.WriteString(simplyLibrarySol) + flags := SolcFlagOpts{ + CombinedOutput: []string{"bin"}, + } + + solReturn, err := solc.Compile([]string{"simpleLibrary.sol"}, FlagOpts{SolcFlagOpts: flags}) + if err != nil { + t.Fatal(err) + } + + if solReturn.Error != nil || solReturn.Warning != "" || len(solReturn.Contracts) != 2 { + t.Fatalf("Expected no errors or warnings and expected contract items. Got %v for errors, %v for warnings, and %v for contract items", solReturn.Error, solReturn.Warning, solReturn.Contracts) + } + // note: When solc upgrades to 0.4.10, will need to add "simpleLibrary.sol:" to beginning of this string + flags.Libraries = map[string]common.Address{"simpleLibrary.sol:Set": common.StringToAddress("0x692a70d2e424a56d2c6c27aa97d1a86395877b3a")} + binFile, err := os.Create("C.bin") + defer os.Remove("C.bin") + if err != nil { + t.Fatal(err) + } + + binFile.WriteString(solReturn.Contracts["simpleLibrary.sol:C"].Bin) + _, err = solc.(*Solidity).Compile([]string{"./C.bin"}, FlagOpts{SolcFlagOpts: flags}) + if err != nil { + t.Fatal(err) + } + output, err := ioutil.ReadFile("C.bin") + if err != nil { + t.Fatal(err) + } + + if strings.Contains(string(output), "_") { + t.Fatal("Expected binaries to link, but they did not") + } +} + +func TestLinkingBinariesAndNormalCompileMixed(t *testing.T) { + solc, err := InitCompiler("solc") + if err != nil { + t.Skip(err) + } + solc = solc.(*Solidity) + file, err := os.Create("simpleLibrary.sol") + defer os.Remove("simpleLibrary.sol") + if err != nil { + t.Fatal(err) + } + file.WriteString(simplyLibrarySol) + flags := SolcFlagOpts{ + CombinedOutput: []string{"bin"}, + } + + solReturn, err := solc.Compile([]string{"simpleLibrary.sol"}, FlagOpts{SolcFlagOpts: flags}) + if err != nil { + t.Fatal(err) + } + + if solReturn.Error != nil || solReturn.Warning != "" || len(solReturn.Contracts) != 2 { + t.Fatalf("Expected no errors or warnings and expected contract items. Got %v for errors, %v for warnings, and %v for contract items", solReturn.Error, solReturn.Warning, solReturn.Contracts) + } + // note: When solc upgrades to 0.4.10, will need to add "simpleLibrary.sol:" to beginning of this string + flags.Libraries = map[string]common.Address{"simpleLibrary.sol:Set": common.StringToAddress("0x692a70d2e424a56d2c6c27aa97d1a86395877b3a")} + binFile, err := os.Create("C.bin") + defer os.Remove("C.bin") + if err != nil { + t.Fatal(err) + } + binFile.WriteString(solReturn.Contracts["simpleLibrary.sol:C"].Bin) + + solOutput, err := solc.Compile([]string{"./C.bin", "simpleLibrary.sol"}, FlagOpts{SolcFlagOpts: flags}) + if err != nil { + t.Fatal(err) + } + binOutput, err := ioutil.ReadFile("C.bin") + if err != nil { + t.Fatal(err) + } + + if strings.Contains(string(binOutput), "_") { + t.Fatal("Expected binaries to link, but they did not") + } + + if solOutput.Error != nil || solOutput.Warning != "" || len(solOutput.Contracts) != 2 { + t.Fatalf("Expected no errors or warnings and expected contract items. Got %v for errors, %v for warnings, and %v for contract items", solReturn.Error, solReturn.Warning, solReturn.Contracts) + } +} + +func TestMultipleFilesCompiling(t *testing.T) { + solc, err := InitCompiler("solc") + if err != nil { + t.Skip(err) + } + solc = solc.(*Solidity) + set, err := os.Create("set.sol") + defer os.Remove("set.sol") + if err != nil { + t.Fatal(err) + } + set.WriteString(solFile1) + + c, err := os.Create("C.sol") + defer os.Remove("C.sol") + if err != nil { + t.Fatal(err) + } + c.WriteString(solFile2) + flags := SolcFlagOpts{ + CombinedOutput: []string{"bin", "abi"}, + } + + solReturn, err := solc.Compile([]string{"C.sol"}, FlagOpts{SolcFlagOpts: flags}) + if err != nil { + t.Fatal(err) + } + + if solReturn.Error != nil || solReturn.Warning != "" || len(solReturn.Contracts) != 2 { + t.Fatalf("Expected no errors or warnings and expected contract items. Got %v for errors, %v for warnings, and %v for contract items", solReturn.Error, solReturn.Warning, solReturn.Contracts) + } +} + +func TestRemappings(t *testing.T) { + solc, err := InitCompiler("solc") + if err != nil { + t.Skip(err) + } + solc = solc.(*Solidity) + if err := os.MkdirAll("."+string(filepath.Separator)+"tempDir", 0777); err != nil { + t.Fatal(err) + } + defer os.RemoveAll("." + string(filepath.Separator) + "tempDir") + os.Chdir("tempDir") + set, err := os.Create("set.sol") + if err != nil { + t.Fatal(err) + } + os.Chdir("..") + set.WriteString(solFile1) + + c, err := os.Create("C.sol") + defer os.Remove("C.sol") + if err != nil { + t.Fatal(err) + } + c.WriteString(solFile2) + flags := SolcFlagOpts{ + CombinedOutput: []string{"bin", "abi"}, + Remappings: []string{`set.sol=./tempDir/set.sol`}, + } + + solReturn, err := solc.Compile([]string{"C.sol"}, FlagOpts{SolcFlagOpts: flags}) + if err != nil { + t.Fatal(err) + } + + if solReturn.Error != nil || solReturn.Warning != "" || len(solReturn.Contracts) != 2 { + t.Fatalf("Expected no errors or warnings and expected contract items. Got %v for errors, %v for warnings, and %v for contract items", solReturn.Error, solReturn.Warning, solReturn.Contracts) } }