native/std: add memorySearch
method
This commit is contained in:
parent
978f4dfbc5
commit
dadfe2b9ab
4 changed files with 188 additions and 1 deletions
|
@ -229,6 +229,9 @@ func TestNativeHelpersCompile(t *testing.T) {
|
||||||
{"atoi", []string{`"4"`, "10"}},
|
{"atoi", []string{`"4"`, "10"}},
|
||||||
{"atoi10", []string{`"4"`}},
|
{"atoi10", []string{`"4"`}},
|
||||||
{"memoryCompare", []string{"[]byte{1}", "[]byte{2}"}},
|
{"memoryCompare", []string{"[]byte{1}", "[]byte{2}"}},
|
||||||
|
{"memorySearch", []string{"[]byte{1}", "[]byte{2}"}},
|
||||||
|
{"memorySearchIndex", []string{"[]byte{1}", "[]byte{2}", "3"}},
|
||||||
|
{"memorySearchLastIndex", []string{"[]byte{1}", "[]byte{2}", "3"}},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,14 +246,21 @@ func runNativeTestCases(t *testing.T, ctr interop.ContractMD, name string, testC
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMethod(t *testing.T, ctr interop.ContractMD, name string, params []string) interop.MethodAndPrice {
|
func getMethod(t *testing.T, ctr interop.ContractMD, name string, params []string) interop.MethodAndPrice {
|
||||||
|
paramLen := len(params)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case name == "itoa10" || name == "atoi10":
|
case name == "itoa10" || name == "atoi10":
|
||||||
name = name[:4]
|
name = name[:4]
|
||||||
|
case strings.HasPrefix(name, "memorySearch"):
|
||||||
|
if strings.HasSuffix(name, "LastIndex") {
|
||||||
|
paramLen += 1 // true should be appended inside of an interop
|
||||||
|
}
|
||||||
|
name = "memorySearch"
|
||||||
default:
|
default:
|
||||||
name = strings.TrimSuffix(name, "WithData")
|
name = strings.TrimSuffix(name, "WithData")
|
||||||
}
|
}
|
||||||
|
|
||||||
md, ok := ctr.GetMethod(name, len(params))
|
md, ok := ctr.GetMethod(name, paramLen)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
return md
|
return md
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,6 +111,27 @@ func newStd() *Std {
|
||||||
md = newMethodAndPrice(s.memoryCompare, 1<<5, callflag.NoneFlag)
|
md = newMethodAndPrice(s.memoryCompare, 1<<5, callflag.NoneFlag)
|
||||||
s.AddMethod(md, desc)
|
s.AddMethod(md, desc)
|
||||||
|
|
||||||
|
desc = newDescriptor("memorySearch", smartcontract.IntegerType,
|
||||||
|
manifest.NewParameter("mem", smartcontract.ByteArrayType),
|
||||||
|
manifest.NewParameter("value", smartcontract.ByteArrayType))
|
||||||
|
md = newMethodAndPrice(s.memorySearch2, 1<<6, callflag.NoneFlag)
|
||||||
|
s.AddMethod(md, desc)
|
||||||
|
|
||||||
|
desc = newDescriptor("memorySearch", smartcontract.IntegerType,
|
||||||
|
manifest.NewParameter("mem", smartcontract.ByteArrayType),
|
||||||
|
manifest.NewParameter("value", smartcontract.ByteArrayType),
|
||||||
|
manifest.NewParameter("start", smartcontract.IntegerType))
|
||||||
|
md = newMethodAndPrice(s.memorySearch3, 1<<6, callflag.NoneFlag)
|
||||||
|
s.AddMethod(md, desc)
|
||||||
|
|
||||||
|
desc = newDescriptor("memorySearch", smartcontract.IntegerType,
|
||||||
|
manifest.NewParameter("mem", smartcontract.ByteArrayType),
|
||||||
|
manifest.NewParameter("value", smartcontract.ByteArrayType),
|
||||||
|
manifest.NewParameter("start", smartcontract.IntegerType),
|
||||||
|
manifest.NewParameter("backward", smartcontract.BoolType))
|
||||||
|
md = newMethodAndPrice(s.memorySearch4, 1<<6, callflag.NoneFlag)
|
||||||
|
s.AddMethod(md, desc)
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,6 +312,49 @@ func (s *Std) memoryCompare(_ *interop.Context, args []stackitem.Item) stackitem
|
||||||
return stackitem.NewBigInteger(big.NewInt(int64(bytes.Compare(s1, s2))))
|
return stackitem.NewBigInteger(big.NewInt(int64(bytes.Compare(s1, s2))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Std) memorySearch2(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
mem := s.toLimitedBytes(args[0])
|
||||||
|
val := s.toLimitedBytes(args[1])
|
||||||
|
index := s.memorySearchAux(mem, val, 0, false)
|
||||||
|
return stackitem.NewBigInteger(big.NewInt(int64(index)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Std) memorySearch3(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
mem := s.toLimitedBytes(args[0])
|
||||||
|
val := s.toLimitedBytes(args[1])
|
||||||
|
start := toUint32(args[2])
|
||||||
|
index := s.memorySearchAux(mem, val, int(start), false)
|
||||||
|
return stackitem.NewBigInteger(big.NewInt(int64(index)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Std) memorySearch4(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
mem := s.toLimitedBytes(args[0])
|
||||||
|
val := s.toLimitedBytes(args[1])
|
||||||
|
start := toUint32(args[2])
|
||||||
|
backward, err := args[3].TryBool()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
index := s.memorySearchAux(mem, val, int(start), backward)
|
||||||
|
return stackitem.NewBigInteger(big.NewInt(int64(index)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Std) memorySearchAux(mem, val []byte, start int, backward bool) int {
|
||||||
|
if backward {
|
||||||
|
if start > len(mem) { // panic in case if cap(mem) > len(mem) for some reasons
|
||||||
|
panic("invalid start index")
|
||||||
|
}
|
||||||
|
return bytes.LastIndex(mem[:start], val)
|
||||||
|
}
|
||||||
|
|
||||||
|
index := bytes.Index(mem[start:], val)
|
||||||
|
if index < 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return index + start
|
||||||
|
}
|
||||||
|
|
||||||
// Metadata implements Contract interface.
|
// Metadata implements Contract interface.
|
||||||
func (s *Std) Metadata() *interop.ContractMD {
|
func (s *Std) Metadata() *interop.ContractMD {
|
||||||
return &s.ContractMD
|
return &s.ContractMD
|
||||||
|
|
|
@ -389,3 +389,95 @@ func TestMemoryCompare(t *testing.T) {
|
||||||
func() { s.memoryCompare(ic, []stackitem.Item{s2, s1}) })
|
func() { s.memoryCompare(ic, []stackitem.Item{s2, s1}) })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMemorySearch(t *testing.T) {
|
||||||
|
s := newStd()
|
||||||
|
ic := &interop.Context{VM: vm.New()}
|
||||||
|
|
||||||
|
check := func(t *testing.T, result int64, args ...interface{}) {
|
||||||
|
items := make([]stackitem.Item, len(args))
|
||||||
|
for i := range args {
|
||||||
|
items[i] = stackitem.Make(args[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
var actual stackitem.Item
|
||||||
|
switch len(items) {
|
||||||
|
case 2:
|
||||||
|
actual = s.memorySearch2(ic, items)
|
||||||
|
case 3:
|
||||||
|
actual = s.memorySearch3(ic, items)
|
||||||
|
case 4:
|
||||||
|
actual = s.memorySearch4(ic, items)
|
||||||
|
default:
|
||||||
|
panic("invalid args length")
|
||||||
|
}
|
||||||
|
require.Equal(t, big.NewInt(result), actual.Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("C# compatibility", func(t *testing.T) {
|
||||||
|
// These tests are taken from C# node.
|
||||||
|
check(t, 2, "abc", "c", 0)
|
||||||
|
check(t, 2, "abc", "c", 1)
|
||||||
|
check(t, 2, "abc", "c", 2)
|
||||||
|
check(t, -1, "abc", "c", 3)
|
||||||
|
check(t, -1, "abc", "d", 0)
|
||||||
|
|
||||||
|
check(t, 2, "abc", "c", 0, false)
|
||||||
|
check(t, 2, "abc", "c", 1, false)
|
||||||
|
check(t, 2, "abc", "c", 2, false)
|
||||||
|
check(t, -1, "abc", "c", 3, false)
|
||||||
|
check(t, -1, "abc", "d", 0, false)
|
||||||
|
|
||||||
|
check(t, -1, "abc", "c", 0, true)
|
||||||
|
check(t, -1, "abc", "c", 1, true)
|
||||||
|
check(t, -1, "abc", "c", 2, true)
|
||||||
|
check(t, 2, "abc", "c", 3, true)
|
||||||
|
check(t, -1, "abc", "d", 0, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("boundary indices", func(t *testing.T) {
|
||||||
|
arg := stackitem.Make("aaa")
|
||||||
|
require.Panics(t, func() {
|
||||||
|
s.memorySearch3(ic, []stackitem.Item{arg, arg, stackitem.Make(-1)})
|
||||||
|
})
|
||||||
|
require.Panics(t, func() {
|
||||||
|
s.memorySearch3(ic, []stackitem.Item{arg, arg, stackitem.Make(4)})
|
||||||
|
})
|
||||||
|
t.Run("still in capacity", func(t *testing.T) {
|
||||||
|
require.Panics(t, func() {
|
||||||
|
arr := stackitem.NewByteArray(make([]byte, 5, 10))
|
||||||
|
s.memorySearch3(ic, []stackitem.Item{arr, arg, stackitem.Make(7)})
|
||||||
|
})
|
||||||
|
require.Panics(t, func() {
|
||||||
|
arr := stackitem.NewByteArray(make([]byte, 5, 10))
|
||||||
|
s.memorySearch4(ic, []stackitem.Item{arr, arg,
|
||||||
|
stackitem.Make(7), stackitem.Make(true)})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("big arguments", func(t *testing.T) {
|
||||||
|
s1 := stackitem.Make(strings.Repeat("x", stdMaxInputLength+1))
|
||||||
|
s2 := stackitem.Make("xxx")
|
||||||
|
start := stackitem.Make(1)
|
||||||
|
b := stackitem.Make(true)
|
||||||
|
|
||||||
|
require.PanicsWithError(t, ErrTooBigInput.Error(),
|
||||||
|
func() { s.memorySearch2(ic, []stackitem.Item{s1, s2}) })
|
||||||
|
|
||||||
|
require.PanicsWithError(t, ErrTooBigInput.Error(),
|
||||||
|
func() { s.memorySearch2(ic, []stackitem.Item{s2, s1}) })
|
||||||
|
|
||||||
|
require.PanicsWithError(t, ErrTooBigInput.Error(),
|
||||||
|
func() { s.memorySearch3(ic, []stackitem.Item{s1, s2, start}) })
|
||||||
|
|
||||||
|
require.PanicsWithError(t, ErrTooBigInput.Error(),
|
||||||
|
func() { s.memorySearch3(ic, []stackitem.Item{s2, s1, start}) })
|
||||||
|
|
||||||
|
require.PanicsWithError(t, ErrTooBigInput.Error(),
|
||||||
|
func() { s.memorySearch4(ic, []stackitem.Item{s1, s2, start, b}) })
|
||||||
|
|
||||||
|
require.PanicsWithError(t, ErrTooBigInput.Error(),
|
||||||
|
func() { s.memorySearch4(ic, []stackitem.Item{s2, s1, start, b}) })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -121,3 +121,24 @@ func MemoryCompare(s1, s2 []byte) int {
|
||||||
return contract.Call(interop.Hash160(Hash), "memoryCompare", contract.NoneFlag,
|
return contract.Call(interop.Hash160(Hash), "memoryCompare", contract.NoneFlag,
|
||||||
s1, s2).(int)
|
s1, s2).(int)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MemorySearch returns index of the first occurrence of val in mem.
|
||||||
|
// If not found, -1 is returned. It uses `memorySearch` method of StdLib native contract.
|
||||||
|
func MemorySearch(mem, pattern []byte) int {
|
||||||
|
return contract.Call(interop.Hash160(Hash), "memorySearch", contract.NoneFlag,
|
||||||
|
mem, pattern).(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemorySearchIndex returns index of the first occurrence of val in mem starting from start.
|
||||||
|
// If not found, -1 is returned. It uses `memorySearch` method of StdLib native contract.
|
||||||
|
func MemorySearchIndex(mem, pattern []byte, start int) int {
|
||||||
|
return contract.Call(interop.Hash160(Hash), "memorySearch", contract.NoneFlag,
|
||||||
|
mem, pattern, start).(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemorySearchLastIndex returns index of the last occurrence of val in mem ending before start.
|
||||||
|
// If not found, -1 is returned. It uses `memorySearch` method of StdLib native contract.
|
||||||
|
func MemorySearchLastIndex(mem, pattern []byte, start int) int {
|
||||||
|
return contract.Call(interop.Hash160(Hash), "memorySearch", contract.NoneFlag,
|
||||||
|
mem, pattern, start, true).(int)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue