Web3.py: Getting web3.exceptions.BadFunctionCallOutput in private chain

Created on 22 Apr 2018  ·  12Comments  ·  Source: ethereum/web3.py

My environment:

  • Version: 4.1.0
  • Python: 3.6
  • OS: Ubuntu16.04
  • Geth version: 1.8.5-unstable
  • solc version: 0.4.21-develop.2018.3.8

My code:

  • contract code:
pragma solidity ^0.4.16;

interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) external; }

contract TokenERC20 {
    string public name;
    string public symbol;
    uint8 public decimals = 18;  // decimals 可以有的小数点个数,最小的代币单位。18 是建议的默认值
    uint256 public totalSupply;

    // 用mapping保存每个地址对应的余额
    mapping (address => uint256) public balanceOf;
    // 存储对账号的控制
    mapping (address => mapping (address => uint256)) public allowance;

    // 事件,用来通知客户端交易发生
    event Transfer(address indexed from, address indexed to, uint256 value);

    // 事件,用来通知客户端代币被消费
    event Burn(address indexed from, uint256 value);

    /**
        * 初始化构造
        */
    function TokenERC20(uint256 initialSupply, string tokenName, string tokenSymbol) public {
        totalSupply = initialSupply * 10 ** uint256(decimals);  // 供应的份额,份额跟最小的代币单位有关,份额 = 币数 * 10 ** decimals。
        balanceOf[msg.sender] = totalSupply;                // 创建拥有的代币
        name = tokenName;                                   // 代币名称
        symbol = tokenSymbol;                               // 代币符号
    }

    /**
        * 代币交易转移的内部实现
        */
    function _transfer(address _from, address _to, uint _value) internal {
        // 确保目标地址不为0x0,因为0x0地址代表销毁
        require(_to != 0x0);
        // 检查发送者余额
        require(balanceOf[_from] >= _value);
        // 确保转移为正数个
        require(balanceOf[_to] + _value > balanceOf[_to]);

        // 以下用来检查交易,
        uint previousBalances = balanceOf[_from] + balanceOf[_to];
        // Subtract from the sender
        balanceOf[_from] -= _value;
        // Add the same to the recipient
        balanceOf[_to] += _value;
        emit Transfer(_from, _to, _value);

        // 用assert来检查代码逻辑。
        assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
    }

    /**
        *  代币交易转移
        * 从自己(创建交易者)账号发送`_value`个代币到 `_to`账号
        *
        * @param _to 接收者地址
        * @param _value 转移数额
        */
    function transfer(address _to, uint256 _value) public {
        _transfer(msg.sender, _to, _value);
    }

    /**
        * 账号之间代币交易转移
        * @param _from 发送者地址
        * @param _to 接收者地址
        * @param _value 转移数额
        */
    function transferFrom(address _from, address _to, uint256 _value) public payable returns (bool success) {
        require(_value <= allowance[_from][msg.sender]);     // Check allowance
        allowance[_from][msg.sender] -= _value;
        _transfer(_from, _to, _value);
        return true;
    }

    /**
        * 设置某个地址(合约)可以创建交易者名义花费的代币数。
        *
        * 允许发送者`_spender` 花费不多于 `_value` 个代币
        *
        * @param _spender The address authorized to spend
        * @param _value the max amount they can spend
        */
    function approve(address _spender, uint256 _value) public
        returns (bool success) {
            allowance[msg.sender][_spender] = _value;
            return true;
        }

    /**
        * 设置允许一个地址(合约)以我(创建交易者)的名义可最多花费的代币数。
        *
        * @param _spender 被授权的地址(合约)
        * @param _value 最大可花费代币数
        * @param _extraData 发送给合约的附加数据
        */
    function approveAndCall(address _spender, uint256 _value, bytes _extraData)
        public
        returns (bool success) {
        tokenRecipient spender = tokenRecipient(_spender);
        if (approve(_spender, _value)) {
            // 通知合约
            spender.receiveApproval(msg.sender, _value, this, _extraData);
            return true;
        }
    }

    /**
        * 销毁我(创建交易者)账户中指定个代币
        */
    function burn(uint256 _value) public returns (bool success) {
        require(balanceOf[msg.sender] >= _value);   // Check if the sender has enough
        balanceOf[msg.sender] -= _value;            // Subtract from the sender
        totalSupply -= _value;                      // Updates totalSupply
        emit Burn(msg.sender, _value);
        return true;
    }

    /**
        * 销毁用户账户中指定个代币
        *
        * Remove `_value` tokens from the system irreversibly on behalf of `_from`.
        *
        * @param _from the address of the sender
        * @param _value the amount of money to burn
        */
    function burnFrom(address _from, uint256 _value) public returns (bool success) {
        require(balanceOf[_from] >= _value);                // Check if the targeted balance is enough
        require(_value <= allowance[_from][msg.sender]);    // Check allowance
        balanceOf[_from] -= _value;                         // Subtract from the targeted balance
        allowance[_from][msg.sender] -= _value;             // Subtract from the sender's allowance
        totalSupply -= _value;                              // Update totalSupply
        emit Burn(_from, _value);
        return true;
    }
}
  • web3.py:
def deployContract(contractPath, initSupply, tokenName, tokenSymbol):
    """
    编译及部署合约

    :param contractPath: 合约路径
    :param initSupply: 发币量
    :param tokenName: 代币名称
    :param tokenSymbol: 代币符号
    """
    compiled_sol = compile_files([contractPath])  # Compiled source code
    contract_interface = compiled_sol['./contracts/TokenERC20.sol:TokenERC20']

    # If an `address` is not passed into this method it returns a contract factory class.
    MyContract = w3.eth.contract(
        abi=contract_interface['abi'],
        bytecode=contract_interface['bin'],
        bytecode_runtime=contract_interface['bin-runtime']
    )

    # Get transaction hash from deployed contract
    tx_hash = MyContract.deploy(
        transaction={'from': w3.eth.accounts[0], 'gas': 410000},
        args=[initSupply,
              tokenName,
              tokenSymbol]
    )

    # Get tx receipt to get contract address
    time.sleep(10)
    tx_receipt = w3.eth.getTransactionReceipt(tx_hash)
    contract_address = tx_receipt['contractAddress']

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

    pdb.set_trace()
    return my_contract

When I deploy the contract, my private chain has start mining

The question is:

  • I can't use the function of the contract neither call() nor transact() when I run the contract in my private chain:
    image
    But the contract has the address, represents that it was deployed successfully.
    image
    But why I can't invoke the function of the contract?
    I need help, Thank you!

  • By the way, when I use geth console to instantiate the contract that web.py deployed use contract.at(). It looks like this:
    image
    image

But I can't invoke the function, too!
image

Most helpful comment

But why?

There's too much code right now for us to look through and debug visually for you.

Here is a debugging technique that you can try: reduce the code in the contract and python by removing features, until the deploy starts working. That should help you identify an offending piece. Then post the difference between the working and non-working code. Also, you can try posting that to https://ethereum.stackexchange.com/questions/tagged/web3.py , which has a bigger crew of people who might be able to help faster.

All 12 comments

By the way, the w3 is that I used IPCProvider:
w3 = Web3(IPCProvider('./data/geth.ipc'))

Now, I can use the transact function. But can't use the call() function:
image
image

But why? I need help! Who can help me! Thank you very much!

can you run w3.eth.getCode(tb.address) and paste the output.

Also, rather than screenshots, it's more useful/helpful if you can provide that as text in a code block.

Thank you very much! After I run the w3.eth.getCode(tb.address), the output is:
HexBytes('0x')

That's represent that this contract was not deployed successfully?

Correct, a return of 0x from w3.eth.getCode(..) means that the contract failed during deployment.

But why? The same contract that I can deployed in web3.js.
The deploy code: MyContract.constructor(100000000, "zhihui", "ZB").transact({'from': w3.eth.accounts[0], 'gas': 410000} is not correct? Or what else problem?

But why?

There's too much code right now for us to look through and debug visually for you.

Here is a debugging technique that you can try: reduce the code in the contract and python by removing features, until the deploy starts working. That should help you identify an offending piece. Then post the difference between the working and non-working code. Also, you can try posting that to https://ethereum.stackexchange.com/questions/tagged/web3.py , which has a bigger crew of people who might be able to help faster.

OK. Thank you!

I find the bug! The reason why the contract was not deployed successfully is that the gas is not enough.
I feel it is very uncomfortable, why not show me the error: gas is not enough when I deploy the contract.
It cost my 5 days to solve the problem.

Glad you got it figured out! Yes, unfortunately, that information is not readily available over json-rpc, which is how web3.py collects information from the node. We can only tell if the transaction succeeded or failed. Not specifying the gas at all during the transaction is often the best solution.

Yes, that's how I do now! Anyway, thank you very much!

Was this page helpful?
0 / 5 - 0 ratings