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",
|
"SHA1", "Hash256", "Hash160",
|
||||||
"VerifySignature", "AppCall",
|
"VerifySignature", "AppCall",
|
||||||
"FromAddress", "Equals",
|
"FromAddress", "Equals",
|
||||||
"panic",
|
"panic", "DynAppCall",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1085,15 +1085,24 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) {
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.HASH160)
|
emit.Opcode(c.prog.BinWriter, opcode.HASH160)
|
||||||
case "VerifySignature":
|
case "VerifySignature":
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.VERIFY)
|
emit.Opcode(c.prog.BinWriter, opcode.VERIFY)
|
||||||
case "AppCall":
|
case "AppCall", "DynAppCall":
|
||||||
numArgs := len(expr.Args) - 1
|
numArgs := len(expr.Args)
|
||||||
|
if name == "AppCall" {
|
||||||
|
numArgs--
|
||||||
|
}
|
||||||
c.emitReverse(numArgs)
|
c.emitReverse(numArgs)
|
||||||
|
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.APPCALL)
|
emit.Opcode(c.prog.BinWriter, opcode.APPCALL)
|
||||||
buf := c.getByteArray(expr.Args[0])
|
var buf []byte
|
||||||
|
if name == "AppCall" {
|
||||||
|
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")
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Zeroes for DynAppCall.
|
||||||
|
buf = make([]byte, 20)
|
||||||
|
}
|
||||||
|
|
||||||
c.prog.WriteBytes(buf)
|
c.prog.WriteBytes(buf)
|
||||||
case "Equals":
|
case "Equals":
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"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/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -52,6 +53,19 @@ func TestFromAddress(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAppCall(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 := `
|
srcInner := `
|
||||||
package foo
|
package foo
|
||||||
func Main(a []byte, b []byte) []byte {
|
func Main(a []byte, b []byte) []byte {
|
||||||
|
@ -62,14 +76,31 @@ 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)
|
||||||
|
|
||||||
|
dynapp, err := compiler.Compile(strings.NewReader(srcDynApp))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
ih := hash.Hash160(inner)
|
ih := hash.Hash160(inner)
|
||||||
|
dh := hash.Hash160(dynapp)
|
||||||
getScript := func(u util.Uint160) ([]byte, bool) {
|
getScript := func(u util.Uint160) ([]byte, bool) {
|
||||||
if u.Equals(ih) {
|
if u.Equals(ih) {
|
||||||
return inner, true
|
return inner, true
|
||||||
}
|
}
|
||||||
|
if u.Equals(dh) {
|
||||||
|
return dynapp, hasDynamicInvoke
|
||||||
|
}
|
||||||
return nil, false
|
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) {
|
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 := vmAndCompile(t, src)
|
||||||
|
@ -118,6 +149,38 @@ func TestAppCall(t *testing.T) {
|
||||||
|
|
||||||
assertResult(t, v, []byte{1, 2, 3, 4})
|
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 {
|
func getAppCallScript(h string) string {
|
||||||
|
|
|
@ -54,3 +54,13 @@ func GetEntryScriptHash() []byte {
|
||||||
func AppCall(scriptHash []byte, args ...interface{}) interface{} {
|
func AppCall(scriptHash []byte, args ...interface{}) interface{} {
|
||||||
return nil
|
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