mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-23 23:25:22 +00:00
Merge pull request #1452 from nspcc-dev/contract/deploy
Support `_deploy` method
This commit is contained in:
commit
d6a1a22afa
31 changed files with 614 additions and 225 deletions
106
cli/contract_test.go
Normal file
106
cli/contract_test.go
Normal 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())
|
||||
})
|
||||
}
|
|
@ -185,11 +185,14 @@ func (e *executor) run(args ...string) error {
|
|||
return e.CLI.Run(args)
|
||||
}
|
||||
|
||||
func (e *executor) checkTxPersisted(t *testing.T) (*transaction.Transaction, uint32) {
|
||||
func (e *executor) checkTxPersisted(t *testing.T, prefix ...string) (*transaction.Transaction, uint32) {
|
||||
line, err := e.Out.ReadString('\n')
|
||||
require.NoError(t, err)
|
||||
|
||||
line = strings.TrimSpace(line)
|
||||
if len(prefix) > 0 {
|
||||
line = strings.TrimPrefix(line, prefix[0])
|
||||
}
|
||||
h, err := util.Uint256DecodeStringLE(line)
|
||||
require.NoError(t, err, "can't decode tx hash: %s", line)
|
||||
|
||||
|
|
31
cli/testdata/deploy/main.go
vendored
Normal file
31
cli/testdata/deploy/main.go
vendored
Normal 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
1
cli/testdata/deploy/neo-go.yml
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
hasstorage: true
|
14
cli/testdata/deploy/sub/put.go
vendored
Normal file
14
cli/testdata/deploy/sub/put.go
vendored
Normal 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
6
cli/testdata/deploy/updated.go
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
package deploy
|
||||
|
||||
// NewMethod in updated contract.
|
||||
func NewMethod() int {
|
||||
return 42
|
||||
}
|
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -37,20 +37,31 @@ func (c *codegen) getIdentName(pkg string, name string) string {
|
|||
}
|
||||
|
||||
// traverseGlobals visits and initializes global variables.
|
||||
// and returns number of variables initialized and
|
||||
// true if any init functions were encountered.
|
||||
func (c *codegen) traverseGlobals() (int, bool) {
|
||||
// and returns number of variables initialized.
|
||||
// Second return value is -1 if no `init()` functions were encountered
|
||||
// and number of maximum amount of locals in any of init functions otherwise.
|
||||
// Same for `_deploy()` functions (see docs/compiler.md).
|
||||
func (c *codegen) traverseGlobals() (int, int, int) {
|
||||
var hasDefer bool
|
||||
var n int
|
||||
var hasInit bool
|
||||
initLocals := -1
|
||||
deployLocals := -1
|
||||
c.ForEachFile(func(f *ast.File, _ *types.Package) {
|
||||
n += countGlobals(f)
|
||||
if !hasInit || !hasDefer {
|
||||
if initLocals == -1 || deployLocals == -1 || !hasDefer {
|
||||
ast.Inspect(f, func(node ast.Node) bool {
|
||||
switch n := node.(type) {
|
||||
case *ast.FuncDecl:
|
||||
if isInitFunc(n) {
|
||||
hasInit = true
|
||||
c, _ := countLocals(n)
|
||||
if c > initLocals {
|
||||
initLocals = c
|
||||
}
|
||||
} else if isDeployFunc(n) {
|
||||
c, _ := countLocals(n)
|
||||
if c > deployLocals {
|
||||
deployLocals = c
|
||||
}
|
||||
}
|
||||
return !hasDefer
|
||||
case *ast.DeferStmt:
|
||||
|
@ -64,14 +75,18 @@ func (c *codegen) traverseGlobals() (int, bool) {
|
|||
if hasDefer {
|
||||
n++
|
||||
}
|
||||
if n != 0 || hasInit {
|
||||
if n != 0 || initLocals > -1 {
|
||||
if n > 255 {
|
||||
c.prog.BinWriter.Err = errors.New("too many global variables")
|
||||
return 0, hasInit
|
||||
return 0, initLocals, deployLocals
|
||||
}
|
||||
if n != 0 {
|
||||
emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)})
|
||||
}
|
||||
if initLocals > 0 {
|
||||
emit.Instruction(c.prog.BinWriter, opcode.INITSLOT, []byte{byte(initLocals), 0})
|
||||
}
|
||||
seenBefore := false
|
||||
c.ForEachPackage(func(pkg *loader.PackageInfo) {
|
||||
if n > 0 {
|
||||
for _, f := range pkg.Files {
|
||||
|
@ -79,10 +94,10 @@ func (c *codegen) traverseGlobals() (int, bool) {
|
|||
c.convertGlobals(f, pkg.Pkg)
|
||||
}
|
||||
}
|
||||
if hasInit {
|
||||
if initLocals > -1 {
|
||||
for _, f := range pkg.Files {
|
||||
c.fillImportMap(f, pkg.Pkg)
|
||||
c.convertInitFuncs(f, pkg.Pkg)
|
||||
seenBefore = c.convertInitFuncs(f, pkg.Pkg, seenBefore) || seenBefore
|
||||
}
|
||||
}
|
||||
// because we reuse `convertFuncDecl` for init funcs,
|
||||
|
@ -96,7 +111,7 @@ func (c *codegen) traverseGlobals() (int, bool) {
|
|||
c.globals["<exception>"] = c.exceptionIndex
|
||||
}
|
||||
}
|
||||
return n, hasInit
|
||||
return n, initLocals, deployLocals
|
||||
}
|
||||
|
||||
// countGlobals counts the global variables in the program to add
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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++
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -21,18 +21,20 @@ func Instruction(w *io.BinWriter, op opcode.Opcode, b []byte) {
|
|||
w.WriteBytes(b)
|
||||
}
|
||||
|
||||
// Opcode emits a single VM Instruction without arguments to the given buffer.
|
||||
func Opcode(w *io.BinWriter, op opcode.Opcode) {
|
||||
w.WriteB(byte(op))
|
||||
// Opcodes emits a single VM Instruction without arguments to the given buffer.
|
||||
func Opcodes(w *io.BinWriter, ops ...opcode.Opcode) {
|
||||
for _, op := range ops {
|
||||
w.WriteB(byte(op))
|
||||
}
|
||||
}
|
||||
|
||||
// Bool emits a bool type the given buffer.
|
||||
func Bool(w *io.BinWriter, ok bool) {
|
||||
if ok {
|
||||
Opcode(w, opcode.PUSHT)
|
||||
Opcodes(w, opcode.PUSHT)
|
||||
return
|
||||
}
|
||||
Opcode(w, opcode.PUSHF)
|
||||
Opcodes(w, opcode.PUSHF)
|
||||
Instruction(w, opcode.CONVERT, []byte{byte(stackitem.BooleanT)})
|
||||
}
|
||||
|
||||
|
@ -51,15 +53,15 @@ func padRight(s int, buf []byte) []byte {
|
|||
func Int(w *io.BinWriter, i int64) {
|
||||
switch {
|
||||
case i == -1:
|
||||
Opcode(w, opcode.PUSHM1)
|
||||
Opcodes(w, opcode.PUSHM1)
|
||||
case i >= 0 && i < 16:
|
||||
val := opcode.Opcode(int(opcode.PUSH1) - 1 + int(i))
|
||||
Opcode(w, val)
|
||||
Opcodes(w, val)
|
||||
default:
|
||||
buf := bigint.ToPreallocatedBytes(big.NewInt(i), make([]byte, 0, 32))
|
||||
// l != 0 becase of switch
|
||||
padSize := byte(8 - bits.LeadingZeros8(byte(len(buf)-1)))
|
||||
Opcode(w, opcode.PUSHINT8+opcode.Opcode(padSize))
|
||||
Opcodes(w, opcode.PUSHINT8+opcode.Opcode(padSize))
|
||||
w.WriteBytes(padRight(1<<padSize, buf))
|
||||
}
|
||||
}
|
||||
|
@ -83,11 +85,11 @@ func Array(w *io.BinWriter, es ...interface{}) {
|
|||
w.Err = errors.New("unsupported type")
|
||||
return
|
||||
}
|
||||
Opcode(w, opcode.PUSHNULL)
|
||||
Opcodes(w, opcode.PUSHNULL)
|
||||
}
|
||||
}
|
||||
Int(w, int64(len(es)))
|
||||
Opcode(w, opcode.PACK)
|
||||
Opcodes(w, opcode.PACK)
|
||||
}
|
||||
|
||||
// String emits a string to the given buffer.
|
||||
|
|
|
@ -181,6 +181,13 @@ func TestEmitBool(t *testing.T) {
|
|||
assert.Equal(t, opcode.Opcode(result[1]), opcode.PUSH0)
|
||||
}
|
||||
|
||||
func TestEmitOpcode(t *testing.T) {
|
||||
w := io.NewBufBinWriter()
|
||||
Opcodes(w.BinWriter, opcode.PUSH1, opcode.NEWMAP)
|
||||
result := w.Bytes()
|
||||
assert.Equal(t, result, []byte{byte(opcode.PUSH1), byte(opcode.NEWMAP)})
|
||||
}
|
||||
|
||||
func TestEmitString(t *testing.T) {
|
||||
buf := io.NewBufBinWriter()
|
||||
str := "City Of Zion"
|
||||
|
|
|
@ -39,7 +39,7 @@ func TestInteropHook(t *testing.T) {
|
|||
|
||||
buf := io.NewBufBinWriter()
|
||||
emit.Syscall(buf.BinWriter, "foo")
|
||||
emit.Opcode(buf.BinWriter, opcode.RET)
|
||||
emit.Opcodes(buf.BinWriter, opcode.RET)
|
||||
v.Load(buf.Bytes())
|
||||
runVM(t, v)
|
||||
assert.Equal(t, 1, v.estack.Len())
|
||||
|
@ -773,11 +773,11 @@ func TestSerializeMapCompat(t *testing.T) {
|
|||
|
||||
// Create a map, push key and value, add KV to map, serialize.
|
||||
buf := io.NewBufBinWriter()
|
||||
emit.Opcode(buf.BinWriter, opcode.NEWMAP)
|
||||
emit.Opcode(buf.BinWriter, opcode.DUP)
|
||||
emit.Opcodes(buf.BinWriter, opcode.NEWMAP)
|
||||
emit.Opcodes(buf.BinWriter, opcode.DUP)
|
||||
emit.Bytes(buf.BinWriter, []byte("key"))
|
||||
emit.Bytes(buf.BinWriter, []byte("value"))
|
||||
emit.Opcode(buf.BinWriter, opcode.SETITEM)
|
||||
emit.Opcodes(buf.BinWriter, opcode.SETITEM)
|
||||
emit.Syscall(buf.BinWriter, interopnames.SystemBinarySerialize)
|
||||
require.NoError(t, buf.Err)
|
||||
|
||||
|
@ -1654,12 +1654,12 @@ func TestSIGN(t *testing.T) {
|
|||
func TestSimpleCall(t *testing.T) {
|
||||
buf := io.NewBufBinWriter()
|
||||
w := buf.BinWriter
|
||||
emit.Opcode(w, opcode.PUSH2)
|
||||
emit.Opcodes(w, opcode.PUSH2)
|
||||
emit.Instruction(w, opcode.CALL, []byte{03})
|
||||
emit.Opcode(w, opcode.RET)
|
||||
emit.Opcode(w, opcode.PUSH10)
|
||||
emit.Opcode(w, opcode.ADD)
|
||||
emit.Opcode(w, opcode.RET)
|
||||
emit.Opcodes(w, opcode.RET)
|
||||
emit.Opcodes(w, opcode.PUSH10)
|
||||
emit.Opcodes(w, opcode.ADD)
|
||||
emit.Opcodes(w, opcode.RET)
|
||||
|
||||
result := 12
|
||||
vm := load(buf.Bytes())
|
||||
|
|
Loading…
Reference in a new issue