diff --git a/docs/docs/guides/smart_contracts/tips_and_tricks.md b/docs/docs/guides/smart_contracts/tips_and_tricks.md new file mode 100644 index 00000000000..bcc98a2f7d4 --- /dev/null +++ b/docs/docs/guides/smart_contracts/tips_and_tricks.md @@ -0,0 +1,132 @@ +--- +sidebar_position: 4 +sidebar_label: 'Tips and Tricks' +--- + +# Smart Contracts Tips and Tricks + +:::tip +📝 This article offers insights into **Smart Contracts** with helpful tips and tricks. If you have suggestions or questions, feel free to open an issue. We also welcome contributions through PRs. +::: + +## Calling Smart Contracts Methods with Parameter Overloading + +### Overview of Function Overloading + +Parameter overloading enables smart contracts to define multiple functions bearing the same name, differentiated only by their parameters. While this enhances legibility and organization, it complicates calls due to the need for precise method identification. + +### Example Code + +Below is a demonstration of invoking two versions of the `funcWithParamsOverloading` function in a smart contract, differentiated by their parameter types: `uint256` versus `address`. + +The Solidity code: + +```solidity +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.8.20 <0.9.0; + + +contract TestOverlading { + + function funcWithParamsOverloading(uint256 userId) public pure returns (string memory) { + return "called for the parameter with the type 'uint256'"; + } + + function funcWithParamsOverloading(address userAddress) public pure returns (string memory) { + return "called for the parameter with the type 'address'"; + } + +} +``` + +The TypeScript: +```typescript +import { Web3 } from 'web3'; + +const ABI = [ + { + inputs: [ + { + internalType: 'uint256', + name: 'userId', + type: 'uint256', + }, + ], + name: 'funcWithParamsOverloading', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'userAddress', + type: 'address', + }, + ], + name: 'funcWithParamsOverloading', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'pure', + type: 'function', + } +] as const; + +(async function () { + const web3 = new Web3(provider); + + const contract = new web3.eth.Contract(ABI, contractAddress); + + // Calling the function that accepts an address + const res1 = await contract.methods['funcWithParamsOverloading(address)'](userAddress).call(); + + // Calling the function that accepts a uint256 + const res2 = await contract.methods['funcWithParamsOverloading(uint256)'](userId).call(); + +})(); +``` + +### Handling Ambiguity in Overloaded Methods + +Omitting the explicit specification for overloading, as highlighted earlier, results in the default selection of the first method match in the ABI, along with a warning. Future web3.js releases will address this with an error to enforce stricter specification. + +#### Demonstrating the Importance of Specificity + +To underline specificity's value, here's a scenario of invoking an overloaded function without specifying the parameter type: + +```typescript +// Assuming a contract with overloaded methods: funcWithParamsOverloading(uint256) and funcWithParamsOverloading(string)... + +(async function () { + try { + // A call without specifying overloading results in a warning and choosing the first matched overload + const ambiguousResult = await contract.methods.funcWithParamsOverloading('0x0123').call(); +})(); +``` + +This generates a console warning on the ambiguity and auto-selects the first matching function overload found in the ABI: + +``` +Multiple methods found that are compatible with the given inputs. Found 2 compatible methods: ["funcWithParamsOverloading(uint256) (signature: 0x...)", "funcWithParamsOverloading(string) (signature: 0x...)"] The first one will be used: funcWithParamsOverloading(uint256) +``` + +### Future Considerations + +Future releases of web3.js, specifically version 5.x, will replace the warning with an error whenever multiple methods match a call without explicit overloading. This aims to foster greater precision in method invocation. + +### Key Takeaway for function overlading: Method Specification + +When working with overloaded smart contract methods, it's imperative to specify the intended method by appending its parameter types within parentheses, such as `funcWithParamsOverloading(address)` versus `funcWithParamsOverloading(uint256)`. This ensures the accuracy of method invocation, leading to more efficient and clearer contract interactions. \ No newline at end of file diff --git a/packages/web3-eth-contract/CHANGELOG.md b/packages/web3-eth-contract/CHANGELOG.md index 9aae3aa558e..172670b179d 100644 --- a/packages/web3-eth-contract/CHANGELOG.md +++ b/packages/web3-eth-contract/CHANGELOG.md @@ -375,3 +375,7 @@ Documentation: ### Fixed - Fix an issue with smart contract function overloading (#6922) + +### Added + +- Add a console warning in case of an ambiguous call to a solidity method with parameter overloading (#6942) diff --git a/packages/web3-eth-contract/src/contract.ts b/packages/web3-eth-contract/src/contract.ts index d7f89f81f1e..8f07beb5095 100644 --- a/packages/web3-eth-contract/src/contract.ts +++ b/packages/web3-eth-contract/src/contract.ts @@ -988,6 +988,7 @@ export class Contract if (isAbiFunctionFragment(abi)) { const methodName = jsonInterfaceMethodToString(abi); const methodSignature = encodeFunctionSignature(methodName); + abi.methodNameWithInputs = methodName; abi.signature = methodSignature; // make constant and payable backwards compatible @@ -1104,9 +1105,24 @@ export class Contract } if (applicableMethodAbi.length === 1) { [methodAbi] = applicableMethodAbi; // take the first item that is the only item in the array - } else { + } else if (applicableMethodAbi.length > 1) { [methodAbi] = applicableMethodAbi; // take the first item in the array - // TODO: Should throw a new error with the list of methods found. + console.warn( + `Multiple methods found that is compatible with the given inputs.\n\tFound ${ + applicableMethodAbi.length + } compatible methods: ${JSON.stringify( + applicableMethodAbi.map( + m => + `${(m as { methodNameWithInputs: string }).methodNameWithInputs} (signature: ${ + (m as { signature: string }).signature + })`, + ), + )} \n\tThe first one will be used: ${ + (methodAbi as { methodNameWithInputs: string }).methodNameWithInputs + }`, + ); + // TODO: 5.x Should throw a new error with the list of methods found. + // Related issue: https://github.com/web3/web3.js/issues/6923 // This is in order to provide an error message when there is more than one method found that fits the inputs. // To do that, replace the pervious line of code with something like the following line: // throw new Web3ValidatorError({ message: 'Multiple methods found', ... list of applicable methods })); diff --git a/packages/web3-eth-contract/test/unit/function_overloading.test.ts b/packages/web3-eth-contract/test/unit/function_overloading.test.ts index af9d0264926..6533aea84e2 100644 --- a/packages/web3-eth-contract/test/unit/function_overloading.test.ts +++ b/packages/web3-eth-contract/test/unit/function_overloading.test.ts @@ -212,6 +212,21 @@ const ABI = [ describe('test Params Overloading', () => { const contract: Contract = new Contract(ABI); + describe('calling a function with multiple compatible inputs without specifying', () => { + // TODO: 5.x Should throw a new error with the list of methods found. + // Related issue: https://github.com/web3/web3.js/issues/6923 + it('should call the first one when the signature is not passed but also show a warning', async () => { + const originalWarn = console.warn; + console.warn = function (message: string) { + expect(message).toMatch('Multiple methods found that is compatible with the given inputs.'); + }; + const abi = contract.methods['funcWithParamsOverloading_pure']( + '0x12eca7a3959a42973ef4452e44948650be8b8610', + ).encodeABI(); + console.warn = originalWarn; + expect(abi.substring(0, 10)).toBe('0x125f6ec5'); + }); + }); describe('funcWithParamsOverloading_pure', () => { it('uint256', async () => { diff --git a/packages/web3-types/src/eth_abi_types.ts b/packages/web3-types/src/eth_abi_types.ts index 84bbdffef8e..e46b79e8498 100644 --- a/packages/web3-types/src/eth_abi_types.ts +++ b/packages/web3-types/src/eth_abi_types.ts @@ -112,6 +112,7 @@ export type AbiFunctionFragment = AbiBaseFragment & { readonly payable?: boolean; // stateMutability == 'payable' readonly signature?: string; + readonly methodNameWithInputs?: string; // like: funcWithParamsOverloading(uint8) }; export type AbiFallbackFragment = AbiBaseFragment & {