Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CU-86dtv5633 - NeonInvoker - Wrong ByteArray type conversion #41

Merged
merged 1 commit into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@cityofzion/neon-dappkit",
"comment": "Check if the ByteArray parameter value is a base64 if it's not a hex string",
"type": "patch"
}
],
"packageName": "@cityofzion/neon-dappkit"
}
12 changes: 9 additions & 3 deletions packages/neon-dappkit/ARGUMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,16 +185,22 @@ invoker.testInvoke({

### ByteArray

It is expecting to receive a HEX string as value on args. It automatically converts a hex to base64.
It is expecting to receive a HEX string as value on args and will automatically converts a hex to base64.
If it's not a valid hex string, then it will check if it's a valid base64 string.
Otherwise, it will throw an error.

```ts
const hexValue = 'HEX string'
const hexValue = '12af980c' // This will end up being converted to base64 'Eq+YDA=='
const base64Value = 'qqLQB6T6hHfzFNUlpWTJ8A=='
invoker.testInvoke({
invocations: [
{
operation: 'method',
scriptHash: contractScriptHash,
args: [{ type: 'ByteArray', value: hexValue }],
args: [
{ type: 'ByteArray', value: hexValue },
{ type: 'ByteArray', value: base64Value },
],
},
],
})
Expand Down
17 changes: 15 additions & 2 deletions packages/neon-dappkit/src/NeonInvoker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,21 @@ export class NeonInvoker implements Neo3Invoker {
value: this.convertParams([map.value])[0],
})),
)
case 'ByteArray':
return sc.ContractParam.byteArray(u.hex2base64(a.value))
case 'ByteArray': {
const base64Regex = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/
const hexLowerRegex = /^([0-9a-f]{2})*$/
const hexUpperRegex = /^([0-9A-F]{2})*$/
let byteArrayValue
if (hexLowerRegex.test(a.value) || hexUpperRegex.test(a.value)) {
byteArrayValue = u.hex2base64(a.value)
} else if (base64Regex.test(a.value)) {
byteArrayValue = a.value
} else {
throw new Error(`Invalid ByteArray value, should be either a valid hex or base64, got: ${a.value}`)
}

return sc.ContractParam.byteArray(byteArrayValue)
}
}
})
}
Expand Down
146 changes: 145 additions & 1 deletion packages/neon-dappkit/test/NeonInvoker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ContractInvocationMulti } from '@cityofzion/neon-dappkit-types'
import { NeonEventListener, NeonInvoker, NeonParser, TypeChecker } from '../src/index'
import assert from 'assert'
import * as path from 'path'
import { tx } from '@cityofzion/neon-js'
import { tx, u } from '@cityofzion/neon-js'
import { wallet } from '@cityofzion/neon-core'
import {
wait,
Expand Down Expand Up @@ -783,4 +783,148 @@ describe('NeonInvoker', function () {
assert.fail('stack return is not Map')
}
})

it('checks if the bytearray arg value is hex or base64', async () => {
const invoker = await NeonInvoker.init({
rpcAddress,
account: account1,
})

const byteArrayCim = (bytearrayArg: string) => {
return {
invocations: [
{
scriptHash: testReturnContract,
operation: 'return_same_bytes',
args: [{ type: 'ByteArray', value: bytearrayArg }],
},
],
} as ContractInvocationMulti
}

// Will turn hex into base64
let validHexValue = 'a0b1c3'
let hexArgTxResult = await invoker.testInvoke(byteArrayCim(validHexValue))
if (!TypeChecker.isStackTypeByteString(hexArgTxResult.stack[0])) {
throw new Error('hexArgTxResult: stack return is not ByteString')
}
let hexArgTxValue = hexArgTxResult.stack[0].value
assert.equal(hexArgTxValue, u.hex2base64(validHexValue), 'hexArgTxValue is not equal to base64 parsed value')

validHexValue = 'A0B1C3'
hexArgTxResult = await invoker.testInvoke(byteArrayCim(validHexValue))
if (!TypeChecker.isStackTypeByteString(hexArgTxResult.stack[0])) {
throw new Error('hexArgTxResult: stack return is not ByteString')
}
hexArgTxValue = hexArgTxResult.stack[0].value
assert.equal(hexArgTxValue, u.hex2base64(validHexValue), 'hexArgTxValue is not equal to base64 parsed value')

// Will use and return base64
const validBase64Value = 'nJInjs09a2A='
const base64ArgTxResult = await invoker.testInvoke(byteArrayCim(validBase64Value))
if (!TypeChecker.isStackTypeByteString(base64ArgTxResult.stack[0])) {
throw new Error('base64ArgTxResult: stack return is not ByteString')
}
const base64ArgTxValue = base64ArgTxResult.stack[0].value
assert.equal(base64ArgTxValue, validBase64Value, 'base64ArgTxValue is not equal to base64 value')

// Will consider as hex if both are possible and parse it to base64
const validBase64AndHexValue = 'abcd'
const base64AndHexArgTxResult = await invoker.testInvoke(byteArrayCim(validBase64AndHexValue))
if (!TypeChecker.isStackTypeByteString(base64AndHexArgTxResult.stack[0])) {
throw new Error('base64ArgTxResult: stack return is not ByteString')
}
const base64AndHexArgTxValue = base64AndHexArgTxResult.stack[0].value
assert.equal(
base64AndHexArgTxValue,
u.hex2base64(validBase64AndHexValue),
'base64AndHexArgTxValue is not equal to base64 parsed value',
)

// Technally, 'aBCd' is a valid hex value, however,
// we chose to consider a string to have lower and upper case characters as a invalid hex value
// so this means that it will be considered as base64
const validUpperLowerValue = 'aBCd'
const upperLowerArgTxResult = await invoker.testInvoke(byteArrayCim(validUpperLowerValue))
if (!TypeChecker.isStackTypeByteString(upperLowerArgTxResult.stack[0])) {
throw new Error('upperLowerArgTxResult: stack return is not ByteString')
}
const upperLowerArgTxValue = upperLowerArgTxResult.stack[0].value
assert.equal(upperLowerArgTxValue, validUpperLowerValue, 'upperLowerArgTxValue is not equal to base64 parsed value')

// Using an array with all 3 previous values should return the same values as expected above
const arrayArgTxResult = await invoker.testInvoke({
invocations: [
{
scriptHash: testReturnContract,
operation: 'return_same_array',
args: [
{
type: 'Array',
value: [
{ type: 'ByteArray', value: validHexValue },
{ type: 'ByteArray', value: validBase64Value },
{ type: 'ByteArray', value: validBase64AndHexValue },
],
},
],
},
],
})
if (!TypeChecker.isStackTypeArray(arrayArgTxResult.stack[0])) {
throw new Error('arrayArgTxResult: stack return is not Array')
}
const argTxValue = arrayArgTxResult.stack[0].value
assert.equal(argTxValue.length, 3, 'arrayArgTxValue length is not 3')
if (!TypeChecker.isStackTypeByteString(argTxValue[0])) {
throw new Error('argTxValue[0].value: stack return is not ByteString')
}
assert.equal(
argTxValue[0].value,
u.hex2base64(validHexValue),
'arrayArgTxValue[0] is not equal to base64 parsed value',
)
if (!TypeChecker.isStackTypeByteString(argTxValue[1])) {
throw new Error('argTxValue[1].value: stack return is not ByteString')
}
assert.equal(argTxValue[1].value, validBase64Value, 'arrayArgTxValue[1] is not equal to base64 value')
if (!TypeChecker.isStackTypeByteString(argTxValue[2])) {
throw new Error('argTxValue[2].value: stack return is not ByteString')
}
assert.equal(
argTxValue[2].value,
u.hex2base64(validBase64AndHexValue),
'arrayArgTxValue[2] is not equal to base64 parsed value',
)
})

it('tests invalid bytearray args', async () => {
const invoker = await NeonInvoker.init({
rpcAddress,
account: account1,
})

const byteArrayCim = (bytearrayArg: string) => {
return {
invocations: [
{
scriptHash: testReturnContract,
operation: 'return_same_bytes',
args: [{ type: 'ByteArray', value: bytearrayArg }],
},
],
} as ContractInvocationMulti
}

let invalidValue = 'abc'
await assert.rejects(
invoker.testInvoke(byteArrayCim(invalidValue)),
`Invalid bytearray value '${invalidValue}' should throw an error`,
)
invalidValue = 'çãmq'
await assert.rejects(
invoker.testInvoke(byteArrayCim(invalidValue)),
`Invalid bytearray value '${invalidValue}' should throw an error`,
)
})
})
Loading