2020-01-27 07:59:57 +00:00
package compiler_test
import (
2020-12-13 15:26:35 +00:00
"errors"
2020-01-27 07:59:57 +00:00
"fmt"
2020-06-08 11:16:41 +00:00
"math/big"
2022-09-29 13:53:28 +00:00
"strconv"
2020-01-27 07:59:57 +00:00
"strings"
"testing"
2021-03-25 12:22:16 +00:00
"github.com/nspcc-dev/neo-go/internal/fakechain"
2020-03-03 14:21:42 +00:00
"github.com/nspcc-dev/neo-go/pkg/compiler"
2020-05-07 11:38:19 +00:00
"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"
2022-04-08 09:27:25 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/native"
2022-09-16 18:08:05 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
2020-05-07 11:38:19 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
2020-03-03 14:21:42 +00:00
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
2022-09-16 16:18:47 +00:00
"github.com/nspcc-dev/neo-go/pkg/encoding/base58"
2020-12-08 12:37:03 +00:00
cinterop "github.com/nspcc-dev/neo-go/pkg/interop"
2022-09-16 16:18:47 +00:00
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
2022-09-29 13:53:28 +00:00
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
2020-12-29 10:45:49 +00:00
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
2021-06-02 07:50:17 +00:00
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
2020-05-07 11:38:19 +00:00
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
2020-12-13 15:26:35 +00:00
"github.com/nspcc-dev/neo-go/pkg/util"
2020-05-07 11:38:19 +00:00
"github.com/nspcc-dev/neo-go/pkg/vm"
2020-06-08 11:16:41 +00:00
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
2022-09-16 16:18:47 +00:00
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
2020-01-27 07:59:57 +00:00
"github.com/stretchr/testify/require"
2020-05-07 11:38:19 +00:00
"go.uber.org/zap/zaptest"
2020-01-27 07:59:57 +00:00
)
2020-12-10 11:08:42 +00:00
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
2023-04-03 10:34:24 +00:00
func Main ( ) any {
2020-12-10 11:08:42 +00:00
return % # v
} `
t . Run ( "Hash160" , func ( t * testing . T ) {
t . Run ( "good" , func ( t * testing . T ) {
2022-10-05 07:46:21 +00:00
a := make ( cinterop . Hash160 , smartcontract . Hash160Len )
2020-12-10 11:08:42 +00:00
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 ) {
2022-10-05 07:46:21 +00:00
a := make ( cinterop . Hash256 , smartcontract . Hash256Len )
2020-12-10 11:08:42 +00:00
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 )
} )
} )
}
2020-01-27 08:53:47 +00:00
func TestFromAddress ( t * testing . T ) {
2020-06-16 10:47:29 +00:00
as1 := "NQRLhCpAru9BjGsMwk67vdMwmzKMRgsnnN"
2020-01-27 08:53:47 +00:00
addr1 , err := address . StringToUint160 ( as1 )
require . NoError ( t , err )
2020-06-16 10:47:29 +00:00
as2 := "NPAsqZkx9WhNd4P72uhZxBhLinSuNkxfB8"
2020-01-27 08:53:47 +00:00
addr2 , err := address . StringToUint160 ( as2 )
require . NoError ( t , err )
t . Run ( "append 2 addresses" , func ( t * testing . T ) {
src := `
package foo
2020-03-03 14:21:42 +00:00
import "github.com/nspcc-dev/neo-go/pkg/interop/util"
2020-01-27 08:53:47 +00:00
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
2020-03-03 14:21:42 +00:00
import "github.com/nspcc-dev/neo-go/pkg/interop/util"
2020-01-27 08:53:47 +00:00
func Main ( ) [ ] byte {
addr1 := util . FromAddress ( "` + as1 + `" )
sum := append ( addr1 , util . FromAddress ( "` + as2 + `" ) ... )
return sum
}
`
eval ( t , src , append ( addr1 . BytesBE ( ) , addr2 . BytesBE ( ) ... ) )
} )
2020-08-19 07:10:40 +00:00
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 ( ) ... ) )
} )
2020-01-27 08:53:47 +00:00
}
2022-09-16 16:18:47 +00:00
func TestAddressToHash160BuiltinConversion ( t * testing . T ) {
a := "NQRLhCpAru9BjGsMwk67vdMwmzKMRgsnnN"
h , err := address . StringToUint160 ( a )
require . NoError ( t , err )
t . Run ( "builtin conversion" , func ( t * testing . T ) {
src := ` package foo
import (
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/lib/address"
)
var addr = address . ToHash160 ( "` + a + `" )
func Main ( ) interop . Hash160 {
return addr
} `
prog := eval ( t , src , h . BytesBE ( ) )
// Address BE bytes expected to be present at program, which indicates that address conversion
// was performed at compile-time.
require . True ( t , strings . Contains ( string ( prog ) , string ( h . BytesBE ( ) ) ) )
// On the contrary, there should be no address string.
require . False ( t , strings . Contains ( string ( prog ) , a ) )
} )
t . Run ( "generate code" , func ( t * testing . T ) {
src := ` package foo
import (
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/lib/address"
)
var addr = "` + a + `"
func Main ( ) interop . Hash160 {
return address . ToHash160 ( addr )
} `
// Error on CALLT (std.Base58CheckDecode - method of StdLib native contract) is expected, which means
// that address.ToHash160 code was honestly generated by the compiler without any optimisations.
prog := evalWithError ( t , src , "(CALLT): runtime error: invalid memory address or nil pointer dereference" )
// Address BE bytes expected not to be present at program, which indicates that address conversion
// was not performed at compile-time.
require . False ( t , strings . Contains ( string ( prog ) , string ( h . BytesBE ( ) ) ) )
// On the contrary, there should be an address string.
require . True ( t , strings . Contains ( string ( prog ) , a ) )
} )
}
func TestInvokeAddressToFromHash160 ( t * testing . T ) {
a := "NQRLhCpAru9BjGsMwk67vdMwmzKMRgsnnN"
h , err := address . StringToUint160 ( a )
require . NoError ( t , err )
bc , acc := chain . NewSingle ( t )
e := neotest . NewExecutor ( t , bc , acc , acc )
src := ` package foo
import (
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/lib/address"
)
const addr = "` + a + `"
func ToHash160 ( a string ) interop . Hash160 {
return address . ToHash160 ( a )
}
func ToHash160AtCompileTime ( ) interop . Hash160 {
return address . ToHash160 ( addr )
}
func FromHash160 ( hash interop . Hash160 ) string {
return address . FromHash160 ( hash )
} `
ctr := neotest . CompileSource ( t , e . CommitteeHash , strings . NewReader ( src ) , & compiler . Options { Name : "Helper" } )
e . DeployContract ( t , ctr , nil )
c := e . CommitteeInvoker ( ctr . Hash )
t . Run ( "ToHash160" , func ( t * testing . T ) {
t . Run ( "invalid address length" , func ( t * testing . T ) {
c . InvokeFail ( t , "invalid address length" , "toHash160" , base58 . CheckEncode ( make ( [ ] byte , util . Uint160Size + 1 + 1 ) ) )
} )
t . Run ( "invalid prefix" , func ( t * testing . T ) {
c . InvokeFail ( t , "invalid address prefix" , "toHash160" , base58 . CheckEncode ( append ( [ ] byte { address . NEO2Prefix } , h . BytesBE ( ) ... ) ) )
} )
t . Run ( "good" , func ( t * testing . T ) {
c . Invoke ( t , stackitem . NewBuffer ( h . BytesBE ( ) ) , "toHash160" , a )
} )
} )
t . Run ( "ToHash160Constant" , func ( t * testing . T ) {
t . Run ( "good" , func ( t * testing . T ) {
c . Invoke ( t , stackitem . NewBuffer ( h . BytesBE ( ) ) , "toHash160AtCompileTime" )
} )
} )
t . Run ( "FromHash160" , func ( t * testing . T ) {
t . Run ( "good" , func ( t * testing . T ) {
c . Invoke ( t , stackitem . NewByteArray ( [ ] byte ( a ) ) , "fromHash160" , h . BytesBE ( ) )
} )
t . Run ( "invalid length" , func ( t * testing . T ) {
c . InvokeFail ( t , "invalid Hash160 length" , "fromHash160" , h . BytesBE ( ) [ : 15 ] )
} )
} )
}
2021-10-22 11:50:51 +00:00
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 ( ) )
}
2020-05-07 11:38:19 +00:00
func spawnVM ( t * testing . T , ic * interop . Context , src string ) * vm . VM {
2021-07-26 12:34:07 +00:00
b , di , err := compiler . CompileWithOptions ( "foo.go" , strings . NewReader ( src ) , nil )
2020-05-07 11:38:19 +00:00
require . NoError ( t , err )
v := core . SpawnVM ( ic )
2021-12-08 19:33:03 +00:00
invokeMethod ( t , testMainIdent , b . Script , v , di )
v . LoadScriptWithFlags ( b . Script , callflag . All )
2020-05-07 11:38:19 +00:00
return v
}
2020-01-27 07:59:57 +00:00
func TestAppCall ( t * testing . T ) {
2020-12-08 12:37:03 +00:00
srcDeep := ` package foo
func Get42 ( ) int {
return 42
} `
2021-07-26 12:34:07 +00:00
barCtr , di , err := compiler . CompileWithOptions ( "bar.go" , strings . NewReader ( srcDeep ) , nil )
2020-12-08 12:37:03 +00:00
require . NoError ( t , err )
2020-12-10 14:42:12 +00:00
mBar , err := di . ConvertToManifest ( & compiler . Options { Name : "Bar" } )
2020-12-08 12:37:03 +00:00
require . NoError ( t , err )
2021-12-08 19:33:03 +00:00
barH := hash . Hash160 ( barCtr . Script )
2020-12-08 12:37:03 +00:00
srcInner := ` package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/contract"
import "github.com/nspcc-dev/neo-go/pkg/interop"
2020-07-24 10:40:54 +00:00
var a int = 3
2020-02-10 07:53:55 +00:00
func Main ( a [ ] byte , b [ ] byte ) [ ] byte {
2020-07-23 15:13:02 +00:00
panic ( "Main was called" )
}
func Append ( a [ ] byte , b [ ] byte ) [ ] byte {
2020-02-10 07:53:55 +00:00
return append ( a , b ... )
2020-01-27 07:59:57 +00:00
}
2020-07-24 10:40:54 +00:00
func Add3 ( n int ) int {
return a + n
}
2020-12-08 12:37:03 +00:00
func CallInner ( ) int {
2020-12-29 10:44:07 +00:00
return contract . Call ( % s , "get42" , contract . All ) . ( int )
2020-12-08 12:37:03 +00:00
} `
srcInner = fmt . Sprintf ( srcInner ,
fmt . Sprintf ( "%#v" , cinterop . Hash160 ( barH . BytesBE ( ) ) ) )
2020-01-27 07:59:57 +00:00
2021-07-26 12:34:07 +00:00
inner , di , err := compiler . CompileWithOptions ( "foo.go" , strings . NewReader ( srcInner ) , nil )
2020-07-23 15:13:02 +00:00
require . NoError ( t , err )
2021-06-02 07:50:17 +00:00
m , err := di . ConvertToManifest ( & compiler . Options {
Name : "Foo" ,
Permissions : [ ] manifest . Permission {
* manifest . NewPermission ( manifest . PermissionWildcard ) ,
} ,
} )
2020-01-27 07:59:57 +00:00
require . NoError ( t , err )
2021-12-08 19:33:03 +00:00
ih := hash . Hash160 ( inner . Script )
2022-02-16 15:04:47 +00:00
var contractGetter = func ( _ * dao . Simple , h util . Uint160 ) ( * state . Contract , error ) {
2020-12-13 15:26:35 +00:00
if h . Equals ( ih ) {
return & state . Contract {
2021-02-09 09:02:38 +00:00
ContractBase : state . ContractBase {
Hash : ih ,
2021-12-08 19:33:03 +00:00
NEF : * inner ,
2021-02-09 09:02:38 +00:00
Manifest : * m ,
} ,
2020-12-13 15:26:35 +00:00
} , nil
} else if h . Equals ( barH ) {
return & state . Contract {
2021-02-09 09:02:38 +00:00
ContractBase : state . ContractBase {
Hash : barH ,
2021-12-08 19:33:03 +00:00
NEF : * barCtr ,
2021-02-09 09:02:38 +00:00
Manifest : * mBar ,
} ,
2020-12-13 15:26:35 +00:00
} , nil
}
return nil , errors . New ( "not found" )
}
2021-03-25 12:22:16 +00:00
fc := fakechain . NewFakeChain ( )
2022-04-12 14:29:11 +00:00
ic := interop . NewContext ( trigger . Application , fc , dao . NewSimple ( storage . NewMemoryStore ( ) , false , false ) ,
2022-06-06 18:53:03 +00:00
interop . DefaultBaseExecFee , native . DefaultStoragePrice , contractGetter , nil , nil , nil , nil , zaptest . NewLogger ( t ) )
2020-01-27 07:59:57 +00:00
t . Run ( "valid script" , func ( t * testing . T ) {
src := getAppCallScript ( fmt . Sprintf ( "%#v" , ih . BytesBE ( ) ) )
2020-05-07 11:38:19 +00:00
v := spawnVM ( t , ic , src )
2020-01-27 07:59:57 +00:00
require . NoError ( t , v . Run ( ) )
2020-02-10 07:53:55 +00:00
assertResult ( t , v , [ ] byte { 1 , 2 , 3 , 4 } )
2020-01-27 07:59:57 +00:00
} )
2020-12-08 12:37:03 +00:00
t . Run ( "callEx, valid" , func ( t * testing . T ) {
2021-02-25 15:12:36 +00:00
src := getCallExScript ( fmt . Sprintf ( "%#v" , ih . BytesBE ( ) ) , "contract.ReadStates|contract.AllowCall" )
2020-12-08 12:37:03 +00:00
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 ( ) )
} )
2020-01-27 07:59:57 +00:00
t . Run ( "missing script" , func ( t * testing . T ) {
h := ih
h [ 0 ] = ^ h [ 0 ]
src := getAppCallScript ( fmt . Sprintf ( "%#v" , h . BytesBE ( ) ) )
2020-05-07 11:38:19 +00:00
v := spawnVM ( t , ic , src )
2020-01-27 07:59:57 +00:00
require . Error ( t , v . Run ( ) )
} )
2020-01-27 12:30:36 +00:00
t . Run ( "convert from string constant" , func ( t * testing . T ) {
src := `
package foo
2020-12-08 11:11:06 +00:00
import "github.com/nspcc-dev/neo-go/pkg/interop/contract"
2020-01-27 12:30:36 +00:00
const scriptHash = ` + fmt.Sprintf("%#v", string(ih.BytesBE())) + `
2020-02-10 07:53:55 +00:00
func Main ( ) [ ] byte {
x := [ ] byte { 1 , 2 }
y := [ ] byte { 3 , 4 }
2020-12-29 10:44:07 +00:00
result := contract . Call ( [ ] byte ( scriptHash ) , "append" , contract . All , x , y )
2020-02-10 07:53:55 +00:00
return result . ( [ ] byte )
2020-01-27 12:30:36 +00:00
}
`
2020-05-07 11:38:19 +00:00
v := spawnVM ( t , ic , src )
2020-01-27 12:30:36 +00:00
require . NoError ( t , v . Run ( ) )
2020-02-10 07:53:55 +00:00
assertResult ( t , v , [ ] byte { 1 , 2 , 3 , 4 } )
2020-01-27 12:30:36 +00:00
} )
2020-07-08 14:37:42 +00:00
t . Run ( "convert from var" , func ( t * testing . T ) {
src := `
package foo
2020-12-08 11:11:06 +00:00
import "github.com/nspcc-dev/neo-go/pkg/interop/contract"
2020-07-08 14:37:42 +00:00
func Main ( ) [ ] byte {
x := [ ] byte { 1 , 2 }
y := [ ] byte { 3 , 4 }
var addr = [ ] byte ( ` + fmt.Sprintf("%#v", string(ih.BytesBE())) + ` )
2020-12-29 10:44:07 +00:00
result := contract . Call ( addr , "append" , contract . All , x , y )
2020-07-08 14:37:42 +00:00
return result . ( [ ] byte )
}
`
v := spawnVM ( t , ic , src )
require . NoError ( t , v . Run ( ) )
assertResult ( t , v , [ ] byte { 1 , 2 , 3 , 4 } )
} )
2020-07-24 10:40:54 +00:00
t . Run ( "InitializedGlobals" , func ( t * testing . T ) {
src := ` package foo
2020-12-08 11:11:06 +00:00
import "github.com/nspcc-dev/neo-go/pkg/interop/contract"
2020-07-24 10:40:54 +00:00
func Main ( ) int {
var addr = [ ] byte ( ` + fmt.Sprintf("%#v", string(ih.BytesBE())) + ` )
2020-12-29 10:44:07 +00:00
result := contract . Call ( addr , "add3" , contract . All , 39 )
2020-07-24 10:40:54 +00:00
return result . ( int )
} `
v := spawnVM ( t , ic , src )
require . NoError ( t , v . Run ( ) )
assertResult ( t , v , big . NewInt ( 42 ) )
} )
2020-08-19 07:10:40 +00:00
t . Run ( "AliasPackage" , func ( t * testing . T ) {
src := ` package foo
2020-12-08 11:11:06 +00:00
import ee "github.com/nspcc-dev/neo-go/pkg/interop/contract"
2020-08-19 07:10:40 +00:00
func Main ( ) int {
var addr = [ ] byte ( ` + fmt.Sprintf("%#v", string(ih.BytesBE())) + ` )
2020-12-29 10:44:07 +00:00
result := ee . Call ( addr , "add3" , ee . All , 39 )
2020-08-19 07:10:40 +00:00
return result . ( int )
} `
v := spawnVM ( t , ic , src )
require . NoError ( t , v . Run ( ) )
assertResult ( t , v , big . NewInt ( 42 ) )
} )
2020-01-27 07:59:57 +00:00
}
func getAppCallScript ( h string ) string {
return `
package foo
2020-12-08 11:11:06 +00:00
import "github.com/nspcc-dev/neo-go/pkg/interop/contract"
2020-02-10 07:53:55 +00:00
func Main ( ) [ ] byte {
x := [ ] byte { 1 , 2 }
y := [ ] byte { 3 , 4 }
2020-12-29 10:44:07 +00:00
result := contract . Call ( ` + h + ` , "append" , contract . All , x , y )
2020-02-10 07:53:55 +00:00
return result . ( [ ] byte )
2020-01-27 07:59:57 +00:00
}
`
}
2020-06-08 11:16:41 +00:00
2020-12-08 12:37:03 +00:00
func getCallExScript ( h string , flags string ) string {
return ` package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/contract"
func Main ( ) int {
2020-12-29 10:44:07 +00:00
result := contract . Call ( ` + h + ` , "callInner" , ` + flags + ` )
2020-12-08 12:37:03 +00:00
return result . ( int )
} `
}
2020-06-08 11:16:41 +00:00
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 ( ) {
2020-08-19 09:37:31 +00:00
if ctx . IP ( ) >= len ( ctx . Program ( ) ) {
2020-06-08 11:16:41 +00:00
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 ) )
}
2020-07-07 18:49:21 +00:00
func TestLenForNil ( t * testing . T ) {
src := `
package foo
func Main ( ) bool {
var a [ ] int = nil
return len ( a ) == 0
} `
eval ( t , src , true )
}
2021-12-08 19:33:03 +00:00
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 )
} )
}
2022-09-16 18:08:05 +00:00
func TestCallWithVersion ( t * testing . T ) {
bc , acc := chain . NewSingle ( t )
e := neotest . NewExecutor ( t , bc , acc , acc )
src := ` package foo
import (
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
util "github.com/nspcc-dev/neo-go/pkg/interop/lib/contract"
)
2023-04-03 10:34:24 +00:00
func CallWithVersion ( hash interop . Hash160 , version int , method string ) any {
2022-09-16 18:08:05 +00:00
return util . CallWithVersion ( hash , version , method , contract . All )
} `
ctr := neotest . CompileSource ( t , e . CommitteeHash , strings . NewReader ( src ) , & compiler . Options { Name : "Helper" } )
e . DeployContract ( t , ctr , nil )
c := e . CommitteeInvoker ( ctr . Hash )
policyH := state . CreateNativeContractHash ( nativenames . Policy )
t . Run ( "good" , func ( t * testing . T ) {
c . Invoke ( t , e . Chain . GetBaseExecFee ( ) , "callWithVersion" , policyH . BytesBE ( ) , 0 , "getExecFeeFactor" )
} )
t . Run ( "unknown contract" , func ( t * testing . T ) {
c . InvokeFail ( t , "unknown contract" , "callWithVersion" , util . Uint160 { 1 , 2 , 3 } . BytesBE ( ) , 0 , "getExecFeeFactor" )
} )
t . Run ( "invalid version" , func ( t * testing . T ) {
c . InvokeFail ( t , "contract version mismatch" , "callWithVersion" , policyH . BytesBE ( ) , 1 , "getExecFeeFactor" )
} )
}
2022-09-29 13:53:28 +00:00
func TestForcedNotifyArgumentsConversion ( t * testing . T ) {
const methodWithEllipsis = "withEllipsis"
const methodWithoutEllipsis = "withoutEllipsis"
check := func ( t * testing . T , method string , targetSCParamTypes [ ] smartcontract . ParamType , expectedVMParamTypes [ ] stackitem . Type , noEventsCheck bool ) {
bc , acc := chain . NewSingle ( t )
e := neotest . NewExecutor ( t , bc , acc , acc )
src := ` package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
const arg4 = 4 // Const value.
func WithoutEllipsis ( ) {
var arg0 int // Default value.
var arg1 int = 1 // Initialized value.
arg2 := 2 // Short decl.
var arg3 int
arg3 = 3 // Declare first, change value afterwards.
runtime . Notify ( "withoutEllipsis" , arg0 , arg1 , arg2 , arg3 , arg4 , 5 , f ( 6 ) ) // The fifth argument is basic literal.
}
func WithEllipsis ( ) {
2023-04-03 10:34:24 +00:00
arg := [ ] any { 0 , 1 , f ( 2 ) , 3 , 4 , 5 , 6 }
2022-09-29 13:53:28 +00:00
runtime . Notify ( "withEllipsis" , arg ... )
}
func f ( i int ) int {
return i
} `
count := len ( targetSCParamTypes )
if count != len ( expectedVMParamTypes ) {
t . Fatalf ( "parameters count mismatch: %d vs %d" , count , len ( expectedVMParamTypes ) )
}
scParams := make ( [ ] manifest . Parameter , len ( targetSCParamTypes ) )
vmParams := make ( [ ] stackitem . Item , len ( expectedVMParamTypes ) )
for i := range scParams {
scParams [ i ] = manifest . Parameter {
Name : strconv . Itoa ( i ) ,
Type : targetSCParamTypes [ i ] ,
}
defaultValue := stackitem . NewBigInteger ( big . NewInt ( int64 ( i ) ) )
var (
val stackitem . Item
err error
)
if expectedVMParamTypes [ i ] == stackitem . IntegerT {
val = defaultValue
} else {
val , err = defaultValue . Convert ( expectedVMParamTypes [ i ] ) // exactly the same conversion should be emitted by compiler and performed by the contract code.
require . NoError ( t , err )
}
vmParams [ i ] = val
}
ctr := neotest . CompileSource ( t , e . CommitteeHash , strings . NewReader ( src ) , & compiler . Options {
Name : "Helper" ,
ContractEvents : [ ] manifest . Event {
{
Name : methodWithoutEllipsis ,
Parameters : scParams ,
} ,
{
Name : methodWithEllipsis ,
Parameters : scParams ,
} ,
} ,
NoEventsCheck : noEventsCheck ,
} )
e . DeployContract ( t , ctr , nil )
c := e . CommitteeInvoker ( ctr . Hash )
t . Run ( method , func ( t * testing . T ) {
h := c . Invoke ( t , stackitem . Null { } , method )
aer := c . GetTxExecResult ( t , h )
require . Equal ( t , 1 , len ( aer . Events ) )
require . Equal ( t , stackitem . NewArray ( vmParams ) , aer . Events [ 0 ] . Item )
} )
}
checkSingleType := func ( t * testing . T , method string , targetSCEventType smartcontract . ParamType , expectedVMType stackitem . Type , noEventsCheck ... bool ) {
count := 7
scParams := make ( [ ] smartcontract . ParamType , count )
vmParams := make ( [ ] stackitem . Type , count )
for i := range scParams {
scParams [ i ] = targetSCEventType
vmParams [ i ] = expectedVMType
}
var noEvents bool
if len ( noEventsCheck ) > 0 {
noEvents = noEventsCheck [ 0 ]
}
check ( t , method , scParams , vmParams , noEvents )
}
t . Run ( "good, single type, default values" , func ( t * testing . T ) {
checkSingleType ( t , methodWithoutEllipsis , smartcontract . IntegerType , stackitem . IntegerT )
} )
t . Run ( "good, single type, conversion to BooleanT" , func ( t * testing . T ) {
checkSingleType ( t , methodWithoutEllipsis , smartcontract . BoolType , stackitem . BooleanT )
} )
t . Run ( "good, single type, Hash160Type->ByteArray" , func ( t * testing . T ) {
checkSingleType ( t , methodWithoutEllipsis , smartcontract . Hash160Type , stackitem . ByteArrayT )
} )
t . Run ( "good, single type, Hash256Type->ByteArray" , func ( t * testing . T ) {
checkSingleType ( t , methodWithoutEllipsis , smartcontract . Hash256Type , stackitem . ByteArrayT )
} )
t . Run ( "good, single type, Signature->ByteArray" , func ( t * testing . T ) {
checkSingleType ( t , methodWithoutEllipsis , smartcontract . SignatureType , stackitem . ByteArrayT )
} )
t . Run ( "good, single type, String->ByteArray" , func ( t * testing . T ) {
checkSingleType ( t , methodWithoutEllipsis , smartcontract . StringType , stackitem . ByteArrayT ) // Special case, runtime.Notify will convert any Buffer to ByteArray.
} )
t . Run ( "good, single type, PublicKeyType->ByteArray" , func ( t * testing . T ) {
checkSingleType ( t , methodWithoutEllipsis , smartcontract . PublicKeyType , stackitem . ByteArrayT )
} )
t . Run ( "good, single type, AnyType->do not change initial type" , func ( t * testing . T ) {
checkSingleType ( t , methodWithoutEllipsis , smartcontract . AnyType , stackitem . IntegerT ) // Special case, compiler should leave the type "as is" and do not emit conversion code.
} )
// Test for InteropInterface->... is missing, because we don't enforce conversion to stackitem.InteropInterface,
// but compiler still checks these notifications against expected manifest.
t . Run ( "good, multiple types, check the conversion order" , func ( t * testing . T ) {
check ( t , methodWithoutEllipsis , [ ] smartcontract . ParamType {
smartcontract . IntegerType ,
smartcontract . BoolType ,
smartcontract . ByteArrayType ,
smartcontract . PublicKeyType ,
smartcontract . Hash160Type ,
smartcontract . AnyType , // leave initial type
smartcontract . StringType ,
} , [ ] stackitem . Type {
stackitem . IntegerT ,
stackitem . BooleanT ,
stackitem . ByteArrayT ,
stackitem . ByteArrayT ,
stackitem . ByteArrayT ,
stackitem . IntegerT , // leave initial type
stackitem . ByteArrayT ,
} , false )
} )
t . Run ( "with ellipsis, do not emit conversion code" , func ( t * testing . T ) {
checkSingleType ( t , methodWithEllipsis , smartcontract . IntegerType , stackitem . IntegerT )
checkSingleType ( t , methodWithEllipsis , smartcontract . BoolType , stackitem . IntegerT )
checkSingleType ( t , methodWithEllipsis , smartcontract . ByteArrayType , stackitem . IntegerT )
} )
t . Run ( "no events check => no conversion code" , func ( t * testing . T ) {
checkSingleType ( t , methodWithoutEllipsis , smartcontract . PublicKeyType , stackitem . IntegerT , true )
} )
}