Compiling, deploying and calling Ethereum smartcontract using Python

We use Python 3.5.3 on Ubuntu 16.04 with setup below:

pip3 install web3==4.7.2 py-solc==3.2.0
python3 -m solc.install v0.4.24
export PATH="$PATH:$HOME/.py-solc/solc-v0.4.24/bin"

The core packages are:

  1. web3 : official python interface to interact with Ethereum blockchain
  2. py-solc: official python wrapper for solc (solidity compiler). Hence solc needs to be installed (covered in setup above)

To compile a solidity smartcontract in a file (.sol) we can use method below:

def compile_contract(contract_source_file, contractName=None):
    """
    Reads file, compiles, returns contract name and interface
    """
    with open(contract_source_file, "r") as f:
        contract_source_code = f.read()
    compiled_sol = compile_source(contract_source_code) # Compiled source code
    if not contractName:
        contractName = list(compiled_sol.keys())[0]
        contract_interface = compiled_sol[contractName]
    else:
        contract_interface = compiled_sol['<stdin>:' + contractName]        
    return contractName, contract_interface

To deploy compiled smart contract, we use:

def deploy_contract(acct, contract_interface, contract_args=None):
    """
    deploys contract using self-signed tx, waits for receipt, returns address
    """
    contract = w3.eth.contract(abi=contract_interface['abi'], bytecode=contract_interface['bin'])
    constructed = contract.constructor() if not contract_args else contract.constructor(*contract_args)
    tx = constructed.buildTransaction({
        'from': acct.address,
        'nonce': w3.eth.getTransactionCount(acct.address),
    })
    print ("Signing and sending raw tx ...")
    signed = acct.signTransaction(tx)
    tx_hash = w3.eth.sendRawTransaction(signed.rawTransaction)
    print ("tx_hash = {} waiting for receipt ...".format(tx_hash.hex()))
    tx_receipt = w3.eth.waitForTransactionReceipt(tx_hash, timeout=120)
    contractAddress = tx_receipt["contractAddress"]
    print ("Receipt accepted. gasUsed={gasUsed} contractAddress={contractAddress}".format(**tx_receipt))
    return contractAddress

Once, deployed we can create a contract object to call its public read methods or attributes:

contract = w3.eth.contract(address=contract_address, abi=contract_interface['abi']) 
# call public methods
val = contract.functions.get().call()
# call public attributes
val = contract.functions.storedData().call()

But in order to call write/update methods, we need to create a transaction:

def exec_contract(acct, nonce, func):
    """
    call contract transactional function func
    """
    construct_txn = func.buildTransaction({'from': acct.address, 'nonce': nonce})
    signed = acct.signTransaction(construct_txn)
    tx_hash = w3.eth.sendRawTransaction(signed.rawTransaction)
    return tx_hash.hex()

Here is an example to use methods above to compile, deploy and invoke a simple smart contract:

// contract.sol

pragma solidity ^0.4.21;

contract simplestorage {
  uint public storedData;

  event Updated(address by, uint _old, uint _new);

  function set(uint x) {
    uint old = storedData;
    storedData = x;
    emit Updated(msg.sender, old, x);
  }

  function get() constant returns (uint retVal) {
    return storedData;
  }
}
from web3 import Web3, HTTPProvider
from solc import compile_source
import random


# config
RPC_ADDRESS = 'http://localhost:8545'
CONTRACT_SOL = 'contract.sol'
CONTRACT_NAME = 'simplestorage'
PRIVATE_KEY = "yourprivatekey"

# instantiate web3 object
w3 = Web3(HTTPProvider(RPC_ADDRESS, request_kwargs={'timeout': 120}))    
acct = w3.eth.account.privateKeyToAccount(PRIVATE_KEY)

# compile contract to get abi
print('Compiling contract..')
contract_name, contract_interface = compile_contract(CONTRACT_SOL, CONTRACT_NAME)

# deploy contract    
print('Deploying contract..')
contract_address = deploy_contract(acct, contract_interface)

# create contract object
contract = w3.eth.contract(address=contract_address, abi=contract_interface['abi']) 

# call non-transactional method
val = contract.functions.get().call()
print('Invoke get()={}'.format(val))
assert val == 0

# call transactional method
nonce = w3.eth.getTransactionCount(acct.address)
from_block_number = w3.eth.blockNumber  
new_val = random.randint(1, 100)      
contract_func = contract.functions.set(new_val)
print('Invoke set()={}'.format(new_val))
tx_hash = exec_contract(acct, nonce, contract_func)
print('tx_hash={} waiting for receipt..'.format(tx_hash))
tx_receipt = w3.eth.waitForTransactionReceipt(tx_hash, timeout=120)        
print("Receipt accepted. gasUsed={gasUsed} blockNumber={blockNumber}". format(**tx_receipt))

# catch event
contract_filter = contract.events.Updated.createFilter(fromBlock=from_block_number)
entries = None
print('Waiting for event..')
while not entries: entries = contract_filter.get_all_entries()    
# _new == new_val
args = entries[0].args
print(args) 
assert args._old == 0
assert args._new == new_val 
assert args.by == acct.address

# call non-transactional method
val = contract.functions.get().call()
print('Invoke get()={}'.format(val))
assert val == new_val

If you are using infura.io, this code will also work except the “catch event” part because infura.io does not support eth_newFilter.

The full code is available in my github. Enjoy! 🍻

Leave a Reply

Your email address will not be published. Required fields are marked *