I'm playing around with ethereum and python and I'm running into some weird behavior I can't make sense of. I'm having trouble understanding how return values work when calling a contract function with the python w3 client. Here's a minimal example which is confusing me in several different ways:
Contract:
pragma solidity ^0.4.0; contract test { function test(){ } function return_true() public returns (bool) { return true; } function return_address() public returns (address) { return 0x111111111111111111111111111111111111111; } }
Python unittest code
from web3 import Web3, EthereumTesterProvider from solc import compile_source from web3.contract import ConciseContract import unittest import os def get_contract_source(file_name): with open(file_name) as f: return f.read() class TestContract(unittest.TestCase): CONTRACT_FILE_PATH = "test.sol" DEFAULT_PROPOSAL_ADDRESS = "0x1111111111111111111111111111111111111111" def setUp(self): # copied from https://github.com/ethereum/web3.py/tree/1802e0f6c7871d921e6c5f6e43db6bf2ef06d8d1 with MIT licence # has slight modifications to work with this unittest contract_source_code = get_contract_source(self.CONTRACT_FILE_PATH) compiled_sol = compile_source(contract_source_code) # Compiled source code contract_interface = compiled_sol[':test'] # web3.py instance self.w3 = Web3(EthereumTesterProvider()) # Instantiate and deploy contract self.contract = self.w3.eth.contract(abi=contract_interface['abi'], bytecode=contract_interface['bin']) # Get transaction hash from deployed contract tx_hash = self.contract.constructor().transact({'from': self.w3.eth.accounts[0]}) # Get tx receipt to get contract address tx_receipt = self.w3.eth.getTransactionReceipt(tx_hash) self.contract_address = tx_receipt['contractAddress'] # Contract instance in concise mode abi = contract_interface['abi'] self.contract_instance = self.w3.eth.contract(address=self.contract_address, abi=abi, ContractFactoryClass=ConciseContract) def test_return_true_with_gas(self): # Fails with HexBytes('0xd302f7841b5d7c1b6dcff6fca0cd039666dbd0cba6e8827e72edb4d06bbab38f') != True self.assertEqual(True, self.contract_instance.return_true(transact={"from": self.w3.eth.accounts[0]})) def test_return_true_no_gas(self): # passes self.assertEqual(True, self.contract_instance.return_true()) def test_return_address(self): # fails with AssertionError: '0x1111111111111111111111111111111111111111' != '0x0111111111111111111111111111111111111111' self.assertEqual(self.DEFAULT_PROPOSAL_ADDRESS, self.contract_instance.return_address())
I have three methods performing tests on the functions in the contract. In one of them, a non-True
value is returned and instead HexBytes
are returned. In another, the contract functions returns an address constant but python sees a different value from what's expected. In yet another case I call the return_true
contract function without gas and the True
constant is seen by python.
- Why does calling
return_true
withtransact={"from": self.w3.eth.accounts[0]}
cause the return value of the function to beHexBytes(...)
? - Why does the address returned by
return_address
differ from what I expect?
I think I have some sort of fundamental misunderstanding of how gas affects function calls.
call
the method, the result is computed locally without sending a transaction to the rest of the network, and the return value of the function is sent back to you in the response. – user94559