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 }