La generación de tokens en un token MiniMe a través del contrato de venta predeterminado arroja un error de código de operación no válido

Estoy construyendo una venta colectiva basada en un MiniMeToken. Durante las pruebas me encontré con un problema realmente extraño.

El repositorio de código ejecutable completo se puede encontrar en https://github.com/roderik/truffle-issue Iniciar testrpcy ejecutar truffle testpara reproducir.

Esta prueba crea una nueva instancia del token y la venta para cada prueba. La generación de tokens directamente en el token funciona perfectamente. Sin embargo, el envío de fondos al contrato de venta falla con un error de código de operación no válido al generar tokens.

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

La depuración durante varias horas me llevó a las líneas exactas que fallan, las llamadas fallan updateValueAtNowen generateTokens

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

Las líneas reales que se rompen son las dos llamadas de establecimiento en 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);
       }
}

La adición de la storagepalabra clave es bastante nueva, pero eliminarla no ayudó.

Dado que la llamada directa funciona, y a través de un contrato no, parece haber una diferencia sutil allí.

¿Alguna idea?

Respuestas (1)

Bueno, gracias a Anton de https://mothership.cx pude resolverlo, agregar gas: 300000a la llamada sendTransaction en la prueba hace que funcione.

Aunque sorprendente, ya que esperaría que arrojara un error de falta de gas.