diff --git a/contracts/v0.8.9/CustomError.sol b/contracts/v0.8.9/CustomError.sol new file mode 100644 index 000000000..4508b5a60 --- /dev/null +++ b/contracts/v0.8.9/CustomError.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.4; + +/// Insufficient balance for transfer. Needed `required` but only +/// `available` available. +/// @param available balance available. +/// @param required requested amount to transfer. +error InsufficientBalance(uint256 available, uint256 required); + +/// @notice example contract from https://blog.soliditylang.org/2021/04/21/custom-errors/ +contract CustomError { + mapping(address => uint) balance; + + function transfer(address to, uint256 amount) public { + if (amount > balance[msg.sender]) + // Error call using named parameters. Equivalent to + // revert InsufficientBalance(balance[msg.sender], amount); + revert InsufficientBalance({ + available: balance[msg.sender], + required: amount + }); + balance[msg.sender] -= amount; + balance[to] += amount; + } + // ... +} diff --git a/packages/hardhat-test/typechain-types/Counter.ts b/packages/hardhat-test/typechain-types/Counter.ts index e33aaaeb8..bc4dfbb79 100644 --- a/packages/hardhat-test/typechain-types/Counter.ts +++ b/packages/hardhat-test/typechain-types/Counter.ts @@ -49,6 +49,8 @@ export interface CounterInterface extends utils.Interface { }; getEvent(nameOrSignatureOrTopic: "CountedTo"): EventFragment; + + errors: {}; } export interface CountedToEventObject { diff --git a/packages/hardhat-test/typechain-types/Demo.ts b/packages/hardhat-test/typechain-types/Demo.ts index fb66a8f88..b73ae05ab 100644 --- a/packages/hardhat-test/typechain-types/Demo.ts +++ b/packages/hardhat-test/typechain-types/Demo.ts @@ -37,6 +37,8 @@ export interface DemoInterface extends utils.Interface { functions: {}; events: {}; + + errors: {}; } export interface Demo extends BaseContract { diff --git a/packages/hardhat-test/typechain-types/StructsInConstructor.ts b/packages/hardhat-test/typechain-types/StructsInConstructor.ts index 386dabbc2..ab9cc3df6 100644 --- a/packages/hardhat-test/typechain-types/StructsInConstructor.ts +++ b/packages/hardhat-test/typechain-types/StructsInConstructor.ts @@ -28,6 +28,8 @@ export interface StructsInConstructorInterface extends utils.Interface { functions: {}; events: {}; + + errors: {}; } export interface StructsInConstructor extends BaseContract { diff --git a/packages/hardhat/test/fixture-projects/hardhat-project/typechain-types/artifacts/contracts/EdgeCases.ts b/packages/hardhat/test/fixture-projects/hardhat-project/typechain-types/artifacts/contracts/EdgeCases.ts index 8eba81138..10a6895dc 100644 --- a/packages/hardhat/test/fixture-projects/hardhat-project/typechain-types/artifacts/contracts/EdgeCases.ts +++ b/packages/hardhat/test/fixture-projects/hardhat-project/typechain-types/artifacts/contracts/EdgeCases.ts @@ -15,6 +15,8 @@ export interface EdgeCasesInterface extends utils.Interface { functions: {}; events: {}; + + errors: {}; } export interface EdgeCases extends BaseContract { diff --git a/packages/hardhat/test/fixture-projects/hardhat-project/typechain-types/artifacts/contracts/TestContract.ts b/packages/hardhat/test/fixture-projects/hardhat-project/typechain-types/artifacts/contracts/TestContract.ts index db01ca653..2345e16d5 100644 --- a/packages/hardhat/test/fixture-projects/hardhat-project/typechain-types/artifacts/contracts/TestContract.ts +++ b/packages/hardhat/test/fixture-projects/hardhat-project/typechain-types/artifacts/contracts/TestContract.ts @@ -15,6 +15,8 @@ export interface TestContractInterface extends utils.Interface { functions: {}; events: {}; + + errors: {}; } export interface TestContract extends BaseContract { diff --git a/packages/hardhat/test/fixture-projects/hardhat-project/typechain-types/artifacts/contracts/TestContract1.ts b/packages/hardhat/test/fixture-projects/hardhat-project/typechain-types/artifacts/contracts/TestContract1.ts index dff563516..ec51c9aff 100644 --- a/packages/hardhat/test/fixture-projects/hardhat-project/typechain-types/artifacts/contracts/TestContract1.ts +++ b/packages/hardhat/test/fixture-projects/hardhat-project/typechain-types/artifacts/contracts/TestContract1.ts @@ -15,6 +15,8 @@ export interface TestContract1Interface extends utils.Interface { functions: {}; events: {}; + + errors: {}; } export interface TestContract1 extends BaseContract { diff --git a/packages/hardhat/test/fixture-projects/hardhat-project/typechain-types/artifacts/contracts/lib/SafeMath.ts b/packages/hardhat/test/fixture-projects/hardhat-project/typechain-types/artifacts/contracts/lib/SafeMath.ts index 279a9ccff..741845129 100644 --- a/packages/hardhat/test/fixture-projects/hardhat-project/typechain-types/artifacts/contracts/lib/SafeMath.ts +++ b/packages/hardhat/test/fixture-projects/hardhat-project/typechain-types/artifacts/contracts/lib/SafeMath.ts @@ -55,6 +55,8 @@ export interface SafeMathInterface extends utils.Interface { decodeFunctionResult(functionFragment: "sub", data: BytesLike): Result; events: {}; + + errors: {}; } export interface SafeMath extends BaseContract { diff --git a/packages/hardhat/test/fixture-projects/hardhat-project/typechain-types/externalArtifacts/ERC20.ts b/packages/hardhat/test/fixture-projects/hardhat-project/typechain-types/externalArtifacts/ERC20.ts index b4cdaf3c1..285ddd955 100644 --- a/packages/hardhat/test/fixture-projects/hardhat-project/typechain-types/externalArtifacts/ERC20.ts +++ b/packages/hardhat/test/fixture-projects/hardhat-project/typechain-types/externalArtifacts/ERC20.ts @@ -120,6 +120,8 @@ export interface ERC20Interface extends utils.Interface { getEvent(nameOrSignatureOrTopic: "Approval"): EventFragment; getEvent(nameOrSignatureOrTopic: "Transfer"): EventFragment; + + errors: {}; } export interface ApprovalEventObject { diff --git a/packages/target-ethers-v5-test/test/CustomError.test.ts b/packages/target-ethers-v5-test/test/CustomError.test.ts new file mode 100644 index 000000000..4e4ff4361 --- /dev/null +++ b/packages/target-ethers-v5-test/test/CustomError.test.ts @@ -0,0 +1,30 @@ +import type { CustomError } from '../types/v0.8.9/CustomError' +import { createNewBlockchain, deployContract } from './common' + +describe('CustomError support', () => { + const chain = createNewBlockchain('CustomError') + + let contract!: CustomError + + beforeEach(async () => { + contract = chain.contract + const { signer } = chain + + signer.provider.pollingInterval = 100 + contract = await deployContract(signer, 'CustomError') + }) + + afterEach(async () => {}) + + it('typed error import', async () => { + try { + await contract.callStatic.transfer('0x0000000000000000000000000000000000000000', 1) + } catch (err) { + const expectedError = contract.interface.errors['InsufficientBalance(uint256,uint256)'] + // eslint-disable-next-line no-console + console.log(expectedError, err) + // TODO: still wip on how to best consume the errors here + // if(err.reason !== expectedError.) + } + }) +}) diff --git a/packages/target-ethers-v5-test/types/factories/v0.8.9/CustomError__factory.ts b/packages/target-ethers-v5-test/types/factories/v0.8.9/CustomError__factory.ts new file mode 100644 index 000000000..fad383002 --- /dev/null +++ b/packages/target-ethers-v5-test/types/factories/v0.8.9/CustomError__factory.ts @@ -0,0 +1,60 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ + +import { Contract, Signer, utils } from "ethers"; +import type { Provider } from "@ethersproject/providers"; +import type { + CustomError, + CustomErrorInterface, +} from "../../v0.8.9/CustomError"; + +const _abi = [ + { + inputs: [ + { + internalType: "uint256", + name: "available", + type: "uint256", + }, + { + internalType: "uint256", + name: "required", + type: "uint256", + }, + ], + name: "InsufficientBalance", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "transfer", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +]; + +export class CustomError__factory { + static readonly abi = _abi; + static createInterface(): CustomErrorInterface { + return new utils.Interface(_abi) as CustomErrorInterface; + } + static connect( + address: string, + signerOrProvider: Signer | Provider + ): CustomError { + return new Contract(address, _abi, signerOrProvider) as CustomError; + } +} diff --git a/packages/target-ethers-v5-test/types/factories/v0.8.9/index.ts b/packages/target-ethers-v5-test/types/factories/v0.8.9/index.ts index 964695c88..9ce92dfec 100644 --- a/packages/target-ethers-v5-test/types/factories/v0.8.9/index.ts +++ b/packages/target-ethers-v5-test/types/factories/v0.8.9/index.ts @@ -4,6 +4,7 @@ export * as kingOfTheHill from "./KingOfTheHill"; export * as rarity from "./Rarity"; export * as nested from "./nested"; +export { CustomError__factory } from "./CustomError__factory"; export { ISimpleToken__factory } from "./ISimpleToken__factory"; export { Issue552_Reproduction__factory } from "./Issue552_Reproduction__factory"; export { SimpleToken__factory } from "./SimpleToken__factory"; diff --git a/packages/target-ethers-v5-test/types/index.ts b/packages/target-ethers-v5-test/types/index.ts index 86c671afb..29f70f9dd 100644 --- a/packages/target-ethers-v5-test/types/index.ts +++ b/packages/target-ethers-v5-test/types/index.ts @@ -30,6 +30,8 @@ export type { Payable } from "./v0.6.4/Payable/Payable"; export { Payable__factory } from "./factories/v0.6.4/Payable/Payable__factory"; export type { PayableFactory } from "./v0.6.4/Payable/PayableFactory"; export { PayableFactory__factory } from "./factories/v0.6.4/Payable/PayableFactory__factory"; +export type { CustomError } from "./v0.8.9/CustomError"; +export { CustomError__factory } from "./factories/v0.8.9/CustomError__factory"; export type { ISimpleToken } from "./v0.8.9/ISimpleToken"; export { ISimpleToken__factory } from "./factories/v0.8.9/ISimpleToken__factory"; export type { Issue552_Reproduction } from "./v0.8.9/Issue552_Reproduction"; diff --git a/packages/target-ethers-v5-test/types/v0.6.4/DataTypesInput.ts b/packages/target-ethers-v5-test/types/v0.6.4/DataTypesInput.ts index a14041bcd..6af137ebd 100644 --- a/packages/target-ethers-v5-test/types/v0.6.4/DataTypesInput.ts +++ b/packages/target-ethers-v5-test/types/v0.6.4/DataTypesInput.ts @@ -388,6 +388,8 @@ export interface DataTypesInputInterface extends utils.Interface { ): Result; events: {}; + + errors: {}; } export interface DataTypesInput extends BaseContract { diff --git a/packages/target-ethers-v5-test/types/v0.6.4/DataTypesPure.ts b/packages/target-ethers-v5-test/types/v0.6.4/DataTypesPure.ts index 911257c2e..7de7f3dd6 100644 --- a/packages/target-ethers-v5-test/types/v0.6.4/DataTypesPure.ts +++ b/packages/target-ethers-v5-test/types/v0.6.4/DataTypesPure.ts @@ -153,6 +153,8 @@ export interface DataTypesPureInterface extends utils.Interface { decodeFunctionResult(functionFragment: "pure_uint8", data: BytesLike): Result; events: {}; + + errors: {}; } export interface DataTypesPure extends BaseContract { diff --git a/packages/target-ethers-v5-test/types/v0.6.4/DataTypesView.ts b/packages/target-ethers-v5-test/types/v0.6.4/DataTypesView.ts index afe48c687..5ddfea9a1 100644 --- a/packages/target-ethers-v5-test/types/v0.6.4/DataTypesView.ts +++ b/packages/target-ethers-v5-test/types/v0.6.4/DataTypesView.ts @@ -153,6 +153,8 @@ export interface DataTypesViewInterface extends utils.Interface { decodeFunctionResult(functionFragment: "view_uint8", data: BytesLike): Result; events: {}; + + errors: {}; } export interface DataTypesView extends BaseContract { diff --git a/packages/target-ethers-v5-test/types/v0.6.4/Events.ts b/packages/target-ethers-v5-test/types/v0.6.4/Events.ts index b9dc14576..24c1489a9 100644 --- a/packages/target-ethers-v5-test/types/v0.6.4/Events.ts +++ b/packages/target-ethers-v5-test/types/v0.6.4/Events.ts @@ -121,6 +121,8 @@ export interface EventsInterface extends utils.Interface { getEvent(nameOrSignatureOrTopic: "Event4"): EventFragment; getEvent(nameOrSignatureOrTopic: "NoArgsEvent"): EventFragment; getEvent(nameOrSignatureOrTopic: "UpdateFrequencySet"): EventFragment; + + errors: {}; } export interface AnonEvent1EventObject { diff --git a/packages/target-ethers-v5-test/types/v0.6.4/Issue428_Reproduction/A.ts b/packages/target-ethers-v5-test/types/v0.6.4/Issue428_Reproduction/A.ts index 98287f216..41e7a32bc 100644 --- a/packages/target-ethers-v5-test/types/v0.6.4/Issue428_Reproduction/A.ts +++ b/packages/target-ethers-v5-test/types/v0.6.4/Issue428_Reproduction/A.ts @@ -19,6 +19,8 @@ export interface AInterface extends utils.Interface { }; getEvent(nameOrSignatureOrTopic: "Committed"): EventFragment; + + errors: {}; } export interface CommittedEventObject { diff --git a/packages/target-ethers-v5-test/types/v0.6.4/Issue428_Reproduction/B.ts b/packages/target-ethers-v5-test/types/v0.6.4/Issue428_Reproduction/B.ts index 2a49529ef..a0f0f3810 100644 --- a/packages/target-ethers-v5-test/types/v0.6.4/Issue428_Reproduction/B.ts +++ b/packages/target-ethers-v5-test/types/v0.6.4/Issue428_Reproduction/B.ts @@ -21,6 +21,8 @@ export interface BInterface extends utils.Interface { getEvent(nameOrSignatureOrTopic: "Committed(uint256)"): EventFragment; getEvent(nameOrSignatureOrTopic: "Committed(address[])"): EventFragment; + + errors: {}; } export interface Committed_uint256_EventObject { diff --git a/packages/target-ethers-v5-test/types/v0.6.4/Library/Lib.ts b/packages/target-ethers-v5-test/types/v0.6.4/Library/Lib.ts index 2bd5f1dfb..87a131f7e 100644 --- a/packages/target-ethers-v5-test/types/v0.6.4/Library/Lib.ts +++ b/packages/target-ethers-v5-test/types/v0.6.4/Library/Lib.ts @@ -32,6 +32,8 @@ export interface LibInterface extends utils.Interface { decodeFunctionResult(functionFragment: "other", data: BytesLike): Result; events: {}; + + errors: {}; } export interface Lib extends BaseContract { diff --git a/packages/target-ethers-v5-test/types/v0.6.4/LibraryConsumer.ts b/packages/target-ethers-v5-test/types/v0.6.4/LibraryConsumer.ts index 70fdfd756..439234fc8 100644 --- a/packages/target-ethers-v5-test/types/v0.6.4/LibraryConsumer.ts +++ b/packages/target-ethers-v5-test/types/v0.6.4/LibraryConsumer.ts @@ -35,6 +35,8 @@ export interface LibraryConsumerInterface extends utils.Interface { decodeFunctionResult(functionFragment: "someOther", data: BytesLike): Result; events: {}; + + errors: {}; } export interface LibraryConsumer extends BaseContract { diff --git a/packages/target-ethers-v5-test/types/v0.6.4/Name-Mangling/NAME12mangling.ts b/packages/target-ethers-v5-test/types/v0.6.4/Name-Mangling/NAME12mangling.ts index 143ad127c..f00d9c734 100644 --- a/packages/target-ethers-v5-test/types/v0.6.4/Name-Mangling/NAME12mangling.ts +++ b/packages/target-ethers-v5-test/types/v0.6.4/Name-Mangling/NAME12mangling.ts @@ -34,6 +34,8 @@ export interface NAME12manglingInterface extends utils.Interface { decodeFunctionResult(functionFragment: "works", data: BytesLike): Result; events: {}; + + errors: {}; } export interface NAME12mangling extends BaseContract { diff --git a/packages/target-ethers-v5-test/types/v0.6.4/Overloads.ts b/packages/target-ethers-v5-test/types/v0.6.4/Overloads.ts index dbe661e54..518e2b35f 100644 --- a/packages/target-ethers-v5-test/types/v0.6.4/Overloads.ts +++ b/packages/target-ethers-v5-test/types/v0.6.4/Overloads.ts @@ -49,6 +49,8 @@ export interface OverloadsInterface extends utils.Interface { ): Result; events: {}; + + errors: {}; } export interface Overloads extends BaseContract { diff --git a/packages/target-ethers-v5-test/types/v0.6.4/Payable/Payable.ts b/packages/target-ethers-v5-test/types/v0.6.4/Payable/Payable.ts index a04ee47b3..d25a696e0 100644 --- a/packages/target-ethers-v5-test/types/v0.6.4/Payable/Payable.ts +++ b/packages/target-ethers-v5-test/types/v0.6.4/Payable/Payable.ts @@ -51,6 +51,8 @@ export interface PayableInterface extends utils.Interface { ): Result; events: {}; + + errors: {}; } export interface Payable extends BaseContract { diff --git a/packages/target-ethers-v5-test/types/v0.6.4/Payable/PayableFactory.ts b/packages/target-ethers-v5-test/types/v0.6.4/Payable/PayableFactory.ts index 094774c8e..eaa1aaa53 100644 --- a/packages/target-ethers-v5-test/types/v0.6.4/Payable/PayableFactory.ts +++ b/packages/target-ethers-v5-test/types/v0.6.4/Payable/PayableFactory.ts @@ -36,6 +36,8 @@ export interface PayableFactoryInterface extends utils.Interface { decodeFunctionResult(functionFragment: "newPayable", data: BytesLike): Result; events: {}; + + errors: {}; } export interface PayableFactory extends BaseContract { diff --git a/packages/target-ethers-v5-test/types/v0.8.9/CustomError.ts b/packages/target-ethers-v5-test/types/v0.8.9/CustomError.ts new file mode 100644 index 000000000..16782e6cd --- /dev/null +++ b/packages/target-ethers-v5-test/types/v0.8.9/CustomError.ts @@ -0,0 +1,122 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { + BaseContract, + BigNumber, + BigNumberish, + BytesLike, + CallOverrides, + ContractTransaction, + Overrides, + PopulatedTransaction, + Signer, + utils, +} from "ethers"; +import type { + FunctionFragment, + Result, + ErrorFragment, +} from "@ethersproject/abi"; +import type { Listener, Provider } from "@ethersproject/providers"; +import type { + TypedEventFilter, + TypedEvent, + TypedListener, + OnEvent, +} from "../common"; + +export interface CustomErrorInterface extends utils.Interface { + functions: { + "transfer(address,uint256)": FunctionFragment; + }; + + getFunction(nameOrSignatureOrTopic: "transfer"): FunctionFragment; + + encodeFunctionData( + functionFragment: "transfer", + values: [string, BigNumberish] + ): string; + + decodeFunctionResult(functionFragment: "transfer", data: BytesLike): Result; + + events: {}; + + errors: { + "InsufficientBalance(uint256,uint256)": ErrorFragment; + }; + + getError(nameOrSignature: "InsufficientBalance"): ErrorFragment; +} + +export interface InsufficientBalanceErrorObject { + available: BigNumber; + required: BigNumber; +} + +export interface CustomError extends BaseContract { + connect(signerOrProvider: Signer | Provider | string): this; + attach(addressOrName: string): this; + deployed(): Promise; + + interface: CustomErrorInterface; + + queryFilter( + event: TypedEventFilter, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>; + + listeners( + eventFilter?: TypedEventFilter + ): Array>; + listeners(eventName?: string): Array; + removeAllListeners( + eventFilter: TypedEventFilter + ): this; + removeAllListeners(eventName?: string): this; + off: OnEvent; + on: OnEvent; + once: OnEvent; + removeListener: OnEvent; + + functions: { + transfer( + to: string, + amount: BigNumberish, + overrides?: Overrides & { from?: string | Promise } + ): Promise; + }; + + transfer( + to: string, + amount: BigNumberish, + overrides?: Overrides & { from?: string | Promise } + ): Promise; + + callStatic: { + transfer( + to: string, + amount: BigNumberish, + overrides?: CallOverrides + ): Promise; + }; + + filters: {}; + + estimateGas: { + transfer( + to: string, + amount: BigNumberish, + overrides?: Overrides & { from?: string | Promise } + ): Promise; + }; + + populateTransaction: { + transfer( + to: string, + amount: BigNumberish, + overrides?: Overrides & { from?: string | Promise } + ): Promise; + }; +} diff --git a/packages/target-ethers-v5-test/types/v0.8.9/ISimpleToken.ts b/packages/target-ethers-v5-test/types/v0.8.9/ISimpleToken.ts index 601e12db8..7e19915ff 100644 --- a/packages/target-ethers-v5-test/types/v0.8.9/ISimpleToken.ts +++ b/packages/target-ethers-v5-test/types/v0.8.9/ISimpleToken.ts @@ -37,6 +37,8 @@ export interface ISimpleTokenInterface extends utils.Interface { decodeFunctionResult(functionFragment: "transfer", data: BytesLike): Result; events: {}; + + errors: {}; } export interface ISimpleToken extends BaseContract { diff --git a/packages/target-ethers-v5-test/types/v0.8.9/Issue552_Reproduction.ts b/packages/target-ethers-v5-test/types/v0.8.9/Issue552_Reproduction.ts index afb3a8f9f..f6803cde9 100644 --- a/packages/target-ethers-v5-test/types/v0.8.9/Issue552_Reproduction.ts +++ b/packages/target-ethers-v5-test/types/v0.8.9/Issue552_Reproduction.ts @@ -78,6 +78,8 @@ export interface Issue552_ReproductionInterface extends utils.Interface { ): Result; events: {}; + + errors: {}; } export interface Issue552_Reproduction extends BaseContract { diff --git a/packages/target-ethers-v5-test/types/v0.8.9/KingOfTheHill/KingOfTheHill.ts b/packages/target-ethers-v5-test/types/v0.8.9/KingOfTheHill/KingOfTheHill.ts index f2d13fffe..4b0a5f4ce 100644 --- a/packages/target-ethers-v5-test/types/v0.8.9/KingOfTheHill/KingOfTheHill.ts +++ b/packages/target-ethers-v5-test/types/v0.8.9/KingOfTheHill/KingOfTheHill.ts @@ -18,6 +18,7 @@ import type { FunctionFragment, Result, EventFragment, + ErrorFragment, } from "@ethersproject/abi"; import type { Listener, Provider } from "@ethersproject/providers"; import type { @@ -63,6 +64,12 @@ export interface KingOfTheHillInterface extends utils.Interface { }; getEvent(nameOrSignatureOrTopic: "HighestBidIncreased"): EventFragment; + + errors: { + "BidNotHighEnough(uint256,uint256)": ErrorFragment; + }; + + getError(nameOrSignature: "BidNotHighEnough"): ErrorFragment; } export interface HighestBidIncreasedEventObject { @@ -76,6 +83,11 @@ export type HighestBidIncreasedEvent = TypedEvent< export type HighestBidIncreasedEventFilter = TypedEventFilter; +export interface BidNotHighEnoughErrorObject { + value: BigNumber; + highestBidValue: BigNumber; +} + export interface KingOfTheHill extends BaseContract { connect(signerOrProvider: Signer | Provider | string): this; attach(addressOrName: string): this; diff --git a/packages/target-ethers-v5-test/types/v0.8.9/KingOfTheHill/Withdrawable.ts b/packages/target-ethers-v5-test/types/v0.8.9/KingOfTheHill/Withdrawable.ts index dc139bedf..b128e3fe2 100644 --- a/packages/target-ethers-v5-test/types/v0.8.9/KingOfTheHill/Withdrawable.ts +++ b/packages/target-ethers-v5-test/types/v0.8.9/KingOfTheHill/Withdrawable.ts @@ -33,6 +33,8 @@ export interface WithdrawableInterface extends utils.Interface { decodeFunctionResult(functionFragment: "withdraw", data: BytesLike): Result; events: {}; + + errors: {}; } export interface Withdrawable extends BaseContract { diff --git a/packages/target-ethers-v5-test/types/v0.8.9/Rarity/ERC721.ts b/packages/target-ethers-v5-test/types/v0.8.9/Rarity/ERC721.ts index 6a51a1c99..166a7afa7 100644 --- a/packages/target-ethers-v5-test/types/v0.8.9/Rarity/ERC721.ts +++ b/packages/target-ethers-v5-test/types/v0.8.9/Rarity/ERC721.ts @@ -123,6 +123,8 @@ export interface ERC721Interface extends utils.Interface { getEvent(nameOrSignatureOrTopic: "Approval"): EventFragment; getEvent(nameOrSignatureOrTopic: "ApprovalForAll"): EventFragment; getEvent(nameOrSignatureOrTopic: "Transfer"): EventFragment; + + errors: {}; } export interface ApprovalEventObject { diff --git a/packages/target-ethers-v5-test/types/v0.8.9/Rarity/ERC721Enumerable.ts b/packages/target-ethers-v5-test/types/v0.8.9/Rarity/ERC721Enumerable.ts index 01e5ca999..b35969aa5 100644 --- a/packages/target-ethers-v5-test/types/v0.8.9/Rarity/ERC721Enumerable.ts +++ b/packages/target-ethers-v5-test/types/v0.8.9/Rarity/ERC721Enumerable.ts @@ -153,6 +153,8 @@ export interface ERC721EnumerableInterface extends utils.Interface { getEvent(nameOrSignatureOrTopic: "Approval"): EventFragment; getEvent(nameOrSignatureOrTopic: "ApprovalForAll"): EventFragment; getEvent(nameOrSignatureOrTopic: "Transfer"): EventFragment; + + errors: {}; } export interface ApprovalEventObject { diff --git a/packages/target-ethers-v5-test/types/v0.8.9/Rarity/IERC721.ts b/packages/target-ethers-v5-test/types/v0.8.9/Rarity/IERC721.ts index 437b6d1d3..9a8682793 100644 --- a/packages/target-ethers-v5-test/types/v0.8.9/Rarity/IERC721.ts +++ b/packages/target-ethers-v5-test/types/v0.8.9/Rarity/IERC721.ts @@ -123,6 +123,8 @@ export interface IERC721Interface extends utils.Interface { getEvent(nameOrSignatureOrTopic: "Approval"): EventFragment; getEvent(nameOrSignatureOrTopic: "ApprovalForAll"): EventFragment; getEvent(nameOrSignatureOrTopic: "Transfer"): EventFragment; + + errors: {}; } export interface ApprovalEventObject { diff --git a/packages/target-ethers-v5-test/types/v0.8.9/Rarity/IERC721Enumerable.ts b/packages/target-ethers-v5-test/types/v0.8.9/Rarity/IERC721Enumerable.ts index 2cb29f60e..8e0686edf 100644 --- a/packages/target-ethers-v5-test/types/v0.8.9/Rarity/IERC721Enumerable.ts +++ b/packages/target-ethers-v5-test/types/v0.8.9/Rarity/IERC721Enumerable.ts @@ -153,6 +153,8 @@ export interface IERC721EnumerableInterface extends utils.Interface { getEvent(nameOrSignatureOrTopic: "Approval"): EventFragment; getEvent(nameOrSignatureOrTopic: "ApprovalForAll"): EventFragment; getEvent(nameOrSignatureOrTopic: "Transfer"): EventFragment; + + errors: {}; } export interface ApprovalEventObject { diff --git a/packages/target-ethers-v5-test/types/v0.8.9/Rarity/IERC721Receiver.ts b/packages/target-ethers-v5-test/types/v0.8.9/Rarity/IERC721Receiver.ts index d348d3663..12c09d38e 100644 --- a/packages/target-ethers-v5-test/types/v0.8.9/Rarity/IERC721Receiver.ts +++ b/packages/target-ethers-v5-test/types/v0.8.9/Rarity/IERC721Receiver.ts @@ -40,6 +40,8 @@ export interface IERC721ReceiverInterface extends utils.Interface { ): Result; events: {}; + + errors: {}; } export interface IERC721Receiver extends BaseContract { diff --git a/packages/target-ethers-v5-test/types/v0.8.9/Rarity/Rarity.ts b/packages/target-ethers-v5-test/types/v0.8.9/Rarity/Rarity.ts index a77ce527e..a796992f2 100644 --- a/packages/target-ethers-v5-test/types/v0.8.9/Rarity/Rarity.ts +++ b/packages/target-ethers-v5-test/types/v0.8.9/Rarity/Rarity.ts @@ -248,6 +248,8 @@ export interface RarityInterface extends utils.Interface { getEvent(nameOrSignatureOrTopic: "Transfer"): EventFragment; getEvent(nameOrSignatureOrTopic: "leveled"): EventFragment; getEvent(nameOrSignatureOrTopic: "summoned"): EventFragment; + + errors: {}; } export interface ApprovalEventObject { diff --git a/packages/target-ethers-v5-test/types/v0.8.9/SimpleToken.ts b/packages/target-ethers-v5-test/types/v0.8.9/SimpleToken.ts index 89978520c..326032ec3 100644 --- a/packages/target-ethers-v5-test/types/v0.8.9/SimpleToken.ts +++ b/packages/target-ethers-v5-test/types/v0.8.9/SimpleToken.ts @@ -37,6 +37,8 @@ export interface SimpleTokenInterface extends utils.Interface { decodeFunctionResult(functionFragment: "transfer", data: BytesLike): Result; events: {}; + + errors: {}; } export interface SimpleToken extends BaseContract { diff --git a/packages/target-ethers-v5-test/types/v0.8.9/index.ts b/packages/target-ethers-v5-test/types/v0.8.9/index.ts index 25c6eeca5..b9e9025e9 100644 --- a/packages/target-ethers-v5-test/types/v0.8.9/index.ts +++ b/packages/target-ethers-v5-test/types/v0.8.9/index.ts @@ -7,6 +7,7 @@ import type * as rarity from "./Rarity"; export type { rarity }; import type * as nested from "./nested"; export type { nested }; +export type { CustomError } from "./CustomError"; export type { ISimpleToken } from "./ISimpleToken"; export type { Issue552_Reproduction } from "./Issue552_Reproduction"; export type { SimpleToken } from "./SimpleToken"; diff --git a/packages/target-ethers-v5-test/types/v0.8.9/nested/a/NestedLibrary.ts b/packages/target-ethers-v5-test/types/v0.8.9/nested/a/NestedLibrary.ts index 3bc95b6bb..e68e30463 100644 --- a/packages/target-ethers-v5-test/types/v0.8.9/nested/a/NestedLibrary.ts +++ b/packages/target-ethers-v5-test/types/v0.8.9/nested/a/NestedLibrary.ts @@ -31,6 +31,8 @@ export interface NestedLibraryInterface extends utils.Interface { decodeFunctionResult(functionFragment: "getValue", data: BytesLike): Result; events: {}; + + errors: {}; } export interface NestedLibrary extends BaseContract { diff --git a/packages/target-ethers-v5-test/types/v0.8.9/nested/b/NestedLibrary.ts b/packages/target-ethers-v5-test/types/v0.8.9/nested/b/NestedLibrary.ts index 5eef3ca0b..3e2e3b3e2 100644 --- a/packages/target-ethers-v5-test/types/v0.8.9/nested/b/NestedLibrary.ts +++ b/packages/target-ethers-v5-test/types/v0.8.9/nested/b/NestedLibrary.ts @@ -31,6 +31,8 @@ export interface NestedLibraryInterface extends utils.Interface { decodeFunctionResult(functionFragment: "getValue", data: BytesLike): Result; events: {}; + + errors: {}; } export interface NestedLibrary extends BaseContract { diff --git a/packages/target-ethers-v5/src/codegen/errors.ts b/packages/target-ethers-v5/src/codegen/errors.ts new file mode 100644 index 000000000..546feee0f --- /dev/null +++ b/packages/target-ethers-v5/src/codegen/errors.ts @@ -0,0 +1,58 @@ +import { createPositionalIdentifier, CustomErrorDeclaration, getSignatureForFn } from 'typechain' + +import { generateOutputComplexTypesAsObject } from './types' + +export function generateErrorTypeExports(errors: CustomErrorDeclaration[]) { + if (errors.length === 1) { + return generateErrorTypeExport(errors[0], false) + } else { + return errors.map((e) => generateErrorTypeExport(e, true)).join('\n') + } +} + +function generateErrorTypeExport(error: CustomErrorDeclaration, includeArgTypes: boolean) { + const components = error.inputs.map((input) => ({ name: input.name, type: input.type })) + const objectOutput = generateOutputComplexTypesAsObject(components, { useStructs: true }) || '{}' + + const identifier = generateErrorIdentifier(error, { includeArgTypes }) + + return ` + export interface ${identifier}Object ${objectOutput}; + ` +} + +export function generateInterfaceErrorDescription(error: CustomErrorDeclaration): string { + return `'${generateErrorSignature(error)}': ErrorFragment;` +} + +export function generateErrorSignature(error: CustomErrorDeclaration): string { + return `${error.name}(${error.inputs.map((input: any) => input.type.originalType).join(',')})` +} + +export function generateErrorInputs(errorArgs: CustomErrorDeclaration['inputs']) { + if (errorArgs.length === 0) { + return '' + } + return ( + errorArgs + .map((arg) => { + return createPositionalIdentifier(arg.name) + }) + .join(', ') + ', ' + ) +} + +export function generateGetError(error: CustomErrorDeclaration, useSignature: boolean): string { + return `getError(nameOrSignature: '${useSignature ? generateErrorSignature(error) : error.name}'): ErrorFragment;` +} + +function generateErrorIdentifier( + error: CustomErrorDeclaration, + { includeArgTypes }: { includeArgTypes?: boolean } = {}, +) { + if (includeArgTypes) { + return getSignatureForFn(error) + '_Error' + } else { + return error.name + 'Error' + } +} diff --git a/packages/target-ethers-v5/src/codegen/index.ts b/packages/target-ethers-v5/src/codegen/index.ts index de8fd9e60..8499a1032 100644 --- a/packages/target-ethers-v5/src/codegen/index.ts +++ b/packages/target-ethers-v5/src/codegen/index.ts @@ -5,12 +5,14 @@ import { Contract, createImportsForUsedIdentifiers, createImportTypeDeclaration, + CustomErrorDeclaration, EventDeclaration, FunctionDeclaration, StructType, } from 'typechain' import { FACTORY_POSTFIX, STRUCT_INPUT_POSTFIX } from '../common' +import { generateErrorTypeExports, generateGetError, generateInterfaceErrorDescription } from './errors' import { EVENT_IMPORTS, EVENT_METHOD_OVERRIDES, @@ -69,9 +71,20 @@ export function codegenContractTypings(contract: Contract, codegenConfig: Codege ${values(contract.events) .flatMap((v) => processDeclaration(v, alwaysGenerateOverloads, generateGetEvent)) .join('\n')} + + errors: { + ${values(contract.errors) + .flatMap((v) => v.map(generateInterfaceErrorDescription)) + .join('\n')} + }; + + ${values(contract.errors) + .flatMap((v) => processDeclaration(v, alwaysGenerateOverloads, generateGetError)) + .join('\n')} } ${values(contract.events).map(generateEventTypeExports).join('\n')} + ${values(contract.errors).map(generateErrorTypeExports).join('\n')} export interface ${contract.name} extends BaseContract { ${codegenConfig.discriminateTypes ? `contractName: '${contract.name}';\n` : ``} @@ -137,7 +150,7 @@ export function codegenContractTypings(contract: Contract, codegenConfig: Codege 'Signer', 'utils', ], - 'type @ethersproject/abi': ['FunctionFragment', 'Result', 'EventFragment'], + 'type @ethersproject/abi': ['FunctionFragment', 'Result', 'EventFragment', 'ErrorFragment'], 'type @ethersproject/providers': ['Listener', 'Provider'], }, source, @@ -375,7 +388,7 @@ function generateLibraryAddressesInterface(contract: Contract, bytecode: Bytecod * @param stringGen - function generating source code based on the declaration * @returns generated source code */ -function processDeclaration( +function processDeclaration( fns: D[], forceGenerateOverloads: boolean, stringGen: (fn: D, useSignature: boolean) => string, diff --git a/packages/target-ethers-v5/test/generation.test.ts b/packages/target-ethers-v5/test/generation.test.ts index 42d2f9154..d12559ae4 100644 --- a/packages/target-ethers-v5/test/generation.test.ts +++ b/packages/target-ethers-v5/test/generation.test.ts @@ -17,6 +17,7 @@ describe('Ethers generation edge cases', () => { rawName: 'TestContract', functions: {}, events: {}, + errors: {}, structs: {}, constructor: [{ name: 'constructor', inputs: [], outputs: [], stateMutability: 'nonpayable' }], path: [], diff --git a/packages/target-web3-v1-test/types/v0.8.9/CustomError.ts b/packages/target-web3-v1-test/types/v0.8.9/CustomError.ts new file mode 100644 index 000000000..5beec33e5 --- /dev/null +++ b/packages/target-web3-v1-test/types/v0.8.9/CustomError.ts @@ -0,0 +1,40 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ + +import type BN from "bn.js"; +import type { ContractOptions } from "web3-eth-contract"; +import type { EventLog } from "web3-core"; +import type { EventEmitter } from "events"; +import type { + Callback, + PayableTransactionObject, + NonPayableTransactionObject, + BlockType, + ContractEventLog, + BaseContract, +} from "../types"; + +export interface EventOptions { + filter?: object; + fromBlock?: BlockType; + topics?: string[]; +} + +export interface CustomError extends BaseContract { + constructor( + jsonInterface: any[], + address?: string, + options?: ContractOptions + ): CustomError; + clone(): CustomError; + methods: { + transfer( + to: string, + amount: number | string | BN + ): NonPayableTransactionObject; + }; + events: { + allEvents(options?: EventOptions, cb?: Callback): EventEmitter; + }; +} diff --git a/packages/target-web3-v1-test/types/v0.8.9/index.ts b/packages/target-web3-v1-test/types/v0.8.9/index.ts index 25c6eeca5..b9e9025e9 100644 --- a/packages/target-web3-v1-test/types/v0.8.9/index.ts +++ b/packages/target-web3-v1-test/types/v0.8.9/index.ts @@ -7,6 +7,7 @@ import type * as rarity from "./Rarity"; export type { rarity }; import type * as nested from "./nested"; export type { nested }; +export type { CustomError } from "./CustomError"; export type { ISimpleToken } from "./ISimpleToken"; export type { Issue552_Reproduction } from "./Issue552_Reproduction"; export type { SimpleToken } from "./SimpleToken"; diff --git a/packages/typechain/src/parser/abiParser.ts b/packages/typechain/src/parser/abiParser.ts index 0f054ed36..6d916eaf9 100644 --- a/packages/typechain/src/parser/abiParser.ts +++ b/packages/typechain/src/parser/abiParser.ts @@ -61,6 +61,7 @@ export interface Contract { constructor: FunctionWithoutOutputDeclaration[] functions: Dictionary events: Dictionary + errors: Dictionary structs: Dictionary documentation?: | { @@ -88,6 +89,12 @@ export interface RawAbiDefinition { type: string } +export interface CustomErrorDeclaration { + name: string + inputs: AbiParameter[] + documentation?: FunctionDocumentation | undefined // should omit `return` from FunctionDocumentation? +} + export interface EventDeclaration { name: string isAnonymous: boolean @@ -150,6 +157,7 @@ export function parse(abi: RawAbiDefinition[], path: string, documentation?: Doc let fallback: FunctionWithoutInputDeclaration | undefined const functions: FunctionDeclaration[] = [] const events: EventDeclaration[] = [] + const errors: CustomErrorDeclaration[] = [] const structs: StructType[] = [] function registerStruct(newStruct: StructType) { @@ -196,6 +204,11 @@ export function parse(abi: RawAbiDefinition[], path: string, documentation?: Doc return } + if (abiPiece.type === 'error') { + errors.push(parseCustomErrorDeclaration(abiPiece, registerStruct, documentation)) + return + } + debug(`Unrecognized abi element: ${abiPiece.type}`) }) @@ -205,6 +218,7 @@ export function parse(abi: RawAbiDefinition[], path: string, documentation?: Doc constructor: constructors, functions: groupBy(functions, (f) => f.name), events: groupBy(events, (e) => e.name), + errors: groupBy(errors, (e) => e.name), structs: groupBy(structs, (e) => e.structName && e.structName.toString()), documentation: documentation ? omit(documentation, ['methods']) : undefined, } @@ -299,6 +313,19 @@ function parseFallback( } } +export function parseCustomErrorDeclaration( + abiPiece: RawAbiDefinition, + registerStruct: (struct: StructType) => void, + documentation?: DocumentationResult, +): CustomErrorDeclaration { + debug(`Parsing custom error declaration "${abiPiece.name}"`) + return { + name: abiPiece.name, + inputs: abiPiece.inputs.map(parseRawAbiParameter.bind(null, registerStruct)), + documentation: getFunctionDocumentation(abiPiece, documentation), + } +} + function parseFunctionDeclaration( abiPiece: RawAbiDefinition, registerStruct: (struct: StructType) => void, diff --git a/packages/typechain/src/utils/signatures.ts b/packages/typechain/src/utils/signatures.ts index e9717398d..4da9fc896 100644 --- a/packages/typechain/src/utils/signatures.ts +++ b/packages/typechain/src/utils/signatures.ts @@ -1,4 +1,4 @@ -import { AbiParameter, EventDeclaration, FunctionDeclaration } from '../parser/abiParser' +import { AbiParameter, CustomErrorDeclaration, EventDeclaration, FunctionDeclaration } from '../parser/abiParser' import { ArrayType, TupleType } from '../parser/parseEvmType' export function getFullSignatureAsSymbolForEvent(event: EventDeclaration): string { @@ -32,6 +32,6 @@ export function getArgumentForSignature(argument: AbiParameter): string { } } -export function getSignatureForFn(fn: FunctionDeclaration): string { +export function getSignatureForFn(fn: FunctionDeclaration | CustomErrorDeclaration): string { return `${fn.name}(${fn.inputs.map((i) => getArgumentForSignature(i)).join(',')})` } diff --git a/packages/typechain/test/parser/abiParser.test.ts b/packages/typechain/test/parser/abiParser.test.ts index ed4e67423..5b22d6cf5 100644 --- a/packages/typechain/test/parser/abiParser.test.ts +++ b/packages/typechain/test/parser/abiParser.test.ts @@ -12,6 +12,7 @@ import { isConstant, isConstantFn, parse, + parseCustomErrorDeclaration, parseEvent, RawAbiDefinition, RawEventAbiDefinition, @@ -368,6 +369,37 @@ describe('parseEvent', () => { }) }) +describe('parseError', () => { + it('works', () => { + const expectedError = { + inputs: [ + { + internalType: 'uint256', + name: 'magic', + type: 'uint256', + }, + { + internalType: 'address', + name: 'target', + type: 'address', + }, + ], + name: 'InsufficientBalance', + type: 'error', + } as RawAbiDefinition + const parsedError = parseCustomErrorDeclaration(expectedError, () => {}) + + expect(parsedError).toEqual({ + name: 'InsufficientBalance', + inputs: [ + { name: 'magic', type: { type: 'uinteger', bits: 256, originalType: 'uint256' } }, + { name: 'target', type: { type: 'address', originalType: 'address' } }, + ], + documentation: undefined, + }) + }) +}) + describe('parse', () => { describe('functions', () => { const abiPiece = { @@ -504,6 +536,7 @@ describe('parse', () => { documentation: undefined, functions: {}, structs: {}, + errors: {}, name: 'Sc1', rawName: 'sc1', path: [],