github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/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 "strings" 12 13 "github.com/hyperledger/burrow/acm/acmstate" 14 "github.com/hyperledger/burrow/crypto" 15 "golang.org/x/crypto/sha3" 16 17 "github.com/hyperledger/burrow/execution/evm/asm" 18 "github.com/hyperledger/burrow/logging" 19 hex "github.com/tmthrgd/go-hex" 20 ) 21 22 // SolidityInput is a structure for the solidity compiler input json form, see: 23 // https://solidity.readthedocs.io/en/v0.5.9/using-the-compiler.html#compiler-input-and-output-json-description 24 type SolidityInput struct { 25 Language string `json:"language"` 26 Sources map[string]SolidityInputSource `json:"sources"` 27 Settings struct { 28 Libraries map[string]map[string]string `json:"libraries"` 29 Optimizer struct { 30 Enabled bool `json:"enabled"` 31 } `json:"optimizer"` 32 OutputSelection struct { 33 File struct { 34 OutputType []string `json:"*"` 35 } `json:"*"` 36 } `json:"outputSelection"` 37 } `json:"settings"` 38 } 39 40 // SolidityInputSource should be set for each solidity input source file in SolidityInput 41 type SolidityInputSource struct { 42 Content string `json:"content,omitempty"` 43 Urls []string `json:"urls,omitempty"` 44 } 45 46 // SolidityOutput is a structure for the output of the solidity json output form 47 type SolidityOutput struct { 48 Contracts map[string]map[string]SolidityContract 49 Errors []struct { 50 Component string 51 FormattedMessage string 52 Message string 53 Severity string 54 Type string 55 } 56 } 57 58 // SolidityContract is defined for each contract defined in the solidity source code 59 type SolidityContract struct { 60 Abi json.RawMessage 61 Evm struct { 62 Bytecode ContractCode 63 DeployedBytecode ContractCode 64 } 65 EWasm struct { 66 Wasm string 67 } 68 Devdoc json.RawMessage 69 Userdoc json.RawMessage 70 Metadata string 71 // This is not present in the solidity output, but we add it ourselves 72 // This is map from DeployedBytecode to Metadata. A Solidity contract can create any number 73 // of contracts, which have distinct metadata. This is a map for the deployed code to metdata, 74 // including the first contract itself. 75 MetadataMap []MetadataMap `json:",omitempty"` 76 } 77 78 type ContractCode struct { 79 Object string 80 LinkReferences json.RawMessage 81 } 82 83 // SolidityMetadata is the json field metadata 84 type SolidityMetadata struct { 85 Version string 86 // The solidity compiler needs to tell us it compiles solidity 87 Language string 88 Compiler struct { 89 Version string 90 Keccak256 string 91 } 92 Sources map[string]struct { 93 Keccak256 string 94 Content string 95 Urls []string 96 } 97 // Other fields elided, see https://solidity.readthedocs.io/en/v0.5.10/metadata.html 98 } 99 100 type Metadata struct { 101 ContractName string 102 SourceFile string 103 CompilerVersion string 104 Abi json.RawMessage 105 } 106 107 type MetadataMap struct { 108 DeployedBytecode ContractCode 109 Metadata Metadata 110 } 111 112 type Response struct { 113 Objects []ResponseItem `json:"objects"` 114 Warning string `json:"warning"` 115 Version string `json:"version"` 116 Error string `json:"error"` 117 } 118 119 // Compile response object 120 type ResponseItem struct { 121 Filename string `json:"filename"` 122 Objectname string `json:"objectname"` 123 Contract SolidityContract `json:"binary"` 124 } 125 126 // LoadSolidityContract is the opposite of the .Save() method. This expects the input file 127 // to be in the Solidity json output format 128 func LoadSolidityContract(file string) (*SolidityContract, error) { 129 codeB, err := ioutil.ReadFile(file) 130 if err != nil { 131 return &SolidityContract{}, err 132 } 133 contract := SolidityContract{} 134 err = json.Unmarshal(codeB, &contract) 135 if err != nil { 136 return &SolidityContract{}, err 137 } 138 return &contract, nil 139 } 140 141 // Save persists the contract in its json form to disk 142 func (contract *SolidityContract) Save(dir, file string) error { 143 str, err := json.Marshal(*contract) 144 if err != nil { 145 return err 146 } 147 // This will make the contract file appear atomically 148 // This is important since if we run concurrent jobs, one job could be compiling a solidity 149 // file while another reads the bin file. If write is incomplete, it will result in failures 150 f, err := ioutil.TempFile(dir, "bin.*.txt") 151 if err != nil { 152 return err 153 } 154 defer os.Remove(f.Name()) 155 _, err = f.Write(str) 156 if err != nil { 157 return err 158 } 159 f.Close() 160 return os.Rename(f.Name(), filepath.Join(dir, file)) 161 } 162 163 func link(bytecode string, linkReferences json.RawMessage, libraries map[string]string) (string, error) { 164 var links map[string]map[string][]struct{ Start, Length int } 165 err := json.Unmarshal(linkReferences, &links) 166 if err != nil { 167 return "", err 168 } 169 for _, f := range links { 170 for name, relos := range f { 171 addr, ok := libraries[name] 172 if !ok { 173 return "", fmt.Errorf("library %s is not defined", name) 174 } 175 for _, relo := range relos { 176 if relo.Length != crypto.AddressLength { 177 return "", fmt.Errorf("linkReference should be %d bytes long, not %d", crypto.AddressLength, relo.Length) 178 } 179 if len(addr) != crypto.AddressHexLength { 180 return "", fmt.Errorf("address %s should be %d character long, not %d", addr, crypto.AddressHexLength, len(addr)) 181 } 182 start := relo.Start * 2 183 end := relo.Start*2 + crypto.AddressHexLength 184 if bytecode[start+1] != '_' || bytecode[end-1] != '_' { 185 return "", fmt.Errorf("relocation dummy not found at %d in %s ", relo.Start, bytecode) 186 } 187 bytecode = bytecode[:start] + addr + bytecode[end:] 188 } 189 } 190 } 191 192 return bytecode, nil 193 } 194 195 // Link will replace the unresolved references with the libraries provided 196 func (contract *SolidityContract) Link(libraries map[string]string) error { 197 bin := contract.Evm.Bytecode.Object 198 if strings.Contains(bin, "_") { 199 bin, err := link(bin, contract.Evm.Bytecode.LinkReferences, libraries) 200 if err != nil { 201 return err 202 } 203 contract.Evm.Bytecode.Object = bin 204 } 205 206 // When compiling a solidity file with many contracts contained it, some of those contracts might 207 // never be created by the contract we're current linking. However, Solidity does not tell us 208 // which contracts can be created by a contract. 209 // See: https://github.com/ethereum/solidity/issues/7111 210 // Some of these contracts might have unresolved libraries. We can safely skip those contracts. 211 if contract.MetadataMap != nil { 212 for i, m := range contract.MetadataMap { 213 bin := m.DeployedBytecode.Object 214 if strings.Contains(bin, "_") { 215 bin, err := link(bin, m.DeployedBytecode.LinkReferences, libraries) 216 if err != nil { 217 continue 218 } 219 contract.MetadataMap[i].DeployedBytecode.Object = bin 220 } 221 } 222 } 223 224 return nil 225 } 226 227 func (contract *SolidityContract) Code() (code string) { 228 code = contract.Evm.Bytecode.Object 229 if code == "" { 230 code = contract.EWasm.Wasm 231 } 232 return 233 } 234 235 func EVM(file string, optimize bool, workDir string, libraries map[string]string, logger *logging.Logger) (*Response, error) { 236 input := SolidityInput{Language: "Solidity", Sources: make(map[string]SolidityInputSource)} 237 238 input.Sources[file] = SolidityInputSource{Urls: []string{file}} 239 input.Settings.Optimizer.Enabled = optimize 240 input.Settings.OutputSelection.File.OutputType = []string{"abi", "evm.bytecode.object", "evm.deployedBytecode.object", "evm.bytecode.linkReferences", "metadata", "bin", "devdoc"} 241 input.Settings.Libraries = make(map[string]map[string]string) 242 input.Settings.Libraries[""] = make(map[string]string) 243 244 for l, a := range libraries { 245 input.Settings.Libraries[""][l] = "0x" + a 246 } 247 248 command, err := json.Marshal(input) 249 if err != nil { 250 return nil, err 251 } 252 253 logger.TraceMsg("Command Input", "command", string(command)) 254 result, err := runSolidity(string(command), workDir) 255 if err != nil { 256 return nil, err 257 } 258 logger.TraceMsg("Command Output", "result", result) 259 260 output := SolidityOutput{} 261 err = json.Unmarshal([]byte(result), &output) 262 if err != nil { 263 return nil, err 264 } 265 266 // Collect our ABIs 267 metamap := make([]MetadataMap, 0) 268 for filename, src := range output.Contracts { 269 for contractname, item := range src { 270 var meta SolidityMetadata 271 _ = json.Unmarshal([]byte(item.Metadata), &meta) 272 if item.Evm.DeployedBytecode.Object != "" { 273 metamap = append(metamap, MetadataMap{ 274 DeployedBytecode: item.Evm.DeployedBytecode, 275 Metadata: Metadata{ 276 ContractName: contractname, 277 SourceFile: filename, 278 CompilerVersion: meta.Compiler.Version, 279 Abi: item.Abi, 280 }, 281 }) 282 } 283 } 284 } 285 286 respItemArray := make([]ResponseItem, 0) 287 288 for f, s := range output.Contracts { 289 for contract, item := range s { 290 item.MetadataMap = metamap 291 respItem := ResponseItem{ 292 Filename: f, 293 Objectname: objectName(contract), 294 Contract: item, 295 } 296 respItemArray = append(respItemArray, respItem) 297 } 298 } 299 300 warnings := "" 301 errors := "" 302 for _, msg := range output.Errors { 303 if msg.Type == "Warning" { 304 warnings += msg.FormattedMessage 305 } else { 306 errors += msg.FormattedMessage 307 } 308 } 309 310 for _, re := range respItemArray { 311 logger.TraceMsg("Response formulated", 312 "name", re.Objectname, 313 "bin", re.Contract.Code(), 314 "abi", string(re.Contract.Abi)) 315 } 316 317 resp := Response{ 318 Objects: respItemArray, 319 Warning: warnings, 320 Error: errors, 321 } 322 323 return &resp, nil 324 } 325 326 func WASM(file string, workDir string, logger *logging.Logger) (*Response, error) { 327 shellCmd := exec.Command("solang", "--target", "ewasm", "--standard-json", file) 328 if workDir != "" { 329 shellCmd.Dir = workDir 330 } 331 output, err := shellCmd.CombinedOutput() 332 if err != nil { 333 logger.InfoMsg("solang failed", "output", string(output)) 334 return nil, err 335 } 336 logger.TraceMsg("Command Output", "result", string(output)) 337 338 wasmoutput := SolidityOutput{} 339 err = json.Unmarshal(output, &wasmoutput) 340 if err != nil { 341 return nil, err 342 } 343 344 respItemArray := make([]ResponseItem, 0) 345 346 for f, s := range wasmoutput.Contracts { 347 for contract, item := range s { 348 respItem := ResponseItem{ 349 Filename: f, 350 Objectname: objectName(contract), 351 Contract: item, 352 } 353 respItemArray = append(respItemArray, respItem) 354 } 355 } 356 357 warnings := "" 358 errors := "" 359 for _, msg := range wasmoutput.Errors { 360 if msg.Type == "Warning" { 361 warnings += msg.FormattedMessage 362 } else { 363 errors += msg.FormattedMessage 364 } 365 } 366 367 for _, re := range respItemArray { 368 logger.TraceMsg("Response formulated", 369 "name", re.Objectname, 370 "bin", re.Contract.Evm.Bytecode.Object, 371 "abi", string(re.Contract.Abi)) 372 } 373 374 resp := Response{ 375 Objects: respItemArray, 376 Warning: warnings, 377 Error: errors, 378 } 379 380 return &resp, nil 381 } 382 383 func objectName(contract string) string { 384 if contract == "" { 385 return "" 386 } 387 parts := strings.Split(strings.TrimSpace(contract), ":") 388 return parts[len(parts)-1] 389 } 390 391 func runSolidity(jsonCmd string, workDir string) (string, error) { 392 buf := bytes.NewBufferString(jsonCmd) 393 shellCmd := exec.Command("solc", "--standard-json", "--allow-paths", "/") 394 if workDir != "" { 395 shellCmd.Dir = workDir 396 } 397 shellCmd.Stdin = buf 398 output, err := shellCmd.CombinedOutput() 399 s := string(output) 400 return s, err 401 } 402 403 func PrintResponse(resp Response, cli bool, logger *logging.Logger) { 404 if resp.Error != "" { 405 logger.InfoMsg("solidity error", "errors", resp.Error) 406 } else { 407 for _, r := range resp.Objects { 408 logger.InfoMsg("Response", 409 "name", r.Objectname, 410 "bin", r.Contract.Code(), 411 "abi", string(r.Contract.Abi[:]), 412 "link", string(r.Contract.Evm.Bytecode.LinkReferences[:]), 413 ) 414 } 415 } 416 } 417 418 // GetMetadata get the CodeHashes + Abis for the generated Code. So, we have a map for all the possible contracts codes hashes to abis 419 func (contract *SolidityContract) GetMetadata(logger *logging.Logger) (map[acmstate.CodeHash]string, error) { 420 res := make(map[acmstate.CodeHash]string) 421 if contract.Evm.DeployedBytecode.Object == "" { 422 return nil, nil 423 } 424 425 for _, m := range contract.MetadataMap { 426 if strings.Contains(m.DeployedBytecode.Object, "_") { 427 continue 428 } 429 runtime, err := hex.DecodeString(m.DeployedBytecode.Object) 430 if err != nil { 431 return nil, err 432 } 433 434 bs, err := json.Marshal(m.Metadata) 435 if err != nil { 436 return nil, err 437 } 438 439 hash := sha3.NewLegacyKeccak256() 440 hash.Write(runtime) 441 var codehash acmstate.CodeHash 442 copy(codehash[:], hash.Sum(nil)) 443 logger.TraceMsg("Found metadata", 444 "code", fmt.Sprintf("%X", runtime), 445 "code hash", fmt.Sprintf("%X", codehash), 446 "meta", string(bs)) 447 res[codehash] = string(bs) 448 } 449 return res, nil 450 } 451 452 // GetDeployCodeHash deals with the issue described in https://github.com/ethereum/solidity/issues/7101 453 // When a library contract (one declared with "libary { }" rather than "contract { }"), the deployed code 454 // will not match what the solidity compiler said it would be. This is done to implement "call protection"; 455 // library contracts are only supposed to be called from our solidity contracts, not directly. To prevent 456 // this, the library deployed code compares the callee address with the contract address itself. If it equal, 457 // it calls revert. 458 // The library contract address is only known post-deploy so this issue can only be handled post-deploy. This 459 // is why this is not dealt with during deploy time. 460 func GetDeployCodeHash(code []byte, address crypto.Address) []byte { 461 if bytes.HasPrefix(code, append([]byte{byte(asm.PUSH20)}, address.Bytes()...)) { 462 code = append([]byte{byte(asm.PUSH20)}, append(make([]byte, crypto.AddressLength), code[crypto.AddressLength+1:]...)...) 463 } 464 465 hash := sha3.NewLegacyKeccak256() 466 hash.Write(code) 467 return hash.Sum(nil) 468 }