Web3.py: How to properly encode function argument types?

Created on 8 Feb 2018  路  11Comments  路  Source: ethereum/web3.py

Hello, I've been getting the error No Matching Transactions error a lot lately when trying to call function arguments with a mix of different arguments (bytes32, uint8, uint256, and so on). I've unsure as to how to properly encode the various function arguments so that web3py can properly match functions. I've tried using Web3.toHex Web3.toBytes and so forth to no avail.

All 11 comments

Hi @postables

Maybe you are passing the correct types, but are exceeding some of the argument lengths? If you paste an example I could take a look.

Here is a demo showing function matching with mixed arguments. I tested using both the built in to_bytes() and web3.Web3.toBytes() functions. The last function call fails to match, because I am passing an integer that exceeds 8 bits.

import web3

from web3 import Web3, EthereumTesterProvider
from solc import compile_source

source = '''
pragma solidity ^0.4.19;


contract TestContract {

   function TestContract(){

   }

   function sendArgs(uint8 a, bytes32 b, string c) returns(string){
         return "A";
   }

   function sendArgs() returns(string){
         return "B";
   }

}
'''

compiled_sol = compile_source(source) # Compiled source code
contract_interface = compiled_sol['<stdin>:TestContract']

w3 = Web3(EthereumTesterProvider())

contract = w3.eth.contract(abi=contract_interface['abi'], bytecode=contract_interface['bin'])

tx_hash = contract.deploy()

receipt = w3.eth.getTransactionReceipt(tx_hash)
contract_address = receipt['contractAddress']


a = 255
too_long = 256
b = 1
c = 'c'

if 'B' == contract.functions.sendArgs().call(transaction={'to':contract_address}):
   print("success selecting sendArg() with no args")

if 'A' == contract.functions.sendArgs(a, b.to_bytes(32, byteorder='little'), c).call(transaction={'to':contract_address}):
   print("success using built-in to_bytes()")

if 'A' == contract.functions.sendArgs(a, w3.toBytes(b), c).call(transaction={'to':contract_address}):
   print("success using web3.to_bytes()")

if 'A' == contract.functions.sendArgs(too_long, w3.toBytes(b), c).call(transaction={'to':contract_address}):
   print("success using passing too long of an integer")

Output:

success selecting sendArg() with no args
success using built-in to_bytes()
success using web3.to_bytes()
Traceback (most recent call last):
  File "various_arg_types.py", line 54, in <module>
    if 'A' == contract.functions.sendArgs(too_long, w3.toBytes(b), c).call(transaction={'to':contract_address}):
  File "/home/dwilson/Develop/Projects/ethereum/web3.py/web3/contract.py", line 750, in __init__
    self._set_function_info()
  File "/home/dwilson/Develop/Projects/ethereum/web3.py/web3/contract.py", line 754, in _set_function_info
    self.abi = find_matching_fn_abi(self.contract_abi, self.fn_name, self.args, self.kwargs)
  File "/home/dwilson/Develop/Projects/ethereum/web3.py/web3/utils/contracts.py", line 93, in find_matching_fn_abi
    raise ValueError("No matching functions found")
ValueError: No matching functions found

Ok I'll post the output that gives me this error once im back home. I don't think its due to exceeding argument length as if I take the same input data, but enter it into my contract through mist everything works as expected.

@postables v3 or v4?

@carver Using v4

I have a similar question - I have a solidity function with the following parameters:

testFunction(address _location, uint256 _reading, uint256 _minParameter, uint256 _maxParameter, bytes32 _codeHash)

What type of python variable should be used for sending an address?

Is there a way to create a uint256 in python?

Thanks!

@stevenvaleri you can just use integers.

@stevenvaleri

IMO i would use go-ethereum bindings (absolutely worth learning golang for, it's what caused be to pick up the language) I've had way better success with it than using web3py. Web3py is extremely good, however I had a ton of problems with the stability of the package and it constantly breaking, and stuff just not working as it is expected to, along with frequent updates complete breaking parts of the package, and a lot more heartache. I honestly spent hours, and hours trying to debug issues with web3py, that by switching to golang and go-ethereum bindings I've literally never encountered. It's been months since I've used web3py (stopped shortly after opening this issue) so perhaps things have changed but I would still recommend go-ethereum bindings instead, however the amount of heartache that I've been spared working with web3py by switching to golang is beyond what words can say. That being said web3py helped introduce the wonderful world of ethereum and web3 to me!

https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts

Thanks @pipermerriam for the quick response!

It ended up being the bytes32 input. I wasn't converting my string to a 32 byte array correctly (likely due to lack of experience in python) - the code below ended up returning the correct format and the function on the smart contract triggered correctly.

def string_to_bytes32(data):
    if len(data) > 32:
        myBytes32 = data[:32]
    else:
        myBytes32 = data.ljust(32, '0')
    return bytes(myBytes32, 'utf-8')

@postables - thanks for the recommendation - I'll look into that. I'm a lot more familiar with web3js and web3j, which i've been pretty happy with. In this case I'm trying to interface with an IoT device on a raspberry pi, so python and web3py are letting me monitor device and interact with the blockchain in the same program.

The name of that argument (_codeHash) implies strongly that you're doing something unintended by clipping the end of the data @stevenvaleri . A code hash would already be 32 bytes long (and usually type bytes, rather than type str). If your data is something longer, then it's probably not a sha256 hash, and not going to give you the result you want.

@carver ahh, that is a good catch!

I'm still only testing right now - so i'm cheating and taking the string of sha256 hash that I know is the correct hash, converting it to bytes and then sending it. Once I have everything working, i'll actually be hashing the data and won't need the conversion from string to byes.

Thanks for the comment!

Stale issue cleanup; if anyone lands here in the future, #1890 introduces some Python types to ABI examples in the docs. For other feature requests/bug reports, please open a new issue.

Was this page helpful?
0 / 5 - 0 ratings