compiler/engine: add dynamic APPCALL generation, fix #914
Previously we could generate dynamic appcall with a kludge of AppCall([]byte{/* 20 zeroes */, realScriptHash, args...) Now there is a separate function for this.
This commit is contained in:
parent
78b2387640
commit
5cebd4a7a2
4 changed files with 88 additions and 6 deletions
|
@ -16,7 +16,7 @@ var (
|
|||
"SHA1", "Hash256", "Hash160",
|
||||
"VerifySignature", "AppCall",
|
||||
"FromAddress", "Equals",
|
||||
"panic",
|
||||
"panic", "DynAppCall",
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -1085,14 +1085,23 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) {
|
|||
emit.Opcode(c.prog.BinWriter, opcode.HASH160)
|
||||
case "VerifySignature":
|
||||
emit.Opcode(c.prog.BinWriter, opcode.VERIFY)
|
||||
case "AppCall":
|
||||
numArgs := len(expr.Args) - 1
|
||||
case "AppCall", "DynAppCall":
|
||||
numArgs := len(expr.Args)
|
||||
if name == "AppCall" {
|
||||
numArgs--
|
||||
}
|
||||
c.emitReverse(numArgs)
|
||||
|
||||
emit.Opcode(c.prog.BinWriter, opcode.APPCALL)
|
||||
buf := c.getByteArray(expr.Args[0])
|
||||
if len(buf) != 20 {
|
||||
c.prog.Err = errors.New("invalid script hash")
|
||||
var buf []byte
|
||||
if name == "AppCall" {
|
||||
buf = c.getByteArray(expr.Args[0])
|
||||
if len(buf) != 20 {
|
||||
c.prog.Err = errors.New("invalid script hash")
|
||||
}
|
||||
} else {
|
||||
// Zeroes for DynAppCall.
|
||||
buf = make([]byte, 20)
|
||||
}
|
||||
|
||||
c.prog.WriteBytes(buf)
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"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/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -52,6 +53,19 @@ func TestFromAddress(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestAppCall(t *testing.T) {
|
||||
const srcDynApp = `
|
||||
package foo
|
||||
import "github.com/nspcc-dev/neo-go/pkg/interop/engine"
|
||||
func Main(h []byte) []byte {
|
||||
x := []byte{1, 2}
|
||||
y := []byte{3, 4}
|
||||
result := engine.DynAppCall(h, x, y)
|
||||
return result.([]byte)
|
||||
}
|
||||
`
|
||||
|
||||
var hasDynamicInvoke bool
|
||||
|
||||
srcInner := `
|
||||
package foo
|
||||
func Main(a []byte, b []byte) []byte {
|
||||
|
@ -62,14 +76,31 @@ func TestAppCall(t *testing.T) {
|
|||
inner, err := compiler.Compile(strings.NewReader(srcInner))
|
||||
require.NoError(t, err)
|
||||
|
||||
dynapp, err := compiler.Compile(strings.NewReader(srcDynApp))
|
||||
require.NoError(t, err)
|
||||
|
||||
ih := hash.Hash160(inner)
|
||||
dh := hash.Hash160(dynapp)
|
||||
getScript := func(u util.Uint160) ([]byte, bool) {
|
||||
if u.Equals(ih) {
|
||||
return inner, true
|
||||
}
|
||||
if u.Equals(dh) {
|
||||
return dynapp, hasDynamicInvoke
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
dynEntryScript := `
|
||||
package foo
|
||||
import "github.com/nspcc-dev/neo-go/pkg/interop/engine"
|
||||
func Main(h []byte) interface{} {
|
||||
return engine.AppCall(` + fmt.Sprintf("%#v", dh.BytesBE()) + `, h)
|
||||
}
|
||||
`
|
||||
dynentry, err := compiler.Compile(strings.NewReader(dynEntryScript))
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("valid script", func(t *testing.T) {
|
||||
src := getAppCallScript(fmt.Sprintf("%#v", ih.BytesBE()))
|
||||
v := vmAndCompile(t, src)
|
||||
|
@ -118,6 +149,38 @@ func TestAppCall(t *testing.T) {
|
|||
|
||||
assertResult(t, v, []byte{1, 2, 3, 4})
|
||||
})
|
||||
|
||||
t.Run("dynamic", func(t *testing.T) {
|
||||
t.Run("valid script", func(t *testing.T) {
|
||||
hasDynamicInvoke = true
|
||||
v := vm.New()
|
||||
v.Load(dynentry)
|
||||
v.SetScriptGetter(getScript)
|
||||
v.Estack().PushVal(ih.BytesBE())
|
||||
|
||||
require.NoError(t, v.Run())
|
||||
|
||||
assertResult(t, v, []byte{1, 2, 3, 4})
|
||||
})
|
||||
t.Run("invalid script", func(t *testing.T) {
|
||||
hasDynamicInvoke = true
|
||||
v := vm.New()
|
||||
v.Load(dynentry)
|
||||
v.SetScriptGetter(getScript)
|
||||
v.Estack().PushVal([]byte{1})
|
||||
|
||||
require.Error(t, v.Run())
|
||||
})
|
||||
t.Run("no dynamic invoke", func(t *testing.T) {
|
||||
hasDynamicInvoke = false
|
||||
v := vm.New()
|
||||
v.Load(dynentry)
|
||||
v.SetScriptGetter(getScript)
|
||||
v.Estack().PushVal(ih.BytesBE())
|
||||
|
||||
require.Error(t, v.Run())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func getAppCallScript(h string) string {
|
||||
|
|
|
@ -54,3 +54,13 @@ func GetEntryScriptHash() []byte {
|
|||
func AppCall(scriptHash []byte, args ...interface{}) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DynAppCall executes previously deployed blockchain contract with specified
|
||||
// hash (160 bit in BE form represented as 20-byte slice) using provided
|
||||
// arguments. It returns whatever this contract returns. It differs from AppCall
|
||||
// in that you can use it for truly dynamic scriptHash values, but at the same
|
||||
// time using it requires HasDynamicInvoke property set for a contract doing
|
||||
// this call. This function uses `APPCALL` opcode.
|
||||
func DynAppCall(scriptHash []byte, args ...interface{}) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue