github.com/mavryk-network/mvgo@v1.19.9/micheline/script.go (about)

     1  // Copyright (c) 2020-2022 Blockwatch Data Inc.
     2  // Author: alex@blockwatch.cc
     3  
     4  package micheline
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/binary"
     9  	"encoding/json"
    10  	"fmt"
    11  	"io"
    12  	"strconv"
    13  
    14  	"github.com/mavryk-network/mvgo/mavryk"
    15  )
    16  
    17  type Script struct {
    18  	Code    Code `json:"code"`    // code section, i.e. parameter & storage types, code
    19  	Storage Prim `json:"storage"` // data section, i.e. initial contract storage
    20  }
    21  
    22  type Code struct {
    23  	Param   Prim // call types
    24  	Storage Prim // storage types
    25  	Code    Prim // program code
    26  	View    Prim // view code (i.e. list of views, may be empty)
    27  	BadCode Prim // catch-all for ill-formed contracts
    28  }
    29  
    30  func NewScript() *Script {
    31  	return &Script{
    32  		Code: Code{
    33  			Param:   Prim{Type: PrimSequence, Args: []Prim{{Type: PrimUnary, OpCode: K_PARAMETER}}},
    34  			Storage: Prim{Type: PrimSequence, Args: []Prim{{Type: PrimUnary, OpCode: K_STORAGE}}},
    35  			Code:    Prim{Type: PrimSequence, Args: []Prim{{Type: PrimUnary, OpCode: K_CODE}}},
    36  			View:    Prim{Type: PrimSequence, Args: []Prim{}},
    37  		},
    38  		Storage: Prim{},
    39  	}
    40  }
    41  
    42  func (s Script) IsValid() bool {
    43  	return s.Code.Param.IsValid() && s.Code.Storage.IsValid()
    44  }
    45  
    46  func (s Script) StorageType() Type {
    47  	return Type{s.Code.Storage.Args[0]}
    48  }
    49  
    50  func (s Script) ParamType() Type {
    51  	return Type{s.Code.Param.Args[0]}
    52  }
    53  
    54  func (s Script) Entrypoints(withPrim bool) (Entrypoints, error) {
    55  	return s.ParamType().Entrypoints(withPrim)
    56  }
    57  
    58  func (s Script) ResolveEntrypointPath(name string) string {
    59  	return s.ParamType().ResolveEntrypointPath(name)
    60  }
    61  
    62  func (s Script) Views(withPrim, withCode bool) (Views, error) {
    63  	views := make(Views, len(s.Code.View.Args))
    64  	for _, v := range s.Code.View.Args {
    65  		view := NewView(v)
    66  		if !withPrim {
    67  			view.Prim = InvalidPrim
    68  		}
    69  		if !withCode {
    70  			view.Code = InvalidPrim
    71  		}
    72  		views[view.Name] = view
    73  	}
    74  	return views, nil
    75  }
    76  
    77  func (s Script) Constants() []mavryk.ExprHash {
    78  	c := make([]mavryk.ExprHash, 0)
    79  	for _, prim := range []Prim{
    80  		s.Code.Param,
    81  		s.Code.Storage,
    82  		s.Code.Code,
    83  		s.Code.View,
    84  		s.Code.BadCode,
    85  	} {
    86  		prim.Walk(func(p Prim) error {
    87  			if p.IsConstant() {
    88  				if h, err := mavryk.ParseExprHash(p.Args[0].String); err == nil {
    89  					c = append(c, h)
    90  				}
    91  			}
    92  			return nil
    93  		})
    94  	}
    95  	return c
    96  }
    97  
    98  func (s *Script) ExpandConstants(dict ConstantDict) {
    99  	// first check if the entire script is a constant
   100  	if s.Code.BadCode.IsConstant() {
   101  		if c, ok := dict.GetString(s.Code.BadCode.Args[0].String); ok {
   102  			// replace entire code section from constant
   103  			s.Code.Param = c.Args[0].Clone()
   104  			s.Code.Storage = c.Args[1].Clone()
   105  			s.Code.Code = c.Args[2].Clone()
   106  			if len(c.Args) > 3 {
   107  				for _, view := range c.Args[3:] {
   108  					s.Code.View.Args = append(s.Code.View.Args, view.Clone())
   109  				}
   110  			}
   111  		}
   112  		s.Code.BadCode = Prim{}
   113  	}
   114  	// continue replacing nested constants
   115  	for _, prim := range []*Prim{
   116  		&s.Code.Param,
   117  		&s.Code.Storage,
   118  		&s.Code.Code,
   119  		&s.Code.View,
   120  	} {
   121  		_ = prim.Visit(func(p *Prim) error {
   122  			if p.IsConstant() {
   123  				if c, ok := dict.GetString(p.Args[0].String); ok {
   124  					*p = c.Clone()
   125  				}
   126  			}
   127  			return nil
   128  		})
   129  	}
   130  }
   131  
   132  // Returns the first 4 bytes of the SHA256 hash from a binary encoded parameter type
   133  // definition. This value is sufficiently unique to identify contracts with exactly
   134  // the same entrypoints including annotations.
   135  //
   136  // To identify syntactically equal entrypoints with or without annotations use
   137  // `IsEqual()`, `IsEqualWithAnno()` or `IsEqualPrim()`.
   138  func (s Script) InterfaceHash() uint64 {
   139  	return s.Code.Param.Hash64()
   140  }
   141  
   142  // Returns the first 4 bytes of the SHA256 hash from a binary encoded storage type
   143  // definition. This value is sufficiently unique to identify contracts with exactly
   144  // the same entrypoints including annotations.
   145  func (s Script) StorageHash() uint64 {
   146  	return s.Code.Storage.Hash64()
   147  }
   148  
   149  // Returns the first 4 bytes of the SHA256 hash from a binary encoded code section
   150  // of a contract.
   151  func (s Script) CodeHash() uint64 {
   152  	return s.Code.Code.Hash64()
   153  }
   154  
   155  // Returns named bigmap ids from the script's storage type and current value.
   156  func (s Script) Bigmaps() map[string]int64 {
   157  	return DetectBigmaps(s.Code.Storage, s.Storage)
   158  }
   159  
   160  // Flattens pair primitives. Basically the same as the UNPAIR michelson operation.
   161  // For instance, `pair (pair (pair 1 2) 3) 4` becomes `pair 1 2 3 4`. Other container
   162  // primitives like map and list remain untouched.
   163  func flatten(p Prim) []Prim {
   164  	res := []Prim{}
   165  	if p.IsPair() {
   166  		for _, v := range p.Args {
   167  			res = append(res, flatten(v)...)
   168  		}
   169  	} else {
   170  		res = append(res, p)
   171  	}
   172  	return res
   173  }
   174  
   175  // Returns a map of named bigmap ids obtained from a storage type and a storage value.
   176  // In the edge case where a T_OR branch hides an exsting bigmap behind a None value,
   177  // the hidden bigmap is not detected.
   178  func DetectBigmaps(typ Prim, storage Prim) map[string]int64 {
   179  	values := flatten(storage)
   180  	m := linkStorageTypeAndValue(typ, &values)
   181  	res := map[string]int64{}
   182  	for k, v := range m {
   183  		if v.Type == T_BIG_MAP {
   184  			res[k] = v.Value.Int.Int64()
   185  		}
   186  	}
   187  	return res
   188  }
   189  
   190  type storageItem struct {
   191  	// The code of the type definition in storage code.
   192  	Type OpCode
   193  	// The type's corresponding value in contract storage.
   194  	Value Prim
   195  }
   196  
   197  // Links storage values in a contract with type definitions in the contract's storage code.
   198  // Returns a mapping between value aliases and storage values.
   199  func linkStorageTypeAndValue(typ Prim, values *[]Prim) map[string]storageItem {
   200  	named := make(map[string]storageItem)
   201  	uniqueName := func(n string) string {
   202  		if _, ok := named[n]; !ok && n != "" {
   203  			return n
   204  		}
   205  		if n == "" {
   206  			n = "bigmap"
   207  		}
   208  		for i := 0; ; i++ {
   209  			name := n + "_" + strconv.Itoa(i)
   210  			if _, ok := named[name]; ok {
   211  				continue
   212  			}
   213  			return name
   214  		}
   215  	}
   216  	// `values` is a queue of storage values collected from the storage value primitive tree.
   217  	// Here assumes `Walk` traverses the storage code primitive tree in the same ordering.
   218  	// The head of the queue should correspond to each primitive encountered here.
   219  	_ = typ.Walk(func(p Prim) error {
   220  		switch p.OpCode {
   221  		case K_STORAGE:
   222  			// The root node of the storage primitive; do nothing and continue
   223  			return nil
   224  
   225  		case T_MAP:
   226  			val := (*values)[0]
   227  			// val.Args is a list of key-value pairs
   228  			for i, v := range val.Args {
   229  				var name string
   230  				switch v.Args[0].Type {
   231  				case PrimString:
   232  					name = v.Args[0].String
   233  				case PrimBytes:
   234  					buf := v.Args[0].Bytes
   235  					if isASCIIBytes(buf) {
   236  						name = string(buf)
   237  					} else if mavryk.IsAddressBytes(buf) {
   238  						a := mavryk.Address{}
   239  						_ = a.Decode(buf)
   240  						name = a.String()
   241  					}
   242  				}
   243  				if name == "" {
   244  					name = p.GetVarAnnoAny() + "_" + strconv.Itoa(i)
   245  				}
   246  				value := v.Args[1]
   247  				nestedValues := flatten(value)
   248  				// Map's value type definition is in the primitive's second argument
   249  				for _, v := range linkStorageTypeAndValue(p.Args[1], &nestedValues) {
   250  					named[uniqueName(name)] = v
   251  				}
   252  			}
   253  			*values = (*values)[1:]
   254  			return PrimSkip
   255  
   256  		case T_BIG_MAP:
   257  			val := (*values)[0]
   258  			if val.IsValid() && val.Type == PrimInt {
   259  				named[uniqueName(p.GetVarAnnoAny())] = storageItem{
   260  					Type:  p.OpCode,
   261  					Value: val,
   262  				}
   263  			}
   264  			*values = (*values)[1:]
   265  			return PrimSkip
   266  
   267  		case T_OPTION:
   268  			val := (*values)[0]
   269  			// option always has only one argument
   270  			// val is Some or None
   271  			if val.OpCode == D_SOME {
   272  				nestedValues := flatten(val.Args[0])
   273  				for n, v := range linkStorageTypeAndValue(p.Args[0], &nestedValues) {
   274  					named[uniqueName(n)] = v
   275  				}
   276  			} else {
   277  				named[uniqueName(p.GetVarAnnoAny())] = storageItem{
   278  					Type:  p.OpCode,
   279  					Value: val,
   280  				}
   281  			}
   282  			*values = (*values)[1:]
   283  			return PrimSkip
   284  
   285  		case T_PAIR:
   286  			for _, arg := range p.Args {
   287  				for n, v := range linkStorageTypeAndValue(arg, values) {
   288  					named[uniqueName(n)] = v
   289  				}
   290  			}
   291  			return PrimSkip
   292  
   293  		case T_LIST:
   294  			val := (*values)[0]
   295  			for _, arg := range val.Args {
   296  				// List items are not flattened in previous operations and remain individual
   297  				// entities until now. Here the primitive is unpacked and processed against
   298  				// the list item type definition.
   299  				nestedValues := flatten(arg)
   300  				// The list item's type definition is in the first argument of the list type.
   301  				for n, v := range linkStorageTypeAndValue(p.Args[0], &nestedValues) {
   302  					named[uniqueName(n)] = v
   303  				}
   304  			}
   305  			*values = (*values)[1:]
   306  			return PrimSkip
   307  
   308  		case T_OR:
   309  			val := (*values)[0]
   310  			nestedValues := flatten(val.Args[0])
   311  			if val.OpCode == D_LEFT {
   312  				// Left branch in OR type's first argument
   313  				for n, v := range linkStorageTypeAndValue(p.Args[0], &nestedValues) {
   314  					named[uniqueName(n)] = v
   315  				}
   316  			} else {
   317  				// OpCode == D_RIGHT
   318  				// Right branch in OR type's second argument
   319  				for n, v := range linkStorageTypeAndValue(p.Args[1], &nestedValues) {
   320  					named[uniqueName(n)] = v
   321  				}
   322  			}
   323  			*values = (*values)[1:]
   324  			return PrimSkip
   325  
   326  		default:
   327  			if len(*values) > 0 {
   328  				named[uniqueName(p.GetVarAnnoAny())] = storageItem{
   329  					Type:  p.OpCode,
   330  					Value: (*values)[0],
   331  				}
   332  				*values = (*values)[1:]
   333  			}
   334  			return PrimSkip
   335  		}
   336  	})
   337  	return named
   338  }
   339  
   340  // Returns a map of all known bigmap type definitions inside the scripts storage type.
   341  // Unlabeled bigmaps are prefixed `bigmap_` followed by a unique sequence number.
   342  // Duplicate names are prevented by adding by a unique sequence number as well.
   343  func (s Script) BigmapTypes() map[string]Type {
   344  	return DetectBigmapTypes(s.Code.Storage)
   345  }
   346  
   347  // Returns a map of all known bigmap type definitions inside a given prim. Keys are
   348  // derived from type annotations. Unlabeled bigmaps are prefixed `bigmap_` followed
   349  // by a unique sequence number. Duplicate names are prevented by adding by
   350  // a unique sequence number as well.
   351  func DetectBigmapTypes(typ Prim) map[string]Type {
   352  	named := make(map[string]Type)
   353  	uniqueName := func(n string) string {
   354  		if _, ok := named[n]; !ok && n != "" {
   355  			return n
   356  		}
   357  		if n == "" {
   358  			n = "bigmap"
   359  		}
   360  		for i := 0; ; i++ {
   361  			name := n + "_" + strconv.Itoa(i)
   362  			if _, ok := named[name]; ok {
   363  				continue
   364  			}
   365  			return name
   366  		}
   367  	}
   368  	_ = typ.Walk(func(p Prim) error {
   369  		switch p.OpCode {
   370  		case K_STORAGE:
   371  			// The root node of the storage primitive; do nothing and continue
   372  			return nil
   373  
   374  		case T_MAP:
   375  			key := p.Args[0].String
   376  			if key == "" {
   377  				key = fmt.Sprint(p.Args[0].Hash64())
   378  			}
   379  			// Map's value type definition is in its second argument
   380  			for _, v := range DetectBigmapTypes(p.Args[1]) {
   381  				named[uniqueName(key)] = v
   382  			}
   383  			return PrimSkip
   384  
   385  		case T_BIG_MAP:
   386  			named[uniqueName(p.GetVarAnnoAny())] = NewType(p)
   387  			return PrimSkip
   388  
   389  		case T_LIST, T_OPTION:
   390  			// Type definition of list items and option values is in the
   391  			// first (and the only) argument of the primitive
   392  			for n, v := range DetectBigmapTypes(p.Args[0]) {
   393  				named[uniqueName(n)] = v
   394  			}
   395  			return PrimSkip
   396  
   397  		case T_OR, T_PAIR:
   398  			// OR candidates are defined in the arguments of the OR primitive
   399  			for _, arg := range p.Args {
   400  				for n, v := range DetectBigmapTypes(arg) {
   401  					named[uniqueName(n)] = v
   402  				}
   403  			}
   404  			return PrimSkip
   405  
   406  		default:
   407  			return PrimSkip
   408  		}
   409  	})
   410  	return named
   411  }
   412  
   413  func (p Script) EncodeBuffer(buf *bytes.Buffer) error {
   414  	// 1 write code segment
   415  	code, err := p.Code.MarshalBinary()
   416  	if err != nil {
   417  		return err
   418  	}
   419  
   420  	// 2 write data segment
   421  	data, err := p.Storage.MarshalBinary()
   422  	if err != nil {
   423  		return err
   424  	}
   425  
   426  	// append to output buffer
   427  	buf.Write(code)
   428  
   429  	// write data size
   430  	binary.Write(buf, binary.BigEndian, uint32(len(data)))
   431  
   432  	// append to output buffer
   433  	buf.Write(data)
   434  
   435  	return nil
   436  }
   437  
   438  func (p *Script) DecodeBuffer(buf *bytes.Buffer) error {
   439  	// 1 Code
   440  	if err := p.Code.DecodeBuffer(buf); err != nil {
   441  		return err
   442  	}
   443  
   444  	// 2 Storage
   445  
   446  	// check storage is present
   447  	if buf.Len() < 4 {
   448  		return io.ErrShortBuffer
   449  	}
   450  
   451  	// starts with BE uint32 total size
   452  	size := int(binary.BigEndian.Uint32(buf.Next(4)))
   453  	if buf.Len() < size {
   454  		return io.ErrShortBuffer
   455  	}
   456  
   457  	// read primitive tree
   458  	n := buf.Len()
   459  	if err := p.Storage.DecodeBuffer(buf); err != nil {
   460  		return err
   461  	}
   462  
   463  	// check we've read the defined amount of bytes
   464  	read := n - buf.Len()
   465  	if size != read {
   466  		return fmt.Errorf("micheline: expected script size %d but read %d bytes", size, read)
   467  	}
   468  
   469  	return nil
   470  }
   471  
   472  func (p Script) MarshalJSON() ([]byte, error) {
   473  	type alias Script
   474  	return json.Marshal(alias(p))
   475  }
   476  
   477  func (p Script) MarshalBinary() ([]byte, error) {
   478  	buf := bytes.NewBuffer(nil)
   479  	err := p.EncodeBuffer(buf)
   480  	return buf.Bytes(), err
   481  }
   482  
   483  func (p *Script) UnmarshalBinary(data []byte) error {
   484  	buf := bytes.NewBuffer(data)
   485  	err := p.DecodeBuffer(buf)
   486  	if err != nil {
   487  		return err
   488  	}
   489  	if buf.Len() > 0 {
   490  		return fmt.Errorf("micheline: %d unexpected extra trailer bytes", buf.Len())
   491  	}
   492  	return nil
   493  }
   494  
   495  func (c Code) MarshalBinary() ([]byte, error) {
   496  	buf := bytes.NewBuffer(nil)
   497  
   498  	// keep space for size
   499  	binary.Write(buf, binary.BigEndian, uint32(0))
   500  
   501  	// root element is a sequence
   502  	root := Prim{
   503  		Type: PrimSequence,
   504  		Args: []Prim{c.Param, c.Storage, c.Code},
   505  	}
   506  
   507  	if len(c.View.Args) > 0 {
   508  		root.Args = append(root.Args, c.View.Args...)
   509  	}
   510  
   511  	// store ill-formed contracts
   512  	if c.BadCode.IsValid() {
   513  		root = Prim{
   514  			Type: PrimSequence,
   515  			Args: []Prim{EmptyPrim, EmptyPrim, EmptyPrim, c.BadCode},
   516  		}
   517  	}
   518  
   519  	if err := root.EncodeBuffer(buf); err != nil {
   520  		return nil, err
   521  	}
   522  
   523  	// patch code size
   524  	res := buf.Bytes()
   525  	binary.BigEndian.PutUint32(res, uint32(len(res)-4))
   526  
   527  	return res, nil
   528  }
   529  
   530  func (c *Code) UnmarshalBinary(data []byte) error {
   531  	return c.DecodeBuffer(bytes.NewBuffer(data))
   532  }
   533  
   534  func (c *Code) DecodeBuffer(buf *bytes.Buffer) error {
   535  	// starts with BE uint32 total size
   536  	size := int(binary.BigEndian.Uint32(buf.Next(4)))
   537  	if buf.Len() < size {
   538  		return io.ErrShortBuffer
   539  	}
   540  
   541  	// read primitive tree
   542  	var prim Prim
   543  	if err := prim.DecodeBuffer(buf); err != nil {
   544  		return err
   545  	}
   546  
   547  	// check for sequence tag
   548  	if prim.Type != PrimSequence {
   549  		return fmt.Errorf("micheline: unexpected program tag 0x%x", prim.Type)
   550  	}
   551  
   552  	// unpack keyed program parts
   553  	for _, v := range prim.Args {
   554  		switch v.OpCode {
   555  		case K_PARAMETER:
   556  			c.Param = v
   557  		case K_STORAGE:
   558  			c.Storage = v
   559  		case K_CODE:
   560  			c.Code = v
   561  		case K_VIEW:
   562  			// append to view list
   563  			c.View.Args = append(c.View.Args, v)
   564  		case 255:
   565  			c.BadCode = v
   566  		default:
   567  			return fmt.Errorf("micheline: unexpected program key 0x%x", v.OpCode)
   568  		}
   569  	}
   570  	return nil
   571  }
   572  
   573  // UnmarshalScriptType is an optimized binary unmarshaller which decodes type trees only.
   574  // Use this to access smart contract types when script and storage are not required.
   575  func UnmarshalScriptType(data []byte) (param Type, storage Type, err error) {
   576  	buf := bytes.NewBuffer(data)
   577  
   578  	// starts with BE uint32 total size
   579  	size := int(binary.BigEndian.Uint32(buf.Next(4)))
   580  	if buf.Len() < size {
   581  		err = io.ErrShortBuffer
   582  		return
   583  	}
   584  
   585  	// we expect a sequence
   586  	b := buf.Next(1)
   587  	if len(b) == 0 {
   588  		err = io.ErrShortBuffer
   589  		return
   590  	}
   591  
   592  	// check tag
   593  	if PrimType(b[0]) != PrimSequence {
   594  		err = fmt.Errorf("micheline: unexpected program tag 0x%x", b[0])
   595  		return
   596  	}
   597  
   598  	// cross-check content size
   599  	size = int(binary.BigEndian.Uint32(buf.Next(4)))
   600  	if buf.Len() < size {
   601  		err = io.ErrShortBuffer
   602  		return
   603  	}
   604  
   605  	// decode K_PARAMETER primitive and unwrap outer prim
   606  	var pPrim Prim
   607  	if err = pPrim.DecodeBuffer(buf); err != nil {
   608  		return
   609  	}
   610  	param.Prim = pPrim.Args[0]
   611  
   612  	// decode K_STORAGE primitive and unwrap outer prim
   613  	var sPrim Prim
   614  	if err = sPrim.DecodeBuffer(buf); err != nil {
   615  		return
   616  	}
   617  	storage.Prim = sPrim.Args[0]
   618  	return
   619  }
   620  
   621  func (c Code) MarshalJSON() ([]byte, error) {
   622  	root := Prim{
   623  		Type: PrimSequence,
   624  		Args: []Prim{c.Param, c.Storage, c.Code},
   625  	}
   626  	if len(c.View.Args) > 0 {
   627  		root.Args = append(root.Args, c.View.Args...)
   628  	}
   629  	if c.BadCode.IsValid() {
   630  		root = c.BadCode
   631  	}
   632  	return json.Marshal(root)
   633  }
   634  
   635  func (c *Code) UnmarshalJSON(data []byte) error {
   636  	// read primitive tree
   637  	var prim Prim
   638  	if err := json.Unmarshal(data, &prim); err != nil {
   639  		return err
   640  	}
   641  
   642  	// check for sequence tag
   643  	if prim.Type != PrimSequence {
   644  		c.BadCode = prim
   645  		return nil
   646  	}
   647  
   648  	// unpack keyed program parts
   649  	isBadCode := false
   650  stopcode:
   651  	for _, v := range prim.Args {
   652  		switch v.OpCode {
   653  		case K_PARAMETER:
   654  			c.Param = v
   655  		case K_STORAGE:
   656  			c.Storage = v
   657  		case K_CODE:
   658  			c.Code = v
   659  		case K_VIEW:
   660  			c.View.Args = append(c.View.Args, v)
   661  		default:
   662  			isBadCode = true
   663  			log.Warnf("micheline: unexpected program key 0x%x (%d)", byte(v.OpCode), v.OpCode)
   664  			break stopcode
   665  		}
   666  	}
   667  	if isBadCode {
   668  		c.BadCode = prim
   669  	}
   670  	return nil
   671  }