0

I'm building a crowdsale based on a MiniMeToken. During testing I ran into a really weird issue.

Full runnable code repository can be found at https://github.com/roderik/truffle-issue Start testrpc and run truffle test to reproduce.

This tests creates a new instance of the token and sale for each test. Generating tokens directly on the token works perfectly. Sending funds to the sale contract however fails with an invalid opcode error when generating 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);
  });
});

Debugging for several hours got me to the exact lines that fail, the calls to updateValueAtNow fail at 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;
}

The actual lines that breaks are the two setter calls on 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);
       }
}

The addition of the storage keyword is a fairly new one, but removing it did not help.

Since the direct call works, and via a contract it does not, there seems to be a sublte difference there.

Any thoughts?

1

Well, thanks to Anton from https://mothership.cx I was able to figure it out, adding gas: 300000 to the sendTransaction call in the test makes it work.

Though surprising since i would expect it to have thrown an out of gas error.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.