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