forked from TrueCloudLab/neoneo-go
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)
|
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
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")
|
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(),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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++
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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())
|
||||||
|
|
Loading…
Add table
Reference in a new issue