191 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			191 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package abi
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"reflect"
 | |
| 	"regexp"
 | |
| 	"strconv"
 | |
| 
 | |
| 	"github.com/ethereum/go-ethereum/ethutil"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	IntTy byte = iota
 | |
| 	UintTy
 | |
| 	BoolTy
 | |
| 	SliceTy
 | |
| 	AddressTy
 | |
| 	RealTy
 | |
| )
 | |
| 
 | |
| // Type is the reflection of the supported argument type
 | |
| type Type struct {
 | |
| 	Kind       reflect.Kind
 | |
| 	Type       reflect.Type
 | |
| 	Size       int
 | |
| 	T          byte   // Our own type checking
 | |
| 	stringKind string // holds the unparsed string for deriving signatures
 | |
| }
 | |
| 
 | |
| // New type returns a fully parsed Type given by the input string or an error if it  can't be parsed.
 | |
| //
 | |
| // Strings can be in the format of:
 | |
| //
 | |
| // 	Input  = Type [ "[" [ Number ] "]" ] Name .
 | |
| // 	Type   = [ "u" ] "int" [ Number ] .
 | |
| //
 | |
| // Examples:
 | |
| //
 | |
| //      string     int       uint       real
 | |
| //      string32   int8      uint8      uint[]
 | |
| //      address    int256    uint256    real[2]
 | |
| func NewType(t string) (typ Type, err error) {
 | |
| 	// 1. full string 2. type 3. (opt.) is slice 4. (opt.) size
 | |
| 	freg, err := regexp.Compile("([a-zA-Z0-9]+)(\\[([0-9]*)?\\])?")
 | |
| 	if err != nil {
 | |
| 		return Type{}, err
 | |
| 	}
 | |
| 	res := freg.FindAllStringSubmatch(t, -1)[0]
 | |
| 	var (
 | |
| 		isslice bool
 | |
| 		size    int
 | |
| 	)
 | |
| 	switch {
 | |
| 	case res[3] != "":
 | |
| 		// err is ignored. Already checked for number through the regexp
 | |
| 		size, _ = strconv.Atoi(res[3])
 | |
| 		isslice = true
 | |
| 	case res[2] != "":
 | |
| 		isslice = true
 | |
| 		size = -1
 | |
| 	case res[0] == "":
 | |
| 		return Type{}, fmt.Errorf("type parse error for `%s`", t)
 | |
| 	}
 | |
| 
 | |
| 	treg, err := regexp.Compile("([a-zA-Z]+)([0-9]*)?")
 | |
| 	if err != nil {
 | |
| 		return Type{}, err
 | |
| 	}
 | |
| 
 | |
| 	parsedType := treg.FindAllStringSubmatch(res[1], -1)[0]
 | |
| 	vsize, _ := strconv.Atoi(parsedType[2])
 | |
| 	vtype := parsedType[1]
 | |
| 	// substitute canonical representation
 | |
| 	if vsize == 0 && (vtype == "int" || vtype == "uint") {
 | |
| 		vsize = 256
 | |
| 		t += "256"
 | |
| 	}
 | |
| 
 | |
| 	if isslice {
 | |
| 		typ.Kind = reflect.Slice
 | |
| 		typ.Size = size
 | |
| 		switch vtype {
 | |
| 		case "int":
 | |
| 			typ.Type = big_ts
 | |
| 		case "uint":
 | |
| 			typ.Type = ubig_ts
 | |
| 		default:
 | |
| 			return Type{}, fmt.Errorf("unsupported arg slice type: %s", t)
 | |
| 		}
 | |
| 	} else {
 | |
| 		switch vtype {
 | |
| 		case "int":
 | |
| 			typ.Kind = reflect.Ptr
 | |
| 			typ.Type = big_t
 | |
| 			typ.Size = 256
 | |
| 			typ.T = IntTy
 | |
| 		case "uint":
 | |
| 			typ.Kind = reflect.Ptr
 | |
| 			typ.Type = ubig_t
 | |
| 			typ.Size = 256
 | |
| 			typ.T = UintTy
 | |
| 		case "bool":
 | |
| 			typ.Kind = reflect.Bool
 | |
| 		case "real": // TODO
 | |
| 			typ.Kind = reflect.Invalid
 | |
| 		case "address":
 | |
| 			typ.Kind = reflect.Slice
 | |
| 			typ.Type = byte_ts
 | |
| 			typ.Size = 20
 | |
| 			typ.T = AddressTy
 | |
| 		case "string":
 | |
| 			typ.Kind = reflect.String
 | |
| 			typ.Size = -1
 | |
| 			if vsize > 0 {
 | |
| 				typ.Size = 32
 | |
| 			}
 | |
| 		default:
 | |
| 			return Type{}, fmt.Errorf("unsupported arg type: %s", t)
 | |
| 		}
 | |
| 	}
 | |
| 	typ.stringKind = t
 | |
| 
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func (t Type) String() (out string) {
 | |
| 	return t.stringKind
 | |
| }
 | |
| 
 | |
| // Test the given input parameter `v` and checks if it matches certain
 | |
| // criteria
 | |
| // * Big integers are checks for ptr types and if the given value is
 | |
| //   assignable
 | |
| // * Integer are checked for size
 | |
| // * Strings, addresses and bytes are checks for type and size
 | |
| func (t Type) pack(v interface{}) ([]byte, error) {
 | |
| 	value := reflect.ValueOf(v)
 | |
| 	switch kind := value.Kind(); kind {
 | |
| 	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
 | |
| 		if t.Type != ubig_t {
 | |
| 			return nil, fmt.Errorf("type mismatch: %s for %T", t.Type, v)
 | |
| 		}
 | |
| 		return packNum(value, t.T), nil
 | |
| 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 | |
| 		if t.Type != ubig_t {
 | |
| 			return nil, fmt.Errorf("type mismatch: %s for %T", t.Type, v)
 | |
| 		}
 | |
| 		return packNum(value, t.T), nil
 | |
| 	case reflect.Ptr:
 | |
| 		// If the value is a ptr do a assign check (only used by
 | |
| 		// big.Int for now)
 | |
| 		if t.Type == ubig_t && value.Type() != ubig_t {
 | |
| 			return nil, fmt.Errorf("type mismatch: %s for %T", t.Type, v)
 | |
| 		}
 | |
| 		return packNum(value, t.T), nil
 | |
| 	case reflect.String:
 | |
| 		if t.Size > -1 && value.Len() > t.Size {
 | |
| 			return nil, fmt.Errorf("%v out of bound. %d for %d", value.Kind(), value.Len(), t.Size)
 | |
| 		}
 | |
| 		return []byte(ethutil.LeftPadString(t.String(), 32)), nil
 | |
| 	case reflect.Slice:
 | |
| 		if t.Size > -1 && value.Len() > t.Size {
 | |
| 			return nil, fmt.Errorf("%v out of bound. %d for %d", value.Kind(), value.Len(), t.Size)
 | |
| 		}
 | |
| 
 | |
| 		// Address is a special slice. The slice acts as one rather than a list of elements.
 | |
| 		if t.T == AddressTy {
 | |
| 			return ethutil.LeftPadBytes(v.([]byte), 32), nil
 | |
| 		}
 | |
| 
 | |
| 		// Signed / Unsigned check
 | |
| 		if (t.T != IntTy && isSigned(value)) || (t.T == UintTy && isSigned(value)) {
 | |
| 			return nil, fmt.Errorf("slice of incompatible types.")
 | |
| 		}
 | |
| 
 | |
| 		var packed []byte
 | |
| 		for i := 0; i < value.Len(); i++ {
 | |
| 			packed = append(packed, packNum(value.Index(i), t.T)...)
 | |
| 		}
 | |
| 		return packed, nil
 | |
| 	case reflect.Bool:
 | |
| 		if value.Bool() {
 | |
| 			return ethutil.LeftPadBytes(ethutil.Big1.Bytes(), 32), nil
 | |
| 		} else {
 | |
| 			return ethutil.LeftPadBytes(ethutil.Big0.Bytes(), 32), nil
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	panic("unreached")
 | |
| }
 |