Assume this simple contract:
pragma solidity 0.6.3;
contract InvalidOpcode {
enum Dir {UP, LEFT, DOWN, RIGHT}
Dir public currentDir;
function changeDir(Dir newDir) public {
require(uint8(newDir) <= uint8(Dir.RIGHT), "Out of range");
currentDir = newDir;
}
}
The exposed interface of the function accepts an uint8 as argument. When run on Remix with a value of 4 (or greater) as the input, the tx will throw with this error:
transact to EnumTest.changeDir errored: VM error: invalid opcode.
invalid opcode
The execution might have thrown.
Debug the transaction to get more information.
Debugging reveals that the require statement is not reached (and is thus useless). The invalid opcode occurs while accesing the argument. The debugger shows the local variable newDir: INVALID_ENUM<4> enum
.
I think the transaction then throws and stops using gas, but I am not sure!
When I run the same thing in a truffle environment, the transaction will exit with status 0
.
truffleAssert.fails(instance.changeDir(4),"status 0")
will catch it, but all gas will be consumed in the transaction! From what I know, it is not possible to debug further (on opcode level) in truffle.
My questions are:
- Does this transaction indeed consume all gas?
- If so, is there a way to prevent this?
- Is there a way to revert the transaction with a meaningful message?
- What is the best practice to expose an Enum argument to the public interface?
Edit:
One way to potentially solve the problem is to change the argument to an uint8:
contract FixedInvalidOpcode {
enum Dir {UP, LEFT, DOWN, RIGHT}
Dir public currentDir;
function changeDir(uint8 newDir) public {
require(newDir <= uint8(Dir.RIGHT), "Out of range");
currentDir = Dir(newDir);
}
}
This behaves as expected. But is the type conversion Dir(newDir)
safe and is this the recommended way to expose the function?
I would still like to get into what is happening if an enum is directly exposed to the interface and if that is something that can be improved in the compiler. In my opinion, the first contract above should revert with a failing assert.