neo-go/pkg/compiler/interop_test.go

421 lines
11 KiB
Go

package compiler_test
import (
"errors"
"fmt"
"math/big"
"strings"
"testing"
"github.com/nspcc-dev/neo-go/internal/fakechain"
"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/encoding/address"
cinterop "github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
)
func TestTypeConstantSize(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/interop"
var a %T // type declaration is always ok
func Main() interface{} {
return %#v
}`
t.Run("Hash160", func(t *testing.T) {
t.Run("good", func(t *testing.T) {
a := make(cinterop.Hash160, 20)
src := fmt.Sprintf(src, a, a)
eval(t, src, []byte(a))
})
t.Run("bad", func(t *testing.T) {
a := make(cinterop.Hash160, 19)
src := fmt.Sprintf(src, a, a)
_, err := compiler.Compile("foo.go", strings.NewReader(src))
require.Error(t, err)
})
})
t.Run("Hash256", func(t *testing.T) {
t.Run("good", func(t *testing.T) {
a := make(cinterop.Hash256, 32)
src := fmt.Sprintf(src, a, a)
eval(t, src, []byte(a))
})
t.Run("bad", func(t *testing.T) {
a := make(cinterop.Hash256, 31)
src := fmt.Sprintf(src, a, a)
_, err := compiler.Compile("foo.go", strings.NewReader(src))
require.Error(t, err)
})
})
}
func TestFromAddress(t *testing.T) {
as1 := "NQRLhCpAru9BjGsMwk67vdMwmzKMRgsnnN"
addr1, err := address.StringToUint160(as1)
require.NoError(t, err)
as2 := "NPAsqZkx9WhNd4P72uhZxBhLinSuNkxfB8"
addr2, err := address.StringToUint160(as2)
require.NoError(t, err)
t.Run("append 2 addresses", func(t *testing.T) {
src := `
package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/util"
func Main() []byte {
addr1 := util.FromAddress("` + as1 + `")
addr2 := util.FromAddress("` + as2 + `")
sum := append(addr1, addr2...)
return sum
}
`
eval(t, src, append(addr1.BytesBE(), addr2.BytesBE()...))
})
t.Run("append 2 addresses inline", func(t *testing.T) {
src := `
package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/util"
func Main() []byte {
addr1 := util.FromAddress("` + as1 + `")
sum := append(addr1, util.FromAddress("` + as2 + `")...)
return sum
}
`
eval(t, src, append(addr1.BytesBE(), addr2.BytesBE()...))
})
t.Run("AliasPackage", func(t *testing.T) {
src := `
package foo
import uu "github.com/nspcc-dev/neo-go/pkg/interop/util"
func Main() []byte {
addr1 := uu.FromAddress("` + as1 + `")
addr2 := uu.FromAddress("` + as2 + `")
sum := append(addr1, addr2...)
return sum
}`
eval(t, src, append(addr1.BytesBE(), addr2.BytesBE()...))
})
}
func TestAbort(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/util"
func Main() int {
util.Abort()
return 1
}`
v := vmAndCompile(t, src)
require.Error(t, v.Run())
require.True(t, v.HasFailed())
}
func spawnVM(t *testing.T, ic *interop.Context, src string) *vm.VM {
b, di, err := compiler.CompileWithDebugInfo("foo.go", strings.NewReader(src))
require.NoError(t, err)
v := core.SpawnVM(ic)
invokeMethod(t, testMainIdent, b.Script, v, di)
v.LoadScriptWithFlags(b.Script, callflag.All)
return v
}
func TestAppCall(t *testing.T) {
srcDeep := `package foo
func Get42() int {
return 42
}`
barCtr, di, err := compiler.CompileWithDebugInfo("bar.go", strings.NewReader(srcDeep))
require.NoError(t, err)
mBar, err := di.ConvertToManifest(&compiler.Options{Name: "Bar"})
require.NoError(t, err)
barH := hash.Hash160(barCtr.Script)
srcInner := `package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/contract"
import "github.com/nspcc-dev/neo-go/pkg/interop"
var a int = 3
func Main(a []byte, b []byte) []byte {
panic("Main was called")
}
func Append(a []byte, b []byte) []byte {
return append(a, b...)
}
func Add3(n int) int {
return a + n
}
func CallInner() int {
return contract.Call(%s, "get42", contract.All).(int)
}`
srcInner = fmt.Sprintf(srcInner,
fmt.Sprintf("%#v", cinterop.Hash160(barH.BytesBE())))
inner, di, err := compiler.CompileWithDebugInfo("foo.go", strings.NewReader(srcInner))
require.NoError(t, err)
m, err := di.ConvertToManifest(&compiler.Options{
Name: "Foo",
Permissions: []manifest.Permission{
*manifest.NewPermission(manifest.PermissionWildcard),
},
})
require.NoError(t, err)
ih := hash.Hash160(inner.Script)
var contractGetter = func(_ dao.DAO, h util.Uint160) (*state.Contract, error) {
if h.Equals(ih) {
return &state.Contract{
ContractBase: state.ContractBase{
Hash: ih,
NEF: *inner,
Manifest: *m,
},
}, nil
} else if h.Equals(barH) {
return &state.Contract{
ContractBase: state.ContractBase{
Hash: barH,
NEF: *barCtr,
Manifest: *mBar,
},
}, nil
}
return nil, errors.New("not found")
}
fc := fakechain.NewFakeChain()
ic := interop.NewContext(trigger.Application, fc, dao.NewSimple(storage.NewMemoryStore(), false, false), contractGetter, nil, nil, nil, zaptest.NewLogger(t))
t.Run("valid script", func(t *testing.T) {
src := getAppCallScript(fmt.Sprintf("%#v", ih.BytesBE()))
v := spawnVM(t, ic, src)
require.NoError(t, v.Run())
assertResult(t, v, []byte{1, 2, 3, 4})
})
t.Run("callEx, valid", func(t *testing.T) {
src := getCallExScript(fmt.Sprintf("%#v", ih.BytesBE()), "contract.ReadStates|contract.AllowCall")
v := spawnVM(t, ic, src)
require.NoError(t, v.Run())
assertResult(t, v, big.NewInt(42))
})
t.Run("callEx, missing flags", func(t *testing.T) {
src := getCallExScript(fmt.Sprintf("%#v", ih.BytesBE()), "contract.NoneFlag")
v := spawnVM(t, ic, src)
require.Error(t, v.Run())
})
t.Run("missing script", func(t *testing.T) {
h := ih
h[0] = ^h[0]
src := getAppCallScript(fmt.Sprintf("%#v", h.BytesBE()))
v := spawnVM(t, ic, src)
require.Error(t, v.Run())
})
t.Run("convert from string constant", func(t *testing.T) {
src := `
package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/contract"
const scriptHash = ` + fmt.Sprintf("%#v", string(ih.BytesBE())) + `
func Main() []byte {
x := []byte{1, 2}
y := []byte{3, 4}
result := contract.Call([]byte(scriptHash), "append", contract.All, x, y)
return result.([]byte)
}
`
v := spawnVM(t, ic, src)
require.NoError(t, v.Run())
assertResult(t, v, []byte{1, 2, 3, 4})
})
t.Run("convert from var", func(t *testing.T) {
src := `
package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/contract"
func Main() []byte {
x := []byte{1, 2}
y := []byte{3, 4}
var addr = []byte(` + fmt.Sprintf("%#v", string(ih.BytesBE())) + `)
result := contract.Call(addr, "append", contract.All, x, y)
return result.([]byte)
}
`
v := spawnVM(t, ic, src)
require.NoError(t, v.Run())
assertResult(t, v, []byte{1, 2, 3, 4})
})
t.Run("InitializedGlobals", func(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/contract"
func Main() int {
var addr = []byte(` + fmt.Sprintf("%#v", string(ih.BytesBE())) + `)
result := contract.Call(addr, "add3", contract.All, 39)
return result.(int)
}`
v := spawnVM(t, ic, src)
require.NoError(t, v.Run())
assertResult(t, v, big.NewInt(42))
})
t.Run("AliasPackage", func(t *testing.T) {
src := `package foo
import ee "github.com/nspcc-dev/neo-go/pkg/interop/contract"
func Main() int {
var addr = []byte(` + fmt.Sprintf("%#v", string(ih.BytesBE())) + `)
result := ee.Call(addr, "add3", ee.All, 39)
return result.(int)
}`
v := spawnVM(t, ic, src)
require.NoError(t, v.Run())
assertResult(t, v, big.NewInt(42))
})
}
func getAppCallScript(h string) string {
return `
package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/contract"
func Main() []byte {
x := []byte{1, 2}
y := []byte{3, 4}
result := contract.Call(` + h + `, "append", contract.All, x, y)
return result.([]byte)
}
`
}
func getCallExScript(h string, flags string) string {
return `package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/contract"
func Main() int {
result := contract.Call(` + h + `, "callInner", ` + flags + `)
return result.(int)
}`
}
func TestBuiltinDoesNotCompile(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/util"
func Main() bool {
a := 1
b := 2
return util.Equals(a, b)
}`
v := vmAndCompile(t, src)
ctx := v.Context()
retCount := 0
for op, _, err := ctx.Next(); err == nil; op, _, err = ctx.Next() {
if ctx.IP() >= len(ctx.Program()) {
break
}
if op == opcode.RET {
retCount++
}
}
require.Equal(t, 1, retCount)
}
func TestInteropPackage(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/block"
func Main() int {
b := block.Block{}
a := block.GetTransactionCount(b)
return a
}`
eval(t, src, big.NewInt(42))
}
func TestBuiltinPackage(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/util"
func Main() int {
if util.Equals(1, 2) { // always returns true
return 1
}
return 2
}`
eval(t, src, big.NewInt(1))
}
func TestLenForNil(t *testing.T) {
src := `
package foo
func Main() bool {
var a []int = nil
return len(a) == 0
}`
eval(t, src, true)
}
func TestCallTConversionErrors(t *testing.T) {
t.Run("variable hash", func(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/neogointernal"
func Main() int {
var hash string
return neogointernal.CallWithToken(hash, "method", 0).(int)
}`
_, err := compiler.Compile("foo.go", strings.NewReader(src))
require.Error(t, err)
})
t.Run("bad hash", func(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/neogointernal"
func Main() int {
return neogointernal.CallWithToken("badstring", "method", 0).(int)
}`
_, err := compiler.Compile("foo.go", strings.NewReader(src))
require.Error(t, err)
})
t.Run("variable method", func(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/neogointernal"
func Main() int {
var method string
return neogointernal.CallWithToken("\xf5\x63\xea\x40\xbc\x28\x3d\x4d\x0e\x05\xc4\x8e\xa3\x05\xb3\xf2\xa0\x73\x40\xef", method, 0).(int)
}`
_, err := compiler.Compile("foo.go", strings.NewReader(src))
require.Error(t, err)
})
t.Run("variable flags", func(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/neogointernal"
func Main() {
var flags int
neogointernal.CallWithTokenNoRet("\xf5\x63\xea\x40\xbc\x28\x3d\x4d\x0e\x05\xc4\x8e\xa3\x05\xb3\xf2\xa0\x73\x40\xef", "method", flags)
}`
_, err := compiler.Compile("foo.go", strings.NewReader(src))
require.Error(t, err)
})
}