Skip to content

Commit

Permalink
100% Coverage web3-utills (#7042)
Browse files Browse the repository at this point in the history
* increase unit tests for SocketProvider

* add a test for SocketProvider

* add a test for PromiseHelper

* add unit tests for utils

* remove un visited code branch

* fix linting issue

* point to a pice of code to investigate

* fix linting issue

* add coverage to hash

* cover json-rpc tests

* cover socket provider file

* add coverage string manipulation

* add coverage to formatters.ts

* fix failing tests

* update hash

* run prettier

* remove unintended cases for formatter and convert

---------

Co-authored-by: Alex Luu <alex.luu@mail.utoronto.ca>
  • Loading branch information
Muhammad-Altabba and Alex Luu committed Jun 3, 2024
1 parent 51d216f commit 8f62749
Show file tree
Hide file tree
Showing 22 changed files with 959 additions and 113 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,5 @@ packages/web3/.in3/
benchmark-data.txt

.eslintcache

.history
37 changes: 23 additions & 14 deletions packages/web3-utils/src/converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ export const ethUnitMap = {
tether: BigInt('1000000000000000000000000000000'),
};

const PrecisionLossWarning = 'Warning: Using type `number` with values that are large or contain many decimals may cause loss of precision, it is recommended to use type `string` or `BigInt` when using conversion methods';
const PrecisionLossWarning =
'Warning: Using type `number` with values that are large or contain many decimals may cause loss of precision, it is recommended to use type `string` or `BigInt` when using conversion methods';

export type EtherUnits = keyof typeof ethUnitMap;
/**
Expand Down Expand Up @@ -366,7 +367,7 @@ export const toHex = (
return returnType ? 'bigint' : numberToHex(value);
}

if(isUint8Array(value)) {
if (isUint8Array(value)) {
return returnType ? 'bytes' : bytesToHex(value);
}

Expand All @@ -386,6 +387,15 @@ export const toHex = (
return returnType ? 'bytes' : `0x${value}`;
}
if (isHex(value) && !isInt(value) && isUInt(value)) {
// This condition seems problematic because meeting
// both conditions `!isInt(value) && isUInt(value)` should be impossible.
// But a value pass for those conditions: "101611154195520776335741463917853444671577865378275924493376429267637792638729"
// Note that according to the docs: it is supposed to be treated as a string (https://docs.web3js.org/guides/web3_upgrade_guide/x/web3_utils_migration_guide#conversion-to-hex)
// In short, the strange is that isInt(value) is false but isUInt(value) is true for the value above.
// TODO: isUInt(value) should be investigated.

// However, if `toHex('101611154195520776335741463917853444671577865378275924493376429267637792638729', true)` is called, it will return `true`.
// But, if `toHex('101611154195520776335741463917853444671577865378275924493376429267637792638729')` is called, it will throw inside `numberToHex`.
return returnType ? 'uint' : numberToHex(value);
}

Expand Down Expand Up @@ -419,14 +429,14 @@ export const toHex = (
*/
export const toNumber = (value: Numbers): number | bigint => {
if (typeof value === 'number') {
if (value > 1e+20) {
console.warn(PrecisionLossWarning)
// JavaScript converts numbers >= 10^21 to scientific notation when coerced to strings,
// leading to potential parsing errors and incorrect representations.
// For instance, String(10000000000000000000000) yields '1e+22'.
// Using BigInt prevents this
return BigInt(value);
}
if (value > 1e20) {
console.warn(PrecisionLossWarning);
// JavaScript converts numbers >= 10^21 to scientific notation when coerced to strings,
// leading to potential parsing errors and incorrect representations.
// For instance, String(10000000000000000000000) yields '1e+22'.
// Using BigInt prevents this
return BigInt(value);
}
return value;
}

Expand Down Expand Up @@ -506,10 +516,9 @@ export const fromWei = (number: Numbers, unit: EtherUnits | number): string => {
if (unit < 0 || !Number.isInteger(unit)) {
throw new InvalidIntegerError(unit);
}
denomination = bigintPower(BigInt(10),BigInt(unit));
denomination = bigintPower(BigInt(10), BigInt(unit));
}


// value in wei would always be integer
// 13456789, 1234
const value = String(toNumber(number));
Expand Down Expand Up @@ -575,8 +584,8 @@ export const toWei = (number: Numbers, unit: EtherUnits | number): string => {
if (unit < 0 || !Number.isInteger(unit)) {
throw new InvalidIntegerError(unit);
}
denomination = bigintPower(BigInt(10),BigInt(unit));

denomination = bigintPower(BigInt(10), BigInt(unit));
}

let parsedNumber = number;
Expand Down
12 changes: 3 additions & 9 deletions packages/web3-utils/src/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,8 @@ const findSchemaByDataPath = (

for (const dataPart of dataPath) {
if (result.oneOf && previousDataPath) {
const path = oneOfPath.find(function (element: [string, number]) {
return (this as unknown as string) === element[0];
}, previousDataPath ?? '');

const currentDataPath = previousDataPath;
const path = oneOfPath.find(([key]) => key === currentDataPath);
if (path && path[0] === previousDataPath) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
result = result.oneOf[path[1]];
Expand All @@ -75,10 +73,6 @@ const findSchemaByDataPath = (
} else if (result.items && (result.items as JsonSchema).properties) {
const node = (result.items as JsonSchema).properties as Record<string, JsonSchema>;

if (!node) {
return undefined;
}

result = node[dataPart];
} else if (result.items && isObject(result.items)) {
result = result.items;
Expand Down Expand Up @@ -307,7 +301,7 @@ export const convert = (

// If value is an object, recurse into it
if (isObject(value)) {
convert(value, schema, dataPath, format);
convert(value, schema, dataPath, format, oneOfPath);
dataPath.pop();
continue;
}
Expand Down
17 changes: 8 additions & 9 deletions packages/web3-utils/src/hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,25 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.

/**
* This package provides utility functions for Ethereum dapps and other web3.js packages.
*
*
* For using Utils functions, first install Web3 package using `npm i web3` or `yarn add web3`.
* After that, Web3 Utils functions will be available as mentioned below.
* After that, Web3 Utils functions will be available as mentioned below.
* ```ts
* import { Web3 } from 'web3';
* const web3 = new Web3();
*
*
* const value = web3.utils.fromWei("1", "ether")
*
*
* ```
*
*
* For using individual package install `web3-utils` package using `npm i web3-utils` or `yarn add web3-utils` and only import required functions.
* This is more efficient approach for building lightweight applications.
* This is more efficient approach for building lightweight applications.
* ```ts
* import { fromWei, soliditySha3Raw } from 'web3-utils';
*
*
* console.log(fromWei("1", "ether"));
* console.log(soliditySha3Raw({ type: "string", value: "helloworld" }))
*
*
* ```
* @module Utils
*/
Expand Down Expand Up @@ -172,7 +172,6 @@ const getType = (arg: Sha3Input): [string, EncodingTypes] => {
if (Array.isArray(arg)) {
throw new Error('Autodetection of array types is not supported.');
}

let type;
let value;
// if type is given
Expand Down
2 changes: 1 addition & 1 deletion packages/web3-utils/src/json_rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ let requestIdSeed: number | undefined;
/**
* Optionally use to make the jsonrpc `id` start from a specific number.
* Without calling this function, the `id` will be filled with a Uuid.
* But after this being called with a number, the `id` will be a number staring from the provided `start` variable.
* But after this being called with a number, the `id` will be a number starting from the provided `start` variable.
* However, if `undefined` was passed to this function, the `id` will be a Uuid again.
* @param start - a number to start incrementing from.
* Or `undefined` to use a new Uuid (this is the default behavior)
Expand Down
39 changes: 19 additions & 20 deletions packages/web3-utils/src/promise_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { isNullish } from 'web3-validator';
export type Timer = ReturnType<typeof setInterval>;
export type Timeout = ReturnType<typeof setTimeout>;


/**
* An alternative to the node function `isPromise` that exists in `util/types` because it is not available on the browser.
* @param object - to check if it is a `Promise`
Expand Down Expand Up @@ -74,7 +73,6 @@ export async function waitWithTimeout<T>(
return result;
}


/**
* Repeatedly calls an async function with a given interval until the result of the function is defined (not undefined or null),
* or until a timeout is reached. It returns promise and intervalId.
Expand All @@ -85,25 +83,27 @@ export function pollTillDefinedAndReturnIntervalId<T>(
func: AsyncFunction<T>,
interval: number,
): [Promise<Exclude<T, undefined>>, Timer] {

let intervalId: Timer | undefined;
const polledRes = new Promise<Exclude<T, undefined>>((resolve, reject) => {
intervalId = setInterval(function intervalCallbackFunc(){
(async () => {
try {
const res = await waitWithTimeout(func, interval);

if (!isNullish(res)) {
intervalId = setInterval(
(function intervalCallbackFunc() {
(async () => {
try {
const res = await waitWithTimeout(func, interval);

if (!isNullish(res)) {
clearInterval(intervalId);
resolve(res as unknown as Exclude<T, undefined>);
}
} catch (error) {
clearInterval(intervalId);
resolve(res as unknown as Exclude<T, undefined>);
reject(error);
}
} catch (error) {
clearInterval(intervalId);
reject(error);
}
})() as unknown;
return intervalCallbackFunc;}() // this will immediate invoke first call
, interval);
})() as unknown;
return intervalCallbackFunc;
})(), // this will immediate invoke first call
interval,
);
});

return [polledRes as unknown as Promise<Exclude<T, undefined>>, intervalId!];
Expand All @@ -113,7 +113,7 @@ export function pollTillDefinedAndReturnIntervalId<T>(
* Repeatedly calls an async function with a given interval until the result of the function is defined (not undefined or null),
* or until a timeout is reached.
* pollTillDefinedAndReturnIntervalId() function should be used instead of pollTillDefined if you need IntervalId in result.
* This function will be deprecated in next major release so use pollTillDefinedAndReturnIntervalId().
* This function will be deprecated in next major release so use pollTillDefinedAndReturnIntervalId().
* @param func - The function to call.
* @param interval - The interval in milliseconds.
*/
Expand Down Expand Up @@ -146,7 +146,7 @@ export function rejectIfTimeout(timeout: number, error: Error): [Timer, Promise<
/**
* Sets an interval that repeatedly executes the given cond function with the specified interval between each call.
* If the condition is met, the interval is cleared and a Promise that rejects with the returned value is returned.
* @param cond - The function/confition to call.
* @param cond - The function/condition to call.
* @param interval - The interval in milliseconds.
* @returns - an array with the interval ID and the Promise.
*/
Expand All @@ -168,4 +168,3 @@ export function rejectIfConditionAtInterval<T>(
});
return [intervalId!, rejectIfCondition];
}

30 changes: 16 additions & 14 deletions packages/web3-utils/src/socket_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,13 +187,13 @@ export abstract class SocketProvider<
protected _validateProviderPath(path: string): boolean {
return !!path;
}

/**
*
* @returns the pendingRequestQueue size
*/
// eslint-disable-next-line class-methods-use-this
public getPendingRequestQueueSize() {
public getPendingRequestQueueSize() {
return this._pendingRequestsQueue.size;
}

Expand Down Expand Up @@ -350,32 +350,34 @@ export abstract class SocketProvider<

/**
* Safely disconnects the socket, async and waits for request size to be 0 before disconnecting
* @param forceDisconnect - If true, will clear queue after 5 attempts of waiting for both pending and sent queue to be 0
* @param forceDisconnect - If true, will clear queue after 5 attempts of waiting for both pending and sent queue to be 0
* @param ms - Determines the ms of setInterval
* @param code - The code to be sent to the server
* @param data - The data to be sent to the server
*/
public async safeDisconnect(code?: number, data?: string, forceDisconnect = false,ms = 1000) {
public async safeDisconnect(code?: number, data?: string, forceDisconnect = false, ms = 1000) {
let retryAttempt = 0;
const checkQueue = async () =>
const checkQueue = async () =>
new Promise(resolve => {
const interval = setInterval(() => {
if (forceDisconnect && retryAttempt === 5) {
if (forceDisconnect && retryAttempt >= 5) {
this.clearQueues();
}
if (this.getPendingRequestQueueSize() === 0 && this.getSentRequestsQueueSize() === 0) {
if (
this.getPendingRequestQueueSize() === 0 &&
this.getSentRequestsQueueSize() === 0
) {
clearInterval(interval);
resolve(true);
}
retryAttempt+=1;
}, ms)
})
retryAttempt += 1;
}, ms);
});

await checkQueue();
this.disconnect(code, data);
}


/**
* Removes all listeners for the specified event type.
* @param type - The event type to remove the listeners for
Expand Down Expand Up @@ -512,7 +514,7 @@ export abstract class SocketProvider<
if (isNullish(responses) || responses.length === 0) {
return;
}

for (const response of responses) {
if (
jsonRpc.isResponseWithNotification(response as JsonRpcNotification) &&
Expand Down Expand Up @@ -544,7 +546,7 @@ export abstract class SocketProvider<
this._sentRequestsQueue.delete(requestId);
}
}

public clearQueues(event?: ConnectionEvent) {
this._clearQueues(event);
}
Expand Down
Loading

1 comment on commit 8f62749

@github-actions
Copy link

Choose a reason for hiding this comment

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

Benchmark

Benchmark suite Current: 8f62749 Previous: 51d216f Ratio
processingTx 8813 ops/sec (±4.63%) 9224 ops/sec (±3.84%) 1.05
processingContractDeploy 35202 ops/sec (±8.08%) 41118 ops/sec (±7.75%) 1.17
processingContractMethodSend 17884 ops/sec (±5.35%) 20556 ops/sec (±6.13%) 1.15
processingContractMethodCall 34262 ops/sec (±7.43%) 41085 ops/sec (±4.96%) 1.20
abiEncode 38844 ops/sec (±8.37%) 46586 ops/sec (±6.55%) 1.20
abiDecode 26526 ops/sec (±8.50%) 31710 ops/sec (±6.85%) 1.20
sign 1546 ops/sec (±0.88%) 1608 ops/sec (±0.76%) 1.04
verify 353 ops/sec (±0.78%) 374 ops/sec (±2.89%) 1.06

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.