vm,compiler: replace APPCALL with System.Contract.Call
Contract calls are performed via syscall System.Contract.Call in NEO3. This implements this in compiler and removes APPCALL from the VM.
This commit is contained in:
parent
ec900c7ff7
commit
73c82584a3
13 changed files with 108 additions and 241 deletions
|
@ -1080,16 +1080,12 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) {
|
||||||
case "SHA256":
|
case "SHA256":
|
||||||
emit.Syscall(c.prog.BinWriter, "Neo.Crypto.SHA256")
|
emit.Syscall(c.prog.BinWriter, "Neo.Crypto.SHA256")
|
||||||
case "AppCall":
|
case "AppCall":
|
||||||
numArgs := len(expr.Args) - 1
|
c.emitReverse(len(expr.Args))
|
||||||
c.emitReverse(numArgs)
|
|
||||||
|
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.APPCALL)
|
|
||||||
buf := c.getByteArray(expr.Args[0])
|
buf := c.getByteArray(expr.Args[0])
|
||||||
if len(buf) != 20 {
|
if len(buf) != 20 {
|
||||||
c.prog.Err = errors.New("invalid script hash")
|
c.prog.Err = errors.New("invalid script hash")
|
||||||
}
|
}
|
||||||
|
emit.Syscall(c.prog.BinWriter, "System.Contract.Call")
|
||||||
c.prog.WriteBytes(buf)
|
|
||||||
case "Equals":
|
case "Equals":
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.EQUAL)
|
emit.Opcode(c.prog.BinWriter, opcode.EQUAL)
|
||||||
case "FromAddress":
|
case "FromAddress":
|
||||||
|
@ -1111,16 +1107,14 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) {
|
||||||
// transformArgs returns a list of function arguments
|
// transformArgs returns a list of function arguments
|
||||||
// which should be put on stack.
|
// which should be put on stack.
|
||||||
// There are special cases for builtins:
|
// There are special cases for builtins:
|
||||||
// 1. When using AppCall, script hash is a part of the instruction so
|
// 1. With FromAddress, parameter conversion is happening at compile-time
|
||||||
// it should be emitted after APPCALL.
|
|
||||||
// 2. With FromAddress, parameter conversion is happening at compile-time
|
|
||||||
// so there is no need to push parameters on stack and perform an actual call
|
// so there is no need to push parameters on stack and perform an actual call
|
||||||
// 3. With panic, generated code depends on if argument was nil or a string so
|
// 2. With panic, generated code depends on if argument was nil or a string so
|
||||||
// it should be handled accordingly.
|
// it should be handled accordingly.
|
||||||
func transformArgs(fun ast.Expr, args []ast.Expr) []ast.Expr {
|
func transformArgs(fun ast.Expr, args []ast.Expr) []ast.Expr {
|
||||||
switch f := fun.(type) {
|
switch f := fun.(type) {
|
||||||
case *ast.SelectorExpr:
|
case *ast.SelectorExpr:
|
||||||
if f.Sel.Name == "AppCall" || f.Sel.Name == "FromAddress" {
|
if f.Sel.Name == "FromAddress" {
|
||||||
return args[1:]
|
return args[1:]
|
||||||
}
|
}
|
||||||
case *ast.Ident:
|
case *ast.Ident:
|
||||||
|
|
|
@ -6,10 +6,17 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||||
|
"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/core/storage"
|
||||||
"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/encoding/address"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.uber.org/zap/zaptest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFromAddress(t *testing.T) {
|
func TestFromAddress(t *testing.T) {
|
||||||
|
@ -51,6 +58,14 @@ func TestFromAddress(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func spawnVM(t *testing.T, ic *interop.Context, src string) *vm.VM {
|
||||||
|
b, err := compiler.Compile(strings.NewReader(src))
|
||||||
|
require.NoError(t, err)
|
||||||
|
v := core.SpawnVM(ic)
|
||||||
|
v.Load(b)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
func TestAppCall(t *testing.T) {
|
func TestAppCall(t *testing.T) {
|
||||||
srcInner := `
|
srcInner := `
|
||||||
package foo
|
package foo
|
||||||
|
@ -62,19 +77,13 @@ func TestAppCall(t *testing.T) {
|
||||||
inner, err := compiler.Compile(strings.NewReader(srcInner))
|
inner, err := compiler.Compile(strings.NewReader(srcInner))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ih := hash.Hash160(inner)
|
ic := interop.NewContext(trigger.Application, nil, dao.NewSimple(storage.NewMemoryStore()), nil, nil, nil, zaptest.NewLogger(t))
|
||||||
getScript := func(u util.Uint160) ([]byte, bool) {
|
require.NoError(t, ic.DAO.PutContractState(&state.Contract{Script: inner}))
|
||||||
if u.Equals(ih) {
|
|
||||||
return inner, true
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
ih := hash.Hash160(inner)
|
||||||
t.Run("valid script", func(t *testing.T) {
|
t.Run("valid script", func(t *testing.T) {
|
||||||
src := getAppCallScript(fmt.Sprintf("%#v", ih.BytesBE()))
|
src := getAppCallScript(fmt.Sprintf("%#v", ih.BytesBE()))
|
||||||
v := vmAndCompile(t, src)
|
v := spawnVM(t, ic, src)
|
||||||
v.SetScriptGetter(getScript)
|
|
||||||
|
|
||||||
require.NoError(t, v.Run())
|
require.NoError(t, v.Run())
|
||||||
|
|
||||||
assertResult(t, v, []byte{1, 2, 3, 4})
|
assertResult(t, v, []byte{1, 2, 3, 4})
|
||||||
|
@ -85,9 +94,7 @@ func TestAppCall(t *testing.T) {
|
||||||
h[0] = ^h[0]
|
h[0] = ^h[0]
|
||||||
|
|
||||||
src := getAppCallScript(fmt.Sprintf("%#v", h.BytesBE()))
|
src := getAppCallScript(fmt.Sprintf("%#v", h.BytesBE()))
|
||||||
v := vmAndCompile(t, src)
|
v := spawnVM(t, ic, src)
|
||||||
v.SetScriptGetter(getScript)
|
|
||||||
|
|
||||||
require.Error(t, v.Run())
|
require.Error(t, v.Run())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -111,9 +118,7 @@ func TestAppCall(t *testing.T) {
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
v := vmAndCompile(t, src)
|
v := spawnVM(t, ic, src)
|
||||||
v.SetScriptGetter(getScript)
|
|
||||||
|
|
||||||
require.NoError(t, v.Run())
|
require.NoError(t, v.Run())
|
||||||
|
|
||||||
assertResult(t, v, []byte{1, 2, 3, 4})
|
assertResult(t, v, []byte{1, 2, 3, 4})
|
||||||
|
|
|
@ -20,8 +20,6 @@ func getPrice(v *vm.VM, op opcode.Opcode, parameter []byte) util.Fixed8 {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch op {
|
switch op {
|
||||||
case opcode.APPCALL, opcode.TAILCALL:
|
|
||||||
return toFixed8(10)
|
|
||||||
case opcode.SYSCALL:
|
case opcode.SYSCALL:
|
||||||
interopID := vm.GetInteropID(parameter)
|
interopID := vm.GetInteropID(parameter)
|
||||||
return getSyscallPrice(v, interopID)
|
return getSyscallPrice(v, interopID)
|
||||||
|
|
|
@ -24,11 +24,11 @@ import (
|
||||||
// up for current blockchain.
|
// up for current blockchain.
|
||||||
func SpawnVM(ic *interop.Context) *vm.VM {
|
func SpawnVM(ic *interop.Context) *vm.VM {
|
||||||
vm := vm.New()
|
vm := vm.New()
|
||||||
bc := ic.Chain.(*Blockchain)
|
|
||||||
vm.SetScriptGetter(ic.GetContract)
|
|
||||||
vm.RegisterInteropGetter(getSystemInterop(ic))
|
vm.RegisterInteropGetter(getSystemInterop(ic))
|
||||||
vm.RegisterInteropGetter(getNeoInterop(ic))
|
vm.RegisterInteropGetter(getNeoInterop(ic))
|
||||||
vm.RegisterInteropGetter(bc.contracts.GetNativeInterop(ic))
|
if ic.Chain != nil {
|
||||||
|
vm.RegisterInteropGetter(ic.Chain.(*Blockchain).contracts.GetNativeInterop(ic))
|
||||||
|
}
|
||||||
return vm
|
return vm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,43 +19,43 @@ func TestInvocationScriptCreationGood(t *testing.T) {
|
||||||
ps Params
|
ps Params
|
||||||
script string
|
script string
|
||||||
}{{
|
}{{
|
||||||
script: "676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
script: "0c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
|
||||||
}, {
|
}, {
|
||||||
ps: Params{{Type: StringT, Value: "transfer"}},
|
ps: Params{{Type: StringT, Value: "transfer"}},
|
||||||
script: "0c087472616e73666572676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
script: "0c087472616e736665720c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
|
||||||
}, {
|
}, {
|
||||||
ps: Params{{Type: NumberT, Value: 42}},
|
ps: Params{{Type: NumberT, Value: 42}},
|
||||||
script: "0c023432676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
script: "0c0234320c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
|
||||||
}, {
|
}, {
|
||||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{}}},
|
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{}}},
|
||||||
script: "10c00c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
script: "10c00c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
|
||||||
}, {
|
}, {
|
||||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.ByteArrayType, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
|
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.ByteArrayType, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
|
||||||
script: "0c1450befd26fdf6e4d957c11e078b24ebce6291456f11c00c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
script: "0c1450befd26fdf6e4d957c11e078b24ebce6291456f11c00c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
|
||||||
}, {
|
}, {
|
||||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.SignatureType, Value: Param{Type: StringT, Value: "4edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f"}}}}}},
|
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.SignatureType, Value: Param{Type: StringT, Value: "4edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f"}}}}}},
|
||||||
script: "0c404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f11c00c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
script: "0c404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f11c00c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
|
||||||
}, {
|
}, {
|
||||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.StringType, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
|
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.StringType, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
|
||||||
script: "0c283530626566643236666466366534643935376331316530373862323465626365363239313435366611c00c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
script: "0c283530626566643236666466366534643935376331316530373862323465626365363239313435366611c00c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
|
||||||
}, {
|
}, {
|
||||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.Hash160Type, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
|
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.Hash160Type, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
|
||||||
script: "0c146f459162ceeb248b071ec157d9e4f6fd26fdbe5011c00c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
script: "0c146f459162ceeb248b071ec157d9e4f6fd26fdbe5011c00c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
|
||||||
}, {
|
}, {
|
||||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.Hash256Type, Value: Param{Type: StringT, Value: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}}}}}},
|
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.Hash256Type, Value: Param{Type: StringT, Value: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}}}}}},
|
||||||
script: "0c20e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6011c00c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
script: "0c20e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6011c00c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
|
||||||
}, {
|
}, {
|
||||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.PublicKeyType, Value: Param{Type: StringT, Value: "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1"}}}}}},
|
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.PublicKeyType, Value: Param{Type: StringT, Value: "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1"}}}}}},
|
||||||
script: "0c2103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c111c00c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
script: "0c2103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c111c00c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
|
||||||
}, {
|
}, {
|
||||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.IntegerType, Value: Param{Type: NumberT, Value: 42}}}}}},
|
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.IntegerType, Value: Param{Type: NumberT, Value: 42}}}}}},
|
||||||
script: "002a11c00c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
script: "002a11c00c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
|
||||||
}, {
|
}, {
|
||||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.BoolType, Value: Param{Type: StringT, Value: "true"}}}}}},
|
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.BoolType, Value: Param{Type: StringT, Value: "true"}}}}}},
|
||||||
script: "1111c00c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
script: "1111c00c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
|
||||||
}, {
|
}, {
|
||||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.BoolType, Value: Param{Type: StringT, Value: "false"}}}}}},
|
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.BoolType, Value: Param{Type: StringT, Value: "false"}}}}}},
|
||||||
script: "1011c00c0161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
script: "1011c00c01610c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52",
|
||||||
}}
|
}}
|
||||||
for _, ps := range paramScripts {
|
for _, ps := range paramScripts {
|
||||||
script, err := CreateFunctionInvocationScript(contract, ps.ps)
|
script, err := CreateFunctionInvocationScript(contract, ps.ps)
|
||||||
|
|
|
@ -54,12 +54,12 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
"getapplicationlog": {
|
"getapplicationlog": {
|
||||||
{
|
{
|
||||||
name: "positive",
|
name: "positive",
|
||||||
params: `["caccb0e0465d87970190c90094e3fb54dbbcfa3e48683bd9a54b1fb834118d87"]`,
|
params: `["c296c0929350d051b9b40cace54db5a3eac4b730a8851e958795d44918f23c08"]`,
|
||||||
result: func(e *executor) interface{} { return &result.ApplicationLog{} },
|
result: func(e *executor) interface{} { return &result.ApplicationLog{} },
|
||||||
check: func(t *testing.T, e *executor, acc interface{}) {
|
check: func(t *testing.T, e *executor, acc interface{}) {
|
||||||
res, ok := acc.(*result.ApplicationLog)
|
res, ok := acc.(*result.ApplicationLog)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
expectedTxHash, err := util.Uint256DecodeStringLE("caccb0e0465d87970190c90094e3fb54dbbcfa3e48683bd9a54b1fb834118d87")
|
expectedTxHash, err := util.Uint256DecodeStringLE("c296c0929350d051b9b40cace54db5a3eac4b730a8851e958795d44918f23c08")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, expectedTxHash, res.TxHash)
|
assert.Equal(t, expectedTxHash, res.TxHash)
|
||||||
assert.Equal(t, 1, len(res.Executions))
|
assert.Equal(t, 1, len(res.Executions))
|
||||||
|
@ -647,7 +647,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
check: func(t *testing.T, e *executor, inv interface{}) {
|
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||||
res, ok := inv.(*result.Invoke)
|
res, ok := inv.(*result.Invoke)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Equal(t, "0c06717765727479676f459162ceeb248b071ec157d9e4f6fd26fdbe50", res.Script)
|
assert.Equal(t, "0c067177657274790c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52", res.Script)
|
||||||
assert.NotEqual(t, "", res.State)
|
assert.NotEqual(t, "", res.State)
|
||||||
assert.NotEqual(t, 0, res.GasConsumed)
|
assert.NotEqual(t, 0, res.GasConsumed)
|
||||||
},
|
},
|
||||||
|
|
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
Binary file not shown.
|
@ -113,8 +113,6 @@ func (c *Context) Next() (opcode.Opcode, []byte, error) {
|
||||||
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL,
|
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL,
|
||||||
opcode.CALLL, opcode.SYSCALL:
|
opcode.CALLL, opcode.SYSCALL:
|
||||||
numtoread = 4
|
numtoread = 4
|
||||||
case opcode.APPCALL, opcode.TAILCALL:
|
|
||||||
numtoread = 20
|
|
||||||
default:
|
default:
|
||||||
if instr <= opcode.PUSHINT256 {
|
if instr <= opcode.PUSHINT256 {
|
||||||
numtoread = 1 << instr
|
numtoread = 1 << instr
|
||||||
|
|
|
@ -144,11 +144,8 @@ func Jmp(w *io.BinWriter, op opcode.Opcode, label uint16) {
|
||||||
// AppCall emits an appcall, if tailCall is true, tailCall opcode will be
|
// AppCall emits an appcall, if tailCall is true, tailCall opcode will be
|
||||||
// emitted instead.
|
// emitted instead.
|
||||||
func AppCall(w *io.BinWriter, scriptHash util.Uint160, tailCall bool) {
|
func AppCall(w *io.BinWriter, scriptHash util.Uint160, tailCall bool) {
|
||||||
op := opcode.APPCALL
|
Bytes(w, scriptHash.BytesBE())
|
||||||
if tailCall {
|
Syscall(w, "System.Contract.Call")
|
||||||
op = opcode.TAILCALL
|
|
||||||
}
|
|
||||||
Instruction(w, op, scriptHash.BytesBE())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppCallWithOperationAndArgs emits an APPCALL with the given operation and arguments.
|
// AppCallWithOperationAndArgs emits an APPCALL with the given operation and arguments.
|
||||||
|
|
|
@ -91,9 +91,6 @@ const (
|
||||||
REVERSE4 Opcode = 0x54
|
REVERSE4 Opcode = 0x54
|
||||||
REVERSEN Opcode = 0x55
|
REVERSEN Opcode = 0x55
|
||||||
|
|
||||||
APPCALL Opcode = 0x67
|
|
||||||
TAILCALL Opcode = 0x69
|
|
||||||
|
|
||||||
// Old stack opcodes
|
// Old stack opcodes
|
||||||
DUPFROMALTSTACK Opcode = 0x6A
|
DUPFROMALTSTACK Opcode = 0x6A
|
||||||
TOALTSTACK Opcode = 0x6B
|
TOALTSTACK Opcode = 0x6B
|
||||||
|
|
|
@ -80,8 +80,6 @@ func _() {
|
||||||
_ = x[REVERSE3-83]
|
_ = x[REVERSE3-83]
|
||||||
_ = x[REVERSE4-84]
|
_ = x[REVERSE4-84]
|
||||||
_ = x[REVERSEN-85]
|
_ = x[REVERSEN-85]
|
||||||
_ = x[APPCALL-103]
|
|
||||||
_ = x[TAILCALL-105]
|
|
||||||
_ = x[DUPFROMALTSTACK-106]
|
_ = x[DUPFROMALTSTACK-106]
|
||||||
_ = x[TOALTSTACK-107]
|
_ = x[TOALTSTACK-107]
|
||||||
_ = x[FROMALTSTACK-108]
|
_ = x[FROMALTSTACK-108]
|
||||||
|
@ -143,7 +141,7 @@ func _() {
|
||||||
_ = x[CONVERT-219]
|
_ = x[CONVERT-219]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMPLJMPIFJMPIFLJMPIFNOTJMPIFNOTLJMPEQJMPEQLJMPNEJMPNELJMPGTJMPGTLJMPGEJMPGELJMPLTJMPLTLJMPLEJMPLELCALLCALLLABORTASSERTTHROWRETSYSCALLDEPTHDROPNIPXDROPCLEARDUPOVERPICKTUCKSWAPOLDPUSH1ROLLREVERSE3REVERSE4REVERSENAPPCALLTAILCALLDUPFROMALTSTACKTOALTSTACKFROMALTSTACKCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALNOTEQUALSIGNABSNEGATEINCDECADDSUBMULDIVMODSHLSHRNOTBOOLANDBOOLORNZNUMEQUALNUMNOTEQUALLTLTEGTGTEMINMAXWITHINPACKUNPACKNEWARRAY0NEWARRAYNEWARRAYTNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSISNULLISTYPECONVERT"
|
const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMPLJMPIFJMPIFLJMPIFNOTJMPIFNOTLJMPEQJMPEQLJMPNEJMPNELJMPGTJMPGTLJMPGEJMPGELJMPLTJMPLTLJMPLEJMPLELCALLCALLLABORTASSERTTHROWRETSYSCALLDEPTHDROPNIPXDROPCLEARDUPOVERPICKTUCKSWAPOLDPUSH1ROLLREVERSE3REVERSE4REVERSENDUPFROMALTSTACKTOALTSTACKFROMALTSTACKCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALNOTEQUALSIGNABSNEGATEINCDECADDSUBMULDIVMODSHLSHRNOTBOOLANDBOOLORNZNUMEQUALNUMNOTEQUALLTLTEGTGTEMINMAXWITHINPACKUNPACKNEWARRAY0NEWARRAYNEWARRAYTNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSISNULLISTYPECONVERT"
|
||||||
|
|
||||||
var _Opcode_map = map[Opcode]string{
|
var _Opcode_map = map[Opcode]string{
|
||||||
0: _Opcode_name[0:8],
|
0: _Opcode_name[0:8],
|
||||||
|
@ -215,67 +213,65 @@ var _Opcode_map = map[Opcode]string{
|
||||||
83: _Opcode_name[380:388],
|
83: _Opcode_name[380:388],
|
||||||
84: _Opcode_name[388:396],
|
84: _Opcode_name[388:396],
|
||||||
85: _Opcode_name[396:404],
|
85: _Opcode_name[396:404],
|
||||||
103: _Opcode_name[404:411],
|
106: _Opcode_name[404:419],
|
||||||
105: _Opcode_name[411:419],
|
107: _Opcode_name[419:429],
|
||||||
106: _Opcode_name[419:434],
|
108: _Opcode_name[429:441],
|
||||||
107: _Opcode_name[434:444],
|
126: _Opcode_name[441:444],
|
||||||
108: _Opcode_name[444:456],
|
127: _Opcode_name[444:450],
|
||||||
126: _Opcode_name[456:459],
|
128: _Opcode_name[450:454],
|
||||||
127: _Opcode_name[459:465],
|
129: _Opcode_name[454:459],
|
||||||
128: _Opcode_name[465:469],
|
144: _Opcode_name[459:465],
|
||||||
129: _Opcode_name[469:474],
|
145: _Opcode_name[465:468],
|
||||||
144: _Opcode_name[474:480],
|
146: _Opcode_name[468:470],
|
||||||
145: _Opcode_name[480:483],
|
147: _Opcode_name[470:473],
|
||||||
146: _Opcode_name[483:485],
|
151: _Opcode_name[473:478],
|
||||||
147: _Opcode_name[485:488],
|
152: _Opcode_name[478:486],
|
||||||
151: _Opcode_name[488:493],
|
153: _Opcode_name[486:490],
|
||||||
152: _Opcode_name[493:501],
|
154: _Opcode_name[490:493],
|
||||||
153: _Opcode_name[501:505],
|
155: _Opcode_name[493:499],
|
||||||
154: _Opcode_name[505:508],
|
156: _Opcode_name[499:502],
|
||||||
155: _Opcode_name[508:514],
|
157: _Opcode_name[502:505],
|
||||||
156: _Opcode_name[514:517],
|
158: _Opcode_name[505:508],
|
||||||
157: _Opcode_name[517:520],
|
159: _Opcode_name[508:511],
|
||||||
158: _Opcode_name[520:523],
|
160: _Opcode_name[511:514],
|
||||||
159: _Opcode_name[523:526],
|
161: _Opcode_name[514:517],
|
||||||
160: _Opcode_name[526:529],
|
162: _Opcode_name[517:520],
|
||||||
161: _Opcode_name[529:532],
|
168: _Opcode_name[520:523],
|
||||||
162: _Opcode_name[532:535],
|
169: _Opcode_name[523:526],
|
||||||
168: _Opcode_name[535:538],
|
170: _Opcode_name[526:529],
|
||||||
169: _Opcode_name[538:541],
|
171: _Opcode_name[529:536],
|
||||||
170: _Opcode_name[541:544],
|
172: _Opcode_name[536:542],
|
||||||
171: _Opcode_name[544:551],
|
177: _Opcode_name[542:544],
|
||||||
172: _Opcode_name[551:557],
|
179: _Opcode_name[544:552],
|
||||||
177: _Opcode_name[557:559],
|
180: _Opcode_name[552:563],
|
||||||
179: _Opcode_name[559:567],
|
181: _Opcode_name[563:565],
|
||||||
180: _Opcode_name[567:578],
|
182: _Opcode_name[565:568],
|
||||||
181: _Opcode_name[578:580],
|
183: _Opcode_name[568:570],
|
||||||
182: _Opcode_name[580:583],
|
184: _Opcode_name[570:573],
|
||||||
183: _Opcode_name[583:585],
|
185: _Opcode_name[573:576],
|
||||||
184: _Opcode_name[585:588],
|
186: _Opcode_name[576:579],
|
||||||
185: _Opcode_name[588:591],
|
187: _Opcode_name[579:585],
|
||||||
186: _Opcode_name[591:594],
|
192: _Opcode_name[585:589],
|
||||||
187: _Opcode_name[594:600],
|
193: _Opcode_name[589:595],
|
||||||
192: _Opcode_name[600:604],
|
194: _Opcode_name[595:604],
|
||||||
193: _Opcode_name[604:610],
|
195: _Opcode_name[604:612],
|
||||||
194: _Opcode_name[610:619],
|
196: _Opcode_name[612:621],
|
||||||
195: _Opcode_name[619:627],
|
197: _Opcode_name[621:631],
|
||||||
196: _Opcode_name[627:636],
|
198: _Opcode_name[631:640],
|
||||||
197: _Opcode_name[636:646],
|
200: _Opcode_name[640:646],
|
||||||
198: _Opcode_name[646:655],
|
202: _Opcode_name[646:650],
|
||||||
200: _Opcode_name[655:661],
|
203: _Opcode_name[650:656],
|
||||||
202: _Opcode_name[661:665],
|
204: _Opcode_name[656:660],
|
||||||
203: _Opcode_name[665:671],
|
205: _Opcode_name[660:666],
|
||||||
204: _Opcode_name[671:675],
|
206: _Opcode_name[666:674],
|
||||||
205: _Opcode_name[675:681],
|
207: _Opcode_name[674:680],
|
||||||
206: _Opcode_name[681:689],
|
208: _Opcode_name[680:687],
|
||||||
207: _Opcode_name[689:695],
|
209: _Opcode_name[687:699],
|
||||||
208: _Opcode_name[695:702],
|
210: _Opcode_name[699:705],
|
||||||
209: _Opcode_name[702:714],
|
211: _Opcode_name[705:715],
|
||||||
210: _Opcode_name[714:720],
|
216: _Opcode_name[715:721],
|
||||||
211: _Opcode_name[720:730],
|
217: _Opcode_name[721:727],
|
||||||
216: _Opcode_name[730:736],
|
219: _Opcode_name[727:734],
|
||||||
217: _Opcode_name[736:742],
|
|
||||||
219: _Opcode_name[742:749],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i Opcode) String() string {
|
func (i Opcode) String() string {
|
||||||
|
|
55
pkg/vm/vm.go
55
pkg/vm/vm.go
|
@ -71,9 +71,6 @@ type VM struct {
|
||||||
// callback to get interop price
|
// callback to get interop price
|
||||||
getPrice func(*VM, opcode.Opcode, []byte) util.Fixed8
|
getPrice func(*VM, opcode.Opcode, []byte) util.Fixed8
|
||||||
|
|
||||||
// callback to get scripts.
|
|
||||||
getScript func(util.Uint160) ([]byte, bool)
|
|
||||||
|
|
||||||
istack *Stack // invocation stack.
|
istack *Stack // invocation stack.
|
||||||
estack *Stack // execution stack.
|
estack *Stack // execution stack.
|
||||||
astack *Stack // alt stack.
|
astack *Stack // alt stack.
|
||||||
|
@ -95,7 +92,6 @@ type VM struct {
|
||||||
func New() *VM {
|
func New() *VM {
|
||||||
vm := &VM{
|
vm := &VM{
|
||||||
getInterop: make([]InteropGetterFunc, 0, 3), // 3 functions is typical for our default usage.
|
getInterop: make([]InteropGetterFunc, 0, 3), // 3 functions is typical for our default usage.
|
||||||
getScript: nil,
|
|
||||||
state: haltState,
|
state: haltState,
|
||||||
istack: NewStack("invocation"),
|
istack: NewStack("invocation"),
|
||||||
|
|
||||||
|
@ -204,8 +200,6 @@ func (v *VM) PrintOps() {
|
||||||
desc = fmt.Sprintf("%d (%d/%x)", ctx.ip+int(offset), offset, parameter)
|
desc = fmt.Sprintf("%d (%d/%x)", ctx.ip+int(offset), offset, parameter)
|
||||||
case opcode.SYSCALL:
|
case opcode.SYSCALL:
|
||||||
desc = fmt.Sprintf("%q", parameter)
|
desc = fmt.Sprintf("%q", parameter)
|
||||||
case opcode.APPCALL, opcode.TAILCALL:
|
|
||||||
desc = fmt.Sprintf("%x", parameter)
|
|
||||||
default:
|
default:
|
||||||
if utf8.Valid(parameter) {
|
if utf8.Valid(parameter) {
|
||||||
desc = fmt.Sprintf("%x (%q)", parameter, parameter)
|
desc = fmt.Sprintf("%x (%q)", parameter, parameter)
|
||||||
|
@ -477,11 +471,6 @@ func (v *VM) SetCheckedHash(h []byte) {
|
||||||
copy(v.checkhash, h)
|
copy(v.checkhash, h)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetScriptGetter sets the script getter for CALL instructions.
|
|
||||||
func (v *VM) SetScriptGetter(gs func(util.Uint160) ([]byte, bool)) {
|
|
||||||
v.getScript = gs
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInteropID converts instruction parameter to an interop ID.
|
// GetInteropID converts instruction parameter to an interop ID.
|
||||||
func GetInteropID(parameter []byte) uint32 {
|
func GetInteropID(parameter []byte) uint32 {
|
||||||
return binary.LittleEndian.Uint32(parameter)
|
return binary.LittleEndian.Uint32(parameter)
|
||||||
|
@ -520,25 +509,6 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch op {
|
|
||||||
case opcode.APPCALL, opcode.TAILCALL:
|
|
||||||
isZero := true
|
|
||||||
for i := range parameter {
|
|
||||||
if parameter[i] != 0 {
|
|
||||||
isZero = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !isZero {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
parameter = v.estack.Pop().Bytes()
|
|
||||||
if !ctx.hasDynamicInvoke {
|
|
||||||
panic("contract is not allowed to make dynamic invocations")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if op <= opcode.PUSHINT256 {
|
if op <= opcode.PUSHINT256 {
|
||||||
v.estack.PushVal(emit.BytesToInt(parameter))
|
v.estack.PushVal(emit.BytesToInt(parameter))
|
||||||
return
|
return
|
||||||
|
@ -1175,31 +1145,6 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
panic(fmt.Sprintf("failed to invoke syscall: %s", err))
|
panic(fmt.Sprintf("failed to invoke syscall: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
case opcode.APPCALL, opcode.TAILCALL:
|
|
||||||
if v.getScript == nil {
|
|
||||||
panic("no getScript callback is set up")
|
|
||||||
}
|
|
||||||
|
|
||||||
if op == opcode.APPCALL {
|
|
||||||
v.checkInvocationStackSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
hash, err := util.Uint160DecodeBytesBE(parameter)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
script, hasDynamicInvoke := v.getScript(hash)
|
|
||||||
if script == nil {
|
|
||||||
panic("could not find script")
|
|
||||||
}
|
|
||||||
|
|
||||||
if op == opcode.TAILCALL {
|
|
||||||
_ = v.istack.Pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
v.loadScriptWithHash(script, hash, hasDynamicInvoke)
|
|
||||||
|
|
||||||
case opcode.RET:
|
case opcode.RET:
|
||||||
oldCtx := v.istack.Pop().Value().(*Context)
|
oldCtx := v.istack.Pop().Value().(*Context)
|
||||||
rvcount := oldCtx.rvcount
|
rvcount := oldCtx.rvcount
|
||||||
|
|
|
@ -1413,69 +1413,6 @@ func TestSIGN(t *testing.T) {
|
||||||
t.Run("ByteArray", getTestFuncForVM(prog, 1, []byte{0, 1}))
|
t.Run("ByteArray", getTestFuncForVM(prog, 1, []byte{0, 1}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAppCall(t *testing.T) {
|
|
||||||
prog := []byte{byte(opcode.APPCALL)}
|
|
||||||
hash := util.Uint160{1, 2}
|
|
||||||
prog = append(prog, hash.BytesBE()...)
|
|
||||||
prog = append(prog, byte(opcode.RET))
|
|
||||||
|
|
||||||
vm := load(prog)
|
|
||||||
vm.SetScriptGetter(func(in util.Uint160) ([]byte, bool) {
|
|
||||||
if in.Equals(hash) {
|
|
||||||
return makeProgram(opcode.DEPTH), true
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
})
|
|
||||||
vm.estack.PushVal(2)
|
|
||||||
|
|
||||||
runVM(t, vm)
|
|
||||||
elem := vm.estack.Pop() // depth should be 1
|
|
||||||
assert.Equal(t, int64(1), elem.BigInt().Int64())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAppCallDynamicBad(t *testing.T) {
|
|
||||||
prog := []byte{byte(opcode.APPCALL)}
|
|
||||||
hash := util.Uint160{}
|
|
||||||
prog = append(prog, hash.BytesBE()...)
|
|
||||||
prog = append(prog, byte(opcode.RET))
|
|
||||||
|
|
||||||
vm := load(prog)
|
|
||||||
vm.SetScriptGetter(func(in util.Uint160) ([]byte, bool) {
|
|
||||||
if in.Equals(hash) {
|
|
||||||
return makeProgram(opcode.DEPTH), true
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
})
|
|
||||||
vm.estack.PushVal(2)
|
|
||||||
vm.estack.PushVal(hash.BytesBE())
|
|
||||||
|
|
||||||
checkVMFailed(t, vm)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAppCallDynamicGood(t *testing.T) {
|
|
||||||
prog := []byte{byte(opcode.APPCALL)}
|
|
||||||
zeroHash := util.Uint160{}
|
|
||||||
hash := util.Uint160{1, 2, 3}
|
|
||||||
prog = append(prog, zeroHash.BytesBE()...)
|
|
||||||
prog = append(prog, byte(opcode.RET))
|
|
||||||
|
|
||||||
vm := load(prog)
|
|
||||||
vm.SetScriptGetter(func(in util.Uint160) ([]byte, bool) {
|
|
||||||
if in.Equals(hash) {
|
|
||||||
return makeProgram(opcode.DEPTH), true
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
})
|
|
||||||
vm.estack.PushVal(42)
|
|
||||||
vm.estack.PushVal(42)
|
|
||||||
vm.estack.PushVal(hash.BytesBE())
|
|
||||||
vm.Context().hasDynamicInvoke = true
|
|
||||||
|
|
||||||
runVM(t, vm)
|
|
||||||
elem := vm.estack.Pop() // depth should be 2
|
|
||||||
assert.Equal(t, int64(2), elem.BigInt().Int64())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSimpleCall(t *testing.T) {
|
func TestSimpleCall(t *testing.T) {
|
||||||
buf := io.NewBufBinWriter()
|
buf := io.NewBufBinWriter()
|
||||||
w := buf.BinWriter
|
w := buf.BinWriter
|
||||||
|
|
Loading…
Reference in a new issue