github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/smartcontract/manifest/standard/comply.go (about)

     1  package standard
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
     8  )
     9  
    10  // Various validation errors.
    11  var (
    12  	ErrMethodMissing         = errors.New("method missing")
    13  	ErrEventMissing          = errors.New("event missing")
    14  	ErrInvalidReturnType     = errors.New("invalid return type")
    15  	ErrInvalidParameterCount = errors.New("invalid parameter count")
    16  	ErrInvalidParameterName  = errors.New("invalid parameter name")
    17  	ErrInvalidParameterType  = errors.New("invalid parameter type")
    18  	ErrSafeMethodMismatch    = errors.New("method has wrong safe flag")
    19  )
    20  
    21  var checks = map[string][]*Standard{
    22  	manifest.NEP11StandardName: {Nep11NonDivisible, Nep11Divisible},
    23  	manifest.NEP17StandardName: {Nep17},
    24  	manifest.NEP11Payable:      {Nep11Payable},
    25  	manifest.NEP17Payable:      {Nep17Payable},
    26  }
    27  
    28  // Check checks if the manifest complies with all provided standards.
    29  // Currently, only NEP-17 is supported.
    30  func Check(m *manifest.Manifest, standards ...string) error {
    31  	return check(m, true, standards...)
    32  }
    33  
    34  // CheckABI is similar to Check but doesn't check parameter names.
    35  func CheckABI(m *manifest.Manifest, standards ...string) error {
    36  	return check(m, false, standards...)
    37  }
    38  
    39  func check(m *manifest.Manifest, checkNames bool, standards ...string) error {
    40  	for i := range standards {
    41  		ss, ok := checks[standards[i]]
    42  		if ok {
    43  			var err error
    44  			for i := range ss {
    45  				if err = comply(m, checkNames, ss[i]); err == nil {
    46  					break
    47  				}
    48  			}
    49  			if err != nil {
    50  				return fmt.Errorf("manifest is not compliant with '%s': %w", standards[i], err)
    51  			}
    52  		}
    53  	}
    54  	return nil
    55  }
    56  
    57  // Comply if m has all methods and event from st manifest and they have the same signature.
    58  // Parameter names are checked to exactly match the ones in the given standard.
    59  func Comply(m *manifest.Manifest, st *Standard) error {
    60  	return comply(m, true, st)
    61  }
    62  
    63  // ComplyABI is similar to Comply but doesn't check parameter names.
    64  func ComplyABI(m *manifest.Manifest, st *Standard) error {
    65  	return comply(m, false, st)
    66  }
    67  
    68  func comply(m *manifest.Manifest, checkNames bool, st *Standard) error {
    69  	if st.Base != nil {
    70  		if err := comply(m, checkNames, st.Base); err != nil {
    71  			return err
    72  		}
    73  	}
    74  	for _, stm := range st.ABI.Methods {
    75  		if err := checkMethod(m, &stm, false, checkNames); err != nil {
    76  			return err
    77  		}
    78  	}
    79  	for _, ste := range st.ABI.Events {
    80  		name := ste.Name
    81  		ed := m.ABI.GetEvent(name)
    82  		if ed == nil {
    83  			return fmt.Errorf("%w: event '%s'", ErrEventMissing, name)
    84  		} else if len(ste.Parameters) != len(ed.Parameters) {
    85  			return fmt.Errorf("%w: event '%s' (expected %d, got %d)", ErrInvalidParameterCount,
    86  				name, len(ste.Parameters), len(ed.Parameters))
    87  		}
    88  		for i := range ste.Parameters {
    89  			if checkNames && ste.Parameters[i].Name != ed.Parameters[i].Name {
    90  				return fmt.Errorf("%w: event '%s'[%d] (expected %s, got %s)", ErrInvalidParameterName,
    91  					name, i, ste.Parameters[i].Name, ed.Parameters[i].Name)
    92  			}
    93  			if ste.Parameters[i].Type != ed.Parameters[i].Type {
    94  				return fmt.Errorf("%w: event '%s' (expected %s, got %s)", ErrInvalidParameterType,
    95  					name, ste.Parameters[i].Type, ed.Parameters[i].Type)
    96  			}
    97  		}
    98  	}
    99  	for _, stm := range st.Optional {
   100  		if err := checkMethod(m, &stm, true, checkNames); err != nil {
   101  			return err
   102  		}
   103  	}
   104  	return nil
   105  }
   106  
   107  func checkMethod(m *manifest.Manifest, expected *manifest.Method,
   108  	allowMissing bool, checkNames bool) error {
   109  	actual := m.ABI.GetMethod(expected.Name, len(expected.Parameters))
   110  	if actual == nil {
   111  		if allowMissing {
   112  			return nil
   113  		}
   114  		return fmt.Errorf("%w: '%s' with %d parameters", ErrMethodMissing,
   115  			expected.Name, len(expected.Parameters))
   116  	}
   117  	if expected.ReturnType != actual.ReturnType {
   118  		return fmt.Errorf("%w: '%s' (expected %s, got %s)", ErrInvalidReturnType,
   119  			expected.Name, expected.ReturnType, actual.ReturnType)
   120  	}
   121  	for i := range expected.Parameters {
   122  		if checkNames && expected.Parameters[i].Name != actual.Parameters[i].Name {
   123  			return fmt.Errorf("%w: '%s'[%d] (expected %s, got %s)", ErrInvalidParameterName,
   124  				expected.Name, i, expected.Parameters[i].Name, actual.Parameters[i].Name)
   125  		}
   126  		if expected.Parameters[i].Type != actual.Parameters[i].Type {
   127  			return fmt.Errorf("%w: '%s'[%d] (expected %s, got %s)", ErrInvalidParameterType,
   128  				expected.Name, i, expected.Parameters[i].Type, actual.Parameters[i].Type)
   129  		}
   130  	}
   131  	if expected.Safe != actual.Safe {
   132  		return fmt.Errorf("'%s' %w: expected %t", expected.Name, ErrSafeMethodMismatch, expected.Safe)
   133  	}
   134  	return nil
   135  }