0

Consider the following sample Truffle project:

.
├── contracts
│   ├── Foo.sol
│   └── Migrations.sol
├── migrations
│   ├── 1_initial_migration.js
│   └── 2_foo.js
├── test
│   └── Foo.test.js
└── truffle.js

Contents of contracts/Foo.sol:

pragma solidity ^0.4.21;
contract Foo {
  enum Bar {zero, one, two, three}
  function getOne() public pure returns (Bar) {
    return Bar.one;
  }
}

Contents of test/Foo.test.js:

let Foo = artifacts.require("./Foo.sol");
contract("", async (accounts) => {
    it("", async () => {
        let instance = await Foo.deployed();
        let one = await instance.getOne();
        assert.equal(one, 1);
    });
});

Let's test our project:

$ truffle test
Compiling ./contracts/Foo.sol...
Compiling ./contracts/Migrations.sol...


  Contract: 
    ✓ 


  1 passing (27ms)

As you can see, everything is perfect. Now, just for the sake of science, let's turn our contract into a library in contracts/Foo.sol:

pragma solidity ^0.4.21;
library Foo {
  enum Bar {zero, one, two, three}
  function getOne() public pure returns (Bar) {
    return Bar.one;
  }
}

And let's run the test again:

$ truffle test
Compiling ./contracts/Foo.sol...
Compiling ./contracts/Migrations.sol...


  Contract: 
    1) 
    > No events were emitted


  0 passing (32ms)
  1 failing

  1) Contract:  :
     Error: invalid solidity type!: Foo.Bar
      at SolidityCoder._requireType (/home/emilio/.nvm/versions/node/v9.8.0/lib/node_modules/truffle/build/webpack:/~/web3/lib/solidity/coder.js:61:1)
      at /home/emilio/.nvm/versions/node/v9.8.0/lib/node_modules/truffle/build/webpack:/~/web3/lib/solidity/coder.js:231:1
      at Array.map (<anonymous>)
      at SolidityCoder.getSolidityTypes (/home/emilio/.nvm/versions/node/v9.8.0/lib/node_modules/truffle/build/webpack:/~/web3/lib/solidity/coder.js:230:1)
      at SolidityCoder.decodeParams (/home/emilio/.nvm/versions/node/v9.8.0/lib/node_modules/truffle/build/webpack:/~/web3/lib/solidity/coder.js:203:1)
      at SolidityFunction.unpackOutput (/home/emilio/.nvm/versions/node/v9.8.0/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/function.js:113:1)
      at Object.callback (/home/emilio/.nvm/versions/node/v9.8.0/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/function.js:145:1)
      at /home/emilio/.nvm/versions/node/v9.8.0/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/method.js:142:1
      at /home/emilio/.nvm/versions/node/v9.8.0/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/requestmanager.js:89:1
      at /home/emilio/.nvm/versions/node/v9.8.0/lib/node_modules/truffle/build/webpack:/~/truffle-provider/wrapper.js:134:1
      at XMLHttpRequest.request.onreadystatechange (/home/emilio/.nvm/versions/node/v9.8.0/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/httpprovider.js:128:1)
      at XMLHttpRequestEventTarget.dispatchEvent (/home/emilio/.nvm/versions/node/v9.8.0/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:64:1)
      at XMLHttpRequest._setReadyState (/home/emilio/.nvm/versions/node/v9.8.0/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:354:1)
      at XMLHttpRequest._onHttpResponseEnd (/home/emilio/.nvm/versions/node/v9.8.0/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:509:1)
      at IncomingMessage.<anonymous> (/home/emilio/.nvm/versions/node/v9.8.0/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:469:1)
      at endReadableNT (_stream_readable.js:1101:12)
      at process._tickCallback (internal/process/next_tick.js:114:19)

Why?

First I said to myself: sure, it's because libraries can't have state variables. But then I realized an enum is not a state variable; it's a type definition. The documentation uses structs extensively, which I would say are similar in nature to enums. Also, although not shown in this example, when referencing the library enum from a contract, it compiles well, which might suggest it's a web3js issue.

So what's going on? Is this by design? What's the best practice here? Define the enum in every contract that uses the library? Turn the enum into a struct? What am I missing?

Reproducibility info

For the sake of reproducibility I include the (rather trivial) contents of migrations/2_foo.js:

let Foo = artifacts.require("./Foo.sol");
module.exports = (deployer) => {
    deployer.deploy(Foo);
};

And truffle.js:

module.exports = {
    networks: {
        development: {
            host: "127.0.0.1",
            port: 8545,
            network_id: "*" // Match any network id
        }
    }
};

The remaining files, as you know, are created by truffle init.

I also run ganache-cli in a separate terminal as my testnet.

Here are the versions of the development tools I'm using:

$ truffle version
Truffle v4.1.5 (core: 4.1.5)
Solidity v0.4.21 (solc-js)
$ ganache-cli version
Ganache CLI v6.1.0 (ganache-core: 2.1.0)
1

If you cast it to an integer type it will work properly.

function getOne() public pure returns (uint) {
  return uint(Bar.one);
}

I don't know why its not working without casting it explicitly. This is another working example but requires a contract

pragma solidity 0.4.21;

library Foo {
  enum Bar {zero, one, two, three}
  function getOne() public pure returns (Bar) {
    return Bar.one;
  }
}

contract Bar {
    function getOne() public pure returns(Foo.Bar) {
        return Foo.getOne();
    }
}

edit:

this a comment on the solidity documentation.

// Since enum types are not part of the ABI, the signature of "getChoice"
// will automatically be changed to "getChoice() returns (uint8)"
// for all matters external to Solidity. The integer type used is just
// large enough to hold all enum values, i.e. if you have more values,
// `uint16` will be used and so on.

somehow when the ABI is created for the library is generated wrongly:

"abi": [
  {
    "constant": true,
    "inputs": [],
    "name": "getOne",
    "outputs": [
      {
        "name": "",
        "type": "Foo.Bar"
      }
    ],
    "payable": false,
    "stateMutability": "pure",
    "type": "function"
  }
],

while when its created for a contract it work fine:

"abi": [
  {
    "constant": true,
    "inputs": [],
    "name": "getOne",
    "outputs": [
      {
        "name": "",
        "type": "uint8"
      }
    ],
    "payable": false,
    "stateMutability": "pure",
    "type": "function"
  }
],

so I might say it looks like a bug of the compiler or is like this by design, I can't answer to this question. But since libraries are meant to be used for internal calls an not external it might a design choice.

Anyway my suggestion is to use a contract to perform this kind of call. You might have functions that requires storage and the only way to access it is to use a contract. Libraries are useful to reuse code and perform common task across contracts.

  • Nice answer, thanks. Do you think this is by design or a web3js bug? Any suggestions as to what the best practice should be in this regard? – e18r Apr 12 '18 at 15:29
  • 1
    please look my edit, I've improved my answer – mirg Apr 12 '18 at 15:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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