tutus-chain/pkg/compiler/convert_test.go

364 lines
8.3 KiB
Go
Executable File

package compiler_test
import (
"bytes"
"encoding/binary"
"fmt"
"math/big"
"strings"
"testing"
"git.marketally.com/tutus-one/tutus-chain/pkg/compiler"
"git.marketally.com/tutus-one/tutus-chain/pkg/smartcontract/trigger"
"git.marketally.com/tutus-one/tutus-chain/pkg/vm"
"git.marketally.com/tutus-one/tutus-chain/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)
type convertTestCase struct {
returnType string
argValue string
result any
}
func getFunctionName(typ string) string {
switch typ {
case "bool":
return "Bool"
case "[]byte":
return "Bytes"
case "int":
return "Integer"
}
panic("invalid type")
}
func TestConvert(t *testing.T) {
srcTmpl := `func F%d() %s {
arg := %s
return convert.To%s(arg)
}
`
convertTestCases := []convertTestCase{
{"bool", "true", true},
{"bool", "false", false},
{"bool", "12", true},
{"bool", "0", false},
{"bool", "[]byte{0, 1, 0}", true},
{"bool", "[]byte{0}", true},
{"bool", `""`, false},
{"int", "true", big.NewInt(1)},
{"int", "false", big.NewInt(0)},
{"int", "12", big.NewInt(12)},
{"int", "0", big.NewInt(0)},
{"int", "[]byte{0, 1, 0}", big.NewInt(256)},
{"int", "[]byte{0}", big.NewInt(0)},
{"[]byte", "true", []byte{1}},
{"[]byte", "false", []byte{0}},
{"[]byte", "12", []byte{0x0C}},
{"[]byte", "0", []byte{}},
{"[]byte", "[]byte{0, 1, 0}", []byte{0, 1, 0}},
}
srcBuilder := bytes.NewBuffer([]byte(`package testcase
import "git.marketally.com/tutus-one/tutus-chain/pkg/interop/convert"
`))
for i, tc := range convertTestCases {
name := getFunctionName(tc.returnType)
_, err := fmt.Fprintf(srcBuilder, srcTmpl, i, tc.returnType, tc.argValue, name)
require.NoError(t, err)
}
ne, di, err := compiler.CompileWithOptions("file.go", strings.NewReader(srcBuilder.String()), nil)
require.NoError(t, err)
for i, tc := range convertTestCases {
v := vm.New()
t.Run(tc.argValue+getFunctionName(tc.returnType), func(t *testing.T) {
v.Reset(trigger.Application)
invokeMethod(t, fmt.Sprintf("F%d", i), ne.Script, v, di)
runAndCheck(t, v, tc.result)
})
}
}
func TestTypeAssertion(t *testing.T) {
t.Run("inside return statement", func(t *testing.T) {
src := `package foo
func Main() int {
a := []byte{1}
var u any
u = a
return u.(int)
}`
eval(t, src, big.NewInt(1))
})
t.Run("inside general declaration", func(t *testing.T) {
src := `package foo
func Main() int {
a := []byte{1}
var u any
u = a
var ret = u.(int)
return ret
}`
eval(t, src, big.NewInt(1))
})
t.Run("inside assignment statement", func(t *testing.T) {
src := `package foo
func Main() int {
a := []byte{1}
var u any
u = a
var ret int
ret = u.(int)
return ret
}`
eval(t, src, big.NewInt(1))
})
t.Run("inside definition statement", func(t *testing.T) {
src := `package foo
func Main() int {
a := []byte{1}
var u any
u = a
ret := u.(int)
return ret
}`
eval(t, src, big.NewInt(1))
})
}
func TestTypeAssertionWithOK(t *testing.T) {
t.Run("inside general declaration", func(t *testing.T) {
src := `package foo
func Main() bool {
a := 1
var u any
u = a
var _, ok = u.(int) // *ast.GenDecl
return ok
}`
_, _, err := compiler.CompileWithOptions("foo.go", strings.NewReader(src), nil)
require.Error(t, err)
require.ErrorIs(t, err, compiler.ErrUnsupportedTypeAssertion)
})
t.Run("inside assignment statement", func(t *testing.T) {
src := `package foo
func Main() bool {
a := 1
var u any
u = a
var ok bool
_, ok = u.(int) // *ast.AssignStmt
return ok
}`
_, _, err := compiler.CompileWithOptions("foo.go", strings.NewReader(src), nil)
require.Error(t, err)
require.ErrorIs(t, err, compiler.ErrUnsupportedTypeAssertion)
})
t.Run("inside definition statement", func(t *testing.T) {
src := `package foo
func Main() bool {
a := 1
var u any
u = a
_, ok := u.(int) // *ast.AssignStmt
return ok
}`
_, _, err := compiler.CompileWithOptions("foo.go", strings.NewReader(src), nil)
require.Error(t, err)
require.ErrorIs(t, err, compiler.ErrUnsupportedTypeAssertion)
})
}
func TestTypeConversion(t *testing.T) {
src := `package foo
type myInt int
func Main() int32 {
var a int32 = 41
b := myInt(a)
incMy := func(x myInt) myInt { return x + 1 }
c := incMy(b)
return int32(c)
}`
eval(t, src, big.NewInt(42))
}
func TestSelectorTypeConversion(t *testing.T) {
src := `package foo
import "git.marketally.com/tutus-one/tutus-chain/pkg/compiler/testdata/types"
import "git.marketally.com/tutus-one/tutus-chain/pkg/interop/util"
import "git.marketally.com/tutus-one/tutus-chain/pkg/interop"
func Main() int {
var a int
if util.Equals(types.Buffer(nil), nil) {
a += 1
}
// Buffer != ByteArray
if util.Equals(types.Buffer("\x12"), "\x12") {
a += 10
}
tmp := []byte{0x23}
if util.Equals(types.ByteString(tmp), "\x23") {
a += 100
}
addr := "aaaaaaaaaaaaaaaaaaaa"
buf := []byte(addr)
if util.Equals(interop.Hash160(addr), interop.Hash160(buf)) {
a += 1000
}
return a
}`
eval(t, src, big.NewInt(1101))
}
func TestTypeConversionString(t *testing.T) {
src := `package foo
type mystr string
func Main() mystr {
b := []byte{'l', 'a', 'm', 'a', 'o'}
s := mystr(b)
b[0] = 'u'
return s
}`
eval(t, src, []byte("lamao"))
}
func TestInterfaceTypeConversion(t *testing.T) {
src := `package foo
func Main() int {
a := 1
b := any(a).(int)
return b
}`
eval(t, src, big.NewInt(1))
}
func TestConvert_Uint(t *testing.T) {
nums := []uint64{
0,
1,
127,
128,
255,
256,
1024,
1<<16 - 1,
1 << 16,
1<<32 - 1,
1 << 32,
1<<64 - 1,
}
var (
countUint64 = len(nums)
countUint32 = len(nums) - 2
countUint16 = len(nums) - 4
countUint8 = 5
)
testCases := []struct {
nums []uint64
offset int
typ string
helperType string
endians map[string]func([]byte, uint64)
}{
{
nums: nums[:countUint64],
typ: "uint64",
helperType: "Uint64",
endians: map[string]func([]byte, uint64){
"LE": binary.LittleEndian.PutUint64,
"BE": binary.BigEndian.PutUint64,
},
},
{
nums: nums[:countUint32],
offset: 4,
typ: "uint32",
helperType: "Uint32",
endians: map[string]func([]byte, uint64){
"LE": binary.LittleEndian.PutUint64,
"BE": binary.BigEndian.PutUint64,
},
},
{
nums: nums[:countUint16],
offset: 6,
typ: "uint16",
helperType: "Uint16",
endians: map[string]func([]byte, uint64){
"LE": binary.LittleEndian.PutUint64,
"BE": binary.BigEndian.PutUint64,
},
},
{
nums: nums[:countUint8],
offset: 7,
typ: "uint8",
helperType: "Uint8",
endians: map[string]func([]byte, uint64){
"": binary.LittleEndian.PutUint64,
},
},
}
srcToBytesTmpl := `package foo
import "git.marketally.com/tutus-one/tutus-chain/pkg/interop/convert"
func Main(args []any) []byte {
return convert.%sToBytes%s(args[0].(%s))
}
`
srcFromBytesTmpl := `package foo
import "git.marketally.com/tutus-one/tutus-chain/pkg/interop/convert"
func Main(args []any) %s {
return convert.Bytes%sTo%s(args[0].([]byte))
}
`
srcCompatibilityCheckTmpl := `package foo
import "git.marketally.com/tutus-one/tutus-chain/pkg/interop/convert"
func Main(args []any) %s {
data := convert.%sToBytes%s(args[0].(%s))
return convert.Bytes%sTo%s(data)
}
`
for _, tc := range testCases {
for endian, convertFunc := range tc.endians {
srcToBytes := fmt.Sprintf(srcToBytesTmpl, tc.helperType, endian, tc.typ)
srcFromBytes := fmt.Sprintf(srcFromBytesTmpl, tc.typ, endian, tc.helperType)
srcCompatibilityCheck := fmt.Sprintf(srcCompatibilityCheckTmpl,
tc.typ, tc.helperType, endian, tc.typ, endian, tc.helperType,
)
buf := make([]byte, 8)
data := buf[:8-tc.offset]
if endian == "BE" {
data = buf[tc.offset:]
}
for _, num := range tc.nums {
convertFunc(buf, num)
evalWithArgs(t, srcToBytes, nil, []stackitem.Item{stackitem.Make(num)}, data)
evalWithArgs(t, srcFromBytes, nil, []stackitem.Item{stackitem.Make(data)},
new(big.Int).SetUint64(num),
)
evalWithArgs(t, srcCompatibilityCheck, nil, []stackitem.Item{stackitem.Make(num)},
new(big.Int).SetUint64(num),
)
}
}
}
}