Merge pull request #1452 from nspcc-dev/contract/deploy

Support `_deploy` method
This commit is contained in:
Roman Khimov 2020-10-06 19:54:14 +03:00 committed by GitHub
commit d6a1a22afa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 614 additions and 225 deletions

106
cli/contract_test.go Normal file
View file

@ -0,0 +1,106 @@
package main
import (
"encoding/hex"
"encoding/json"
"io/ioutil"
"os"
"path"
"strings"
"testing"
"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/stretchr/testify/require"
)
func TestComlileAndInvokeFunction(t *testing.T) {
e := newExecutor(t, true)
defer e.Close(t)
// For proper nef generation.
config.Version = "0.90.0-test"
tmpDir := os.TempDir()
nefName := path.Join(tmpDir, "deploy.nef")
manifestName := path.Join(tmpDir, "deploy.manifest.json")
e.Run(t, "neo-go", "contract", "compile",
"--in", "testdata/deploy/main.go", // compile single file
"--config", "testdata/deploy/neo-go.yml",
"--out", nefName, "--manifest", manifestName)
defer func() {
os.Remove(nefName)
os.Remove(manifestName)
}()
e.In.WriteString("one\r")
e.Run(t, "neo-go", "contract", "deploy",
"--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr,
"--wallet", validatorWallet, "--address", validatorAddr,
"--in", nefName, "--manifest", manifestName)
line, err := e.Out.ReadString('\n')
require.NoError(t, err)
line = strings.TrimSpace(strings.TrimPrefix(line, "Contract: "))
h, err := util.Uint160DecodeStringLE(line)
require.NoError(t, err)
e.checkTxPersisted(t)
e.In.WriteString("one\r")
e.Run(t, "neo-go", "contract", "testinvokefunction",
"--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr,
h.StringLE(), "getValue")
res := new(result.Invoke)
require.NoError(t, json.Unmarshal(e.Out.Bytes(), res))
require.Equal(t, vm.HaltState.String(), res.State)
require.Len(t, res.Stack, 1)
require.Equal(t, []byte("on create|sub create"), res.Stack[0].Value())
t.Run("Update", func(t *testing.T) {
nefName := path.Join(tmpDir, "updated.nef")
manifestName := path.Join(tmpDir, "updated.manifest.json")
e.Run(t, "neo-go", "contract", "compile",
"--config", "testdata/deploy/neo-go.yml",
"--in", "testdata/deploy/", // compile all files in dir
"--out", nefName, "--manifest", manifestName)
defer func() {
os.Remove(nefName)
os.Remove(manifestName)
}()
rawNef, err := ioutil.ReadFile(nefName)
require.NoError(t, err)
realNef, err := nef.FileFromBytes(rawNef)
require.NoError(t, err)
rawManifest, err := ioutil.ReadFile(manifestName)
require.NoError(t, err)
e.In.WriteString("one\r")
e.Run(t, "neo-go", "contract", "invokefunction",
"--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr,
"--wallet", validatorWallet, "--address", validatorAddr,
h.StringLE(), "update",
"bytes:"+hex.EncodeToString(realNef.Script),
"bytes:"+hex.EncodeToString(rawManifest),
)
e.checkTxPersisted(t, "Sent invocation transaction ")
e.In.WriteString("one\r")
e.Run(t, "neo-go", "contract", "testinvokefunction",
"--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr,
hash.Hash160(realNef.Script).StringLE(), "getValue")
res := new(result.Invoke)
require.NoError(t, json.Unmarshal(e.Out.Bytes(), res))
require.Equal(t, vm.HaltState.String(), res.State)
require.Len(t, res.Stack, 1)
require.Equal(t, []byte("on update|sub update"), res.Stack[0].Value())
})
}

View file

@ -185,11 +185,14 @@ func (e *executor) run(args ...string) error {
return e.CLI.Run(args)
}
func (e *executor) checkTxPersisted(t *testing.T) (*transaction.Transaction, uint32) {
func (e *executor) checkTxPersisted(t *testing.T, prefix ...string) (*transaction.Transaction, uint32) {
line, err := e.Out.ReadString('\n')
require.NoError(t, err)
line = strings.TrimSpace(line)
if len(prefix) > 0 {
line = strings.TrimPrefix(line, prefix[0])
}
h, err := util.Uint256DecodeStringLE(line)
require.NoError(t, err, "can't decode tx hash: %s", line)

31
cli/testdata/deploy/main.go vendored Normal file
View file

@ -0,0 +1,31 @@
package deploy
import (
"github.com/nspcc-dev/neo-go/cli/testdata/deploy/sub"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
)
var key = "key"
func _deploy(isUpdate bool) {
ctx := storage.GetContext()
value := "on create"
if isUpdate {
value = "on update"
}
storage.Put(ctx, key, value)
}
// Update updates contract with the new one.
func Update(script, manifest []byte) {
contract.Update(script, manifest)
}
// GetValue returns stored value.
func GetValue() string {
ctx := storage.GetReadOnlyContext()
val1 := storage.Get(ctx, key)
val2 := storage.Get(ctx, sub.Key)
return val1.(string) + "|" + val2.(string)
}

1
cli/testdata/deploy/neo-go.yml vendored Normal file
View file

@ -0,0 +1 @@
hasstorage: true

14
cli/testdata/deploy/sub/put.go vendored Normal file
View file

@ -0,0 +1,14 @@
package sub
import "github.com/nspcc-dev/neo-go/pkg/interop/storage"
var Key = "sub"
func _deploy(isUpdate bool) {
ctx := storage.GetContext()
value := "sub create"
if isUpdate {
value = "sub update"
}
storage.Put(ctx, Key, value)
}

6
cli/testdata/deploy/updated.go vendored Normal file
View file

@ -0,0 +1,6 @@
package deploy
// NewMethod in updated contract.
func NewMethod() int {
return 42
}

View file

@ -102,7 +102,7 @@ func handleCandidate(ctx *cli.Context, method string) error {
gas := flags.Fixed8FromContext(ctx, "gas")
w := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(w.BinWriter, client.NeoContractHash, method, acc.PrivateKey().PublicKey().Bytes())
emit.Opcode(w.BinWriter, opcode.ASSERT)
emit.Opcodes(w.BinWriter, opcode.ASSERT)
tx, err := c.CreateTxFromScript(w.Bytes(), acc, -1, int64(gas), transaction.Signer{
Account: acc.Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
@ -160,7 +160,7 @@ func handleVote(ctx *cli.Context) error {
gas := flags.Fixed8FromContext(ctx, "gas")
w := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(w.BinWriter, client.NeoContractHash, "vote", addr.BytesBE(), pubArg)
emit.Opcode(w.BinWriter, opcode.ASSERT)
emit.Opcodes(w.BinWriter, opcode.ASSERT)
tx, err := c.CreateTxFromScript(w.Bytes(), acc, -1, int64(gas), transaction.Signer{
Account: acc.Contract.ScriptHash(),

View file

@ -36,6 +36,10 @@ functionality as Neo .net Framework library.
Compiler provides some helpful builtins in `util` and `convert` packages.
Refer to them for detailed documentation.
`_deploy()` function has a special meaning and is executed when contract is deployed.
It should return no value and accept single bool argument which will be true on contract update.
`_deploy()` functions are called for every imported package in the same order as `init()`.
## Quick start
### Compiling

View file

@ -37,20 +37,31 @@ func (c *codegen) getIdentName(pkg string, name string) string {
}
// traverseGlobals visits and initializes global variables.
// and returns number of variables initialized and
// true if any init functions were encountered.
func (c *codegen) traverseGlobals() (int, bool) {
// and returns number of variables initialized.
// Second return value is -1 if no `init()` functions were encountered
// and number of maximum amount of locals in any of init functions otherwise.
// Same for `_deploy()` functions (see docs/compiler.md).
func (c *codegen) traverseGlobals() (int, int, int) {
var hasDefer bool
var n int
var hasInit bool
initLocals := -1
deployLocals := -1
c.ForEachFile(func(f *ast.File, _ *types.Package) {
n += countGlobals(f)
if !hasInit || !hasDefer {
if initLocals == -1 || deployLocals == -1 || !hasDefer {
ast.Inspect(f, func(node ast.Node) bool {
switch n := node.(type) {
case *ast.FuncDecl:
if isInitFunc(n) {
hasInit = true
c, _ := countLocals(n)
if c > initLocals {
initLocals = c
}
} else if isDeployFunc(n) {
c, _ := countLocals(n)
if c > deployLocals {
deployLocals = c
}
}
return !hasDefer
case *ast.DeferStmt:
@ -64,14 +75,18 @@ func (c *codegen) traverseGlobals() (int, bool) {
if hasDefer {
n++
}
if n != 0 || hasInit {
if n != 0 || initLocals > -1 {
if n > 255 {
c.prog.BinWriter.Err = errors.New("too many global variables")
return 0, hasInit
return 0, initLocals, deployLocals
}
if n != 0 {
emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)})
}
if initLocals > 0 {
emit.Instruction(c.prog.BinWriter, opcode.INITSLOT, []byte{byte(initLocals), 0})
}
seenBefore := false
c.ForEachPackage(func(pkg *loader.PackageInfo) {
if n > 0 {
for _, f := range pkg.Files {
@ -79,10 +94,10 @@ func (c *codegen) traverseGlobals() (int, bool) {
c.convertGlobals(f, pkg.Pkg)
}
}
if hasInit {
if initLocals > -1 {
for _, f := range pkg.Files {
c.fillImportMap(f, pkg.Pkg)
c.convertInitFuncs(f, pkg.Pkg)
seenBefore = c.convertInitFuncs(f, pkg.Pkg, seenBefore) || seenBefore
}
}
// because we reuse `convertFuncDecl` for init funcs,
@ -96,7 +111,7 @@ func (c *codegen) traverseGlobals() (int, bool) {
c.globals["<exception>"] = c.exceptionIndex
}
}
return n, hasInit
return n, initLocals, deployLocals
}
// countGlobals counts the global variables in the program to add

View file

@ -61,6 +61,8 @@ type codegen struct {
// initEndOffset specifies the end of the initialization method.
initEndOffset int
// deployEndOffset specifies the end of the deployment method.
deployEndOffset int
// importMap contains mapping from package aliases to full package names for the current file.
importMap map[string]string
@ -176,13 +178,12 @@ func (c *codegen) emitLoadConst(t types.TypeAndValue) {
func (c *codegen) emitLoadField(i int) {
emit.Int(c.prog.BinWriter, int64(i))
emit.Opcode(c.prog.BinWriter, opcode.PICKITEM)
emit.Opcodes(c.prog.BinWriter, opcode.PICKITEM)
}
func (c *codegen) emitStoreStructField(i int) {
emit.Int(c.prog.BinWriter, int64(i))
emit.Opcode(c.prog.BinWriter, opcode.ROT)
emit.Opcode(c.prog.BinWriter, opcode.SETITEM)
emit.Opcodes(c.prog.BinWriter, opcode.ROT, opcode.SETITEM)
}
// getVarIndex returns variable type and position in corresponding slot,
@ -226,7 +227,7 @@ func (c *codegen) emitLoadVar(pkg string, name string) {
func (c *codegen) emitLoadByIndex(t varType, i int) {
base, _ := getBaseOpcode(t)
if i < 7 {
emit.Opcode(c.prog.BinWriter, base+opcode.Opcode(i))
emit.Opcodes(c.prog.BinWriter, base+opcode.Opcode(i))
} else {
emit.Instruction(c.prog.BinWriter, base+7, []byte{byte(i)})
}
@ -235,7 +236,7 @@ func (c *codegen) emitLoadByIndex(t varType, i int) {
// emitStoreVar stores top value from the evaluation stack in the specified variable.
func (c *codegen) emitStoreVar(pkg string, name string) {
if name == "_" {
emit.Opcode(c.prog.BinWriter, opcode.DROP)
emit.Opcodes(c.prog.BinWriter, opcode.DROP)
return
}
t, i := c.getVarIndex(pkg, name)
@ -246,7 +247,7 @@ func (c *codegen) emitStoreVar(pkg string, name string) {
func (c *codegen) emitStoreByIndex(t varType, i int) {
_, base := getBaseOpcode(t)
if i < 7 {
emit.Opcode(c.prog.BinWriter, base+opcode.Opcode(i))
emit.Opcodes(c.prog.BinWriter, base+opcode.Opcode(i))
} else {
emit.Instruction(c.prog.BinWriter, base+7, []byte{byte(i)})
}
@ -264,20 +265,20 @@ func (c *codegen) emitDefault(t types.Type) {
case info&types.IsBoolean != 0:
emit.Bool(c.prog.BinWriter, false)
default:
emit.Opcode(c.prog.BinWriter, opcode.PUSHNULL)
emit.Opcodes(c.prog.BinWriter, opcode.PUSHNULL)
}
case *types.Struct:
num := t.NumFields()
emit.Int(c.prog.BinWriter, int64(num))
emit.Opcode(c.prog.BinWriter, opcode.NEWSTRUCT)
emit.Opcodes(c.prog.BinWriter, opcode.NEWSTRUCT)
for i := 0; i < num; i++ {
emit.Opcode(c.prog.BinWriter, opcode.DUP)
emit.Opcodes(c.prog.BinWriter, opcode.DUP)
emit.Int(c.prog.BinWriter, int64(i))
c.emitDefault(t.Field(i).Type())
emit.Opcode(c.prog.BinWriter, opcode.SETITEM)
emit.Opcodes(c.prog.BinWriter, opcode.SETITEM)
}
default:
emit.Opcode(c.prog.BinWriter, opcode.PUSHNULL)
emit.Opcodes(c.prog.BinWriter, opcode.PUSHNULL)
}
}
@ -302,11 +303,23 @@ func isInitFunc(decl *ast.FuncDecl) bool {
decl.Type.Results.NumFields() == 0
}
func (c *codegen) convertInitFuncs(f *ast.File, pkg *types.Package) {
func (c *codegen) clearSlots(n int) {
for i := 0; i < n; i++ {
emit.Opcodes(c.prog.BinWriter, opcode.PUSHNULL)
c.emitStoreByIndex(varLocal, i)
}
}
func (c *codegen) convertInitFuncs(f *ast.File, pkg *types.Package, seenBefore bool) bool {
ast.Inspect(f, func(node ast.Node) bool {
switch n := node.(type) {
case *ast.FuncDecl:
if isInitFunc(n) {
if seenBefore {
cnt, _ := countLocals(n)
c.clearSlots(cnt)
seenBefore = true
}
c.convertFuncDecl(f, n, pkg)
}
case *ast.GenDecl:
@ -314,6 +327,39 @@ func (c *codegen) convertInitFuncs(f *ast.File, pkg *types.Package) {
}
return true
})
return seenBefore
}
func isDeployFunc(decl *ast.FuncDecl) bool {
if decl.Name.Name != "_deploy" || decl.Recv != nil ||
decl.Type.Params.NumFields() != 1 ||
decl.Type.Results.NumFields() != 0 {
return false
}
typ, ok := decl.Type.Params.List[0].Type.(*ast.Ident)
return ok && typ.Name == "bool"
}
func (c *codegen) convertDeployFuncs() {
seenBefore := false
c.ForEachFile(func(f *ast.File, pkg *types.Package) {
ast.Inspect(f, func(node ast.Node) bool {
switch n := node.(type) {
case *ast.FuncDecl:
if isDeployFunc(n) {
if seenBefore {
cnt, _ := countLocals(n)
c.clearSlots(cnt)
}
c.convertFuncDecl(f, n, pkg)
seenBefore = true
}
case *ast.GenDecl:
return false
}
return true
})
})
}
func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl, pkg *types.Package) {
@ -322,7 +368,8 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl, pkg *types.
ok, isLambda bool
)
isInit := isInitFunc(decl)
if isInit {
isDeploy := isDeployFunc(decl)
if isInit || isDeploy {
f = c.newFuncScope(decl, c.newLabel())
} else {
f, ok = c.funcs[c.getFuncNameFromDecl("", decl)]
@ -346,16 +393,18 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl, pkg *types.
// All globals copied into the scope of the function need to be added
// to the stack size of the function.
sizeLoc := f.countLocals()
if sizeLoc > 255 {
c.prog.Err = errors.New("maximum of 255 local variables is allowed")
}
sizeArg := f.countArgs()
if sizeArg > 255 {
c.prog.Err = errors.New("maximum of 255 local variables is allowed")
}
if sizeLoc != 0 || sizeArg != 0 {
emit.Instruction(c.prog.BinWriter, opcode.INITSLOT, []byte{byte(sizeLoc), byte(sizeArg)})
if !isInit && !isDeploy {
sizeLoc := f.countLocals()
if sizeLoc > 255 {
c.prog.Err = errors.New("maximum of 255 local variables is allowed")
}
sizeArg := f.countArgs()
if sizeArg > 255 {
c.prog.Err = errors.New("maximum of 255 local variables is allowed")
}
if sizeLoc != 0 || sizeArg != 0 {
emit.Instruction(c.prog.BinWriter, opcode.INITSLOT, []byte{byte(sizeLoc), byte(sizeArg)})
}
}
f.vars.newScope()
@ -387,9 +436,9 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl, pkg *types.
// If we have reached the end of the function without encountering `return` statement,
// we should clean alt.stack manually.
// This can be the case with void and named-return functions.
if !isInit && !lastStmtIsReturn(decl) {
if !isInit && !isDeploy && !lastStmtIsReturn(decl) {
c.saveSequencePoint(decl.Body)
emit.Opcode(c.prog.BinWriter, opcode.RET)
emit.Opcodes(c.prog.BinWriter, opcode.RET)
}
f.rng.End = uint16(c.prog.Len() - 1)
@ -510,8 +559,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
}
ast.Walk(c, t.X)
ast.Walk(c, t.Index)
emit.Opcode(c.prog.BinWriter, opcode.ROT)
emit.Opcode(c.prog.BinWriter, opcode.SETITEM)
emit.Opcodes(c.prog.BinWriter, opcode.ROT, opcode.SETITEM)
}
}
return nil
@ -527,19 +575,16 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
if n.Low != nil {
ast.Walk(c, n.Low)
} else {
emit.Opcode(c.prog.BinWriter, opcode.PUSH0)
emit.Opcodes(c.prog.BinWriter, opcode.PUSH0)
}
if n.High != nil {
ast.Walk(c, n.High)
} else {
emit.Opcode(c.prog.BinWriter, opcode.OVER)
emit.Opcode(c.prog.BinWriter, opcode.SIZE)
emit.Opcodes(c.prog.BinWriter, opcode.OVER, opcode.SIZE)
}
emit.Opcode(c.prog.BinWriter, opcode.OVER)
emit.Opcode(c.prog.BinWriter, opcode.SUB)
emit.Opcode(c.prog.BinWriter, opcode.SUBSTR)
emit.Opcodes(c.prog.BinWriter, opcode.OVER, opcode.SUB, opcode.SUBSTR)
return nil
@ -574,7 +619,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
c.processDefers()
c.saveSequencePoint(n)
emit.Opcode(c.prog.BinWriter, opcode.RET)
emit.Opcodes(c.prog.BinWriter, opcode.RET)
return nil
case *ast.IfStmt:
@ -630,9 +675,9 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
if l := len(cc.List); l != 0 { // if not `default`
for j := range cc.List {
emit.Opcode(c.prog.BinWriter, opcode.DUP)
emit.Opcodes(c.prog.BinWriter, opcode.DUP)
ast.Walk(c, cc.List[j])
emit.Opcode(c.prog.BinWriter, eqOpcode)
emit.Opcodes(c.prog.BinWriter, eqOpcode)
if j == l-1 {
emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOTL, lEnd)
} else {
@ -691,7 +736,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
if tv := c.typeAndValueOf(n); tv.Value != nil {
c.emitLoadConst(tv)
} else if n.Name == "nil" {
emit.Opcode(c.prog.BinWriter, opcode.PUSHNULL)
emit.Opcodes(c.prog.BinWriter, opcode.PUSHNULL)
} else {
c.emitLoadVar("", n.Name)
}
@ -714,7 +759,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
ast.Walk(c, n.Elts[i])
}
emit.Int(c.prog.BinWriter, int64(ln))
emit.Opcode(c.prog.BinWriter, opcode.PACK)
emit.Opcodes(c.prog.BinWriter, opcode.PACK)
}
return nil
@ -786,12 +831,9 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
if ok && !isInteropPath(typ.String()) {
// To clone struct fields we create a new array and append struct to it.
// This way even non-pointer struct fields will be copied.
emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY0)
emit.Opcode(c.prog.BinWriter, opcode.DUP)
emit.Opcode(c.prog.BinWriter, opcode.ROT)
emit.Opcode(c.prog.BinWriter, opcode.APPEND)
emit.Opcode(c.prog.BinWriter, opcode.PUSH0)
emit.Opcode(c.prog.BinWriter, opcode.PICKITEM)
emit.Opcodes(c.prog.BinWriter, opcode.NEWARRAY0,
opcode.DUP, opcode.ROT, opcode.APPEND,
opcode.PUSH0, opcode.PICKITEM)
}
}
// Do not swap for builtin functions.
@ -802,7 +844,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
varSize := len(n.Args) - typ.Params().Len() + 1
c.emitReverse(varSize)
emit.Int(c.prog.BinWriter, int64(varSize))
emit.Opcode(c.prog.BinWriter, opcode.PACK)
emit.Opcodes(c.prog.BinWriter, opcode.PACK)
numArgs -= varSize - 1
}
c.emitReverse(numArgs)
@ -822,11 +864,11 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
c.emitConvert(stackitem.ByteArrayT)
} else if isFunc {
c.emitLoadVar("", name)
emit.Opcode(c.prog.BinWriter, opcode.CALLA)
emit.Opcodes(c.prog.BinWriter, opcode.CALLA)
}
case isLiteral:
ast.Walk(c, n.Fun)
emit.Opcode(c.prog.BinWriter, opcode.CALLA)
emit.Opcodes(c.prog.BinWriter, opcode.CALLA)
case isSyscall(f):
c.convertSyscall(n, f.pkg.Name(), f.name)
default:
@ -843,7 +885,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
sz = f.Results().Len()
}
for i := 0; i < sz; i++ {
emit.Opcode(c.prog.BinWriter, opcode.DROP)
emit.Opcodes(c.prog.BinWriter, opcode.DROP)
}
}
@ -909,11 +951,11 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
case token.ADD:
// +10 == 10, no need to do anything in this case
case token.SUB:
emit.Opcode(c.prog.BinWriter, opcode.NEGATE)
emit.Opcodes(c.prog.BinWriter, opcode.NEGATE)
case token.NOT:
emit.Opcode(c.prog.BinWriter, opcode.NOT)
emit.Opcodes(c.prog.BinWriter, opcode.NOT)
case token.XOR:
emit.Opcode(c.prog.BinWriter, opcode.INVERT)
emit.Opcodes(c.prog.BinWriter, opcode.INVERT)
default:
c.prog.Err = fmt.Errorf("invalid unary operator: %s", n.Op)
return nil
@ -937,7 +979,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
// This will load local whatever X is.
ast.Walk(c, n.X)
ast.Walk(c, n.Index)
emit.Opcode(c.prog.BinWriter, opcode.PICKITEM) // just pickitem here
emit.Opcodes(c.prog.BinWriter, opcode.PICKITEM) // just pickitem here
return nil
@ -1049,13 +1091,11 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
// For slices we iterate index from 0 to len-1, storing array, len and index on stack.
// For maps we iterate index from 0 to len-1, storing map, keyarray, size and index on stack.
_, isMap := c.typeOf(n.X).Underlying().(*types.Map)
emit.Opcode(c.prog.BinWriter, opcode.DUP)
emit.Opcodes(c.prog.BinWriter, opcode.DUP)
if isMap {
emit.Opcode(c.prog.BinWriter, opcode.KEYS)
emit.Opcode(c.prog.BinWriter, opcode.DUP)
emit.Opcodes(c.prog.BinWriter, opcode.KEYS, opcode.DUP)
}
emit.Opcode(c.prog.BinWriter, opcode.SIZE)
emit.Opcode(c.prog.BinWriter, opcode.PUSH0)
emit.Opcodes(c.prog.BinWriter, opcode.SIZE, opcode.PUSH0)
stackSize := 3 // slice, len(slice), index
if isMap {
@ -1064,8 +1104,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
c.pushStackLabel(label, stackSize)
c.setLabel(start)
emit.Opcode(c.prog.BinWriter, opcode.OVER)
emit.Opcode(c.prog.BinWriter, opcode.OVER)
emit.Opcodes(c.prog.BinWriter, opcode.OVER, opcode.OVER)
emit.Jmp(c.prog.BinWriter, opcode.JMPLEL, end)
var keyLoaded bool
@ -1074,11 +1113,11 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
if isMap {
c.rangeLoadKey()
if needValue {
emit.Opcode(c.prog.BinWriter, opcode.DUP)
emit.Opcodes(c.prog.BinWriter, opcode.DUP)
keyLoaded = true
}
} else {
emit.Opcode(c.prog.BinWriter, opcode.DUP)
emit.Opcodes(c.prog.BinWriter, opcode.DUP)
}
c.emitStoreVar("", n.Key.(*ast.Ident).Name)
}
@ -1089,9 +1128,10 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
if isMap {
// we have loaded only key from key array, now load value
emit.Int(c.prog.BinWriter, 4)
emit.Opcode(c.prog.BinWriter, opcode.PICK) // load map itself (+1 because key was pushed)
emit.Opcode(c.prog.BinWriter, opcode.SWAP) // key should be on top
emit.Opcode(c.prog.BinWriter, opcode.PICKITEM)
emit.Opcodes(c.prog.BinWriter,
opcode.PICK, // load map itself (+1 because key was pushed)
opcode.SWAP, // key should be on top
opcode.PICKITEM)
}
c.emitStoreVar("", n.Value.(*ast.Ident).Name)
}
@ -1100,7 +1140,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
c.setLabel(post)
emit.Opcode(c.prog.BinWriter, opcode.INC)
emit.Opcodes(c.prog.BinWriter, opcode.INC)
emit.Jmp(c.prog.BinWriter, opcode.JMPL, start)
c.setLabel(end)
@ -1164,16 +1204,17 @@ func (c *codegen) processDefers() {
c.setLabel(before)
emit.Int(c.prog.BinWriter, 0)
c.emitStoreByIndex(varLocal, c.scope.finallyProcessedIndex)
emit.Opcode(c.prog.BinWriter, opcode.ENDFINALLY)
emit.Opcodes(c.prog.BinWriter, opcode.ENDFINALLY)
c.setLabel(after)
}
}
func (c *codegen) rangeLoadKey() {
emit.Int(c.prog.BinWriter, 2)
emit.Opcode(c.prog.BinWriter, opcode.PICK) // load keys
emit.Opcode(c.prog.BinWriter, opcode.OVER) // load index in key array
emit.Opcode(c.prog.BinWriter, opcode.PICKITEM)
emit.Opcodes(c.prog.BinWriter,
opcode.PICK, // load keys
opcode.OVER, // load index in key array
opcode.PICKITEM)
}
func isFallthroughStmt(c ast.Node) bool {
@ -1230,11 +1271,11 @@ func (c *codegen) emitBinaryExpr(n *ast.BinaryExpr, needJump bool, cond bool, jm
return
} else if arg := c.getCompareWithNilArg(n); arg != nil {
ast.Walk(c, arg)
emit.Opcode(c.prog.BinWriter, opcode.ISNULL)
emit.Opcodes(c.prog.BinWriter, opcode.ISNULL)
if needJump {
c.emitJumpOnCondition(cond == (n.Op == token.EQL), jmpLabel)
} else if n.Op == token.NEQ {
emit.Opcode(c.prog.BinWriter, opcode.NOT)
emit.Opcodes(c.prog.BinWriter, opcode.NOT)
}
return
}
@ -1299,14 +1340,13 @@ func (c *codegen) dropStackLabel() {
func (c *codegen) dropItems(n int) {
if n < 4 {
for i := 0; i < n; i++ {
emit.Opcode(c.prog.BinWriter, opcode.DROP)
emit.Opcodes(c.prog.BinWriter, opcode.DROP)
}
return
}
emit.Int(c.prog.BinWriter, int64(n))
emit.Opcode(c.prog.BinWriter, opcode.PACK)
emit.Opcode(c.prog.BinWriter, opcode.DROP)
emit.Opcodes(c.prog.BinWriter, opcode.PACK, opcode.DROP)
}
// emitReverse reverses top num items of the stack.
@ -1314,14 +1354,14 @@ func (c *codegen) emitReverse(num int) {
switch num {
case 0, 1:
case 2:
emit.Opcode(c.prog.BinWriter, opcode.SWAP)
emit.Opcodes(c.prog.BinWriter, opcode.SWAP)
case 3:
emit.Opcode(c.prog.BinWriter, opcode.REVERSE3)
emit.Opcodes(c.prog.BinWriter, opcode.REVERSE3)
case 4:
emit.Opcode(c.prog.BinWriter, opcode.REVERSE4)
emit.Opcodes(c.prog.BinWriter, opcode.REVERSE4)
default:
emit.Int(c.prog.BinWriter, int64(num))
emit.Opcode(c.prog.BinWriter, opcode.REVERSEN)
emit.Opcodes(c.prog.BinWriter, opcode.REVERSEN)
}
}
@ -1400,7 +1440,7 @@ func (c *codegen) convertSyscall(expr *ast.CallExpr, api, name string) {
// This NOP instruction is basically not needed, but if we do, we have a
// one to one matching avm file with neo-python which is very nice for debugging.
emit.Opcode(c.prog.BinWriter, opcode.NOP)
emit.Opcodes(c.prog.BinWriter, opcode.NOP)
}
// emitSliceHelper emits 3 items on stack: slice, its first index, and its size.
@ -1416,8 +1456,7 @@ func (c *codegen) emitSliceHelper(e ast.Expr) {
if src.High != nil {
ast.Walk(c, src.High)
} else {
emit.Opcode(c.prog.BinWriter, opcode.DUP)
emit.Opcode(c.prog.BinWriter, opcode.SIZE)
emit.Opcodes(c.prog.BinWriter, opcode.DUP, opcode.SIZE)
}
if src.Low != nil {
ast.Walk(c, src.Low)
@ -1427,17 +1466,13 @@ func (c *codegen) emitSliceHelper(e ast.Expr) {
}
default:
ast.Walk(c, src)
emit.Opcode(c.prog.BinWriter, opcode.DUP)
emit.Opcode(c.prog.BinWriter, opcode.SIZE)
emit.Opcodes(c.prog.BinWriter, opcode.DUP, opcode.SIZE)
emit.Int(c.prog.BinWriter, 0)
}
if !hasLowIndex {
emit.Opcode(c.prog.BinWriter, opcode.SWAP)
emit.Opcodes(c.prog.BinWriter, opcode.SWAP)
} else {
emit.Opcode(c.prog.BinWriter, opcode.DUP)
emit.Opcode(c.prog.BinWriter, opcode.ROT)
emit.Opcode(c.prog.BinWriter, opcode.SWAP)
emit.Opcode(c.prog.BinWriter, opcode.SUB)
emit.Opcodes(c.prog.BinWriter, opcode.DUP, opcode.ROT, opcode.SWAP, opcode.SUB)
}
}
@ -1456,22 +1491,21 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) {
c.emitSliceHelper(expr.Args[0])
c.emitSliceHelper(expr.Args[1])
emit.Int(c.prog.BinWriter, 3)
emit.Opcode(c.prog.BinWriter, opcode.ROLL)
emit.Opcode(c.prog.BinWriter, opcode.MIN)
emit.Opcodes(c.prog.BinWriter, opcode.ROLL, opcode.MIN)
if !c.scope.voidCalls[expr] {
// insert top item to the bottom of MEMCPY args, so that it is left on stack
emit.Opcode(c.prog.BinWriter, opcode.DUP)
emit.Opcodes(c.prog.BinWriter, opcode.DUP)
emit.Int(c.prog.BinWriter, 6)
emit.Opcode(c.prog.BinWriter, opcode.REVERSEN)
emit.Opcodes(c.prog.BinWriter, opcode.REVERSEN)
emit.Int(c.prog.BinWriter, 5)
emit.Opcode(c.prog.BinWriter, opcode.REVERSEN)
emit.Opcodes(c.prog.BinWriter, opcode.REVERSEN)
}
emit.Opcode(c.prog.BinWriter, opcode.MEMCPY)
emit.Opcodes(c.prog.BinWriter, opcode.MEMCPY)
case "make":
typ := c.typeOf(expr.Args[0])
switch {
case isMap(typ):
emit.Opcode(c.prog.BinWriter, opcode.NEWMAP)
emit.Opcodes(c.prog.BinWriter, opcode.NEWMAP)
default:
if len(expr.Args) == 3 {
c.prog.Err = fmt.Errorf("`make()` with a capacity argument is not supported")
@ -1479,57 +1513,47 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) {
}
ast.Walk(c, expr.Args[1])
if isByteSlice(typ) {
emit.Opcode(c.prog.BinWriter, opcode.NEWBUFFER)
emit.Opcodes(c.prog.BinWriter, opcode.NEWBUFFER)
} else {
neoT := toNeoType(typ.(*types.Slice).Elem())
emit.Instruction(c.prog.BinWriter, opcode.NEWARRAYT, []byte{byte(neoT)})
}
}
case "len":
emit.Opcode(c.prog.BinWriter, opcode.DUP)
emit.Opcode(c.prog.BinWriter, opcode.ISNULL)
emit.Opcodes(c.prog.BinWriter, opcode.DUP, opcode.ISNULL)
emit.Instruction(c.prog.BinWriter, opcode.JMPIF, []byte{2 + 1 + 2})
emit.Opcode(c.prog.BinWriter, opcode.SIZE)
emit.Opcodes(c.prog.BinWriter, opcode.SIZE)
emit.Instruction(c.prog.BinWriter, opcode.JMP, []byte{2 + 1 + 1})
emit.Opcode(c.prog.BinWriter, opcode.DROP)
emit.Opcode(c.prog.BinWriter, opcode.PUSH0)
emit.Opcodes(c.prog.BinWriter, opcode.DROP, opcode.PUSH0)
case "append":
arg := expr.Args[0]
typ := c.typeInfo.Types[arg].Type
c.emitReverse(len(expr.Args))
emit.Opcode(c.prog.BinWriter, opcode.DUP)
emit.Opcode(c.prog.BinWriter, opcode.ISNULL)
emit.Opcodes(c.prog.BinWriter, opcode.DUP, opcode.ISNULL)
emit.Instruction(c.prog.BinWriter, opcode.JMPIFNOT, []byte{2 + 3})
if isByteSlice(typ) {
emit.Opcode(c.prog.BinWriter, opcode.DROP)
emit.Opcode(c.prog.BinWriter, opcode.PUSH0)
emit.Opcode(c.prog.BinWriter, opcode.NEWBUFFER)
emit.Opcodes(c.prog.BinWriter, opcode.DROP, opcode.PUSH0, opcode.NEWBUFFER)
} else {
emit.Opcode(c.prog.BinWriter, opcode.DROP)
emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY0)
emit.Opcode(c.prog.BinWriter, opcode.NOP)
emit.Opcodes(c.prog.BinWriter, opcode.DROP, opcode.NEWARRAY0, opcode.NOP)
}
// Jump target.
for range expr.Args[1:] {
if isByteSlice(typ) {
emit.Opcode(c.prog.BinWriter, opcode.SWAP)
emit.Opcode(c.prog.BinWriter, opcode.CAT)
emit.Opcodes(c.prog.BinWriter, opcode.SWAP, opcode.CAT)
} else {
emit.Opcode(c.prog.BinWriter, opcode.DUP)
emit.Opcode(c.prog.BinWriter, opcode.ROT)
emit.Opcode(c.prog.BinWriter, opcode.APPEND)
emit.Opcodes(c.prog.BinWriter, opcode.DUP, opcode.ROT, opcode.APPEND)
}
}
case "panic":
emit.Opcode(c.prog.BinWriter, opcode.THROW)
emit.Opcodes(c.prog.BinWriter, opcode.THROW)
case "recover":
if !c.scope.voidCalls[expr] {
c.emitLoadByIndex(varGlobal, c.exceptionIndex)
}
emit.Opcode(c.prog.BinWriter, opcode.PUSHNULL)
emit.Opcodes(c.prog.BinWriter, opcode.PUSHNULL)
c.emitStoreByIndex(varGlobal, c.exceptionIndex)
case "delete":
emit.Opcode(c.prog.BinWriter, opcode.REMOVE)
emit.Opcodes(c.prog.BinWriter, opcode.REMOVE)
case "ToInteger", "ToByteArray", "ToBool":
typ := stackitem.IntegerT
switch name {
@ -1544,9 +1568,9 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) {
c.prog.Err = errors.New("`Remove` supports only non-byte slices")
return
}
emit.Opcode(c.prog.BinWriter, opcode.REMOVE)
emit.Opcodes(c.prog.BinWriter, opcode.REMOVE)
case "Equals":
emit.Opcode(c.prog.BinWriter, opcode.EQUAL)
emit.Opcodes(c.prog.BinWriter, opcode.EQUAL)
case "FromAddress":
// We can be sure that this is a ast.BasicLit just containing a simple
// address string. Note that the string returned from calling Value will
@ -1607,21 +1631,21 @@ func (c *codegen) convertByteArray(lit *ast.CompositeLit) {
emit.Bytes(c.prog.BinWriter, buf)
c.emitConvert(stackitem.BufferT)
for _, i := range varIndices {
emit.Opcode(c.prog.BinWriter, opcode.DUP)
emit.Opcodes(c.prog.BinWriter, opcode.DUP)
emit.Int(c.prog.BinWriter, int64(i))
ast.Walk(c, lit.Elts[i])
emit.Opcode(c.prog.BinWriter, opcode.SETITEM)
emit.Opcodes(c.prog.BinWriter, opcode.SETITEM)
}
}
func (c *codegen) convertMap(lit *ast.CompositeLit) {
emit.Opcode(c.prog.BinWriter, opcode.NEWMAP)
emit.Opcodes(c.prog.BinWriter, opcode.NEWMAP)
for i := range lit.Elts {
elem := lit.Elts[i].(*ast.KeyValueExpr)
emit.Opcode(c.prog.BinWriter, opcode.DUP)
emit.Opcodes(c.prog.BinWriter, opcode.DUP)
ast.Walk(c, elem.Key)
ast.Walk(c, elem.Value)
emit.Opcode(c.prog.BinWriter, opcode.SETITEM)
emit.Opcodes(c.prog.BinWriter, opcode.SETITEM)
}
}
@ -1646,12 +1670,12 @@ func (c *codegen) convertStruct(lit *ast.CompositeLit, ptr bool) {
return
}
emit.Opcode(c.prog.BinWriter, opcode.NOP)
emit.Opcodes(c.prog.BinWriter, opcode.NOP)
emit.Int(c.prog.BinWriter, int64(strct.NumFields()))
if ptr {
emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY)
emit.Opcodes(c.prog.BinWriter, opcode.NEWARRAY)
} else {
emit.Opcode(c.prog.BinWriter, opcode.NEWSTRUCT)
emit.Opcodes(c.prog.BinWriter, opcode.NEWSTRUCT)
}
keyedLit := len(lit.Elts) > 0
@ -1665,7 +1689,7 @@ func (c *codegen) convertStruct(lit *ast.CompositeLit, ptr bool) {
sField := strct.Field(i)
var initialized bool
emit.Opcode(c.prog.BinWriter, opcode.DUP)
emit.Opcodes(c.prog.BinWriter, opcode.DUP)
emit.Int(c.prog.BinWriter, int64(i))
if !keyedLit {
@ -1689,7 +1713,7 @@ func (c *codegen) convertStruct(lit *ast.CompositeLit, ptr bool) {
if !initialized {
c.emitDefault(sField.Type())
}
emit.Opcode(c.prog.BinWriter, opcode.SETITEM)
emit.Opcodes(c.prog.BinWriter, opcode.SETITEM)
}
}
@ -1699,7 +1723,7 @@ func (c *codegen) emitToken(tok token.Token, typ types.Type) {
c.prog.Err = err
return
}
emit.Opcode(c.prog.BinWriter, op)
emit.Opcodes(c.prog.BinWriter, op)
}
func convertToken(tok token.Token, typ types.Type) (opcode.Opcode, error) {
@ -1803,10 +1827,19 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error {
// Bring all imported functions into scope.
c.ForEachFile(c.resolveFuncDecls)
n, hasInit := c.traverseGlobals()
n, initLocals, deployLocals := c.traverseGlobals()
hasInit := initLocals > -1
if n > 0 || hasInit {
emit.Opcode(c.prog.BinWriter, opcode.RET)
c.initEndOffset = c.prog.Len()
emit.Opcodes(c.prog.BinWriter, opcode.RET)
}
hasDeploy := deployLocals > -1
if hasDeploy {
emit.Instruction(c.prog.BinWriter, opcode.INITSLOT, []byte{byte(deployLocals), 1})
c.convertDeployFuncs()
c.deployEndOffset = c.prog.Len()
emit.Opcodes(c.prog.BinWriter, opcode.RET)
}
// sort map keys to generate code deterministically.
@ -1824,7 +1857,7 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error {
// Don't convert the function if it's not used. This will save a lot
// of bytecode space.
name := c.getFuncNameFromDecl(pkg.Path(), n)
if !isInitFunc(n) && funUsage.funcUsed(name) && !isInteropPath(pkg.Path()) {
if !isInitFunc(n) && !isDeployFunc(n) && funUsage.funcUsed(name) && !isInteropPath(pkg.Path()) {
c.convertFuncDecl(f, n, pkg)
}
}
@ -1847,6 +1880,9 @@ func newCodegen(info *buildInfo, pkg *loader.PackageInfo) *codegen {
constMap: map[string]types.TypeAndValue{},
docIndex: map[string]int{},
initEndOffset: -1,
deployEndOffset: -1,
sequencePoints: make(map[string][]DebugSeqPoint),
}
}

View file

@ -134,6 +134,27 @@ func (c *codegen) emitDebugInfo(contract []byte) *DebugInfo {
SeqPoints: c.sequencePoints["init"],
})
}
if c.deployEndOffset >= 0 {
d.Methods = append(d.Methods, MethodDebugInfo{
ID: manifest.MethodDeploy,
Name: DebugMethodName{
Name: manifest.MethodDeploy,
Namespace: c.mainPkg.Pkg.Name(),
},
IsExported: true,
IsFunction: true,
Range: DebugRange{
Start: uint16(c.initEndOffset + 1),
End: uint16(c.deployEndOffset),
},
Parameters: []DebugParam{{
Name: "isUpdate",
Type: "Boolean",
}},
ReturnType: "Void",
SeqPoints: c.sequencePoints[manifest.MethodDeploy],
})
}
for name, scope := range c.funcs {
m := c.methodInfoFromScope(name, scope)
if m.Range.Start == m.Range.End {

View file

@ -55,6 +55,7 @@ func MethodParams(addr interop.Hash160, h interop.Hash256,
type MyStruct struct {}
func (ms MyStruct) MethodOnStruct() { }
func (ms *MyStruct) MethodOnPointerToStruct() { }
func _deploy(isUpdate bool) {}
`
info, err := getBuildInfo("foo.go", src)
@ -83,6 +84,7 @@ func (ms *MyStruct) MethodOnPointerToStruct() { }
"MethodOnStruct": "Void",
"MethodOnPointerToStruct": "Void",
"MethodParams": "Boolean",
"_deploy": "Void",
}
for i := range d.Methods {
name := d.Methods[i].ID
@ -104,6 +106,10 @@ func (ms *MyStruct) MethodOnPointerToStruct() { }
t.Run("param types", func(t *testing.T) {
paramTypes := map[string][]DebugParam{
"_deploy": {{
Name: "isUpdate",
Type: "Boolean",
}},
"MethodInt": {{
Name: "a",
Type: "String",
@ -151,8 +157,16 @@ func (ms *MyStruct) MethodOnPointerToStruct() { }
Hash: hash.Hash160(buf),
Methods: []manifest.Method{
{
Name: "main",
Name: "_deploy",
Offset: 0,
Parameters: []manifest.Parameter{
manifest.NewParameter("isUpdate", smartcontract.BoolType),
},
ReturnType: smartcontract.VoidType,
},
{
Name: "main",
Offset: 4,
Parameters: []manifest.Parameter{
manifest.NewParameter("op", smartcontract.StringType),
},
@ -160,7 +174,7 @@ func (ms *MyStruct) MethodOnPointerToStruct() { }
},
{
Name: "methodInt",
Offset: 66,
Offset: 70,
Parameters: []manifest.Parameter{
{
Name: "a",
@ -171,31 +185,31 @@ func (ms *MyStruct) MethodOnPointerToStruct() { }
},
{
Name: "methodString",
Offset: 97,
Offset: 101,
Parameters: []manifest.Parameter{},
ReturnType: smartcontract.StringType,
},
{
Name: "methodByteArray",
Offset: 103,
Offset: 107,
Parameters: []manifest.Parameter{},
ReturnType: smartcontract.ByteArrayType,
},
{
Name: "methodArray",
Offset: 108,
Offset: 112,
Parameters: []manifest.Parameter{},
ReturnType: smartcontract.ArrayType,
},
{
Name: "methodStruct",
Offset: 113,
Offset: 117,
Parameters: []manifest.Parameter{},
ReturnType: smartcontract.ArrayType,
},
{
Name: "methodConcat",
Offset: 88,
Offset: 92,
Parameters: []manifest.Parameter{
{
Name: "a",
@ -214,7 +228,7 @@ func (ms *MyStruct) MethodOnPointerToStruct() { }
},
{
Name: "methodParams",
Offset: 125,
Offset: 129,
Parameters: []manifest.Parameter{
manifest.NewParameter("addr", smartcontract.Hash160Type),
manifest.NewParameter("h", smartcontract.Hash256Type),

View file

@ -152,10 +152,10 @@ func (c *funcScope) analyzeVoidCalls(node ast.Node) bool {
return true
}
func (c *funcScope) countLocals() int {
func countLocals(decl *ast.FuncDecl) (int, bool) {
size := 0
hasDefer := false
ast.Inspect(c.decl, func(n ast.Node) bool {
ast.Inspect(decl, func(n ast.Node) bool {
switch n := n.(type) {
case *ast.FuncType:
num := n.Results.NumFields()
@ -186,6 +186,11 @@ func (c *funcScope) countLocals() int {
}
return true
})
return size, hasDefer
}
func (c *funcScope) countLocals() int {
size, hasDefer := countLocals(c.decl)
if hasDefer {
c.finallyProcessedIndex = size
size++

View file

@ -24,11 +24,13 @@ func TestInit(t *testing.T) {
var m = map[int]int{}
var a = 2
func init() {
m[1] = 11
b := 11
m[1] = b
}
func init() {
a = 1
m[3] = 30
var b int
m[3] = 30 + b
}
func Main() int {
return m[1] + m[3] + a

View file

@ -237,12 +237,12 @@ func TestVerifyTx(t *testing.T) {
}
emit.AppCallWithOperationAndArgs(w.BinWriter, sc, "transfer",
neoOwner, a.Contract.ScriptHash(), amount)
emit.Opcode(w.BinWriter, opcode.ASSERT)
emit.Opcodes(w.BinWriter, opcode.ASSERT)
}
}
emit.AppCallWithOperationAndArgs(w.BinWriter, gasHash, "transfer",
neoOwner, testchain.CommitteeScriptHash(), int64(1_000_000_000))
emit.Opcode(w.BinWriter, opcode.ASSERT)
emit.Opcodes(w.BinWriter, opcode.ASSERT)
require.NoError(t, w.Err)
txMove := bc.newTestTx(neoOwner, w.Bytes())
@ -782,7 +782,7 @@ func TestSubscriptions(t *testing.T) {
script = io.NewBufBinWriter()
emit.Bytes(script.BinWriter, []byte("nay!"))
emit.Syscall(script.BinWriter, interopnames.SystemRuntimeNotify)
emit.Opcode(script.BinWriter, opcode.THROW)
emit.Opcodes(script.BinWriter, opcode.THROW)
require.NoError(t, script.Err)
txBad := transaction.New(netmode.UnitTestNet, script.Bytes(), 0)
txBad.Signers = []transaction.Signer{{Account: neoOwner}}

View file

@ -361,7 +361,7 @@ func TestCreateBasicChain(t *testing.T) {
func newNEP5Transfer(sc, from, to util.Uint160, amount int64) *transaction.Transaction {
w := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(w.BinWriter, sc, "transfer", from, to, amount)
emit.Opcode(w.BinWriter, opcode.ASSERT)
emit.Opcodes(w.BinWriter, opcode.ASSERT)
script := w.Bytes()
return transaction.New(testchain.Network(), script, 10000000)

View file

@ -6,6 +6,7 @@ import (
"strings"
"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/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util"
@ -44,21 +45,28 @@ func callExInternal(ic *interop.Context, h []byte, name string, args []stackitem
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(ic.VM.GetCurrentScriptHash())
if err == nil {
if !curr.Manifest.CanCall(&cs.Manifest, name) {
return errors.New("disallowed method call")
}
}
return CallExInternal(ic, cs, name, args, f)
}
// CallExInternal calls a contract with flags and can't be invoked directly by user.
func CallExInternal(ic *interop.Context, cs *state.Contract,
name string, args []stackitem.Item, f smartcontract.CallFlag) error {
md := cs.Manifest.ABI.GetMethod(name)
if md == nil {
return fmt.Errorf("method '%s' not found", name)
}
if len(args) != len(md.Parameters) {
return fmt.Errorf("invalid argument count: %d (expected %d)", len(args), len(md.Parameters))
}
u := cs.ScriptHash()
ic.Invocations[u]++
ic.VM.LoadScriptWithHash(cs.Script, u, ic.VM.Context().GetCallFlags()&f)
var isNative bool

View file

@ -9,8 +9,10 @@ import (
"github.com/mr-tron/base58"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"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/stackitem"
@ -109,7 +111,7 @@ func contractCreate(ic *interop.Context) error {
return fmt.Errorf("cannot convert contract to stack item: %w", err)
}
ic.VM.Estack().PushVal(cs)
return nil
return callDeploy(ic, newcontract, false)
}
func checkNonEmpty(b []byte, max int) error {
@ -194,6 +196,15 @@ func contractUpdate(ic *interop.Context) error {
}
}
return callDeploy(ic, contract, true)
}
func callDeploy(ic *interop.Context, cs *state.Contract, isUpdate bool) error {
md := cs.Manifest.ABI.GetMethod(manifest.MethodDeploy)
if md != nil {
return contract.CallExInternal(ic, cs, manifest.MethodDeploy,
[]stackitem.Item{stackitem.NewBool(isUpdate)}, smartcontract.All)
}
return nil
}

View file

@ -10,15 +10,18 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/interop/callback"
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io"
"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/vm"
"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/stackitem"
"github.com/stretchr/testify/require"
@ -365,23 +368,49 @@ func TestStoragePut(t *testing.T) {
// getTestContractState returns 2 contracts second of which is allowed to call the first.
func getTestContractState() (*state.Contract, *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),
byte(opcode.PUSH1), byte(opcode.PUSH2), byte(opcode.RET),
byte(opcode.RET),
byte(opcode.LDSFLD0), byte(opcode.SUB), byte(opcode.CONVERT), byte(stackitem.BooleanT), byte(opcode.RET),
}
w := io.NewBufBinWriter()
emit.Opcodes(w.BinWriter, opcode.ABORT)
addOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.ADD, opcode.RET)
ret7Off := w.Len()
emit.Opcodes(w.BinWriter, opcode.PUSH7, opcode.RET)
dropOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.DROP, opcode.RET)
initOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.INITSSLOT, 1, opcode.PUSH3, opcode.STSFLD0, opcode.RET)
add3Off := w.Len()
emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.ADD, opcode.RET)
invalidRetOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.PUSH1, opcode.PUSH2, opcode.RET)
justRetOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.RET)
verifyOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.SUB,
opcode.CONVERT, opcode.Opcode(stackitem.BooleanT), opcode.RET)
deployOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.JMPIF, 2+8+3)
emit.String(w.BinWriter, "create")
emit.Opcodes(w.BinWriter, opcode.CALL, 3+8+3, opcode.RET)
emit.String(w.BinWriter, "update")
emit.Opcodes(w.BinWriter, opcode.CALL, 3, opcode.RET)
putValOff := w.Len()
emit.String(w.BinWriter, "initial")
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext)
emit.Syscall(w.BinWriter, interopnames.SystemStoragePut)
emit.Opcodes(w.BinWriter, opcode.RET)
getValOff := w.Len()
emit.String(w.BinWriter, "initial")
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext)
emit.Syscall(w.BinWriter, interopnames.SystemStorageGet)
script := w.Bytes()
h := hash.Hash160(script)
m := manifest.NewManifest(h)
m.Features = smartcontract.HasStorage
m.ABI.Methods = []manifest.Method{
{
Name: "add",
Offset: 1,
Offset: addOff,
Parameters: []manifest.Parameter{
manifest.NewParameter("addend1", smartcontract.IntegerType),
manifest.NewParameter("addend2", smartcontract.IntegerType),
@ -390,23 +419,23 @@ func getTestContractState() (*state.Contract, *state.Contract) {
},
{
Name: "ret7",
Offset: 3,
Offset: ret7Off,
Parameters: []manifest.Parameter{},
ReturnType: smartcontract.IntegerType,
},
{
Name: "drop",
Offset: 5,
Offset: dropOff,
ReturnType: smartcontract.VoidType,
},
{
Name: manifest.MethodInit,
Offset: 7,
Offset: initOff,
ReturnType: smartcontract.VoidType,
},
{
Name: "add3",
Offset: 12,
Offset: add3Off,
Parameters: []manifest.Parameter{
manifest.NewParameter("addend", smartcontract.IntegerType),
},
@ -414,19 +443,40 @@ func getTestContractState() (*state.Contract, *state.Contract) {
},
{
Name: "invalidReturn",
Offset: 15,
Offset: invalidRetOff,
ReturnType: smartcontract.IntegerType,
},
{
Name: "justReturn",
Offset: 18,
Offset: justRetOff,
ReturnType: smartcontract.IntegerType,
},
{
Name: manifest.MethodVerify,
Offset: 19,
Offset: verifyOff,
ReturnType: smartcontract.BoolType,
},
{
Name: manifest.MethodDeploy,
Offset: deployOff,
Parameters: []manifest.Parameter{
manifest.NewParameter("isUpdate", smartcontract.BoolType),
},
ReturnType: smartcontract.VoidType,
},
{
Name: "getValue",
Offset: getValOff,
ReturnType: smartcontract.StringType,
},
{
Name: "putValue",
Offset: putValOff,
Parameters: []manifest.Parameter{
manifest.NewParameter("value", smartcontract.StringType),
},
ReturnType: smartcontract.VoidType,
},
}
cs := &state.Contract{
Script: script,
@ -442,6 +492,7 @@ func getTestContractState() (*state.Contract, *state.Contract) {
perm.Methods.Add("add3")
perm.Methods.Add("invalidReturn")
perm.Methods.Add("justReturn")
perm.Methods.Add("getValue")
m.Permissions = append(m.Permissions, *perm)
return cs, &state.Contract{
@ -838,6 +889,55 @@ func TestContractUpdate(t *testing.T) {
})
}
// TestContractCreateDeploy checks that `_deploy` method was called
// during contract creation or update.
func TestContractCreateDeploy(t *testing.T) {
v, ic, bc := createVM(t)
defer bc.Close()
v.GasLimit = -1
putArgs := func(cs *state.Contract) {
rawManifest, err := cs.Manifest.MarshalJSON()
require.NoError(t, err)
v.Estack().PushVal(rawManifest)
v.Estack().PushVal(cs.Script)
}
cs, currCs := getTestContractState()
v.LoadScriptWithFlags([]byte{byte(opcode.RET)}, smartcontract.All)
putArgs(cs)
require.NoError(t, contractCreate(ic))
require.NoError(t, ic.VM.Run())
v.LoadScriptWithFlags(currCs.Script, smartcontract.All)
err := contract.CallExInternal(ic, cs, "getValue", nil, smartcontract.All)
require.NoError(t, err)
require.NoError(t, v.Run())
require.Equal(t, "create", v.Estack().Pop().String())
v.LoadScriptWithFlags(cs.Script, smartcontract.All)
md := cs.Manifest.ABI.GetMethod("justReturn")
v.Jump(v.Context(), md.Offset)
t.Run("Update", func(t *testing.T) {
newCs := &state.Contract{
ID: cs.ID,
Script: append(cs.Script, byte(opcode.RET)),
Manifest: cs.Manifest,
}
newCs.Manifest.ABI.Hash = hash.Hash160(newCs.Script)
putArgs(newCs)
require.NoError(t, contractUpdate(ic))
require.NoError(t, v.Run())
v.LoadScriptWithFlags(currCs.Script, smartcontract.All)
err = contract.CallExInternal(ic, newCs, "getValue", nil, smartcontract.All)
require.NoError(t, err)
require.NoError(t, v.Run())
require.Equal(t, "update", v.Estack().Pop().String())
})
}
func TestContractGetCallFlags(t *testing.T) {
v, ic, bc := createVM(t)
defer bc.Close()

View file

@ -83,10 +83,10 @@ func (cs *Contracts) GetPersistScript() []byte {
continue
}
emit.Int(w.BinWriter, 0)
emit.Opcode(w.BinWriter, opcode.NEWARRAY)
emit.Opcodes(w.BinWriter, opcode.NEWARRAY)
emit.String(w.BinWriter, "onPersist")
emit.AppCall(w.BinWriter, md.Hash)
emit.Opcode(w.BinWriter, opcode.DROP)
emit.Opcodes(w.BinWriter, opcode.DROP)
}
cs.persistScript = w.Bytes()
return cs.persistScript
@ -106,10 +106,10 @@ func (cs *Contracts) GetPostPersistScript() []byte {
continue
}
emit.Int(w.BinWriter, 0)
emit.Opcode(w.BinWriter, opcode.NEWARRAY)
emit.Opcodes(w.BinWriter, opcode.NEWARRAY)
emit.String(w.BinWriter, "postPersist")
emit.AppCall(w.BinWriter, md.Hash)
emit.Opcode(w.BinWriter, opcode.DROP)
emit.Opcodes(w.BinWriter, opcode.DROP)
}
cs.postPersistScript = w.Bytes()
return cs.postPersistScript

View file

@ -56,7 +56,7 @@ func init() {
w.Reset()
emit.Int(w.BinWriter, 0)
emit.Opcode(w.BinWriter, opcode.NEWARRAY)
emit.Opcodes(w.BinWriter, opcode.NEWARRAY)
emit.String(w.BinWriter, "finish")
emit.Bytes(w.BinWriter, h.BytesBE())
emit.Syscall(w.BinWriter, interopnames.SystemContractCall)

View file

@ -23,10 +23,10 @@ func (bc *Blockchain) setNodesByRole(t *testing.T, ok bool, r native.Role, nodes
emit.Bytes(w.BinWriter, pub.Bytes())
}
emit.Int(w.BinWriter, int64(len(nodes)))
emit.Opcode(w.BinWriter, opcode.PACK)
emit.Opcodes(w.BinWriter, opcode.PACK)
emit.Int(w.BinWriter, int64(r))
emit.Int(w.BinWriter, 2)
emit.Opcode(w.BinWriter, opcode.PACK)
emit.Opcodes(w.BinWriter, opcode.PACK)
emit.String(w.BinWriter, "designateAsRole")
emit.AppCall(w.BinWriter, bc.contracts.Designate.Hash)
require.NoError(t, w.Err)

View file

@ -27,26 +27,26 @@ import (
func getOracleContractState(h util.Uint160) *state.Contract {
w := io.NewBufBinWriter()
emit.Int(w.BinWriter, 5)
emit.Opcode(w.BinWriter, opcode.PACK)
emit.Opcodes(w.BinWriter, opcode.PACK)
emit.String(w.BinWriter, "request")
emit.Bytes(w.BinWriter, h.BytesBE())
emit.Syscall(w.BinWriter, interopnames.SystemContractCall)
emit.Opcode(w.BinWriter, opcode.RET)
emit.Opcodes(w.BinWriter, opcode.RET)
// `handle` method aborts if len(userData) == 2
offset := w.Len()
emit.Opcode(w.BinWriter, opcode.OVER)
emit.Opcode(w.BinWriter, opcode.SIZE)
emit.Opcodes(w.BinWriter, opcode.OVER)
emit.Opcodes(w.BinWriter, opcode.SIZE)
emit.Int(w.BinWriter, 2)
emit.Instruction(w.BinWriter, opcode.JMPNE, []byte{3})
emit.Opcode(w.BinWriter, opcode.ABORT)
emit.Opcodes(w.BinWriter, opcode.ABORT)
emit.Int(w.BinWriter, 4) // url, userData, code, result
emit.Opcode(w.BinWriter, opcode.PACK)
emit.Opcodes(w.BinWriter, opcode.PACK)
emit.Syscall(w.BinWriter, interopnames.SystemBinarySerialize)
emit.String(w.BinWriter, "lastOracleResponse")
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext)
emit.Syscall(w.BinWriter, interopnames.SystemStoragePut)
emit.Opcode(w.BinWriter, opcode.RET)
emit.Opcodes(w.BinWriter, opcode.RET)
m := manifest.NewManifest(h)
m.Features = smartcontract.HasStorage

View file

@ -314,7 +314,7 @@ func (p *PublicKey) GetVerificationScript() []byte {
return buf.Bytes()
}
emit.Bytes(buf.BinWriter, b)
emit.Opcode(buf.BinWriter, opcode.PUSHNULL)
emit.Opcodes(buf.BinWriter, opcode.PUSHNULL)
emit.Syscall(buf.BinWriter, interopnames.NeoCryptoVerifyWithECDsaSecp256r1)
return buf.Bytes()

View file

@ -132,7 +132,7 @@ func (c *Client) CreateNEP5MultiTransferTx(acc *wallet.Account, gas int64, recip
for i := range recipients {
emit.AppCallWithOperationAndArgs(w.BinWriter, recipients[i].Token, "transfer", from,
recipients[i].Address, recipients[i].Amount)
emit.Opcode(w.BinWriter, opcode.ASSERT)
emit.Opcodes(w.BinWriter, opcode.ASSERT)
}
return c.CreateTxFromScript(w.Bytes(), acc, -1, gas, transaction.Signer{
Account: acc.Contract.ScriptHash(),

View file

@ -107,7 +107,7 @@ func expandArrayIntoScript(script *io.BinWriter, slice []Param) error {
return err
}
emit.Int(script, int64(len(val)))
emit.Opcode(script, opcode.PACK)
emit.Opcodes(script, opcode.PACK)
default:
return fmt.Errorf("parameter type %v is not supported", fp.Type)
}
@ -139,7 +139,7 @@ func CreateFunctionInvocationScript(contract util.Uint160, params Params) ([]byt
return nil, err
}
emit.Int(script.BinWriter, int64(len(slice)))
emit.Opcode(script.BinWriter, opcode.PACK)
emit.Opcodes(script.BinWriter, opcode.PACK)
}
}

View file

@ -31,7 +31,7 @@ func CreateMultiSigRedeemScript(m int, publicKeys keys.PublicKeys) ([]byte, erro
emit.Bytes(buf.BinWriter, pubKey.Bytes())
}
emit.Int(buf.BinWriter, int64(len(publicKeys)))
emit.Opcode(buf.BinWriter, opcode.PUSHNULL)
emit.Opcodes(buf.BinWriter, opcode.PUSHNULL)
emit.Syscall(buf.BinWriter, interopnames.NeoCryptoCheckMultisigWithECDsaSecp256r1)
return buf.Bytes(), nil

View file

@ -15,6 +15,9 @@ const (
// MethodInit is a name for default initialization method.
MethodInit = "_initialize"
// MethodDeploy is a name for default method called during contract deployment.
MethodDeploy = "_deploy"
// MethodVerify is a name for default verification method.
MethodVerify = "verify"

View file

@ -21,18 +21,20 @@ func Instruction(w *io.BinWriter, op opcode.Opcode, b []byte) {
w.WriteBytes(b)
}
// Opcode emits a single VM Instruction without arguments to the given buffer.
func Opcode(w *io.BinWriter, op opcode.Opcode) {
w.WriteB(byte(op))
// Opcodes emits a single VM Instruction without arguments to the given buffer.
func Opcodes(w *io.BinWriter, ops ...opcode.Opcode) {
for _, op := range ops {
w.WriteB(byte(op))
}
}
// Bool emits a bool type the given buffer.
func Bool(w *io.BinWriter, ok bool) {
if ok {
Opcode(w, opcode.PUSHT)
Opcodes(w, opcode.PUSHT)
return
}
Opcode(w, opcode.PUSHF)
Opcodes(w, opcode.PUSHF)
Instruction(w, opcode.CONVERT, []byte{byte(stackitem.BooleanT)})
}
@ -51,15 +53,15 @@ func padRight(s int, buf []byte) []byte {
func Int(w *io.BinWriter, i int64) {
switch {
case i == -1:
Opcode(w, opcode.PUSHM1)
Opcodes(w, opcode.PUSHM1)
case i >= 0 && i < 16:
val := opcode.Opcode(int(opcode.PUSH1) - 1 + int(i))
Opcode(w, val)
Opcodes(w, val)
default:
buf := bigint.ToPreallocatedBytes(big.NewInt(i), make([]byte, 0, 32))
// l != 0 becase of switch
padSize := byte(8 - bits.LeadingZeros8(byte(len(buf)-1)))
Opcode(w, opcode.PUSHINT8+opcode.Opcode(padSize))
Opcodes(w, opcode.PUSHINT8+opcode.Opcode(padSize))
w.WriteBytes(padRight(1<<padSize, buf))
}
}
@ -83,11 +85,11 @@ func Array(w *io.BinWriter, es ...interface{}) {
w.Err = errors.New("unsupported type")
return
}
Opcode(w, opcode.PUSHNULL)
Opcodes(w, opcode.PUSHNULL)
}
}
Int(w, int64(len(es)))
Opcode(w, opcode.PACK)
Opcodes(w, opcode.PACK)
}
// String emits a string to the given buffer.

View file

@ -181,6 +181,13 @@ func TestEmitBool(t *testing.T) {
assert.Equal(t, opcode.Opcode(result[1]), opcode.PUSH0)
}
func TestEmitOpcode(t *testing.T) {
w := io.NewBufBinWriter()
Opcodes(w.BinWriter, opcode.PUSH1, opcode.NEWMAP)
result := w.Bytes()
assert.Equal(t, result, []byte{byte(opcode.PUSH1), byte(opcode.NEWMAP)})
}
func TestEmitString(t *testing.T) {
buf := io.NewBufBinWriter()
str := "City Of Zion"

View file

@ -39,7 +39,7 @@ func TestInteropHook(t *testing.T) {
buf := io.NewBufBinWriter()
emit.Syscall(buf.BinWriter, "foo")
emit.Opcode(buf.BinWriter, opcode.RET)
emit.Opcodes(buf.BinWriter, opcode.RET)
v.Load(buf.Bytes())
runVM(t, v)
assert.Equal(t, 1, v.estack.Len())
@ -773,11 +773,11 @@ func TestSerializeMapCompat(t *testing.T) {
// Create a map, push key and value, add KV to map, serialize.
buf := io.NewBufBinWriter()
emit.Opcode(buf.BinWriter, opcode.NEWMAP)
emit.Opcode(buf.BinWriter, opcode.DUP)
emit.Opcodes(buf.BinWriter, opcode.NEWMAP)
emit.Opcodes(buf.BinWriter, opcode.DUP)
emit.Bytes(buf.BinWriter, []byte("key"))
emit.Bytes(buf.BinWriter, []byte("value"))
emit.Opcode(buf.BinWriter, opcode.SETITEM)
emit.Opcodes(buf.BinWriter, opcode.SETITEM)
emit.Syscall(buf.BinWriter, interopnames.SystemBinarySerialize)
require.NoError(t, buf.Err)
@ -1654,12 +1654,12 @@ func TestSIGN(t *testing.T) {
func TestSimpleCall(t *testing.T) {
buf := io.NewBufBinWriter()
w := buf.BinWriter
emit.Opcode(w, opcode.PUSH2)
emit.Opcodes(w, opcode.PUSH2)
emit.Instruction(w, opcode.CALL, []byte{03})
emit.Opcode(w, opcode.RET)
emit.Opcode(w, opcode.PUSH10)
emit.Opcode(w, opcode.ADD)
emit.Opcode(w, opcode.RET)
emit.Opcodes(w, opcode.RET)
emit.Opcodes(w, opcode.PUSH10)
emit.Opcodes(w, opcode.ADD)
emit.Opcodes(w, opcode.RET)
result := 12
vm := load(buf.Bytes())