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) 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') line, err := e.Out.ReadString('\n')
require.NoError(t, err) require.NoError(t, err)
line = strings.TrimSpace(line) line = strings.TrimSpace(line)
if len(prefix) > 0 {
line = strings.TrimPrefix(line, prefix[0])
}
h, err := util.Uint256DecodeStringLE(line) h, err := util.Uint256DecodeStringLE(line)
require.NoError(t, err, "can't decode tx hash: %s", 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") gas := flags.Fixed8FromContext(ctx, "gas")
w := io.NewBufBinWriter() w := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(w.BinWriter, client.NeoContractHash, method, acc.PrivateKey().PublicKey().Bytes()) 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{ tx, err := c.CreateTxFromScript(w.Bytes(), acc, -1, int64(gas), transaction.Signer{
Account: acc.Contract.ScriptHash(), Account: acc.Contract.ScriptHash(),
Scopes: transaction.CalledByEntry, Scopes: transaction.CalledByEntry,
@ -160,7 +160,7 @@ func handleVote(ctx *cli.Context) error {
gas := flags.Fixed8FromContext(ctx, "gas") gas := flags.Fixed8FromContext(ctx, "gas")
w := io.NewBufBinWriter() w := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(w.BinWriter, client.NeoContractHash, "vote", addr.BytesBE(), pubArg) 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{ tx, err := c.CreateTxFromScript(w.Bytes(), acc, -1, int64(gas), transaction.Signer{
Account: acc.Contract.ScriptHash(), 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. Compiler provides some helpful builtins in `util` and `convert` packages.
Refer to them for detailed documentation. 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 ## Quick start
### Compiling ### Compiling

View file

@ -37,20 +37,31 @@ func (c *codegen) getIdentName(pkg string, name string) string {
} }
// traverseGlobals visits and initializes global variables. // traverseGlobals visits and initializes global variables.
// and returns number of variables initialized and // and returns number of variables initialized.
// true if any init functions were encountered. // Second return value is -1 if no `init()` functions were encountered
func (c *codegen) traverseGlobals() (int, bool) { // 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 hasDefer bool
var n int var n int
var hasInit bool initLocals := -1
deployLocals := -1
c.ForEachFile(func(f *ast.File, _ *types.Package) { c.ForEachFile(func(f *ast.File, _ *types.Package) {
n += countGlobals(f) n += countGlobals(f)
if !hasInit || !hasDefer { if initLocals == -1 || deployLocals == -1 || !hasDefer {
ast.Inspect(f, func(node ast.Node) bool { ast.Inspect(f, func(node ast.Node) bool {
switch n := node.(type) { switch n := node.(type) {
case *ast.FuncDecl: case *ast.FuncDecl:
if isInitFunc(n) { 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 return !hasDefer
case *ast.DeferStmt: case *ast.DeferStmt:
@ -64,14 +75,18 @@ func (c *codegen) traverseGlobals() (int, bool) {
if hasDefer { if hasDefer {
n++ n++
} }
if n != 0 || hasInit { if n != 0 || initLocals > -1 {
if n > 255 { if n > 255 {
c.prog.BinWriter.Err = errors.New("too many global variables") c.prog.BinWriter.Err = errors.New("too many global variables")
return 0, hasInit return 0, initLocals, deployLocals
} }
if n != 0 { if n != 0 {
emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)}) 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) { c.ForEachPackage(func(pkg *loader.PackageInfo) {
if n > 0 { if n > 0 {
for _, f := range pkg.Files { for _, f := range pkg.Files {
@ -79,10 +94,10 @@ func (c *codegen) traverseGlobals() (int, bool) {
c.convertGlobals(f, pkg.Pkg) c.convertGlobals(f, pkg.Pkg)
} }
} }
if hasInit { if initLocals > -1 {
for _, f := range pkg.Files { for _, f := range pkg.Files {
c.fillImportMap(f, pkg.Pkg) 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, // because we reuse `convertFuncDecl` for init funcs,
@ -96,7 +111,7 @@ func (c *codegen) traverseGlobals() (int, bool) {
c.globals["<exception>"] = c.exceptionIndex c.globals["<exception>"] = c.exceptionIndex
} }
} }
return n, hasInit return n, initLocals, deployLocals
} }
// countGlobals counts the global variables in the program to add // 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 specifies the end of the initialization method.
initEndOffset int 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 contains mapping from package aliases to full package names for the current file.
importMap map[string]string importMap map[string]string
@ -176,13 +178,12 @@ func (c *codegen) emitLoadConst(t types.TypeAndValue) {
func (c *codegen) emitLoadField(i int) { func (c *codegen) emitLoadField(i int) {
emit.Int(c.prog.BinWriter, int64(i)) 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) { func (c *codegen) emitStoreStructField(i int) {
emit.Int(c.prog.BinWriter, int64(i)) emit.Int(c.prog.BinWriter, int64(i))
emit.Opcode(c.prog.BinWriter, opcode.ROT) emit.Opcodes(c.prog.BinWriter, opcode.ROT, opcode.SETITEM)
emit.Opcode(c.prog.BinWriter, opcode.SETITEM)
} }
// getVarIndex returns variable type and position in corresponding slot, // 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) { func (c *codegen) emitLoadByIndex(t varType, i int) {
base, _ := getBaseOpcode(t) base, _ := getBaseOpcode(t)
if i < 7 { if i < 7 {
emit.Opcode(c.prog.BinWriter, base+opcode.Opcode(i)) emit.Opcodes(c.prog.BinWriter, base+opcode.Opcode(i))
} else { } else {
emit.Instruction(c.prog.BinWriter, base+7, []byte{byte(i)}) 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. // emitStoreVar stores top value from the evaluation stack in the specified variable.
func (c *codegen) emitStoreVar(pkg string, name string) { func (c *codegen) emitStoreVar(pkg string, name string) {
if name == "_" { if name == "_" {
emit.Opcode(c.prog.BinWriter, opcode.DROP) emit.Opcodes(c.prog.BinWriter, opcode.DROP)
return return
} }
t, i := c.getVarIndex(pkg, name) 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) { func (c *codegen) emitStoreByIndex(t varType, i int) {
_, base := getBaseOpcode(t) _, base := getBaseOpcode(t)
if i < 7 { if i < 7 {
emit.Opcode(c.prog.BinWriter, base+opcode.Opcode(i)) emit.Opcodes(c.prog.BinWriter, base+opcode.Opcode(i))
} else { } else {
emit.Instruction(c.prog.BinWriter, base+7, []byte{byte(i)}) 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: case info&types.IsBoolean != 0:
emit.Bool(c.prog.BinWriter, false) emit.Bool(c.prog.BinWriter, false)
default: default:
emit.Opcode(c.prog.BinWriter, opcode.PUSHNULL) emit.Opcodes(c.prog.BinWriter, opcode.PUSHNULL)
} }
case *types.Struct: case *types.Struct:
num := t.NumFields() num := t.NumFields()
emit.Int(c.prog.BinWriter, int64(num)) 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++ { 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)) emit.Int(c.prog.BinWriter, int64(i))
c.emitDefault(t.Field(i).Type()) c.emitDefault(t.Field(i).Type())
emit.Opcode(c.prog.BinWriter, opcode.SETITEM) emit.Opcodes(c.prog.BinWriter, opcode.SETITEM)
} }
default: 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 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 { ast.Inspect(f, func(node ast.Node) bool {
switch n := node.(type) { switch n := node.(type) {
case *ast.FuncDecl: case *ast.FuncDecl:
if isInitFunc(n) { if isInitFunc(n) {
if seenBefore {
cnt, _ := countLocals(n)
c.clearSlots(cnt)
seenBefore = true
}
c.convertFuncDecl(f, n, pkg) c.convertFuncDecl(f, n, pkg)
} }
case *ast.GenDecl: case *ast.GenDecl:
@ -314,6 +327,39 @@ func (c *codegen) convertInitFuncs(f *ast.File, pkg *types.Package) {
} }
return true 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) { 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 ok, isLambda bool
) )
isInit := isInitFunc(decl) isInit := isInitFunc(decl)
if isInit { isDeploy := isDeployFunc(decl)
if isInit || isDeploy {
f = c.newFuncScope(decl, c.newLabel()) f = c.newFuncScope(decl, c.newLabel())
} else { } else {
f, ok = c.funcs[c.getFuncNameFromDecl("", decl)] 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 // All globals copied into the scope of the function need to be added
// to the stack size of the function. // to the stack size of the function.
sizeLoc := f.countLocals() if !isInit && !isDeploy {
if sizeLoc > 255 { sizeLoc := f.countLocals()
c.prog.Err = errors.New("maximum of 255 local variables is allowed") if sizeLoc > 255 {
} c.prog.Err = errors.New("maximum of 255 local variables is allowed")
sizeArg := f.countArgs() }
if sizeArg > 255 { sizeArg := f.countArgs()
c.prog.Err = errors.New("maximum of 255 local variables is allowed") 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 sizeLoc != 0 || sizeArg != 0 {
emit.Instruction(c.prog.BinWriter, opcode.INITSLOT, []byte{byte(sizeLoc), byte(sizeArg)})
}
} }
f.vars.newScope() 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, // If we have reached the end of the function without encountering `return` statement,
// we should clean alt.stack manually. // we should clean alt.stack manually.
// This can be the case with void and named-return functions. // This can be the case with void and named-return functions.
if !isInit && !lastStmtIsReturn(decl) { if !isInit && !isDeploy && !lastStmtIsReturn(decl) {
c.saveSequencePoint(decl.Body) 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) 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.X)
ast.Walk(c, t.Index) ast.Walk(c, t.Index)
emit.Opcode(c.prog.BinWriter, opcode.ROT) emit.Opcodes(c.prog.BinWriter, opcode.ROT, opcode.SETITEM)
emit.Opcode(c.prog.BinWriter, opcode.SETITEM)
} }
} }
return nil return nil
@ -527,19 +575,16 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
if n.Low != nil { if n.Low != nil {
ast.Walk(c, n.Low) ast.Walk(c, n.Low)
} else { } else {
emit.Opcode(c.prog.BinWriter, opcode.PUSH0) emit.Opcodes(c.prog.BinWriter, opcode.PUSH0)
} }
if n.High != nil { if n.High != nil {
ast.Walk(c, n.High) ast.Walk(c, n.High)
} else { } else {
emit.Opcode(c.prog.BinWriter, opcode.OVER) emit.Opcodes(c.prog.BinWriter, opcode.OVER, opcode.SIZE)
emit.Opcode(c.prog.BinWriter, opcode.SIZE)
} }
emit.Opcode(c.prog.BinWriter, opcode.OVER) emit.Opcodes(c.prog.BinWriter, opcode.OVER, opcode.SUB, opcode.SUBSTR)
emit.Opcode(c.prog.BinWriter, opcode.SUB)
emit.Opcode(c.prog.BinWriter, opcode.SUBSTR)
return nil return nil
@ -574,7 +619,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
c.processDefers() c.processDefers()
c.saveSequencePoint(n) c.saveSequencePoint(n)
emit.Opcode(c.prog.BinWriter, opcode.RET) emit.Opcodes(c.prog.BinWriter, opcode.RET)
return nil return nil
case *ast.IfStmt: 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` if l := len(cc.List); l != 0 { // if not `default`
for j := range cc.List { 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]) ast.Walk(c, cc.List[j])
emit.Opcode(c.prog.BinWriter, eqOpcode) emit.Opcodes(c.prog.BinWriter, eqOpcode)
if j == l-1 { if j == l-1 {
emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOTL, lEnd) emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOTL, lEnd)
} else { } else {
@ -691,7 +736,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
if tv := c.typeAndValueOf(n); tv.Value != nil { if tv := c.typeAndValueOf(n); tv.Value != nil {
c.emitLoadConst(tv) c.emitLoadConst(tv)
} else if n.Name == "nil" { } else if n.Name == "nil" {
emit.Opcode(c.prog.BinWriter, opcode.PUSHNULL) emit.Opcodes(c.prog.BinWriter, opcode.PUSHNULL)
} else { } else {
c.emitLoadVar("", n.Name) c.emitLoadVar("", n.Name)
} }
@ -714,7 +759,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
ast.Walk(c, n.Elts[i]) ast.Walk(c, n.Elts[i])
} }
emit.Int(c.prog.BinWriter, int64(ln)) emit.Int(c.prog.BinWriter, int64(ln))
emit.Opcode(c.prog.BinWriter, opcode.PACK) emit.Opcodes(c.prog.BinWriter, opcode.PACK)
} }
return nil return nil
@ -786,12 +831,9 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
if ok && !isInteropPath(typ.String()) { if ok && !isInteropPath(typ.String()) {
// To clone struct fields we create a new array and append struct to it. // To clone struct fields we create a new array and append struct to it.
// This way even non-pointer struct fields will be copied. // This way even non-pointer struct fields will be copied.
emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY0) emit.Opcodes(c.prog.BinWriter, opcode.NEWARRAY0,
emit.Opcode(c.prog.BinWriter, opcode.DUP) opcode.DUP, opcode.ROT, opcode.APPEND,
emit.Opcode(c.prog.BinWriter, opcode.ROT) opcode.PUSH0, opcode.PICKITEM)
emit.Opcode(c.prog.BinWriter, opcode.APPEND)
emit.Opcode(c.prog.BinWriter, opcode.PUSH0)
emit.Opcode(c.prog.BinWriter, opcode.PICKITEM)
} }
} }
// Do not swap for builtin functions. // 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 varSize := len(n.Args) - typ.Params().Len() + 1
c.emitReverse(varSize) c.emitReverse(varSize)
emit.Int(c.prog.BinWriter, int64(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 numArgs -= varSize - 1
} }
c.emitReverse(numArgs) c.emitReverse(numArgs)
@ -822,11 +864,11 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
c.emitConvert(stackitem.ByteArrayT) c.emitConvert(stackitem.ByteArrayT)
} else if isFunc { } else if isFunc {
c.emitLoadVar("", name) c.emitLoadVar("", name)
emit.Opcode(c.prog.BinWriter, opcode.CALLA) emit.Opcodes(c.prog.BinWriter, opcode.CALLA)
} }
case isLiteral: case isLiteral:
ast.Walk(c, n.Fun) ast.Walk(c, n.Fun)
emit.Opcode(c.prog.BinWriter, opcode.CALLA) emit.Opcodes(c.prog.BinWriter, opcode.CALLA)
case isSyscall(f): case isSyscall(f):
c.convertSyscall(n, f.pkg.Name(), f.name) c.convertSyscall(n, f.pkg.Name(), f.name)
default: default:
@ -843,7 +885,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
sz = f.Results().Len() sz = f.Results().Len()
} }
for i := 0; i < sz; i++ { 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: case token.ADD:
// +10 == 10, no need to do anything in this case // +10 == 10, no need to do anything in this case
case token.SUB: case token.SUB:
emit.Opcode(c.prog.BinWriter, opcode.NEGATE) emit.Opcodes(c.prog.BinWriter, opcode.NEGATE)
case token.NOT: case token.NOT:
emit.Opcode(c.prog.BinWriter, opcode.NOT) emit.Opcodes(c.prog.BinWriter, opcode.NOT)
case token.XOR: case token.XOR:
emit.Opcode(c.prog.BinWriter, opcode.INVERT) emit.Opcodes(c.prog.BinWriter, opcode.INVERT)
default: default:
c.prog.Err = fmt.Errorf("invalid unary operator: %s", n.Op) c.prog.Err = fmt.Errorf("invalid unary operator: %s", n.Op)
return nil return nil
@ -937,7 +979,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
// This will load local whatever X is. // This will load local whatever X is.
ast.Walk(c, n.X) ast.Walk(c, n.X)
ast.Walk(c, n.Index) 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 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 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. // 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) _, isMap := c.typeOf(n.X).Underlying().(*types.Map)
emit.Opcode(c.prog.BinWriter, opcode.DUP) emit.Opcodes(c.prog.BinWriter, opcode.DUP)
if isMap { if isMap {
emit.Opcode(c.prog.BinWriter, opcode.KEYS) emit.Opcodes(c.prog.BinWriter, opcode.KEYS, opcode.DUP)
emit.Opcode(c.prog.BinWriter, opcode.DUP)
} }
emit.Opcode(c.prog.BinWriter, opcode.SIZE) emit.Opcodes(c.prog.BinWriter, opcode.SIZE, opcode.PUSH0)
emit.Opcode(c.prog.BinWriter, opcode.PUSH0)
stackSize := 3 // slice, len(slice), index stackSize := 3 // slice, len(slice), index
if isMap { if isMap {
@ -1064,8 +1104,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
c.pushStackLabel(label, stackSize) c.pushStackLabel(label, stackSize)
c.setLabel(start) c.setLabel(start)
emit.Opcode(c.prog.BinWriter, opcode.OVER) emit.Opcodes(c.prog.BinWriter, opcode.OVER, opcode.OVER)
emit.Opcode(c.prog.BinWriter, opcode.OVER)
emit.Jmp(c.prog.BinWriter, opcode.JMPLEL, end) emit.Jmp(c.prog.BinWriter, opcode.JMPLEL, end)
var keyLoaded bool var keyLoaded bool
@ -1074,11 +1113,11 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
if isMap { if isMap {
c.rangeLoadKey() c.rangeLoadKey()
if needValue { if needValue {
emit.Opcode(c.prog.BinWriter, opcode.DUP) emit.Opcodes(c.prog.BinWriter, opcode.DUP)
keyLoaded = true keyLoaded = true
} }
} else { } else {
emit.Opcode(c.prog.BinWriter, opcode.DUP) emit.Opcodes(c.prog.BinWriter, opcode.DUP)
} }
c.emitStoreVar("", n.Key.(*ast.Ident).Name) c.emitStoreVar("", n.Key.(*ast.Ident).Name)
} }
@ -1089,9 +1128,10 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
if isMap { if isMap {
// we have loaded only key from key array, now load value // we have loaded only key from key array, now load value
emit.Int(c.prog.BinWriter, 4) emit.Int(c.prog.BinWriter, 4)
emit.Opcode(c.prog.BinWriter, opcode.PICK) // load map itself (+1 because key was pushed) emit.Opcodes(c.prog.BinWriter,
emit.Opcode(c.prog.BinWriter, opcode.SWAP) // key should be on top opcode.PICK, // load map itself (+1 because key was pushed)
emit.Opcode(c.prog.BinWriter, opcode.PICKITEM) opcode.SWAP, // key should be on top
opcode.PICKITEM)
} }
c.emitStoreVar("", n.Value.(*ast.Ident).Name) c.emitStoreVar("", n.Value.(*ast.Ident).Name)
} }
@ -1100,7 +1140,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
c.setLabel(post) 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) emit.Jmp(c.prog.BinWriter, opcode.JMPL, start)
c.setLabel(end) c.setLabel(end)
@ -1164,16 +1204,17 @@ func (c *codegen) processDefers() {
c.setLabel(before) c.setLabel(before)
emit.Int(c.prog.BinWriter, 0) emit.Int(c.prog.BinWriter, 0)
c.emitStoreByIndex(varLocal, c.scope.finallyProcessedIndex) c.emitStoreByIndex(varLocal, c.scope.finallyProcessedIndex)
emit.Opcode(c.prog.BinWriter, opcode.ENDFINALLY) emit.Opcodes(c.prog.BinWriter, opcode.ENDFINALLY)
c.setLabel(after) c.setLabel(after)
} }
} }
func (c *codegen) rangeLoadKey() { func (c *codegen) rangeLoadKey() {
emit.Int(c.prog.BinWriter, 2) emit.Int(c.prog.BinWriter, 2)
emit.Opcode(c.prog.BinWriter, opcode.PICK) // load keys emit.Opcodes(c.prog.BinWriter,
emit.Opcode(c.prog.BinWriter, opcode.OVER) // load index in key array opcode.PICK, // load keys
emit.Opcode(c.prog.BinWriter, opcode.PICKITEM) opcode.OVER, // load index in key array
opcode.PICKITEM)
} }
func isFallthroughStmt(c ast.Node) bool { func isFallthroughStmt(c ast.Node) bool {
@ -1230,11 +1271,11 @@ func (c *codegen) emitBinaryExpr(n *ast.BinaryExpr, needJump bool, cond bool, jm
return return
} else if arg := c.getCompareWithNilArg(n); arg != nil { } else if arg := c.getCompareWithNilArg(n); arg != nil {
ast.Walk(c, arg) ast.Walk(c, arg)
emit.Opcode(c.prog.BinWriter, opcode.ISNULL) emit.Opcodes(c.prog.BinWriter, opcode.ISNULL)
if needJump { if needJump {
c.emitJumpOnCondition(cond == (n.Op == token.EQL), jmpLabel) c.emitJumpOnCondition(cond == (n.Op == token.EQL), jmpLabel)
} else if n.Op == token.NEQ { } else if n.Op == token.NEQ {
emit.Opcode(c.prog.BinWriter, opcode.NOT) emit.Opcodes(c.prog.BinWriter, opcode.NOT)
} }
return return
} }
@ -1299,14 +1340,13 @@ func (c *codegen) dropStackLabel() {
func (c *codegen) dropItems(n int) { func (c *codegen) dropItems(n int) {
if n < 4 { if n < 4 {
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
emit.Opcode(c.prog.BinWriter, opcode.DROP) emit.Opcodes(c.prog.BinWriter, opcode.DROP)
} }
return return
} }
emit.Int(c.prog.BinWriter, int64(n)) emit.Int(c.prog.BinWriter, int64(n))
emit.Opcode(c.prog.BinWriter, opcode.PACK) emit.Opcodes(c.prog.BinWriter, opcode.PACK, opcode.DROP)
emit.Opcode(c.prog.BinWriter, opcode.DROP)
} }
// emitReverse reverses top num items of the stack. // emitReverse reverses top num items of the stack.
@ -1314,14 +1354,14 @@ func (c *codegen) emitReverse(num int) {
switch num { switch num {
case 0, 1: case 0, 1:
case 2: case 2:
emit.Opcode(c.prog.BinWriter, opcode.SWAP) emit.Opcodes(c.prog.BinWriter, opcode.SWAP)
case 3: case 3:
emit.Opcode(c.prog.BinWriter, opcode.REVERSE3) emit.Opcodes(c.prog.BinWriter, opcode.REVERSE3)
case 4: case 4:
emit.Opcode(c.prog.BinWriter, opcode.REVERSE4) emit.Opcodes(c.prog.BinWriter, opcode.REVERSE4)
default: default:
emit.Int(c.prog.BinWriter, int64(num)) 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 // 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. // 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. // 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 { if src.High != nil {
ast.Walk(c, src.High) ast.Walk(c, src.High)
} else { } else {
emit.Opcode(c.prog.BinWriter, opcode.DUP) emit.Opcodes(c.prog.BinWriter, opcode.DUP, opcode.SIZE)
emit.Opcode(c.prog.BinWriter, opcode.SIZE)
} }
if src.Low != nil { if src.Low != nil {
ast.Walk(c, src.Low) ast.Walk(c, src.Low)
@ -1427,17 +1466,13 @@ func (c *codegen) emitSliceHelper(e ast.Expr) {
} }
default: default:
ast.Walk(c, src) ast.Walk(c, src)
emit.Opcode(c.prog.BinWriter, opcode.DUP) emit.Opcodes(c.prog.BinWriter, opcode.DUP, opcode.SIZE)
emit.Opcode(c.prog.BinWriter, opcode.SIZE)
emit.Int(c.prog.BinWriter, 0) emit.Int(c.prog.BinWriter, 0)
} }
if !hasLowIndex { if !hasLowIndex {
emit.Opcode(c.prog.BinWriter, opcode.SWAP) emit.Opcodes(c.prog.BinWriter, opcode.SWAP)
} else { } else {
emit.Opcode(c.prog.BinWriter, opcode.DUP) emit.Opcodes(c.prog.BinWriter, opcode.DUP, opcode.ROT, opcode.SWAP, opcode.SUB)
emit.Opcode(c.prog.BinWriter, opcode.ROT)
emit.Opcode(c.prog.BinWriter, opcode.SWAP)
emit.Opcode(c.prog.BinWriter, opcode.SUB)
} }
} }
@ -1456,22 +1491,21 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) {
c.emitSliceHelper(expr.Args[0]) c.emitSliceHelper(expr.Args[0])
c.emitSliceHelper(expr.Args[1]) c.emitSliceHelper(expr.Args[1])
emit.Int(c.prog.BinWriter, 3) emit.Int(c.prog.BinWriter, 3)
emit.Opcode(c.prog.BinWriter, opcode.ROLL) emit.Opcodes(c.prog.BinWriter, opcode.ROLL, opcode.MIN)
emit.Opcode(c.prog.BinWriter, opcode.MIN)
if !c.scope.voidCalls[expr] { if !c.scope.voidCalls[expr] {
// insert top item to the bottom of MEMCPY args, so that it is left on stack // 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.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.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": case "make":
typ := c.typeOf(expr.Args[0]) typ := c.typeOf(expr.Args[0])
switch { switch {
case isMap(typ): case isMap(typ):
emit.Opcode(c.prog.BinWriter, opcode.NEWMAP) emit.Opcodes(c.prog.BinWriter, opcode.NEWMAP)
default: default:
if len(expr.Args) == 3 { if len(expr.Args) == 3 {
c.prog.Err = fmt.Errorf("`make()` with a capacity argument is not supported") 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]) ast.Walk(c, expr.Args[1])
if isByteSlice(typ) { if isByteSlice(typ) {
emit.Opcode(c.prog.BinWriter, opcode.NEWBUFFER) emit.Opcodes(c.prog.BinWriter, opcode.NEWBUFFER)
} else { } else {
neoT := toNeoType(typ.(*types.Slice).Elem()) neoT := toNeoType(typ.(*types.Slice).Elem())
emit.Instruction(c.prog.BinWriter, opcode.NEWARRAYT, []byte{byte(neoT)}) emit.Instruction(c.prog.BinWriter, opcode.NEWARRAYT, []byte{byte(neoT)})
} }
} }
case "len": case "len":
emit.Opcode(c.prog.BinWriter, opcode.DUP) emit.Opcodes(c.prog.BinWriter, opcode.DUP, opcode.ISNULL)
emit.Opcode(c.prog.BinWriter, opcode.ISNULL)
emit.Instruction(c.prog.BinWriter, opcode.JMPIF, []byte{2 + 1 + 2}) 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.Instruction(c.prog.BinWriter, opcode.JMP, []byte{2 + 1 + 1})
emit.Opcode(c.prog.BinWriter, opcode.DROP) emit.Opcodes(c.prog.BinWriter, opcode.DROP, opcode.PUSH0)
emit.Opcode(c.prog.BinWriter, opcode.PUSH0)
case "append": case "append":
arg := expr.Args[0] arg := expr.Args[0]
typ := c.typeInfo.Types[arg].Type typ := c.typeInfo.Types[arg].Type
c.emitReverse(len(expr.Args)) c.emitReverse(len(expr.Args))
emit.Opcode(c.prog.BinWriter, opcode.DUP) emit.Opcodes(c.prog.BinWriter, opcode.DUP, opcode.ISNULL)
emit.Opcode(c.prog.BinWriter, opcode.ISNULL)
emit.Instruction(c.prog.BinWriter, opcode.JMPIFNOT, []byte{2 + 3}) emit.Instruction(c.prog.BinWriter, opcode.JMPIFNOT, []byte{2 + 3})
if isByteSlice(typ) { if isByteSlice(typ) {
emit.Opcode(c.prog.BinWriter, opcode.DROP) emit.Opcodes(c.prog.BinWriter, opcode.DROP, opcode.PUSH0, opcode.NEWBUFFER)
emit.Opcode(c.prog.BinWriter, opcode.PUSH0)
emit.Opcode(c.prog.BinWriter, opcode.NEWBUFFER)
} else { } else {
emit.Opcode(c.prog.BinWriter, opcode.DROP) emit.Opcodes(c.prog.BinWriter, opcode.DROP, opcode.NEWARRAY0, opcode.NOP)
emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY0)
emit.Opcode(c.prog.BinWriter, opcode.NOP)
} }
// Jump target. // Jump target.
for range expr.Args[1:] { for range expr.Args[1:] {
if isByteSlice(typ) { if isByteSlice(typ) {
emit.Opcode(c.prog.BinWriter, opcode.SWAP) emit.Opcodes(c.prog.BinWriter, opcode.SWAP, opcode.CAT)
emit.Opcode(c.prog.BinWriter, opcode.CAT)
} else { } else {
emit.Opcode(c.prog.BinWriter, opcode.DUP) emit.Opcodes(c.prog.BinWriter, opcode.DUP, opcode.ROT, opcode.APPEND)
emit.Opcode(c.prog.BinWriter, opcode.ROT)
emit.Opcode(c.prog.BinWriter, opcode.APPEND)
} }
} }
case "panic": case "panic":
emit.Opcode(c.prog.BinWriter, opcode.THROW) emit.Opcodes(c.prog.BinWriter, opcode.THROW)
case "recover": case "recover":
if !c.scope.voidCalls[expr] { if !c.scope.voidCalls[expr] {
c.emitLoadByIndex(varGlobal, c.exceptionIndex) c.emitLoadByIndex(varGlobal, c.exceptionIndex)
} }
emit.Opcode(c.prog.BinWriter, opcode.PUSHNULL) emit.Opcodes(c.prog.BinWriter, opcode.PUSHNULL)
c.emitStoreByIndex(varGlobal, c.exceptionIndex) c.emitStoreByIndex(varGlobal, c.exceptionIndex)
case "delete": case "delete":
emit.Opcode(c.prog.BinWriter, opcode.REMOVE) emit.Opcodes(c.prog.BinWriter, opcode.REMOVE)
case "ToInteger", "ToByteArray", "ToBool": case "ToInteger", "ToByteArray", "ToBool":
typ := stackitem.IntegerT typ := stackitem.IntegerT
switch name { switch name {
@ -1544,9 +1568,9 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) {
c.prog.Err = errors.New("`Remove` supports only non-byte slices") c.prog.Err = errors.New("`Remove` supports only non-byte slices")
return return
} }
emit.Opcode(c.prog.BinWriter, opcode.REMOVE) emit.Opcodes(c.prog.BinWriter, opcode.REMOVE)
case "Equals": case "Equals":
emit.Opcode(c.prog.BinWriter, opcode.EQUAL) emit.Opcodes(c.prog.BinWriter, opcode.EQUAL)
case "FromAddress": case "FromAddress":
// We can be sure that this is a ast.BasicLit just containing a simple // 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 // 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) emit.Bytes(c.prog.BinWriter, buf)
c.emitConvert(stackitem.BufferT) c.emitConvert(stackitem.BufferT)
for _, i := range varIndices { 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)) emit.Int(c.prog.BinWriter, int64(i))
ast.Walk(c, lit.Elts[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) { 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 { for i := range lit.Elts {
elem := lit.Elts[i].(*ast.KeyValueExpr) 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.Key)
ast.Walk(c, elem.Value) 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 return
} }
emit.Opcode(c.prog.BinWriter, opcode.NOP) emit.Opcodes(c.prog.BinWriter, opcode.NOP)
emit.Int(c.prog.BinWriter, int64(strct.NumFields())) emit.Int(c.prog.BinWriter, int64(strct.NumFields()))
if ptr { if ptr {
emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY) emit.Opcodes(c.prog.BinWriter, opcode.NEWARRAY)
} else { } else {
emit.Opcode(c.prog.BinWriter, opcode.NEWSTRUCT) emit.Opcodes(c.prog.BinWriter, opcode.NEWSTRUCT)
} }
keyedLit := len(lit.Elts) > 0 keyedLit := len(lit.Elts) > 0
@ -1665,7 +1689,7 @@ func (c *codegen) convertStruct(lit *ast.CompositeLit, ptr bool) {
sField := strct.Field(i) sField := strct.Field(i)
var initialized bool var initialized bool
emit.Opcode(c.prog.BinWriter, opcode.DUP) emit.Opcodes(c.prog.BinWriter, opcode.DUP)
emit.Int(c.prog.BinWriter, int64(i)) emit.Int(c.prog.BinWriter, int64(i))
if !keyedLit { if !keyedLit {
@ -1689,7 +1713,7 @@ func (c *codegen) convertStruct(lit *ast.CompositeLit, ptr bool) {
if !initialized { if !initialized {
c.emitDefault(sField.Type()) 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 c.prog.Err = err
return return
} }
emit.Opcode(c.prog.BinWriter, op) emit.Opcodes(c.prog.BinWriter, op)
} }
func convertToken(tok token.Token, typ types.Type) (opcode.Opcode, error) { 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. // Bring all imported functions into scope.
c.ForEachFile(c.resolveFuncDecls) c.ForEachFile(c.resolveFuncDecls)
n, hasInit := c.traverseGlobals() n, initLocals, deployLocals := c.traverseGlobals()
hasInit := initLocals > -1
if n > 0 || hasInit { if n > 0 || hasInit {
emit.Opcode(c.prog.BinWriter, opcode.RET)
c.initEndOffset = c.prog.Len() 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. // 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 // Don't convert the function if it's not used. This will save a lot
// of bytecode space. // of bytecode space.
name := c.getFuncNameFromDecl(pkg.Path(), n) 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) c.convertFuncDecl(f, n, pkg)
} }
} }
@ -1847,6 +1880,9 @@ func newCodegen(info *buildInfo, pkg *loader.PackageInfo) *codegen {
constMap: map[string]types.TypeAndValue{}, constMap: map[string]types.TypeAndValue{},
docIndex: map[string]int{}, docIndex: map[string]int{},
initEndOffset: -1,
deployEndOffset: -1,
sequencePoints: make(map[string][]DebugSeqPoint), sequencePoints: make(map[string][]DebugSeqPoint),
} }
} }

View file

@ -134,6 +134,27 @@ func (c *codegen) emitDebugInfo(contract []byte) *DebugInfo {
SeqPoints: c.sequencePoints["init"], 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 { for name, scope := range c.funcs {
m := c.methodInfoFromScope(name, scope) m := c.methodInfoFromScope(name, scope)
if m.Range.Start == m.Range.End { if m.Range.Start == m.Range.End {

View file

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

View file

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

View file

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

View file

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

View file

@ -6,6 +6,7 @@ import (
"strings" "strings"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
@ -44,21 +45,28 @@ func callExInternal(ic *interop.Context, h []byte, name string, args []stackitem
if strings.HasPrefix(name, "_") { if strings.HasPrefix(name, "_") {
return errors.New("invalid method name (starts with '_')") 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()) curr, err := ic.DAO.GetContractState(ic.VM.GetCurrentScriptHash())
if err == nil { if err == nil {
if !curr.Manifest.CanCall(&cs.Manifest, name) { if !curr.Manifest.CanCall(&cs.Manifest, name) {
return errors.New("disallowed method call") 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) { if len(args) != len(md.Parameters) {
return fmt.Errorf("invalid argument count: %d (expected %d)", 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.Invocations[u]++
ic.VM.LoadScriptWithHash(cs.Script, u, ic.VM.Context().GetCallFlags()&f) ic.VM.LoadScriptWithHash(cs.Script, u, ic.VM.Context().GetCallFlags()&f)
var isNative bool var isNative bool

View file

@ -9,8 +9,10 @@ import (
"github.com/mr-tron/base58" "github.com/mr-tron/base58"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "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/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "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) return fmt.Errorf("cannot convert contract to stack item: %w", err)
} }
ic.VM.Estack().PushVal(cs) ic.VM.Estack().PushVal(cs)
return nil return callDeploy(ic, newcontract, false)
} }
func checkNonEmpty(b []byte, max int) error { 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 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"
"github.com/nspcc-dev/neo-go/pkg/core/interop/callback" "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/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/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "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/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -365,23 +368,49 @@ func TestStoragePut(t *testing.T) {
// getTestContractState returns 2 contracts second of which is allowed to call the first. // getTestContractState returns 2 contracts second of which is allowed to call the first.
func getTestContractState() (*state.Contract, *state.Contract) { func getTestContractState() (*state.Contract, *state.Contract) {
script := []byte{ w := io.NewBufBinWriter()
byte(opcode.ABORT), // abort if no offset was provided emit.Opcodes(w.BinWriter, opcode.ABORT)
byte(opcode.ADD), byte(opcode.RET), addOff := w.Len()
byte(opcode.PUSH7), byte(opcode.RET), emit.Opcodes(w.BinWriter, opcode.ADD, opcode.RET)
byte(opcode.DROP), byte(opcode.RET), ret7Off := w.Len()
byte(opcode.INITSSLOT), 1, byte(opcode.PUSH3), byte(opcode.STSFLD0), byte(opcode.RET), emit.Opcodes(w.BinWriter, opcode.PUSH7, opcode.RET)
byte(opcode.LDSFLD0), byte(opcode.ADD), byte(opcode.RET), dropOff := w.Len()
byte(opcode.PUSH1), byte(opcode.PUSH2), byte(opcode.RET), emit.Opcodes(w.BinWriter, opcode.DROP, opcode.RET)
byte(opcode.RET), initOff := w.Len()
byte(opcode.LDSFLD0), byte(opcode.SUB), byte(opcode.CONVERT), byte(stackitem.BooleanT), byte(opcode.RET), 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) h := hash.Hash160(script)
m := manifest.NewManifest(h) m := manifest.NewManifest(h)
m.Features = smartcontract.HasStorage
m.ABI.Methods = []manifest.Method{ m.ABI.Methods = []manifest.Method{
{ {
Name: "add", Name: "add",
Offset: 1, Offset: addOff,
Parameters: []manifest.Parameter{ Parameters: []manifest.Parameter{
manifest.NewParameter("addend1", smartcontract.IntegerType), manifest.NewParameter("addend1", smartcontract.IntegerType),
manifest.NewParameter("addend2", smartcontract.IntegerType), manifest.NewParameter("addend2", smartcontract.IntegerType),
@ -390,23 +419,23 @@ func getTestContractState() (*state.Contract, *state.Contract) {
}, },
{ {
Name: "ret7", Name: "ret7",
Offset: 3, Offset: ret7Off,
Parameters: []manifest.Parameter{}, Parameters: []manifest.Parameter{},
ReturnType: smartcontract.IntegerType, ReturnType: smartcontract.IntegerType,
}, },
{ {
Name: "drop", Name: "drop",
Offset: 5, Offset: dropOff,
ReturnType: smartcontract.VoidType, ReturnType: smartcontract.VoidType,
}, },
{ {
Name: manifest.MethodInit, Name: manifest.MethodInit,
Offset: 7, Offset: initOff,
ReturnType: smartcontract.VoidType, ReturnType: smartcontract.VoidType,
}, },
{ {
Name: "add3", Name: "add3",
Offset: 12, Offset: add3Off,
Parameters: []manifest.Parameter{ Parameters: []manifest.Parameter{
manifest.NewParameter("addend", smartcontract.IntegerType), manifest.NewParameter("addend", smartcontract.IntegerType),
}, },
@ -414,19 +443,40 @@ func getTestContractState() (*state.Contract, *state.Contract) {
}, },
{ {
Name: "invalidReturn", Name: "invalidReturn",
Offset: 15, Offset: invalidRetOff,
ReturnType: smartcontract.IntegerType, ReturnType: smartcontract.IntegerType,
}, },
{ {
Name: "justReturn", Name: "justReturn",
Offset: 18, Offset: justRetOff,
ReturnType: smartcontract.IntegerType, ReturnType: smartcontract.IntegerType,
}, },
{ {
Name: manifest.MethodVerify, Name: manifest.MethodVerify,
Offset: 19, Offset: verifyOff,
ReturnType: smartcontract.BoolType, 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{ cs := &state.Contract{
Script: script, Script: script,
@ -442,6 +492,7 @@ func getTestContractState() (*state.Contract, *state.Contract) {
perm.Methods.Add("add3") perm.Methods.Add("add3")
perm.Methods.Add("invalidReturn") perm.Methods.Add("invalidReturn")
perm.Methods.Add("justReturn") perm.Methods.Add("justReturn")
perm.Methods.Add("getValue")
m.Permissions = append(m.Permissions, *perm) m.Permissions = append(m.Permissions, *perm)
return cs, &state.Contract{ 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) { func TestContractGetCallFlags(t *testing.T) {
v, ic, bc := createVM(t) v, ic, bc := createVM(t)
defer bc.Close() defer bc.Close()

View file

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

View file

@ -56,7 +56,7 @@ func init() {
w.Reset() w.Reset()
emit.Int(w.BinWriter, 0) emit.Int(w.BinWriter, 0)
emit.Opcode(w.BinWriter, opcode.NEWARRAY) emit.Opcodes(w.BinWriter, opcode.NEWARRAY)
emit.String(w.BinWriter, "finish") emit.String(w.BinWriter, "finish")
emit.Bytes(w.BinWriter, h.BytesBE()) emit.Bytes(w.BinWriter, h.BytesBE())
emit.Syscall(w.BinWriter, interopnames.SystemContractCall) 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.Bytes(w.BinWriter, pub.Bytes())
} }
emit.Int(w.BinWriter, int64(len(nodes))) 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, int64(r))
emit.Int(w.BinWriter, 2) emit.Int(w.BinWriter, 2)
emit.Opcode(w.BinWriter, opcode.PACK) emit.Opcodes(w.BinWriter, opcode.PACK)
emit.String(w.BinWriter, "designateAsRole") emit.String(w.BinWriter, "designateAsRole")
emit.AppCall(w.BinWriter, bc.contracts.Designate.Hash) emit.AppCall(w.BinWriter, bc.contracts.Designate.Hash)
require.NoError(t, w.Err) require.NoError(t, w.Err)

View file

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

View file

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

View file

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

View file

@ -107,7 +107,7 @@ func expandArrayIntoScript(script *io.BinWriter, slice []Param) error {
return err return err
} }
emit.Int(script, int64(len(val))) emit.Int(script, int64(len(val)))
emit.Opcode(script, opcode.PACK) emit.Opcodes(script, opcode.PACK)
default: default:
return fmt.Errorf("parameter type %v is not supported", fp.Type) 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 return nil, err
} }
emit.Int(script.BinWriter, int64(len(slice))) 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.Bytes(buf.BinWriter, pubKey.Bytes())
} }
emit.Int(buf.BinWriter, int64(len(publicKeys))) 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) emit.Syscall(buf.BinWriter, interopnames.NeoCryptoCheckMultisigWithECDsaSecp256r1)
return buf.Bytes(), nil return buf.Bytes(), nil

View file

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

View file

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

View file

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