1

I am currently debugging a more complex smart contract which, for reasons I do not understand, behaves differently despite the same input. More precisely, it returns different values for the same input(s) when deployed multiple times. I tried to simplify the contract and my calls to web3.py as much as possible. The following smart contract is the minimum working example that I can come up with that still exhibits this problem:

pragma solidity ^0.4.0;

contract TestContract
{
    bytes32 recorded_value;
    bytes32 recorded_salt; //Unused, but required for the problem to appear

    function TestContract(bytes32 value, bytes32 salt) public
    {
        recorded_value = value;
    }

    function getHash() public constant
        returns (bytes32)
    {
        return keccak256(recorded_value);
    }
}

This is how I compile and call the smart contract:

#Based on the source code from https://github.com/pipermerriam/web3.py

import json
import web3

from web3 import Web3, HTTPProvider, TestRPCProvider
from solc import compile_source
from web3.contract import ConciseContract

w3 = Web3(TestRPCProvider())

def deploy_smart_contract(source_code_file_path, contract_name, constructor_arguments):
  with open(source_code_file_path) as f:
    commitment_helper_source_code = f.read()
  compiled_sol = compile_source(commitment_helper_source_code)
  contract_interface = compiled_sol['<stdin>:' + contract_name]
  smart_contract = w3.eth.contract(contract_interface['abi'], bytecode=contract_interface['bin'])
  tx_hash = smart_contract.deploy(transaction={'from': w3.eth.accounts[0]}, args=constructor_arguments)
  tx_receipt = w3.eth.getTransactionReceipt(tx_hash)
  smart_contract_address = tx_receipt['contractAddress']
  deployed_smart_contract = w3.eth.contract(contract_interface['abi'], smart_contract_address, ContractFactoryClass=ConciseContract)
  return deployed_smart_contract, smart_contract

def print_hex(char_array):
  output = "0x";
  for char in char_array:
    output += str("%02x" % ord(char))
  return output

contract_instance, contract = deploy_smart_contract('contract.sol', 'TestContract', {Web3.toBytes(123), Web3.toBytes(100)})

value = contract_instance.getHash()
print("Hash: " + print_hex(value))

If I execute this code several times, the result looks like this:

(.venv-py3) andreas@***:/media/sf_temp$ python test.py
127.0.0.1 - - [27/Jan/2018 15:39:56] "POST / HTTP/1.1" 200 499
127.0.0.1 - - [27/Jan/2018 15:39:56] "POST / HTTP/1.1" 200 48
127.0.0.1 - - [27/Jan/2018 15:39:56] "POST / HTTP/1.1" 200 44
127.0.0.1 - - [27/Jan/2018 15:39:56] "POST / HTTP/1.1" 200 1350
127.0.0.1 - - [27/Jan/2018 15:39:56] "POST / HTTP/1.1" 200 107
127.0.0.1 - - [27/Jan/2018 15:39:56] "POST / HTTP/1.1" 200 391
127.0.0.1 - - [27/Jan/2018 15:39:56] "POST / HTTP/1.1" 200 391
Hash: 0x4cab01aa70c7d27ce12007ae744261ed3e0ee9162bc588f68526ea903ec2eb99
(.venv-py3) andreas@***:/media/sf_temp$ python test.py
127.0.0.1 - - [27/Jan/2018 15:39:58] "POST / HTTP/1.1" 200 499
127.0.0.1 - - [27/Jan/2018 15:39:58] "POST / HTTP/1.1" 200 48
127.0.0.1 - - [27/Jan/2018 15:39:58] "POST / HTTP/1.1" 200 44
127.0.0.1 - - [27/Jan/2018 15:39:58] "POST / HTTP/1.1" 200 1350
127.0.0.1 - - [27/Jan/2018 15:39:58] "POST / HTTP/1.1" 200 107
127.0.0.1 - - [27/Jan/2018 15:39:58] "POST / HTTP/1.1" 200 391
127.0.0.1 - - [27/Jan/2018 15:39:58] "POST / HTTP/1.1" 200 391
Hash: 0x75a12a7aab601952ef71dd6da71725eaa4eb7883096067546c8ae3f99cc9222e

Note the different hash. This happens every few times that I execute the smart contract, but unpredictably, i.e., there seems to be no pattern related to timing or any other environmental variables. What is happening here?

The problem also appears when I don't use keccak256. The output will then sometimes show 0x7b00000000000000000000000000000000000000000000000000000000000000 instead of 0x6400000000000000000000000000000000000000000000000000000000000000, but it is much harder to provoke. The problem disappears completely when I remove recorded_salt and the salt parameter (from the contract and my API call), so I assume that I am calling the constructor incorrectly in some way or another.

  • Try running the same code with the new version of Solidity and web3.py and if the problem persist I think you should raise the issue on GitHub. – Roman Frolov Jan 27 '18 at 21:28
  • @RomanFrolov: I am running the latest (non-beta) version of web3.py. I think I will open an issue on Github, as you suggested, and update here, if anything comes of it. – Andreas Unterweger Jan 27 '18 at 22:18
1

This is really subtle. As pointed out by this answer to the issue that I opened on Github:

You are using a set for your arguments. Sets are not ordered so it's semi-random what order those will come out when you iterate over them. If you just change that to a tuple or list [..] you'll get consisten[t] results.

So the solution is to use a list for the constructor arguments (note the square brackets instead of the curly braces):

contract_instance, contract = deploy_smart_contract('contract.sol', 'TestContract', [Web3.toBytes(123), Web3.toBytes(100)])

This site is temporarily in read only mode and not accepting new answers.

Not the answer you're looking for? Browse other questions tagged .