Merge pull request #742 from nspcc-dev/add-dynamic-appcall
vm: add support for dynamic invocations in APPCALL
This commit is contained in:
commit
abd7855890
6 changed files with 90 additions and 19 deletions
|
@ -63,11 +63,11 @@ func TestAppCall(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
ih := hash.Hash160(inner)
|
||||
getScript := func(u util.Uint160) []byte {
|
||||
getScript := func(u util.Uint160) ([]byte, bool) {
|
||||
if u.Equals(ih) {
|
||||
return inner
|
||||
return inner, true
|
||||
}
|
||||
return nil
|
||||
return nil, false
|
||||
}
|
||||
|
||||
t.Run("valid script", func(t *testing.T) {
|
||||
|
|
|
@ -1929,12 +1929,13 @@ func (bc *Blockchain) GetScriptHashesForVerifying(t *transaction.Transaction) ([
|
|||
// up for current blockchain.
|
||||
func (bc *Blockchain) spawnVMWithInterops(interopCtx *interopContext) *vm.VM {
|
||||
vm := vm.New()
|
||||
vm.SetScriptGetter(func(hash util.Uint160) []byte {
|
||||
vm.SetScriptGetter(func(hash util.Uint160) ([]byte, bool) {
|
||||
cs, err := interopCtx.dao.GetContractState(hash)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, false
|
||||
}
|
||||
return cs.Script
|
||||
hasDynamicInvoke := (cs.Properties & smartcontract.HasDynamicInvoke) != 0
|
||||
return cs.Script, hasDynamicInvoke
|
||||
})
|
||||
vm.RegisterInteropGetter(interopCtx.getSystemInterop)
|
||||
vm.RegisterInteropGetter(interopCtx.getNeoInterop)
|
||||
|
|
|
@ -35,6 +35,9 @@ type Context struct {
|
|||
|
||||
// Script hash of the prog.
|
||||
scriptHash util.Uint160
|
||||
|
||||
// Whether it's allowed to make dynamic calls from this context.
|
||||
hasDynamicInvoke bool
|
||||
}
|
||||
|
||||
var errNoInstParam = errors.New("failed to read instruction parameter")
|
||||
|
|
|
@ -174,7 +174,7 @@ func testFile(t *testing.T, filename string) {
|
|||
})
|
||||
}
|
||||
|
||||
func getScript(scripts []map[string]vmUTScript) func(util.Uint160) []byte {
|
||||
func getScript(scripts []map[string]vmUTScript) func(util.Uint160) ([]byte, bool) {
|
||||
store := make(map[util.Uint160][]byte)
|
||||
for i := range scripts {
|
||||
for _, v := range scripts[i] {
|
||||
|
@ -182,7 +182,7 @@ func getScript(scripts []map[string]vmUTScript) func(util.Uint160) []byte {
|
|||
}
|
||||
}
|
||||
|
||||
return func(a util.Uint160) []byte { return store[a] }
|
||||
return func(a util.Uint160) ([]byte, bool) { return store[a], true }
|
||||
}
|
||||
|
||||
func compareItems(t *testing.T, a, b StackItem) {
|
||||
|
|
38
pkg/vm/vm.go
38
pkg/vm/vm.go
|
@ -68,7 +68,7 @@ type VM struct {
|
|||
getPrice func(*VM, opcode.Opcode, []byte) util.Fixed8
|
||||
|
||||
// callback to get scripts.
|
||||
getScript func(util.Uint160) []byte
|
||||
getScript func(util.Uint160) ([]byte, bool)
|
||||
|
||||
istack *Stack // invocation stack.
|
||||
estack *Stack // execution stack.
|
||||
|
@ -266,9 +266,11 @@ func (v *VM) LoadScript(b []byte) {
|
|||
// loadScriptWithHash if similar to the LoadScript method, but it also loads
|
||||
// given script hash directly into the Context to avoid its recalculations. It's
|
||||
// up to user of this function to make sure the script and hash match each other.
|
||||
func (v *VM) loadScriptWithHash(b []byte, hash util.Uint160) {
|
||||
func (v *VM) loadScriptWithHash(b []byte, hash util.Uint160, hasDynamicInvoke bool) {
|
||||
v.LoadScript(b)
|
||||
v.istack.Top().Value().(*Context).scriptHash = hash
|
||||
ctx := v.Context()
|
||||
ctx.scriptHash = hash
|
||||
ctx.hasDynamicInvoke = hasDynamicInvoke
|
||||
}
|
||||
|
||||
// Context returns the current executed context. Nil if there is no context,
|
||||
|
@ -472,7 +474,7 @@ func (v *VM) SetCheckedHash(h []byte) {
|
|||
}
|
||||
|
||||
// SetScriptGetter sets the script getter for CALL instructions.
|
||||
func (v *VM) SetScriptGetter(gs func(util.Uint160) []byte) {
|
||||
func (v *VM) SetScriptGetter(gs func(util.Uint160) ([]byte, bool)) {
|
||||
v.getScript = gs
|
||||
}
|
||||
|
||||
|
@ -518,6 +520,27 @@ 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()
|
||||
fallthrough
|
||||
case opcode.CALLED, opcode.CALLEDT:
|
||||
if !ctx.hasDynamicInvoke {
|
||||
panic("contract is not allowed to make dynamic invocations")
|
||||
}
|
||||
}
|
||||
|
||||
if op >= opcode.PUSHBYTES1 && op <= opcode.PUSHBYTES75 {
|
||||
v.estack.PushVal(parameter)
|
||||
return
|
||||
|
@ -1150,7 +1173,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
panic(err)
|
||||
}
|
||||
|
||||
script := v.getScript(hash)
|
||||
script, hasDynamicInvoke := v.getScript(hash)
|
||||
if script == nil {
|
||||
panic("could not find script")
|
||||
}
|
||||
|
@ -1159,7 +1182,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
_ = v.istack.Pop()
|
||||
}
|
||||
|
||||
v.loadScriptWithHash(script, hash)
|
||||
v.loadScriptWithHash(script, hash, hasDynamicInvoke)
|
||||
|
||||
case opcode.RET:
|
||||
oldCtx := v.istack.Pop().Value().(*Context)
|
||||
|
@ -1354,12 +1377,13 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
script := v.getScript(hash)
|
||||
script, hasDynamicInvoke := v.getScript(hash)
|
||||
if script == nil {
|
||||
panic(fmt.Sprintf("could not find script %s", hash))
|
||||
}
|
||||
newCtx = NewContext(script)
|
||||
newCtx.scriptHash = hash
|
||||
newCtx.hasDynamicInvoke = hasDynamicInvoke
|
||||
}
|
||||
newCtx.rvcount = rvcount
|
||||
newCtx.estack = NewStack("evaluation")
|
||||
|
|
|
@ -1670,16 +1670,16 @@ func TestSIGNByteArray(t *testing.T) {
|
|||
|
||||
func TestAppCall(t *testing.T) {
|
||||
prog := []byte{byte(opcode.APPCALL)}
|
||||
hash := util.Uint160{}
|
||||
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 {
|
||||
vm.SetScriptGetter(func(in util.Uint160) ([]byte, bool) {
|
||||
if in.Equals(hash) {
|
||||
return makeProgram(opcode.DEPTH)
|
||||
return makeProgram(opcode.DEPTH), true
|
||||
}
|
||||
return nil
|
||||
return nil, false
|
||||
})
|
||||
vm.estack.PushVal(2)
|
||||
|
||||
|
@ -1688,6 +1688,49 @@ func TestAppCall(t *testing.T) {
|
|||
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) {
|
||||
progStr := "52c56b525a7c616516006c766b00527ac46203006c766b00c3616c756653c56b6c766b00527ac46c766b51527ac46203006c766b00c36c766b51c393616c7566"
|
||||
result := 12
|
||||
|
|
Loading…
Reference in a new issue