la transferencia de tokens falla en el contrato de venta colectiva

Tengo un contrato simbólico y un contrato de crowdsale. Cuando trato de enviar ether al contrato de crowdsale desde accounts[3](o cualquier otro realmente), aparece un error de código de operación:

Error: Excepción de máquina virtual al procesar la transacción: código de operación no válido.

Estoy probando con Truffle. He enviado Testtokens a la dirección de crowdsalecontract. He enviado Ether a la dirección del contrato del token. Todo en el contrato de token funciona cuando interactúo con él directamente. Intenté muchas cosas, pero sigo atascado en este punto. Cualquier ayuda muy apreciada. Ejecutar estos contratos en testrpc

código de migración de trufas:

module.exports = function(deployer,accounts) {
  var sendaccounts = web3.eth.accounts;
  var sendTo = sendaccounts[0];

  deployer.deploy(TestToken).then(function() {
    return deployer.deploy(TestCrowdSale, sendTo, TestToken.address);
  });

código web3 que genera el error:

var sender = accounts[3];
var receiver = crowdsaleAddress;

var amount = web3.toWei(0.5, 'ether');

web3.eth.sendTransaction({from:sender, to:receiver, value: amount});

en el código de solidez de crowdsale, si esta línea está comentada, el error no ocurre:

tokenReward.transfer(msg.sender, amount);

código de solidez del token:

pragma solidity ^0.4.13;

contract TestToken {
  /* Public variables of the token */
  string public constant name = 'TestToken';
  string public constant symbol = 'TEST';
  uint256 public constant decimals = 6;
  address owner;
  address contractaddress;
  //Swap value in % of ETH
  uint256 public swapValue = 50;
  // One hundred million coins, each divided to up to 10^decimals units.
  uint256 private constant totalSupply = 100000000 * (10 ** decimals);
  uint256 public constant TestWei = 100000000000000;
  bool crowdsaleClosed = false;

  /* This creates an array with all balances */
  mapping (address => uint256) public balanceOf;
  mapping (address => mapping (address => uint256)) public allowance;

  /* This generates a public event on the blockchain that will notify clients */
  event Transfer(address indexed from, address indexed to, uint256 value);
  //event LogWithdrawal(address receiver, uint amount);

  modifier onlyOwner() {
    // Only owner is allowed to do this action.
    if (msg.sender != owner) {
      revert();
    }
    _;
  }

  /* Initializes contract with initial supply tokens to the creator of the contract */
  function TestToken() {
    contractaddress = address(this);
    balanceOf[msg.sender] = totalSupply / 2;              // Give the creator half of all tokens
    balanceOf[contractaddress] = totalSupply / 2;              // Give the contract half of all tokens
    owner = msg.sender;
  }

  /*ERC20*/

  /* Internal transfer, only can be called by this contract */
  function _transfer(address _from, address _to, uint256 _value) internal {
  //function _transfer(address _from, address _to, uint _value) public {
    require (_to != 0x0);                               // Prevent transfer to 0x0 address. Use burn() instead
    require (balanceOf[_from] > _value);                // Check if the sender has enough
    require (balanceOf[_to] + _value > balanceOf[_to]); // Check for overflows
    balanceOf[_from] -= _value;                         // Subtract from the sender
    balanceOf[_to] += _value;                            // Add the same to the recipient
    Transfer(_from, _to, _value);
  }

  /// @notice Send `_value` tokens to `_to` from your account
  /// @param _to The address of the recipient
  /// @param _value the amount to send
  function transfer(address _to, uint256 _value) public returns (bool success) {
    if(_to == contractaddress ){
      swap(msg.sender, _value);
    }
    else{
      _transfer(msg.sender, _to, _value);
      return true;
    }
  }

  //deposit collateral ETH
  function depositFunds() public payable onlyOwner
  {
  }

  /*fallback function*/
  function () public payable onlyOwner{}

  //raise the minimum eth payout, minimum will always be 50% ETH
  function raiseSwap(uint _newSwap)
  onlyOwner
  {
    //swap can only be more than 50%
    //to guarantee there will always be at least a 50% payout
    if(_newSwap > 50){
      swapValue = _newSwap;
    }
  }

  //swap for swapvalue % ether (minimum 50)
  function swap(address _sender,uint256 _value) internal returns(bool success){
    //must be even integer
    uint even = _value / 2;
    require((2 * even) == _value);

    uint TestRefund = (_value / 100) * swapValue;
    uint ethRefund = TestRefund * TestWei;
    // Test is tranferred to contract balance
    uint256 originalBalance = balanceOf[_sender];
    // all necessary checks happen inside _transfer function
    _transfer(_sender, owner, _value);
    // check if tokens are returned succesfully
    if(originalBalance == balanceOf[_sender] + _value){
      //transfer swapvalue % of eth per Test back to sender
      _sender.transfer(ethRefund);
      return true;
    }
  }

  function getBalance(address addr) public returns(uint256) {
    return balanceOf[addr];
  }

  function getEtherBalance() public returns(uint256) {
    //return contract ether balance;
    return this.balance;
  }

  function getOwner() public returns(address) {
    return owner;
  }

  /* exchange ETH for Test with the contract address */
  function buyTest() public payable returns (bool){
    require(msg.value > 0);
    uint even = msg.value / 2;
    require((2 * even) == msg.value);
    uint _value;
    if(swapValue > 100){
      uint amount = (msg.value / swapValue) * 100;
      _value = amount / TestWei;
    }
    else{
      //Receive 10000 Test per 1 ETH
      _value = msg.value / TestWei;
    }

    _transfer(contractaddress, msg.sender, _value);
    return true;
  }

  function closeCrowdSale() public
  onlyOwner{
    crowdsaleClosed = true;
  }
}

código de venta masiva:

pragma solidity ^0.4.13;
import './TestToken.sol';

contract TestCrowdSale {
  address public beneficiary;
  address public crowdsaleAddress;
  //debugging
  address public tokenAddress;
  uint public fundingGoal;
  uint public amountRaised;
  uint public deadline;
  uint public initiation;
  uint public price;
  uint256 public constant TestWei = 100000000000000;

  TestToken public tokenReward;
  mapping(address => uint256) public balanceOf;
  bool fundingGoalReached = false;
  bool crowdsaleClosed = false;

  event GoalReached(address beneficiary, uint amountRaised);
  event FundTransfer(address backer, uint amount, bool isContribution);

  /**
   * Constructor function
   *
   * Setup the owner
   */
  function TestCrowdSale(
    address ifSuccessfulSendTo,
    address addressOfTokenUsedAsReward
  ) {
    beneficiary = ifSuccessfulSendTo;
    fundingGoal = 10 * 1 ether;
    deadline = now + 100 * 1 minutes;
    initiation = now;
    price = TestWei;
    crowdsaleAddress = address(this);
    tokenAddress = addressOfTokenUsedAsReward;
    tokenReward = TestToken(addressOfTokenUsedAsReward);
  }

  /**
   * Fallback function
   *
   * The function without name is the default function that is called whenever anyone sends funds to a contract
   */
  function () public payable {
    //require(!crowdsaleClosed);
    uint256 amount = msg.value / price;
    balanceOf[msg.sender] += amount;
    amountRaised += amount;
    tokenReward.transfer(msg.sender, amount);
    FundTransfer(msg.sender, amount, true);
  }

  modifier afterDeadline() { if (now >= deadline) _; }

  /**
   * Check if goal was reached
   *
   * Checks if the goal or time limit has been reached and ends the campaign
   */
  function checkGoalReached() afterDeadline {
    if (amountRaised >= fundingGoal){
      fundingGoalReached = true;
      GoalReached(beneficiary, amountRaised);
    }
    crowdsaleClosed = true;
  }

  /**
   * Withdraw the funds
   *
   * Checks to see if goal or time limit has been reached, and if so, and the funding goal was reached,
   * sends the entire amount to the beneficiary. If goal was not reached, each contributor can withdraw
   * the amount they contributed.
   */
  function safeWithdrawal() afterDeadline {
    if (!fundingGoalReached) {
      uint amount = balanceOf[msg.sender];
      balanceOf[msg.sender] = 0;
      if (amount > 0) {
        if (msg.sender.send(amount)) {
          FundTransfer(msg.sender, amount, false);
        } else {
          balanceOf[msg.sender] = amount;
        }
      }
    }

    if (fundingGoalReached && beneficiary == msg.sender) {
      if (beneficiary.send(amountRaised)) {
        FundTransfer(beneficiary, amountRaised, false);
      } else {
        //If we fail to send the funds to beneficiary, unlock funders balance
        fundingGoalReached = false;
      }
    }
  }
}
El error fue causado por una cantidad insuficiente de gas para completar la transacción. Vea los comentarios a continuación.

Respuestas (1)

Estoy un 90% seguro de que está fallando en esta línea:

require (saldoDe[_desde] > _valor); // Comprobar si el remitente tiene suficiente

La función alternativa de TestCrowdSale está llamando

tokenReward.transfer(msg.sender, cantidad);

Entonces TestToken .transfer(address _to, uint256 _value) está llamando

_transfer(mensaje.remitente, _a, _valor);

Esto significa que balanceOf[_from]siempre va a ser balanceOf[TestCrowdSale]. Si TestCrowdSaleno tiene un saldo mayor que msg.value / price, su transacción fallará.

Recomiendo crear una nueva función en TestToken , por ejemplo subscribe. transferestá diseñado solo para eso, transferir, por lo que esto no es apropiado para un nuevo suscriptor de su crowdsale.

No, el error persiste, incluso cuando puse ese cheque en un comentario. balanceOf[TestCrowdSale] tiene suficiente cantidad de tokens. Sin embargo, el saldo de éter del contrato de venta colectiva es 0. ¿Podría ser este el problema? cuando tokenReward.transfer(msg.sender, cantidad); se llama, ¿qué dirección debe pagar esta transacción?
Envió 15 ether a la dirección de crowdsale. El error aún persiste.
Con todas las declaraciones requeridas sin comentar, el error aún persistía, pero recibí otro error, 'se quedó sin gasolina', así que probé la cantidad de gasolina necesaria, y en mi llamada de javascript web3, agregué: gas: 1000000 Funciona ahora ... ahora agregará las declaraciones require de nuevo y ver si todavía funciona.
Problema resuelto, no había suficiente gasolina para completar la transacción. No puedo creer que yo no haya pensado en esto antes. ¡Gracias por la ayuda! @nyusternie