github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/interop/contract/call.go (about)

     1  package contract
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"math/big"
     7  	"strings"
     8  
     9  	"github.com/nspcc-dev/neo-go/pkg/core/dao"
    10  	"github.com/nspcc-dev/neo-go/pkg/core/interop"
    11  	"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
    12  	"github.com/nspcc-dev/neo-go/pkg/core/state"
    13  	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
    14  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
    15  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
    16  	"github.com/nspcc-dev/neo-go/pkg/util"
    17  	"github.com/nspcc-dev/neo-go/pkg/vm"
    18  	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
    19  )
    20  
    21  type policyChecker interface {
    22  	IsBlocked(*dao.Simple, util.Uint160) bool
    23  }
    24  
    25  // LoadToken calls method specified by the token id.
    26  func LoadToken(ic *interop.Context, id int32) error {
    27  	ctx := ic.VM.Context()
    28  	if !ctx.GetCallFlags().Has(callflag.ReadStates | callflag.AllowCall) {
    29  		return errors.New("invalid call flags")
    30  	}
    31  	tok := ctx.GetNEF().Tokens[id]
    32  	if int(tok.ParamCount) > ctx.Estack().Len() {
    33  		return errors.New("stack is too small")
    34  	}
    35  	args := make([]stackitem.Item, tok.ParamCount)
    36  	for i := range args {
    37  		args[i] = ic.VM.Estack().Pop().Item()
    38  	}
    39  	cs, err := ic.GetContract(tok.Hash)
    40  	if err != nil {
    41  		return fmt.Errorf("token contract %s not found: %w", tok.Hash.StringLE(), err)
    42  	}
    43  	return callInternal(ic, cs, tok.Method, tok.CallFlag, tok.HasReturn, args, false)
    44  }
    45  
    46  // Call calls a contract with flags.
    47  func Call(ic *interop.Context) error {
    48  	h := ic.VM.Estack().Pop().Bytes()
    49  	method := ic.VM.Estack().Pop().String()
    50  	fs := callflag.CallFlag(int32(ic.VM.Estack().Pop().BigInt().Int64()))
    51  	if fs&^callflag.All != 0 {
    52  		return errors.New("call flags out of range")
    53  	}
    54  	args := ic.VM.Estack().Pop().Array()
    55  	u, err := util.Uint160DecodeBytesBE(h)
    56  	if err != nil {
    57  		return errors.New("invalid contract hash")
    58  	}
    59  	cs, err := ic.GetContract(u)
    60  	if err != nil {
    61  		return fmt.Errorf("called contract %s not found: %w", u.StringLE(), err)
    62  	}
    63  	if strings.HasPrefix(method, "_") {
    64  		return errors.New("invalid method name (starts with '_')")
    65  	}
    66  	md := cs.Manifest.ABI.GetMethod(method, len(args))
    67  	if md == nil {
    68  		return fmt.Errorf("method not found: %s/%d", method, len(args))
    69  	}
    70  	hasReturn := md.ReturnType != smartcontract.VoidType
    71  	return callInternal(ic, cs, method, fs, hasReturn, args, true)
    72  }
    73  
    74  func callInternal(ic *interop.Context, cs *state.Contract, name string, f callflag.CallFlag,
    75  	hasReturn bool, args []stackitem.Item, isDynamic bool) error {
    76  	md := cs.Manifest.ABI.GetMethod(name, len(args))
    77  	if md.Safe {
    78  		f &^= (callflag.WriteStates | callflag.AllowNotify)
    79  	} else if ctx := ic.VM.Context(); ctx != nil && ctx.IsDeployed() {
    80  		curr, err := ic.GetContract(ic.VM.GetCurrentScriptHash())
    81  		if err == nil {
    82  			if !curr.Manifest.CanCall(cs.Hash, &cs.Manifest, name) {
    83  				return errors.New("disallowed method call")
    84  			}
    85  		}
    86  	}
    87  	return callExFromNative(ic, ic.VM.GetCurrentScriptHash(), cs, name, args, f, hasReturn, isDynamic, false)
    88  }
    89  
    90  // callExFromNative calls a contract with flags using the provided calling hash.
    91  func callExFromNative(ic *interop.Context, caller util.Uint160, cs *state.Contract,
    92  	name string, args []stackitem.Item, f callflag.CallFlag, hasReturn bool, isDynamic bool, callFromNative bool) error {
    93  	for _, nc := range ic.Natives {
    94  		if nc.Metadata().Name == nativenames.Policy {
    95  			var pch = nc.(policyChecker)
    96  			if pch.IsBlocked(ic.DAO, cs.Hash) {
    97  				return fmt.Errorf("contract %s is blocked", cs.Hash.StringLE())
    98  			}
    99  			break
   100  		}
   101  	}
   102  	md := cs.Manifest.ABI.GetMethod(name, len(args))
   103  	if md == nil {
   104  		return fmt.Errorf("method '%s' not found", name)
   105  	}
   106  
   107  	if len(args) != len(md.Parameters) {
   108  		return fmt.Errorf("invalid argument count: %d (expected %d)", len(args), len(md.Parameters))
   109  	}
   110  
   111  	methodOff := md.Offset
   112  	initOff := -1
   113  	md = cs.Manifest.ABI.GetMethod(manifest.MethodInit, 0)
   114  	if md != nil {
   115  		initOff = md.Offset
   116  	}
   117  	ic.Invocations[cs.Hash]++
   118  	f = ic.VM.Context().GetCallFlags() & f
   119  
   120  	wrapped := ic.VM.ContractHasTryBlock() && // If the method is not wrapped into try-catch block, then changes should be discarded anyway if exception occurs.
   121  		f&(callflag.All^callflag.ReadOnly) != 0 // If the method is safe, then it's read-only and doesn't perform storage changes or emit notifications.
   122  	baseNtfCount := len(ic.Notifications)
   123  	baseDAO := ic.DAO
   124  	if wrapped {
   125  		ic.DAO = ic.DAO.GetPrivate()
   126  	}
   127  	onUnload := func(v *vm.VM, ctx *vm.Context, commit bool) error {
   128  		if wrapped {
   129  			if commit {
   130  				_, err := ic.DAO.Persist()
   131  				if err != nil {
   132  					return fmt.Errorf("failed to persist changes %w", err)
   133  				}
   134  			} else {
   135  				ic.Notifications = ic.Notifications[:baseNtfCount] // Rollback all notification changes made by current context.
   136  			}
   137  			ic.DAO = baseDAO
   138  		}
   139  		if callFromNative && !commit {
   140  			return fmt.Errorf("unhandled exception")
   141  		}
   142  		if isDynamic {
   143  			return vm.DynamicOnUnload(v, ctx, commit)
   144  		}
   145  		return nil
   146  	}
   147  	ic.VM.LoadNEFMethod(&cs.NEF, caller, cs.Hash, f,
   148  		hasReturn, methodOff, initOff, onUnload)
   149  
   150  	for e, i := ic.VM.Estack(), len(args)-1; i >= 0; i-- {
   151  		e.PushItem(args[i])
   152  	}
   153  	return nil
   154  }
   155  
   156  // ErrNativeCall is returned for failed calls from native.
   157  var ErrNativeCall = errors.New("failed native call")
   158  
   159  // CallFromNative performs synchronous call from native contract.
   160  func CallFromNative(ic *interop.Context, caller util.Uint160, cs *state.Contract, method string, args []stackitem.Item, hasReturn bool) error {
   161  	startSize := len(ic.VM.Istack())
   162  	if err := callExFromNative(ic, caller, cs, method, args, callflag.All, hasReturn, false, true); err != nil {
   163  		return err
   164  	}
   165  
   166  	for !ic.VM.HasStopped() && len(ic.VM.Istack()) > startSize {
   167  		if err := ic.VM.Step(); err != nil {
   168  			return fmt.Errorf("%w: %w", ErrNativeCall, err)
   169  		}
   170  	}
   171  	if ic.VM.HasFailed() {
   172  		return ErrNativeCall
   173  	}
   174  	return nil
   175  }
   176  
   177  // GetCallFlags returns current context calling flags.
   178  func GetCallFlags(ic *interop.Context) error {
   179  	ic.VM.Estack().PushItem(stackitem.NewBigInteger(big.NewInt(int64(ic.VM.Context().GetCallFlags()))))
   180  	return nil
   181  }