@ -14,6 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package compiler wraps the Solidity compiler executable (solc).
package compiler
import (
@ -21,42 +22,23 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
)
var (
versionRegexp = regexp . MustCompile ( "[0-9]+\\.[0-9]+\\.[0-9]+" )
legacyRegexp = regexp . MustCompile ( "0\\.(9\\..*|1\\.[01])" )
paramsLegacy = [ ] string {
"--binary" , // Request to output the contract in binary (hexadecimal).
"file" , //
"--json-abi" , // Request to output the contract's JSON ABI interface.
"file" , //
"--natspec-user" , // Request to output the contract's Natspec user documentation.
"file" , //
"--natspec-dev" , // Request to output the contract's Natspec developer documentation.
"file" ,
"--add-std" ,
"1" ,
}
paramsNew = [ ] string {
"--bin" , // Request to output the contract in binary (hexadecimal).
"--abi" , // Request to output the contract's JSON ABI interface.
"--userdoc" , // Request to output the contract's Natspec user documentation.
"--devdoc" , // Request to output the contract's Natspec developer documentation.
solcParams = [ ] string {
"--combined-json" , "bin,abi,userdoc,devdoc" ,
"--add-std" , // include standard lib contracts
"--optimize" , // code optimizer switched on
"-o" , // output directory
}
)
@ -76,135 +58,112 @@ type ContractInfo struct {
DeveloperDoc interface { } ` json:"developerDoc" `
}
// Solidity contains information about the solidity compiler.
type Solidity struct {
solcPath string
version string
fullVersion string
legacy bool
Path , Version , FullVersion string
}
func New ( solcPath string ) ( sol * Solidity , err error ) {
// set default solc
if len ( solcPath ) == 0 {
solcPath = "solc"
}
solcPath , err = exec . LookPath ( solcPath )
if err != nil {
return
// --combined-output format
type solcOutput struct {
Contracts map [ string ] struct { Bin , Abi , Devdoc , Userdoc string }
Version string
}
cmd := exec . Command ( solcPath , "--version" )
// SolidityVersion runs solc and parses its version output.
func SolidityVersion ( solc string ) ( * Solidity , error ) {
if solc == "" {
solc = "solc"
}
var out bytes . Buffer
cmd := exec . Command ( solc , "--version" )
cmd . Stdout = & out
err = cmd . Run ( )
if err != nil {
return
}
fullVersion := out . String ( )
version := versionRegexp . FindString ( fullVersion )
legacy := legacyRegexp . MatchString ( version )
sol = & Solidity {
solcPath : solcPath ,
version : version ,
fullVersion : fullVersion ,
legacy : legacy ,
}
glog . V ( logger . Info ) . Infoln ( sol . Info ( ) )
return
if err := cmd . Run ( ) ; err != nil {
return nil , err
}
func ( sol * Solidity ) Info ( ) string {
return fmt . Sprintf ( "%s\npath: %s" , sol . fullVersion , sol . solcPath )
s := & Solidity {
Path : cmd . Path ,
FullVersion : out . String ( ) ,
Version : versionRegexp . FindString ( out . String ( ) ) ,
}
func ( sol * Solidity ) Version ( ) string {
return sol . version
return s , nil
}
// Compile builds and returns all the contracts contained within a source string.
func ( sol * Solidity ) Compile ( source string ) ( map [ string ] * Contract , error ) {
// Short circuit if no source code was specified
// 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" )
}
// Create a safe place to dump compilation output
wd , err := ioutil . TempDir ( "" , "solc" )
if solc == "" {
solc = "solc"
}
// Write source to a temporary file. Compiling stdin used to be supported
// but seems to produce an exception with solc 0.3.5.
infile , err := ioutil . TempFile ( "" , "geth-compile-solidity" )
if err != nil {
return nil , fmt . Errorf ( "solc: failed to create temporary build folder: %v" , err )
return nil , err
}
defer os . RemoveAll ( wd )
// Assemble the compiler command, change to the temp folder and capture any errors
stderr := new ( bytes . Buffer )
var params [ ] string
if sol . legacy {
params = paramsLegacy
} else {
params = paramsNew
params = append ( params , wd )
defer os . Remove ( infile . Name ( ) )
if _ , err := io . WriteString ( infile , source ) ; err != nil {
return nil , err
}
if err := infile . Close ( ) ; err != nil {
return nil , err
}
compilerOptions := strings . Join ( params , " " )
cmd := exec . Command ( sol . solcPath , params ... )
cmd . Stdin = strings . NewReader ( source )
cmd . Stderr = stderr
return CompileSolidity ( solc , infile . Name ( ) )
}
if err := cmd . Run ( ) ; err != nil {
return nil , fmt . Errorf ( "solc: %v\n%s" , err , string ( stderr . Bytes ( ) ) )
// 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 " )
}
// Sanity check that something was actually built
matches , _ := filepath . Glob ( filepath . Join ( wd , "*.bin*" ) )
if len ( matches ) < 1 {
return nil , fmt . Errorf ( "solc: no build results found" )
source , err := slurpFiles ( sourcefiles )
if err != nil {
return nil , err
}
if solc == "" {
solc = "solc"
}
// Compilation succeeded, assemble and return the contracts
contracts := make ( map [ string ] * Contract )
for _ , path := range matches {
_ , file := filepath . Split ( path )
base := strings . Split ( file , "." ) [ 0 ]
// Parse the individual compilation results (code binary, ABI definitions, user and dev docs)
var binary [ ] byte
binext := ".bin"
if sol . legacy {
binext = ".binary"
var stderr , stdout bytes . Buffer
args := append ( solcParams , "--" )
cmd := exec . Command ( solc , append ( args , sourcefiles ... ) ... )
cmd . Stderr = & stderr
cmd . Stdout = & stdout
if err := cmd . Run ( ) ; err != nil {
return nil , fmt . Errorf ( "solc: %v\n%s" , err , stderr . Bytes ( ) )
}
if binary , err = ioutil . ReadFile ( filepath . Join ( wd , base + binext ) ) ; err != nil {
return nil , fmt . Errorf ( "solc: error reading compiler output for code: %v" , err )
var output solcOutput
if err := json . Unmarshal ( stdout . Bytes ( ) , & output ) ; err != nil {
return nil , err
}
shortVersion := versionRegexp . FindString ( output . Version )
// 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 blob , err := ioutil . ReadFile ( filepath . Join ( wd , base + ".abi" ) ) ; err != nil {
return nil , fmt . Errorf ( "solc: error reading abi definition: %v" , err )
} else if err = json . Unmarshal ( blob , & abi ) ; err != nil {
return nil , fmt . Errorf ( "solc: error parsing abi definition: %v" , err )
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 blob , err := ioutil . ReadFile ( filepath . Join ( wd , base + ".docuser" ) ) ; err != nil {
if err := json . Unmarshal ( [ ] byte ( info . Userdoc ) , & userdoc ) ; err != nil {
return nil , fmt . Errorf ( "solc: error reading user doc: %v" , err )
} else if err = json . Unmarshal ( blob , & userdoc ) ; err != nil {
return nil , fmt . Errorf ( "solc: error parsing user doc: %v" , err )
}
var devdoc interface { }
if blob , err := ioutil . ReadFile ( filepath . Join ( wd , base + ".docdev" ) ) ; err != nil {
if err := json . Unmarshal ( [ ] byte ( info . Devdoc ) , & devdoc ) ; err != nil {
return nil , fmt . Errorf ( "solc: error reading dev doc: %v" , err )
} else if err = json . Unmarshal ( blob , & devdoc ) ; err != nil {
return nil , fmt . Errorf ( "solc: error parsing dev doc: %v" , err )
}
// Assemble the final contract
contracts [ base ] = & Contract {
Code : "0x" + string ( binary ) ,
contracts [ name ] = & Contract {
Code : "0x" + info . Bin ,
Info : ContractInfo {
Source : source ,
Language : "Solidity" ,
LanguageVersion : sol . v ersion ,
CompilerVersion : sol . v ersion ,
CompilerOptions : compilerOptions ,
LanguageVersion : shortV ersion ,
CompilerVersion : shortV ersion ,
CompilerOptions : strings . Join ( solcParams , " " ) ,
AbiDefinition : abi ,
UserDoc : userdoc ,
DeveloperDoc : devdoc ,
@ -214,12 +173,24 @@ func (sol *Solidity) Compile(source string) (map[string]*Contract, error) {
return contracts , nil
}
func SaveInfo ( info * ContractInfo , filename string ) ( contenthash common . Hash , err error ) {
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
}
// SaveInfo serializes info to the given file and returns its Keccak256 hash.
func SaveInfo ( info * ContractInfo , filename string ) ( common . Hash , error ) {
infojson , err := json . Marshal ( info )
if err != nil {
return
return common . Hash { } , err
}
contenthash = common . BytesToHash ( crypto . Keccak256 ( infojson ) )
err = ioutil . WriteFile ( filename , infojson , 0600 )
return
contenthash := common . BytesToHash ( crypto . Keccak256 ( infojson ) )
return contenthash , ioutil . WriteFile ( filename , infojson , 0600 )
}