Skip to content

Commit ed9f9ff

Browse files
author
Tural Devrishev
committed
core: port StdLib's hexEncode and hexDecode APIs
Close #3990. Signed-off-by: Tural Devrishev <[email protected]>
1 parent 0db4a68 commit ed9f9ff

File tree

8 files changed

+124
-3
lines changed

8 files changed

+124
-3
lines changed

docs/node-configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,7 @@ in development and can change in an incompatible way.
582582
| `Cockatrice` | Introduces the ability to update native contracts. Includes a couple of new native smart contract APIs: `keccak256` of native CryptoLib contract and `getCommitteeAddress` of native NeoToken contract. | https://github.com/nspcc-dev/neo-go/pull/3402 <br> https://github.com/neo-project/neo/pull/2942 <br> https://github.com/nspcc-dev/neo-go/pull/3301 <br> https://github.com/neo-project/neo/pull/2925 <br> https://github.com/nspcc-dev/neo-go/pull/3362 <br> https://github.com/neo-project/neo/pull/3154 |
583583
| `Domovoi` | Makes node use executing contract state for the contract call permissions check instead of the state stored in the native Management contract. In C# also makes System.Runtime.GetNotifications interop properly count stack references of notification parameters which prevents users from creating objects that exceed MaxStackSize constraint, but NeoGo has never had this bug, thus proper behaviour is preserved even before HFDomovoi. It results in the fact that some T5 testnet transactions have different ApplicationLogs compared to the C# node, but the node states match. | https://github.com/nspcc-dev/neo-go/pull/3476 <br> https://github.com/neo-project/neo/pull/3290 <br> https://github.com/nspcc-dev/neo-go/pull/3473 <br> https://github.com/neo-project/neo/pull/3290 <br> https://github.com/neo-project/neo/pull/3301 <br> https://github.com/nspcc-dev/neo-go/pull/3485 |
584584
| `Echidna` | Introduces `Designation` event extension with `Old` and `New` roles data to native RoleManagement contract. Adds support for `base64UrlEncode` and `base64UrlDecode` methods to native StdLib contract. Extends the list of required call flags for `registerCandidate`, `unregisterCandidate`and `vote` methods of native NeoToken contract with AllowNotify flag. Enables `onNEP17Payment` method of NEO contract for candidate registration. Introduces constraint for maximum number of execution notifications. Adds support for `recoverSecp256K1` method of native CryptoLib contract. Introduces `setMillisecondsPerBlock` and `getMillisecondsPerBlock` methods of native Policy contract. Introduces support for NotaryAssisted transaction attribute and native Notary contract. | https://github.com/nspcc-dev/neo-go/pull/3554 <br> https://github.com/nspcc-dev/neo-go/pull/3761 <br> https://github.com/nspcc-dev/neo-go/pull/3554 <br> https://github.com/neo-project/neo/pull/3597 <br> https://github.com/nspcc-dev/neo-go/pull/3700 <br> https://github.com/nspcc-dev/neo-go/pull/3640 <br> https://github.com/neo-project/neo/pull/3548 <br> https://github.com/nspcc-dev/neo-go/pull/3863 <br> https://github.com/neo-project/neo/pull/3696 <br> https://github.com/neo-project/neo/pull/3895 <br> https://github.com/nspcc-dev/neo-go/pull/3835 <br> https://github.com/nspcc-dev/neo-go/pull/3854 <br> https://github.com/neo-project/neo/pull/3175 <br> https://github.com/nspcc-dev/neo-go/pull/3478 <br> https://github.com/neo-project/neo/pull/3178 |
585-
| `Faun` | Adds `getBlockedAccounts` method to native Policy contract. | https://github.com/nspcc-dev/neo-go/pull/3932 <br> https://github.com/nspcc-dev/neo-go/pull/4004|
585+
| `Faun` | Adds `getBlockedAccounts` method to native Policy contract. Adds `hexEncode` and `hexDecode` methods to native StdLib contract. | https://github.com/nspcc-dev/neo-go/pull/3932 <br> https://github.com/nspcc-dev/neo-go/pull/4004|
586586

587587
## DB compatibility
588588

pkg/config/hardfork.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ const (
5151
// https://github.com/neo-project/neo/pull/3175).
5252
HFEchidna // Echidna
5353
// HFFaun represents hard-fork introduced in #3931, #4004 (ported from
54-
// https://github.com/neo-project/neo/pull/4147).
54+
// https://github.com/neo-project/neo/pull/4147,
55+
// https://github.com/neo-project/neo/pull/4150).
5556
HFFaun // Faun
5657
// hfLast denotes the end of hardforks enum. Consider adding new hardforks
5758
// before hfLast.

pkg/core/native/native_test/management_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ var (
7070
// faunCSS holds serialized native contract states built for genesis block (with UpdateCounter 0)
7171
// under assumption that hardforks from Aspidochelone to Faun (included) are enabled.
7272
faunCSS = map[string]string{
73+
nativenames.StdLib: `{"id":-2,"hash":"0xacce6fd80d44e1796aa0c2c625e9e4e0ce39efc0","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQA==","checksum":2426471238},"manifest":{"name":"StdLib","abi":{"methods":[{"name":"atoi","offset":0,"parameters":[{"name":"value","type":"String"}],"returntype":"Integer","safe":true},{"name":"atoi","offset":7,"parameters":[{"name":"value","type":"String"},{"name":"base","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"base58CheckDecode","offset":14,"parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","safe":true},{"name":"base58CheckEncode","offset":21,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","safe":true},{"name":"base58Decode","offset":28,"parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","safe":true},{"name":"base58Encode","offset":35,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","safe":true},{"name":"base64Decode","offset":42,"parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","safe":true},{"name":"base64Encode","offset":49,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","safe":true},{"name":"base64UrlDecode","offset":56,"parameters":[{"name":"s","type":"String"}],"returntype":"String","safe":true},{"name":"base64UrlEncode","offset":63,"parameters":[{"name":"data","type":"String"}],"returntype":"String","safe":true},{"name":"deserialize","offset":70,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"Any","safe":true},{"name":"hexDecode","offset":77,"parameters":[{"name":"str","type":"String"}],"returntype":"ByteArray","safe":true},{"name":"hexEncode","offset":84,"parameters":[{"name":"bytes","type":"ByteArray"}],"returntype":"String","safe":true},{"name":"itoa","offset":91,"parameters":[{"name":"value","type":"Integer"}],"returntype":"String","safe":true},{"name":"itoa","offset":98,"parameters":[{"name":"value","type":"Integer"},{"name":"base","type":"Integer"}],"returntype":"String","safe":true},{"name":"jsonDeserialize","offset":105,"parameters":[{"name":"json","type":"ByteArray"}],"returntype":"Any","safe":true},{"name":"jsonSerialize","offset":112,"parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","safe":true},{"name":"memoryCompare","offset":119,"parameters":[{"name":"str1","type":"ByteArray"},{"name":"str2","type":"ByteArray"}],"returntype":"Integer","safe":true},{"name":"memorySearch","offset":126,"parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"}],"returntype":"Integer","safe":true},{"name":"memorySearch","offset":133,"parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"memorySearch","offset":140,"parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"},{"name":"backward","type":"Boolean"}],"returntype":"Integer","safe":true},{"name":"serialize","offset":147,"parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","safe":true},{"name":"strLen","offset":154,"parameters":[{"name":"str","type":"String"}],"returntype":"Integer","safe":true},{"name":"stringSplit","offset":161,"parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"}],"returntype":"Array","safe":true},{"name":"stringSplit","offset":168,"parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"},{"name":"removeEmptyEntries","type":"Boolean"}],"returntype":"Array","safe":true}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`,
7374
nativenames.Policy: `{"id":-7,"hash":"0xcc5e4edd9f5f8dba8bb65734541df7a1c081c67b","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dA","checksum":2208257578},"manifest":{"name":"PolicyContract","abi":{"methods":[{"name":"blockAccount","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean","safe":false},{"name":"getAttributeFee","offset":7,"parameters":[{"name":"attributeType","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"getBlockedAccounts","offset":14,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"getExecFeeFactor","offset":21,"parameters":[],"returntype":"Integer","safe":true},{"name":"getFeePerByte","offset":28,"parameters":[],"returntype":"Integer","safe":true},{"name":"getMaxTraceableBlocks","offset":35,"parameters":[],"returntype":"Integer","safe":true},{"name":"getMaxValidUntilBlockIncrement","offset":42,"parameters":[],"returntype":"Integer","safe":true},{"name":"getMillisecondsPerBlock","offset":49,"parameters":[],"returntype":"Integer","safe":true},{"name":"getStoragePrice","offset":56,"parameters":[],"returntype":"Integer","safe":true},{"name":"isBlocked","offset":63,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean","safe":true},{"name":"setAttributeFee","offset":70,"parameters":[{"name":"attributeType","type":"Integer"},{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setExecFeeFactor","offset":77,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setFeePerByte","offset":84,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setMaxTraceableBlocks","offset":91,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setMaxValidUntilBlockIncrement","offset":98,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setMillisecondsPerBlock","offset":105,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setStoragePrice","offset":112,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"unblockAccount","offset":119,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean","safe":false}],"events":[{"name":"MillisecondsPerBlockChanged","parameters":[{"name":"old","type":"Integer"},{"name":"new","type":"Integer"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`,
7475
}
7576
)

pkg/core/native/native_test/policy_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ package native_test
33
import (
44
"bytes"
55
"fmt"
6-
"github.com/nspcc-dev/neo-go/pkg/compiler"
76
"slices"
87
"strings"
98
"testing"
109
"time"
1110

11+
"github.com/nspcc-dev/neo-go/pkg/compiler"
1212
"github.com/nspcc-dev/neo-go/pkg/config"
1313
"github.com/nspcc-dev/neo-go/pkg/core/interop"
1414
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package native
2+
3+
import (
4+
"testing"
5+
6+
"github.com/nspcc-dev/neo-go/pkg/config"
7+
"github.com/nspcc-dev/neo-go/pkg/core/native/nativehashes"
8+
"github.com/nspcc-dev/neo-go/pkg/neotest"
9+
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
10+
)
11+
12+
func TestStd_HexEncodeDecode(t *testing.T) {
13+
bc, acc := chain.NewSingleWithCustomConfig(t, func(c *config.Blockchain) {
14+
c.Hardforks = map[string]uint32{
15+
config.HFFaun.String(): 2,
16+
}
17+
})
18+
e := neotest.NewExecutor(t, bc, acc, acc)
19+
p := e.CommitteeInvoker(nativehashes.StdLib)
20+
21+
expectedBytes := []byte{0x00, 0x01, 0x02, 0x03}
22+
expectedString := "00010203"
23+
24+
p.InvokeFail(t, "method not found: hexEncode/1", "hexEncode", expectedBytes)
25+
26+
e.AddNewBlock(t)
27+
28+
p.Invoke(t, expectedString, "hexEncode", expectedBytes)
29+
p.Invoke(t, expectedBytes, "hexDecode", expectedString)
30+
}

pkg/core/native/std.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/base64"
66
"encoding/hex"
77
"errors"
8+
"fmt"
89
"math/big"
910
"slices"
1011
"strings"
@@ -172,6 +173,16 @@ func newStd() *Std {
172173
md = NewMethodAndPrice(s.strLen, 1<<8, callflag.NoneFlag)
173174
s.AddMethod(md, desc)
174175

176+
desc = NewDescriptor("hexEncode", smartcontract.StringType,
177+
manifest.NewParameter("bytes", smartcontract.ByteArrayType))
178+
md = NewMethodAndPrice(s.hexEncode, 1<<5, callflag.NoneFlag, config.HFFaun)
179+
s.AddMethod(md, desc)
180+
181+
desc = NewDescriptor("hexDecode", smartcontract.ByteArrayType,
182+
manifest.NewParameter("str", smartcontract.StringType))
183+
md = NewMethodAndPrice(s.hexDecode, 1<<5, callflag.NoneFlag, config.HFFaun)
184+
s.AddMethod(md, desc)
185+
175186
return s
176187
}
177188

@@ -321,6 +332,20 @@ func (s *Std) base64Decode(_ *interop.Context, args []stackitem.Item) stackitem.
321332
return stackitem.NewByteArray(result)
322333
}
323334

335+
func (s *Std) hexEncode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
336+
src := toLimitedBytes(args[0])
337+
return stackitem.NewByteArray([]byte(hex.EncodeToString(src)))
338+
}
339+
340+
func (s *Std) hexDecode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
341+
src := toLimitedString(args[0])
342+
dst, err := hex.DecodeString(src)
343+
if err != nil {
344+
panic(fmt.Errorf("hexDecode: invalid hex string %q: %w", src, err))
345+
}
346+
return stackitem.NewByteArray(dst)
347+
}
348+
324349
func (s *Std) base64UrlEncode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
325350
src := toLimitedBytes(args[0])
326351
result := base64.URLEncoding.EncodeToString(src)

pkg/core/native/std_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,3 +639,51 @@ func TestStd_StrLen(t *testing.T) {
639639
check(t, 1, bad)
640640
check(t, 3, bad+"ab")
641641
}
642+
643+
func TestStd_HexEncodeDecode(t *testing.T) {
644+
s := newStd()
645+
var actual stackitem.Item
646+
647+
original := []byte("abc")
648+
hexStr := hex.EncodeToString(original)
649+
650+
t.Run("hexEncode positive", func(t *testing.T) {
651+
require.NotPanics(t, func() {
652+
actual = s.hexEncode(nil, []stackitem.Item{stackitem.Make(original)})
653+
})
654+
require.Equal(t, stackitem.Make(hexStr), actual)
655+
})
656+
657+
t.Run("hexEncode error big input", func(t *testing.T) {
658+
bigBytes := make([]byte, stdMaxInputLength+1)
659+
require.PanicsWithError(t, ErrTooBigInput.Error(), func() {
660+
_ = s.hexEncode(nil, []stackitem.Item{stackitem.Make(bigBytes)})
661+
})
662+
})
663+
664+
t.Run("hexDecode positive", func(t *testing.T) {
665+
require.NotPanics(t, func() {
666+
actual = s.hexDecode(nil, []stackitem.Item{stackitem.Make(hexStr)})
667+
})
668+
require.Equal(t, stackitem.Make(original), actual)
669+
})
670+
671+
t.Run("hexDecode error invalid chars", func(t *testing.T) {
672+
require.Panics(t, func() {
673+
_ = s.hexDecode(nil, []stackitem.Item{stackitem.Make("zz")})
674+
})
675+
})
676+
677+
t.Run("hexDecode error invalid conversion", func(t *testing.T) {
678+
require.Panics(t, func() {
679+
_ = s.hexDecode(nil, []stackitem.Item{stackitem.NewMap()})
680+
})
681+
})
682+
683+
t.Run("hexDecode error big input", func(t *testing.T) {
684+
bigBytes := []stackitem.Item{stackitem.NewByteArray(make([]byte, stdMaxInputLength+1))}
685+
require.PanicsWithError(t, ErrTooBigInput.Error(), func() {
686+
_ = s.hexDecode(nil, bigBytes)
687+
})
688+
})
689+
}

pkg/interop/native/std/std.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,19 @@ func StrLen(s string) int {
180180
return neogointernal.CallWithToken(Hash, "strLen", int(contract.NoneFlag),
181181
s).(int)
182182
}
183+
184+
// HexEncode encodes a given byte slice into a hex string and returns it.
185+
// It uses `hexEncode` method of StdLib native contract.
186+
// Note that this method is available starting from [config.HFEchidna] hardfork.
187+
func HexEncode(b []byte) string {
188+
return neogointernal.CallWithToken(Hash, "hexEncode", int(contract.NoneFlag),
189+
b).(string)
190+
}
191+
192+
// HexDecode decodes a given hex string into a byte slice and returns it.
193+
// It uses `hexDecode` method of StdLib native contract.
194+
// Note that this method is available starting from [config.HFEchidna] hardfork.
195+
func HexDecode(s string) []byte {
196+
return neogointernal.CallWithToken(Hash, "hexDecode", int(contract.NoneFlag),
197+
s).([]byte)
198+
}

0 commit comments

Comments
 (0)