-
Notifications
You must be signed in to change notification settings - Fork 168
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🪚 OmniGraph™ Add EVM error parser (#65)
- Loading branch information
1 parent
8c641a8
commit 9a3e724
Showing
15 changed files
with
481 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
dist | ||
node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
artifacts | ||
cache | ||
deployments |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
dist/ | ||
node_modules/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<p align="center"> | ||
<a href="https://layerzero.network"> | ||
<img alt="LayerZero" style="max-width: 500px" src="https://d3a2dpnnrypp5h.cloudfront.net/bridge-app/lz.png"/> | ||
</a> | ||
</p> | ||
|
||
<h1 align="center">@layerzerolabs/utils-evm-test</h1> | ||
|
||
## Development | ||
|
||
This package provides integration tests for `@layerzerolabs/utils-evm` using `hardhat`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.19; | ||
|
||
contract Thrower { | ||
error CustomErrorWithNoArguments(); | ||
error CustomErrorWithAnArgument(string message); | ||
|
||
function throwWithAssert() external pure { | ||
assert(0 == 1); | ||
} | ||
|
||
// For some reason in hardhat node this function does not revert | ||
function throwWithRevertAndNoArguments() external pure { | ||
revert(); | ||
} | ||
|
||
function throwWithRevertAndArgument(string calldata message) external pure { | ||
revert(message); | ||
} | ||
|
||
// For some reason in hardhat node this function does not revert | ||
function throwWithRequireAndNoArguments() external pure { | ||
require(0 == 1); | ||
} | ||
|
||
function throwWithRequireAndArgument(string calldata message) external pure { | ||
require(0 == 1, message); | ||
} | ||
|
||
function throwWithCustomErrorAndNoArguments() external pure { | ||
revert CustomErrorWithNoArguments(); | ||
} | ||
|
||
function throwWithCustomErrorAndArgument(string calldata message) external pure { | ||
revert CustomErrorWithAnArgument(message); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import '@nomiclabs/hardhat-ethers' | ||
import { HardhatUserConfig } from 'hardhat/types' | ||
|
||
const config: HardhatUserConfig = { | ||
solidity: { | ||
version: '0.8.19', | ||
}, | ||
} | ||
|
||
export default config |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
{ | ||
"name": "@layerzerolabs/utils-evm-test", | ||
"version": "0.0.1", | ||
"private": true, | ||
"description": "Integration tests for ua-utils-evm-hardhat for V2", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/LayerZero-Labs/lz-utils.git", | ||
"directory": "packages/utils-evm-test" | ||
}, | ||
"license": "MIT", | ||
"scripts": { | ||
"lint": "npx eslint '**/*.{js,ts,json}'", | ||
"test": "npx hardhat test" | ||
}, | ||
"devDependencies": { | ||
"@ethersproject/abi": "^5.7.0", | ||
"@ethersproject/abstract-provider": "^5.7.0", | ||
"@ethersproject/abstract-signer": "^5.7.0", | ||
"@ethersproject/contracts": "^5.7.0", | ||
"@ethersproject/providers": "^5.7.0", | ||
"@layerzerolabs/lz-definitions": "~1.5.68", | ||
"@layerzerolabs/test-utils": "~0.0.1", | ||
"@layerzerolabs/utils": "~0.0.1", | ||
"@layerzerolabs/utils-evm": "~0.0.1", | ||
"@nomiclabs/hardhat-ethers": "^2.2.3", | ||
"@types/mocha": "^10.0.6", | ||
"chai": "^4.3.10", | ||
"ethers": "^5.7.0", | ||
"fast-check": "^3.14.0", | ||
"hardhat": "^2.19.0", | ||
"ts-node": "^10.9.1", | ||
"typescript": "^5.2.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
import fc from 'fast-check' | ||
import hre from 'hardhat' | ||
import { expect } from 'chai' | ||
import { Contract } from '@ethersproject/contracts' | ||
import { | ||
createErrorParser, | ||
PanicError, | ||
RevertError, | ||
CustomError, | ||
UnknownError, | ||
OmniContractFactory, | ||
} from '@layerzerolabs/utils-evm' | ||
import { OmniError } from '@layerzerolabs/utils' | ||
import { pointArbitrary } from '@layerzerolabs/test-utils' | ||
|
||
describe('errors/parser', () => { | ||
describe('createErrorParser', () => { | ||
const CONTRACT_NAME = 'Thrower' | ||
|
||
let contract: Contract | ||
let omniContractFactory: OmniContractFactory | ||
|
||
/** | ||
* Helper utility that swaps the promise resolution for rejection and other way around | ||
* | ||
* This is useful for the below tests since we are testing that promises reject | ||
* and want to get their rejection values. | ||
* | ||
* @param promise `Promise<unknown>` | ||
* | ||
* @returns `Promise<unknown>` | ||
*/ | ||
const assertFailed = async (promise: Promise<unknown>): Promise<unknown> => | ||
promise.then( | ||
(result) => { | ||
expect.fail(`Expected a promise to always reject but it resolved with ${JSON.stringify(result)}`) | ||
}, | ||
(error) => error | ||
) | ||
|
||
before(async () => { | ||
const contractFactory = await hre.ethers.getContractFactory(CONTRACT_NAME) | ||
|
||
contract = await contractFactory.deploy() | ||
omniContractFactory = async ({ eid, address }) => ({ eid, contract: contractFactory.attach(address) }) | ||
}) | ||
|
||
it('should pass an error through if it already is a ContractError', async () => { | ||
const errorParser = createErrorParser(omniContractFactory) | ||
|
||
await fc.assert( | ||
fc.asyncProperty(pointArbitrary, async (point) => { | ||
const omniError: OmniError = { error: new RevertError('A reason is worth a million bytes'), point } | ||
const parsedError = await errorParser(omniError) | ||
|
||
expect(parsedError.point).to.eql(point) | ||
expect(parsedError.error).to.be.instanceOf(RevertError) | ||
expect(parsedError.error.reason).to.equal('A reason is worth a million bytes') | ||
}) | ||
) | ||
}) | ||
|
||
it('should parse assert/panic', async () => { | ||
const errorParser = createErrorParser(omniContractFactory) | ||
|
||
await fc.assert( | ||
fc.asyncProperty(pointArbitrary, async (point) => { | ||
const error = await assertFailed(contract.throwWithAssert()) | ||
const omniError: OmniError = { error, point } | ||
const parsedError = await errorParser(omniError) | ||
|
||
expect(parsedError.point).to.eql(point) | ||
expect(parsedError.error).to.be.instanceOf(PanicError) | ||
expect(parsedError.error.reason).to.eql(BigInt(1)) | ||
}) | ||
) | ||
}) | ||
|
||
it('should parse revert with arguments', async () => { | ||
const errorParser = createErrorParser(omniContractFactory) | ||
|
||
await fc.assert( | ||
fc.asyncProperty(pointArbitrary, async (point) => { | ||
const error = await assertFailed(contract.throwWithRevertAndArgument('my bad')) | ||
const omniError: OmniError = { error, point } | ||
const parsedError = await errorParser(omniError) | ||
|
||
expect(parsedError.point).to.eql(point) | ||
expect(parsedError.error).to.be.instanceOf(RevertError) | ||
expect(parsedError.error.reason).to.eql('my bad') | ||
}) | ||
) | ||
}) | ||
|
||
it('should parse require with an argument', async () => { | ||
const errorParser = createErrorParser(omniContractFactory) | ||
|
||
await fc.assert( | ||
fc.asyncProperty(pointArbitrary, async (point) => { | ||
const error = await assertFailed(contract.throwWithRequireAndArgument('my bad')) | ||
const omniError: OmniError = { error, point } | ||
const parsedError = await errorParser(omniError) | ||
|
||
expect(parsedError.point).to.eql(point) | ||
expect(parsedError.error).to.be.instanceOf(RevertError) | ||
expect(parsedError.error.reason).to.eql('my bad') | ||
}) | ||
) | ||
}) | ||
|
||
it('should parse require with a custom error with no arguments', async () => { | ||
const errorParser = createErrorParser(omniContractFactory) | ||
|
||
await fc.assert( | ||
fc.asyncProperty(pointArbitrary, async (point) => { | ||
const error = await assertFailed(contract.throwWithCustomErrorAndNoArguments()) | ||
const omniError: OmniError = { error, point } | ||
const parsedError = await errorParser(omniError) | ||
|
||
expect(parsedError.point).to.eql(point) | ||
expect(parsedError.error).to.be.instanceOf(CustomError) | ||
expect(parsedError.error.reason).to.eql('CustomErrorWithNoArguments') | ||
expect((parsedError.error as CustomError).args).to.eql([]) | ||
}) | ||
) | ||
}) | ||
|
||
it('should parse require with a custom error with an argument', async () => { | ||
const errorParser = createErrorParser(omniContractFactory) | ||
|
||
await fc.assert( | ||
fc.asyncProperty(pointArbitrary, async (point) => { | ||
const error = await assertFailed(contract.throwWithCustomErrorAndArgument('my bad')) | ||
const omniError: OmniError = { error, point } | ||
const parsedError = await errorParser(omniError) | ||
|
||
expect(parsedError.point).to.eql(point) | ||
expect(parsedError.error).to.be.instanceOf(CustomError) | ||
expect(parsedError.error.reason).to.eql('CustomErrorWithAnArgument') | ||
expect((parsedError.error as CustomError).args).to.eql(['my bad']) | ||
}) | ||
) | ||
}) | ||
|
||
it('should parse string', async () => { | ||
const errorParser = createErrorParser(omniContractFactory) | ||
|
||
await fc.assert( | ||
fc.asyncProperty(pointArbitrary, async (point) => { | ||
const omniError: OmniError = { error: 'some weird error', point } | ||
const parsedError = await errorParser(omniError) | ||
|
||
expect(parsedError.point).to.eql(point) | ||
expect(parsedError.error).to.be.instanceOf(UnknownError) | ||
expect(parsedError.error.reason).to.be.undefined | ||
expect(parsedError.error.message).to.eql('Unknown error: some weird error') | ||
}) | ||
) | ||
}) | ||
|
||
it('should parse an Error', async () => { | ||
const errorParser = createErrorParser(omniContractFactory) | ||
|
||
await fc.assert( | ||
fc.asyncProperty(pointArbitrary, async (point) => { | ||
const omniError: OmniError = { error: new Error('some weird error'), point } | ||
const parsedError = await errorParser(omniError) | ||
|
||
expect(parsedError.point).to.eql(point) | ||
expect(parsedError.error).to.be.instanceOf(UnknownError) | ||
expect(parsedError.error.reason).to.be.undefined | ||
expect(parsedError.error.message).to.eql('Unknown error: Error: some weird error') | ||
}) | ||
) | ||
}) | ||
|
||
it('should never reject', async () => { | ||
const errorParser = createErrorParser(omniContractFactory) | ||
|
||
await fc.assert( | ||
fc.asyncProperty(pointArbitrary, fc.anything(), async (point, error) => { | ||
const omniError: OmniError = { error, point } | ||
const parsedError = await errorParser(omniError) | ||
|
||
expect(parsedError.point).to.eql(point) | ||
expect(parsedError.error).to.be.instanceOf(UnknownError) | ||
expect(parsedError.error.reason).to.be.undefined | ||
expect(parsedError.error.message).to.eql(`Unknown error: ${error}`) | ||
}) | ||
) | ||
}) | ||
|
||
// FIXME Write tests for throwWithRevertAndNoArguments - in hardhat node they don't seem to revert | ||
// FIXME Write tests for throwWithRequireAndNoArguments - in hardhat node they don't seem to revert | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"extends": "../../tsconfig.json", | ||
"exclude": ["dist", "node_modules"], | ||
"include": ["src", "test", "*.config.ts"], | ||
"compilerOptions": { | ||
"module": "commonjs", | ||
"types": ["node", "mocha"] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
export abstract class ContractError<TReason = unknown> extends Error { | ||
public abstract readonly reason: TReason | ||
} | ||
|
||
export class UnknownError extends ContractError<undefined> { | ||
public readonly reason = undefined | ||
|
||
constructor(message = 'Unknown contract error') { | ||
super(message) | ||
} | ||
} | ||
|
||
export class PanicError extends ContractError<bigint> { | ||
constructor( | ||
public readonly reason: bigint, | ||
message: string = `Contract panicked with code ${reason}` | ||
) { | ||
super(message) | ||
} | ||
} | ||
|
||
export class RevertError extends ContractError<string> { | ||
constructor( | ||
public readonly reason: string, | ||
message: string = `Contract reverted with reason '${reason}'` | ||
) { | ||
super(message) | ||
} | ||
} | ||
|
||
export class CustomError extends ContractError<string> { | ||
constructor( | ||
public readonly reason: string, | ||
public readonly args: unknown[], | ||
message: string = `Contract reverted with custom error '${reason}'` | ||
) { | ||
super(message) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './errors' | ||
export * from './parser' |
Oops, something went wrong.