diff --git a/pkg/compiler/interop_test.go b/pkg/compiler/interop_test.go index 95c2a0d68..7bbebe18d 100644 --- a/pkg/compiler/interop_test.go +++ b/pkg/compiler/interop_test.go @@ -277,6 +277,29 @@ func TestCurrentSigners(t *testing.T) { }) } +func TestStdLib_StrLen(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/native/std" + ) + func Main(s string) int { + return std.StrLen(s) + }` + ctr := neotest.CompileSource(t, e.CommitteeHash, strings.NewReader(src), &compiler.Options{Name: "Helper"}) + e.DeployContract(t, ctr, nil) + c := e.CommitteeInvoker(ctr.Hash) + + expected := stackitem.Make(1) + c.Invoke(t, expected, "main", "🦆") + c.Invoke(t, expected, "main", "ã") + c.Invoke(t, expected, "main", "a") + + expected = stackitem.Make(7) + c.Invoke(t, expected, "main", "abc 123") +} + 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) diff --git a/pkg/core/native/std.go b/pkg/core/native/std.go index 92f8988ec..86f013cf1 100644 --- a/pkg/core/native/std.go +++ b/pkg/core/native/std.go @@ -7,6 +7,7 @@ import ( "errors" "math/big" "strings" + "unicode/utf8" "github.com/mr-tron/base58" "github.com/nspcc-dev/neo-go/pkg/config" @@ -159,6 +160,11 @@ func newStd() *Std { md = newMethodAndPrice(s.stringSplit3, 1<<8, callflag.NoneFlag) s.AddMethod(md, desc) + desc = newDescriptor("strLen", smartcontract.IntegerType, + manifest.NewParameter("str", smartcontract.StringType)) + md = newMethodAndPrice(s.strLen, 1<<8, callflag.NoneFlag) + s.AddMethod(md, desc) + return s } @@ -421,6 +427,12 @@ func (s *Std) stringSplitAux(str, sep string, removeEmpty bool) []stackitem.Item return result } +func (s *Std) strLen(_ *interop.Context, args []stackitem.Item) stackitem.Item { + str := s.toLimitedString(args[0]) + + return stackitem.NewBigInteger(big.NewInt(int64(utf8.RuneCountInString(str)))) +} + // Metadata implements the Contract interface. func (s *Std) Metadata() *interop.ContractMD { return &s.ContractMD diff --git a/pkg/core/native/std_test.go b/pkg/core/native/std_test.go index dc1461328..974e54b8f 100644 --- a/pkg/core/native/std_test.go +++ b/pkg/core/native/std_test.go @@ -558,3 +558,25 @@ func TestStringSplit(t *testing.T) { func() { s.stringSplit2(ic, []stackitem.Item{s1, s2}) }) }) } + +func TestStd_StrLen(t *testing.T) { + s := newStd() + ic := &interop.Context{VM: vm.New()} + + check := func(t *testing.T, expected int64, str string) { + args := []stackitem.Item{stackitem.Make(str)} + actual := s.strLen(ic, args) + l, ok := actual.Value().(*big.Int) + require.True(t, ok) + require.Equal(t, expected, l.Int64()) + } + + // These tests are taken from https://github.com/neo-project/neo/pull/2854/files. + check(t, 1, "🦆") + check(t, 1, "ã") + check(t, 1, "a") + + bad := string(rune(0xff)) + check(t, 1, bad) + check(t, 3, bad+"ab") +} diff --git a/pkg/interop/native/std/std.go b/pkg/interop/native/std/std.go index e79e32d3d..8bf2b8fbf 100644 --- a/pkg/interop/native/std/std.go +++ b/pkg/interop/native/std/std.go @@ -173,3 +173,10 @@ func StringSplitNonEmpty(s, sep string) []string { return neogointernal.CallWithToken(Hash, "stringSplit", int(contract.NoneFlag), s, sep, true).([]string) } + +// StrLen returns length of the string in Utf- characters. +// It uses `strLen` method of StdLib native contract. +func StrLen(s string) int { + return neogointernal.CallWithToken(Hash, "strLen", int(contract.NoneFlag), + s).(int) +}