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)])

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.