github.com/eris-ltd/erisdb@v0.25.0/deploy/jobs/jobs_contracts.go (about) 1 package jobs 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "strings" 8 9 "github.com/hyperledger/burrow/execution/errors" 10 "github.com/hyperledger/burrow/execution/exec" 11 "github.com/hyperledger/burrow/logging" 12 13 "github.com/hyperledger/burrow/crypto" 14 compilers "github.com/hyperledger/burrow/deploy/compile" 15 "github.com/hyperledger/burrow/deploy/def" 16 "github.com/hyperledger/burrow/deploy/util" 17 "github.com/hyperledger/burrow/execution/evm/abi" 18 "github.com/hyperledger/burrow/txs/payload" 19 hex "github.com/tmthrgd/go-hex" 20 ) 21 22 var errCodeMissing = fmt.Errorf("error: no binary code found in contract. Contract may be abstract due to missing function body or inherited function signatures not matching.") 23 24 func BuildJob(build *def.Build, deployScript *def.Playbook, resp *compilers.Response, logger *logging.Logger) (result string, err error) { 25 // assemble contract 26 contractPath, err := findContractFile(build.Contract, deployScript.BinPath, deployScript.Path) 27 if err != nil { 28 return 29 } 30 31 logger.InfoMsg("Contract path", "path", contractPath) 32 33 // normal compilation/deploy sequence 34 if resp == nil { 35 logger.InfoMsg("Error compiling contracts: Missing compiler result") 36 return "", fmt.Errorf("internal error") 37 } else if resp.Error != "" { 38 logger.InfoMsg("Error compiling contracts", "Language error", resp.Error) 39 return "", fmt.Errorf("%v", resp.Error) 40 } else if resp.Warning != "" { 41 logger.InfoMsg("Warning during contraction compilation", "warning", resp.Warning) 42 } 43 44 // Save 45 binP := build.BinPath 46 if binP == "" { 47 binP = deployScript.BinPath 48 49 if _, err := os.Stat(binP); os.IsNotExist(err) { 50 if err := os.Mkdir(binP, 0775); err != nil { 51 return "", err 52 } 53 } 54 } 55 56 for _, res := range resp.Objects { 57 switch build.Instance { 58 case "": 59 if res.Filename != build.Contract { 60 logger.TraceMsg("Ignoring output for differint solidity file", "found", res.Filename, "expected", build.Contract) 61 continue 62 } 63 case "all": 64 default: 65 if res.Objectname != build.Instance { 66 continue 67 } 68 } 69 70 // saving binary 71 logger.InfoMsg("Saving Binary", "name", res.Objectname, "dir", binP) 72 73 err = res.Contract.Save(binP, fmt.Sprintf("%s.bin", res.Objectname)) 74 if err != nil { 75 return "", err 76 } 77 78 if build.Store != "" { 79 dir := filepath.Dir(build.Store) 80 file := filepath.Base(build.Store) 81 82 err = res.Contract.Save(dir, file) 83 if err != nil { 84 return "", err 85 } 86 } 87 } 88 89 return "", nil 90 } 91 92 func FormulateDeployJob(deploy *def.Deploy, do *def.DeployArgs, deployScript *def.Playbook, client *def.Client, intermediate interface{}, logger *logging.Logger) (txs []*payload.CallTx, contracts []*compilers.ResponseItem, err error) { 93 deploy.Libraries, _ = util.PreProcessLibs(deploy.Libraries, do, deployScript, client, logger) 94 // trim the extension and path 95 contractName := filepath.Base(deploy.Contract) 96 contractName = strings.TrimSuffix(contractName, filepath.Ext(contractName)) 97 98 // Use defaults 99 deploy.Source = useDefault(deploy.Source, deployScript.Account) 100 deploy.Instance = useDefault(deploy.Instance, contractName) 101 deploy.Amount = useDefault(deploy.Amount, do.DefaultAmount) 102 deploy.Fee = useDefault(deploy.Fee, do.DefaultFee) 103 deploy.Gas = useDefault(deploy.Gas, do.DefaultGas) 104 105 // assemble contract 106 contractPath, err := findContractFile(deploy.Contract, deployScript.BinPath, deployScript.Path) 107 if err != nil { 108 return 109 } 110 111 txs = make([]*payload.CallTx, 0) 112 libs := make(map[string]string) 113 var list []string 114 if strings.Contains(deploy.Libraries, " ") { 115 list = strings.Split(deploy.Libraries, " ") 116 } else { 117 list = strings.Split(deploy.Libraries, ",") 118 } 119 for _, l := range list { 120 if l != "" { 121 v := strings.Split(l, ":") 122 if len(v) != 2 { 123 return nil, nil, fmt.Errorf("library %s should be contract:address format", l) 124 } 125 libs[v[0]] = v[1] 126 } 127 } 128 129 contracts = make([]*compilers.ResponseItem, 0) 130 131 // compile 132 if filepath.Ext(deploy.Contract) != ".sol" { 133 logger.InfoMsg("Binary file detected. Using binary deploy sequence.", "Binary path", contractPath) 134 135 contract, err := compilers.LoadSolidityContract(contractPath) 136 if err != nil { 137 return nil, nil, fmt.Errorf("unable to read contract %s: %v", contractPath, err) 138 } 139 err = contract.Link(libs) 140 if err != nil { 141 return nil, nil, fmt.Errorf("unable to link contract %s: %v", contractPath, err) 142 } 143 contractCode := contract.Evm.Bytecode.Object 144 145 mergeAbiSpecBytes(client, contract.Abi) 146 147 if deploy.Data != nil { 148 _, callDataArray, err := util.PreProcessInputData("", deploy.Data, do, deployScript, client, true, logger) 149 if err != nil { 150 return nil, nil, err 151 } 152 packedBytes, _, err := abi.EncodeFunctionCall(string(contract.Abi), "", logger, callDataArray...) 153 if err != nil { 154 return nil, nil, err 155 } 156 callData := hex.EncodeToString(packedBytes) 157 contractCode = contractCode + callData 158 } 159 160 tx, err := deployTx(client, deploy, contractName, string(contractCode), logger) 161 if err != nil { 162 return nil, nil, fmt.Errorf("could not deploy binary contract: %v", err) 163 } 164 txs = []*payload.CallTx{tx} 165 contracts = append(contracts, &compilers.ResponseItem{Filename: contractPath, Objectname: contractName, Contract: *contract}) 166 } else { 167 contractPath = deploy.Contract 168 logger.InfoMsg("Contract path", "path", contractPath) 169 // normal compilation/deploy sequence 170 171 resp, err := getCompilerWork(intermediate) 172 if err != nil { 173 return nil, nil, err 174 } 175 176 if resp == nil { 177 logger.InfoMsg("Error compiling contracts: Missing compiler result") 178 return nil, nil, fmt.Errorf("internal error") 179 } else if resp.Error != "" { 180 logger.InfoMsg("Error compiling contracts: Language error:", "error", resp.Error) 181 return nil, nil, fmt.Errorf("%v", resp.Error) 182 } else if resp.Warning != "" { 183 logger.InfoMsg("Warning during contract compilation", "warning", resp.Warning) 184 } 185 // loop through objects returned from compiler 186 switch { 187 case len(resp.Objects) == 1: 188 response := resp.Objects[0] 189 logger.InfoMsg("Deploying the single contract from solidity file", 190 "path", contractPath, 191 "abi", string(response.Contract.Abi), 192 "bin", response.Contract.Evm.Bytecode.Object) 193 if response.Contract.Evm.Bytecode.Object == "" { 194 return nil, nil, errCodeMissing 195 } 196 mergeAbiSpecBytes(client, response.Contract.Abi) 197 198 tx, err := deployContract(deploy, do, deployScript, client, response, libs, logger) 199 if err != nil { 200 return nil, nil, err 201 } 202 203 txs = []*payload.CallTx{tx} 204 contracts = append(contracts, &resp.Objects[0]) 205 case deploy.Instance == "all": 206 logger.InfoMsg("Deploying all contracts", "path", contractPath) 207 var baseObj *payload.CallTx 208 var baseContract *compilers.ResponseItem 209 deployedCount := 0 210 for i, response := range resp.Objects { 211 if response.Contract.Evm.Bytecode.Object == "" { 212 continue 213 } 214 mergeAbiSpecBytes(client, response.Contract.Abi) 215 tx, err := deployContract(deploy, do, deployScript, client, response, libs, logger) 216 if err != nil { 217 return nil, nil, err 218 } 219 deployedCount++ 220 if strings.ToLower(response.Objectname) == strings.ToLower(strings.TrimSuffix(filepath.Base(deploy.Contract), filepath.Ext(filepath.Base(deploy.Contract)))) { 221 baseObj = tx 222 baseContract = &resp.Objects[i] 223 } else { 224 txs = append(txs, tx) 225 contracts = append(contracts, &resp.Objects[i]) 226 } 227 } 228 229 // Make sure the Contact which matches the filename is last, so that addres is used 230 if baseObj != nil { 231 txs = append(txs, baseObj) 232 contracts = append(contracts, baseContract) 233 } else if deployedCount == 0 { 234 return nil, nil, errCodeMissing 235 } 236 237 default: 238 logger.InfoMsg("Deploying a single contract that matches", "contract", deploy.Instance) 239 for i, response := range resp.Objects { 240 if response.Contract.Evm.Bytecode.Object == "" || 241 response.Filename != deploy.Contract { 242 continue 243 } 244 if matchInstanceName(response.Objectname, deploy.Instance) { 245 if response.Contract.Evm.Bytecode.Object == "" { 246 return nil, nil, errCodeMissing 247 } 248 logger.InfoMsg("Deploy contract", 249 "contract", response.Objectname, 250 "Abi", string(response.Contract.Abi), 251 "Bin", response.Contract.Evm.Bytecode.Object) 252 tx, err := deployContract(deploy, do, deployScript, client, response, libs, logger) 253 if err != nil { 254 return nil, nil, err 255 } 256 txs = append(txs, tx) 257 // make sure we copy response, as it is the loop variable and will be overwritten 258 contracts = append(contracts, &resp.Objects[i]) 259 } 260 } 261 } 262 } 263 264 return 265 } 266 267 func DeployJob(deploy *def.Deploy, do *def.DeployArgs, script *def.Playbook, client *def.Client, txs []*payload.CallTx, contracts []*compilers.ResponseItem, logger *logging.Logger) (result string, err error) { 268 // saving contract 269 // additional data may be sent along with the contract 270 // these are naively added to the end of the contract code using standard 271 // mint packing 272 273 for i, tx := range txs { 274 // Sign, broadcast, display 275 contractAddress, err := deployFinalize(do, client, tx, logger) 276 if err != nil { 277 return "", fmt.Errorf("Error finalizing contract deploy %s: %v", deploy.Contract, err) 278 } 279 280 // saving contract/library abi at abi/address 281 if contracts != nil && contractAddress != nil { 282 contract := contracts[i].Contract 283 // saving binary 284 logger.TraceMsg("Saving Binary", "address", contractAddress.String()) 285 err = contract.Save(script.BinPath, fmt.Sprintf("%s.bin", contractAddress.String())) 286 if err != nil { 287 return "", err 288 } 289 result = contractAddress.String() 290 } else { 291 // we shouldn't reach this point because we should have an error before this. 292 return "", fmt.Errorf("The contract did not deploy. Unable to save abi to abi/contractAddress.") 293 } 294 } 295 296 return result, nil 297 } 298 299 func matchInstanceName(objectName, deployInstance string) bool { 300 if objectName == "" { 301 return false 302 } 303 // Ignore the filename component that newer versions of Solidity include in object name 304 305 objectNameParts := strings.Split(objectName, ":") 306 deployInstanceParts := strings.Split(deployInstance, "/") 307 return strings.ToLower(objectNameParts[len(objectNameParts)-1]) == strings.ToLower(deployInstanceParts[len(deployInstanceParts)-1]) 308 } 309 310 func findContractFile(contract, binPath string, deployPath string) (string, error) { 311 contractPaths := []string{ 312 contract, 313 filepath.Join(binPath, contract), 314 filepath.Join(binPath, filepath.Base(contract)), 315 filepath.Join(deployPath, contract), 316 filepath.Join(deployPath, filepath.Base(contract)), 317 } 318 319 for _, p := range contractPaths { 320 if _, err := os.Stat(p); err == nil { 321 return p, nil 322 } 323 } 324 325 return "", fmt.Errorf("Could not find contract in any of %v", contractPaths) 326 } 327 328 // TODO [rj] refactor to remove [contractPath] from functions signature => only used in a single error throw. 329 func deployContract(deploy *def.Deploy, do *def.DeployArgs, script *def.Playbook, client *def.Client, compilersResponse compilers.ResponseItem, libs map[string]string, logger *logging.Logger) (*payload.CallTx, error) { 330 contract := compilersResponse.Contract 331 contractName := compilersResponse.Objectname 332 logger.InfoMsg("Saving Binary", "contract", contractName) 333 err := contract.Save(script.BinPath, fmt.Sprintf("%s.bin", contractName)) 334 if err != nil { 335 return nil, err 336 } 337 338 if deploy.Store != "" { 339 dir := filepath.Dir(deploy.Store) 340 file := filepath.Base(deploy.Store) 341 342 err = contract.Save(dir, file) 343 if err != nil { 344 return nil, err 345 } 346 } 347 348 err = contract.Link(libs) 349 if err != nil { 350 return nil, err 351 } 352 contractCode := contract.Evm.Bytecode.Object 353 354 if deploy.Data != nil { 355 _, callDataArray, err := util.PreProcessInputData(compilersResponse.Objectname, deploy.Data, do, script, client, true, logger) 356 if err != nil { 357 return nil, err 358 } 359 packedBytes, _, err := abi.EncodeFunctionCall(string(compilersResponse.Contract.Abi), "", logger, callDataArray...) 360 if err != nil { 361 return nil, err 362 } 363 callData := hex.EncodeToString(packedBytes) 364 contractCode = contractCode + callData 365 } 366 367 return deployTx(client, deploy, compilersResponse.Objectname, contractCode, logger) 368 } 369 370 func deployTx(client *def.Client, deploy *def.Deploy, contractName, contractCode string, logger *logging.Logger) (*payload.CallTx, error) { 371 // Deploy contract 372 logger.TraceMsg("Deploying Contract", 373 "contract", contractName, 374 "source", deploy.Source, 375 "code", contractCode, 376 "chain", client.ChainAddress) 377 378 return client.Call(&def.CallArg{ 379 Input: deploy.Source, 380 Amount: deploy.Amount, 381 Fee: deploy.Fee, 382 Gas: deploy.Gas, 383 Data: contractCode, 384 Sequence: deploy.Sequence, 385 }, logger) 386 } 387 388 func FormulateCallJob(call *def.Call, do *def.DeployArgs, deployScript *def.Playbook, client *def.Client, logger *logging.Logger) (tx *payload.CallTx, err error) { 389 var callData string 390 var callDataArray []interface{} 391 //todo: find a way to call the fallback function here 392 call.Function, callDataArray, err = util.PreProcessInputData(call.Function, call.Data, do, deployScript, client, false, logger) 393 if err != nil { 394 return nil, err 395 } 396 // Use default 397 call.Source = useDefault(call.Source, deployScript.Account) 398 call.Amount = useDefault(call.Amount, do.DefaultAmount) 399 call.Fee = useDefault(call.Fee, do.DefaultFee) 400 call.Gas = useDefault(call.Gas, do.DefaultGas) 401 402 // formulate call 403 var packedBytes []byte 404 var funcSpec *abi.FunctionSpec 405 logger.TraceMsg("Looking for ABI in", "path", deployScript.BinPath, "bin", call.Bin, "dest", call.Destination) 406 if call.Bin != "" { 407 packedBytes, funcSpec, err = abi.EncodeFunctionCallFromFile(call.Bin, deployScript.BinPath, call.Function, logger, callDataArray...) 408 callData = hex.EncodeToString(packedBytes) 409 } 410 if call.Bin == "" || err != nil { 411 packedBytes, funcSpec, err = abi.EncodeFunctionCallFromFile(call.Destination, deployScript.BinPath, call.Function, logger, callDataArray...) 412 callData = hex.EncodeToString(packedBytes) 413 } 414 if err != nil { 415 if call.Function == "()" { 416 logger.InfoMsg("Calling the fallback function") 417 } else { 418 err = util.ABIErrorHandler(err, call, nil, logger) 419 return 420 } 421 } 422 423 if funcSpec.Constant { 424 logger.InfoMsg("Function call to constant function, query-contract type job will be faster than call") 425 } 426 427 logger.InfoMsg("Calling", 428 "destination", call.Destination, 429 "function", call.Function, 430 "data", callData) 431 432 return client.Call(&def.CallArg{ 433 Input: call.Source, 434 Amount: call.Amount, 435 Address: call.Destination, 436 Fee: call.Fee, 437 Gas: call.Gas, 438 Data: callData, 439 Sequence: call.Sequence, 440 }, logger) 441 } 442 443 func CallJob(call *def.Call, tx *payload.CallTx, do *def.DeployArgs, playbook *def.Playbook, client *def.Client, logger *logging.Logger) (string, []*abi.Variable, error) { 444 var err error 445 446 // Sign, broadcast, display 447 txe, err := client.SignAndBroadcast(tx, logger) 448 if err != nil { 449 var err = util.ChainErrorHandler(payload.InputsString(tx.GetInputs()), err, logger) 450 return "", nil, err 451 } 452 453 if txe.Exception != nil { 454 switch txe.Exception.ErrorCode() { 455 case errors.ErrorCodeExecutionReverted: 456 message, err := abi.UnpackRevert(txe.Result.Return) 457 if err != nil { 458 return "", nil, err 459 } 460 if message != nil { 461 logger.InfoMsg("Transaction reverted with reason", 462 "Revert Reason", *message) 463 return *message, nil, txe.Exception.AsError() 464 } else { 465 logger.InfoMsg("Transaction reverted with no reason") 466 return "", nil, txe.Exception.AsError() 467 } 468 default: 469 logger.InfoMsg("Transaction execution exception") 470 return "", nil, txe.Exception.AsError() 471 } 472 } 473 474 logEvents(txe, client, logger) 475 476 var result string 477 478 // Formally process the return 479 if txe.GetResult().GetReturn() != nil { 480 logger.TraceMsg("Decoding Raw Result", "return", hex.EncodeUpperToString(txe.Result.Return)) 481 482 if call.Bin != "" { 483 call.Variables, err = abi.DecodeFunctionReturnFromFile(call.Bin, playbook.BinPath, call.Function, txe.Result.Return, logger) 484 } 485 if call.Bin == "" || err != nil { 486 call.Variables, err = abi.DecodeFunctionReturnFromFile(call.Destination, playbook.BinPath, call.Function, txe.Result.Return, logger) 487 } 488 if err != nil { 489 return "", nil, err 490 } 491 logger.TraceMsg("Variables", "call", call.Variables) 492 result = util.GetReturnValue(call.Variables, logger) 493 if result != "" { 494 logger.InfoMsg("Return value", "value", result) 495 } else { 496 logger.InfoMsg("No return value") 497 } 498 } else { 499 logger.InfoMsg("No return result value") 500 } 501 502 if call.Save == "tx" { 503 logger.InfoMsg("Saving tx hash instead of contract return") 504 result = fmt.Sprintf("%X", txe.Receipt.TxHash) 505 } 506 507 return result, call.Variables, nil 508 } 509 510 func deployFinalize(do *def.DeployArgs, client *def.Client, tx payload.Payload, logger *logging.Logger) (*crypto.Address, error) { 511 txe, err := client.SignAndBroadcast(tx, logger) 512 if err != nil { 513 return nil, util.ChainErrorHandler(payload.InputsString(tx.GetInputs()), err, logger) 514 } 515 516 if err := util.ReadTxSignAndBroadcast(txe, err, logger); err != nil { 517 return nil, err 518 } 519 520 // The contructor can generate events 521 logEvents(txe, client, logger) 522 523 if !txe.Receipt.CreatesContract || txe.Receipt.ContractAddress == crypto.ZeroAddress { 524 // Shouldn't get ZeroAddress when CreatesContract is true, but still 525 return nil, fmt.Errorf("result from SignAndBroadcast does not contain address for the deployed contract") 526 } 527 return &txe.Receipt.ContractAddress, nil 528 } 529 530 func logEvents(txe *exec.TxExecution, client *def.Client, logger *logging.Logger) { 531 if client.AllSpecs == nil { 532 return 533 } 534 535 for _, event := range txe.Events { 536 eventLog := event.GetLog() 537 538 if eventLog == nil { 539 continue 540 } 541 542 var eventID abi.EventID 543 copy(eventID[:], eventLog.GetTopic(0).Bytes()) 544 545 evAbi, ok := client.AllSpecs.EventsById[eventID] 546 if !ok { 547 logger.InfoMsg("Could not find ABI for Event", "Event ID", hex.EncodeUpperToString(eventID[:])) 548 continue 549 } 550 551 vals := make([]interface{}, len(evAbi.Inputs)) 552 for i := range vals { 553 vals[i] = new(string) 554 } 555 556 if err := abi.UnpackEvent(&evAbi, eventLog.Topics, eventLog.Data, vals...); err == nil { 557 var fields []interface{} 558 fields = append(fields, "name") 559 fields = append(fields, evAbi.Name) 560 for i := range vals { 561 fields = append(fields, evAbi.Inputs[i].Name) 562 val := vals[i].(*string) 563 fields = append(fields, *val) 564 } 565 logger.InfoMsg("EVM Event", fields...) 566 } 567 } 568 } 569 570 func mergeAbiSpecBytes(client *def.Client, bs []byte) { 571 spec, err := abi.ReadAbiSpec(bs) 572 if err == nil { 573 client.AllSpecs = abi.MergeAbiSpec([]*abi.AbiSpec{client.AllSpecs, spec}) 574 } 575 }