github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/accounts/abi/bind/bind.go (about)

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