github.com/Tri-stone/burrow@v0.25.0/deploy/compile/compilers.go (about) 1 package compile 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "regexp" 12 "strings" 13 14 "github.com/hyperledger/burrow/crypto" 15 "github.com/hyperledger/burrow/logging" 16 ) 17 18 type SolidityInput struct { 19 Language string `json:"language"` 20 Sources map[string]SolidityInputSource `json:"sources"` 21 Settings struct { 22 Libraries map[string]map[string]string `json:"libraries"` 23 Optimizer struct { 24 Enabled bool `json:"enabled"` 25 } `json:"optimizer"` 26 OutputSelection struct { 27 File struct { 28 OutputType []string `json:"*"` 29 } `json:"*"` 30 } `json:"outputSelection"` 31 } `json:"settings"` 32 } 33 34 type SolidityInputSource struct { 35 Content string `json:"content,omitempty"` 36 Urls []string `json:"urls,omitempty"` 37 } 38 39 type SolidityOutput struct { 40 Contracts map[string]map[string]SolidityContract 41 Errors []struct { 42 Component string 43 FormattedMessage string 44 Message string 45 Severity string 46 Type string 47 } 48 } 49 50 type SolidityContract struct { 51 Abi json.RawMessage 52 Evm struct { 53 Bytecode struct { 54 Object string 55 Opcodes string 56 LinkReferences json.RawMessage 57 } 58 } 59 Devdoc json.RawMessage 60 Userdoc json.RawMessage 61 Metadata string 62 } 63 64 type Response struct { 65 Objects []ResponseItem `json:"objects"` 66 Warning string `json:"warning"` 67 Version string `json:"version"` 68 Error string `json:"error"` 69 } 70 71 // Compile response object 72 type ResponseItem struct { 73 Filename string `json:"filename"` 74 Objectname string `json:"objectname"` 75 Contract SolidityContract `json:"binary"` 76 } 77 78 func LoadSolidityContract(file string) (*SolidityContract, error) { 79 codeB, err := ioutil.ReadFile(file) 80 if err != nil { 81 return &SolidityContract{}, err 82 } 83 contract := SolidityContract{} 84 err = json.Unmarshal(codeB, &contract) 85 if err != nil { 86 return &SolidityContract{}, err 87 } 88 return &contract, nil 89 } 90 91 func (contract *SolidityContract) Save(dir, file string) error { 92 str, err := json.Marshal(*contract) 93 if err != nil { 94 return err 95 } 96 // This will make the contract file appear atomically 97 // This is important since if we run concurrent jobs, one job could be compiling a solidity 98 // file while another reads the bin file. If write is incomplete, it will result in failures 99 f, err := ioutil.TempFile(dir, "bin.*.txt") 100 if err != nil { 101 return err 102 } 103 defer os.Remove(f.Name()) 104 _, err = f.Write(str) 105 if err != nil { 106 return err 107 } 108 f.Close() 109 return os.Rename(f.Name(), filepath.Join(dir, file)) 110 } 111 112 func (contract *SolidityContract) Link(libraries map[string]string) error { 113 bin := contract.Evm.Bytecode.Object 114 if !strings.Contains(bin, "_") { 115 return nil 116 } 117 var links map[string]map[string][]struct{ Start, Length int } 118 err := json.Unmarshal(contract.Evm.Bytecode.LinkReferences, &links) 119 if err != nil { 120 return err 121 } 122 for _, f := range links { 123 for name, relos := range f { 124 addr, ok := libraries[name] 125 if !ok { 126 return fmt.Errorf("library %s is not defined", name) 127 } 128 for _, relo := range relos { 129 if relo.Length != crypto.AddressLength { 130 return fmt.Errorf("linkReference should be %d bytes long, not %d", crypto.AddressLength, relo.Length) 131 } 132 if len(addr) != crypto.AddressHexLength { 133 return fmt.Errorf("address %s should be %d character long, not %d", addr, crypto.AddressHexLength, len(addr)) 134 } 135 start := relo.Start * 2 136 end := relo.Start*2 + crypto.AddressHexLength 137 if bin[start+1] != '_' || bin[end-1] != '_' { 138 return fmt.Errorf("relocation dummy not found at %d in %s ", relo.Start, bin) 139 } 140 bin = bin[:start] + addr + bin[end:] 141 } 142 } 143 } 144 145 contract.Evm.Bytecode.Object = bin 146 147 return nil 148 } 149 150 func Compile(file string, optimize bool, workDir string, libraries map[string]string, logger *logging.Logger) (*Response, error) { 151 input := SolidityInput{Language: "Solidity", Sources: make(map[string]SolidityInputSource)} 152 153 input.Sources[file] = SolidityInputSource{Urls: []string{file}} 154 input.Settings.Optimizer.Enabled = optimize 155 input.Settings.OutputSelection.File.OutputType = []string{"abi", "evm.bytecode.linkReferences", "metadata", "bin", "devdoc"} 156 input.Settings.Libraries = make(map[string]map[string]string) 157 input.Settings.Libraries[""] = make(map[string]string) 158 159 if libraries != nil { 160 for l, a := range libraries { 161 input.Settings.Libraries[""][l] = "0x" + a 162 } 163 } 164 165 command, err := json.Marshal(input) 166 if err != nil { 167 return nil, err 168 } 169 170 logger.TraceMsg("Command Input", "command", string(command)) 171 result, err := runSolidity(string(command), workDir) 172 if err != nil { 173 return nil, err 174 } 175 logger.TraceMsg("Command Output", "result", result) 176 177 output := SolidityOutput{} 178 err = json.Unmarshal([]byte(result), &output) 179 if err != nil { 180 return nil, err 181 } 182 183 respItemArray := make([]ResponseItem, 0) 184 185 for f, s := range output.Contracts { 186 for contract, item := range s { 187 respItem := ResponseItem{ 188 Filename: f, 189 Objectname: objectName(contract), 190 Contract: item, 191 } 192 respItemArray = append(respItemArray, respItem) 193 } 194 } 195 196 warnings := "" 197 errors := "" 198 for _, msg := range output.Errors { 199 if msg.Type == "Warning" { 200 warnings += msg.FormattedMessage 201 } else { 202 errors += msg.FormattedMessage 203 } 204 } 205 206 for _, re := range respItemArray { 207 logger.TraceMsg("Response formulated", 208 "name", re.Objectname, 209 "bin", re.Contract.Evm.Bytecode.Object, 210 "abi", string(re.Contract.Abi)) 211 } 212 213 resp := Response{ 214 Objects: respItemArray, 215 Warning: warnings, 216 Error: errors, 217 } 218 219 return &resp, nil 220 } 221 222 func objectName(contract string) string { 223 if contract == "" { 224 return "" 225 } 226 parts := strings.Split(strings.TrimSpace(contract), ":") 227 return parts[len(parts)-1] 228 } 229 230 func runSolidity(jsonCmd string, workDir string) (string, error) { 231 buf := bytes.NewBufferString(jsonCmd) 232 shellCmd := exec.Command("solc", "--standard-json", "--allow-paths", "/") 233 if workDir != "" { 234 shellCmd.Dir = workDir 235 } 236 shellCmd.Stdin = buf 237 output, err := shellCmd.CombinedOutput() 238 s := string(output) 239 return s, err 240 } 241 242 func PrintResponse(resp Response, cli bool, logger *logging.Logger) { 243 if resp.Error != "" { 244 logger.InfoMsg("solidity error", "errors", resp.Error) 245 } else { 246 for _, r := range resp.Objects { 247 logger.InfoMsg("Response", 248 "name", r.Objectname, 249 "bin", r.Contract.Evm.Bytecode, 250 "abi", string(r.Contract.Abi[:]), 251 "link", string(r.Contract.Evm.Bytecode.LinkReferences[:]), 252 ) 253 } 254 } 255 } 256 257 func extractObjectNames(script []byte) ([]string, error) { 258 regExpression, err := regexp.Compile("(contract|library) (.+?) (is)?(.+?)?({)") 259 if err != nil { 260 return nil, err 261 } 262 objectNamesList := regExpression.FindAllSubmatch(script, -1) 263 var objects []string 264 for _, objectNames := range objectNamesList { 265 objects = append(objects, string(objectNames[2])) 266 } 267 return objects, nil 268 }