331 lines
11 KiB
Go
331 lines
11 KiB
Go
|
package smartcontract
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"io/ioutil"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
"testing"
|
||
|
|
||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||
|
"github.com/stretchr/testify/require"
|
||
|
"github.com/urfave/cli"
|
||
|
)
|
||
|
|
||
|
func TestGenerate(t *testing.T) {
|
||
|
m := manifest.NewManifest("MyContract")
|
||
|
m.ABI.Methods = append(m.ABI.Methods,
|
||
|
manifest.Method{
|
||
|
Name: manifest.MethodDeploy,
|
||
|
ReturnType: smartcontract.VoidType,
|
||
|
},
|
||
|
manifest.Method{
|
||
|
Name: "sum",
|
||
|
Parameters: []manifest.Parameter{
|
||
|
manifest.NewParameter("first", smartcontract.IntegerType),
|
||
|
manifest.NewParameter("second", smartcontract.IntegerType),
|
||
|
},
|
||
|
ReturnType: smartcontract.IntegerType,
|
||
|
},
|
||
|
manifest.Method{
|
||
|
Name: "sum", // overloaded method
|
||
|
Parameters: []manifest.Parameter{
|
||
|
manifest.NewParameter("first", smartcontract.IntegerType),
|
||
|
manifest.NewParameter("second", smartcontract.IntegerType),
|
||
|
manifest.NewParameter("third", smartcontract.IntegerType),
|
||
|
},
|
||
|
ReturnType: smartcontract.IntegerType,
|
||
|
},
|
||
|
manifest.Method{
|
||
|
Name: "sum3",
|
||
|
Parameters: []manifest.Parameter{},
|
||
|
ReturnType: smartcontract.IntegerType,
|
||
|
Safe: true,
|
||
|
},
|
||
|
manifest.Method{
|
||
|
Name: "justExecute",
|
||
|
Parameters: []manifest.Parameter{
|
||
|
manifest.NewParameter("arr", smartcontract.ArrayType),
|
||
|
},
|
||
|
ReturnType: smartcontract.VoidType,
|
||
|
},
|
||
|
manifest.Method{
|
||
|
Name: "getPublicKey",
|
||
|
Parameters: nil,
|
||
|
ReturnType: smartcontract.PublicKeyType,
|
||
|
},
|
||
|
manifest.Method{
|
||
|
Name: "otherTypes",
|
||
|
Parameters: []manifest.Parameter{
|
||
|
manifest.NewParameter("ctr", smartcontract.Hash160Type),
|
||
|
manifest.NewParameter("tx", smartcontract.Hash256Type),
|
||
|
manifest.NewParameter("sig", smartcontract.SignatureType),
|
||
|
manifest.NewParameter("data", smartcontract.AnyType),
|
||
|
},
|
||
|
ReturnType: smartcontract.BoolType,
|
||
|
},
|
||
|
manifest.Method{
|
||
|
Name: "emptyName",
|
||
|
Parameters: []manifest.Parameter{
|
||
|
manifest.NewParameter("", smartcontract.MapType),
|
||
|
},
|
||
|
ReturnType: smartcontract.AnyType,
|
||
|
},
|
||
|
manifest.Method{
|
||
|
Name: "searchStorage",
|
||
|
Parameters: []manifest.Parameter{
|
||
|
manifest.NewParameter("ctx", smartcontract.InteropInterfaceType),
|
||
|
},
|
||
|
ReturnType: smartcontract.InteropInterfaceType,
|
||
|
},
|
||
|
manifest.Method{
|
||
|
Name: "getFromMap",
|
||
|
Parameters: []manifest.Parameter{
|
||
|
manifest.NewParameter("intMap", smartcontract.MapType),
|
||
|
manifest.NewParameter("indices", smartcontract.ArrayType),
|
||
|
},
|
||
|
ReturnType: smartcontract.ArrayType,
|
||
|
},
|
||
|
manifest.Method{
|
||
|
Name: "doSomething",
|
||
|
Parameters: []manifest.Parameter{
|
||
|
manifest.NewParameter("bytes", smartcontract.ByteArrayType),
|
||
|
manifest.NewParameter("str", smartcontract.StringType),
|
||
|
},
|
||
|
ReturnType: smartcontract.InteropInterfaceType,
|
||
|
},
|
||
|
manifest.Method{
|
||
|
Name: "getBlockWrapper",
|
||
|
Parameters: []manifest.Parameter{},
|
||
|
ReturnType: smartcontract.InteropInterfaceType,
|
||
|
},
|
||
|
manifest.Method{
|
||
|
Name: "myFunc",
|
||
|
Parameters: []manifest.Parameter{
|
||
|
manifest.NewParameter("in", smartcontract.MapType),
|
||
|
},
|
||
|
ReturnType: smartcontract.ArrayType,
|
||
|
})
|
||
|
|
||
|
manifestFile := filepath.Join(t.TempDir(), "manifest.json")
|
||
|
outFile := filepath.Join(t.TempDir(), "out.go")
|
||
|
|
||
|
rawManifest, err := json.Marshal(m)
|
||
|
require.NoError(t, err)
|
||
|
require.NoError(t, ioutil.WriteFile(manifestFile, rawManifest, os.ModePerm))
|
||
|
|
||
|
h := util.Uint160{
|
||
|
0x04, 0x08, 0x15, 0x16, 0x23, 0x42, 0x43, 0x44, 0x00, 0x01,
|
||
|
0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF, 0x03, 0x04,
|
||
|
}
|
||
|
app := cli.NewApp()
|
||
|
app.Commands = []cli.Command{generateWrapperCmd}
|
||
|
|
||
|
rawCfg := `package: wrapper
|
||
|
hash: ` + h.StringLE() + `
|
||
|
overrides:
|
||
|
searchStorage.ctx: storage.Context
|
||
|
searchStorage: iterator.Iterator
|
||
|
getFromMap.intMap: "map[string]int"
|
||
|
getFromMap.indices: "[]string"
|
||
|
getFromMap: "[]int"
|
||
|
getBlockWrapper: ledger.Block
|
||
|
myFunc.in: "map[int]github.com/heyitsme/mycontract.Input"
|
||
|
myFunc: "[]github.com/heyitsme/mycontract.Output"
|
||
|
callflags:
|
||
|
doSomething: ReadStates
|
||
|
`
|
||
|
cfgPath := filepath.Join(t.TempDir(), "binding.yml")
|
||
|
require.NoError(t, ioutil.WriteFile(cfgPath, []byte(rawCfg), os.ModePerm))
|
||
|
|
||
|
require.NoError(t, app.Run([]string{"", "generate-wrapper",
|
||
|
"--manifest", manifestFile,
|
||
|
"--config", cfgPath,
|
||
|
"--out", outFile,
|
||
|
"--hash", h.StringLE(),
|
||
|
}))
|
||
|
|
||
|
const expected = `// Package wrapper contains wrappers for MyContract contract.
|
||
|
package wrapper
|
||
|
|
||
|
import (
|
||
|
"github.com/heyitsme/mycontract"
|
||
|
"github.com/nspcc-dev/neo-go/pkg/interop"
|
||
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||
|
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
|
||
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/ledger"
|
||
|
"github.com/nspcc-dev/neo-go/pkg/interop/neogointernal"
|
||
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||
|
)
|
||
|
|
||
|
// Hash contains contract hash in big-endian form.
|
||
|
const Hash = "\x04\x08\x15\x16\x23\x42\x43\x44\x00\x01\xca\xfe\xba\xbe\xde\xad\xbe\xef\x03\x04"
|
||
|
|
||
|
// Sum invokes ` + "`sum`" + ` method of contract.
|
||
|
func Sum(first int, second int) int {
|
||
|
return neogointernal.CallWithToken(Hash, "sum", int(contract.All), first, second).(int)
|
||
|
}
|
||
|
|
||
|
// Sum_3 invokes ` + "`sum`" + ` method of contract.
|
||
|
func Sum_3(first int, second int, third int) int {
|
||
|
return neogointernal.CallWithToken(Hash, "sum", int(contract.All), first, second, third).(int)
|
||
|
}
|
||
|
|
||
|
// Sum3 invokes ` + "`sum3`" + ` method of contract.
|
||
|
func Sum3() int {
|
||
|
return neogointernal.CallWithToken(Hash, "sum3", int(contract.ReadOnly)).(int)
|
||
|
}
|
||
|
|
||
|
// JustExecute invokes ` + "`justExecute`" + ` method of contract.
|
||
|
func JustExecute(arr []interface{}) {
|
||
|
neogointernal.CallWithTokenNoRet(Hash, "justExecute", int(contract.All), arr)
|
||
|
}
|
||
|
|
||
|
// GetPublicKey invokes ` + "`getPublicKey`" + ` method of contract.
|
||
|
func GetPublicKey() interop.PublicKey {
|
||
|
return neogointernal.CallWithToken(Hash, "getPublicKey", int(contract.All)).(interop.PublicKey)
|
||
|
}
|
||
|
|
||
|
// OtherTypes invokes ` + "`otherTypes`" + ` method of contract.
|
||
|
func OtherTypes(ctr interop.Hash160, tx interop.Hash256, sig interop.Signature, data interface{}) bool {
|
||
|
return neogointernal.CallWithToken(Hash, "otherTypes", int(contract.All), ctr, tx, sig, data).(bool)
|
||
|
}
|
||
|
|
||
|
// EmptyName invokes ` + "`emptyName`" + ` method of contract.
|
||
|
func EmptyName(arg0 map[string]interface{}) interface{} {
|
||
|
return neogointernal.CallWithToken(Hash, "emptyName", int(contract.All), arg0).(interface{})
|
||
|
}
|
||
|
|
||
|
// SearchStorage invokes ` + "`searchStorage`" + ` method of contract.
|
||
|
func SearchStorage(ctx storage.Context) iterator.Iterator {
|
||
|
return neogointernal.CallWithToken(Hash, "searchStorage", int(contract.All), ctx).(iterator.Iterator)
|
||
|
}
|
||
|
|
||
|
// GetFromMap invokes ` + "`getFromMap`" + ` method of contract.
|
||
|
func GetFromMap(intMap map[string]int, indices []string) []int {
|
||
|
return neogointernal.CallWithToken(Hash, "getFromMap", int(contract.All), intMap, indices).([]int)
|
||
|
}
|
||
|
|
||
|
// DoSomething invokes ` + "`doSomething`" + ` method of contract.
|
||
|
func DoSomething(bytes []byte, str string) interface{} {
|
||
|
return neogointernal.CallWithToken(Hash, "doSomething", int(contract.ReadStates), bytes, str).(interface{})
|
||
|
}
|
||
|
|
||
|
// GetBlockWrapper invokes ` + "`getBlockWrapper`" + ` method of contract.
|
||
|
func GetBlockWrapper() ledger.Block {
|
||
|
return neogointernal.CallWithToken(Hash, "getBlockWrapper", int(contract.All)).(ledger.Block)
|
||
|
}
|
||
|
|
||
|
// MyFunc invokes ` + "`myFunc`" + ` method of contract.
|
||
|
func MyFunc(in map[int]mycontract.Input) []mycontract.Output {
|
||
|
return neogointernal.CallWithToken(Hash, "myFunc", int(contract.All), in).([]mycontract.Output)
|
||
|
}
|
||
|
`
|
||
|
|
||
|
data, err := ioutil.ReadFile(outFile)
|
||
|
require.NoError(t, err)
|
||
|
require.Equal(t, expected, string(data))
|
||
|
}
|
||
|
|
||
|
func TestGenerateValidPackageName(t *testing.T) {
|
||
|
m := manifest.NewManifest("My space\tcontract")
|
||
|
m.ABI.Methods = append(m.ABI.Methods,
|
||
|
manifest.Method{
|
||
|
Name: "get",
|
||
|
Parameters: []manifest.Parameter{},
|
||
|
ReturnType: smartcontract.IntegerType,
|
||
|
},
|
||
|
)
|
||
|
|
||
|
manifestFile := filepath.Join(t.TempDir(), "manifest.json")
|
||
|
outFile := filepath.Join(t.TempDir(), "out.go")
|
||
|
|
||
|
rawManifest, err := json.Marshal(m)
|
||
|
require.NoError(t, err)
|
||
|
require.NoError(t, ioutil.WriteFile(manifestFile, rawManifest, os.ModePerm))
|
||
|
|
||
|
h := util.Uint160{
|
||
|
0x04, 0x08, 0x15, 0x16, 0x23, 0x42, 0x43, 0x44, 0x00, 0x01,
|
||
|
0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF, 0x03, 0x04,
|
||
|
}
|
||
|
app := cli.NewApp()
|
||
|
app.Commands = []cli.Command{generateWrapperCmd}
|
||
|
require.NoError(t, app.Run([]string{"", "generate-wrapper",
|
||
|
"--manifest", manifestFile,
|
||
|
"--out", outFile,
|
||
|
"--hash", h.StringLE(),
|
||
|
}))
|
||
|
|
||
|
data, err := ioutil.ReadFile(outFile)
|
||
|
require.NoError(t, err)
|
||
|
require.Equal(t, `// Package myspacecontract contains wrappers for My space contract contract.
|
||
|
package myspacecontract
|
||
|
|
||
|
import (
|
||
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||
|
"github.com/nspcc-dev/neo-go/pkg/interop/neogointernal"
|
||
|
)
|
||
|
|
||
|
// Hash contains contract hash in big-endian form.
|
||
|
const Hash = "\x04\x08\x15\x16\x23\x42\x43\x44\x00\x01\xca\xfe\xba\xbe\xde\xad\xbe\xef\x03\x04"
|
||
|
|
||
|
// Get invokes `+"`get`"+` method of contract.
|
||
|
func Get() int {
|
||
|
return neogointernal.CallWithToken(Hash, "get", int(contract.All)).(int)
|
||
|
}
|
||
|
`, string(data))
|
||
|
}
|
||
|
|
||
|
func TestGenerate_Errors(t *testing.T) {
|
||
|
app := cli.NewApp()
|
||
|
app.Commands = []cli.Command{generateWrapperCmd}
|
||
|
app.ExitErrHandler = func(*cli.Context, error) {}
|
||
|
|
||
|
checkError := func(t *testing.T, msg string, args ...string) {
|
||
|
// cli.ExitError doesn't implement wraping properly, so we check for an error message.
|
||
|
err := app.Run(append([]string{"", "generate-wrapper"}, args...))
|
||
|
require.True(t, strings.Contains(err.Error(), msg), "got: %v", err)
|
||
|
}
|
||
|
t.Run("missing manifest argument", func(t *testing.T) {
|
||
|
checkError(t, errNoManifestFile.Error())
|
||
|
})
|
||
|
t.Run("missing manifest file", func(t *testing.T) {
|
||
|
checkError(t, "can't read contract manifest", "--manifest", "notexists")
|
||
|
})
|
||
|
t.Run("invalid manifest", func(t *testing.T) {
|
||
|
manifestFile := filepath.Join(t.TempDir(), "invalid.json")
|
||
|
require.NoError(t, ioutil.WriteFile(manifestFile, []byte("[]"), os.ModePerm))
|
||
|
checkError(t, "", "--manifest", manifestFile)
|
||
|
})
|
||
|
|
||
|
manifestFile := filepath.Join(t.TempDir(), "manifest.json")
|
||
|
m := manifest.NewManifest("MyContract")
|
||
|
rawManifest, err := json.Marshal(m)
|
||
|
require.NoError(t, err)
|
||
|
require.NoError(t, ioutil.WriteFile(manifestFile, rawManifest, os.ModePerm))
|
||
|
|
||
|
t.Run("invalid hash", func(t *testing.T) {
|
||
|
checkError(t, "invalid contract hash", "--manifest", manifestFile, "--hash", "xxx")
|
||
|
})
|
||
|
t.Run("missing config", func(t *testing.T) {
|
||
|
checkError(t, "can't read config file",
|
||
|
"--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(),
|
||
|
"--config", filepath.Join(t.TempDir(), "not.exists.yml"))
|
||
|
})
|
||
|
t.Run("invalid config", func(t *testing.T) {
|
||
|
rawCfg := `package: wrapper
|
||
|
callflags:
|
||
|
someFunc: ReadSometimes
|
||
|
`
|
||
|
cfgPath := filepath.Join(t.TempDir(), "binding.yml")
|
||
|
require.NoError(t, ioutil.WriteFile(cfgPath, []byte(rawCfg), os.ModePerm))
|
||
|
|
||
|
checkError(t, "can't parse config file",
|
||
|
"--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(),
|
||
|
"--config", cfgPath)
|
||
|
})
|
||
|
}
|