被调用的函数突然需要支付

Called function suddenly needs to be payable

我有一个 public uint 变量表示 'which round' 它是一个推进回合并在回合推进的同时进行处理的函数:

       uint public round;

       function completeRound() public inPaused() inRound() {
            if (round == 6) {
              // win
          } else {
            reduceByHalf();
            round.add(1);
          }
          
       }

如果我运行这个在remix中,它运行s 4次然后第5次一直失败,说明突然需要支付一个功能:

交易到 Playingwithsmartcontracts.completeRound 错误:VM 错误:还原。 revert 交易已经恢复到初始状态。注意:如果您发送价值,并且您发送的价值应小于您的当前余额,则调用的函数应该是可支付的。调试交易以获取更多信息。

如果我在调用 reduceByHalf 的地方注释掉 round.add(1),代码会整天工作。我可以在 Remix 中无限期地点击它而不会出错。

奇怪的是,这开始是一个跟踪回合的枚举,并且有同样的问题。在推进枚举时,我可以在上述失败之前进行 5 次,并将其注释掉使一切正常。

reduceByHalf 代码似乎不是罪魁祸首,但它显示在下面以防它与问题有关:


    struct Foo {
        address owner;
        uint mintedRound;
        uint winningRound;
    }
    struct FooOwner {
        uint[] foos;
        uint totalWinningFoos;
    }

    uint[][5] roundFoos;
    uint[][5] roundWinners;

    mapping(uint => Foo) public winningFoos;
    mapping(address => FooOwner) public fooOwners;

    uint totalWinningFoos;

    function shuffleFoos (uint256[] memory _array) internal view returns(uint[] memory){
        uint[] memory clone = cloneArray(_array, _array.length);
    
        for (uint256 i = 0; i < clone.length; i++) {
            uint256 n = i + uint256(keccak256(abi.encodePacked(msg.sender, block.timestamp))) % (clone.length - i);
            uint256 temp = clone[n];
            clone[n] = clone[i];
            clone[i] = temp;
        }
        
        return clone;
    }
    
    function cloneArray(uint256[] memory _array, uint256 _length) internal pure returns (uint256[] memory) {
        uint256[] memory array = new uint256[](_length);
        for (uint256 i = 0; i < _length; i++) {
            array[i] = _array[i];
        }
        return array;
    }

    function reduceByHalf() internal  {
        uint[] memory clone = shuffleFoos(roundFoos[round]);
      
        uint halfLength = 0;

        halfLength = roundFoos[round].length.div(2);
        for (uint w = 0; w < halfLength; w++) {
           
           uint fooId = clone[w];
           
           roundWinners[round].push(fooId); 
           winningFoos[round].winningRound = round;
           
           address fooOwner = winningFoos[fooId].owner;
           
           fooOwners[fooOwner].totalWinningFoos = fooOwners[fooOwner].totalWinningFoos.add(1);    
        }
        
        totalWinningFoos = totalWinningFoos.add(halfLength);
    }

据我所知,我没有发送价值,也不确定为什么它只认为我在交易执行时发送价值 5.

谁能帮我理解 Remix/Solidity 的疯狂之处?

我完全不能理解某些东西,但它看起来像是关于数字 5 的东西......我可以将回合推进到 6,但是当我将 uint 值设置为 5 时,我就开始看到这些问题....太奇怪了....

The transaction has been reverted to the initial state.

这是您案例中错误消息的重要部分。

Note: The called function should be payable if you send value

这只是一个注释,可能是因为这种组合经常发生。但是由于您的函数和交易不发送任何值,因此它不适用于您的情况。


round.add(1);

这个(失败的)片段表明,应该有一个库 used for uint, but it's not defined. I'm gonna go with the SameMath 库,因为 .add() 函数名称和在 uint 上的使用。但理论上,它可以是任何库,SafeMath 只是这种情况下最可能的选择。

注意 round.add(1);(使用 SafeMath)returns round 的值增加 1,但它不会在任何地方存储(增加的)值.这看起来像是打字错误,实际用法应该是 round = round.add(1);

您的代码没有显示 SafeMath 库的任何用法,也没有显示 Solidity 版本,所以我将把我的回答分为 3 个部分。

  1. 您正在使用 Solidity 0.8+。

    不需要 SameMath,因为整数溢出在较低级别处理,您可以安全地替换

    // even with correctly imported SafeMath, it doesn't update the stored value
    round.add(1);
    

    // updates the stored value
    round++;
    
  2. 您使用的是 Solidity 0.7 或更早版本,以及 uint256(不是 uint

    的 SafeMath

    更改定义

    uint public round;
    

    uint256 public round;
    

    这样,SafeMath 将用于 round 并且允许使用函数 .add().

    请注意,您可能还想存储增量值,请参阅上面示例中的粗体段落。

  3. 您使用的是 Solidity 0.7 或更早版本,并且根本没有使用 SafeMath。

    您需要import the SafeMath library然后进行第 2 点中描述的更改。