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 }