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  }