为什么我不能在 Solidity 中更改合约状态?

Why can't I change contract state in Solidity?

我 运行 遇到了一个测试问题,该测试似乎表明 Solidity 无法更改合约存储变量的值。

这是 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]
        );
    });
});

这是 Solidity 代码:

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;
  }
}

下面是测试运行程序输出的相关部分:

  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)

我的第一个想法是存在某种竞争条件:所有三个调用都在它们中的任何一个有机会递增它之前获取初始值。但我的阅读表明,以太坊将操作序列化,因此你无法在单个合约中进行比赛。此外,我尝试在对 returnAndIncrement() 的调用之间插入五秒钟的暂停,以试图打破任何现有的竞争,但对结果没有影响。

我的第二个想法是我的测试配置存在一些根本性的错误,所以无论实际发生了什么,我都只是返回零。所以我开始 currentValue 在 100 而不是 0,正如你在上面看到的那样;那不是问题。

我的第三个想法是,当我认为我正在将 currentValue 的值复制到 value 时,我实际上在做的是使 value 成为对值的引用currentValue,所以当我递增 currentValue 时,我也在递增 value。但如果是这样的话,我会得到 [101, 102, 103] 而不是 [100, 100, 100]

你的赋值有点乱,看代码注释:

uint32 private currentValue = 100;

function returnAndIncrement () public returns (uint32 value) {
    // 1. memory variable `value` is now 100
    value = currentValue;         

    // 2. storage variable `currentValue` is now 101
    currentValue = currentValue + 1;

    // 3. you're returning the `value` from memory (which has value 100)
    return value;                     
}

根据上下文猜测,您可能想return存储中的增量值。

最简单的方法是:

uint32 private currentValue = 100;

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

编辑:或者一点 Solidity 魔法。 :) 这实际上具有稍微便宜的 gas 成本(28432 相对于上面示例中的 29284),因为访问(昂贵的)存储的机会较少。

uint32 private currentValue = 100;

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

要更改智能合约的状态,您需要发送交易而不是调用。

变化:

subject.returnAndIncrement.call ()

收件人:

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

有关详细信息,请查看 web3js doc

但是send transaction的return不是你要找的值,你可能需要查看日志才能得到这个值;