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

Use BigInt when decoding functions' and events' parameters returned from Smart Contracts calls and events #5435

Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -733,3 +733,16 @@ should use 4.0.1-alpha.0 for testing.
- Dependency tree cannot be resolved by Yarn due to old deprecated packages picked by yarn - fixed (#5382)

## [Unreleased]

Muhammad-Altabba marked this conversation as resolved.
Show resolved Hide resolved
#### web3-eth-contract

- According to the latest change in `web3-eth-abi`, the decoded values of the large numbers, returned from function calls or events, are now available as `BigInt`.

#### web3-error

- Add optional `innerError` property to the abstract class `Web3Error`.

#### web3-eth-abi

- Return `BigInt` instead of `string` when decoding function parameters for large numbers, such as `uint256`.
- If an error happens when decoding a value, preserve that exception at `innerError` inside the `AbiError`.
4 changes: 3 additions & 1 deletion packages/web3-errors/src/web3_error_base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ export abstract class Web3Error extends Error implements ErrorInterface {
public readonly name: string;
public abstract readonly code: number;
public stack: string | undefined;
public innerError: Error | undefined;
Muhammad-Altabba marked this conversation as resolved.
Show resolved Hide resolved

public constructor(msg?: string) {
public constructor(msg?: string, innerError?: Error) {
super(msg);
this.innerError = innerError;
this.name = this.constructor.name;

if (typeof Error.captureStackTrace === 'function') {
Expand Down
2 changes: 1 addition & 1 deletion packages/web3-eth-abi/src/api/parameters_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export const encodeParameters = (abi: ReadonlyArray<AbiInput>, params: unknown[]
modifiedParams,
);
} catch (err) {
throw new AbiError(`Parameter encoding error: ${(err as Error).message}`);
throw new AbiError(`Parameter encoding error`, err as Error);
}
};

Expand Down
15 changes: 5 additions & 10 deletions packages/web3-eth-abi/src/ethers_abi_coder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,11 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.

import { AbiCoder } from '@ethersproject/abi';

const ethersAbiCoder = new AbiCoder((type, value) => {
if (
/^u?int/.exec(type) &&
!Array.isArray(value) &&
// eslint-disable-next-line @typescript-eslint/ban-types
(!(!!value && typeof value === 'object') || (value as Function).constructor.name !== 'BN')
) {
// Because of tye type def from @ethersproject/abi
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
return value.toString();
const ethersAbiCoder = new AbiCoder((_, value) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
if (['BigNumber', 'BN'].includes(value?.constructor?.name)) {
// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-unsafe-argument
return BigInt(value);
}

// Because of tye type def from @ethersproject/abi
Expand Down
9 changes: 9 additions & 0 deletions packages/web3-eth-abi/test/fixtures/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.

import { encodeParameters, decodeParameters } from '../../src/api/parameters_api';

// Because Jest does not support BigInt (https://github.com/facebook/jest/issues/12827)
// The BigInt values in this file is in a string format.
// And the following override is to convert BigInt to a string inside the Unit Tests that uses this file,
// i.e when serialization is needed there (because the values in this file is in a string format).
(BigInt.prototype as any).toJSON = function () {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call
return this.toString();
};

export const jsonInterfaceValidData: [any, string][] = [
[
{
Expand Down
3 changes: 2 additions & 1 deletion packages/web3-eth-abi/test/unit/api/logs_api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ describe('logs_api', () => {
it.each(validDecodeLogsData)(
'should pass for valid values: %j',
({ input: { abi, data, topics }, output }) => {
expect(decodeLog(abi, data, topics)).toEqual(output);
const expected = decodeLog(abi, data, topics);
expect(JSON.parse(JSON.stringify(expected))).toEqual(output);
},
);
});
Expand Down
14 changes: 8 additions & 6 deletions packages/web3-eth-abi/test/unit/api/parameters_api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ describe('parameters_api', () => {
it.each(validEncodeParametersData)(
'%#: should pass for valid values: %j',
({ input: [abi, params], output }) => {
expect(encodeParameters(abi, params)).toEqual(output);
const expected = encodeParameters(abi, params);
expect(JSON.parse(JSON.stringify(expected))).toEqual(output);
},
);
});

describe('invalid data', () => {
it.each(inValidEncodeParametersData)(
'%#: should pass for valid values: %j',
'%#: should not pass for invalid values: %j',
({ input: [abi, params], output }) => {
expect(() => encodeParameters(abi, params)).toThrow(output);
},
Expand All @@ -52,7 +53,8 @@ describe('parameters_api', () => {
it.each(validEncodeDoesNotMutateData)(
'%#: should pass for valid values: %j',
({ input: [abi, params], output, expectedInput }) => {
expect(encodeParameters(abi, params)).toEqual(output);
const expected = encodeParameters(abi, params);
expect(JSON.parse(JSON.stringify(expected))).toEqual(output);
// check that params has not been mutated
expect(JSON.parse(JSON.stringify(params))).toEqual(
JSON.parse(JSON.stringify(expectedInput)),
Expand Down Expand Up @@ -90,7 +92,7 @@ describe('parameters_api', () => {

describe('invalid data', () => {
it.each(inValidDecodeParametersData)(
'%#: should pass for valid values: %j',
'%#: should not pass for invalid values: %j',
({ input: [abi, bytes], output }) => {
expect(() => decodeParameters(abi, bytes)).toThrow(output);
},
Expand All @@ -105,9 +107,9 @@ describe('parameters_api', () => {
({ input: [abi, params], output, outputResult }) => {
const rwAbi = abi as AbiInput[];
const encodedBytes = encodeParameters(abi, params);
expect(encodedBytes).toEqual(output);
expect(JSON.parse(JSON.stringify(encodedBytes))).toEqual(output);
const decodedBytes = decodeParameters(rwAbi, encodedBytes);
expect(decodedBytes).toEqual(outputResult);
expect(JSON.parse(JSON.stringify(decodedBytes))).toEqual(outputResult);
},
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ import {
} from '../fixtures/system_test_utils';
import { processAsync, toUpperCaseHex } from '../shared_fixtures/utils';

const initialSupply = '5000000000';
const initialSupply = BigInt('5000000000');

describe('contract', () => {
describe('erc20', () => {
let contract: Contract<typeof ERC20TokenAbi>;
let deployOptions: Record<string, unknown>;
let sendOptions: Record<string, unknown>;

beforeAll(async () => {
beforeAll(() => {
contract = new Contract(ERC20TokenAbi, undefined, {
provider: getSystemTestProvider(),
});
Expand Down Expand Up @@ -69,7 +69,7 @@ describe('contract', () => {
});

it('should return the decimals', async () => {
expect(await contractDeployed.methods.decimals().call()).toBe('18');
expect(await contractDeployed.methods.decimals().call()).toBe(BigInt(18));
});

it('should return total supply', async () => {
Expand All @@ -78,10 +78,11 @@ describe('contract', () => {

it('should transfer tokens', async () => {
const acc2 = await createTempAccount();
await contractDeployed.methods.transfer(acc2.address, '10').send(sendOptions);
const value = BigInt(10);
await contractDeployed.methods.transfer(acc2.address, value).send(sendOptions);

expect(await contractDeployed.methods.balanceOf(acc2.address).call()).toBe(
'10',
value,
);
});
});
Expand All @@ -107,7 +108,7 @@ describe('contract', () => {
).resolves.toEqual({
from: toUpperCaseHex(sendOptions.from as string),
to: toUpperCaseHex(acc2.address),
value: '100',
value: BigInt(100),
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ describe('contract', () => {
).resolves.toEqual({
from: '0x0000000000000000000000000000000000000000',
to: toUpperCaseHex(acc2.address),
tokenId: '0',
tokenId: BigInt(0),
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('contract', () => {
let deployOptions: Record<string, unknown>;
let sendOptions: Record<string, unknown>;

beforeAll(async () => {
beforeAll(() => {
contract = new Contract(BasicAbi, undefined, {
provider: getSystemTestProvider(),
});
Expand Down Expand Up @@ -75,29 +75,25 @@ describe('contract', () => {
itIf(isWs)(
'should trigger the "contract.events.<eventName>" for indexed parameters',
async () => {
// eslint-disable-next-line jest/no-standalone-expect
return expect(
processAsync(async resolve => {
const event = contractDeployed.events.MultiValueIndexedEvent({
filter: { val: 100 },
});
const res = await processAsync(async resolve => {
const event = contractDeployed.events.MultiValueIndexedEvent({
filter: { val: 100 },
});

event.on('data', resolve);
event.on('data', resolve);

// trigger event
await contractDeployed.methods
.firesMultiValueIndexedEvent('value', 12, true)
.send(sendOptions);
await contractDeployed.methods
.firesMultiValueIndexedEvent('value', 100, true)
.send(sendOptions);
}),
).resolves.toEqual(
expect.objectContaining({
event: 'MultiValueIndexedEvent',
returnValues: expect.objectContaining({ val: '100' }),
}),
);
// trigger event
await contractDeployed.methods
.firesMultiValueIndexedEvent('value', 12, true)
.send(sendOptions);
await contractDeployed.methods
.firesMultiValueIndexedEvent('value', 100, true)
.send(sendOptions);
});
// eslint-disable-next-line jest/no-standalone-expect
expect((res as any)?.event).toBe('MultiValueIndexedEvent');
// eslint-disable-next-line jest/no-standalone-expect
expect((res as any)?.returnValues.val).toBe(BigInt(100));
},
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('contract', () => {
const result = await contractDeployed.methods.getValues().call();

expect(result).toEqual({
'0': '10',
'0': BigInt(10),
'1': 'string init value',
'2': false,
__length__: 3,
Expand Down
6 changes: 3 additions & 3 deletions packages/web3-eth-ens/test/integration/ens.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,15 +212,15 @@ describe('ens', () => {
it('should get TTL', async () => {
const TTL = await ens.getTTL(web3jsName);

expect(TTL).toBe('0');
expect(TTL).toBe(BigInt(0));
});

it('should set TTL', async () => {
await ens.setTTL(web3jsName, ttl, sendOptions);

const ttlResult = await ens.getTTL(web3jsName);

expect(ttlResult).toBe(ttl.toString());
expect(ttlResult).toBe(BigInt(ttl));
});

it('should set subnode owner', async () => {
Expand All @@ -245,7 +245,7 @@ describe('ens', () => {

const owner = await ens.getOwner(fullDomain);

expect(ttlResult).toBe(ttl.toString());
expect(ttlResult).toBe(BigInt(ttl));
expect(owner).toBe(toChecksumAddress(accountOne));
});

Expand Down