From 7a6e49200949325f69a05b3bf465ce64eb41bb8d Mon Sep 17 00:00:00 2001 From: Muhammad Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:09:32 +0200 Subject: [PATCH 1/5] Handle common cases for smart contract errors according to EIP 838 (#7155) * handle smart contracts errors `Error(string)` and `Panic(uint256)` * add tests * update CHANGELOG.md --- packages/web3-eth-abi/CHANGELOG.md | 5 ++- .../src/decode_contract_error_data.ts | 30 ++++++++++++++++ packages/web3-eth-abi/test/fixtures/data.ts | 34 +++++++++++++++++++ .../test/unit/decodeContractErrorData.test.ts | 8 ++++- 4 files changed, 75 insertions(+), 2 deletions(-) diff --git a/packages/web3-eth-abi/CHANGELOG.md b/packages/web3-eth-abi/CHANGELOG.md index 6d04047ddef..1bb5910ba18 100644 --- a/packages/web3-eth-abi/CHANGELOG.md +++ b/packages/web3-eth-abi/CHANGELOG.md @@ -182,4 +182,7 @@ Documentation: - fix encodedata in EIP-712 (#7095) -## [Unreleased] \ No newline at end of file +## [Unreleased] + +### Added +- Handle common cases for smart contract errors according to EIP 838: `0x4e487b71` which is the ‘selector’ for `Panic(uint256)` and `0x08c379a0` is the ‘selector’ of `Error(string)`. (7155) diff --git a/packages/web3-eth-abi/src/decode_contract_error_data.ts b/packages/web3-eth-abi/src/decode_contract_error_data.ts index 98eb02d4fd8..11566b79645 100644 --- a/packages/web3-eth-abi/src/decode_contract_error_data.ts +++ b/packages/web3-eth-abi/src/decode_contract_error_data.ts @@ -39,6 +39,36 @@ export const decodeContractErrorData = ( errorSignature = jsonInterfaceMethodToString(errorAbi); // decode abi.inputs according to EIP-838 errorArgs = decodeParameters([...errorAbi.inputs], error.data.substring(10)); + } else if (error.data.startsWith('0x08c379a0')) { + // If ABI was not provided, check for the 2 famous errors: 'Error(string)' or 'Panic(uint256)' + + errorName = 'Error'; + errorSignature = 'Error(string)'; + // decode abi.inputs according to EIP-838 + errorArgs = decodeParameters( + [ + { + name: 'message', + type: 'string', + }, + ], + error.data.substring(10), + ); + } else if (error.data.startsWith('0x4e487b71')) { + errorName = 'Panic'; + errorSignature = 'Panic(uint256)'; + // decode abi.inputs according to EIP-838 + errorArgs = decodeParameters( + [ + { + name: 'code', + type: 'uint256', + }, + ], + error.data.substring(10), + ); + } else { + console.error('No matching error abi found for error data', error.data); } } catch (err) { console.error(err); diff --git a/packages/web3-eth-abi/test/fixtures/data.ts b/packages/web3-eth-abi/test/fixtures/data.ts index 0fe90638f95..f8990a22e86 100644 --- a/packages/web3-eth-abi/test/fixtures/data.ts +++ b/packages/web3-eth-abi/test/fixtures/data.ts @@ -974,6 +974,40 @@ export const validDecodeContractErrorData: { input: any[]; output: any; }[] = [ + { + input: [ + [], + { + code: 12, + message: 'message', + data: '0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000155468697320697320612063616c6c207265766572740000000000000000000000', + }, + ], + output: { + errorName: 'Error', + errorSignature: 'Error(string)', + errorArgs: { + message: 'This is a call revert', + }, + }, + }, + { + input: [ + [], + { + code: 12, + message: 'message', + data: '0x4e487b71000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000', + }, + ], + output: { + errorName: 'Panic', + errorSignature: 'Panic(uint256)', + errorArgs: { + code: 42, + }, + }, + }, { input: [ [ diff --git a/packages/web3-eth-abi/test/unit/decodeContractErrorData.test.ts b/packages/web3-eth-abi/test/unit/decodeContractErrorData.test.ts index 3f6a624ef22..9e99add02f3 100644 --- a/packages/web3-eth-abi/test/unit/decodeContractErrorData.test.ts +++ b/packages/web3-eth-abi/test/unit/decodeContractErrorData.test.ts @@ -32,7 +32,13 @@ describe('decodeContractErrorData', () => { expect(err.errorName).toEqual(output.errorName); expect(err.errorSignature).toEqual(output.errorSignature); expect(err.errorArgs?.message).toEqual(output.errorArgs?.message); - expect(Number(err.errorArgs?.code)).toEqual(output.errorArgs?.code); + + // This ensures they are equal if one was provided + // It also skips if both are not provided + if (err.errorArgs?.code || output.errorArgs?.code) { + // eslint-disable-next-line jest/no-conditional-expect + expect(Number(err.errorArgs?.code)).toEqual(output.errorArgs?.code); + } expect(err.cause?.code).toEqual(output.cause?.code); }, ); From 75df2677caa955ed3d62eac18e3c6a1fad2103b7 Mon Sep 17 00:00:00 2001 From: Dan Forbes Date: Mon, 9 Sep 2024 06:39:29 -0700 Subject: [PATCH 2/5] feat(docs): upgrade-bn (#7232) Add a section in "Upgrade from 1.x" for `BN` vs `BigInt` Closes #6705 --- .../1.x/web3_utils_migration_guide.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/docs/guides/web3_upgrade_guide/1.x/web3_utils_migration_guide.md b/docs/docs/guides/web3_upgrade_guide/1.x/web3_utils_migration_guide.md index 0b977890e6e..5282b720823 100644 --- a/docs/docs/guides/web3_upgrade_guide/1.x/web3_utils_migration_guide.md +++ b/docs/docs/guides/web3_upgrade_guide/1.x/web3_utils_migration_guide.md @@ -17,6 +17,18 @@ import web3Utils from 'web3-utils'; import * as web3Utils from 'web3-utils'; ``` +## No `BN` property + +The `web3-utils` package no longer includes a `BN` property for using the [the `bn.js` package](https://github.com/indutny/bn.js/) for working with (big) numbers. In 4.x, [the native JavaScript `BigInt` type](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) is used. + +```ts +// 1.x +new Web3.utils.BN(1); + +// 4.x +BigInt(4); +``` + ## Unit conversion functions The `toWei` does not have an optional second parameter. You have to pass the source unit explicitly. From 2f2424487a6ba2865b5a381f22f83500a3133cda Mon Sep 17 00:00:00 2001 From: Dan Forbes Date: Mon, 9 Sep 2024 06:53:01 -0700 Subject: [PATCH 3/5] feat(requestEIP6963Providers): return-type (#7239) * feat(requestEIP6963Providers): return-type Add return type to requestEIP6963Providers function signature. Closes #7238 * Update Changelog --- CHANGELOG.md | 4 ++++ packages/web3/src/web3_eip6963.ts | 8 +++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f2e5ea09b0..48117f4e47c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2697,4 +2697,8 @@ If there are any bugs, improvements, optimizations or any new feature proposal f - Revert `TransactionFactory.registerTransactionType` if there is a version mistatch between `web3-eth` and `web3-eth-accounts` and fix nextjs problem. (#7216) +#### web3 + +- `Web3.providers` namespace exports `type EIP6963ProviderResponse = Map`. Return type for the static `Web3.requestEIP6963Providers` is now `Promise`. (#7239) + ## [Unreleased] diff --git a/packages/web3/src/web3_eip6963.ts b/packages/web3/src/web3_eip6963.ts index 787502a4125..d2169191b5c 100644 --- a/packages/web3/src/web3_eip6963.ts +++ b/packages/web3/src/web3_eip6963.ts @@ -35,6 +35,8 @@ export interface EIP6963ProviderDetail { provider: EIP1193Provider; } +export type EIP6963ProviderResponse = Map; + export interface EIP6963AnnounceProviderEvent extends CustomEvent { type: Eip6963EventName.eip6963announceProvider; detail: EIP6963ProviderDetail; @@ -44,15 +46,15 @@ export interface EIP6963RequestProviderEvent extends Event { type: Eip6963EventName.eip6963requestProvider; } -export const eip6963ProvidersMap: Map = new Map(); +export const eip6963ProvidersMap: EIP6963ProviderResponse = new Map(); export const web3ProvidersMapUpdated = "web3:providersMapUpdated"; export interface EIP6963ProvidersMapUpdateEvent extends CustomEvent { type: string; - detail: Map; + detail: EIP6963ProviderResponse; } -export const requestEIP6963Providers = async () => +export const requestEIP6963Providers = async (): Promise => new Promise((resolve, reject) => { if (typeof window === 'undefined') { reject(new Error("window object not available, EIP-6963 is intended to be used within a browser")); From 973ee809d48a96441cbfd502f12915dcc0cb5dad Mon Sep 17 00:00:00 2001 From: Dan Forbes Date: Mon, 9 Sep 2024 07:20:04 -0700 Subject: [PATCH 4/5] feat(docs): extend (#7233) Documentation for `Web3Context.extend` method Closes #6768 --- docs/docs/guides/advanced/custom_RPC.md | 2 +- docs/docs/guides/advanced/extend.md | 38 +++++++++++++++++++++++ docs/docs/guides/advanced/tree_shaking.md | 2 +- 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 docs/docs/guides/advanced/extend.md diff --git a/docs/docs/guides/advanced/custom_RPC.md b/docs/docs/guides/advanced/custom_RPC.md index 28ccfeeea63..6874062c814 100644 --- a/docs/docs/guides/advanced/custom_RPC.md +++ b/docs/docs/guides/advanced/custom_RPC.md @@ -13,7 +13,7 @@ import TabItem from '@theme/TabItem'; Web3.js is a popular library for interacting with the Ethereum blockchain. It provides a set of APIs to interact with Ethereum nodes via JSON-RPC calls. For adding new JSON-RPC function calls to the library, you can do so using the plugin feature in web3.js 4.x. This allows you to extend the functionality of Web3.js and add support for new JSON-RPC methods. :::caution -In Web3.js 1.x, `web3.extend()` function could be used to add new JSON-RPC methods. `web3.extend()` is also available in Web3 v4.0.4+ with some breaking changes. However it is recommended to use Web3 Plugin feature for extending web3 functionality if you are developing new feature. +In Web3.js 1.x, `web3.extend()` function could be used to add new JSON-RPC methods. `web3.extend()` is also available in Web3 v4.0.4+ with some breaking changes. However it is recommended to use Web3 Plugin feature for extending web3 functionality if you are developing new feature. Read the ["Extending Web3.js"](/guides/advanced/extend) guide to learn more about the legacy `web3.extend()` method. ::: Following tutorial will guide you through the process of creating a custom plugin to extend the functionality of web3.js 4.x and add support for new RPC methods. diff --git a/docs/docs/guides/advanced/extend.md b/docs/docs/guides/advanced/extend.md new file mode 100644 index 00000000000..4b9d011db73 --- /dev/null +++ b/docs/docs/guides/advanced/extend.md @@ -0,0 +1,38 @@ +--- +sidebar_position: 2 +sidebar_label: Extending Web3.js +--- + +# Extending Web3.js + +Although the preferred way to add custom RPC methods to Web3.js is to [create a plugin](/guides/advanced/custom_RPC), Web3.js also exposes a [legacy `extend` method](/api/web3/class/Web3Context#extend) that can be used for the same purpose. Keep reading to learn how to use the legacy `extend` method to add a custom RPC method to an instance of Web3.js. + +## `ExtensionObject` + +The legacy `extend` method accepts a single parameter that should implement the [`ExtensionObject` interface](/api/web3/namespace/core/#ExtensionObject). An `ExtensionObject` consists of two properties: an optional `string` property named `property` and a required property named `methods` that is an array of objects that implement the [`Method` interface](/api/web3/namespace/core/#Method). The `Method` interface specifies two properties, both of which are required and both of which are strings: `name` and `call`. The `property` property of an `Extension` object can be used to specify the name of the Web3.js member property that will expose the custom RPC methods (if this parameter is omitted, the new RPC methods will be exposed by the "root" Web3.js object). Each element of the `methods` array from the `ExtensionObject` specifies a new custom RPC method - the `name` property is the name of the new function that will be used to call the custom RPC method and the `call` property is the actual RPC endpoint that should be invoked. The new function will accept parameters that will be passed along when invoking the RPC endpoint. + +Here is a complete example of using the legacy `extend` method: + +```js +import { Web3 } from "web3"; + +const web3 = new Web3("https://eth.llamarpc.com"); + +async function main() { + web3.extend({ + property: "BlockReceipts", + methods: [ + { + name: "getBlockReceipts", + // https://www.quicknode.com/docs/ethereum/eth_getBlockReceipts + call: "eth_getBlockReceipts", + }, + ], + }); + + const receipts = await web3.BlockReceipts.getBlockReceipts("latest"); + console.log(receipts); +} + +main(); +``` diff --git a/docs/docs/guides/advanced/tree_shaking.md b/docs/docs/guides/advanced/tree_shaking.md index 46f301e738c..43b4aa8b717 100644 --- a/docs/docs/guides/advanced/tree_shaking.md +++ b/docs/docs/guides/advanced/tree_shaking.md @@ -1,5 +1,5 @@ --- -sidebar_position: 2 +sidebar_position: 3 sidebar_label: Tree Shaking Guide --- From 27155eaf29f8b7a3b36e7958188076aa420c4c46 Mon Sep 17 00:00:00 2001 From: Dan Forbes Date: Mon, 9 Sep 2024 07:20:31 -0700 Subject: [PATCH 5/5] fix(onNewProviderDiscovered): callback-parameter (#7242) * fix(onNewProviderDiscovered): callback-parameter Callback for `onNewProviderDiscovered`should have `EIP6963ProvidersMapUpdateEvent` parameter Closes #7241 * Update Changelog * Fix Changelog * Remove Unnecessary Whitespace --- CHANGELOG.md | 1 + packages/web3/src/web3_eip6963.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48117f4e47c..908ae117acc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2700,5 +2700,6 @@ If there are any bugs, improvements, optimizations or any new feature proposal f #### web3 - `Web3.providers` namespace exports `type EIP6963ProviderResponse = Map`. Return type for the static `Web3.requestEIP6963Providers` is now `Promise`. (#7239) +- The callback function provided to the static `Web3.onNewProviderDiscovered` function expects a parameter of type `EIP6963ProvidersMapUpdateEvent` as opposed to `EIP6963AnnounceProviderEvent`. (#7242) ## [Unreleased] diff --git a/packages/web3/src/web3_eip6963.ts b/packages/web3/src/web3_eip6963.ts index d2169191b5c..5693dea4776 100644 --- a/packages/web3/src/web3_eip6963.ts +++ b/packages/web3/src/web3_eip6963.ts @@ -84,7 +84,7 @@ export const requestEIP6963Providers = async (): Promise void) => { +export const onNewProviderDiscovered = (callback: (providerEvent: EIP6963ProvidersMapUpdateEvent) => void) => { if (typeof window === 'undefined') { throw new Error("window object not available, EIP-6963 is intended to be used within a browser"); }