forked from TrueCloudLab/neoneo-go
Merge pull request #1228 from nspcc-dev/fix/contractcall
Adjust `System.Contract.Call.*` interops
This commit is contained in:
commit
5cf4481331
32 changed files with 546 additions and 292 deletions
|
@ -16,7 +16,6 @@ var (
|
||||||
goBuiltins = []string{"len", "append", "panic"}
|
goBuiltins = []string{"len", "append", "panic"}
|
||||||
// Custom builtin utility functions.
|
// Custom builtin utility functions.
|
||||||
customBuiltins = []string{
|
customBuiltins = []string{
|
||||||
"AppCall",
|
|
||||||
"FromAddress", "Equals",
|
"FromAddress", "Equals",
|
||||||
"ToBool", "ToByteArray", "ToInteger",
|
"ToBool", "ToByteArray", "ToInteger",
|
||||||
}
|
}
|
||||||
|
@ -28,16 +27,23 @@ func (c *codegen) newGlobal(name string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// traverseGlobals visits and initializes global variables.
|
// traverseGlobals visits and initializes global variables.
|
||||||
func (c *codegen) traverseGlobals(f ast.Node) {
|
// and returns number of variables initialized.
|
||||||
n := countGlobals(f)
|
func (c *codegen) traverseGlobals(fs ...*ast.File) int {
|
||||||
|
var n int
|
||||||
|
for _, f := range fs {
|
||||||
|
n += countGlobals(f)
|
||||||
|
}
|
||||||
if n != 0 {
|
if n != 0 {
|
||||||
if n > 255 {
|
if n > 255 {
|
||||||
c.prog.BinWriter.Err = errors.New("too many global variables")
|
c.prog.BinWriter.Err = errors.New("too many global variables")
|
||||||
return
|
return 0
|
||||||
}
|
}
|
||||||
emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)})
|
emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)})
|
||||||
|
for _, f := range fs {
|
||||||
|
c.convertGlobals(f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
c.convertGlobals(f)
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
// countGlobals counts the global variables in the program to add
|
// countGlobals counts the global variables in the program to add
|
||||||
|
@ -113,10 +119,11 @@ func lastStmtIsReturn(decl *ast.FuncDecl) (b bool) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func analyzeFuncUsage(pkgs map[*types.Package]*loader.PackageInfo) funcUsage {
|
func analyzeFuncUsage(mainPkg *loader.PackageInfo, pkgs map[*types.Package]*loader.PackageInfo) funcUsage {
|
||||||
usage := funcUsage{}
|
usage := funcUsage{}
|
||||||
|
|
||||||
for _, pkg := range pkgs {
|
for _, pkg := range pkgs {
|
||||||
|
isMain := pkg == mainPkg
|
||||||
for _, f := range pkg.Files {
|
for _, f := range pkg.Files {
|
||||||
ast.Inspect(f, func(node ast.Node) bool {
|
ast.Inspect(f, func(node ast.Node) bool {
|
||||||
switch n := node.(type) {
|
switch n := node.(type) {
|
||||||
|
@ -127,6 +134,11 @@ func analyzeFuncUsage(pkgs map[*types.Package]*loader.PackageInfo) funcUsage {
|
||||||
case *ast.SelectorExpr:
|
case *ast.SelectorExpr:
|
||||||
usage[t.Sel.Name] = true
|
usage[t.Sel.Name] = true
|
||||||
}
|
}
|
||||||
|
case *ast.FuncDecl:
|
||||||
|
// exported functions are always assumed to be used
|
||||||
|
if isMain && n.Name.IsExported() {
|
||||||
|
usage[n.Name.Name] = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
|
@ -21,9 +21,6 @@ import (
|
||||||
"golang.org/x/tools/go/loader"
|
"golang.org/x/tools/go/loader"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The identifier of the entry function. Default set to Main.
|
|
||||||
const mainIdent = "Main"
|
|
||||||
|
|
||||||
type codegen struct {
|
type codegen struct {
|
||||||
// Information about the program with all its dependencies.
|
// Information about the program with all its dependencies.
|
||||||
buildInfo *buildInfo
|
buildInfo *buildInfo
|
||||||
|
@ -62,6 +59,12 @@ type codegen struct {
|
||||||
// to a text span in the source file.
|
// to a text span in the source file.
|
||||||
sequencePoints map[string][]DebugSeqPoint
|
sequencePoints map[string][]DebugSeqPoint
|
||||||
|
|
||||||
|
// initEndOffset specifies the end of the initialization method.
|
||||||
|
initEndOffset int
|
||||||
|
|
||||||
|
// mainPkg is a main package metadata.
|
||||||
|
mainPkg *loader.PackageInfo
|
||||||
|
|
||||||
// Label table for recording jump destinations.
|
// Label table for recording jump destinations.
|
||||||
l []int
|
l []int
|
||||||
}
|
}
|
||||||
|
@ -1220,13 +1223,6 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) {
|
||||||
typ = stackitem.BooleanT
|
typ = stackitem.BooleanT
|
||||||
}
|
}
|
||||||
c.emitConvert(typ)
|
c.emitConvert(typ)
|
||||||
case "AppCall":
|
|
||||||
c.emitReverse(len(expr.Args))
|
|
||||||
buf := c.getByteArray(expr.Args[0])
|
|
||||||
if buf != nil && len(buf) != 20 {
|
|
||||||
c.prog.Err = errors.New("invalid script hash")
|
|
||||||
}
|
|
||||||
emit.Syscall(c.prog.BinWriter, "System.Contract.Call")
|
|
||||||
case "Equals":
|
case "Equals":
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.EQUAL)
|
emit.Opcode(c.prog.BinWriter, opcode.EQUAL)
|
||||||
case "FromAddress":
|
case "FromAddress":
|
||||||
|
@ -1419,14 +1415,7 @@ func (c *codegen) newLambda(u uint16, lit *ast.FuncLit) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error {
|
func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error {
|
||||||
// Resolve the entrypoint of the program.
|
funUsage := analyzeFuncUsage(pkg, info.program.AllPackages)
|
||||||
main, mainFile := resolveEntryPoint(mainIdent, pkg)
|
|
||||||
if main == nil {
|
|
||||||
c.prog.Err = fmt.Errorf("could not find func main. Did you forget to declare it? ")
|
|
||||||
return c.prog.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
funUsage := analyzeFuncUsage(info.program.AllPackages)
|
|
||||||
|
|
||||||
// Bring all imported functions into scope.
|
// Bring all imported functions into scope.
|
||||||
for _, pkg := range info.program.AllPackages {
|
for _, pkg := range info.program.AllPackages {
|
||||||
|
@ -1435,10 +1424,12 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.traverseGlobals(mainFile)
|
c.mainPkg = pkg
|
||||||
|
n := c.traverseGlobals(pkg.Files...)
|
||||||
// convert the entry point first.
|
if n > 0 {
|
||||||
c.convertFuncDecl(mainFile, main, pkg.Pkg)
|
emit.Opcode(c.prog.BinWriter, opcode.RET)
|
||||||
|
c.initEndOffset = c.prog.Len()
|
||||||
|
}
|
||||||
|
|
||||||
// sort map keys to generate code deterministically.
|
// sort map keys to generate code deterministically.
|
||||||
keys := make([]*types.Package, 0, len(info.program.AllPackages))
|
keys := make([]*types.Package, 0, len(info.program.AllPackages))
|
||||||
|
@ -1458,7 +1449,7 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error {
|
||||||
case *ast.FuncDecl:
|
case *ast.FuncDecl:
|
||||||
// Don't convert the function if it's not used. This will save a lot
|
// Don't convert the function if it's not used. This will save a lot
|
||||||
// of bytecode space.
|
// of bytecode space.
|
||||||
if n.Name.Name != mainIdent && funUsage.funcUsed(n.Name.Name) {
|
if funUsage.funcUsed(n.Name.Name) {
|
||||||
c.convertFuncDecl(f, n, k)
|
c.convertFuncDecl(f, n, k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1504,10 +1495,8 @@ func (c *codegen) resolveFuncDecls(f *ast.File, pkg *types.Package) {
|
||||||
for _, decl := range f.Decls {
|
for _, decl := range f.Decls {
|
||||||
switch n := decl.(type) {
|
switch n := decl.(type) {
|
||||||
case *ast.FuncDecl:
|
case *ast.FuncDecl:
|
||||||
if n.Name.Name != mainIdent {
|
c.newFunc(n)
|
||||||
c.newFunc(n)
|
c.funcs[n.Name.Name].pkg = pkg
|
||||||
c.funcs[n.Name.Name].pkg = pkg
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,7 +134,7 @@ func CompileAndSave(src string, o *Options) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.ManifestFile != "" {
|
if o.ManifestFile != "" {
|
||||||
m, err := di.convertToManifest(o.ContractFeatures)
|
m, err := di.ConvertToManifest(o.ContractFeatures)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return b, errors.Wrap(err, "failed to convert debug info to manifest")
|
return b, errors.Wrap(err, "failed to convert debug info to manifest")
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
|
|
||||||
// DebugInfo represents smart-contract debug information.
|
// DebugInfo represents smart-contract debug information.
|
||||||
type DebugInfo struct {
|
type DebugInfo struct {
|
||||||
|
MainPkg string `json:"-"`
|
||||||
Hash util.Uint160 `json:"hash"`
|
Hash util.Uint160 `json:"hash"`
|
||||||
Documents []string `json:"documents"`
|
Documents []string `json:"documents"`
|
||||||
Methods []MethodDebugInfo `json:"methods"`
|
Methods []MethodDebugInfo `json:"methods"`
|
||||||
|
@ -102,8 +103,24 @@ func (c *codegen) saveSequencePoint(n ast.Node) {
|
||||||
|
|
||||||
func (c *codegen) emitDebugInfo(contract []byte) *DebugInfo {
|
func (c *codegen) emitDebugInfo(contract []byte) *DebugInfo {
|
||||||
d := &DebugInfo{
|
d := &DebugInfo{
|
||||||
Hash: hash.Hash160(contract),
|
MainPkg: c.mainPkg.Pkg.Name(),
|
||||||
Events: []EventDebugInfo{},
|
Hash: hash.Hash160(contract),
|
||||||
|
Events: []EventDebugInfo{},
|
||||||
|
}
|
||||||
|
if c.initEndOffset > 0 {
|
||||||
|
d.Methods = append(d.Methods, MethodDebugInfo{
|
||||||
|
ID: manifest.MethodInit,
|
||||||
|
Name: DebugMethodName{
|
||||||
|
Name: manifest.MethodInit,
|
||||||
|
Namespace: c.mainPkg.Pkg.Name(),
|
||||||
|
},
|
||||||
|
IsExported: true,
|
||||||
|
Range: DebugRange{
|
||||||
|
Start: 0,
|
||||||
|
End: uint16(c.initEndOffset),
|
||||||
|
},
|
||||||
|
ReturnType: "Void",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
for name, scope := range c.funcs {
|
for name, scope := range c.funcs {
|
||||||
m := c.methodInfoFromScope(name, scope)
|
m := c.methodInfoFromScope(name, scope)
|
||||||
|
@ -270,6 +287,7 @@ func (m *MethodDebugInfo) ToManifestMethod() (manifest.Method, error) {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
result.Name = strings.ToLower(string(m.Name.Name[0])) + m.Name.Name[1:]
|
result.Name = strings.ToLower(string(m.Name.Name[0])) + m.Name.Name[1:]
|
||||||
|
result.Offset = int(m.Range.Start)
|
||||||
result.Parameters = parameters
|
result.Parameters = parameters
|
||||||
result.ReturnType = returnType
|
result.ReturnType = returnType
|
||||||
return result, nil
|
return result, nil
|
||||||
|
@ -337,30 +355,16 @@ func parsePairJSON(data []byte, sep string) (string, string, error) {
|
||||||
return ss[0], ss[1], nil
|
return ss[0], ss[1], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// convertToManifest converts contract to the manifest.Manifest struct for debugger.
|
// ConvertToManifest converts contract to the manifest.Manifest struct for debugger.
|
||||||
// Note: manifest is taken from the external source, however it can be generated ad-hoc. See #1038.
|
// Note: manifest is taken from the external source, however it can be generated ad-hoc. See #1038.
|
||||||
func (di *DebugInfo) convertToManifest(fs smartcontract.PropertyState) (*manifest.Manifest, error) {
|
func (di *DebugInfo) ConvertToManifest(fs smartcontract.PropertyState) (*manifest.Manifest, error) {
|
||||||
var (
|
var err error
|
||||||
entryPoint manifest.Method
|
if di.MainPkg == "" {
|
||||||
mainNamespace string
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
for _, method := range di.Methods {
|
|
||||||
if method.Name.Name == mainIdent {
|
|
||||||
entryPoint, err = method.ToManifestMethod()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mainNamespace = method.Name.Namespace
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if entryPoint.Name == "" {
|
|
||||||
return nil, errors.New("no Main method was found")
|
return nil, errors.New("no Main method was found")
|
||||||
}
|
}
|
||||||
methods := make([]manifest.Method, 0)
|
methods := make([]manifest.Method, 0)
|
||||||
for _, method := range di.Methods {
|
for _, method := range di.Methods {
|
||||||
if method.Name.Name != mainIdent && method.IsExported && method.Name.Namespace == mainNamespace {
|
if method.IsExported && method.Name.Namespace == di.MainPkg {
|
||||||
mMethod, err := method.ToManifestMethod()
|
mMethod, err := method.ToManifestMethod()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -379,10 +383,9 @@ func (di *DebugInfo) convertToManifest(fs smartcontract.PropertyState) (*manifes
|
||||||
result := manifest.NewManifest(di.Hash)
|
result := manifest.NewManifest(di.Hash)
|
||||||
result.Features = fs
|
result.Features = fs
|
||||||
result.ABI = manifest.ABI{
|
result.ABI = manifest.ABI{
|
||||||
Hash: di.Hash,
|
Hash: di.Hash,
|
||||||
EntryPoint: entryPoint,
|
Methods: methods,
|
||||||
Methods: methods,
|
Events: events,
|
||||||
Events: events,
|
|
||||||
}
|
}
|
||||||
result.Permissions = []manifest.Permission{
|
result.Permissions = []manifest.Permission{
|
||||||
{
|
{
|
||||||
|
|
|
@ -127,24 +127,24 @@ func unexportedMethod() int { return 1 }
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("convert to Manifest", func(t *testing.T) {
|
t.Run("convert to Manifest", func(t *testing.T) {
|
||||||
actual, err := d.convertToManifest(smartcontract.HasStorage)
|
actual, err := d.ConvertToManifest(smartcontract.HasStorage)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
// note: offsets are hard to predict, so we just take them from the output
|
||||||
expected := &manifest.Manifest{
|
expected := &manifest.Manifest{
|
||||||
ABI: manifest.ABI{
|
ABI: manifest.ABI{
|
||||||
Hash: hash.Hash160(buf),
|
Hash: hash.Hash160(buf),
|
||||||
EntryPoint: manifest.Method{
|
|
||||||
Name: "main",
|
|
||||||
Parameters: []manifest.Parameter{
|
|
||||||
{
|
|
||||||
Name: "op",
|
|
||||||
Type: smartcontract.StringType,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ReturnType: smartcontract.BoolType,
|
|
||||||
},
|
|
||||||
Methods: []manifest.Method{
|
Methods: []manifest.Method{
|
||||||
{
|
{
|
||||||
Name: "methodInt",
|
Name: "main",
|
||||||
|
Offset: 0,
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
manifest.NewParameter("op", smartcontract.StringType),
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.BoolType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "methodInt",
|
||||||
|
Offset: 66,
|
||||||
Parameters: []manifest.Parameter{
|
Parameters: []manifest.Parameter{
|
||||||
{
|
{
|
||||||
Name: "a",
|
Name: "a",
|
||||||
|
@ -155,26 +155,31 @@ func unexportedMethod() int { return 1 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "methodString",
|
Name: "methodString",
|
||||||
|
Offset: 97,
|
||||||
Parameters: []manifest.Parameter{},
|
Parameters: []manifest.Parameter{},
|
||||||
ReturnType: smartcontract.StringType,
|
ReturnType: smartcontract.StringType,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "methodByteArray",
|
Name: "methodByteArray",
|
||||||
|
Offset: 103,
|
||||||
Parameters: []manifest.Parameter{},
|
Parameters: []manifest.Parameter{},
|
||||||
ReturnType: smartcontract.ByteArrayType,
|
ReturnType: smartcontract.ByteArrayType,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "methodArray",
|
Name: "methodArray",
|
||||||
|
Offset: 108,
|
||||||
Parameters: []manifest.Parameter{},
|
Parameters: []manifest.Parameter{},
|
||||||
ReturnType: smartcontract.ArrayType,
|
ReturnType: smartcontract.ArrayType,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "methodStruct",
|
Name: "methodStruct",
|
||||||
|
Offset: 113,
|
||||||
Parameters: []manifest.Parameter{},
|
Parameters: []manifest.Parameter{},
|
||||||
ReturnType: smartcontract.ArrayType,
|
ReturnType: smartcontract.ArrayType,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "methodConcat",
|
Name: "methodConcat",
|
||||||
|
Offset: 88,
|
||||||
Parameters: []manifest.Parameter{
|
Parameters: []manifest.Parameter{
|
||||||
{
|
{
|
||||||
Name: "a",
|
Name: "a",
|
||||||
|
@ -214,7 +219,6 @@ func unexportedMethod() int { return 1 }
|
||||||
}
|
}
|
||||||
require.True(t, expected.ABI.Hash.Equals(actual.ABI.Hash))
|
require.True(t, expected.ABI.Hash.Equals(actual.ABI.Hash))
|
||||||
require.ElementsMatch(t, expected.ABI.Methods, actual.ABI.Methods)
|
require.ElementsMatch(t, expected.ABI.Methods, actual.ABI.Methods)
|
||||||
require.Equal(t, expected.ABI.EntryPoint, actual.ABI.EntryPoint)
|
|
||||||
require.Equal(t, expected.ABI.Events, actual.ABI.Events)
|
require.Equal(t, expected.ABI.Events, actual.ABI.Events)
|
||||||
require.Equal(t, expected.Groups, actual.Groups)
|
require.Equal(t, expected.Groups, actual.Groups)
|
||||||
require.Equal(t, expected.Features, actual.Features)
|
require.Equal(t, expected.Features, actual.Features)
|
||||||
|
|
|
@ -3,7 +3,12 @@ package compiler_test
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestChangeGlobal(t *testing.T) {
|
func TestChangeGlobal(t *testing.T) {
|
||||||
|
@ -105,3 +110,20 @@ func TestArgumentLocal(t *testing.T) {
|
||||||
eval(t, src, big.NewInt(40))
|
eval(t, src, big.NewInt(40))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContractWithNoMain(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
var someGlobal int = 1
|
||||||
|
func Add3(a int) int {
|
||||||
|
someLocal := 2
|
||||||
|
return someGlobal + someLocal + a
|
||||||
|
}`
|
||||||
|
b, di, err := compiler.CompileWithDebugInfo(strings.NewReader(src))
|
||||||
|
require.NoError(t, err)
|
||||||
|
v := vm.New()
|
||||||
|
invokeMethod(t, "Add3", b, v, di)
|
||||||
|
v.Estack().PushVal(39)
|
||||||
|
require.NoError(t, v.Run())
|
||||||
|
require.Equal(t, 1, v.Estack().Len())
|
||||||
|
require.Equal(t, big.NewInt(42), v.PopResult())
|
||||||
|
}
|
||||||
|
|
|
@ -63,9 +63,10 @@ func TestFromAddress(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func spawnVM(t *testing.T, ic *interop.Context, src string) *vm.VM {
|
func spawnVM(t *testing.T, ic *interop.Context, src string) *vm.VM {
|
||||||
b, err := compiler.Compile(strings.NewReader(src))
|
b, di, err := compiler.CompileWithDebugInfo(strings.NewReader(src))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
v := core.SpawnVM(ic)
|
v := core.SpawnVM(ic)
|
||||||
|
invokeMethod(t, testMainIdent, b, v, di)
|
||||||
v.LoadScriptWithFlags(b, smartcontract.All)
|
v.LoadScriptWithFlags(b, smartcontract.All)
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
@ -73,18 +74,30 @@ func spawnVM(t *testing.T, ic *interop.Context, src string) *vm.VM {
|
||||||
func TestAppCall(t *testing.T) {
|
func TestAppCall(t *testing.T) {
|
||||||
srcInner := `
|
srcInner := `
|
||||||
package foo
|
package foo
|
||||||
|
var a int = 3
|
||||||
func Main(a []byte, b []byte) []byte {
|
func Main(a []byte, b []byte) []byte {
|
||||||
|
panic("Main was called")
|
||||||
|
}
|
||||||
|
func Append(a []byte, b []byte) []byte {
|
||||||
return append(a, b...)
|
return append(a, b...)
|
||||||
}
|
}
|
||||||
|
func Add3(n int) int {
|
||||||
|
return a + n
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
inner, err := compiler.Compile(strings.NewReader(srcInner))
|
inner, di, err := compiler.CompileWithDebugInfo(strings.NewReader(srcInner))
|
||||||
|
require.NoError(t, err)
|
||||||
|
m, err := di.ConvertToManifest(smartcontract.NoProperties)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ic := interop.NewContext(trigger.Application, nil, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet), nil, nil, nil, zaptest.NewLogger(t))
|
|
||||||
require.NoError(t, ic.DAO.PutContractState(&state.Contract{Script: inner}))
|
|
||||||
|
|
||||||
ih := hash.Hash160(inner)
|
ih := hash.Hash160(inner)
|
||||||
|
ic := interop.NewContext(trigger.Application, nil, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet), nil, nil, nil, zaptest.NewLogger(t))
|
||||||
|
require.NoError(t, ic.DAO.PutContractState(&state.Contract{
|
||||||
|
Script: inner,
|
||||||
|
Manifest: *m,
|
||||||
|
}))
|
||||||
|
|
||||||
t.Run("valid script", func(t *testing.T) {
|
t.Run("valid script", func(t *testing.T) {
|
||||||
src := getAppCallScript(fmt.Sprintf("%#v", ih.BytesBE()))
|
src := getAppCallScript(fmt.Sprintf("%#v", ih.BytesBE()))
|
||||||
v := spawnVM(t, ic, src)
|
v := spawnVM(t, ic, src)
|
||||||
|
@ -102,13 +115,6 @@ func TestAppCall(t *testing.T) {
|
||||||
require.Error(t, v.Run())
|
require.Error(t, v.Run())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("invalid script address", func(t *testing.T) {
|
|
||||||
src := getAppCallScript("[]byte{1, 2, 3}")
|
|
||||||
|
|
||||||
_, err := compiler.Compile(strings.NewReader(src))
|
|
||||||
require.Error(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("convert from string constant", func(t *testing.T) {
|
t.Run("convert from string constant", func(t *testing.T) {
|
||||||
src := `
|
src := `
|
||||||
package foo
|
package foo
|
||||||
|
@ -117,7 +123,7 @@ func TestAppCall(t *testing.T) {
|
||||||
func Main() []byte {
|
func Main() []byte {
|
||||||
x := []byte{1, 2}
|
x := []byte{1, 2}
|
||||||
y := []byte{3, 4}
|
y := []byte{3, 4}
|
||||||
result := engine.AppCall([]byte(scriptHash), x, y)
|
result := engine.AppCall([]byte(scriptHash), "append", x, y)
|
||||||
return result.([]byte)
|
return result.([]byte)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
@ -136,7 +142,7 @@ func TestAppCall(t *testing.T) {
|
||||||
x := []byte{1, 2}
|
x := []byte{1, 2}
|
||||||
y := []byte{3, 4}
|
y := []byte{3, 4}
|
||||||
var addr = []byte(` + fmt.Sprintf("%#v", string(ih.BytesBE())) + `)
|
var addr = []byte(` + fmt.Sprintf("%#v", string(ih.BytesBE())) + `)
|
||||||
result := engine.AppCall(addr, x, y)
|
result := engine.AppCall(addr, "append", x, y)
|
||||||
return result.([]byte)
|
return result.([]byte)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
@ -146,6 +152,21 @@ func TestAppCall(t *testing.T) {
|
||||||
|
|
||||||
assertResult(t, v, []byte{1, 2, 3, 4})
|
assertResult(t, v, []byte{1, 2, 3, 4})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("InitializedGlobals", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/interop/engine"
|
||||||
|
func Main() int {
|
||||||
|
var addr = []byte(` + fmt.Sprintf("%#v", string(ih.BytesBE())) + `)
|
||||||
|
result := engine.AppCall(addr, "add3", 39)
|
||||||
|
return result.(int)
|
||||||
|
}`
|
||||||
|
|
||||||
|
v := spawnVM(t, ic, src)
|
||||||
|
require.NoError(t, v.Run())
|
||||||
|
|
||||||
|
assertResult(t, v, big.NewInt(42))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAppCallScript(h string) string {
|
func getAppCallScript(h string) string {
|
||||||
|
@ -155,7 +176,7 @@ func getAppCallScript(h string) string {
|
||||||
func Main() []byte {
|
func Main() []byte {
|
||||||
x := []byte{1, 2}
|
x := []byte{1, 2}
|
||||||
y := []byte{3, 4}
|
y := []byte{3, 4}
|
||||||
result := engine.AppCall(` + h + `, x, y)
|
result := engine.AppCall(` + h + `, "append", x, y)
|
||||||
return result.([]byte)
|
return result.([]byte)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
|
@ -44,6 +44,9 @@ var syscalls = map[string]map[string]Syscall{
|
||||||
"Next": {"System.Enumerator.Next", false},
|
"Next": {"System.Enumerator.Next", false},
|
||||||
"Value": {"System.Enumerator.Value", false},
|
"Value": {"System.Enumerator.Value", false},
|
||||||
},
|
},
|
||||||
|
"engine": {
|
||||||
|
"AppCall": {"System.Contract.Call", false},
|
||||||
|
},
|
||||||
"iterator": {
|
"iterator": {
|
||||||
"Concat": {"System.Iterator.Concat", false},
|
"Concat": {"System.Iterator.Concat", false},
|
||||||
"Create": {"System.Iterator.Create", false},
|
"Create": {"System.Iterator.Create", false},
|
||||||
|
|
|
@ -7,6 +7,8 @@ import (
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
@ -20,6 +22,9 @@ type testCase struct {
|
||||||
result interface{}
|
result interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// testMainIdent is a method invoked in tests by default.
|
||||||
|
const testMainIdent = "Main"
|
||||||
|
|
||||||
func runTestCases(t *testing.T, tcases []testCase) {
|
func runTestCases(t *testing.T, tcases []testCase) {
|
||||||
for _, tcase := range tcases {
|
for _, tcase := range tcases {
|
||||||
t.Run(tcase.name, func(t *testing.T) { eval(t, tcase.src, tcase.result) })
|
t.Run(tcase.name, func(t *testing.T) { eval(t, tcase.src, tcase.result) })
|
||||||
|
@ -65,12 +70,31 @@ func vmAndCompileInterop(t *testing.T, src string) (*vm.VM, *storagePlugin) {
|
||||||
storePlugin := newStoragePlugin()
|
storePlugin := newStoragePlugin()
|
||||||
vm.RegisterInteropGetter(storePlugin.getInterop)
|
vm.RegisterInteropGetter(storePlugin.getInterop)
|
||||||
|
|
||||||
b, err := compiler.Compile(strings.NewReader(src))
|
b, di, err := compiler.CompileWithDebugInfo(strings.NewReader(src))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
vm.Load(b)
|
invokeMethod(t, testMainIdent, b, vm, di)
|
||||||
return vm, storePlugin
|
return vm, storePlugin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func invokeMethod(t *testing.T, method string, script []byte, v *vm.VM, di *compiler.DebugInfo) {
|
||||||
|
mainOffset := -1
|
||||||
|
initOffset := -1
|
||||||
|
for i := range di.Methods {
|
||||||
|
switch di.Methods[i].ID {
|
||||||
|
case method:
|
||||||
|
mainOffset = int(di.Methods[i].Range.Start)
|
||||||
|
case manifest.MethodInit:
|
||||||
|
initOffset = int(di.Methods[i].Range.Start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.True(t, mainOffset >= 0)
|
||||||
|
v.LoadScriptWithFlags(script, smartcontract.All)
|
||||||
|
v.Jump(v.Context(), mainOffset)
|
||||||
|
if initOffset >= 0 {
|
||||||
|
v.Call(v.Context(), initOffset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type storagePlugin struct {
|
type storagePlugin struct {
|
||||||
mem map[string][]byte
|
mem map[string][]byte
|
||||||
interops map[uint32]vm.InteropFunc
|
interops map[uint32]vm.InteropFunc
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/internal/random"
|
"github.com/nspcc-dev/neo-go/pkg/internal/random"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -62,12 +61,6 @@ func TestCachedDaoContracts(t *testing.T) {
|
||||||
require.NotNil(t, err)
|
require.NotNil(t, err)
|
||||||
|
|
||||||
m := manifest.NewManifest(hash.Hash160(script))
|
m := manifest.NewManifest(hash.Hash160(script))
|
||||||
m.ABI.EntryPoint.Name = "somename"
|
|
||||||
m.ABI.EntryPoint.Parameters = []manifest.Parameter{
|
|
||||||
manifest.NewParameter("first", smartcontract.IntegerType),
|
|
||||||
manifest.NewParameter("second", smartcontract.StringType),
|
|
||||||
}
|
|
||||||
m.ABI.EntryPoint.ReturnType = smartcontract.BoolType
|
|
||||||
|
|
||||||
cs := &state.Contract{
|
cs := &state.Contract{
|
||||||
ID: 123,
|
ID: 123,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -10,6 +11,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||||
|
@ -20,7 +22,6 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
|
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
|
@ -223,19 +224,15 @@ func TestCreateBasicChain(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Push some contract into the chain.
|
// Push some contract into the chain.
|
||||||
avm, err := ioutil.ReadFile(prefix + "test_contract.avm")
|
c, err := ioutil.ReadFile(prefix + "test_contract.go")
|
||||||
|
require.NoError(t, err)
|
||||||
|
avm, di, err := compiler.CompileWithDebugInfo(bytes.NewReader(c))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Logf("contractHash: %s", hash.Hash160(avm).StringLE())
|
t.Logf("contractHash: %s", hash.Hash160(avm).StringLE())
|
||||||
|
|
||||||
script := io.NewBufBinWriter()
|
script := io.NewBufBinWriter()
|
||||||
m := manifest.NewManifest(hash.Hash160(avm))
|
m, err := di.ConvertToManifest(smartcontract.HasStorage)
|
||||||
m.ABI.EntryPoint.Name = "Main"
|
require.NoError(t, err)
|
||||||
m.ABI.EntryPoint.Parameters = []manifest.Parameter{
|
|
||||||
manifest.NewParameter("method", smartcontract.StringType),
|
|
||||||
manifest.NewParameter("params", smartcontract.ArrayType),
|
|
||||||
}
|
|
||||||
m.ABI.EntryPoint.ReturnType = smartcontract.BoolType
|
|
||||||
m.Features = smartcontract.HasStorage
|
|
||||||
bs, err := m.MarshalJSON()
|
bs, err := m.MarshalJSON()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
emit.Bytes(script.BinWriter, bs)
|
emit.Bytes(script.BinWriter, bs)
|
||||||
|
@ -255,7 +252,7 @@ func TestCreateBasicChain(t *testing.T) {
|
||||||
|
|
||||||
// Now invoke this contract.
|
// Now invoke this contract.
|
||||||
script = io.NewBufBinWriter()
|
script = io.NewBufBinWriter()
|
||||||
emit.AppCallWithOperationAndArgs(script.BinWriter, hash.Hash160(avm), "Put", "testkey", "testvalue")
|
emit.AppCallWithOperationAndArgs(script.BinWriter, hash.Hash160(avm), "putValue", "testkey", "testvalue")
|
||||||
|
|
||||||
txInv := transaction.New(testchain.Network(), script.Bytes(), 1*native.GASFactor)
|
txInv := transaction.New(testchain.Network(), script.Bytes(), 1*native.GASFactor)
|
||||||
txInv.Nonce = getNextNonce()
|
txInv.Nonce = getNextNonce()
|
||||||
|
|
|
@ -288,12 +288,6 @@ func createVMAndContractState(t *testing.T) (*vm.VM, *state.Contract, *interop.C
|
||||||
v := vm.New()
|
v := vm.New()
|
||||||
script := []byte("testscript")
|
script := []byte("testscript")
|
||||||
m := manifest.NewManifest(hash.Hash160(script))
|
m := manifest.NewManifest(hash.Hash160(script))
|
||||||
m.ABI.EntryPoint.Parameters = []manifest.Parameter{
|
|
||||||
manifest.NewParameter("Name", smartcontract.StringType),
|
|
||||||
manifest.NewParameter("Amount", smartcontract.IntegerType),
|
|
||||||
manifest.NewParameter("Hash", smartcontract.Hash160Type),
|
|
||||||
}
|
|
||||||
m.ABI.EntryPoint.ReturnType = smartcontract.ArrayType
|
|
||||||
m.Features = smartcontract.HasStorage
|
m.Features = smartcontract.HasStorage
|
||||||
contractState := &state.Contract{
|
contractState := &state.Contract{
|
||||||
Script: script,
|
Script: script,
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||||
|
@ -16,6 +17,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
@ -492,16 +494,54 @@ func contractCallExInternal(ic *interop.Context, v *vm.VM, h []byte, method stac
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
name := string(bs)
|
||||||
|
if strings.HasPrefix(name, "_") {
|
||||||
|
return errors.New("invalid method name (starts with '_')")
|
||||||
|
}
|
||||||
|
md := cs.Manifest.ABI.GetMethod(name)
|
||||||
|
if md == nil {
|
||||||
|
return fmt.Errorf("method '%s' not found", name)
|
||||||
|
}
|
||||||
curr, err := ic.DAO.GetContractState(v.GetCurrentScriptHash())
|
curr, err := ic.DAO.GetContractState(v.GetCurrentScriptHash())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if !curr.Manifest.CanCall(&cs.Manifest, string(bs)) {
|
if !curr.Manifest.CanCall(&cs.Manifest, string(bs)) {
|
||||||
return errors.New("disallowed method call")
|
return errors.New("disallowed method call")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
arr, ok := args.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("second argument must be an array")
|
||||||
|
}
|
||||||
|
if len(arr) != len(md.Parameters) {
|
||||||
|
return fmt.Errorf("invalid argument count: %d (expected %d)", len(arr), len(md.Parameters))
|
||||||
|
}
|
||||||
|
|
||||||
ic.Invocations[u]++
|
ic.Invocations[u]++
|
||||||
v.LoadScriptWithHash(cs.Script, u, v.Context().GetCallFlags()&f)
|
v.LoadScriptWithHash(cs.Script, u, v.Context().GetCallFlags()&f)
|
||||||
v.Estack().PushVal(args)
|
var isNative bool
|
||||||
v.Estack().PushVal(method)
|
for i := range ic.Natives {
|
||||||
|
if ic.Natives[i].Metadata().Hash.Equals(u) {
|
||||||
|
isNative = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isNative {
|
||||||
|
v.Estack().PushVal(args)
|
||||||
|
v.Estack().PushVal(method)
|
||||||
|
} else {
|
||||||
|
for i := len(arr) - 1; i >= 0; i-- {
|
||||||
|
v.Estack().PushVal(arr[i])
|
||||||
|
}
|
||||||
|
// use Jump not Call here because context was loaded in LoadScript above.
|
||||||
|
v.Jump(v.Context(), md.Offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
md = cs.Manifest.ABI.GetMethod(manifest.MethodInit)
|
||||||
|
if md != nil {
|
||||||
|
v.Call(v.Context(), md.Offset)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -323,6 +324,155 @@ func TestBlockchainGetContractState(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getTestContractState() *state.Contract {
|
||||||
|
script := []byte{
|
||||||
|
byte(opcode.ABORT), // abort if no offset was provided
|
||||||
|
byte(opcode.ADD), byte(opcode.RET),
|
||||||
|
byte(opcode.PUSH7), byte(opcode.RET),
|
||||||
|
byte(opcode.DROP), byte(opcode.RET),
|
||||||
|
byte(opcode.INITSSLOT), 1, byte(opcode.PUSH3), byte(opcode.STSFLD0), byte(opcode.RET),
|
||||||
|
byte(opcode.LDSFLD0), byte(opcode.ADD), byte(opcode.RET),
|
||||||
|
}
|
||||||
|
h := hash.Hash160(script)
|
||||||
|
m := manifest.NewManifest(h)
|
||||||
|
m.ABI.Methods = []manifest.Method{
|
||||||
|
{
|
||||||
|
Name: "add",
|
||||||
|
Offset: 1,
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
manifest.NewParameter("addend1", smartcontract.IntegerType),
|
||||||
|
manifest.NewParameter("addend2", smartcontract.IntegerType),
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.IntegerType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "ret7",
|
||||||
|
Offset: 3,
|
||||||
|
Parameters: []manifest.Parameter{},
|
||||||
|
ReturnType: smartcontract.IntegerType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "drop",
|
||||||
|
Offset: 5,
|
||||||
|
ReturnType: smartcontract.VoidType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: manifest.MethodInit,
|
||||||
|
Offset: 7,
|
||||||
|
ReturnType: smartcontract.VoidType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "add3",
|
||||||
|
Offset: 12,
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
manifest.NewParameter("addend", smartcontract.IntegerType),
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.IntegerType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return &state.Contract{
|
||||||
|
Script: script,
|
||||||
|
Manifest: *m,
|
||||||
|
ID: 42,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContractCall(t *testing.T) {
|
||||||
|
v, ic, bc := createVM(t)
|
||||||
|
defer bc.Close()
|
||||||
|
|
||||||
|
cs := getTestContractState()
|
||||||
|
require.NoError(t, ic.DAO.PutContractState(cs))
|
||||||
|
|
||||||
|
currScript := []byte{byte(opcode.NOP)}
|
||||||
|
initVM := func(v *vm.VM) {
|
||||||
|
v.Istack().Clear()
|
||||||
|
v.Estack().Clear()
|
||||||
|
v.Load(currScript)
|
||||||
|
v.Estack().PushVal(42) // canary
|
||||||
|
}
|
||||||
|
|
||||||
|
h := cs.Manifest.ABI.Hash
|
||||||
|
m := manifest.NewManifest(hash.Hash160(currScript))
|
||||||
|
perm := manifest.NewPermission(manifest.PermissionHash, h)
|
||||||
|
perm.Methods.Add("add")
|
||||||
|
perm.Methods.Add("drop")
|
||||||
|
perm.Methods.Add("add3")
|
||||||
|
m.Permissions = append(m.Permissions, *perm)
|
||||||
|
|
||||||
|
require.NoError(t, ic.DAO.PutContractState(&state.Contract{
|
||||||
|
Script: currScript,
|
||||||
|
Manifest: *m,
|
||||||
|
ID: 123,
|
||||||
|
}))
|
||||||
|
|
||||||
|
addArgs := stackitem.NewArray([]stackitem.Item{stackitem.Make(1), stackitem.Make(2)})
|
||||||
|
t.Run("Good", func(t *testing.T) {
|
||||||
|
initVM(v)
|
||||||
|
v.Estack().PushVal(addArgs)
|
||||||
|
v.Estack().PushVal("add")
|
||||||
|
v.Estack().PushVal(h.BytesBE())
|
||||||
|
require.NoError(t, contractCall(ic, v))
|
||||||
|
require.NoError(t, v.Run())
|
||||||
|
require.Equal(t, 2, v.Estack().Len())
|
||||||
|
require.Equal(t, big.NewInt(3), v.Estack().Pop().Value())
|
||||||
|
require.Equal(t, big.NewInt(42), v.Estack().Pop().Value())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("CallExInvalidFlag", func(t *testing.T) {
|
||||||
|
initVM(v)
|
||||||
|
v.Estack().PushVal(byte(0xFF))
|
||||||
|
v.Estack().PushVal(addArgs)
|
||||||
|
v.Estack().PushVal("add")
|
||||||
|
v.Estack().PushVal(h.BytesBE())
|
||||||
|
require.Error(t, contractCallEx(ic, v))
|
||||||
|
})
|
||||||
|
|
||||||
|
runInvalid := func(args ...interface{}) func(t *testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
initVM(v)
|
||||||
|
for i := range args {
|
||||||
|
v.Estack().PushVal(args[i])
|
||||||
|
}
|
||||||
|
require.Error(t, contractCall(ic, v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Invalid", func(t *testing.T) {
|
||||||
|
t.Run("Hash", runInvalid(addArgs, "add", h.BytesBE()[1:]))
|
||||||
|
t.Run("MissingHash", runInvalid(addArgs, "add", util.Uint160{}.BytesBE()))
|
||||||
|
t.Run("Method", runInvalid(addArgs, stackitem.NewInterop("add"), h.BytesBE()))
|
||||||
|
t.Run("MissingMethod", runInvalid(addArgs, "sub", h.BytesBE()))
|
||||||
|
t.Run("DisallowedMethod", runInvalid(stackitem.NewArray(nil), "ret7", h.BytesBE()))
|
||||||
|
t.Run("Arguments", runInvalid(1, "add", h.BytesBE()))
|
||||||
|
t.Run("NotEnoughArguments", runInvalid(
|
||||||
|
stackitem.NewArray([]stackitem.Item{stackitem.Make(1)}), "add", h.BytesBE()))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("IsolatedStack", func(t *testing.T) {
|
||||||
|
initVM(v)
|
||||||
|
v.Estack().PushVal(stackitem.NewArray(nil))
|
||||||
|
v.Estack().PushVal("drop")
|
||||||
|
v.Estack().PushVal(h.BytesBE())
|
||||||
|
require.NoError(t, contractCall(ic, v))
|
||||||
|
require.Error(t, v.Run())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("CallInitialize", func(t *testing.T) {
|
||||||
|
t.Run("Directly", runInvalid(stackitem.NewArray([]stackitem.Item{}), "_initialize", h.BytesBE()))
|
||||||
|
|
||||||
|
initVM(v)
|
||||||
|
v.Estack().PushVal(stackitem.NewArray([]stackitem.Item{stackitem.Make(5)}))
|
||||||
|
v.Estack().PushVal("add3")
|
||||||
|
v.Estack().PushVal(h.BytesBE())
|
||||||
|
require.NoError(t, contractCall(ic, v))
|
||||||
|
require.NoError(t, v.Run())
|
||||||
|
require.Equal(t, 2, v.Estack().Len())
|
||||||
|
require.Equal(t, big.NewInt(8), v.Estack().Pop().Value())
|
||||||
|
require.Equal(t, big.NewInt(42), v.Estack().Pop().Value())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestContractCreate(t *testing.T) {
|
func TestContractCreate(t *testing.T) {
|
||||||
v, cs, ic, bc := createVMAndContractState(t)
|
v, cs, ic, bc := createVMAndContractState(t)
|
||||||
v.GasLimit = -1
|
v.GasLimit = -1
|
||||||
|
@ -520,13 +670,6 @@ func TestContractUpdate(t *testing.T) {
|
||||||
manifest := &manifest.Manifest{
|
manifest := &manifest.Manifest{
|
||||||
ABI: manifest.ABI{
|
ABI: manifest.ABI{
|
||||||
Hash: cs.ScriptHash(),
|
Hash: cs.ScriptHash(),
|
||||||
EntryPoint: manifest.Method{
|
|
||||||
Name: "Main",
|
|
||||||
Parameters: []manifest.Parameter{
|
|
||||||
manifest.NewParameter("NewParameter", smartcontract.IntegerType),
|
|
||||||
},
|
|
||||||
ReturnType: smartcontract.StringType,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Features: smartcontract.HasStorage,
|
Features: smartcontract.HasStorage,
|
||||||
}
|
}
|
||||||
|
@ -554,13 +697,6 @@ func TestContractUpdate(t *testing.T) {
|
||||||
newManifest := manifest.Manifest{
|
newManifest := manifest.Manifest{
|
||||||
ABI: manifest.ABI{
|
ABI: manifest.ABI{
|
||||||
Hash: hash.Hash160(newScript),
|
Hash: hash.Hash160(newScript),
|
||||||
EntryPoint: manifest.Method{
|
|
||||||
Name: "Main",
|
|
||||||
Parameters: []manifest.Parameter{
|
|
||||||
manifest.NewParameter("VeryNewParameter", smartcontract.IntegerType),
|
|
||||||
},
|
|
||||||
ReturnType: smartcontract.StringType,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Features: smartcontract.HasStorage,
|
Features: smartcontract.HasStorage,
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,12 +18,6 @@ func Deploy(ic *interop.Context, _ *vm.VM) error {
|
||||||
for _, native := range ic.Natives {
|
for _, native := range ic.Natives {
|
||||||
md := native.Metadata()
|
md := native.Metadata()
|
||||||
|
|
||||||
ps := md.Manifest.ABI.EntryPoint.Parameters
|
|
||||||
params := make([]smartcontract.ParamType, len(ps))
|
|
||||||
for i := range ps {
|
|
||||||
params[i] = ps[i].Type
|
|
||||||
}
|
|
||||||
|
|
||||||
cs := &state.Contract{
|
cs := &state.Contract{
|
||||||
ID: md.ContractID,
|
ID: md.ContractID,
|
||||||
Script: md.Script,
|
Script: md.Script,
|
||||||
|
|
|
@ -78,7 +78,7 @@ func newNEP5Native(name string) *nep5TokenNative {
|
||||||
md = newMethodAndPrice(n.Transfer, 8000000, smartcontract.AllowModifyStates)
|
md = newMethodAndPrice(n.Transfer, 8000000, smartcontract.AllowModifyStates)
|
||||||
n.AddMethod(md, desc, false)
|
n.AddMethod(md, desc, false)
|
||||||
|
|
||||||
desc = newDescriptor("onPersist", smartcontract.BoolType)
|
desc = newDescriptor("onPersist", smartcontract.VoidType)
|
||||||
md = newMethodAndPrice(getOnPersistWrapper(n.OnPersist), 0, smartcontract.AllowModifyStates)
|
md = newMethodAndPrice(getOnPersistWrapper(n.OnPersist), 0, smartcontract.AllowModifyStates)
|
||||||
n.AddMethod(md, desc, false)
|
n.AddMethod(md, desc, false)
|
||||||
|
|
||||||
|
|
|
@ -105,7 +105,7 @@ func newPolicy() *Policy {
|
||||||
md = newMethodAndPrice(p.unblockAccount, 3000000, smartcontract.NoneFlag)
|
md = newMethodAndPrice(p.unblockAccount, 3000000, smartcontract.NoneFlag)
|
||||||
p.AddMethod(md, desc, false)
|
p.AddMethod(md, desc, false)
|
||||||
|
|
||||||
desc = newDescriptor("onPersist", smartcontract.BoolType)
|
desc = newDescriptor("onPersist", smartcontract.VoidType)
|
||||||
md = newMethodAndPrice(getOnPersistWrapper(p.OnPersist), 0, smartcontract.AllowModifyStates)
|
md = newMethodAndPrice(getOnPersistWrapper(p.OnPersist), 0, smartcontract.AllowModifyStates)
|
||||||
p.AddMethod(md, desc, false)
|
p.AddMethod(md, desc, false)
|
||||||
return p
|
return p
|
||||||
|
|
|
@ -95,7 +95,10 @@ func TestNativeContract_Invoke(t *testing.T) {
|
||||||
tn := newTestNative()
|
tn := newTestNative()
|
||||||
chain.registerNative(tn)
|
chain.registerNative(tn)
|
||||||
|
|
||||||
err := chain.dao.PutContractState(&state.Contract{Script: tn.meta.Script})
|
err := chain.dao.PutContractState(&state.Contract{
|
||||||
|
Script: tn.meta.Script,
|
||||||
|
Manifest: tn.meta.Manifest,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
w := io.NewBufBinWriter()
|
w := io.NewBufBinWriter()
|
||||||
|
|
|
@ -7,12 +7,8 @@ package engine
|
||||||
|
|
||||||
// AppCall executes previously deployed blockchain contract with specified hash
|
// AppCall executes previously deployed blockchain contract with specified hash
|
||||||
// (160 bit in BE form represented as 20-byte slice) using provided arguments.
|
// (160 bit in BE form represented as 20-byte slice) using provided arguments.
|
||||||
// It returns whatever this contract returns. Even though this function accepts
|
// It returns whatever this contract returns. This function uses
|
||||||
// a slice for scriptHash you can only use it for contracts known at
|
|
||||||
// compile time, because there is a significant difference between static and
|
|
||||||
// dynamic calls in Neo (contracts should have a special property declared
|
|
||||||
// and paid for to be able to use dynamic calls). This function uses
|
|
||||||
// `System.Contract.Call` syscall.
|
// `System.Contract.Call` syscall.
|
||||||
func AppCall(scriptHash []byte, args ...interface{}) interface{} {
|
func AppCall(scriptHash []byte, method string, args ...interface{}) interface{} {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -298,19 +298,13 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
||||||
}
|
}
|
||||||
return c.GetContractState(hash)
|
return c.GetContractState(hash)
|
||||||
},
|
},
|
||||||
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","manifest":{"abi":{"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","entryPoint":{"name":"Main","parameters":[{"name":"method","type":"String"},{"name":"params","type":"Array"}],"returntype":"Boolean"},"methods":[],"events":[]},"groups":[],"features":{"payable":false,"storage":true},"permissions":null,"trusts":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`,
|
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","manifest":{"abi":{"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","methods":[],"events":[]},"groups":[],"features":{"payable":false,"storage":true},"permissions":null,"trusts":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`,
|
||||||
result: func(c *Client) interface{} {
|
result: func(c *Client) interface{} {
|
||||||
script, err := base64.StdEncoding.DecodeString("VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==")
|
script, err := base64.StdEncoding.DecodeString("VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
m := manifest.NewManifest(hash.Hash160(script))
|
m := manifest.NewManifest(hash.Hash160(script))
|
||||||
m.ABI.EntryPoint.Name = "Main"
|
|
||||||
m.ABI.EntryPoint.Parameters = []manifest.Parameter{
|
|
||||||
manifest.NewParameter("method", smartcontract.StringType),
|
|
||||||
manifest.NewParameter("params", smartcontract.ArrayType),
|
|
||||||
}
|
|
||||||
m.ABI.EntryPoint.ReturnType = smartcontract.BoolType
|
|
||||||
m.Features = smartcontract.HasStorage
|
m.Features = smartcontract.HasStorage
|
||||||
cs := &state.Contract{
|
cs := &state.Contract{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
|
|
|
@ -51,8 +51,8 @@ type rpcTestCase struct {
|
||||||
check func(t *testing.T, e *executor, result interface{})
|
check func(t *testing.T, e *executor, result interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
const testContractHash = "402da558b87b5e54b59dc242c788bb4dd4cd906c"
|
const testContractHash = "36c3b0c85d98607db00b711885ec3e411d9b1672"
|
||||||
const deploymentTxHash = "2afd69cc80ebe900a060450e8628b57063f3ec93ca5fc7f94582be4a4f3a041f"
|
const deploymentTxHash = "dcf4fe429ec84947361c86c2192b14641be7f0c6e2bdf8d150fad731160ed386"
|
||||||
|
|
||||||
var rpcTestCases = map[string][]rpcTestCase{
|
var rpcTestCases = map[string][]rpcTestCase{
|
||||||
"getapplicationlog": {
|
"getapplicationlog": {
|
||||||
|
|
BIN
pkg/rpc/server/testdata/test_contract.avm
vendored
BIN
pkg/rpc/server/testdata/test_contract.avm
vendored
Binary file not shown.
164
pkg/rpc/server/testdata/test_contract.go
vendored
164
pkg/rpc/server/testdata/test_contract.go
vendored
|
@ -10,84 +10,88 @@ const (
|
||||||
decimals = 2
|
decimals = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
func Main(operation string, args []interface{}) interface{} {
|
func Init() bool {
|
||||||
runtime.Notify("contract call", operation, args)
|
ctx := storage.GetContext()
|
||||||
switch operation {
|
h := runtime.GetExecutingScriptHash()
|
||||||
case "Put":
|
amount := totalSupply
|
||||||
ctx := storage.GetContext()
|
storage.Put(ctx, h, amount)
|
||||||
storage.Put(ctx, args[0].([]byte), args[1].([]byte))
|
runtime.Notify("transfer", []byte{}, h, amount)
|
||||||
return true
|
return true
|
||||||
case "totalSupply":
|
}
|
||||||
return totalSupply
|
|
||||||
case "decimals":
|
func Transfer(from, to []byte, amount int) bool {
|
||||||
return decimals
|
ctx := storage.GetContext()
|
||||||
case "name":
|
if len(from) != 20 {
|
||||||
return "Rubl"
|
runtime.Log("invalid 'from' address")
|
||||||
case "symbol":
|
return false
|
||||||
return "RUB"
|
}
|
||||||
case "balanceOf":
|
if len(to) != 20 {
|
||||||
ctx := storage.GetContext()
|
runtime.Log("invalid 'to' address")
|
||||||
addr := args[0].([]byte)
|
return false
|
||||||
if len(addr) != 20 {
|
}
|
||||||
runtime.Log("invalid address")
|
if amount < 0 {
|
||||||
return false
|
runtime.Log("invalid amount")
|
||||||
}
|
return false
|
||||||
var amount int
|
}
|
||||||
val := storage.Get(ctx, addr)
|
|
||||||
if val != nil {
|
var fromBalance int
|
||||||
amount = val.(int)
|
val := storage.Get(ctx, from)
|
||||||
}
|
if val != nil {
|
||||||
runtime.Notify("balanceOf", addr, amount)
|
fromBalance = val.(int)
|
||||||
return amount
|
}
|
||||||
case "transfer":
|
if fromBalance < amount {
|
||||||
ctx := storage.GetContext()
|
runtime.Log("insufficient funds")
|
||||||
from := args[0].([]byte)
|
return false
|
||||||
if len(from) != 20 {
|
}
|
||||||
runtime.Log("invalid 'from' address")
|
fromBalance -= amount
|
||||||
return false
|
storage.Put(ctx, from, fromBalance)
|
||||||
}
|
|
||||||
to := args[1].([]byte)
|
var toBalance int
|
||||||
if len(to) != 20 {
|
val = storage.Get(ctx, to)
|
||||||
runtime.Log("invalid 'to' address")
|
if val != nil {
|
||||||
return false
|
toBalance = val.(int)
|
||||||
}
|
}
|
||||||
amount := args[2].(int)
|
toBalance += amount
|
||||||
if amount < 0 {
|
storage.Put(ctx, to, toBalance)
|
||||||
runtime.Log("invalid amount")
|
|
||||||
return false
|
runtime.Notify("transfer", from, to, amount)
|
||||||
}
|
|
||||||
|
return true
|
||||||
var fromBalance int
|
}
|
||||||
val := storage.Get(ctx, from)
|
|
||||||
if val != nil {
|
func BalanceOf(addr []byte) int {
|
||||||
fromBalance = val.(int)
|
ctx := storage.GetContext()
|
||||||
}
|
if len(addr) != 20 {
|
||||||
if fromBalance < amount {
|
runtime.Log("invalid address")
|
||||||
runtime.Log("insufficient funds")
|
return 0
|
||||||
return false
|
}
|
||||||
}
|
var amount int
|
||||||
fromBalance -= amount
|
val := storage.Get(ctx, addr)
|
||||||
storage.Put(ctx, from, fromBalance)
|
if val != nil {
|
||||||
|
amount = val.(int)
|
||||||
var toBalance int
|
}
|
||||||
val = storage.Get(ctx, to)
|
runtime.Notify("balanceOf", addr, amount)
|
||||||
if val != nil {
|
return amount
|
||||||
toBalance = val.(int)
|
}
|
||||||
}
|
|
||||||
toBalance += amount
|
func Name() string {
|
||||||
storage.Put(ctx, to, toBalance)
|
return "Rubl"
|
||||||
|
}
|
||||||
runtime.Notify("transfer", from, to, amount)
|
|
||||||
|
func Symbol() string {
|
||||||
return true
|
return "RUB"
|
||||||
case "init":
|
}
|
||||||
ctx := storage.GetContext()
|
|
||||||
h := runtime.GetExecutingScriptHash()
|
func Decimals() int {
|
||||||
amount := totalSupply
|
return decimals
|
||||||
storage.Put(ctx, h, amount)
|
}
|
||||||
runtime.Notify("transfer", []byte{}, h, amount)
|
|
||||||
return true
|
func TotalSupply() int {
|
||||||
default:
|
return totalSupply
|
||||||
panic("invalid operation")
|
}
|
||||||
}
|
|
||||||
|
func PutValue(key []byte, value []byte) bool {
|
||||||
|
ctx := storage.GetContext()
|
||||||
|
storage.Put(ctx, key, value)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
Binary file not shown.
|
@ -11,12 +11,14 @@ import (
|
||||||
// MaxManifestSize is a max length for a valid contract manifest.
|
// MaxManifestSize is a max length for a valid contract manifest.
|
||||||
const MaxManifestSize = 2048
|
const MaxManifestSize = 2048
|
||||||
|
|
||||||
|
// MethodInit is a name for default initialization method.
|
||||||
|
const MethodInit = "_initialize"
|
||||||
|
|
||||||
// ABI represents a contract application binary interface.
|
// ABI represents a contract application binary interface.
|
||||||
type ABI struct {
|
type ABI struct {
|
||||||
Hash util.Uint160 `json:"hash"`
|
Hash util.Uint160 `json:"hash"`
|
||||||
EntryPoint Method `json:"entryPoint"`
|
Methods []Method `json:"methods"`
|
||||||
Methods []Method `json:"methods"`
|
Events []Event `json:"events"`
|
||||||
Events []Event `json:"events"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manifest represens contract metadata.
|
// Manifest represens contract metadata.
|
||||||
|
@ -65,11 +67,20 @@ func NewManifest(h util.Uint160) *Manifest {
|
||||||
// DefaultManifest returns default contract manifest.
|
// DefaultManifest returns default contract manifest.
|
||||||
func DefaultManifest(h util.Uint160) *Manifest {
|
func DefaultManifest(h util.Uint160) *Manifest {
|
||||||
m := NewManifest(h)
|
m := NewManifest(h)
|
||||||
m.ABI.EntryPoint = *DefaultEntryPoint()
|
|
||||||
m.Permissions = []Permission{*NewPermission(PermissionWildcard)}
|
m.Permissions = []Permission{*NewPermission(PermissionWildcard)}
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMethod returns methods with the specified name.
|
||||||
|
func (a *ABI) GetMethod(name string) *Method {
|
||||||
|
for i := range a.Methods {
|
||||||
|
if a.Methods[i].Name == name {
|
||||||
|
return &a.Methods[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// CanCall returns true is current contract is allowed to call
|
// CanCall returns true is current contract is allowed to call
|
||||||
// method of another contract.
|
// method of another contract.
|
||||||
func (m *Manifest) CanCall(toCall *Manifest, method string) bool {
|
func (m *Manifest) CanCall(toCall *Manifest, method string) bool {
|
||||||
|
|
|
@ -13,39 +13,39 @@ import (
|
||||||
// https://github.com/neo-project/neo/blob/master/tests/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs#L10
|
// https://github.com/neo-project/neo/blob/master/tests/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs#L10
|
||||||
func TestManifest_MarshalJSON(t *testing.T) {
|
func TestManifest_MarshalJSON(t *testing.T) {
|
||||||
t.Run("default", func(t *testing.T) {
|
t.Run("default", func(t *testing.T) {
|
||||||
s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returntype":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":null}`
|
s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":null}`
|
||||||
m := testUnmarshalMarshalManifest(t, s)
|
m := testUnmarshalMarshalManifest(t, s)
|
||||||
require.Equal(t, DefaultManifest(util.Uint160{}), m)
|
require.Equal(t, DefaultManifest(util.Uint160{}), m)
|
||||||
})
|
})
|
||||||
|
|
||||||
// this vector is missing from original repo
|
// this vector is missing from original repo
|
||||||
t.Run("features", func(t *testing.T) {
|
t.Run("features", func(t *testing.T) {
|
||||||
s := `{"groups":[],"features":{"storage":true,"payable":true},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returntype":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":null}`
|
s := `{"groups":[],"features":{"storage":true,"payable":true},"abi":{"hash":"0x0000000000000000000000000000000000000000","methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":null}`
|
||||||
testUnmarshalMarshalManifest(t, s)
|
testUnmarshalMarshalManifest(t, s)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("permissions", func(t *testing.T) {
|
t.Run("permissions", func(t *testing.T) {
|
||||||
s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returntype":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"0x0000000000000000000000000000000000000000","methods":["method1","method2"]}],"trusts":[],"safemethods":[],"extra":null}`
|
s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","methods":[],"events":[]},"permissions":[{"contract":"0x0000000000000000000000000000000000000000","methods":["method1","method2"]}],"trusts":[],"safemethods":[],"extra":null}`
|
||||||
testUnmarshalMarshalManifest(t, s)
|
testUnmarshalMarshalManifest(t, s)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("safe methods", func(t *testing.T) {
|
t.Run("safe methods", func(t *testing.T) {
|
||||||
s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returntype":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":["balanceOf"],"extra":null}`
|
s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":["balanceOf"],"extra":null}`
|
||||||
testUnmarshalMarshalManifest(t, s)
|
testUnmarshalMarshalManifest(t, s)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("trust", func(t *testing.T) {
|
t.Run("trust", func(t *testing.T) {
|
||||||
s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returntype":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":["0x0000000000000000000000000000000000000001"],"safemethods":[],"extra":null}`
|
s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":["0x0000000000000000000000000000000000000001"],"safemethods":[],"extra":null}`
|
||||||
testUnmarshalMarshalManifest(t, s)
|
testUnmarshalMarshalManifest(t, s)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("groups", func(t *testing.T) {
|
t.Run("groups", func(t *testing.T) {
|
||||||
s := `{"groups":[{"pubkey":"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c","signature":"QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQ=="}],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returntype":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":null}`
|
s := `{"groups":[{"pubkey":"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c","signature":"QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQ=="}],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":null}`
|
||||||
testUnmarshalMarshalManifest(t, s)
|
testUnmarshalMarshalManifest(t, s)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("extra", func(t *testing.T) {
|
t.Run("extra", func(t *testing.T) {
|
||||||
s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returntype":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":{"key":"value"}}`
|
s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":{"key":"value"}}`
|
||||||
testUnmarshalMarshalManifest(t, s)
|
testUnmarshalMarshalManifest(t, s)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ type groupAux struct {
|
||||||
// Method represents method's metadata.
|
// Method represents method's metadata.
|
||||||
type Method struct {
|
type Method struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Offset int `json:"offset"`
|
||||||
Parameters []Parameter `json:"parameters"`
|
Parameters []Parameter `json:"parameters"`
|
||||||
ReturnType smartcontract.ParamType `json:"returntype"`
|
ReturnType smartcontract.ParamType `json:"returntype"`
|
||||||
}
|
}
|
||||||
|
@ -50,18 +51,6 @@ func NewParameter(name string, typ smartcontract.ParamType) Parameter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultEntryPoint represents default entrypoint to a contract.
|
|
||||||
func DefaultEntryPoint() *Method {
|
|
||||||
return &Method{
|
|
||||||
Name: "Main",
|
|
||||||
Parameters: []Parameter{
|
|
||||||
NewParameter("operation", smartcontract.StringType),
|
|
||||||
NewParameter("args", smartcontract.ArrayType),
|
|
||||||
},
|
|
||||||
ReturnType: smartcontract.AnyType,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsValid checks whether group's signature corresponds to the given hash.
|
// IsValid checks whether group's signature corresponds to the given hash.
|
||||||
func (g *Group) IsValid(h util.Uint160) bool {
|
func (g *Group) IsValid(h util.Uint160) bool {
|
||||||
return g.PublicKey.Verify(g.Signature, hash.Sha256(h.BytesBE()).BytesBE())
|
return g.PublicKey.Verify(g.Signature, hash.Sha256(h.BytesBE()).BytesBE())
|
||||||
|
|
|
@ -228,7 +228,7 @@ func compareStacks(t *testing.T, expected []vmUTStackItem, actual *Stack) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func compareSlots(t *testing.T, expected []vmUTStackItem, actual *Slot) {
|
func compareSlots(t *testing.T, expected []vmUTStackItem, actual *Slot) {
|
||||||
if actual == nil && len(expected) == 0 {
|
if actual.storage == nil && len(expected) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
require.NotNil(t, actual)
|
require.NotNil(t, actual)
|
||||||
|
|
|
@ -10,16 +10,25 @@ type Slot struct {
|
||||||
refs *refCounter
|
refs *refCounter
|
||||||
}
|
}
|
||||||
|
|
||||||
// newSlot returns new slot of n items.
|
// newSlot returns new slot with the provided reference counter.
|
||||||
func newSlot(n int, refs *refCounter) *Slot {
|
func newSlot(refs *refCounter) *Slot {
|
||||||
return &Slot{
|
return &Slot{
|
||||||
storage: make([]stackitem.Item, n),
|
refs: refs,
|
||||||
refs: refs,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// init sets static slot size to n. It is intended to be used only by INITSSLOT.
|
||||||
|
func (s *Slot) init(n int) {
|
||||||
|
if s.storage != nil {
|
||||||
|
panic("already initialized")
|
||||||
|
}
|
||||||
|
s.storage = make([]stackitem.Item, n)
|
||||||
|
}
|
||||||
|
|
||||||
func (v *VM) newSlot(n int) *Slot {
|
func (v *VM) newSlot(n int) *Slot {
|
||||||
return newSlot(n, v.refs)
|
s := newSlot(v.refs)
|
||||||
|
s.init(n)
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set sets i-th storage slot.
|
// Set sets i-th storage slot.
|
||||||
|
@ -43,12 +52,17 @@ func (s *Slot) Get(i int) stackitem.Item {
|
||||||
return stackitem.Null{}
|
return stackitem.Null{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size returns slot size.
|
|
||||||
func (s *Slot) Size() int { return len(s.storage) }
|
|
||||||
|
|
||||||
// Clear removes all slot variables from reference counter.
|
// Clear removes all slot variables from reference counter.
|
||||||
func (s *Slot) Clear() {
|
func (s *Slot) Clear() {
|
||||||
for _, item := range s.storage {
|
for _, item := range s.storage {
|
||||||
s.refs.Remove(item)
|
s.refs.Remove(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Size returns slot size.
|
||||||
|
func (s *Slot) Size() int {
|
||||||
|
if s.storage == nil {
|
||||||
|
panic("not initialized")
|
||||||
|
}
|
||||||
|
return len(s.storage)
|
||||||
|
}
|
||||||
|
|
|
@ -9,8 +9,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSlot_Get(t *testing.T) {
|
func TestSlot_Get(t *testing.T) {
|
||||||
s := newSlot(3, newRefCounter())
|
s := newSlot(newRefCounter())
|
||||||
require.NotNil(t, s)
|
require.NotNil(t, s)
|
||||||
|
require.Panics(t, func() { s.Size() })
|
||||||
|
|
||||||
|
s.init(3)
|
||||||
require.Equal(t, 3, s.Size())
|
require.Equal(t, 3, s.Size())
|
||||||
|
|
||||||
// Null is the default
|
// Null is the default
|
||||||
|
|
47
pkg/vm/vm.go
47
pkg/vm/vm.go
|
@ -279,9 +279,11 @@ func (v *VM) LoadScript(b []byte) {
|
||||||
// LoadScriptWithFlags loads script and sets call flag to f.
|
// LoadScriptWithFlags loads script and sets call flag to f.
|
||||||
func (v *VM) LoadScriptWithFlags(b []byte, f smartcontract.CallFlag) {
|
func (v *VM) LoadScriptWithFlags(b []byte, f smartcontract.CallFlag) {
|
||||||
ctx := NewContext(b)
|
ctx := NewContext(b)
|
||||||
|
v.estack = v.newItemStack("estack")
|
||||||
ctx.estack = v.estack
|
ctx.estack = v.estack
|
||||||
ctx.tryStack = NewStack("exception")
|
ctx.tryStack = NewStack("exception")
|
||||||
ctx.callFlag = f
|
ctx.callFlag = f
|
||||||
|
ctx.static = newSlot(v.refs)
|
||||||
v.istack.PushVal(ctx)
|
v.istack.PushVal(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -568,13 +570,10 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
v.estack.PushVal(result)
|
v.estack.PushVal(result)
|
||||||
|
|
||||||
case opcode.INITSSLOT:
|
case opcode.INITSSLOT:
|
||||||
if ctx.static != nil {
|
|
||||||
panic("already initialized")
|
|
||||||
}
|
|
||||||
if parameter[0] == 0 {
|
if parameter[0] == 0 {
|
||||||
panic("zero argument")
|
panic("zero argument")
|
||||||
}
|
}
|
||||||
ctx.static = v.newSlot(int(parameter[0]))
|
ctx.static.init(int(parameter[0]))
|
||||||
|
|
||||||
case opcode.INITSLOT:
|
case opcode.INITSLOT:
|
||||||
if ctx.local != nil || ctx.arguments != nil {
|
if ctx.local != nil || ctx.arguments != nil {
|
||||||
|
@ -1232,18 +1231,14 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
}
|
}
|
||||||
|
|
||||||
if cond {
|
if cond {
|
||||||
v.jump(ctx, offset)
|
v.Jump(ctx, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
case opcode.CALL, opcode.CALLL:
|
case opcode.CALL, opcode.CALLL:
|
||||||
v.checkInvocationStackSize()
|
v.checkInvocationStackSize()
|
||||||
newCtx := ctx.Copy()
|
// Note: jump offset must be calculated regarding to new context,
|
||||||
newCtx.local = nil
|
// but it is cloned and thus has the same script and instruction pointer.
|
||||||
newCtx.arguments = nil
|
v.Call(ctx, v.getJumpOffset(ctx, parameter))
|
||||||
v.istack.PushVal(newCtx)
|
|
||||||
|
|
||||||
offset := v.getJumpOffset(newCtx, parameter)
|
|
||||||
v.jump(newCtx, offset)
|
|
||||||
|
|
||||||
case opcode.CALLA:
|
case opcode.CALLA:
|
||||||
ptr := v.estack.Pop().Item().(*stackitem.Pointer)
|
ptr := v.estack.Pop().Item().(*stackitem.Pointer)
|
||||||
|
@ -1251,11 +1246,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
panic("invalid script in pointer")
|
panic("invalid script in pointer")
|
||||||
}
|
}
|
||||||
|
|
||||||
newCtx := ctx.Copy()
|
v.Call(ctx, ptr.Position())
|
||||||
newCtx.local = nil
|
|
||||||
newCtx.arguments = nil
|
|
||||||
v.istack.PushVal(newCtx)
|
|
||||||
v.jump(newCtx, ptr.Position())
|
|
||||||
|
|
||||||
case opcode.SYSCALL:
|
case opcode.SYSCALL:
|
||||||
interopID := GetInteropID(parameter)
|
interopID := GetInteropID(parameter)
|
||||||
|
@ -1404,7 +1395,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
} else {
|
} else {
|
||||||
ctx.tryStack.Pop()
|
ctx.tryStack.Pop()
|
||||||
}
|
}
|
||||||
v.jump(ctx, eOffset)
|
v.Jump(ctx, eOffset)
|
||||||
|
|
||||||
case opcode.ENDFINALLY:
|
case opcode.ENDFINALLY:
|
||||||
if v.uncaughtException != nil {
|
if v.uncaughtException != nil {
|
||||||
|
@ -1412,7 +1403,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
eCtx := ctx.tryStack.Pop().Value().(*exceptionHandlingContext)
|
eCtx := ctx.tryStack.Pop().Value().(*exceptionHandlingContext)
|
||||||
v.jump(ctx, eCtx.EndOffset)
|
v.Jump(ctx, eCtx.EndOffset)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("unknown opcode %s", op.String()))
|
panic(fmt.Sprintf("unknown opcode %s", op.String()))
|
||||||
|
@ -1468,11 +1459,21 @@ func (v *VM) throw(item stackitem.Item) {
|
||||||
v.handleException()
|
v.handleException()
|
||||||
}
|
}
|
||||||
|
|
||||||
// jump performs jump to the offset.
|
// Jump performs jump to the offset.
|
||||||
func (v *VM) jump(ctx *Context, offset int) {
|
func (v *VM) Jump(ctx *Context, offset int) {
|
||||||
ctx.nextip = offset
|
ctx.nextip = offset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call calls method by offset. It is similar to Jump but also
|
||||||
|
// pushes new context to the invocation state
|
||||||
|
func (v *VM) Call(ctx *Context, offset int) {
|
||||||
|
newCtx := ctx.Copy()
|
||||||
|
newCtx.local = nil
|
||||||
|
newCtx.arguments = nil
|
||||||
|
v.istack.PushVal(newCtx)
|
||||||
|
v.Jump(newCtx, offset)
|
||||||
|
}
|
||||||
|
|
||||||
// getJumpOffset returns instruction number in a current context
|
// getJumpOffset returns instruction number in a current context
|
||||||
// to a which JMP should be performed.
|
// to a which JMP should be performed.
|
||||||
// parameter should have length either 1 or 4 and
|
// parameter should have length either 1 or 4 and
|
||||||
|
@ -1526,10 +1527,10 @@ func (v *VM) handleException() {
|
||||||
ectx.State = eCatch
|
ectx.State = eCatch
|
||||||
v.estack.PushVal(v.uncaughtException)
|
v.estack.PushVal(v.uncaughtException)
|
||||||
v.uncaughtException = nil
|
v.uncaughtException = nil
|
||||||
v.jump(ictx, ectx.CatchOffset)
|
v.Jump(ictx, ectx.CatchOffset)
|
||||||
} else {
|
} else {
|
||||||
ectx.State = eFinally
|
ectx.State = eFinally
|
||||||
v.jump(ictx, ectx.FinallyOffset)
|
v.Jump(ictx, ectx.FinallyOffset)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -2596,6 +2596,14 @@ func TestSLOTOpcodes(t *testing.T) {
|
||||||
t.Run("Local", getTestFuncForVM(makeProgram(opcode.INITSLOT, 8, 0, opcode.STLOC, 7, opcode.LDLOC, 7), 42, 42))
|
t.Run("Local", getTestFuncForVM(makeProgram(opcode.INITSLOT, 8, 0, opcode.STLOC, 7, opcode.LDLOC, 7), 42, 42))
|
||||||
t.Run("Argument", getTestFuncForVM(makeProgram(opcode.INITSLOT, 0, 2, opcode.STARG, 1, opcode.LDARG, 1), 42, 42, 1, 2))
|
t.Run("Argument", getTestFuncForVM(makeProgram(opcode.INITSLOT, 0, 2, opcode.STARG, 1, opcode.LDARG, 1), 42, 42, 1, 2))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("InitStaticSlotInMethod", func(t *testing.T) {
|
||||||
|
prog := makeProgram(
|
||||||
|
opcode.CALL, 4, opcode.LDSFLD0, opcode.RET,
|
||||||
|
opcode.INITSSLOT, 1, opcode.PUSH12, opcode.STSFLD0, opcode.RET,
|
||||||
|
)
|
||||||
|
runWithArgs(t, prog, 12)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeProgram(opcodes ...opcode.Opcode) []byte {
|
func makeProgram(opcodes ...opcode.Opcode) []byte {
|
||||||
|
|
Loading…
Reference in a new issue