package vm import ( "bytes" "encoding/binary" "encoding/hex" "encoding/json" "errors" "fmt" "math/big" "reflect" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" ) // A StackItem represents the "real" value that is pushed on the stack. type StackItem interface { fmt.Stringer Value() interface{} // Dup duplicates current StackItem. Dup() StackItem // Bool converts StackItem to a boolean value. Bool() bool // TryBytes converts StackItem to a byte slice. TryBytes() ([]byte, error) // TryInteger converts StackItem to an integer. TryInteger() (*big.Int, error) // Equals checks if 2 StackItems are equal. Equals(s StackItem) bool // ToContractParameter converts StackItem to smartcontract.Parameter ToContractParameter(map[StackItem]bool) smartcontract.Parameter // Type returns stack item type. Type() StackItemType // Convert converts StackItem to another type. Convert(StackItemType) (StackItem, error) } var errInvalidConversion = errors.New("invalid conversion type") func makeStackItem(v interface{}) StackItem { switch val := v.(type) { case int: return &BigIntegerItem{ value: big.NewInt(int64(val)), } case int64: return &BigIntegerItem{ value: big.NewInt(val), } case uint8: return &BigIntegerItem{ value: big.NewInt(int64(val)), } case uint16: return &BigIntegerItem{ value: big.NewInt(int64(val)), } case uint32: return &BigIntegerItem{ value: big.NewInt(int64(val)), } case uint64: b := make([]byte, 8) binary.BigEndian.PutUint64(b, val) bigInt := big.NewInt(0) bigInt.SetBytes(b) return &BigIntegerItem{ value: bigInt, } case []byte: return &ByteArrayItem{ value: val, } case string: return &ByteArrayItem{ value: []byte(val), } case bool: return &BoolItem{ value: val, } case []StackItem: return &ArrayItem{ value: val, } case *big.Int: return NewBigIntegerItem(val) case StackItem: return val case []int: var a []StackItem for _, i := range val { a = append(a, makeStackItem(i)) } return makeStackItem(a) default: i64T := reflect.TypeOf(int64(0)) if reflect.TypeOf(val).ConvertibleTo(i64T) { i64Val := reflect.ValueOf(val).Convert(i64T).Interface() return makeStackItem(i64Val) } panic( fmt.Sprintf( "invalid stack item type: %v (%v)", val, reflect.TypeOf(val), ), ) } } // convertPrimitive converts primitive item to a specified type. func convertPrimitive(item StackItem, typ StackItemType) (StackItem, error) { if item.Type() == typ { return item, nil } switch typ { case IntegerT: bi, err := item.TryInteger() if err != nil { return nil, err } return NewBigIntegerItem(bi), nil case ByteArrayT: b, err := item.TryBytes() if err != nil { return nil, err } return NewByteArrayItem(b), nil case BufferT: panic("TODO") // #877 case BooleanT: return NewBoolItem(item.Bool()), nil default: return nil, errInvalidConversion } } // StructItem represents a struct on the stack. type StructItem struct { value []StackItem } // NewStructItem returns an new StructItem object. func NewStructItem(items []StackItem) *StructItem { return &StructItem{ value: items, } } // Value implements StackItem interface. func (i *StructItem) Value() interface{} { return i.value } func (i *StructItem) String() string { return "Struct" } // Dup implements StackItem interface. func (i *StructItem) Dup() StackItem { // it's a reference type, so no copying here. return i } // Bool implements StackItem interface. func (i *StructItem) Bool() bool { return true } // TryBytes implements StackItem interface. func (i *StructItem) TryBytes() ([]byte, error) { return nil, errors.New("can't convert Struct to ByteArray") } // TryInteger implements StackItem interface. func (i *StructItem) TryInteger() (*big.Int, error) { return nil, errors.New("can't convert Struct to Integer") } // Equals implements StackItem interface. func (i *StructItem) Equals(s StackItem) bool { if i == s { return true } else if s == nil { return false } val, ok := s.(*StructItem) if !ok || len(i.value) != len(val.value) { return false } for j := range i.value { if !i.value[j].Equals(val.value[j]) { return false } } return true } // ToContractParameter implements StackItem interface. func (i *StructItem) ToContractParameter(seen map[StackItem]bool) smartcontract.Parameter { var value []smartcontract.Parameter if !seen[i] { seen[i] = true for _, stackItem := range i.value { parameter := stackItem.ToContractParameter(seen) value = append(value, parameter) } } return smartcontract.Parameter{ Type: smartcontract.ArrayType, Value: value, } } // Type implements StackItem interface. func (i *StructItem) Type() StackItemType { return StructT } // Convert implements StackItem interface. func (i *StructItem) Convert(typ StackItemType) (StackItem, error) { switch typ { case StructT: return i, nil case ArrayT: return NewArrayItem(i.value), nil case BooleanT: return NewBoolItem(i.Bool()), nil default: return nil, errInvalidConversion } } // Clone returns a Struct with all Struct fields copied by value. // Array fields are still copied by reference. func (i *StructItem) Clone() *StructItem { ret := &StructItem{make([]StackItem, len(i.value))} for j := range i.value { switch t := i.value[j].(type) { case *StructItem: ret.value[j] = t.Clone() default: ret.value[j] = t } } return ret } // NullItem represents null on the stack. type NullItem struct{} // String implements StackItem interface. func (i NullItem) String() string { return "Null" } // Value implements StackItem interface. func (i NullItem) Value() interface{} { return nil } // Dup implements StackItem interface. // There is no need to perform a real copy here, // as NullItem has no internal state. func (i NullItem) Dup() StackItem { return i } // Bool implements StackItem interface. func (i NullItem) Bool() bool { return false } // TryBytes implements StackItem interface. func (i NullItem) TryBytes() ([]byte, error) { return nil, errors.New("can't convert Null to ByteArray") } // TryInteger implements StackItem interface. func (i NullItem) TryInteger() (*big.Int, error) { return nil, errors.New("can't convert Null to Integer") } // Equals implements StackItem interface. func (i NullItem) Equals(s StackItem) bool { _, ok := s.(NullItem) return ok } // ToContractParameter implements StackItem interface. func (i NullItem) ToContractParameter(map[StackItem]bool) smartcontract.Parameter { return smartcontract.Parameter{ Type: smartcontract.AnyType, } } // Type implements StackItem interface. func (i NullItem) Type() StackItemType { return AnyT } // Convert implements StackItem interface. func (i NullItem) Convert(typ StackItemType) (StackItem, error) { if typ == AnyT || !typ.IsValid() { return nil, errInvalidConversion } return i, nil } // BigIntegerItem represents a big integer on the stack. type BigIntegerItem struct { value *big.Int } // NewBigIntegerItem returns an new BigIntegerItem object. func NewBigIntegerItem(value *big.Int) *BigIntegerItem { if value.BitLen() > MaxBigIntegerSizeBits { panic("integer is too big") } return &BigIntegerItem{ value: value, } } // Bytes converts i to a slice of bytes. func (i *BigIntegerItem) Bytes() []byte { return emit.IntToBytes(i.value) } // Bool implements StackItem interface. func (i *BigIntegerItem) Bool() bool { return i.value.Sign() != 0 } // TryBytes implements StackItem interface. func (i *BigIntegerItem) TryBytes() ([]byte, error) { return i.Bytes(), nil } // TryInteger implements StackItem interface. func (i *BigIntegerItem) TryInteger() (*big.Int, error) { return i.value, nil } // Equals implements StackItem interface. func (i *BigIntegerItem) Equals(s StackItem) bool { if i == s { return true } else if s == nil { return false } val, ok := s.(*BigIntegerItem) if ok { return i.value.Cmp(val.value) == 0 } bs, err := s.TryBytes() return err == nil && bytes.Equal(i.Bytes(), bs) } // Value implements StackItem interface. func (i *BigIntegerItem) Value() interface{} { return i.value } func (i *BigIntegerItem) String() string { return "BigInteger" } // Dup implements StackItem interface. func (i *BigIntegerItem) Dup() StackItem { n := new(big.Int) return &BigIntegerItem{n.Set(i.value)} } // ToContractParameter implements StackItem interface. func (i *BigIntegerItem) ToContractParameter(map[StackItem]bool) smartcontract.Parameter { return smartcontract.Parameter{ Type: smartcontract.IntegerType, Value: i.value.Int64(), } } // Type implements StackItem interface. func (i *BigIntegerItem) Type() StackItemType { return IntegerT } // Convert implements StackItem interface. func (i *BigIntegerItem) Convert(typ StackItemType) (StackItem, error) { return convertPrimitive(i, typ) } // MarshalJSON implements the json.Marshaler interface. func (i *BigIntegerItem) MarshalJSON() ([]byte, error) { return json.Marshal(i.value) } // BoolItem represents a boolean StackItem. type BoolItem struct { value bool } // NewBoolItem returns an new BoolItem object. func NewBoolItem(val bool) *BoolItem { return &BoolItem{ value: val, } } // Value implements StackItem interface. func (i *BoolItem) Value() interface{} { return i.value } // MarshalJSON implements the json.Marshaler interface. func (i *BoolItem) MarshalJSON() ([]byte, error) { return json.Marshal(i.value) } func (i *BoolItem) String() string { return "Boolean" } // Dup implements StackItem interface. func (i *BoolItem) Dup() StackItem { return &BoolItem{i.value} } // Bool implements StackItem interface. func (i *BoolItem) Bool() bool { return i.value } // Bytes converts BoolItem to bytes. func (i *BoolItem) Bytes() []byte { if i.value { return []byte{1} } return []byte{0} } // TryBytes implements StackItem interface. func (i *BoolItem) TryBytes() ([]byte, error) { return i.Bytes(), nil } // TryInteger implements StackItem interface. func (i *BoolItem) TryInteger() (*big.Int, error) { if i.value { return big.NewInt(1), nil } return big.NewInt(0), nil } // Equals implements StackItem interface. func (i *BoolItem) Equals(s StackItem) bool { if i == s { return true } else if s == nil { return false } val, ok := s.(*BoolItem) if ok { return i.value == val.value } bs, err := s.TryBytes() return err == nil && bytes.Equal(i.Bytes(), bs) } // ToContractParameter implements StackItem interface. func (i *BoolItem) ToContractParameter(map[StackItem]bool) smartcontract.Parameter { return smartcontract.Parameter{ Type: smartcontract.BoolType, Value: i.value, } } // Type implements StackItem interface. func (i *BoolItem) Type() StackItemType { return BooleanT } // Convert implements StackItem interface. func (i *BoolItem) Convert(typ StackItemType) (StackItem, error) { return convertPrimitive(i, typ) } // ByteArrayItem represents a byte array on the stack. type ByteArrayItem struct { value []byte } // NewByteArrayItem returns an new ByteArrayItem object. func NewByteArrayItem(b []byte) *ByteArrayItem { return &ByteArrayItem{ value: b, } } // Value implements StackItem interface. func (i *ByteArrayItem) Value() interface{} { return i.value } // MarshalJSON implements the json.Marshaler interface. func (i *ByteArrayItem) MarshalJSON() ([]byte, error) { return json.Marshal(hex.EncodeToString(i.value)) } func (i *ByteArrayItem) String() string { return "ByteArray" } // Bool implements StackItem interface. func (i *ByteArrayItem) Bool() bool { if len(i.value) > MaxBigIntegerSizeBits/8 { return true } for _, b := range i.value { if b != 0 { return true } } return false } // TryBytes implements StackItem interface. func (i *ByteArrayItem) TryBytes() ([]byte, error) { return i.value, nil } // TryInteger implements StackItem interface. func (i *ByteArrayItem) TryInteger() (*big.Int, error) { bi := emit.BytesToInt(i.value) if bi.BitLen() > MaxBigIntegerSizeBits { return nil, errors.New("integer is too big") } return bi, nil } // Equals implements StackItem interface. func (i *ByteArrayItem) Equals(s StackItem) bool { if i == s { return true } else if s == nil { return false } bs, err := s.TryBytes() return err == nil && bytes.Equal(i.value, bs) } // Dup implements StackItem interface. func (i *ByteArrayItem) Dup() StackItem { a := make([]byte, len(i.value)) copy(a, i.value) return &ByteArrayItem{a} } // ToContractParameter implements StackItem interface. func (i *ByteArrayItem) ToContractParameter(map[StackItem]bool) smartcontract.Parameter { return smartcontract.Parameter{ Type: smartcontract.ByteArrayType, Value: i.value, } } // Type implements StackItem interface. func (i *ByteArrayItem) Type() StackItemType { return ByteArrayT } // Convert implements StackItem interface. func (i *ByteArrayItem) Convert(typ StackItemType) (StackItem, error) { return convertPrimitive(i, typ) } // ArrayItem represents a new ArrayItem object. type ArrayItem struct { value []StackItem } // NewArrayItem returns a new ArrayItem object. func NewArrayItem(items []StackItem) *ArrayItem { return &ArrayItem{ value: items, } } // Value implements StackItem interface. func (i *ArrayItem) Value() interface{} { return i.value } // MarshalJSON implements the json.Marshaler interface. func (i *ArrayItem) MarshalJSON() ([]byte, error) { return json.Marshal(i.value) } func (i *ArrayItem) String() string { return "Array" } // Bool implements StackItem interface. func (i *ArrayItem) Bool() bool { return true } // TryBytes implements StackItem interface. func (i *ArrayItem) TryBytes() ([]byte, error) { return nil, errors.New("can't convert Array to ByteArray") } // TryInteger implements StackItem interface. func (i *ArrayItem) TryInteger() (*big.Int, error) { return nil, errors.New("can't convert Array to Integer") } // Equals implements StackItem interface. func (i *ArrayItem) Equals(s StackItem) bool { return i == s } // Dup implements StackItem interface. func (i *ArrayItem) Dup() StackItem { // reference type return i } // ToContractParameter implements StackItem interface. func (i *ArrayItem) ToContractParameter(seen map[StackItem]bool) smartcontract.Parameter { var value []smartcontract.Parameter if !seen[i] { seen[i] = true for _, stackItem := range i.value { parameter := stackItem.ToContractParameter(seen) value = append(value, parameter) } } return smartcontract.Parameter{ Type: smartcontract.ArrayType, Value: value, } } // Type implements StackItem interface. func (i *ArrayItem) Type() StackItemType { return ArrayT } // Convert implements StackItem interface. func (i *ArrayItem) Convert(typ StackItemType) (StackItem, error) { switch typ { case ArrayT: return i, nil case StructT: return NewStructItem(i.value), nil case BooleanT: return NewBoolItem(i.Bool()), nil default: return nil, errInvalidConversion } } // MapElement is a key-value pair of StackItems. type MapElement struct { Key StackItem Value StackItem } // MapItem represents Map object. It's ordered, so we use slice representation // which should be fine for maps with less than 32 or so elements. Given that // our VM has quite low limit of overall stack items, it should be good enough, // but it can be extended with a real map for fast random access in the future // if need be. type MapItem struct { value []MapElement } // NewMapItem returns new MapItem object. func NewMapItem() *MapItem { return &MapItem{ value: make([]MapElement, 0), } } // Value implements StackItem interface. func (i *MapItem) Value() interface{} { return i.value } // Bool implements StackItem interface. func (i *MapItem) Bool() bool { return true } // TryBytes implements StackItem interface. func (i *MapItem) TryBytes() ([]byte, error) { return nil, errors.New("can't convert Map to ByteArray") } // TryInteger implements StackItem interface. func (i *MapItem) TryInteger() (*big.Int, error) { return nil, errors.New("can't convert Map to Integer") } // Equals implements StackItem interface. func (i *MapItem) Equals(s StackItem) bool { return i == s } func (i *MapItem) String() string { return "Map" } // Index returns an index of the key in map. func (i *MapItem) Index(key StackItem) int { for k := range i.value { if i.value[k].Key.Equals(key) { return k } } return -1 } // Has checks if map has specified key. func (i *MapItem) Has(key StackItem) bool { return i.Index(key) >= 0 } // Dup implements StackItem interface. func (i *MapItem) Dup() StackItem { // reference type return i } // ToContractParameter implements StackItem interface. func (i *MapItem) ToContractParameter(seen map[StackItem]bool) smartcontract.Parameter { value := make([]smartcontract.ParameterPair, 0) if !seen[i] { seen[i] = true for k := range i.value { value = append(value, smartcontract.ParameterPair{ Key: i.value[k].Key.ToContractParameter(seen), Value: i.value[k].Value.ToContractParameter(seen), }) } } return smartcontract.Parameter{ Type: smartcontract.MapType, Value: value, } } // Type implements StackItem interface. func (i *MapItem) Type() StackItemType { return MapT } // Convert implements StackItem interface. func (i *MapItem) Convert(typ StackItemType) (StackItem, error) { switch typ { case MapT: return i, nil case BooleanT: return NewBoolItem(i.Bool()), nil default: return nil, errInvalidConversion } } // Add adds key-value pair to the map. func (i *MapItem) Add(key, value StackItem) { if !isValidMapKey(key) { panic("wrong key type") } index := i.Index(key) if index >= 0 { i.value[index].Value = value } else { i.value = append(i.value, MapElement{key, value}) } } // Drop removes given index from the map (no bounds check done here). func (i *MapItem) Drop(index int) { copy(i.value[index:], i.value[index+1:]) i.value = i.value[:len(i.value)-1] } // isValidMapKey checks whether it's possible to use given StackItem as a Map // key. func isValidMapKey(key StackItem) bool { switch key.(type) { case *BoolItem, *BigIntegerItem, *ByteArrayItem: return true default: return false } } // InteropItem represents interop data on the stack. type InteropItem struct { value interface{} } // NewInteropItem returns new InteropItem object. func NewInteropItem(value interface{}) *InteropItem { return &InteropItem{ value: value, } } // Value implements StackItem interface. func (i *InteropItem) Value() interface{} { return i.value } // String implements stringer interface. func (i *InteropItem) String() string { return "InteropItem" } // Dup implements StackItem interface. func (i *InteropItem) Dup() StackItem { // reference type return i } // Bool implements StackItem interface. func (i *InteropItem) Bool() bool { return true } // TryBytes implements StackItem interface. func (i *InteropItem) TryBytes() ([]byte, error) { return nil, errors.New("can't convert Interop to ByteArray") } // TryInteger implements StackItem interface. func (i *InteropItem) TryInteger() (*big.Int, error) { return nil, errors.New("can't convert Interop to Integer") } // Equals implements StackItem interface. func (i *InteropItem) Equals(s StackItem) bool { if i == s { return true } else if s == nil { return false } val, ok := s.(*InteropItem) return ok && i.value == val.value } // ToContractParameter implements StackItem interface. func (i *InteropItem) ToContractParameter(map[StackItem]bool) smartcontract.Parameter { return smartcontract.Parameter{ Type: smartcontract.InteropInterfaceType, Value: nil, } } // Type implements StackItem interface. func (i *InteropItem) Type() StackItemType { return InteropT } // Convert implements StackItem interface. func (i *InteropItem) Convert(typ StackItemType) (StackItem, error) { switch typ { case InteropT: return i, nil case BooleanT: return NewBoolItem(i.Bool()), nil default: return nil, errInvalidConversion } } // MarshalJSON implements the json.Marshaler interface. func (i *InteropItem) MarshalJSON() ([]byte, error) { return json.Marshal(i.value) } // PointerItem represents VM-level instruction pointer. type PointerItem struct { pos int script []byte hash util.Uint160 } // NewPointerItem returns new pointer on the specified position. func NewPointerItem(pos int, script []byte) *PointerItem { return &PointerItem{ pos: pos, script: script, hash: hash.Hash160(script), } } // String implements StackItem interface. func (p *PointerItem) String() string { return "Pointer" } // Value implements StackItem interface. func (p *PointerItem) Value() interface{} { return p.pos } // Dup implements StackItem interface. func (p *PointerItem) Dup() StackItem { return &PointerItem{ pos: p.pos, script: p.script, hash: p.hash, } } // Bool implements StackItem interface. func (p *PointerItem) Bool() bool { return true } // TryBytes implements StackItem interface. func (p *PointerItem) TryBytes() ([]byte, error) { return nil, errors.New("can't convert Pointer to ByteArray") } // TryInteger implements StackItem interface. func (p *PointerItem) TryInteger() (*big.Int, error) { return nil, errors.New("can't convert Pointer to Integer") } // Equals implements StackItem interface. func (p *PointerItem) Equals(s StackItem) bool { if p == s { return true } ptr, ok := s.(*PointerItem) return ok && p.pos == ptr.pos && p.hash == ptr.hash } // ToContractParameter implements StackItem interface. func (p *PointerItem) ToContractParameter(map[StackItem]bool) smartcontract.Parameter { return smartcontract.NewParameter(smartcontract.AnyType) } // Type implements StackItem interface. func (p *PointerItem) Type() StackItemType { return PointerT } // Convert implements StackItem interface. func (p *PointerItem) Convert(typ StackItemType) (StackItem, error) { switch typ { case PointerT: return p, nil case BooleanT: return NewBoolItem(p.Bool()), nil default: return nil, errInvalidConversion } }