Skip to content

Commit

Permalink
docs: updated verious util function docs, added tests, refactoring (#…
Browse files Browse the repository at this point in the history
…1129)

* docs: updated verious util function docs, added tests, refactoring

* docs: json util jsdocs improvements

* chore: typo quickfix

* chore: added missing results to jsdoc examples

* chore: removed jsdoc from non-exported helper function

* chore: utils/json docs fixes and refactoring
  • Loading branch information
dsperac authored Jun 5, 2024
1 parent de47bad commit 54fde47
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 19 deletions.
23 changes: 22 additions & 1 deletion __tests__/utils/address.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,22 @@ import {
validateChecksumAddress,
} from '../../src/utils/address';

describe('addAddressPadding', () => {
test('should correctly add padding', () => {
const addr = '0x6eff1d71';
const padded = '0x000000000000000000000000000000000000000000000000000000006eff1d71';

return expect(addAddressPadding(addr)).toBe(padded);
});

test('should add hex prefix', () => {
const addr = 'a7ee790591d9fa3efc87067d95a643f8455e0b8190eb8cb7bfd39e4fb7571fdf';
const padded = '0xa7ee790591d9fa3efc87067d95a643f8455e0b8190eb8cb7bfd39e4fb7571fdf';

return expect(addAddressPadding(addr)).toBe(padded);
});
});

describe('validateAndParseAddress', () => {
test('should pass when correct starknet address is passed', () => {
const addr = '0x7ee790591d9fa3efc87067d95a643f8455e0b8190eb8cb7bfd39e4fb7571fdf';
Expand All @@ -14,11 +30,16 @@ describe('validateAndParseAddress', () => {
});

test('should add 0x prefix if not provided', () => {
const addr = '0x6eff1d71068df8e6677f59a556151c56ed13e14ad431a9bef6fcb3fc5e6fa7';
const addr = '6eff1d71068df8e6677f59a556151c56ed13e14ad431a9bef6fcb3fc5e6fa7';

return expect(validateAndParseAddress(addr)).toEqual(`${addAddressPadding(addr)}`);
});

test('should fail for invalid address', () => {
const addr = 'test';
expect(() => validateAndParseAddress(addr)).toThrow('Cannot convert 0xtest to a BigInt');
});

test('should fail for out of bound address', () => {
const addr = num.toHex(constants.ADDR_BOUND + 1n);
expect(() => validateAndParseAddress(addr)).toThrow(/^Message not signable/);
Expand Down
31 changes: 31 additions & 0 deletions __tests__/utils/contract.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { isSierra, extractContractHashes } from '../../src/utils/contract';
import { compiledHelloSierra, compiledHelloSierraCasm, compiledErc20 } from '../config/fixtures';

describe('isSierra', () => {
test('should return true for a contract in Sierra format', () => {
expect(isSierra(compiledHelloSierra)).toBe(true);
});

test('should return false for a contract not in Sierra format', () => {
expect(isSierra(compiledErc20)).toBe(false);
});
});

describe('extractContractHashes', () => {
test('should properly extract hashes from contract', () => {
const declareContractPayload = {
contract: compiledHelloSierra,
casm: compiledHelloSierraCasm,
};
const result = extractContractHashes(declareContractPayload);

expect(result).toHaveProperty(
'classHash',
'0x50f3c3b9bb088969310de339fd1c1da88945f5db15bd5ea0810e4d954308734'
);
expect(result).toHaveProperty(
'compiledClassHash',
'0x31c736e739e4bd35116ed6cdcbb99c94e6f4fa8268d339da23e1ca80fe1de8d'
);
});
});
12 changes: 8 additions & 4 deletions src/utils/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { assertInRange, toHex } from './num';

/**
* Format a hex number to '0x' and 64 characters, adding leading zeros if necessary.
*
* @param {BigNumberish} address
* @returns {string} Hex string : 0x followed by 64 characters. No upper case characters in the response.
* @example
Expand All @@ -19,13 +20,17 @@ import { assertInRange, toHex } from './num';
* ```
*/
export function addAddressPadding(address: BigNumberish): string {
return addHexPrefix(removeHexPrefix(toHex(address)).padStart(64, '0'));
const hex = toHex(addHexPrefix(address.toString()));
const padded = removeHexPrefix(hex).padStart(64, '0');
return addHexPrefix(padded);
}

/**
* Check the validity of a Starknet address, and format it as a hex number : '0x' and 64 characters, adding leading zeros if necessary.
*
* @param {BigNumberish} address
* @returns {string} Hex string : 0x followed by 64 characters. No upper case characters in the response.
* @throws address argument must be a valid address inside the address range bound
* @example
* ```typescript
* const address = "0x90591d9fa3efc87067d95a643f8455e0b8190eb8cb7bfd39e4fb7571fdf";
Expand All @@ -34,14 +39,14 @@ export function addAddressPadding(address: BigNumberish): string {
* ```
*/
export function validateAndParseAddress(address: BigNumberish): string {
assertInRange(address, ZERO, ADDR_BOUND - 1n, 'Starknet Address');

const result = addAddressPadding(address);

if (!result.match(/^(0x)?[0-9a-fA-F]{64}$/)) {
throw new Error('Invalid Address Format');
}

assertInRange(result, ZERO, ADDR_BOUND - 1n, 'Starknet Address');

return result;
}

Expand Down Expand Up @@ -80,7 +85,6 @@ export function getChecksumAddress(address: BigNumberish): string {
* a given address to reduce the risk of errors introduced from typing an address or cut and paste issues.
*
* @param address string
*
* @returns true if the ChecksumAddress is valid
* @example
* ```typescript
Expand Down
5 changes: 5 additions & 0 deletions src/utils/assert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
* @param {any} condition - The condition to check.
* @param {string} [message] - The optional message to include in the error.
* @throws {Error} Throws an error if the condition is false.
* @example
* ```typescript
* const address = '0xa7ee790591d9fa3efc87067d95a643f8455e0b8190eb8cb7bfd39e4fb7571fdf';
* assert(/^(0x)?[0-9a-fA-F]{64}$/.test(address), 'Invalid address format');
* ```
*/
export default function assert(condition: boolean, message?: string): asserts condition {
if (!condition) {
Expand Down
17 changes: 15 additions & 2 deletions src/utils/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ import { isString } from './shortString';
*
* @param {CairoContract | string} contract - The contract to check. Can be either a CairoContract object or a string representation of the contract.
* @return {boolean} - Returns true if the contract is a Sierra contract, otherwise false.
* @example
* ```typescript
* const result = isSierra(contract);
* // result = true | false
* ```
*/
export function isSierra(
contract: CairoContract | string
Expand All @@ -30,10 +35,18 @@ export function isSierra(
* Extracts contract hashes from `DeclareContractPayload`.
*
* @param {DeclareContractPayload} payload - The payload containing contract information.
*
* @return {CompleteDeclareContractPayload} - The `CompleteDeclareContractPayload` with extracted contract hashes.
*
* @throws {Error} - If extraction of compiledClassHash or classHash fails.
* @example
* ```typescript
* const result = extractContractHashes(contract);
* // result = {
* // contract: ...,
* // classHash: ...,
* // casm: ...,
* // compiledClassHash: ...,
* // }
* ```
*/
export function extractContractHashes(
payload: DeclareContractPayload
Expand Down
29 changes: 28 additions & 1 deletion src/utils/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,34 @@ import { cleanHex } from './num';
* Parse Transaction Receipt Event from UDC invoke transaction and
* create DeployContractResponse compatible response with addition of the UDC Event data
*
* @returns DeployContractResponse | UDC Event Response data
* @param {InvokeTransactionReceiptResponse} txReceipt
* @return {object} Object including DeployContractResponse and UDC Event data
* @example
* ```typescript
* const deployment = await account.deploy({
* classHash,
* constructorCalldata: [
* encodeShortString('Token'),
* encodeShortString('ERC20'),
* account.address,
* ],
* salt,
* unique: true,
* });
* const txReceipt = await provider.waitForTransaction(deployment.transaction_hash);
* const udcEvent = parseUDCEvent(txReceipt as any);
* // udcEvent = {
* // transaction_hash: ...,
* // contract_address: ...,
* // address: ...,
* // deployer: ...,
* // unique: ...,
* // classHash: ...,
* // calldata_len: ...,
* // calldata: ...,
* // salt: ...,
* // }
* ```
*/
export function parseUDCEvent(txReceipt: InvokeTransactionReceiptResponse) {
if (!txReceipt.events) {
Expand Down
49 changes: 38 additions & 11 deletions src/utils/json.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,63 @@
import * as json from 'lossless-json';

/**
* Convert string to number or bigint based on size
* Helper to convert string to number or bigint based on size
*/
const parseIntAsNumberOrBigInt = (x: string) => {
if (!json.isInteger(x)) return parseFloat(x);
const v = parseInt(x, 10);
return Number.isSafeInteger(v) ? v : BigInt(x);
const parseIntAsNumberOrBigInt = (str: string) => {
if (!json.isInteger(str)) return parseFloat(str);
const num = parseInt(str, 10);
return Number.isSafeInteger(num) ? num : BigInt(str);
};

/**
* Convert JSON string to JSON object
*
* NOTE: the String() wrapping is used so the behavior conforms to JSON.parse()
* which can accept simple data types but is not represented in the default typing
* @param x JSON string
*
* @param str JSON string
* @return {object} Parsed json object
* @example
* ```typescript
* const str = '[123, 12.3, 11223344556677889900]';
* const result = parse(str);
* // result = [123, 12.3, 11223344556677890048n]
* ```
*/
export const parse = (x: string): any => json.parse(String(x), undefined, parseIntAsNumberOrBigInt);
export const parse = (str: string): any =>
json.parse(String(str), undefined, parseIntAsNumberOrBigInt);

/**
* Convert JSON string to JSON object with all numbers as bigint
* @param x JSON string
* @param str JSON string
* @return {object} Parsed json object
* @example
* ```typescript
* const str = '[123, 12.3, 1234567890]';
* const result = parseAlwaysAsBig(str);
* // result = [123n, 12.3, 1234567890n]
* ```
*/
export const parseAlwaysAsBig = (x: string): any =>
json.parse(String(x), undefined, json.parseNumberAndBigInt);
export const parseAlwaysAsBig = (str: string): any =>
json.parse(String(str), undefined, json.parseNumberAndBigInt);

/**
* Convert JSON object to JSON string
*
* NOTE: the not-null assertion is used so the return type conforms to JSON.stringify()
* which can also return undefined but is not represented in the default typing
* @returns JSON string
*
* @param value JSON object
* @param [replacer] Function that alters the behavior of the stringification process
* @param [space] Used to insert white space into the output JSON string
* @param [numberStringifiers] Function used to stringify numbers (returning undefined will delete the property from the object)
* @return {string} JSON string
* @example
* ```typescript
* const value = [123, 12.3, 1234567890];
* const result = stringify(value);
* // result = '[123,12.3,1234567890]'
* ```
*/
export const stringify = (
value: unknown,
Expand Down

0 comments on commit 54fde47

Please sign in to comment.