core, interop: add strLen method for native StdLib contract

Close #3195.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
This commit is contained in:
Anna Shaleva 2023-11-20 19:23:33 +03:00
parent b5cf3f592f
commit 406c9f8b92
4 changed files with 64 additions and 0 deletions

View file

@ -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 { func spawnVM(t *testing.T, ic *interop.Context, src string) *vm.VM {
b, di, err := compiler.CompileWithOptions("foo.go", strings.NewReader(src), nil) b, di, err := compiler.CompileWithOptions("foo.go", strings.NewReader(src), nil)
require.NoError(t, err) require.NoError(t, err)

View file

@ -7,6 +7,7 @@ import (
"errors" "errors"
"math/big" "math/big"
"strings" "strings"
"unicode/utf8"
"github.com/mr-tron/base58" "github.com/mr-tron/base58"
"github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config"
@ -159,6 +160,11 @@ func newStd() *Std {
md = newMethodAndPrice(s.stringSplit3, 1<<8, callflag.NoneFlag) md = newMethodAndPrice(s.stringSplit3, 1<<8, callflag.NoneFlag)
s.AddMethod(md, desc) 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 return s
} }
@ -421,6 +427,12 @@ func (s *Std) stringSplitAux(str, sep string, removeEmpty bool) []stackitem.Item
return result 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. // Metadata implements the Contract interface.
func (s *Std) Metadata() *interop.ContractMD { func (s *Std) Metadata() *interop.ContractMD {
return &s.ContractMD return &s.ContractMD

View file

@ -558,3 +558,25 @@ func TestStringSplit(t *testing.T) {
func() { s.stringSplit2(ic, []stackitem.Item{s1, s2}) }) 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")
}

View file

@ -173,3 +173,10 @@ func StringSplitNonEmpty(s, sep string) []string {
return neogointernal.CallWithToken(Hash, "stringSplit", int(contract.NoneFlag), return neogointernal.CallWithToken(Hash, "stringSplit", int(contract.NoneFlag),
s, sep, true).([]string) 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)
}