From cbac9a1830ac8d1212036cdf97ee0a125ef922fa Mon Sep 17 00:00:00 2001 From: Nicolas Villanueva Date: Wed, 6 Sep 2023 19:35:12 +0100 Subject: [PATCH] feature: display the revert reason when calls or gas estimations fail (#98) --- e2e-tests/contracts/Greeter.sol | 5 ++-- e2e-tests/package.json | 1 + e2e-tests/test/main.test.ts | 42 ++++++++++++++++++++++++++++++++- e2e-tests/yarn.lock | 5 ++++ src/node.rs | 24 +++++++++++-------- 5 files changed, 64 insertions(+), 13 deletions(-) diff --git a/e2e-tests/contracts/Greeter.sol b/e2e-tests/contracts/Greeter.sol index dd8764f5..33f2aa58 100644 --- a/e2e-tests/contracts/Greeter.sol +++ b/e2e-tests/contracts/Greeter.sol @@ -1,9 +1,10 @@ //SPDX-License-Identifier: Unlicense pragma solidity ^0.8.0; +import "@openzeppelin/contracts/access/Ownable.sol"; import "hardhat/console.sol"; -contract Greeter { +contract Greeter is Ownable { string private greeting; constructor(string memory _greeting) { @@ -14,7 +15,7 @@ contract Greeter { return greeting; } - function setGreeting(string memory _greeting) public { + function setGreeting(string memory _greeting) public onlyOwner { console.log("setGreeting called"); console.log(_greeting); require( diff --git a/e2e-tests/package.json b/e2e-tests/package.json index b5523e04..8b89167e 100644 --- a/e2e-tests/package.json +++ b/e2e-tests/package.json @@ -8,6 +8,7 @@ "@matterlabs/hardhat-zksync-deploy": "^0.6.3", "@matterlabs/hardhat-zksync-solc": "^0.4.0", "@nomiclabs/hardhat-etherscan": "^3.1.7", + "@openzeppelin/contracts": "^4.9.3", "@types/chai": "^4.3.4", "@types/mocha": "^10.0.1", "chai": "^4.3.7", diff --git a/e2e-tests/test/main.test.ts b/e2e-tests/test/main.test.ts index 8e10afc4..598c5b8a 100644 --- a/e2e-tests/test/main.test.ts +++ b/e2e-tests/test/main.test.ts @@ -1,7 +1,8 @@ import { expect } from 'chai'; -import { Wallet, Contract } from 'zksync-web3'; +import { Wallet, Contract, Provider } from 'zksync-web3'; import * as hre from 'hardhat'; import { Deployer } from '@matterlabs/hardhat-zksync-deploy'; +import { ethers } from 'ethers'; const RICH_WALLET_PK = '0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110'; @@ -11,6 +12,19 @@ async function deployGreeter(deployer: Deployer): Promise { return await deployer.deploy(artifact, ['Hi']); } +async function fundAccount( + wallet: ethers.Wallet, + address: string, + amount: string, +) { + await ( + await wallet.sendTransaction({ + to: address, + value: ethers.utils.parseEther(amount), + }) + ).wait(); +} + describe('Greeter', function () { it("Should return the new greeting once it's changed", async function () { const wallet = new Wallet(RICH_WALLET_PK); @@ -26,4 +40,30 @@ describe('Greeter', function () { expect(await greeter.greet()).to.equal('Hola, mundo!'); }); + + it("should prevent non-owners from setting greeting", async function () { + let errorThrown = false; + try { + const provider = new Provider("http://127.0.0.1:8011"); + const wallet = new Wallet(RICH_WALLET_PK, provider); + const deployer = new Deployer(hre, wallet); + + // setup user wallet + const userWallet = Wallet.createRandom().connect(provider); + await fundAccount(wallet, userWallet.address, "3"); + + // deploy Greeter contract + const artifact = await deployer.loadArtifact('Greeter'); + const greeter = await deployer.deploy(artifact, ["Hello, world!"]); + + // should revert + const tx = await greeter.connect(userWallet).setGreeting("Hola, mundo!"); + await tx.wait(); + } catch (e) { + expect(e.message).to.include("Ownable: caller is not the owner"); + errorThrown = true; + } + + expect(errorThrown).to.be.true; + }); }); diff --git a/e2e-tests/yarn.lock b/e2e-tests/yarn.lock index 54265d77..78db55b1 100644 --- a/e2e-tests/yarn.lock +++ b/e2e-tests/yarn.lock @@ -674,6 +674,11 @@ table "^6.8.0" undici "^5.14.0" +"@openzeppelin/contracts@^4.9.3": + version "4.9.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.3.tgz#00d7a8cf35a475b160b3f0293a6403c511099364" + integrity sha512-He3LieZ1pP2TNt5JbkPA4PNT9WC3gOTOlDcFGJW4Le4QKqwmiNJCRt44APfxMxvq7OugU/cqYuPcSBzOw38DAg== + "@scure/base@~1.1.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" diff --git a/src/node.rs b/src/node.rs index bd32a5d7..a22846f6 100644 --- a/src/node.rs +++ b/src/node.rs @@ -430,17 +430,19 @@ impl InMemoryNodeInner { ); println!("{}", format!("\tOverhead: {}", overhead).red()); let message = tx_revert_reason.to_string(); + let pretty_message = format!( + "execution reverted{}{}", + if message.is_empty() { "" } else { ": " }, + message + ); let data = match tx_revert_reason { TxRevertReason::EthCall(vm_revert_reason) => vm_revert_reason.encoded_data(), TxRevertReason::TxReverted(vm_revert_reason) => vm_revert_reason.encoded_data(), _ => vec![], }; + println!("{}", pretty_message.on_red()); Err(into_jsrpc_error(Web3Error::SubmitTransactionError( - format!( - "execution reverted{}{}", - if message.is_empty() { "" } else { ": " }, - message - ), + pretty_message, data, ))) } @@ -1199,6 +1201,11 @@ impl EthNamespaceT for Ok(vm_block_result) => match vm_block_result.full_result.revert_reason { Some(revert) => { let message = revert.revert_reason.to_string(); + let pretty_message = format!( + "execution reverted{}{}", + if message.is_empty() { "" } else { ": " }, + message + ); let data = match revert.revert_reason { TxRevertReason::EthCall(vm_revert_reason) => { vm_revert_reason.encoded_data() @@ -1208,12 +1215,9 @@ impl EthNamespaceT for } _ => vec![], }; + println!("{}", pretty_message.on_red()); Err(into_jsrpc_error(Web3Error::SubmitTransactionError( - format!( - "execution reverted{}{}", - if message.is_empty() { "" } else { ": " }, - message - ), + pretty_message, data, ))) .into_boxed_future()