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

     1  // Copyright (c) 2020-2021 Blockwatch Data Inc.
     2  // Author: alex@blockwatch.cc
     3  
     4  package micheline
     5  
     6  import (
     7  	"encoding/json"
     8  	"strings"
     9  )
    10  
    11  func (s *Script) Implements(i Interface) bool {
    12  	eps, _ := s.Entrypoints(true)
    13  	if len(eps) == 0 {
    14  		return false
    15  	}
    16  	return i.Matches(eps)
    17  }
    18  
    19  func (s *Script) ImplementsStrict(i Interface) bool {
    20  	eps, _ := s.Entrypoints(true)
    21  	if len(eps) == 0 {
    22  		return false
    23  	}
    24  	return i.MatchesStrict(eps)
    25  }
    26  
    27  func (s *Script) Interfaces() Interfaces {
    28  	eps, _ := s.Entrypoints(true)
    29  	if len(eps) == 0 {
    30  		return nil
    31  	}
    32  	iv := make(Interfaces, 0)
    33  	for _, i := range WellKnownInterfaces {
    34  		if !i.Matches(eps) {
    35  			continue
    36  		}
    37  		iv = append(iv, i)
    38  	}
    39  	return iv
    40  }
    41  
    42  func (s *Script) InterfacesStrict() Interfaces {
    43  	eps, _ := s.Entrypoints(true)
    44  	if len(eps) == 0 {
    45  		return nil
    46  	}
    47  	iv := make(Interfaces, 0)
    48  	for _, i := range WellKnownInterfaces {
    49  		if !i.MatchesStrict(eps) {
    50  			continue
    51  		}
    52  		iv = append(iv, i)
    53  	}
    54  	return iv
    55  }
    56  
    57  type Interface string
    58  
    59  func (m Interface) String() string {
    60  	return string(m)
    61  }
    62  
    63  // Checks if a contract implements all entrypoints required by a standard
    64  // interface without requiring argument labels to match. This is a looser
    65  // definition of interface compliance, but in line with the Michelson
    66  // type system which ignores annotation labels for type equality.
    67  //
    68  // This check uses extracted Typedefs to avoid issues when the Micheline
    69  // primitive structure diverges from the defined interface (e.g. due to
    70  // comb type unfolding).
    71  func (m Interface) Matches(e Entrypoints) bool {
    72  	for _, spec := range InterfaceSpecs[m] {
    73  		// use Typedef to avoid differences in data encoding with comb pairs
    74  		specType := NewType(spec).Typedef("")
    75  
    76  		var matched bool
    77  		for _, ep := range e {
    78  			// check entrypoint name
    79  			if ep.Name != spec.GetVarAnnoAny() {
    80  				continue
    81  			}
    82  
    83  			// check entrypoint type
    84  			epType := NewType(*ep.Prim).Typedef("")
    85  			if specType.Equal(epType) {
    86  				matched = true
    87  				break
    88  			}
    89  		}
    90  		if !matched {
    91  			return false
    92  		}
    93  	}
    94  	return true
    95  }
    96  
    97  // Checks if a contract strictly implements all standard interface
    98  // entrypoints including argument types and argument names (annotations).
    99  //
   100  // This check uses extracted Typedefs to avoid issues when the Micheline
   101  // primitive structure diverges from the defined interface (e.g. due to
   102  // comb type unfolding).
   103  func (m Interface) MatchesStrict(e Entrypoints) bool {
   104  	for _, spec := range InterfaceSpecs[m] {
   105  		// use Typedef to avoid differences in data encoding with comb pairs
   106  		specType := NewType(spec).Typedef("")
   107  
   108  		var matched bool
   109  		for _, ep := range e {
   110  			// check entrypoint name
   111  			if ep.Name != spec.GetVarAnnoAny() {
   112  				continue
   113  			}
   114  
   115  			// check entrypoint type
   116  			epType := NewType(*ep.Prim).Typedef("")
   117  			if specType.StrictEqual(epType) {
   118  				matched = true
   119  				break
   120  			}
   121  		}
   122  		if !matched {
   123  			return false
   124  		}
   125  	}
   126  	return true
   127  }
   128  
   129  func (m Interface) Contains(e Entrypoint) bool {
   130  	epType := NewType(*e.Prim).Typedef("")
   131  	for _, spec := range InterfaceSpecs[m] {
   132  		// check entrypoint name
   133  		if e.Name != spec.GetVarAnnoAny() {
   134  			continue
   135  		}
   136  		// check entrypoint type
   137  		specType := NewType(spec).Typedef("")
   138  		if specType.Equal(epType) {
   139  			return true
   140  		}
   141  	}
   142  	return false
   143  }
   144  
   145  func (m Interface) ContainsStrict(e Entrypoint) bool {
   146  	epType := NewType(*e.Prim).Typedef("")
   147  	for _, spec := range InterfaceSpecs[m] {
   148  		// check entrypoint name
   149  		if e.Name != spec.GetVarAnnoAny() {
   150  			continue
   151  		}
   152  		// check entrypoint type
   153  		specType := NewType(spec).Typedef("")
   154  		if specType.StrictEqual(epType) {
   155  			return true
   156  		}
   157  	}
   158  	return false
   159  }
   160  
   161  func (m Interface) TypeOf(name string) Type {
   162  	for _, v := range InterfaceSpecs[m] {
   163  		if v.GetVarAnnoAny() == name {
   164  			return NewType(v)
   165  		}
   166  	}
   167  	return Type{}
   168  }
   169  
   170  func (m Interface) PrimOf(name string) Prim {
   171  	for _, v := range InterfaceSpecs[m] {
   172  		if v.GetVarAnnoAny() == name {
   173  			return v
   174  		}
   175  	}
   176  	return Prim{}
   177  }
   178  
   179  type Interfaces []Interface
   180  
   181  func (i Interfaces) Contains(x Interface) bool {
   182  	for _, v := range i {
   183  		if v == x {
   184  			return true
   185  		}
   186  	}
   187  	return false
   188  }
   189  
   190  func (i Interfaces) String() string {
   191  	if len(i) == 0 {
   192  		return ""
   193  	}
   194  	var b strings.Builder
   195  	for k, v := range i {
   196  		if k > 0 {
   197  			b.WriteRune(',')
   198  		}
   199  		b.WriteString(string(v))
   200  	}
   201  	return b.String()
   202  }
   203  
   204  func (i *Interfaces) Parse(s string) error {
   205  	if len(s) == 0 {
   206  		return nil
   207  	}
   208  	split := strings.Split(s, ",")
   209  	if cap(*i) < len(split) {
   210  		*i = make([]Interface, len(split))
   211  	}
   212  	(*i) = (*i)[:len(split)]
   213  	for k := range split {
   214  		(*i)[k] = Interface(split[k])
   215  	}
   216  	return nil
   217  }
   218  
   219  func (i Interfaces) MarshalText() ([]byte, error) {
   220  	return []byte(i.String()), nil
   221  }
   222  
   223  func (i *Interfaces) UnmarshalText(b []byte) error {
   224  	return i.Parse(string(b))
   225  }
   226  
   227  func (i Interfaces) MarshalJSON() ([]byte, error) {
   228  	x := make([]string, len(i))
   229  	for k, v := range i {
   230  		x[k] = string(v)
   231  	}
   232  	return json.Marshal(x)
   233  }
   234  
   235  var (
   236  	IManager     = Interface("MANAGER")
   237  	ISetDelegate = Interface("SET_DELEGATE")
   238  	ITzip5       = Interface("TZIP-005")
   239  	ITzip7       = Interface("TZIP-007")
   240  	ITzip12      = Interface("TZIP-012")
   241  
   242  	WellKnownInterfaces = []Interface{
   243  		IManager,
   244  		ISetDelegate,
   245  		ITzip5,
   246  		ITzip7,
   247  		ITzip12,
   248  	}
   249  )
   250  
   251  // WellKnownInterfaces contains entrypoint types for standard call interfaces and other
   252  // known contracts.
   253  var InterfaceSpecs = map[Interface][]Prim{
   254  	// manager.tz
   255  	IManager: {
   256  		// 1 (lambda %do unit (list operation))
   257  		NewCodeAnno(T_LAMBDA, "%do", NewCode(T_UNIT), NewCode(T_LIST, NewCode(T_OPERATION))),
   258  		// 2 (unit %default)
   259  		NewCodeAnno(T_UNIT, "%default"),
   260  	},
   261  	// generic set delegate interface
   262  	ISetDelegate: {
   263  		// option %setDelegate key_hash
   264  		NewCodeAnno(T_OPTION, "%setDelegate", NewCode(T_KEY_HASH)),
   265  	},
   266  	// Tzip 5 a.k.a FA1
   267  	// https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-5/tzip-5.md
   268  	ITzip5: {
   269  		// (address :from, (address :to, nat :value)) %transfer
   270  		NewPairType(
   271  			NewCodeAnno(T_ADDRESS, ":from"),
   272  			NewPairType(
   273  				NewCodeAnno(T_ADDRESS, ":to"),
   274  				NewCodeAnno(T_NAT, ":value"),
   275  			),
   276  			"%transfer",
   277  		),
   278  		// view (address :owner) nat %getBalance
   279  		NewPairType(
   280  			NewCodeAnno(T_ADDRESS, ":owner"),
   281  			NewCode(T_CONTRACT, NewCode(T_NAT)),
   282  			"%getBalance",
   283  		),
   284  		// view unit nat %getTotalSupply
   285  		NewPairType(
   286  			NewCode(T_UNIT),
   287  			NewCode(T_CONTRACT, NewCode(T_NAT)),
   288  			"%getTotalSupply",
   289  		),
   290  	},
   291  	// Tzip 7 a.k.a FA1.2
   292  	// https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-7/tzip-7.md
   293  	ITzip7: {
   294  		// (address :from, (address :to, nat :value)) %transfer
   295  		NewPairType(
   296  			NewCodeAnno(T_ADDRESS, ":from"),
   297  			NewPairType(
   298  				NewCodeAnno(T_ADDRESS, ":to"),
   299  				NewCodeAnno(T_NAT, ":value"),
   300  			),
   301  			"%transfer",
   302  		),
   303  		// (address :spender, nat :value) %approve
   304  		NewPairType(
   305  			NewCodeAnno(T_ADDRESS, ":spender"),
   306  			NewCodeAnno(T_NAT, ":value"),
   307  			"%approve",
   308  		),
   309  		// (view (address :owner, address :spender) nat) %getAllowance
   310  		NewPairType(
   311  			NewPairType(
   312  				NewCodeAnno(T_ADDRESS, ":owner"),
   313  				NewCodeAnno(T_ADDRESS, ":spender"),
   314  			),
   315  			NewCode(T_CONTRACT, NewCode(T_NAT)),
   316  			"%getAllowance",
   317  		),
   318  		// (view (address :owner) nat) %getBalance
   319  		NewPairType(
   320  			NewCodeAnno(T_ADDRESS, ":owner"),
   321  			NewCode(T_CONTRACT, NewCode(T_NAT)),
   322  			"%getBalance",
   323  		),
   324  		// (view unit nat) %getTotalSupply
   325  		NewPairType(
   326  			NewCode(T_UNIT),
   327  			NewCode(T_CONTRACT, NewCode(T_NAT)),
   328  			"%getTotalSupply",
   329  		),
   330  	},
   331  	// Tzip 12 a.k.a. FA2
   332  	// https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-12/tzip-12.md
   333  	ITzip12: {
   334  		// (list %transfer
   335  		//   (pair
   336  		//     (address %from_)
   337  		//     (list %txs
   338  		//       (pair
   339  		//         (address %to_)
   340  		//         (pair
   341  		//           (nat %token_id)
   342  		//           (nat %amount)
   343  		//         )
   344  		//       )
   345  		//     )
   346  		//   )
   347  		// )
   348  		NewCodeAnno(T_LIST, "%transfer",
   349  			NewPairType(
   350  				NewCodeAnno(T_ADDRESS, "%from_"),
   351  				NewCodeAnno(T_LIST, "%txs",
   352  					NewPairType(
   353  						NewCodeAnno(T_ADDRESS, "%to_"),
   354  						NewPairType(
   355  							NewCodeAnno(T_NAT, "%token_id"),
   356  							NewCodeAnno(T_NAT, "%amount"),
   357  						),
   358  					),
   359  				),
   360  			),
   361  		),
   362  		// (pair %balance_of
   363  		//   (list %requests
   364  		//     (pair
   365  		//       (address %owner)
   366  		//       (nat %token_id)
   367  		//     )
   368  		//   )
   369  		//   (contract %callback
   370  		//     (list
   371  		//       (pair
   372  		//         (pair %request
   373  		//           (address %owner)
   374  		//           (nat %token_id)
   375  		//         )
   376  		//         (nat %balance)
   377  		//       )
   378  		//     )
   379  		//   )
   380  		// )
   381  		// DISABLED because of some broken tokens which get mis-classified
   382  		// NewPairType(
   383  		// 	NewCodeAnno(T_LIST, "%requests",
   384  		// 		NewPairType(
   385  		// 			NewCodeAnno(T_ADDRESS, "%owner"),
   386  		// 			NewCodeAnno(T_NAT, "%token_id"),
   387  		// 		),
   388  		// 	),
   389  		// 	NewCodeAnno(T_CONTRACT, "%callback",
   390  		// 		NewCode(T_LIST,
   391  		// 			NewPairType(
   392  		// 				NewPairType(
   393  		// 					NewCodeAnno(T_ADDRESS, "%owner"),
   394  		// 					NewCodeAnno(T_NAT, "%token_id"),
   395  		// 					"%request",
   396  		// 				),
   397  		// 				NewCodeAnno(T_NAT, "%balance"),
   398  		// 			),
   399  		// 		),
   400  		// 	),
   401  		// 	"%balance_of",
   402  		// ),
   403  		// (list %update_operators
   404  		//   (or
   405  		//     (pair %add_operator
   406  		//       (address %owner)
   407  		//       (pair
   408  		//         (address %operator)
   409  		//         (nat %token_id)
   410  		//       )
   411  		//     )
   412  		//     (pair %remove_operator
   413  		//       (address %owner)
   414  		//       (pair
   415  		//         (address %operator)
   416  		//         (nat %token_id)
   417  		//       )
   418  		//     )
   419  		//   )
   420  		// )
   421  		NewCodeAnno(T_LIST, "%update_operators",
   422  			NewCode(T_OR,
   423  				NewPairType(
   424  					NewCodeAnno(T_ADDRESS, "%owner"),
   425  					NewPairType(
   426  						NewCodeAnno(T_ADDRESS, "%operator"),
   427  						NewCodeAnno(T_NAT, "%token_id"),
   428  					),
   429  					"%add_operator",
   430  				),
   431  				NewPairType(
   432  					NewCodeAnno(T_ADDRESS, "%owner"),
   433  					NewPairType(
   434  						NewCodeAnno(T_ADDRESS, "%operator"),
   435  						NewCodeAnno(T_NAT, "%token_id"),
   436  					),
   437  					"%remove_operator",
   438  				),
   439  			),
   440  		),
   441  	},
   442  }