diff --git a/cli/contract_test.go b/cli/contract_test.go new file mode 100644 index 000000000..99c93ea4b --- /dev/null +++ b/cli/contract_test.go @@ -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()) + }) +} diff --git a/cli/executor_test.go b/cli/executor_test.go index e4198d437..53c403b36 100644 --- a/cli/executor_test.go +++ b/cli/executor_test.go @@ -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) diff --git a/cli/testdata/deploy/main.go b/cli/testdata/deploy/main.go new file mode 100644 index 000000000..f1d39360e --- /dev/null +++ b/cli/testdata/deploy/main.go @@ -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) +} diff --git a/cli/testdata/deploy/neo-go.yml b/cli/testdata/deploy/neo-go.yml new file mode 100644 index 000000000..3a9140d76 --- /dev/null +++ b/cli/testdata/deploy/neo-go.yml @@ -0,0 +1 @@ +hasstorage: true diff --git a/cli/testdata/deploy/sub/put.go b/cli/testdata/deploy/sub/put.go new file mode 100644 index 000000000..c3fe1c6ea --- /dev/null +++ b/cli/testdata/deploy/sub/put.go @@ -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) +} diff --git a/cli/testdata/deploy/updated.go b/cli/testdata/deploy/updated.go new file mode 100644 index 000000000..fb6681d0f --- /dev/null +++ b/cli/testdata/deploy/updated.go @@ -0,0 +1,6 @@ +package deploy + +// NewMethod in updated contract. +func NewMethod() int { + return 42 +} diff --git a/cli/wallet/validator.go b/cli/wallet/validator.go index 671f16d8f..16d33fdb4 100644 --- a/cli/wallet/validator.go +++ b/cli/wallet/validator.go @@ -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(), diff --git a/docs/compiler.md b/docs/compiler.md index d3166c73b..382aa0d93 100644 --- a/docs/compiler.md +++ b/docs/compiler.md @@ -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 diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index 0dcc3d262..b1015d3cd 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -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[""] = c.exceptionIndex } } - return n, hasInit + return n, initLocals, deployLocals } // countGlobals counts the global variables in the program to add diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 4c98a30f8..5a9bbebf9 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -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), } } diff --git a/pkg/compiler/debug.go b/pkg/compiler/debug.go index 4fd9a2b59..bd51eb8ee 100644 --- a/pkg/compiler/debug.go +++ b/pkg/compiler/debug.go @@ -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 { diff --git a/pkg/compiler/debug_test.go b/pkg/compiler/debug_test.go index d795a05e8..6eabaf703 100644 --- a/pkg/compiler/debug_test.go +++ b/pkg/compiler/debug_test.go @@ -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), diff --git a/pkg/compiler/func_scope.go b/pkg/compiler/func_scope.go index 41f3db76c..3e977c42e 100644 --- a/pkg/compiler/func_scope.go +++ b/pkg/compiler/func_scope.go @@ -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++ diff --git a/pkg/compiler/init_test.go b/pkg/compiler/init_test.go index 696e119f2..45e1ad68c 100644 --- a/pkg/compiler/init_test.go +++ b/pkg/compiler/init_test.go @@ -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 diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index 0b069c6b6..d3058776b 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -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}} diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index e918bde1f..5dfe24c90 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -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) diff --git a/pkg/core/interop/contract/call.go b/pkg/core/interop/contract/call.go index 6560dd2c7..cb0b4a333 100644 --- a/pkg/core/interop/contract/call.go +++ b/pkg/core/interop/contract/call.go @@ -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 diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index 686bb8d7b..f59276306 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -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 } diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index 9f67cfc93..d947fb08e 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -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() diff --git a/pkg/core/native/contract.go b/pkg/core/native/contract.go index e3ab6a18b..bd6de2605 100644 --- a/pkg/core/native/contract.go +++ b/pkg/core/native/contract.go @@ -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 diff --git a/pkg/core/native/oracle.go b/pkg/core/native/oracle.go index 4bd88e11f..2a695bb9d 100644 --- a/pkg/core/native/oracle.go +++ b/pkg/core/native/oracle.go @@ -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) diff --git a/pkg/core/native_designate_test.go b/pkg/core/native_designate_test.go index 33bbd6e57..c15098a59 100644 --- a/pkg/core/native_designate_test.go +++ b/pkg/core/native_designate_test.go @@ -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) diff --git a/pkg/core/native_oracle_test.go b/pkg/core/native_oracle_test.go index 2248c5400..1b93e6ffd 100644 --- a/pkg/core/native_oracle_test.go +++ b/pkg/core/native_oracle_test.go @@ -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 diff --git a/pkg/crypto/keys/publickey.go b/pkg/crypto/keys/publickey.go index f10f4e628..0daa0431c 100644 --- a/pkg/crypto/keys/publickey.go +++ b/pkg/crypto/keys/publickey.go @@ -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() diff --git a/pkg/rpc/client/nep5.go b/pkg/rpc/client/nep5.go index 1fc5fa89b..876dc13e8 100644 --- a/pkg/rpc/client/nep5.go +++ b/pkg/rpc/client/nep5.go @@ -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(), diff --git a/pkg/rpc/request/txBuilder.go b/pkg/rpc/request/txBuilder.go index 82d8507d8..610b9fc7b 100644 --- a/pkg/rpc/request/txBuilder.go +++ b/pkg/rpc/request/txBuilder.go @@ -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) } } diff --git a/pkg/smartcontract/contract.go b/pkg/smartcontract/contract.go index 6a88fe07b..0de8ade41 100644 --- a/pkg/smartcontract/contract.go +++ b/pkg/smartcontract/contract.go @@ -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 diff --git a/pkg/smartcontract/manifest/manifest.go b/pkg/smartcontract/manifest/manifest.go index 027c6cd5c..d7a63aeed 100644 --- a/pkg/smartcontract/manifest/manifest.go +++ b/pkg/smartcontract/manifest/manifest.go @@ -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" diff --git a/pkg/vm/emit/emit.go b/pkg/vm/emit/emit.go index a5ce828df..a4825e82f 100644 --- a/pkg/vm/emit/emit.go +++ b/pkg/vm/emit/emit.go @@ -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<