Skip to content

Commit

Permalink
Merge pull request #41 from CityOfZion/CU-86dtv5633
Browse files Browse the repository at this point in the history
CU-86dtv5633 - NeonInvoker - Wrong ByteArray type conversion
  • Loading branch information
melanke authored Jun 19, 2024
2 parents 981d0b5 + 37212e8 commit 33cd81e
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 6 deletions.
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`,
)
})
})

0 comments on commit 33cd81e

Please sign in to comment.