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  }