Das Generieren von Token in einem MiniMe-Token über den Standardkaufvertrag löst einen ungültigen Opcode-Fehler aus

Ich baue einen Crowdsale basierend auf einem MiniMeToken auf. Beim Testen bin ich auf ein wirklich seltsames Problem gestoßen.

Das vollständige Repository für ausführbaren Code finden Sie unter https://github.com/roderik/truffle-issue Starten testrpcund ausführen truffle test, um zu reproduzieren.

Dieser Test erstellt für jeden Test eine neue Instanz des Tokens und des Verkaufs. Das Generieren von Token direkt auf dem Token funktioniert einwandfrei. Das Senden von Geldern an den Kaufvertrag schlägt jedoch mit einem ungültigen Opcode-Fehler beim Generieren von Token fehl.

const MiniMeTokenFactory = artifacts.require('MiniMeTokenFactory');
const Campaign = artifacts.require('Campaign');
const MultiSigWallet = artifacts.require('MultiSigWallet');
const MyToken = artifacts.require('MyToken');

const timetravel = s => {
  return new Promise((resolve, reject) => {
    web3.currentProvider.sendAsync(
      {
        jsonrpc: '2.0',
        method: 'evm_increaseTime',
        params: [s],
        id: new Date().getTime(),
      },
      function(err) {
        if (err) return reject(err);
        resolve();
      }
    );
  });
};

contract('Campaign', function(accounts) {
  let factory;
  let token;
  let wallet;
  let sale;

  const startTime = 1505750400; // 09/18/2017 @ 4:00pm (UTC) = 5:00pm (CET)
  const endTime = 1508169600; // 10/16/2017 @ 4:00pm (UTC) = 5:00pm (CET)

  beforeEach(async () => {
    factory = await MiniMeTokenFactory.new();
    wallet = await MultiSigWallet.new(
      [
        accounts[7], // account_index: 7
        accounts[8], // account_index: 8
        accounts[9], // account_index: 9
      ],
      2
    );
    token = await MyToken.new(factory.address);
    sale = await Campaign.new(
      startTime,
      endTime,
      28125000000000000000000,
      wallet.address,
      token.address
    );
  });

  it('should return correct balances after generation', async function() {
    await token.generateTokens(accounts[1], 100);
    const totalSupply = await token.totalSupply();
    assert.equal(totalSupply.toNumber(), 100);
  });

  it('should work when trying to send ether during the sale', async function() {
    await token.changeController(sale.address);
    const { timestamp } = web3.eth.getBlock('latest');
    const travelTime = startTime - timestamp + 60; // 60 seconds after the start of the sale
    await timetravel(travelTime);
    web3.eth.sendTransaction({
      from: accounts[0],
      to: sale.address,
      value: web3.toWei(1, 'ether'),
    });
    const totalSupply = await token.totalSupply();
    assert.equal(totalSupply.toNumber(), 1200);
    const totalCollected = await sale.totalCollected;
    assert.equal(totalCollected.toNumber(), 1200);
    const balance0 = await token.balanceOf(accounts[0]);
    assert.equal(balance0.toNumber(), 1200);
  });
});

Das mehrstündige Debuggen brachte mich zu den genauen Zeilen, die fehlschlagen, den Aufrufen, die updateValueAtNowbei generateTokens fehlschlagen

/// @notice Generates `_amount` tokens that are assigned to `_owner`
/// @param _owner The address that will be assigned the new tokens
/// @param _amount The quantity of tokens generated
/// @return True if the tokens are generated correctly
function generateTokens(address _owner, uint _amount
) onlyController returns (bool) {
    uint curTotalSupply = totalSupply();
    require(curTotalSupply + _amount >= curTotalSupply); // Check for overflow
    uint previousBalanceTo = balanceOf(_owner);
    require(previousBalanceTo + _amount >= previousBalanceTo); // Check for overflow
    updateValueAtNow(totalSupplyHistory, curTotalSupply + _amount);
    updateValueAtNow(balances[_owner], previousBalanceTo + _amount);
    Transfer(0, _owner, _amount);
    return true;
}

Die eigentlichen Zeilen, die unterbrochen werden, sind die beiden Setter-Aufrufe auf newCheckpoint

function updateValueAtNow(Checkpoint[] storage checkpoints, uint _value
) internal  {
    if ((checkpoints.length == 0)
    || (checkpoints[checkpoints.length -1].fromBlock < block.number)) {
           Checkpoint storage newCheckPoint = checkpoints[ checkpoints.length++ ];
           newCheckPoint.fromBlock =  uint128(block.number); // THIS ONE
           newCheckPoint.value = uint128(_value); // AND THIS ONE
       } else {
           Checkpoint storage oldCheckPoint = checkpoints[checkpoints.length-1];
           oldCheckPoint.value = uint128(_value);
       }
}

Das Hinzufügen des storageSchlüsselworts ist ziemlich neu, aber das Entfernen hat nicht geholfen.

Da der Direktruf funktioniert und über einen Vertrag nicht, scheint es da einen feinen Unterschied zu geben.

Irgendwelche Gedanken?

Antworten (1)

Nun, dank Anton von https://mothership.cx konnte ich es herausfinden, durch das Hinzufügen gas: 300000zum sendTransaction-Aufruf im Test funktioniert es.

Obwohl überraschend, da ich erwarten würde, dass es einen Gasfehler ausgelöst hat.