Skip to content

Commit

Permalink
😨 Waffle-provider support panic codes (#721)
Browse files Browse the repository at this point in the history
  • Loading branch information
yivlad authored May 6, 2022
1 parent ba71ce4 commit 4622881
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 52 deletions.
6 changes: 6 additions & 0 deletions .changeset/dirty-badgers-sin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@ethereum-waffle/chai": patch
"@ethereum-waffle/provider": patch
---

Support panic codes in ganache
5 changes: 3 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 0 additions & 7 deletions waffle-chai/src/matchers/revertedWith.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,6 @@ export function supportRevertedWith(Assertion: Chai.AssertionStatic) {
const decodeHardhatError = (error: any) => {
const tryDecode = (error: any) => {
const errorString = String(error);
{
const regexp = new RegExp('VM Exception while processing transaction: reverted with reason string \'(.*)\'');
const matches = regexp.exec(errorString);
if (matches && matches.length >= 1) {
return matches[1];
}
}
{
const regexp = new RegExp('VM Exception while processing transaction: reverted with custom error \'(.*)\'');
const matches = regexp.exec(errorString);
Expand Down
96 changes: 84 additions & 12 deletions waffle-chai/test/contracts/Matchers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export const MATCHERS_SOURCE = `
pragma solidity ^0.6.0;
pragma solidity ^0.8.0;
contract Matchers {
uint counter;
Expand All @@ -20,6 +20,11 @@ export const MATCHERS_SOURCE = `
revert("Revert cause");
}
function doPanic() public pure {
uint d = 0;
uint x = 1 / d;
}
function doRevertWithComplexReason() public pure {
revert("Revert cause (with complex reason)");
}
Expand Down Expand Up @@ -50,17 +55,84 @@ export const MATCHERS_SOURCE = `
`;

export const MATCHERS_ABI = [
'function doNothing() public pure',
'function doModify() public',
'function doThrow() public pure',
'function doRevert() public pure',
'function doRevertWithComplexReason() public pure',
'function doRequireFail() public pure',
'function doRequireSuccess() public pure',
'function doThrowAndModify() public',
'function doRevertAndModify() public',
'function doRequireFailAndModify() public'
{
inputs: [],
name: 'doModify',
outputs: [],
stateMutability: 'nonpayable',
type: 'function'
},
{
inputs: [],
name: 'doNothing',
outputs: [],
stateMutability: 'pure',
type: 'function'
},
{
inputs: [],
name: 'doPanic',
outputs: [],
stateMutability: 'pure',
type: 'function'
},
{
inputs: [],
name: 'doRequireFail',
outputs: [],
stateMutability: 'pure',
type: 'function'
},
{
inputs: [],
name: 'doRequireFailAndModify',
outputs: [],
stateMutability: 'nonpayable',
type: 'function'
},
{
inputs: [],
name: 'doRequireSuccess',
outputs: [],
stateMutability: 'pure',
type: 'function'
},
{
inputs: [],
name: 'doRevert',
outputs: [],
stateMutability: 'pure',
type: 'function'
},
{
inputs: [],
name: 'doRevertAndModify',
outputs: [],
stateMutability: 'nonpayable',
type: 'function'
},
{
inputs: [],
name: 'doRevertWithComplexReason',
outputs: [],
stateMutability: 'pure',
type: 'function'
},
{
inputs: [],
name: 'doThrow',
outputs: [],
stateMutability: 'pure',
type: 'function'
},
{
inputs: [],
name: 'doThrowAndModify',
outputs: [],
stateMutability: 'nonpayable',
type: 'function'
}
];

// eslint-disable-next-line max-len
export const MATCHERS_BYTECODE = '608060405234801561001057600080fd5b50610446806100206000396000f3fe608060405234801561001057600080fd5b506004361061009e5760003560e01c8063afc874d211610066578063afc874d2146100d5578063b217ca11146100df578063be3e2e60146100e9578063eacf8a57146100f3578063fff78f9c146100fd5761009e565b806301236db4146100a35780632f576f20146100ad578063841caf38146100b7578063a6f34dcb146100c1578063af9b5739146100cb575b600080fd5b6100ab610107565b005b6100b5610185565b005b6100bf610187565b005b6100c96101a1565b005b6100d3610227565b005b6100dd610278565b005b6100e76102e6565b005b6100f16102f8565b005b6100fb61036e565b005b6101056103e4565b005b600160008082825401925050819055506040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600c8152602001807f526576657274206361757365000000000000000000000000000000000000000081525060200191505060405180910390fd5b565b60016000808282540192505081905550600061019f57fe5b565b600160008082825401925050819055506000610225576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600d8152602001807f526571756972652063617573650000000000000000000000000000000000000081525060200191505060405180910390fd5b565b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260228152602001806103ef6022913960400191505060405180910390fd5b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600c8152602001807f526576657274206361757365000000000000000000000000000000000000000081525060200191505060405180910390fd5b60016000808282540192505081905550565b600161036c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260108152602001807f4e6576657220746f206265207365656e0000000000000000000000000000000081525060200191505060405180910390fd5b565b60006103e2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600d8152602001807f526571756972652063617573650000000000000000000000000000000000000081525060200191505060405180910390fd5b565b60006103ec57fe5b56fe52657665727420636175736520287769746820636f6d706c657820726561736f6e29a26469706673582212202c86634aa4c6cf8a67f1325a400b5ccc8e1376865e933bf14efaa0f187772cac64736f6c63430006000033';
export const MATCHERS_BYTECODE = '608060405234801561001057600080fd5b50610671806100206000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c8063af9b573911610071578063af9b5739146100e0578063afc874d2146100ea578063b217ca11146100f4578063be3e2e60146100fe578063eacf8a5714610108578063fff78f9c14610112576100a9565b806301236db4146100ae5780632f576f20146100b8578063841caf38146100c25780639817185d146100cc578063a6f34dcb146100d6575b600080fd5b6100b661011c565b005b6100c0610170565b005b6100ca610172565b005b6100d461019c565b005b6100de6101b2565b005b6100e861020e565b005b6100f2610249565b005b6100fc610284565b005b61010661029f565b005b6101106102e2565b005b61011a610325565b005b600160008082825461012e9190610453565b925050819055506040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610167906103c2565b60405180910390fd5b565b60016000808282546101849190610453565b92505081905550600061019a576101996104e4565b5b565b6000808160016101ac91906104a9565b90505050565b60016000808282546101c49190610453565b92505081905550600061020c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610203906103e2565b60405180910390fd5b565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161024090610402565b60405180910390fd5b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161027b906103c2565b60405180910390fd5b60016000808282546102969190610453565b92505081905550565b60016102e0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102d790610422565b60405180910390fd5b565b6000610323576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161031a906103e2565b60405180910390fd5b565b6000610334576103336104e4565b5b565b6000610343600c83610442565b915061034e82610571565b602082019050919050565b6000610366600d83610442565b91506103718261059a565b602082019050919050565b6000610389602283610442565b9150610394826105c3565b604082019050919050565b60006103ac601083610442565b91506103b782610612565b602082019050919050565b600060208201905081810360008301526103db81610336565b9050919050565b600060208201905081810360008301526103fb81610359565b9050919050565b6000602082019050818103600083015261041b8161037c565b9050919050565b6000602082019050818103600083015261043b8161039f565b9050919050565b600082825260208201905092915050565b600061045e826104da565b9150610469836104da565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0382111561049e5761049d610513565b5b828201905092915050565b60006104b4826104da565b91506104bf836104da565b9250826104cf576104ce610542565b5b828204905092915050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052600160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b7f5265766572742063617573650000000000000000000000000000000000000000600082015250565b7f5265717569726520636175736500000000000000000000000000000000000000600082015250565b7f52657665727420636175736520287769746820636f6d706c657820726561736f60008201527f6e29000000000000000000000000000000000000000000000000000000000000602082015250565b7f4e6576657220746f206265207365656e0000000000000000000000000000000060008201525056fea26469706673582212200d6f653037fb208b2c580e808f575116267849837dff0eb0b0e67eb37bed45f464736f6c63430008070033';
4 changes: 4 additions & 0 deletions waffle-chai/test/matchers/revertedWithTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,8 @@ export const revertedWithTest = (provider: MockProvider) => {
expect(Promise.reject('Always reject')).to.be.revertedWith('Always reject')
).to.be.eventually.rejected;
});

it('Handle panic error', async () => {
await expect(matchers.doPanic()).to.be.revertedWith('panic code 0x12');
});
};
9 changes: 0 additions & 9 deletions waffle-hardhat/contracts/Panic.sol

This file was deleted.

26 changes: 8 additions & 18 deletions waffle-hardhat/test/reverted.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {expect} from 'chai';
import {MockProvider} from 'ethereum-waffle';
import {revertedTest, revertedWithTest} from '@ethereum-waffle/chai/test';
import {ContractFactory} from 'ethers';
import Panic from '../build/contracts/Panic.sol/Panic.json';
import CustomError from '../build/contracts/CustomError.sol/Matchers.json';

describe('INTEGRATION: Matchers: reverted', () => {
Expand All @@ -24,22 +23,13 @@ describe('INTEGRATION: Matchers: revertedWith', () => {
});

revertedWithTest(provider);
});

it('Panic code', async () => {
await waffle.provider.send('hardhat_reset', []);
const wallets = waffle.provider.getWallets();
const wallet = wallets[0];
const factory = new ContractFactory(Panic.abi, Panic.bytecode, wallet);
const panicContract = await factory.deploy();
await expect(panicContract.panic()).to.be.revertedWith('panic code 0x32');
});

it('Handle custom error', async () => {
await waffle.provider.send('hardhat_reset', []);
const wallets = waffle.provider.getWallets();
const wallet = wallets[0];
const factory = new ContractFactory(CustomError.abi, CustomError.bytecode, wallet);
const matchers = await factory.deploy();
await expect(matchers.doRevertWithCustomError()).to.be.revertedWith('CustomError(0)');
it('Handle custom error', async () => {
await waffle.provider.send('hardhat_reset', []);
const wallets = waffle.provider.getWallets();
const wallet = wallets[0];
const factory = new ContractFactory(CustomError.abi, CustomError.bytecode, wallet);
const matchers = await factory.deploy();
await expect(matchers.doRevertWithCustomError()).to.be.revertedWith('CustomError(0)');
});
});
46 changes: 42 additions & 4 deletions waffle-provider/src/revertString.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,61 @@ import {toUtf8String} from 'ethers/lib/utils';
import {Provider} from 'ganache';
import {log} from './log';

const getHardhatErrorString = (callRevertError: any) => {
const tryDecode = (error: any) => {
const stackTrace = error?.stackTrace;
const errorBuffer = stackTrace?.[stackTrace.length - 1].message?.value;
if (errorBuffer) {
return '0x' + errorBuffer.toString('hex');
}
};

return tryDecode(callRevertError) ?? tryDecode(callRevertError.error);
};

const getGanacheErrorString = (callRevertError: any) => {
return callRevertError?.error?.data;
};

/* eslint-disable no-control-regex */

/**
* Decodes a revert string from a failed call/query that reverts on chain.
* @param callRevertError The error catched from performing a reverting call (query)
*/
export const decodeRevertString = (callRevertError: any): string => {
const errorString: string | undefined =
getHardhatErrorString(callRevertError) ??
getGanacheErrorString(callRevertError);

if (errorString === undefined) {
return '';
}

/**
* https://ethereum.stackexchange.com/a/66173
* Numeric.toHexString(Hash.sha3("Error(string)".getBytes())).substring(0, 10)
*/
const errorMethodId = '0x08c379a0';
const errorString: string | undefined = callRevertError?.error?.data;
if (errorString.startsWith(errorMethodId)) {
return toUtf8String('0x' + errorString.substring(138))
.replace(/\x00/g, ''); // Trim null characters.
}

const panicCodeId = '0x4e487b71';
if (errorString.startsWith(panicCodeId)) {
let panicCode = parseInt(errorString.substring(panicCodeId.length), 16).toString(16);
if (panicCode.length % 2 !== 0) {
panicCode = '0' + panicCode;
}

if (['00', '01'].includes(panicCode)) {
return ''; // For backwards compatibility;
}
return 'panic code 0x' + panicCode;
}

if (!errorString?.startsWith(errorMethodId)) return '';
return toUtf8String('0x' + errorString.substring(138))
.replace(/\x00/g, ''); // Trim null characters.
return '';
};

export const appendRevertString = async (etherProvider: providers.Web3Provider, receipt: any) => {
Expand Down

0 comments on commit 4622881

Please sign in to comment.