github.com/codingfuture/orig-energi3@v0.8.4/accounts/abi/bind/bind.go (about)

     1  // Copyright 2018 The Energi Core Authors
     2  // Copyright 2016 The go-ethereum Authors
     3  // This file is part of the Energi Core library.
     4  //
     5  // The Energi Core library is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Lesser General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // The Energi Core library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU Lesser General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public License
    16  // along with the Energi Core library. If not, see <http://www.gnu.org/licenses/>.
    17  
    18  // Package bind generates Ethereum contract Go bindings.
    19  //
    20  // Detailed usage document and tutorial available on the go-ethereum Wiki page:
    21  // https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts
    22  package bind
    23  
    24  import (
    25  	"bytes"
    26  	"fmt"
    27  	"go/format"
    28  	"regexp"
    29  	"strings"
    30  	"text/template"
    31  	"unicode"
    32  
    33  	"github.com/ethereum/go-ethereum/accounts/abi"
    34  )
    35  
    36  // Lang is a target programming language selector to generate bindings for.
    37  type Lang int
    38  
    39  const (
    40  	LangGo Lang = iota
    41  	LangJava
    42  	LangObjC
    43  )
    44  
    45  // Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant
    46  // to be used as is in client code, but rather as an intermediate struct which
    47  // enforces compile time type safety and naming convention opposed to having to
    48  // manually maintain hard coded strings that break on runtime.
    49  func Bind(types []string, abis []string, bytecodes []string, runtimecodes []string, pkg string, lang Lang) (string, error) {
    50  	// Process each individual contract requested binding
    51  	contracts := make(map[string]*tmplContract)
    52  
    53  	for i := 0; i < len(types); i++ {
    54  		// Parse the actual ABI to generate the binding for
    55  		evmABI, err := abi.JSON(strings.NewReader(abis[i]))
    56  		if err != nil {
    57  			return "", err
    58  		}
    59  		// Strip any whitespace from the JSON ABI
    60  		strippedABI := strings.Map(func(r rune) rune {
    61  			if unicode.IsSpace(r) {
    62  				return -1
    63  			}
    64  			return r
    65  		}, abis[i])
    66  
    67  		// Extract the call and transact methods; events; and sort them alphabetically
    68  		var (
    69  			calls     = make(map[string]*tmplMethod)
    70  			transacts = make(map[string]*tmplMethod)
    71  			events    = make(map[string]*tmplEvent)
    72  		)
    73  		for _, original := range evmABI.Methods {
    74  			// Normalize the method for capital cases and non-anonymous inputs/outputs
    75  			normalized := original
    76  			normalized.Name = methodNormalizer[lang](original.Name)
    77  
    78  			normalized.Inputs = make([]abi.Argument, len(original.Inputs))
    79  			copy(normalized.Inputs, original.Inputs)
    80  			for j, input := range normalized.Inputs {
    81  				if input.Name == "" {
    82  					normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
    83  				}
    84  			}
    85  			normalized.Outputs = make([]abi.Argument, len(original.Outputs))
    86  			copy(normalized.Outputs, original.Outputs)
    87  			for j, output := range normalized.Outputs {
    88  				if output.Name != "" {
    89  					normalized.Outputs[j].Name = capitalise(output.Name)
    90  				}
    91  			}
    92  			// Append the methods to the call or transact lists
    93  			if original.Const {
    94  				calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)}
    95  			} else {
    96  				transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)}
    97  			}
    98  		}
    99  		for _, original := range evmABI.Events {
   100  			// Skip anonymous events as they don't support explicit filtering
   101  			if original.Anonymous {
   102  				continue
   103  			}
   104  			// Normalize the event for capital cases and non-anonymous outputs
   105  			normalized := original
   106  			normalized.Name = methodNormalizer[lang](original.Name)
   107  
   108  			normalized.Inputs = make([]abi.Argument, len(original.Inputs))
   109  			copy(normalized.Inputs, original.Inputs)
   110  			for j, input := range normalized.Inputs {
   111  				// Indexed fields are input, non-indexed ones are outputs
   112  				if input.Indexed {
   113  					if input.Name == "" {
   114  						normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
   115  					}
   116  				}
   117  			}
   118  			// Append the event to the accumulator list
   119  			events[original.Name] = &tmplEvent{Original: original, Normalized: normalized}
   120  		}
   121  		contracts[types[i]] = &tmplContract{
   122  			Type:        capitalise(types[i]),
   123  			InputABI:    strings.Replace(strippedABI, "\"", "\\\"", -1),
   124  			InputBin:    strings.TrimSpace(bytecodes[i]),
   125  			RuntimeBin:  strings.TrimSpace(runtimecodes[i]),
   126  			Constructor: evmABI.Constructor,
   127  			Calls:       calls,
   128  			Transacts:   transacts,
   129  			Events:      events,
   130  		}
   131  	}
   132  	// Generate the contract template data content and render it
   133  	data := &tmplData{
   134  		Package:   pkg,
   135  		Contracts: contracts,
   136  	}
   137  	buffer := new(bytes.Buffer)
   138  
   139  	funcs := map[string]interface{}{
   140  		"bindtype":      bindType[lang],
   141  		"bindtopictype": bindTopicType[lang],
   142  		"namedtype":     namedType[lang],
   143  		"capitalise":    capitalise,
   144  		"decapitalise":  decapitalise,
   145  	}
   146  	tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang]))
   147  	if err := tmpl.Execute(buffer, data); err != nil {
   148  		return "", err
   149  	}
   150  	// For Go bindings pass the code through gofmt to clean it up
   151  	if lang == LangGo {
   152  		code, err := format.Source(buffer.Bytes())
   153  		if err != nil {
   154  			return "", fmt.Errorf("%v\n%s", err, buffer)
   155  		}
   156  		return string(code), nil
   157  	}
   158  	// For all others just return as is for now
   159  	return buffer.String(), nil
   160  }
   161  
   162  // bindType is a set of type binders that convert Solidity types to some supported
   163  // programming language types.
   164  var bindType = map[Lang]func(kind abi.Type) string{
   165  	LangGo:   bindTypeGo,
   166  	LangJava: bindTypeJava,
   167  }
   168  
   169  // Helper function for the binding generators.
   170  // It reads the unmatched characters after the inner type-match,
   171  //  (since the inner type is a prefix of the total type declaration),
   172  //  looks for valid arrays (possibly a dynamic one) wrapping the inner type,
   173  //  and returns the sizes of these arrays.
   174  //
   175  // Returned array sizes are in the same order as solidity signatures; inner array size first.
   176  // Array sizes may also be "", indicating a dynamic array.
   177  func wrapArray(stringKind string, innerLen int, innerMapping string) (string, []string) {
   178  	remainder := stringKind[innerLen:]
   179  	//find all the sizes
   180  	matches := regexp.MustCompile(`\[(\d*)\]`).FindAllStringSubmatch(remainder, -1)
   181  	parts := make([]string, 0, len(matches))
   182  	for _, match := range matches {
   183  		//get group 1 from the regex match
   184  		parts = append(parts, match[1])
   185  	}
   186  	return innerMapping, parts
   187  }
   188  
   189  // Translates the array sizes to a Go-lang declaration of a (nested) array of the inner type.
   190  // Simply returns the inner type if arraySizes is empty.
   191  func arrayBindingGo(inner string, arraySizes []string) string {
   192  	out := ""
   193  	//prepend all array sizes, from outer (end arraySizes) to inner (start arraySizes)
   194  	for i := len(arraySizes) - 1; i >= 0; i-- {
   195  		out += "[" + arraySizes[i] + "]"
   196  	}
   197  	out += inner
   198  	return out
   199  }
   200  
   201  // bindTypeGo converts a Solidity type to a Go one. Since there is no clear mapping
   202  // from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly
   203  // mapped will use an upscaled type (e.g. *big.Int).
   204  func bindTypeGo(kind abi.Type) string {
   205  	stringKind := kind.String()
   206  	innerLen, innerMapping := bindUnnestedTypeGo(stringKind)
   207  	return arrayBindingGo(wrapArray(stringKind, innerLen, innerMapping))
   208  }
   209  
   210  // The inner function of bindTypeGo, this finds the inner type of stringKind.
   211  // (Or just the type itself if it is not an array or slice)
   212  // The length of the matched part is returned, with the translated type.
   213  func bindUnnestedTypeGo(stringKind string) (int, string) {
   214  
   215  	switch {
   216  	case strings.HasPrefix(stringKind, "address"):
   217  		return len("address"), "common.Address"
   218  
   219  	case strings.HasPrefix(stringKind, "bytes"):
   220  		parts := regexp.MustCompile(`bytes([0-9]*)`).FindStringSubmatch(stringKind)
   221  		return len(parts[0]), fmt.Sprintf("[%s]byte", parts[1])
   222  
   223  	case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"):
   224  		parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(stringKind)
   225  		switch parts[2] {
   226  		case "8", "16", "32", "64":
   227  			return len(parts[0]), fmt.Sprintf("%sint%s", parts[1], parts[2])
   228  		}
   229  		return len(parts[0]), "*big.Int"
   230  
   231  	case strings.HasPrefix(stringKind, "bool"):
   232  		return len("bool"), "bool"
   233  
   234  	case strings.HasPrefix(stringKind, "string"):
   235  		return len("string"), "string"
   236  
   237  	default:
   238  		return len(stringKind), stringKind
   239  	}
   240  }
   241  
   242  // Translates the array sizes to a Java declaration of a (nested) array of the inner type.
   243  // Simply returns the inner type if arraySizes is empty.
   244  func arrayBindingJava(inner string, arraySizes []string) string {
   245  	// Java array type declarations do not include the length.
   246  	return inner + strings.Repeat("[]", len(arraySizes))
   247  }
   248  
   249  // bindTypeJava converts a Solidity type to a Java one. Since there is no clear mapping
   250  // from all Solidity types to Java ones (e.g. uint17), those that cannot be exactly
   251  // mapped will use an upscaled type (e.g. BigDecimal).
   252  func bindTypeJava(kind abi.Type) string {
   253  	stringKind := kind.String()
   254  	innerLen, innerMapping := bindUnnestedTypeJava(stringKind)
   255  	return arrayBindingJava(wrapArray(stringKind, innerLen, innerMapping))
   256  }
   257  
   258  // The inner function of bindTypeJava, this finds the inner type of stringKind.
   259  // (Or just the type itself if it is not an array or slice)
   260  // The length of the matched part is returned, with the translated type.
   261  func bindUnnestedTypeJava(stringKind string) (int, string) {
   262  
   263  	switch {
   264  	case strings.HasPrefix(stringKind, "address"):
   265  		parts := regexp.MustCompile(`address(\[[0-9]*\])?`).FindStringSubmatch(stringKind)
   266  		if len(parts) != 2 {
   267  			return len(stringKind), stringKind
   268  		}
   269  		if parts[1] == "" {
   270  			return len("address"), "Address"
   271  		}
   272  		return len(parts[0]), "Addresses"
   273  
   274  	case strings.HasPrefix(stringKind, "bytes"):
   275  		parts := regexp.MustCompile(`bytes([0-9]*)`).FindStringSubmatch(stringKind)
   276  		if len(parts) != 2 {
   277  			return len(stringKind), stringKind
   278  		}
   279  		return len(parts[0]), "byte[]"
   280  
   281  	case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"):
   282  		//Note that uint and int (without digits) are also matched,
   283  		// these are size 256, and will translate to BigInt (the default).
   284  		parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(stringKind)
   285  		if len(parts) != 3 {
   286  			return len(stringKind), stringKind
   287  		}
   288  
   289  		namedSize := map[string]string{
   290  			"8":  "byte",
   291  			"16": "short",
   292  			"32": "int",
   293  			"64": "long",
   294  		}[parts[2]]
   295  
   296  		//default to BigInt
   297  		if namedSize == "" {
   298  			namedSize = "BigInt"
   299  		}
   300  		return len(parts[0]), namedSize
   301  
   302  	case strings.HasPrefix(stringKind, "bool"):
   303  		return len("bool"), "boolean"
   304  
   305  	case strings.HasPrefix(stringKind, "string"):
   306  		return len("string"), "String"
   307  
   308  	default:
   309  		return len(stringKind), stringKind
   310  	}
   311  }
   312  
   313  // bindTopicType is a set of type binders that convert Solidity types to some
   314  // supported programming language topic types.
   315  var bindTopicType = map[Lang]func(kind abi.Type) string{
   316  	LangGo:   bindTopicTypeGo,
   317  	LangJava: bindTopicTypeJava,
   318  }
   319  
   320  // bindTypeGo converts a Solidity topic type to a Go one. It is almost the same
   321  // funcionality as for simple types, but dynamic types get converted to hashes.
   322  func bindTopicTypeGo(kind abi.Type) string {
   323  	bound := bindTypeGo(kind)
   324  	if bound == "string" || bound == "[]byte" {
   325  		bound = "common.Hash"
   326  	}
   327  	return bound
   328  }
   329  
   330  // bindTypeGo converts a Solidity topic type to a Java one. It is almost the same
   331  // funcionality as for simple types, but dynamic types get converted to hashes.
   332  func bindTopicTypeJava(kind abi.Type) string {
   333  	bound := bindTypeJava(kind)
   334  	if bound == "String" || bound == "Bytes" {
   335  		bound = "Hash"
   336  	}
   337  	return bound
   338  }
   339  
   340  // namedType is a set of functions that transform language specific types to
   341  // named versions that my be used inside method names.
   342  var namedType = map[Lang]func(string, abi.Type) string{
   343  	LangGo:   func(string, abi.Type) string { panic("this shouldn't be needed") },
   344  	LangJava: namedTypeJava,
   345  }
   346  
   347  // namedTypeJava converts some primitive data types to named variants that can
   348  // be used as parts of method names.
   349  func namedTypeJava(javaKind string, solKind abi.Type) string {
   350  	switch javaKind {
   351  	case "byte[]":
   352  		return "Binary"
   353  	case "byte[][]":
   354  		return "Binaries"
   355  	case "string":
   356  		return "String"
   357  	case "string[]":
   358  		return "Strings"
   359  	case "boolean":
   360  		return "Bool"
   361  	case "boolean[]":
   362  		return "Bools"
   363  	case "BigInt[]":
   364  		return "BigInts"
   365  	default:
   366  		parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(solKind.String())
   367  		if len(parts) != 4 {
   368  			return javaKind
   369  		}
   370  		switch parts[2] {
   371  		case "8", "16", "32", "64":
   372  			if parts[3] == "" {
   373  				return capitalise(fmt.Sprintf("%sint%s", parts[1], parts[2]))
   374  			}
   375  			return capitalise(fmt.Sprintf("%sint%ss", parts[1], parts[2]))
   376  
   377  		default:
   378  			return javaKind
   379  		}
   380  	}
   381  }
   382  
   383  // methodNormalizer is a name transformer that modifies Solidity method names to
   384  // conform to target language naming concentions.
   385  var methodNormalizer = map[Lang]func(string) string{
   386  	LangGo:   abi.ToCamelCase,
   387  	LangJava: decapitalise,
   388  }
   389  
   390  // capitalise makes a camel-case string which starts with an upper case character.
   391  func capitalise(input string) string {
   392  	return abi.ToCamelCase(input)
   393  }
   394  
   395  // decapitalise makes a camel-case string which starts with a lower case character.
   396  func decapitalise(input string) string {
   397  	if len(input) == 0 {
   398  		return input
   399  	}
   400  
   401  	goForm := abi.ToCamelCase(input)
   402  	return strings.ToLower(goForm[:1]) + goForm[1:]
   403  }
   404  
   405  // structured checks whether a list of ABI data types has enough information to
   406  // operate through a proper Go struct or if flat returns are needed.
   407  func structured(args abi.Arguments) bool {
   408  	if len(args) < 2 {
   409  		return false
   410  	}
   411  	exists := make(map[string]bool)
   412  	for _, out := range args {
   413  		// If the name is anonymous, we can't organize into a struct
   414  		if out.Name == "" {
   415  			return false
   416  		}
   417  		// If the field name is empty when normalized or collides (var, Var, _var, _Var),
   418  		// we can't organize into a struct
   419  		field := capitalise(out.Name)
   420  		if field == "" || exists[field] {
   421  			return false
   422  		}
   423  		exists[field] = true
   424  	}
   425  	return true
   426  }