Skip to content

Commit

Permalink
Add unsigned types (#598)
Browse files Browse the repository at this point in the history
  • Loading branch information
gregLibert authored May 6, 2024
1 parent eea9622 commit 62fc246
Show file tree
Hide file tree
Showing 9 changed files with 495 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from './operation'
export * from './storage'
export * from './args'
export * as Mas from './mas'
export * from './serializers'
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ export function argsListToBytes(argsList: IParam[]): Uint8Array {
new Uint8Array(0)
)
}

export * from './number/index'
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * as U8 from './u8'
export * as U16 from './u16'
export * as U32 from './u32'
export * as U64 from './u64'
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { U16, U32, U64, U8 } from '..'
import { FIRST, ONE, ZERO } from '../../../utils'

function mustBeValidUnsigned(sizeInBits: number, value: bigint): void {
if (value < BigInt(ZERO)) {
throw new Error("negative value can't be serialized as unsigned integer.")
}
if (value >= BigInt(ONE) << BigInt(sizeInBits)) {
throw new Error(`value ${value} is too large for an U${sizeInBits}.`)
}
}

export function unsignedToByte(sizeInBits: number, value: bigint): Uint8Array {
mustBeValidUnsigned(sizeInBits, value)
const buffer = new ArrayBuffer(sizeInBits / U8.SIZE_BIT)
const view = new DataView(buffer)
switch (sizeInBits) {
case U8.SIZE_BIT:
view.setUint8(FIRST, Number(value))
break
case U16.SIZE_BIT:
view.setUint16(FIRST, Number(value), true)
break
case U32.SIZE_BIT:
view.setUint32(FIRST, Number(value), true)
break
case U64.SIZE_BIT:
view.setBigUint64(FIRST, value, true)
break
default:
throw new Error(`unsupported U${sizeInBits} serialization.`)
}
return new Uint8Array(view.buffer)
}

export function unsignedFromByte(
sizeInBits: number,
bytes: Uint8Array,
index = FIRST
): bigint {
if (bytes.length < index + sizeInBits / U8.SIZE_BIT) {
throw new Error('not enough bytes to read the value.')
}
const view = new DataView(bytes.buffer)
switch (sizeInBits) {
case U8.SIZE_BIT:
return BigInt(view.getUint8(index))
case U16.SIZE_BIT:
return BigInt(view.getUint16(index, true))
case U32.SIZE_BIT:
return BigInt(view.getUint32(index, true))
case U64.SIZE_BIT:
return view.getBigUint64(index, true)
default:
throw new Error(`unsupported U${sizeInBits} deserialization`)
}
}

export function numberToUnsigned(
sizeInBits: number,
value: number | bigint
): bigint {
if (typeof value === 'number' && !Number.isSafeInteger(value)) {
throw new Error(`value ${value} is not a safe integer.`)
}
const int = BigInt(value)
mustBeValidUnsigned(sizeInBits, int)
return int
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { U8 } from '.'
import { ONE } from '../../../utils'
import { numberToUnsigned, unsignedFromByte, unsignedToByte } from './integers'

export type U16 = bigint

export const SIZE_BYTE = 2
export const SIZE_BIT = SIZE_BYTE * U8.SIZE_BIT
export const MAX = (BigInt(ONE) << BigInt(SIZE_BIT)) - BigInt(ONE)

/**
* Converts an U16 value to bytes
*
* @param value - The number to convert
* @returns The bytes representation of the number
* @throws if the value is negative or too large for U16
*/
export function toBytes(value: U16): Uint8Array {
return unsignedToByte(SIZE_BIT, value)
}

/**
* Converts bytes to an U16 value
*
* @remarks
* Silently ignores bytes that are not needed to represent the U8 value.
*
* @param bytes - The bytes to convert
* @returns The U16 representation of the bytes
*/
export function fromBytes(bytes: Uint8Array): U16 {
return unsignedFromByte(SIZE_BIT, bytes)
}

/**
* Converts an U16 value to a number
* @param buffer - The buffer to read from
* @param offset - The optional offset in the buffer at which to start reading the U16 value (default: 0)
* @returns The U16 representation of the bytes
*/
export function fromBuffer(
buffer: Uint8Array,
offset: number
): { value: U16; offset: number } {
const value = unsignedFromByte(SIZE_BIT, buffer, offset)
offset += SIZE_BYTE
return { value, offset }
}

/**
* Converts a number to an U16 value
*
* @param value - The number to convert
* @returns The U16 representation of the number
* @throws if the value is not a safe integer, negative or too large for U16
*/
export function fromNumber(value: number): U16 {
return numberToUnsigned(SIZE_BIT, value)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { U8 } from '.'
import { ONE } from '../../../utils'
import { numberToUnsigned, unsignedFromByte, unsignedToByte } from './integers'

export type U32 = bigint

export const SIZE_BYTE = 4
export const SIZE_BIT = SIZE_BYTE * U8.SIZE_BIT
export const MAX = (BigInt(ONE) << BigInt(SIZE_BIT)) - BigInt(ONE)

/**
* Converts an U32 value to bytes
*
* @param value - The number to convert
* @returns The bytes representation of the number
* @throws if the value is negative or too large for U32
*/
export function toBytes(value: U32): Uint8Array {
return unsignedToByte(SIZE_BIT, value)
}

/**
* Converts bytes to an U32 value
*
* @remarks
* Silently ignores bytes that are not needed to represent the U8 value.
*
* @param bytes - The bytes to convert
* @returns The U32 representation of the bytes
*/
export function fromBytes(bytes: Uint8Array): U32 {
return unsignedFromByte(SIZE_BIT, bytes)
}

/**
* Converts an U32 value to a number
* @param buffer - The buffer to read from
* @param offset - The optional offset in the buffer at which to start reading the U32 value (default: 0)
* @returns The U32 representation of the bytes
*/
export function fromBuffer(
buffer: Uint8Array,
offset: number
): { value: U32; offset: number } {
const value = unsignedFromByte(SIZE_BIT, buffer, offset)
offset += SIZE_BYTE
return { value, offset }
}

/**
* Converts a number to an U32 value
*
* @param value - The number to convert
* @returns The U32 representation of the number
* @throws if the value is not a safe integer, negative or too large for U32
*/
export function fromNumber(value: number): U32 {
return numberToUnsigned(SIZE_BIT, value)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { U8 } from '.'
import { ONE } from '../../../utils'
import { numberToUnsigned, unsignedFromByte, unsignedToByte } from './integers'

export type U64 = bigint

export const SIZE_BYTE = 8
export const SIZE_BIT = SIZE_BYTE * U8.SIZE_BIT
export const MAX = (BigInt(ONE) << BigInt(SIZE_BIT)) - BigInt(ONE)

/**
* Converts an U64 value to bytes
*
* @param value - The number to convert
* @returns The bytes representation of the number
* @throws if the value is negative or too large for U64
*/
export function toBytes(value: U64): Uint8Array {
return unsignedToByte(SIZE_BIT, value)
}

/**
* Converts bytes to an U64 value
*
* @remarks
* Silently ignores bytes that are not needed to represent the U8 value.
*
* @param bytes - The bytes to convert
* @returns The U64 representation of the bytes
*/
export function fromBytes(bytes: Uint8Array): U64 {
return unsignedFromByte(SIZE_BIT, bytes)
}

/**
* Converts an U64 value to a number
* @param buffer - The buffer to read from
* @param offset - The optional offset in the buffer at which to start reading the U64 value (default: 0)
* @returns The U64 representation of the bytes
*/
export function fromBuffer(
buffer: Uint8Array,
offset: number
): { value: U64; offset: number } {
const value = unsignedFromByte(SIZE_BIT, buffer, offset)
offset += SIZE_BYTE
return { value, offset }
}

/**
* Converts a number to an U64 value
*
* @param value - The number to convert
* @returns The U64 representation of the number
* @throws if the value is not a safe integer, negative or too large for U64
*/
export function fromNumber(value: number | bigint): U64 {
return numberToUnsigned(SIZE_BIT, value)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { ONE } from '../../../utils'
import { numberToUnsigned, unsignedFromByte, unsignedToByte } from './integers'

export type U8 = bigint

export const SIZE_BYTE = 1
export const SIZE_BIT = 8
export const MAX = (BigInt(ONE) << BigInt(SIZE_BIT)) - BigInt(ONE)

/**
* Converts an U8 value to bytes
*
* @param value - The number to convert
* @returns The bytes representation of the number
* @throws if the value is negative or too large for U8
*/
export function toBytes(value: U8): Uint8Array {
return unsignedToByte(SIZE_BIT, value)
}

/**
* Converts bytes to an U8 value
*
* @remarks
* Silently ignores bytes that are not needed to represent the U8 value.
*
* @param bytes - The bytes to convert
* @returns The U8 representation of the bytes
*/
export function fromBytes(bytes: Uint8Array): U8 {
return unsignedFromByte(SIZE_BIT, bytes)
}

/**
* Converts an U8 value to a number
* @param buffer - The buffer to read from
* @param offset - The optional offset in the buffer at which to start reading the U8 value (default: 0)
* @returns The U8 representation of the bytes
*/
export function fromBuffer(
buffer: Uint8Array,
offset: number
): { value: U8; offset: number } {
const value = unsignedFromByte(SIZE_BIT, buffer, offset)
offset += SIZE_BYTE
return { value, offset }
}

/**
* Converts a number to an U8 value
*
* @param value - The number to convert
* @returns The U8 representation of the number
* @throws if the value is not a safe integer, negative or too large for U8
*/
export function fromNumber(value: number): U8 {
return numberToUnsigned(SIZE_BIT, value)
}
Loading

2 comments on commit 62fc246

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report for experimental massa-web3

St.❔
Category Percentage Covered / Total
🟒 Statements 84.47% 968/1146
🟑 Branches 76.4% 191/250
🟒 Functions 85.71% 186/217
🟒 Lines 84.62% 968/1144

Test suite run success

121 tests passing in 13 suites.

Report generated by πŸ§ͺjest coverage report action from 62fc246

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report for experimental massa-web3

St.❔
Category Percentage Covered / Total
🟒 Statements 84.47% 968/1146
🟑 Branches 76.4% 191/250
🟒 Functions 85.71% 186/217
🟒 Lines 84.62% 968/1144

Test suite run success

121 tests passing in 13 suites.

Report generated by πŸ§ͺjest coverage report action from 62fc246

Please sign in to comment.