VM Exception while processing transaction: out of gas
Asked Answered
V

1

5

I am using testrpc, web3 1.0 and solidity to build a simple Dapp, but I'm always getting this error and I can't find what is wrong. Please help.

My javascript file:

const Web3 = require('web3');
const fs = require('fs');

const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));

const code = fs.readFileSync('Voting.sol').toString();
const solc = require('solc');
const compiledCode = solc.compile(code);

// deploy contract
const abiDefinition = JSON.parse(compiledCode.contracts[':Voting'].interface);
const VotingContract = new web3.eth.Contract(abiDefinition);
const byteCode = compiledCode.contracts[':Voting'].bytecode;
const deployedContract = VotingContract
.deploy({data: byteCode, arguments: [['a','b','c']]})
.send({
  from: '0x386fd5fbe3804f24b35477f06aa78a178ce021bd',
  gas: 4700000,
  gasPrice: '2000000000'
}, function(error, transactionHash) {})
.on('error', function(error){})
.on('transactionHash', function(transactionHash){})
.on('receipt', function(receipt){
   console.log(receipt.contractAddress);
})
.then(function(newContractInstance) {
  newContractInstance.methods.getList().call({from: '0x386fd5fbe3804f24b35477f06aa78a178ce021bd'}).then(console.log);
});

My contract file:

pragma solidity ^0.4.11;
// We have to specify what version of compiler this code will compile with

contract Voting {
  /* mapping field below is equivalent to an associative array or hash.
  The key of the mapping is candidate name stored as type bytes32 and value is
  an unsigned integer to store the vote count
  */

  mapping (bytes32 => uint8) public votesReceived;

  /* Solidity doesn't let you pass in an array of strings in the constructor (yet).
  We will use an array of bytes32 instead to store the list of candidates
  */

  bytes32[] public candidateList;

  /* This is the constructor which will be called once when you
  deploy the contract to the blockchain. When we deploy the contract,
  we will pass an array of candidates who will be contesting in the election
  */
  function Voting(bytes32[] candidateNames) {
    candidateList = candidateNames;
  }

  function getList() returns (bytes32[]) {
    return candidateList;
  }

  // This function returns the total votes a candidate has received so far
  function totalVotesFor(bytes32 candidate) returns (uint8) {
    require(validCandidate(candidate) == false);
    return votesReceived[candidate];
  }

  // This function increments the vote count for the specified candidate. This
  // is equivalent to casting a vote
  function voteForCandidate(bytes32 candidate) {
    require(validCandidate(candidate) == false);
    votesReceived[candidate] += 1;
  }

  function validCandidate(bytes32 candidate) returns (bool) {
    for(uint i = 0; i < candidateList.length; i++) {
      if (candidateList[i] == candidate) {
        return true;
      }
    }
    return false;
  }
}

Also, I'm starting the testrpc using the following command:

testrpc --account="0xce2ddf7d4509856c2b7256d002c004db6e34eeb19b37cee04f7b493d2b89306d, 2000000000000000000000000000000"

Any help would be appreciated.

Viscous answered 7/8, 2017 at 3:31 Comment(0)
H
8

You should not be using gas to call a getter method. Remember that reading from blockchain is free - it is the writing data that costs money (gas), because the writes have to be verified and reach consensus.

So, your getter methods should be marked with constant attribute, e.g.

function getList() constant returns (bytes32[]) {
  return candidateList;
}

Second of all, you don't even need a getter for candidateList since the property can be accessed directly, e.g. newContractInstance.candidateList()

Third, you should use a mapping instead of an array, for example mapping(bytes32 => bool) public validCandidates because your contract only cares if a candidate is valid. You really, really don't want to have loops in your contract because you want your function calls to have constant gas cost. If you use loops, you will run out of gas.

Putting all of the above together you get a contract like this

pragma solidity ^0.4.11;
// We have to specify what version of compiler this code will compile with

contract Voting {
  /* mapping field below is equivalent to an associative array or hash.
  The key of the mapping is candidate name stored as type bytes32 and value is
  an unsigned integer to store the vote count
  */

  mapping (bytes32 => uint8) public votesReceived;
  mapping (bytes32 => bool) public validCandidates;

  /* This is the constructor which will be called once when you
  deploy the contract to the blockchain. When we deploy the contract,
  we will pass an array of candidates who will be contesting in the election
  */
  function Voting(bytes32[] candidateList) {
    for (uint i = 0; i < candidateList.length; i++) {
      validCandidates[candidateList[i]] = true;
    }
  }

  // This function returns the total votes a candidate has received so far
  function totalVotesFor(bytes32 candidate) constant returns (uint8) {
    return votesReceived[candidate];
  }

  // This function increments the vote count for the specified candidate. This
  // is equivalent to casting a vote
  function voteForCandidate(bytes32 candidate) onlyForValidCandidate(candidate) {
    votesReceived[candidate] += 1;
  }

  function isValidCandidate(bytes32 candidate) constant returns (bool)  {
    return validCandidates[candidate];
  }

  modifier onlyForValidCandidate(bytes32 candidate) {
    require(isValidCandidate(candidate));
    _;
  }
}
Hals answered 8/8, 2017 at 18:4 Comment(2)
I know it took me a while :D why is a loop in the constructor better than in another function?Gausman
In this particular contract you have to have the loop in the constructor. If it was in another function, anyone could call it unless you implemented permissions. Loops aren't bad, per se but you have to be aware about the gas costHals

© 2022 - 2024 — McMap. All rights reserved.