Truffle Testing – Debugging ‘Returned Error: VM Exception while Processing Transaction: Revert’

contract-debuggingsoliditytruffletruffle-test

I'm using truffle, the ganache AppImage to run a personal blockchain, and have 2 solidity smart contracts Migrations.sol and PassportManager.sol

I'm running a truffle test and getting Error: Returned error: VM Exception while processing transaction: revert so I'm guessing something's wrong with my deployed contract, something is throwing an exception in my deployed code.

How can I debug this?
I'm not using Remix and I'd hate to add MORE tools to my toolbox. Just truffle, ganache and my text editor so far, and I'd like to keep it simple.

Running my tests on JavaScript only creates 4 blocks (4 transactions) on Ganache, and none of those 4, tested with truffle debug <tx-id> brings up any useful info – one of the txes returns the bytecode for deploying PassportManager.sol (which happens every time for some reason!) and the other 3 all have something to do with Migrations.sol.

Here's the tests and code just so you know what's up:

function addIDFileToPassport(address passport_id, bytes32 id_file) public returns (address, bytes32, uint) {
        Passport storage p = user_passports[passport_id]; //Passport is a struct I've declared earlier, like so:
        /* struct Passport { 
                string flair_name;
                mapping(bytes32 => uint) identity_files;
                bytes32[] identity_files_LUT;
       
        }*/
        require(p.controller == msg.sender);
        p.identity_files_LUT.push(id_file);
        p.identity_files[id_file] = 1;
        return (p.controller, p.identity_files_LUT[p.identity_files_LUT.length - 1], p.identity_files[id_file]);
    }

And the failing test:

/* Other tests above */
it("should add an identity file sha256 hash to a controlled passport", () => {
        let this_address = accounts[0];
        let doc_hash = "0x21f3a9de43f07d855f49b946a10c30df432e8af95311435f77daf894216dcd41";
        //let hex_doc_hash = web3.utils.asciiToHex(doc_hash);
        //console.log(hex_doc_hash);
        //let hexArray_doc_hash = web3.utils.hexToBytes(doc_hash);
        //console.log(hexArray_doc_hash);
        //console.log(web3.utils.utf
        
    return PassportManager.deployed()
        .then(instance => {
            meta = instance;
            return meta.addIDFileToPassport.call(this_address, doc_hash);
        })
        .then(returned_tuple => {
            assert.equal(returned_tuple[0], this_address, "Passport controller, passed and returned by PassportManager.addIDFileToPassport(), should match!");
            assert.equal(returned_tuple[1], doc_hash, "Document hash (bytes32), passed and returned by PassportManager.addIDFileToPassport(), should match!");
            assert.equal(returned_tuple[2], 1, "Trust score of newly added doc_hash should be 1!");
        });
    });

Best Answer

To answer and close my own question for anyone coming up on it in the future:

  1. The code going "boom" was inside my Smart Contract, as evident by the VM exception error and the revert thing. I got into the habit of temporarily adding error logging to my require() statements inside solidity, so I managed to track where it was breaking. So, do use require(foo == true, "foo shouldn't be false!"); to try and track things.
  2. My problem was being too tired to read the entire truffle+ganache documentation. Inside truffle tests, calls do not make persistent changes to the blockchain (don't ask me how, but they seem to be dry-runs of a transaction), so my first test was not leaving anything behind. To make changes persist between tests, I actually needed to send a proper Transaction to my ganache blockchain.

That made my changes persist from test 1 to test 2, and I managed to get passing tests.
What I did was:

  • Dry run my tests with call() and test returned results (because Transactions don't actually return results, they return a tx id and you gotta debug that manually)
  • If everything went well with the call, I make a proper transaction by invoking the method directly (in my case, I go from meta.initPassport.call("John Doe"); to meta.initPassport("John Doe", {from: accounts[0]}); - simple, right? I guess not. More info on that special second parameter (on my contract, initPassport() only takes 1 parameter, but we pass 2 on the transaction inside test.js) can be found here: https://www.trufflesuite.com/docs/truffle/getting-started/interacting-with-your-contracts#making-a-transaction
  • My transaction leaves persistent changes to my deployed (migrated) contract, so I move on with test 2, and do calls or transactions there as I please. But at least on test 2 I can now test on previous conditions and results!
Related Topic