forked from TrueCloudLab/neoneo-go
544f2c2cb2
We shouldn't use StoragePrice from Blockchain because its dao doesn't contain the whole set of changes from previouse transactions in the current block. Instead, we should use an updated storage price for each transaction and retrieve the price from cached DAO.
422 lines
11 KiB
Go
422 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/native"
|
|
"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.CompileWithOptions("foo.go", strings.NewReader(src), nil)
|
|
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.CompileWithOptions("bar.go", strings.NewReader(srcDeep), nil)
|
|
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.CompileWithOptions("foo.go", strings.NewReader(srcInner), nil)
|
|
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.Simple, 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), interop.DefaultBaseExecFee, native.DefaultStoragePrice, 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)
|
|
})
|
|
}
|