Search code examples
tddblockchainethereumsoliditysmartcontracts

Why can't I change contract state in Solidity?


I'm running into problems with a test that seems to indicate that Solidity can't change the value of a contract storage variable.

Here's the test in JavaScript:

const Mystery = artifacts.require ("Mystery");

contract ("Mystery", async accounts => {

    it ("Incrementing performs as intended", async () => {
        const subject = await Mystery.deployed ();

        const firstValue = (await subject.returnAndIncrement.call ()).toNumber ();
        const secondValue = (await subject.returnAndIncrement.call ()).toNumber ();
        const thirdValue = (await subject.returnAndIncrement.call ()).toNumber ();

        assert.equal (
            [firstValue, secondValue, thirdValue],
            [100, 101, 102]
        );
    });
});

Here's the Solidity code:

pragma solidity >=0.4.22 <0.9.0;

contract Mystery {

  uint32 private currentValue = 100;

  function returnAndIncrement () public returns (uint32 value) {
    value = currentValue;
    currentValue = currentValue + 1;
    return value;
  }
}

And here are the relevant portions of the output from the test runner:

  Contract: Mystery
    1) Incrementing performs as intended
    > No events were emitted


  0 passing (993ms)
  1 failing

  1) Contract: Mystery
       Incrementing performs as intended:

      AssertionError: expected [ 100, 100, 100 ] to equal [ 100, 101, 102 ]
      + expected - actual

       [
         100
      -  100
      -  100
      +  101
      +  102
       ]
      
      at Context.it (test/TestMystery.js:12:16)
      at process._tickCallback (internal/process/next_tick.js:68:7)

My first thought was that there was some kind of race condition: that all three invocations were grabbing the initial value before any of them had a chance to increment it. But my reading indicates that Ethereum serializes operations so that you can't get races inside a single contract. Also, I tried inserting five-second pauses between the calls to returnAndIncrement() in an attempt to break any existing races, but there was no effect on the results.

My second thought was that there was something fundamental wrong with the configuration of my test, so that I was just getting zeros back regardless of what was actually happening. So I started the currentValue at 100 instead of 0, as you see above; that's not the issue.

My third thought was that when I think I'm copying the value of currentValue into value, what I'm actually doing is making value a reference to the value of currentValue, so that when I increment currentValue I'm also incrementing value. But if that were the case, I'd be getting [101, 102, 103] back instead of [100, 100, 100].


Solution

  • To change the state of a smart contract you need to send a transaction instead of call.

    Change:

    subject.returnAndIncrement.call ()
    

    To:

      subject.returnAndIncrement.send({..}) // you can pass options such gas, account .. 
    

    For more details look at the web3js doc

    But the return of send transaction is not the value you are looking for, you may need to look in the logs to get the value;