221 lines
6.1 KiB
Go
Executable File
221 lines
6.1 KiB
Go
Executable File
package vm
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"slices"
|
|
|
|
"git.marketally.com/tutus-one/tutus-chain/pkg/crypto/hash"
|
|
"git.marketally.com/tutus-one/tutus-chain/pkg/smartcontract/callflag"
|
|
"git.marketally.com/tutus-one/tutus-chain/pkg/smartcontract/manifest"
|
|
"git.marketally.com/tutus-one/tutus-chain/pkg/smartcontract/nef"
|
|
"git.marketally.com/tutus-one/tutus-chain/pkg/smartcontract/scparser"
|
|
"git.marketally.com/tutus-one/tutus-chain/pkg/util"
|
|
"git.marketally.com/tutus-one/tutus-chain/pkg/vm/invocations"
|
|
"git.marketally.com/tutus-one/tutus-chain/pkg/vm/stackitem"
|
|
)
|
|
|
|
// scriptContext is a part of the Context that is shared between multiple Contexts,
|
|
// it's created when a new script is loaded into the VM while regular
|
|
// CALL/CALLL/CALLA internal invocations reuse it.
|
|
type scriptContext struct {
|
|
// The raw program script.
|
|
prog []byte
|
|
|
|
// Breakpoints.
|
|
breakPoints []int
|
|
|
|
// Evaluation stack pointer.
|
|
estack *Stack
|
|
|
|
static Slot
|
|
|
|
// Script hash of the prog.
|
|
scriptHash util.Uint160
|
|
|
|
// Caller's contract script hash.
|
|
callingScriptHash util.Uint160
|
|
|
|
// Caller's scriptContext, if not entry.
|
|
callingContext *scriptContext
|
|
|
|
// Call flags this context was created with.
|
|
callFlag callflag.CallFlag
|
|
|
|
// NEF represents a NEF file for the current contract.
|
|
NEF *nef.File
|
|
// Manifest represents a manifest for the current contract.
|
|
Manifest *manifest.Manifest
|
|
// invTree is an invocation tree (or a branch of it) for this context.
|
|
invTree *invocations.Tree
|
|
// onUnload is a callback that should be called after current context unloading
|
|
// if no exception occurs.
|
|
onUnload ContextUnloadCallback
|
|
// whitelisted denotes whether execution fee charging should be omitted for
|
|
// the current context.
|
|
whitelisted bool
|
|
}
|
|
|
|
// Context represents the current execution context of the VM.
|
|
type Context struct {
|
|
scparser.Context
|
|
|
|
sc *scriptContext
|
|
|
|
local Slot
|
|
arguments Slot
|
|
|
|
// Exception context stack.
|
|
tryStack Stack
|
|
|
|
// retCount specifies the number of return values.
|
|
retCount int
|
|
}
|
|
|
|
type contextAux struct {
|
|
Script string
|
|
IP int
|
|
NextIP int
|
|
Caller string
|
|
}
|
|
|
|
// ContextUnloadCallback is a callback method used on context unloading from istack.
|
|
type ContextUnloadCallback func(v *VM, ctx *Context, commit bool) error
|
|
|
|
// ErrMultiRet is returned when caller does not expect multiple return values
|
|
// from callee.
|
|
var ErrMultiRet = errors.New("multiple return values in a cross-contract call")
|
|
|
|
// NewContext returns a new Context object.
|
|
func NewContext(b []byte) *Context {
|
|
return NewContextWithParams(b, -1, 0)
|
|
}
|
|
|
|
// NewContextWithParams creates new Context objects using script, parameter count,
|
|
// return value count and initial position in script.
|
|
func NewContextWithParams(b []byte, rvcount int, pos int) *Context {
|
|
return &Context{
|
|
Context: *scparser.NewContext(b, pos),
|
|
sc: &scriptContext{
|
|
prog: b,
|
|
},
|
|
retCount: rvcount,
|
|
}
|
|
}
|
|
|
|
// Estack returns the evaluation stack of c.
|
|
func (c *Context) Estack() *Stack {
|
|
return c.sc.estack
|
|
}
|
|
|
|
// GetCallFlags returns the calling flags which the context was created with.
|
|
func (c *Context) GetCallFlags() callflag.CallFlag {
|
|
return c.sc.callFlag
|
|
}
|
|
|
|
// Program returns the loaded program.
|
|
func (c *Context) Program() []byte {
|
|
return c.sc.prog
|
|
}
|
|
|
|
// ScriptHash returns a hash of the script in the current context.
|
|
func (c *Context) ScriptHash() util.Uint160 {
|
|
if c.sc.scriptHash.Equals(util.Uint160{}) {
|
|
c.sc.scriptHash = hash.Hash160(c.sc.prog)
|
|
}
|
|
return c.sc.scriptHash
|
|
}
|
|
|
|
// GetNEF returns NEF structure used by this context if it's present.
|
|
func (c *Context) GetNEF() *nef.File {
|
|
return c.sc.NEF
|
|
}
|
|
|
|
// GetManifest returns Manifest used by this context if it's present.
|
|
func (c *Context) GetManifest() *manifest.Manifest {
|
|
return c.sc.Manifest
|
|
}
|
|
|
|
// NumOfReturnVals returns the number of return values expected from this context.
|
|
func (c *Context) NumOfReturnVals() int {
|
|
return c.retCount
|
|
}
|
|
|
|
func (c *Context) atBreakPoint() bool {
|
|
return slices.Contains(c.sc.breakPoints, c.NextIP())
|
|
}
|
|
|
|
// IsDeployed returns whether this context contains a deployed contract.
|
|
func (c *Context) IsDeployed() bool {
|
|
return c.sc.NEF != nil
|
|
}
|
|
|
|
// getContextScriptHash returns script hash of the invocation stack element
|
|
// number n.
|
|
func (v *VM) getContextScriptHash(n int) util.Uint160 {
|
|
if len(v.istack) <= n {
|
|
return util.Uint160{}
|
|
}
|
|
return v.istack[len(v.istack)-1-n].ScriptHash()
|
|
}
|
|
|
|
// IsCalledByEntry checks parent script contexts and return true if the current one
|
|
// is an entry script (the first loaded into the VM) or one called by it.
|
|
func (c *Context) IsCalledByEntry() bool {
|
|
return c.sc.callingContext == nil || c.sc.callingContext.callingContext == nil
|
|
}
|
|
|
|
// PushContextScriptHash pushes the script hash of the
|
|
// invocation stack element number n to the evaluation stack.
|
|
func (v *VM) PushContextScriptHash(n int) error {
|
|
h := v.getContextScriptHash(n)
|
|
v.Estack().PushItem(stackitem.NewByteArray(h.BytesBE()))
|
|
return nil
|
|
}
|
|
|
|
// MarshalJSON implements the JSON marshalling interface.
|
|
func (c *Context) MarshalJSON() ([]byte, error) {
|
|
var aux = contextAux{
|
|
Script: c.ScriptHash().StringLE(),
|
|
IP: c.IP(),
|
|
NextIP: c.NextIP(),
|
|
Caller: c.sc.callingScriptHash.StringLE(),
|
|
}
|
|
return json.Marshal(aux)
|
|
}
|
|
|
|
// DynamicOnUnload implements OnUnload script for dynamic calls, if no exception
|
|
// has occurred it checks that the context has exactly 0 (in which case a `Null`
|
|
// is pushed) or 1 returned value.
|
|
func DynamicOnUnload(v *VM, ctx *Context, commit bool) error {
|
|
if commit {
|
|
eLen := ctx.Estack().Len()
|
|
if eLen == 0 { // No return value, add one.
|
|
v.Context().Estack().PushItem(stackitem.Null{}) // Must use current context stack.
|
|
} else if eLen > 1 { // Only one can be returned.
|
|
return ErrMultiRet
|
|
} // One value returned, it's OK.
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// BreakPoints returns the current set of Context's breakpoints.
|
|
func (c *Context) BreakPoints() []int {
|
|
return slices.Clone(c.sc.breakPoints)
|
|
}
|
|
|
|
// ArgumentsSlot returns the arguments slot of Context.
|
|
func (c *Context) ArgumentsSlot() *Slot {
|
|
return &c.arguments
|
|
}
|
|
|
|
// LocalsSlot returns the local slot of Context.
|
|
func (c *Context) LocalsSlot() *Slot {
|
|
return &c.local
|
|
}
|
|
|
|
// StaticsSlot returns the static slot of Context.
|
|
func (c *Context) StaticsSlot() *Slot {
|
|
return &c.sc.static
|
|
}
|