Web3.py: How to use the function inside a deployed contract? [BadFunctionCallOutput]

Created on 17 Apr 2017  路  12Comments  路  Source: ethereum/web3.py

  • Version: 0.15.3
  • Python: 2.7
  • OS: linux

Here is an example to develop a smart contract by python which is written by myself.

Suppose the RPC port of geth node is 8200, and there are at least 2 accounts in keystore.
At least 2 python package are required (web3, py-solc)

sudo pip install web3
sudo pip install py-solc

Input following code into python command console:

source_code = 'contract MyToken {     address issuer;     mapping (address => uint) balances;      event Issue(address account, uint amount);     event Transfer(address from, address to, uint amount);      function MyToken() {         issuer = msg.sender;     }      function issue(address account, uint amount) {         if (msg.sender != issuer) throw;         balances[account] += amount;     }      function transfer(address to, uint amount) {         if (balances[msg.sender] < amount) throw;          balances[msg.sender] -= amount;         balances[to] += amount;          Transfer(msg.sender, to, amount);     }      function getBalance(address account) constant returns (uint) {         return balances[account];     } }'

import web3
obj = web3.Web3(web3.HTTPProvider('http://localhost:8200'))
psn = web3.personal.Personal(obj)
psn.unlockAccount(obj.eth.accounts[0],"123456",1000) #replace 123456 to your Ethereum accounts password

from solc import compile_source
compile_sol = compile_source(source_code)

my_contract = web3.contract.construct_contract_factory(
    web3=obj, 
    abi = compile_sol['<stdin>:MyToken']['abi'],
    code = compile_sol['<stdin>:MyToken']['bin'], 
    code_runtime = compile_sol['<stdin>:MyToken']['bin-runtime'],
    source = source_code
)

my_contract.bytecode = my_contract.code
trans_hash = my_contract.deploy(transaction={'from':obj.eth.accounts[0],'value':120})

The contract should be deploy on the blockchain if this transaction is mined.

But when I try to call the function inside the contract, there are some exceptions occured:

my_contract.call({'to':obj.eth.accounts[0]}).getBalance(obj.eth.accounts[0])

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.7/dist-packages/eth_utils/string.py", line 85, in inner`
    return force_obj_to_text(fn(*args, **kwargs))
  File "/usr/local/lib/python2.7/dist-packages/web3/contract.py", line 779, in call_contract_function
    raise_from(BadFunctionCallOutput(msg), e)
  File "/usr/local/lib/python2.7/dist-packages/web3/contract.py", line 767, in call_contract_function
    output_data = decode_abi(output_types, return_data)
  File "/usr/local/lib/python2.7/dist-packages/eth_abi/abi.py", line 108, in decode_abi
    return decoder(stream)
  File "/usr/local/lib/python2.7/dist-packages/eth_abi/decoding.py", line 102, in __call__
    return self.decode(stream)
  File "/usr/local/lib/python2.7/dist-packages/eth_utils/functional.py", line 22, in inner
    return callback(fn(*args, **kwargs))
  File "/usr/local/lib/python2.7/dist-packages/eth_abi/decoding.py", line 140, in decode
    yield decoder(stream)
  File "/usr/local/lib/python2.7/dist-packages/eth_abi/decoding.py", line 102, in __call__
    return self.decode(stream)
  File "/usr/local/lib/python2.7/dist-packages/eth_abi/decoding.py", line 165, in decode
    raw_data = cls.read_data_from_stream(stream)
  File "/usr/local/lib/python2.7/dist-packages/eth_abi/decoding.py", line 247, in read_data_from_stream
    cls.data_byte_size,
web3.exceptions.BadFunctionCallOutput: Could not decode contract function call getBalance return data 0x for output_types [u'uint256']

How to deal with this problem? Is there any examples to reference?
Thank you very much!

Most helpful comment

Glad to hear it worked.

@pipermerriam May I suggest to put together a step by step tutorial of web3.py, with code snippets of deploying a contract (likely a standard ERC20 one), executing methods, etc. That would be another issue though.

Cute Animal Picture

fullsizerender

All 12 comments

my_contract.call({'to':obj.eth.accounts[0]}).getBalance(obj.eth.accounts[0])

Here seems the account address was passed as to, instead of a contract address.

Assuming we use testrpc and the contract address is available immediately, the code snippet of obtaining a contract address and making a call could be something like this:

trans_hash = my_contract.deploy(...)
txn_receipt = web3.eth.getTransactionReceipt(trans_hash)
contract_address = txn_receipt['contractAddress']
my_contract.call({'to':contract_address}).getBalance(web3.eth.accounts[0])

Also, the arg transaction of the method call is optional (default to None), so if we assign my_contract a proper contract address, we can omit the arg transaction.

my_contract = web3.eth.contract(abi=compile_sol[':MyToken']['abi'], address=contract_address)
my_contract.call().getBalance(web3.eth.accounts[0])

Hope it makes sense to you.

@weipin Thank you very much for your patient answer!

I have tried 2 suggested methods to call the function. Both methods except the same Error:

web3.exceptions.BadFunctionCallOutput: Could not decode contract function call getBalance return data 0x for output_types [u'uint256']

Since the return type of getBalance() function is an unit256 type data, I guess there are some problems to decode return data. Is there any other potential solutions to solve this problem?

@weipin Your suggestion is so useful! After trying another example, I find that the error comes from contract deploy step.

Glad to hear it worked.

@pipermerriam May I suggest to put together a step by step tutorial of web3.py, with code snippets of deploying a contract (likely a standard ERC20 one), executing methods, etc. That would be another issue though.

Cute Animal Picture

fullsizerender

Hello @Gingko925. If you can try the following modified version of your code. Please check your web3 version and update to the latest if you are not on 3.8.0

source_code = 'contract MyToken {     address issuer;     mapping (address => uint) balances;      event Issue(address account, uint amount);     event Transfer(address from, address to, uint amount);      function MyToken() {         issuer = msg.sender;     }      function issue(address account, uint amount) {         if (msg.sender != issuer) throw;         balances[account] += amount;     }      function transfer(address to, uint amount) {         if (balances[msg.sender] < amount) throw;          balances[msg.sender] -= amount;         balances[to] += amount;          Transfer(msg.sender, to, amount);     }      function getBalance(address account) constant returns (uint) {         return balances[account];     } }'

from web3 import Web3  # The `web3.Web3` object is all you should need to import in most normal cases.
web3 = Web3(Web3.HTTPProvider('http://localhost:8200'))
web3.personal.unlockAccount(web3.eth.accounts[0],"123456",1000)

from solc import compile_source
compile_sol = compile_source(source_code)

# If an `address` is not passed into this method it returns a contract factory class.
MyContract = web3.eth.contract(
    abi = compile_sol['<stdin>:MyToken']['abi'],
    bytecode = compile_sol['<stdin>:MyToken']['bin'],   # The keyword `code` has been deprecated.  You should use `bytecode` instead.
    bytecode_runtime = compile_sol['<stdin>:MyToken']['bin-runtime'],  # the keyword `code_runtime` has been deprecated.  You should use `bytecode_runtime` instead.
)

trans_hash = MyContract.deploy(transaction={'from':web3.eth.accounts[0],'value':120})
# wait for mining
trans_receipt = web3.eth.getTransactionReceipt(trans_hash)

# get the contract address
contract_address = trans_receipt['contractAddress']

# now we can instantiate the contract factory to get an instance of the contract.
my_contract = MyContract(contract_address)

# now you should be able to call the contract methods.
my_contract.call().getBalance(obj.eth.accounts[0])

This was of course all typed by hand so there may be some typographical errors. In general this structure should work though. Let me know if it doesn't.

Just wanted to point out the above code example has an issue. There is no "value" defined in the contract.
trans_hash = MyContract.deploy(transaction={'from':web3.eth.accounts[0],'value':120})

it should be:
trans_hash = MyContract.deploy(transaction={'from':web3.eth.accounts[0]})

Also please update the office web3.py doc: http://web3py.readthedocs.io/en/latest/contracts.html. it is not consistent with the instructions here.

Hi folks,

I cannot for the life of me figure out how to run a function inside a deployed contract without deploying the contract myself first.

Hopefully this is considered "inside the scope" of this issue, if not, I am happy to open another issue.

I've got the code in Piper's example running fine, but what I can't figure out is how to execute a method on a contract I do not myself own.

In the example:

trans_hash = MyContract.deploy(transaction={'from':web3.eth.accounts[0],'value':120})
# wait for mining
trans_receipt = web3.eth.getTransactionReceipt(trans_hash)
contract_address = trans_receipt['contractAddress']

It would seem to me that

  1. one would be able to execute a contract that they do not deploy themselves, provided they have the abi, bytecode, (or the upstream solidity file).
  2. it's horribly inefficient to deploy the contract to the chain _every time_ you want to interact with the contract on-chain

but if i try to run pipers example, minus the deploy:

source_code = 'contract MyToken {     address issuer;     mapping (address => uint) balances;      event Issue(address account, uint amount);     event Transfer(address from, address to, uint amount);      function MyToken() {         issuer = msg.sender;     }      function issue(address account, uint amount) {         if (msg.sender != issuer) throw;         balances[account] += amount;     }      function transfer(address to, uint amount) {         if (balances[msg.sender] < amount) throw;          balances[msg.sender] -= amount;         balances[to] += amount;          Transfer(msg.sender, to, amount);     }      function getBalance(address account) constant returns (uint) {         return balances[account];     } }'

from web3 import Web3  # The `web3.Web3` object is all you should need to import in most normal cases.
web3 = Web3(Web3.HTTPProvider('http://localhost:8200'))
web3.personal.unlockAccount(web3.eth.accounts[0],"123456",1000)

from solc import compile_source
compile_sol = compile_source(source_code)

# If an `address` is not passed into this method it returns a contract factory class.
MyContract = web3.eth.contract(
    abi = compile_sol['<stdin>:MyToken']['abi'],
    bytecode = compile_sol['<stdin>:MyToken']['bin'],   # The keyword `code` has been deprecated.  You should use `bytecode` instead.
    bytecode_runtime = compile_sol['<stdin>:MyToken']['bin-runtime'],  # the keyword `code_runtime` has been deprecated.  You should use `bytecode_runtime` instead.
)

#trans_hash = MyContract.deploy(transaction={'from':web3.eth.accounts[0],'value':120})
# wait for mining
#trans_receipt = web3.eth.getTransactionReceipt(trans_hash)

# get the contract address
#contract_address = trans_receipt['contractAddress']
contract_address = 'some_address_which_has_already_been_deployed_to


# now we can instantiate the contract factory to get an instance of the contract.
my_contract = MyContract(contract_address)

# now you should be able to call the contract methods.
my_contract.call().getBalance(obj.eth.accounts[0])

I get an error message:

web3.exceptions.BadFunctionCallOutput: Could not decode contract function call getBalance return data 0x for output_types [u'uint256']

I've poked around the issues board and documentation, and I don't seem to see where this is covered end to end. Can anyone advise?

Thanks
@owocki

I cannot for the life of me figure out how to run a function inside a deployed contract without deploying the contract myself first.

I figured it out. testrpc (which is the RPC provider I am using, instead of localhost:8200 like the demo) resets after after single instanstiation of my script. Thereby, the contract was no longer deployed!

I switched over to a persistent testrpc instance and that did the trick.

Thanks for being my :rubber_ducky:

鉁岋笍 !

Closing as I think this is resolved. I'm a big 馃憤 on having a "this is how you do common contract interactions" section for the documentation. If anyone wanted to write that I'd be grateful. I'm currently swamped with all the other things.

鈿★笍 A tip worth 0.08 ETH has been granted to @pipermerriam for this issue from @owocki. 鈿★笍

Nice work @pipermerriam, check your email for further instructions. | Send a Tip

@owocki I briefly dug through both spam folders in my email accounts and can't find this alleged email.

@pipermerriam looks like it went to your gmail.com address... i just resent it to your pipermerriam dot com one

Was this page helpful?
0 / 5 - 0 ratings