GSNRecipient doesn't work with solidity-coverage

Compiling contracts normally via truffle compile --all or oz compile works fine. However, when measuring test coverage with solidity-coverage, I cannot go past beyond the compile step.

:computer: Environment

  • @openzeppelin/contracts-ethereum-package 2.2.3
  • @openzeppelin/contracts-ethereum-package 2.5.2
  • ethereumjs-testrpc-sc 6.5.1-sc.0
  • solidity 0.5.10
  • solidity-coverage 0.6.5
  • truffle 5.035

:memo:Details

I suspect the problem is that solidity-coverage modifies the contract methods to add its own events and this messes up with the way GSNRecipient expects to be inherited.

I get these compilation errors:

Error: CompileError: /Users/paulrberg/Projects/Sablier/Sablier/packages/payroll/coverageEnv/contracts/Payroll.sol:190:5: TypeError: Overriding function changes state mutability from "view" to "nonpayable".
    function acceptRelayedCall(
    ^ (Relevant source part starts here and spans across multiple lines).
@openzeppelin/contracts-ethereum-package/contracts/GSN/bouncers/GSNBouncerSignature.sol:22:5: Overridden function is here:
    function acceptRelayedCall(
    ^ (Relevant source part starts here and spans across multiple lines).
,/Users/paulrberg/Projects/Sablier/Sablier/packages/payroll/coverageEnv/contracts/Payroll.sol:190:5: TypeError: Overriding function changes state mutability from "view" to "nonpayable".
    function acceptRelayedCall(
    ^ (Relevant source part starts here and spans across multiple lines).
@openzeppelin/contracts-ethereum-package/contracts/GSN/IRelayRecipient.sol:12:5: Overridden function is here:
    function acceptRelayedCall(
    ^ (Relevant source part starts here and spans across multiple lines).

:1234: Code to reproduce

How I implemented acceptRelayedCall:

function acceptRelayedCall(
    address relay,
    address from,
    bytes calldata encodedFunction,
    uint256 transactionFee,
    uint256 gasPrice,
    uint256 gasLimit,
    uint256 _nonce,
    bytes calldata approvalData,
    uint256
) external view returns (uint256, bytes memory) {
    bytes memory blob = abi.encodePacked(
        relay,
        from,
        encodedFunction,
        transactionFee,
        gasPrice,
        gasLimit,
        _nonce, // Prevents replays on RelayHub
        getHubAddr(), // Prevents replays in multiple RelayHubs
        address(this) // Prevents replays in multiple recipients
    );
    if (keccak256(blob).toEthSignedMessageHash().recover(approvalData) == owner()) {
        return _approveRelayedCall();
    } else {
        return _rejectRelayedCall(uint256(GSNBouncerSignatureErrorCodes.INVALID_SIGNER));
    }
}

See the whole contract for more details.

1 Like

Hi @PaulRBerg,

I can reproduce the error using Truffle and GSN

Setup

mkdir gsncoverage
cd gsncoverage
npm init -y
npm i truffle
npm i @openzeppelin/contracts@next
npx truffle init

Counter.sol

pragma solidity ^0.5.0;

import "@openzeppelin/contracts/GSN/GSNRecipient.sol";

contract Counter is GSNRecipient {
  uint256 public value;

  function increase() public {
    value += 1;
  }

  function acceptRelayedCall(
    address relay,
    address from,
    bytes calldata encodedFunction,
    uint256 transactionFee,
    uint256 gasPrice,
    uint256 gasLimit,
    uint256 nonce,
    bytes calldata approvalData,
    uint256 maxPossibleCharge
  ) external view returns (uint256, bytes memory) {
    return _approveRelayedCall();
  }
}

Counter.test.js

const Counter = artifacts.require('Counter');

contract('Counter', ([_, other]) => {
    it('should increment', async () => {
        const counter = await Counter.new();
        await counter.increase({ from: other });
        const value = await counter.value();
        assert.equal(value, 1, 'The value was not 1.');
    });
});

Add coverage

Using solidity-coverage ^0.7.0-beta.0

npm i solidity-coverage@beta

Add coverage network to truffle-config.js

    coverage: {
      host: 'localhost',
      network_id: '*', // eslint-disable-line camelcase
      port: 8555,
      gas: 0xfffffffffff,
      gasPrice: 0x01,
    },

Run coverage

npx solidity-coverage

Error:

Error: CompileError: /mnt/c/Users/andre/Documents/projects/forum/gsncoverage/coverageEnv/contracts/Counter.sol:21:3: TypeError: Overriding function changes state mutability from "view" to "nonpayable".
  function acceptRelayedCall(
  ^ (Relevant source part starts here and spans across multiple lines).
@openzeppelin/contracts/GSN/IRelayRecipient.sol:12:5: Overridden function is here:
    function acceptRelayedCall(
    ^ (Relevant source part starts here and spans across multiple lines).

Compilation failed. See above.

Update

I made it work after Christopher Gewecke from the solidity-coverage team was so kind to make a PR to the sablier monorepo.

  Contract: Counter
    ✓ should increment (140ms)


  1 passing (158ms)

--------------|----------|----------|----------|----------|----------------|
File          |  % Stmts | % Branch |  % Funcs |  % Lines |Uncovered Lines |
--------------|----------|----------|----------|----------|----------------|
 contracts/   |       50 |      100 |       50 |       50 |                |
  Counter.sol |       50 |      100 |       50 |       50 |             23 |
--------------|----------|----------|----------|----------|----------------|
All files     |       50 |      100 |       50 |       50 |                |
--------------|----------|----------|----------|----------|----------------|

Istanbul coverage reports generated
Cleaning up...
Shutting down ganache-core

The issue does indeed exist in solidity coverage versions “0.6.5” and below, but everything works fine with “^0.7.0.beta.0”. You just have to use it as a truffle plugin:

// truffle-config.js

module.exports = {
  ...
  networks: {
    development: {
      host: "127.0.0.1",
      network_id: "*",
      port: 8545
    }
  },
  plugins: ["solidity-coverage"],
  ...
};

And then spin up a ganache-cli instance in another window and run this in the terminal:

npx truffle run coverage

I guess it would be worth it to add a warning in the README for the older versions of solidity-coverage?

1 Like

Ohh, interesting! We also have a couple issues with solidity-coverage in the OZ Contracts repo, but those are related to gas costs introduced by the instrumentation.

There is a very thorough plan to improve the coverage tool that will fix most if not all of these issues, so we figured it made more sense to wait until the newer version was done instead of trying to get the current one working for 100% of the codebase.

1 Like

Hi @PaulRBerg,

Thanks for the information. I added solidity-coverage as a truffle plugin to my sample project, ran npx truffle run coverage and was able to generate a coverage report.

I have updated the guide to specify adding as a truffle plugin: Test smart contracts like a rockstar

FYI: @obernardovieira

2 Likes

No worries!

  1. I looked on that guide, you can safely remove the “coverage” network in truffle-config.js (see below)
  2. There’s another command towards the bottom that needs to be updated: npx solidity-coverage should be npx truffle run coverage
coverage: {
    host: 'localhost',
    network_id: '*', // eslint-disable-line camelcase
    port: 8555,
    gas: 0xfffffffffff,
    gasPrice: 0x01,
}

(solidity-coverage works with the default “development” network)

1 Like

Hi @PaulRBerg,

Thank you. Great information, I will update the guide tomorrow.