Skip to content

Commit e396362

Browse files
native: add Base64URL support to the StdLib starting from HFEchidna
Close #3550 Signed-off-by: Ekaterina Pavlova <ekt@morphbits.io>
1 parent d2da286 commit e396362

File tree

4 files changed

+58
-1
lines changed

4 files changed

+58
-1
lines changed

docs/node-configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ in development and can change in an incompatible way.
470470
| `Basilisk` | Enables strict smart contract script check against a set of JMP instructions and against method boundaries enabled on contract deploy or update. Increases `stackitem.Integer` JSON parsing precision up to the maximum value supported by the NeoVM. Enables strict check for notifications emitted by a contract to precisely match the events specified in the contract manifest. | https://github.com/nspcc-dev/neo-go/pull/3056 <br> https://github.com/neo-project/neo/pull/2881 <br> https://github.com/nspcc-dev/neo-go/pull/3080 <br> https://github.com/neo-project/neo/pull/2883 <br> https://github.com/nspcc-dev/neo-go/pull/3085 <br> https://github.com/neo-project/neo/pull/2810 |
471471
| `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 |
472472
| `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 |
473-
| `Echidna` | Introduces `Designation` event extension with `Old` and `New` roles data to native RoleManagement contract. | https://github.com/nspcc-dev/neo-go/pull/3554 <br> https://github.com/nspcc-dev/neo-go/pull/3761 |
473+
| `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. | https://github.com/nspcc-dev/neo-go/pull/3554 <br> https://github.com/nspcc-dev/neo-go/pull/3761 |
474474

475475

476476
## DB compatibility

pkg/core/native/native_test/management_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ var (
5757
nativenames.Neo: `{"id":-5,"hash":"0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1325686241},"manifest":{"name":"NeoToken","abi":{"methods":[{"name":"balanceOf","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"decimals","offset":7,"parameters":[],"returntype":"Integer","safe":true},{"name":"getAccountState","offset":14,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Array","safe":true},{"name":"getAllCandidates","offset":21,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"getCandidateVote","offset":28,"parameters":[{"name":"pubKey","type":"PublicKey"}],"returntype":"Integer","safe":true},{"name":"getCandidates","offset":35,"parameters":[],"returntype":"Array","safe":true},{"name":"getCommittee","offset":42,"parameters":[],"returntype":"Array","safe":true},{"name":"getCommitteeAddress","offset":49,"parameters":[],"returntype":"Hash160","safe":true},{"name":"getGasPerBlock","offset":56,"parameters":[],"returntype":"Integer","safe":true},{"name":"getNextBlockValidators","offset":63,"parameters":[],"returntype":"Array","safe":true},{"name":"getRegisterPrice","offset":70,"parameters":[],"returntype":"Integer","safe":true},{"name":"registerCandidate","offset":77,"parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean","safe":false},{"name":"setGasPerBlock","offset":84,"parameters":[{"name":"gasPerBlock","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setRegisterPrice","offset":91,"parameters":[{"name":"registerPrice","type":"Integer"}],"returntype":"Void","safe":false},{"name":"symbol","offset":98,"parameters":[],"returntype":"String","safe":true},{"name":"totalSupply","offset":105,"parameters":[],"returntype":"Integer","safe":true},{"name":"transfer","offset":112,"parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Boolean","safe":false},{"name":"unclaimedGas","offset":119,"parameters":[{"name":"account","type":"Hash160"},{"name":"end","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"unregisterCandidate","offset":126,"parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean","safe":false},{"name":"vote","offset":133,"parameters":[{"name":"account","type":"Hash160"},{"name":"voteTo","type":"PublicKey"}],"returntype":"Boolean","safe":false}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}]},{"name":"CandidateStateChanged","parameters":[{"name":"pubkey","type":"PublicKey"},{"name":"registered","type":"Boolean"},{"name":"votes","type":"Integer"}]},{"name":"Vote","parameters":[{"name":"account","type":"Hash160"},{"name":"from","type":"PublicKey"},{"name":"to","type":"PublicKey"},{"name":"amount","type":"Integer"}]},{"name":"CommitteeChanged","parameters":[{"name":"old","type":"Array"},{"name":"new","type":"Array"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":["NEP-17"],"trusts":[],"extra":null},"updatecounter":0}`,
5858
}
5959
echidnaCSS = map[string]string{
60+
nativenames.StdLib: `{"id":-2,"hash":"0xacce6fd80d44e1796aa0c2c625e9e4e0ce39efc0","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":2681632925},"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":"ByteArray","safe":true},{"name":"base64UrlEncode","offset":63,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","safe":true},{"name":"deserialize","offset":70,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"Any","safe":true},{"name":"itoa","offset":77,"parameters":[{"name":"value","type":"Integer"}],"returntype":"String","safe":true},{"name":"itoa","offset":84,"parameters":[{"name":"value","type":"Integer"},{"name":"base","type":"Integer"}],"returntype":"String","safe":true},{"name":"jsonDeserialize","offset":91,"parameters":[{"name":"json","type":"ByteArray"}],"returntype":"Any","safe":true},{"name":"jsonSerialize","offset":98,"parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","safe":true},{"name":"memoryCompare","offset":105,"parameters":[{"name":"str1","type":"ByteArray"},{"name":"str2","type":"ByteArray"}],"returntype":"Integer","safe":true},{"name":"memorySearch","offset":112,"parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"}],"returntype":"Integer","safe":true},{"name":"memorySearch","offset":119,"parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"memorySearch","offset":126,"parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"},{"name":"backward","type":"Boolean"}],"returntype":"Integer","safe":true},{"name":"serialize","offset":133,"parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","safe":true},{"name":"strLen","offset":140,"parameters":[{"name":"str","type":"String"}],"returntype":"Integer","safe":true},{"name":"stringSplit","offset":147,"parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"}],"returntype":"Array","safe":true},{"name":"stringSplit","offset":154,"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}`,
6061
nativenames.Designation: `{"id":-8,"hash":"0x49cf4e5378ffcd4dec034fd98a174c5491e395e2","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0A=","checksum":983638438},"manifest":{"name":"RoleManagement","abi":{"methods":[{"name":"designateAsRole","offset":0,"parameters":[{"name":"role","type":"Integer"},{"name":"nodes","type":"Array"}],"returntype":"Void","safe":false},{"name":"getDesignatedByRole","offset":7,"parameters":[{"name":"role","type":"Integer"},{"name":"index","type":"Integer"}],"returntype":"Array","safe":true}],"events":[{"name":"Designation","parameters":[{"name":"Role","type":"Integer"},{"name":"BlockIndex","type":"Integer"},{"name":"Old","type":"Array"},{"name":"New","type":"Array"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`,
6162
}
6263
)

pkg/core/native/std.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,16 @@ func newStd() *Std {
100100
md = newMethodAndPrice(s.base64Decode, 1<<5, callflag.NoneFlag)
101101
s.AddMethod(md, desc)
102102

103+
desc = newDescriptor("base64UrlEncode", smartcontract.StringType,
104+
manifest.NewParameter("data", smartcontract.ByteArrayType))
105+
md = newMethodAndPrice(s.base64UrlEncode, 1<<5, callflag.NoneFlag, config.HFEchidna)
106+
s.AddMethod(md, desc)
107+
108+
desc = newDescriptor("base64UrlDecode", smartcontract.ByteArrayType,
109+
manifest.NewParameter("s", smartcontract.StringType))
110+
md = newMethodAndPrice(s.base64UrlDecode, 1<<5, callflag.NoneFlag, config.HFEchidna)
111+
s.AddMethod(md, desc)
112+
103113
desc = newDescriptor("base58Encode", smartcontract.StringType,
104114
manifest.NewParameter("data", smartcontract.ByteArrayType))
105115
md = newMethodAndPrice(s.base58Encode, 1<<13, callflag.NoneFlag)
@@ -314,6 +324,23 @@ func (s *Std) base64Decode(_ *interop.Context, args []stackitem.Item) stackitem.
314324
return stackitem.NewByteArray(result)
315325
}
316326

327+
func (s *Std) base64UrlEncode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
328+
src := s.toLimitedBytes(args[0])
329+
result := base64.URLEncoding.EncodeToString(src)
330+
331+
return stackitem.NewByteArray([]byte(result))
332+
}
333+
334+
func (s *Std) base64UrlDecode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
335+
src := s.toLimitedString(args[0])
336+
result, err := base64.URLEncoding.DecodeString(src)
337+
if err != nil {
338+
panic(err)
339+
}
340+
341+
return stackitem.NewByteArray(result)
342+
}
343+
317344
func (s *Std) base58Encode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
318345
src := s.toLimitedBytes(args[0])
319346
result := base58.Encode(src)

pkg/core/native/std_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,10 @@ func TestStdLibJSON(t *testing.T) {
170170
func TestStdLibEncodeDecode(t *testing.T) {
171171
s := newStd()
172172
original := []byte("my pretty string")
173+
// Source C# implementation: https://github.com/neo-project/neo/blob/216c39eddd03de2cb1f6f8aa5b3c19be60606f27/tests/Neo.UnitTests/SmartContract/Native/UT_StdLib.cs#s#L417-L418
174+
originalUrl := []byte("Subject=test@example.com&Issuer=https://example.com")
173175
encoded64 := base64.StdEncoding.EncodeToString(original)
176+
encoded64Url := "U3ViamVjdD10ZXN0QGV4YW1wbGUuY29tJklzc3Vlcj1odHRwczovL2V4YW1wbGUuY29t"
174177
encoded58 := base58.Encode(original)
175178
encoded58Check := base58neogo.CheckEncode(original)
176179
ic := &interop.Context{VM: vm.New()}
@@ -188,6 +191,16 @@ func TestStdLibEncodeDecode(t *testing.T) {
188191
require.PanicsWithError(t, ErrTooBigInput.Error(),
189192
func() { s.base64Encode(ic, bigInputArgs) })
190193
})
194+
t.Run("Encode64Url", func(t *testing.T) {
195+
require.NotPanics(t, func() {
196+
actual = s.base64UrlEncode(ic, []stackitem.Item{stackitem.Make(originalUrl)})
197+
})
198+
require.Equal(t, stackitem.Make(encoded64Url), actual)
199+
})
200+
t.Run("Encode64Url/error", func(t *testing.T) {
201+
require.PanicsWithError(t, ErrTooBigInput.Error(),
202+
func() { s.base64UrlEncode(ic, bigInputArgs) })
203+
})
191204
t.Run("Encode58", func(t *testing.T) {
192205
require.NotPanics(t, func() {
193206
actual = s.base58Encode(ic, []stackitem.Item{stackitem.Make(original)})
@@ -224,6 +237,22 @@ func TestStdLibEncodeDecode(t *testing.T) {
224237
require.PanicsWithError(t, ErrTooBigInput.Error(),
225238
func() { s.base64Decode(ic, bigInputArgs) })
226239
})
240+
t.Run("Decode64Url/positive", func(t *testing.T) {
241+
require.NotPanics(t, func() {
242+
actual = s.base64UrlDecode(ic, []stackitem.Item{stackitem.Make(encoded64Url)})
243+
})
244+
require.Equal(t, stackitem.Make(originalUrl), actual)
245+
})
246+
t.Run("Decode64Url/error", func(t *testing.T) {
247+
require.Panics(t, func() {
248+
_ = s.base64UrlDecode(ic, []stackitem.Item{stackitem.Make(encoded64Url + "%")})
249+
})
250+
require.Panics(t, func() {
251+
_ = s.base64UrlDecode(ic, []stackitem.Item{stackitem.NewInterop(nil)})
252+
})
253+
require.PanicsWithError(t, ErrTooBigInput.Error(),
254+
func() { s.base64UrlDecode(ic, bigInputArgs) })
255+
})
227256
t.Run("Decode58/positive", func(t *testing.T) {
228257
require.NotPanics(t, func() {
229258
actual = s.base58Decode(ic, []stackitem.Item{stackitem.Make(encoded58)})

0 commit comments

Comments
 (0)