mirror of https://github.com/ethereum/go-ethereum
Merge pull request #833 from ethersphere/frontier/solidity
solidity compiler and contract metadocs integrationpull/876/head
commit
45c070fc8b
@ -0,0 +1 @@ |
|||||||
|
{"code":"605280600c6000396000f3006000357c010000000000000000000000000000000000000000000000000000000090048063c6888fa114602e57005b60376004356041565b8060005260206000f35b6000600782029050604d565b91905056","info":{"abiDefinition":[{"constant":false,"inputs":[{"name":"a","type":"uint256"}],"name":"multiply","outputs":[{"name":"d","type":"uint256"}],"type":"function"}],"compilerVersion":"0.9.13","developerDoc":{"methods":{}},"language":"Solidity","languageVersion":"0","source":"contract test {\n /// @notice Will multiply `a` by 7.\n function multiply(uint a) returns(uint d) {\n return a * 7;\n }\n}\n","userDoc":{"methods":{"multiply(uint256)":{"notice":"Will multiply `a` by 7."}}}}} |
@ -0,0 +1,187 @@ |
|||||||
|
package compiler |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"encoding/json" |
||||||
|
"fmt" |
||||||
|
"io/ioutil" |
||||||
|
"os" |
||||||
|
"os/exec" |
||||||
|
"path" |
||||||
|
"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" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
flair = "Christian <c@ethdev.com> and Lefteris <lefteris@ethdev.com> (c) 2014-2015" |
||||||
|
languageVersion = "0" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
versionRegExp = regexp.MustCompile("[0-9]+.[0-9]+.[0-9]+") |
||||||
|
params = []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", |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
type Contract struct { |
||||||
|
Code string `json:"code"` |
||||||
|
Info ContractInfo `json:"info"` |
||||||
|
} |
||||||
|
|
||||||
|
type ContractInfo struct { |
||||||
|
Source string `json:"source"` |
||||||
|
Language string `json:"language"` |
||||||
|
LanguageVersion string `json:"languageVersion"` |
||||||
|
CompilerVersion string `json:"compilerVersion"` |
||||||
|
AbiDefinition interface{} `json:"abiDefinition"` |
||||||
|
UserDoc interface{} `json:"userDoc"` |
||||||
|
DeveloperDoc interface{} `json:"developerDoc"` |
||||||
|
} |
||||||
|
|
||||||
|
type Solidity struct { |
||||||
|
solcPath string |
||||||
|
version 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 |
||||||
|
} |
||||||
|
|
||||||
|
cmd := exec.Command(solcPath, "--version") |
||||||
|
var out bytes.Buffer |
||||||
|
cmd.Stdout = &out |
||||||
|
err = cmd.Run() |
||||||
|
if err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
version := versionRegExp.FindString(out.String()) |
||||||
|
sol = &Solidity{ |
||||||
|
solcPath: solcPath, |
||||||
|
version: version, |
||||||
|
} |
||||||
|
glog.V(logger.Info).Infoln(sol.Info()) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func (sol *Solidity) Info() string { |
||||||
|
return fmt.Sprintf("solc v%s\nSolidity Compiler: %s\n%s", sol.version, sol.solcPath, flair) |
||||||
|
} |
||||||
|
|
||||||
|
func (sol *Solidity) Compile(source string) (contract *Contract, err error) { |
||||||
|
|
||||||
|
if len(source) == 0 { |
||||||
|
err = fmt.Errorf("empty source") |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
wd, err := ioutil.TempDir("", "solc") |
||||||
|
if err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
defer os.RemoveAll(wd) |
||||||
|
|
||||||
|
in := strings.NewReader(source) |
||||||
|
var out bytes.Buffer |
||||||
|
// cwd set to temp dir
|
||||||
|
cmd := exec.Command(sol.solcPath, params...) |
||||||
|
cmd.Dir = wd |
||||||
|
cmd.Stdin = in |
||||||
|
cmd.Stdout = &out |
||||||
|
err = cmd.Run() |
||||||
|
if err != nil { |
||||||
|
err = fmt.Errorf("solc error: %v", err) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
matches, _ := filepath.Glob(wd + "/*.binary") |
||||||
|
if len(matches) < 1 { |
||||||
|
err = fmt.Errorf("solc error: missing code output") |
||||||
|
return |
||||||
|
} |
||||||
|
if len(matches) > 1 { |
||||||
|
err = fmt.Errorf("multi-contract sources are not supported") |
||||||
|
return |
||||||
|
} |
||||||
|
_, file := filepath.Split(matches[0]) |
||||||
|
base := strings.Split(file, ".")[0] |
||||||
|
|
||||||
|
codeFile := path.Join(wd, base+".binary") |
||||||
|
abiDefinitionFile := path.Join(wd, base+".abi") |
||||||
|
userDocFile := path.Join(wd, base+".docuser") |
||||||
|
developerDocFile := path.Join(wd, base+".docdev") |
||||||
|
|
||||||
|
code, err := ioutil.ReadFile(codeFile) |
||||||
|
if err != nil { |
||||||
|
err = fmt.Errorf("error reading compiler output for code: %v", err) |
||||||
|
return |
||||||
|
} |
||||||
|
abiDefinitionJson, err := ioutil.ReadFile(abiDefinitionFile) |
||||||
|
if err != nil { |
||||||
|
err = fmt.Errorf("error reading compiler output for abiDefinition: %v", err) |
||||||
|
return |
||||||
|
} |
||||||
|
var abiDefinition interface{} |
||||||
|
err = json.Unmarshal(abiDefinitionJson, &abiDefinition) |
||||||
|
|
||||||
|
userDocJson, err := ioutil.ReadFile(userDocFile) |
||||||
|
if err != nil { |
||||||
|
err = fmt.Errorf("error reading compiler output for userDoc: %v", err) |
||||||
|
return |
||||||
|
} |
||||||
|
var userDoc interface{} |
||||||
|
err = json.Unmarshal(userDocJson, &userDoc) |
||||||
|
|
||||||
|
developerDocJson, err := ioutil.ReadFile(developerDocFile) |
||||||
|
if err != nil { |
||||||
|
err = fmt.Errorf("error reading compiler output for developerDoc: %v", err) |
||||||
|
return |
||||||
|
} |
||||||
|
var developerDoc interface{} |
||||||
|
err = json.Unmarshal(developerDocJson, &developerDoc) |
||||||
|
|
||||||
|
contract = &Contract{ |
||||||
|
Code: string(code), |
||||||
|
Info: ContractInfo{ |
||||||
|
Source: source, |
||||||
|
Language: "Solidity", |
||||||
|
LanguageVersion: languageVersion, |
||||||
|
CompilerVersion: sol.version, |
||||||
|
AbiDefinition: abiDefinition, |
||||||
|
UserDoc: userDoc, |
||||||
|
DeveloperDoc: developerDoc, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func ExtractInfo(contract *Contract, filename string) (contenthash common.Hash, err error) { |
||||||
|
contractInfo, err := json.Marshal(contract.Info) |
||||||
|
if err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
contenthash = common.BytesToHash(crypto.Sha3(contractInfo)) |
||||||
|
err = ioutil.WriteFile(filename, contractInfo, 0600) |
||||||
|
return |
||||||
|
} |
@ -0,0 +1,89 @@ |
|||||||
|
package compiler |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"io/ioutil" |
||||||
|
"os" |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
source = ` |
||||||
|
contract test { |
||||||
|
/// @notice Will multiply ` + "`a`" + ` by 7.
|
||||||
|
function multiply(uint a) returns(uint d) { |
||||||
|
return a * 7; |
||||||
|
} |
||||||
|
} |
||||||
|
` |
||||||
|
code = "605280600c6000396000f3006000357c010000000000000000000000000000000000000000000000000000000090048063c6888fa114602e57005b60376004356041565b8060005260206000f35b6000600782029050604d565b91905056" |
||||||
|
info = `{"source":"\ncontract test {\n /// @notice Will multiply ` + "`a`" + ` by 7.\n function multiply(uint a) returns(uint d) {\n return a * 7;\n }\n}\n","language":"Solidity","languageVersion":"0","compilerVersion":"0.9.13","abiDefinition":[{"constant":false,"inputs":[{"name":"a","type":"uint256"}],"name":"multiply","outputs":[{"name":"d","type":"uint256"}],"type":"function"}],"userDoc":{"methods":{"multiply(uint256)":{"notice":"Will multiply ` + "`a`" + ` by 7."}}},"developerDoc":{"methods":{}}}` |
||||||
|
|
||||||
|
infohash = common.HexToHash("0xfdb031637e8a1c1891143f8d129ebc7f7c4e4b41ecad8c85abe1756190f74204") |
||||||
|
) |
||||||
|
|
||||||
|
func TestCompiler(t *testing.T) { |
||||||
|
sol, err := New("") |
||||||
|
if err != nil { |
||||||
|
t.Skip("no solc installed") |
||||||
|
} |
||||||
|
contract, err := sol.Compile(source) |
||||||
|
if err != nil { |
||||||
|
t.Errorf("error compiling source. result %v: %v", contract, err) |
||||||
|
return |
||||||
|
} |
||||||
|
if contract.Code != code { |
||||||
|
t.Errorf("wrong code, expected\n%s, got\n%s", code, contract.Code) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestCompileError(t *testing.T) { |
||||||
|
sol, err := New("") |
||||||
|
if err != nil { |
||||||
|
t.Skip("no solc installed") |
||||||
|
} |
||||||
|
contract, err := sol.Compile(source[2:]) |
||||||
|
if err == nil { |
||||||
|
t.Errorf("error expected compiling source. got none. result %v", contract) |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestNoCompiler(t *testing.T) { |
||||||
|
_, err := New("/path/to/solc") |
||||||
|
if err != nil { |
||||||
|
t.Log("solidity quits with error: %v", err) |
||||||
|
} else { |
||||||
|
t.Errorf("no solc installed, but got no error") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestExtractInfo(t *testing.T) { |
||||||
|
var cinfo ContractInfo |
||||||
|
err := json.Unmarshal([]byte(info), &cinfo) |
||||||
|
if err != nil { |
||||||
|
t.Errorf("%v", err) |
||||||
|
} |
||||||
|
contract := &Contract{ |
||||||
|
Code: "", |
||||||
|
Info: cinfo, |
||||||
|
} |
||||||
|
filename := "/tmp/solctest.info.json" |
||||||
|
os.Remove(filename) |
||||||
|
cinfohash, err := ExtractInfo(contract, filename) |
||||||
|
if err != nil { |
||||||
|
t.Errorf("%v", err) |
||||||
|
} |
||||||
|
got, err := ioutil.ReadFile(filename) |
||||||
|
if err != nil { |
||||||
|
t.Errorf("%v", err) |
||||||
|
} |
||||||
|
if string(got) != info { |
||||||
|
t.Errorf("incorrect info.json extracted, expected:\n%s\ngot\n%s", info, string(got)) |
||||||
|
} |
||||||
|
if cinfohash != infohash { |
||||||
|
t.Errorf("content hash for info is incorrect. expected %v, got %v", infohash.Hex(), cinfohash.Hex()) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue