30

I am writing below a simple contract which stores all results of questionnaires for each ID.

contract answer{
  mapping(address => mapping(string => bool)) voters;

  struct qList {
    uint count; //The number of respondents
    mapping(address => mapping(uint => uint)) answer;
  }

  mapping(string => qList) questionnaires;

  function vote(string ID, uint qNum, uint ans) returns (bool) {
    if(voters[msg.sender][ID]) throw;
    voters[msg.sender][ID] = true;
    questionnaires[ID].count += 1;
    questionnaires[ID].answer[msg.sender][qNum] = ans;
    return true;
  }

  function getNumResult(string ID) constant returns (uint res) {
    return questionnaires[ID].count;
  }
}

The function vote can be called and mined successfully, however I cannot get the return value of vote.

Appreciate it if someone would advise the cause of this and solution to get the return value of function with arguments.

Ismael
  • 30,570
  • 21
  • 53
  • 96
A. Take
  • 425
  • 1
  • 4
  • 10

4 Answers4

48

It is not currently possible to return values from functions which modify the blockchain. To receive a return value, you can mark functions as "pure" or "view".

For state-changing functions, the only way to "return" information is by using Solidity Events, which coalesce as LOG opcodes in the Ethereum Virtual Machine.

Paul Razvan Berg
  • 17,902
  • 6
  • 73
  • 143
Taylor Gerring
  • 9,580
  • 4
  • 34
  • 39
11

Summary

Your code runs as expected when I execute it through the geth console. If it does not work for you, try increase the gas you send with your transactions.

As @Taylor Gerring has stated in his answer, you may not be able to get the results from your vote() function, but your code seems to work OK.

If you want the result from your vote() function, which in your example is a check as to whether a person has voted before, you already have this data in your voters data.



The Details

I've taken your source code and just changed the class name from answer to Answer, and converted your comment from // to /*...*/:

contract Answer {
  mapping(address => mapping(string => bool)) voters;

  struct qList {
    uint count; /* The number of respondents */
    mapping(address => mapping(uint => uint)) answer;
  }

  mapping(string => qList) questionnaires;

  function vote(string ID, uint qNum, uint ans) returns (bool) {
    if (voters[msg.sender][ID]) throw;
    voters[msg.sender][ID] = true;
    questionnaires[ID].count += 1;
    questionnaires[ID].answer[msg.sender][qNum] = ans;
    return true;
  }

  function getNumResult(string ID) constant returns (uint res) {
    return questionnaires[ID].count;
  }
}

I've stripped out the CR-LF from the code and collapsed the spaces, and executed the following statement in geth:

> var answerSource='contract Answer { mapping(address => mapping(string => bool)) voters; struct qList { uint count; /* The number of respondents */ mapping(address => mapping(uint => uint)) answer; } mapping(string => qList) questionnaires; function vote(string ID, uint qNum, uint ans) returns (bool) { if(voters[msg.sender][ID]) throw; voters[msg.sender][ID] = true; questionnaires[ID].count += 1; questionnaires[ID].answer[msg.sender][qNum] = ans; return true; } function getNumResult(string ID) constant returns (uint res) { return questionnaires[ID].count; }}'
undefined

I then compiled your code and inserted it into my dev blockchain:

> var answerCompiled = web3.eth.compile.solidity(answerSource);
undefined

> var answerContract = web3.eth.contract(answerCompiled.Answer.info.abiDefinition);
undefined 

> var answer = answerContract.new({
    from:web3.eth.accounts[0], 
    data: answerCompiled.Answer.code, gas: 2000000}, 
    function(e, contract) {
      if (!e) {
        if(!contract.address) {
          console.log("Contract transaction send: TransactionHash: " 
            + contract.transactionHash + " waiting to be mined...");
        } else {
          console.log("Contract mined! Address: " + contract.address);
          console.log(contract);
        }
    }
})

Contract transaction send: TransactionHash: 0x5b5eb3c6d2a4b43eff4444b71b762911ddc72e239d1d495b6bec7b2e6a738df0 waiting to be mined...

I waited for the contract to be mined and got the following message:

Contract mined! Address: 0xe51ac93e4c28206f0f0296e5b6d66daf0a917bc3
[object Object]

Checked getNumResult():

> answer.getNumResult("idOne")
0

Voted:

> answer.vote("idOne", 1, 1, eth.accounts[0], {
  from:web3.eth.accounts[0], 
  data: answerCompiled.Answer.code,
  gas: 1000000
});
"0xfcbf47472733c0922552aa617fc7cb1226c346edc022fbe09ea521dfb75f7699"

Waited for the transaction to be mined and checked the transaction:

> eth.getTransaction("0xfcbf47472733c0922552aa617fc7cb1226c346edc022fbe09ea521dfb75f7699")
{
  ...
  blockNumber: 6567,
  ...
}

Checked getNumResult():

> answer.getNumResult("idOne")
1

Sent another vote with a different ID:

> answer.vote("idTwo", 2, 1, eth.accounts[0], {
  from:web3.eth.accounts[0], 
  data: answerCompiled.Answer.code,
  gas: 1000000
});

Checked the results:

> answer.getNumResult("idTwo")
1

Sent another vote from the same account with the same ID:

> answer.vote("idOne", 2, 1, eth.accounts[0], {
  from:web3.eth.accounts[0], 
  data: answerCompiled.Answer.code,
  gas: 1000000
});
"0xbbf9db6eb7c02571948002f56e3a7c56b6c7f55c2a4bbc70a244bb2afbf44e1f"

And I noticed the following error:

PC 00000366: JUMP GAS: 976744 COST: 8 ERROR: invalid jump destination (PUSH1) 2

The error above must have been generated from the throw statement in your code if the same ID is voted from the same account:

if (voters[msg.sender][ID]) throw;

I then sent another vote from my second account:

> answer.vote("idOne", 2, 1, eth.accounts[1], {
  from:web3.eth.accounts[1], 
  data: answerCompiled.Answer.code,
  gas: 1000000
});

And the results were updated as expected:

> answer.getNumResult("idOne")
2
BokkyPooBah
  • 40,274
  • 14
  • 123
  • 193
7

With non-constant function vote, you can only get the transaction hash back immediately because the transaction may never get mined. Or it could take several blocks as indicated by "Waiting for transaction to be mined..." Recommend to check: What is the difference between a transaction and a call?

Events

Events are needed to get the "return value" of vote.

Example of how to add and trigger an event:

contract answer{
  // ...
  event VoteEvent(string ID, bool returnValue);

  function vote(string ID, uint qNum, uint ans) returns (bool) {
    // ...
    VoteEvent(ID, true);
    return true;
  }
}

See Contract Events for the different ways to watch for and get event data using web3.js.

eth
  • 85,679
  • 53
  • 285
  • 406
  • 1
    So is there any sense in writing "returns (bool)" in "vote()" function? – Aniket Jun 01 '17 at 08:18
  • 3
    @A.K. If vote might return false, for example if you've already voted, the return value could be helpful so that other contracts can do things like if (!someContract.vote("ak", 7, 2)) {...}. Return values can be used by other contracts, but web3.js can only use events. If it's always return true, then the bool can probably be removed and the event simplified to something like UserVoted(string ID). – eth Jun 07 '17 at 01:30
  • got it completely – Aniket Jun 07 '17 at 05:35
0

There is an untidy trick you can use during your development phase. When you have a function, myRealFunc(), whose return value you cannot access, have the myRealFunc() function write to a separate (and created just for this purpose) contract variable, myReturnValue, the value it is about to return. Then create a new function (again created just for this development phase) myDebugFunction() which does nothing other than return the value, my ReturnValue, stored by the previous myRealFunc() function call. Then from your truffle console or wherever you make the call to myDebugFunction and you get your previously hidden return value, myReturnValue. I find I have to make the call like this : instance.myDebugFunction.call() where instance has previously been setup as an instance of your smart contract.

This hack break several paradigms of good programming practice such as not modifying code in order to test it. You also have to remember to remove this hack code prior to deployment to a main net. That said, I have found it useful for my personal debug / development purposes.

Nick Fitz
  • 21
  • 2