-
Notifications
You must be signed in to change notification settings - Fork 44
/
compiler.go
215 lines (168 loc) · 4.7 KB
/
compiler.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
package solar
import (
"bytes"
"encoding/json"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/pkg/errors"
"github.com/qtumproject/solar/contract"
)
type rawCompilerOutput struct {
Version string
Contracts map[string]contract.RawCompiledContract
}
func (o *rawCompilerOutput) CompiledContracts() contract.CompiledContracts {
contracts := make(contract.CompiledContracts)
for name, rawContract := range o.Contracts {
if len(rawContract.Bin) == 0 {
continue
}
// name: filepath:ContractName
contractName := name
parts := strings.Split(name, ":")
if len(parts) == 2 {
contractName = parts[1]
}
compiledContract := &contract.CompiledContract{
Source: name,
Name: contractName,
Bin: rawContract.Bin,
BinKeccak256: rawContract.BinHash256(),
ABI: rawContract.Metadata.Output.ABI,
}
contracts[name] = compiledContract
}
return contracts
}
type CompilerError struct {
SourceFile string
ErrorOutput string
}
func (err *CompilerError) Error() string {
return err.ErrorOutput
}
type CompilerOptions struct {
NoOptimize bool
AllowPaths []string
}
type Compiler struct {
// only used for error reporting
Filename string
Opts CompilerOptions
Repo *contract.ContractsRepository
compiledContracts contract.CompiledContracts
}
func (c *Compiler) mainContractSource() string {
mainContractName := basenameNoExt(c.Filename)
return c.Filename + ":" + mainContractName
}
func (c *Compiler) mainContractName() string {
mainContractName := basenameNoExt(c.Filename)
return mainContractName
}
// Compile returns only the contract that has the same name as the source file
func (c *Compiler) Compile() (*contract.CompiledContract, error) {
contracts, err := c.getCompiledContracts()
if err != nil {
return nil, err
}
contractSrc := c.mainContractSource()
contract, ok := contracts[contractSrc]
if !ok {
return nil, errors.Errorf("cannot find contract: %s", c.mainContractName())
}
return contract, nil
}
func (c *Compiler) RelatedContracts() (contract.CompiledContracts, error) {
contracts, err := c.getCompiledContracts()
if err != nil {
return nil, err
}
contractSrc := c.mainContractSource()
relatedContracts := make(contract.CompiledContracts)
for name, contract := range contracts {
if name == contractSrc {
continue
}
relatedContracts[name] = contract
}
return relatedContracts, nil
}
// CompileAll returns all contracts in a source file
func (c *Compiler) compileAll() (contract.CompiledContracts, error) {
_, err := os.Stat(c.Filename)
if err != nil && os.IsNotExist(err) {
return nil, errors.Errorf("file not found: %s", c.Filename)
}
output, err := c.execSolc()
if err != nil {
return nil, err
}
return output.CompiledContracts(), nil
}
func (c *Compiler) getCompiledContracts() (contract.CompiledContracts, error) {
if c.compiledContracts != nil {
return c.compiledContracts, nil
}
contracts, err := c.compileAll()
if err != nil {
return nil, err
}
c.compiledContracts = contracts
return contracts, nil
}
func (c *Compiler) execSolc() (*rawCompilerOutput, error) {
opts := c.Opts
filename := c.Filename
args := []string{filename, "--combined-json", "bin,metadata", "--evm-version", "constantinople"}
if !opts.NoOptimize {
args = append(args, "--optimize")
}
if len(opts.AllowPaths) > 0 {
var expandedPaths []string
for _, allowPath := range opts.AllowPaths {
expandedPath, err := filepath.EvalSymlinks(allowPath)
if err != nil {
log.Println("allow path error:", err, allowPath)
continue
}
expandedPaths = append(expandedPaths, expandedPath)
}
args = append(args, "--allow-paths", strings.Join(expandedPaths, ","))
}
// libraries linkage support
if c.Repo != nil && len(c.Repo.Libraries) > 0 {
var linkages []string
// A linkable library is specified with a string that looks like:
// contracts/SafeMathLib.sol:SafeMathLib:4242424242424242424242424242424242424242
for _, lib := range c.Repo.Libraries {
linkages = append(linkages, fmt.Sprintf("%s:%s:%s", lib.DeployName, lib.Name, lib.Address))
}
args = append(args, "--libraries", strings.Join(linkages, ","))
}
var stderr bytes.Buffer
fmt.Printf("exec: solc %v\n", args)
cmd := exec.Command("solc", args...)
cmd.Stderr = &stderr
stdout, err := cmd.Output()
if _, hasExitErr := err.(*exec.ExitError); hasExitErr {
return nil, &CompilerError{
SourceFile: filename,
ErrorOutput: stderr.String(),
}
}
if err != nil {
return nil, errors.Wrap(err, "exec")
}
output := &rawCompilerOutput{}
// fmt.Println("solc output", string(stdout))
err = json.Unmarshal(stdout, output)
if err != nil {
return nil, errors.Wrap(err, "parse solc output")
}
return output, nil
}