7

I am trying to unit test a Solidity Library contract function using truffle and web3. My simplified Library contract looks like this:

pragma solidity >=0.5.16;
pragma experimental ABIEncoderV2;

library BrickLib {
    struct ECSignature {
        uint8 v;
    }

    function checkSig(ECSignature memory sig)
    public pure returns(bool) {
        return true;
    }
}

I am attempting to call that function using web3/truffle in my unit test as follows:

const v = 1
await brickLib.checkSig.call({ v })

When this call is made, I get the following error:

Error: Returned error: VM Exception while processing transaction: revert

The same error also appears in the remix UI when I try to call the checkSig method from the user interface by deploying the library contract and entering "[1]" in the parameter. The exact error in Remix is:

call to BrickLib.checkSig errored: VM error: revert.
revert  The transaction has been reverted to the initial state.
Note: The called function should be payable if you send value and the value you send should be less than your current balance.  Debug the transaction to get more information. 

However, any of the following modifications to the contract rectify the issue:

  1. Changing the contract from a library to a contract.
  2. Removing the memory parameter completely, even if there are other non-memory parameters present.
  3. Calling the checkSig function from within another contract that is importing the library contract.
  4. Changing the data type of the memory parameter from a struct to a standard type such as bytes memory.

I suspect this all has to do with how Solidity calls library methods and that the call method changes when memory arguments are introduced, but it is not exactly clear what is happening. Maybe I need to use a delegatecall instead of a regular call?

As I am writing unit tests for my contract library in Javascript, I would prefer not to have to deploy a second contract using the library in order to test it. I would also prefer to write my tests in Javascript and not Solidity. I'd rather also maintain the use of a struct, as my real code is more complex and structs assist in maintainability.

2
  • 2
    +1 for a very-well written question! Commented May 24, 2020 at 8:41
  • After some testing it has a strange behavior, the same function called from a contract will work, even calling the library from a contract will work. It is just that calling the library directly with a struct param fails, but calling with a uint will work.
    – Ismael
    Commented May 24, 2020 at 19:26

2 Answers 2

0

Selectors for library functions and external functions are calculated differently:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import "hardhat/console.sol";

struct ECSignature {
    uint8 v;
}
library BrickLib {
    function checkSig(ECSignature memory sig) public pure returns(bool) {
        return true;
    }
}
contract testcontract {
    function checkSig(ECSignature memory sig) public pure returns(bool) {
        return true;
    }
    constructor() {
        console.logBytes4(BrickLib.checkSig.selector); // 0xfb476b23
        console.logBytes4(bytes4(keccak256(bytes("checkSig(ECSignature)")))); // 0xfb476b23
        console.logBytes4(this.checkSig.selector);     // 0x7a8bd55f
        console.logBytes4(bytes4(keccak256(bytes("checkSig((uint8))")))); // 0x7a8bd55f
    }
}
0

What about a simple approach?

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import "hardhat/console.sol";

library BrickLib {

    struct ECSignature {
        uint8 v;
    }

    function checkSig(ECSignature memory /* sig */) public pure returns (bool) {
        return true;
    }
}

contract BrickTest {

    function testCheckSig(BrickLib.ECSignature memory sig) public pure returns (bool) {
        return BrickLib.checkSig(sig);
    }
}

Hope it helps.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.