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

docs: updated verious util function docs, added tests, refactoring #1129

Merged
merged 10 commits into from
Jun 5, 2024
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
* ```
dsperac marked this conversation as resolved.
Show resolved Hide resolved
*/
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: ...,
* // }
* ```
dsperac marked this conversation as resolved.
Show resolved Hide resolved
*/
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: ...,
* // }
* ```
dsperac marked this conversation as resolved.
Show resolved Hide resolved
*/
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, 11223344556677889900]';
* const result = parseAlwaysAsBig(str);
* // result = [123n, 12.3, 11223344556677890048n]
* ```
*/
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 str = '[123, 12.3, 11223344556677889900]';
* const result = stringify(str);
* // result = [123, 12.3, 11223344556677890048n]
dsperac marked this conversation as resolved.
Show resolved Hide resolved
* ```
*/
export const stringify = (
value: unknown,
Expand Down