github.com/datachainlab/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  }