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.