2021-10-25 11:48:21 +00:00
package smartcontract
import (
2022-10-26 20:27:24 +00:00
"bytes"
2021-10-25 11:48:21 +00:00
"encoding/json"
"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 ,
} ,
2022-10-27 19:35:06 +00:00
manifest . Method {
Name : "zum" ,
Parameters : [ ] manifest . Parameter {
manifest . NewParameter ( "type" , smartcontract . IntegerType ) ,
manifest . NewParameter ( "typev" , smartcontract . IntegerType ) ,
manifest . NewParameter ( "func" , smartcontract . IntegerType ) ,
} ,
ReturnType : smartcontract . IntegerType ,
} ,
2021-10-25 11:48:21 +00:00
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 : "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 )
2022-02-22 16:27:32 +00:00
require . NoError ( t , os . WriteFile ( manifestFile , rawManifest , os . ModePerm ) )
2021-10-25 11:48:21 +00:00
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" )
2022-02-22 16:27:32 +00:00
require . NoError ( t , os . WriteFile ( cfgPath , [ ] byte ( rawCfg ) , os . ModePerm ) )
2021-10-25 11:48:21 +00:00
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 )
}
2022-10-27 19:35:06 +00:00
// Zum invokes ` + "`zum`" + ` method of contract.
func Zum ( typev int , typev_ int , funcv int ) int {
return neogointernal . CallWithToken ( Hash , "zum" , int ( contract . All ) , typev , typev_ , funcv ) . ( int )
}
2021-10-25 11:48:21 +00:00
// JustExecute invokes ` + "`justExecute`" + ` method of contract.
2023-04-03 10:34:24 +00:00
func JustExecute ( arr [ ] any ) {
2021-10-25 11:48:21 +00:00
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.
2023-04-03 10:34:24 +00:00
func OtherTypes ( ctr interop . Hash160 , tx interop . Hash256 , sig interop . Signature , data any ) bool {
2021-10-25 11:48:21 +00:00
return neogointernal . CallWithToken ( Hash , "otherTypes" , int ( contract . All ) , ctr , tx , sig , data ) . ( bool )
}
// 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.
2023-04-03 10:34:24 +00:00
func DoSomething ( bytes [ ] byte , str string ) any {
return neogointernal . CallWithToken ( Hash , "doSomething" , int ( contract . ReadStates ) , bytes , str ) . ( any )
2021-10-25 11:48:21 +00:00
}
// 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 )
}
`
2022-02-22 16:27:32 +00:00
data , err := os . ReadFile ( outFile )
2021-10-25 11:48:21 +00:00
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 ,
2022-10-26 20:27:24 +00:00
Safe : true ,
2021-10-25 11:48:21 +00:00
} ,
)
manifestFile := filepath . Join ( t . TempDir ( ) , "manifest.json" )
outFile := filepath . Join ( t . TempDir ( ) , "out.go" )
rawManifest , err := json . Marshal ( m )
require . NoError ( t , err )
2022-02-22 16:27:32 +00:00
require . NoError ( t , os . WriteFile ( manifestFile , rawManifest , os . ModePerm ) )
2021-10-25 11:48:21 +00:00
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 ( )
2022-10-26 20:27:24 +00:00
app . Commands = [ ] cli . Command { generateWrapperCmd , generateRPCWrapperCmd }
2021-10-25 11:48:21 +00:00
require . NoError ( t , app . Run ( [ ] string { "" , "generate-wrapper" ,
"--manifest" , manifestFile ,
"--out" , outFile ,
2022-02-25 07:27:51 +00:00
"--hash" , "0x" + h . StringLE ( ) ,
2021-10-25 11:48:21 +00:00
} ) )
2022-02-22 16:27:32 +00:00
data , err := os . ReadFile ( outFile )
2021-10-25 11:48:21 +00:00
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 {
2022-10-26 20:27:24 +00:00
return neogointernal . CallWithToken ( Hash , "get" , int ( contract . ReadOnly ) ) . ( int )
}
` , string ( data ) )
require . NoError ( t , app . Run ( [ ] string { "" , "generate-rpcwrapper" ,
"--manifest" , manifestFile ,
"--out" , outFile ,
"--hash" , "0x" + h . StringLE ( ) ,
} ) )
data , err = os . ReadFile ( outFile )
require . NoError ( t , err )
require . Equal ( t , ` // Package myspacecontract contains RPC wrappers for My space contract contract.
package myspacecontract
import (
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
2022-11-08 12:43:32 +00:00
"github.com/nspcc-dev/neo-go/pkg/util"
2022-10-26 20:27:24 +00:00
"math/big"
)
// Hash contains contract hash.
var Hash = util . Uint160 { 0x4 , 0x8 , 0x15 , 0x16 , 0x23 , 0x42 , 0x43 , 0x44 , 0x0 , 0x1 , 0xca , 0xfe , 0xba , 0xbe , 0xde , 0xad , 0xbe , 0xef , 0x3 , 0x4 }
// Invoker is used by ContractReader to call various safe methods.
type Invoker interface {
2023-04-03 10:34:24 +00:00
Call ( contract util . Uint160 , operation string , params ... any ) ( * result . Invoke , error )
2022-10-26 20:27:24 +00:00
}
// ContractReader implements safe contract methods.
type ContractReader struct {
invoker Invoker
}
// NewReader creates an instance of ContractReader using Hash and the given Invoker.
func NewReader ( invoker Invoker ) * ContractReader {
return & ContractReader { invoker }
}
// Get invokes `+"`get`"+` method of contract.
func ( c * ContractReader ) Get ( ) ( * big . Int , error ) {
return unwrap . BigInt ( c . invoker . Call ( Hash , "get" ) )
2021-10-25 11:48:21 +00:00
}
` , string ( data ) )
}
2022-10-26 20:27:24 +00:00
func TestGenerateRPCBindings ( t * testing . T ) {
tmpDir := t . TempDir ( )
app := cli . NewApp ( )
app . Commands = [ ] cli . Command { generateWrapperCmd , generateRPCWrapperCmd }
2022-11-08 12:43:32 +00:00
var checkBinding = func ( manifest string , hash string , good string ) {
t . Run ( manifest , func ( t * testing . T ) {
outFile := filepath . Join ( tmpDir , "out.go" )
require . NoError ( t , app . Run ( [ ] string { "" , "generate-rpcwrapper" ,
"--manifest" , manifest ,
"--out" , outFile ,
"--hash" , hash ,
} ) )
data , err := os . ReadFile ( outFile )
require . NoError ( t , err )
data = bytes . ReplaceAll ( data , [ ] byte ( "\r" ) , [ ] byte { } ) // Windows.
expected , err := os . ReadFile ( good )
require . NoError ( t , err )
expected = bytes . ReplaceAll ( expected , [ ] byte ( "\r" ) , [ ] byte { } ) // Windows.
require . Equal ( t , string ( expected ) , string ( data ) )
} )
}
2022-10-26 20:27:24 +00:00
2022-11-08 12:43:32 +00:00
checkBinding ( filepath . Join ( "testdata" , "nex" , "nex.manifest.json" ) ,
"0xa2a67f09e8cf22c6bfd5cea24adc0f4bf0a11aa8" ,
filepath . Join ( "testdata" , "nex" , "nex.go" ) )
checkBinding ( filepath . Join ( "testdata" , "nameservice" , "nns.manifest.json" ) ,
"0x50ac1c37690cc2cfc594472833cf57505d5f46de" ,
filepath . Join ( "testdata" , "nameservice" , "nns.go" ) )
checkBinding ( filepath . Join ( "testdata" , "gas" , "gas.manifest.json" ) ,
"0xd2a4cff31913016155e38e474a2c06d08be276cf" ,
filepath . Join ( "testdata" , "gas" , "gas.go" ) )
2022-11-08 14:00:26 +00:00
checkBinding ( filepath . Join ( "testdata" , "verifyrpc" , "verify.manifest.json" ) ,
2022-11-08 13:36:38 +00:00
"0x00112233445566778899aabbccddeeff00112233" ,
filepath . Join ( "testdata" , "verifyrpc" , "verify.go" ) )
2022-11-09 08:58:01 +00:00
checkBinding ( filepath . Join ( "testdata" , "nonepiter" , "iter.manifest.json" ) ,
"0x00112233445566778899aabbccddeeff00112233" ,
filepath . Join ( "testdata" , "nonepiter" , "iter.go" ) )
2022-10-26 20:27:24 +00:00
}
2022-11-11 11:59:01 +00:00
func TestAssistedRPCBindings ( t * testing . T ) {
tmpDir := t . TempDir ( )
app := cli . NewApp ( )
app . Commands = NewCommands ( )
var checkBinding = func ( source string ) {
t . Run ( source , func ( t * testing . T ) {
manifestF := filepath . Join ( tmpDir , "manifest.json" )
bindingF := filepath . Join ( tmpDir , "binding.yml" )
nefF := filepath . Join ( tmpDir , "out.nef" )
require . NoError ( t , app . Run ( [ ] string { "" , "contract" , "compile" ,
"--in" , source ,
"--config" , filepath . Join ( source , "config.yml" ) ,
"--manifest" , manifestF ,
"--bindings" , bindingF ,
"--out" , nefF ,
} ) )
outFile := filepath . Join ( tmpDir , "out.go" )
require . NoError ( t , app . Run ( [ ] string { "" , "contract" , "generate-rpcwrapper" ,
"--config" , bindingF ,
"--manifest" , manifestF ,
"--out" , outFile ,
"--hash" , "0x00112233445566778899aabbccddeeff00112233" ,
} ) )
data , err := os . ReadFile ( outFile )
require . NoError ( t , err )
data = bytes . ReplaceAll ( data , [ ] byte ( "\r" ) , [ ] byte { } ) // Windows.
expected , err := os . ReadFile ( filepath . Join ( source , "rpcbindings.out" ) )
require . NoError ( t , err )
expected = bytes . ReplaceAll ( expected , [ ] byte ( "\r" ) , [ ] byte { } ) // Windows.
require . Equal ( t , string ( expected ) , string ( data ) )
} )
}
checkBinding ( filepath . Join ( "testdata" , "types" ) )
2022-11-25 10:04:24 +00:00
checkBinding ( filepath . Join ( "testdata" , "structs" ) )
2022-11-11 11:59:01 +00:00
}
2021-10-25 11:48:21 +00:00
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 )
}
2022-08-22 10:41:57 +00:00
t . Run ( "invalid hash" , func ( t * testing . T ) {
2022-03-22 11:56:50 +00:00
checkError ( t , "invalid contract hash" , "--hash" , "xxx" , "--manifest" , "yyy" , "--out" , "zzz" )
2022-08-22 10:41:57 +00:00
} )
2021-10-25 11:48:21 +00:00
t . Run ( "missing manifest argument" , func ( t * testing . T ) {
2022-03-22 11:56:50 +00:00
checkError ( t , "Required flag \"manifest\" not set" , "--hash" , util . Uint160 { } . StringLE ( ) , "--out" , "zzz" )
2021-10-25 11:48:21 +00:00
} )
t . Run ( "missing manifest file" , func ( t * testing . T ) {
2022-03-22 11:56:50 +00:00
checkError ( t , "can't read contract manifest" , "--manifest" , "notexists" , "--hash" , util . Uint160 { } . StringLE ( ) , "--out" , "zzz" )
2021-10-25 11:48:21 +00:00
} )
2022-08-22 10:41:57 +00:00
t . Run ( "empty manifest" , func ( t * testing . T ) {
2021-10-25 11:48:21 +00:00
manifestFile := filepath . Join ( t . TempDir ( ) , "invalid.json" )
2022-02-22 16:27:32 +00:00
require . NoError ( t , os . WriteFile ( manifestFile , [ ] byte ( "[]" ) , os . ModePerm ) )
2022-03-22 11:56:50 +00:00
checkError ( t , "json: cannot unmarshal array into Go value of type manifest.Manifest" , "--manifest" , manifestFile , "--hash" , util . Uint160 { } . StringLE ( ) , "--out" , "zzz" )
2022-08-22 10:41:57 +00:00
} )
t . Run ( "invalid manifest" , func ( t * testing . T ) {
manifestFile := filepath . Join ( t . TempDir ( ) , "invalid.json" )
m := manifest . NewManifest ( "MyContract" ) // no methods
rawManifest , err := json . Marshal ( m )
require . NoError ( t , err )
require . NoError ( t , os . WriteFile ( manifestFile , rawManifest , os . ModePerm ) )
2022-03-22 11:56:50 +00:00
checkError ( t , "ABI: no methods" , "--manifest" , manifestFile , "--hash" , util . Uint160 { } . StringLE ( ) , "--out" , "zzz" )
2021-10-25 11:48:21 +00:00
} )
manifestFile := filepath . Join ( t . TempDir ( ) , "manifest.json" )
m := manifest . NewManifest ( "MyContract" )
2022-08-22 10:41:57 +00:00
m . ABI . Methods = append ( m . ABI . Methods , manifest . Method {
Name : "method0" ,
Offset : 0 ,
ReturnType : smartcontract . AnyType ,
Safe : true ,
} )
2021-10-25 11:48:21 +00:00
rawManifest , err := json . Marshal ( m )
require . NoError ( t , err )
2022-02-22 16:27:32 +00:00
require . NoError ( t , os . WriteFile ( manifestFile , rawManifest , os . ModePerm ) )
2021-10-25 11:48:21 +00:00
t . Run ( "missing config" , func ( t * testing . T ) {
checkError ( t , "can't read config file" ,
"--manifest" , manifestFile , "--hash" , util . Uint160 { } . StringLE ( ) ,
2022-03-22 11:56:50 +00:00
"--config" , filepath . Join ( t . TempDir ( ) , "not.exists.yml" ) , "--out" , "zzz" )
2021-10-25 11:48:21 +00:00
} )
t . Run ( "invalid config" , func ( t * testing . T ) {
rawCfg := ` package : wrapper
callflags :
someFunc : ReadSometimes
`
cfgPath := filepath . Join ( t . TempDir ( ) , "binding.yml" )
2022-02-22 16:27:32 +00:00
require . NoError ( t , os . WriteFile ( cfgPath , [ ] byte ( rawCfg ) , os . ModePerm ) )
2021-10-25 11:48:21 +00:00
checkError ( t , "can't parse config file" ,
"--manifest" , manifestFile , "--hash" , util . Uint160 { } . StringLE ( ) ,
2022-03-22 11:56:50 +00:00
"--config" , cfgPath , "--out" , "zzz" )
2021-10-25 11:48:21 +00:00
} )
}