PolarSPARC

Using Ganache and Solidity with Python


Bhaskar S 01/02/2022


Overview

Ethereum is a popular decentralized, open source blockchain platform that powers the second largest cryptocurreny called Ether (ETH). In addition, the platform offers a virtual machine environment that allows one to deploy and execute decentralized applications (dApps) called Smart Contracts.

Ganache-CLI is a command-line interface for running a locally hosted Ethereum blockchain that enables one to develop and test Ethereum distributed applications (dApps) in a rapid fasion. It includes support for all the popular json-rpc API calls related to Ethereum and enables one to develop, deploy, and test dApps in a safe and deterministic environment.

Smart Contracts in Ethereum are built using a programing language called Solidity, which is a high-level, contract-oriented, statically typed, and bytecode compiled language.

For this article, we will show how to setup a development environment for building, deploying, and testing Ethereum dApps. For the demonstration, we will use some aspects of the motor vehicle use-case that is described in the introductory article Introduction to Blockchain .

Setup and Installation

The setup will be on a Ubuntu 20.04 LTS based Linux desktop.

Ensure that the desktop has Docker installed and setup. Else, please refer to the article Introduction to Docker for additional help.

In addition, ensure that the Python programming language (version 3.8 or above) is installed.

Assuming that we are logged in as polarsparc and the current working directory is the home directory /home/polarsparc, we will setup a directory structure by executing the following commands in a terminal window:

$ cd $HOME

$ mkdir -p Projects/Ethereum

$ mkdir -p Projects/Ethereum/ganache

$ mkdir -p Projects/Ethereum/solidity

$ mkdir -p Projects/Ethereum/solidity/build

Now, change the current working directory to /home/polarsparc/Projects/Ethereum. In the following sections, we will refer to this location as $ETH_HOME.

At the time of this article, the current stable version of ganache-cli docker image was 6.12.2.

To pull and download the docker image for Ganache-CLI, execute the following command:

$ docker pull trufflesuite/ganache-cli:v6.12.2

The following would be the typical output:

Output.1

v6.12.2: Pulling from trufflesuite/ganache-cli
0a6724ff3fcd: Pull complete 
c845cd6699b8: Pull complete 
d44fb5719f0e: Pull complete 
a25b4309e732: Pull complete 
7a1fff9a31e3: Pull complete 
Digest: sha256:c062707f17f355872d703cde3de6a12fc45a027ed42857c72514171a5f466ab7
Status: Downloaded newer image for trufflesuite/ganache-cli:v6.12.2
docker.io/trufflesuite/ganache-cli:v6.12.2

Once the download is complete, execute the following command to check the version of ganachae-cli:

$ docker run --rm --name ganache-cli trufflesuite/ganache-cli:v6.12.2 --version

The following would be the typical output:

Output.2

Ganache CLI v6.12.2 (ganache-core: 2.13.2)

Next, it is time to pull the solidity compiler image. At the time of this article, the current stable version of solidity docker image was 0.8.11.

To pull and download the docker image for Solidity, execute the following command:

$ docker pull ethereum/solc:0.8.11

The following would be the typical output:

Output.3

0.8.11: Pulling from ethereum/solc
dd1163f2b048: Pull complete 
Digest: sha256:1aa1445646cec83b10445214dbecb9b6fb638b44d70a142059fb1bc984dac3c9
Status: Downloaded newer image for ethereum/solc:0.8.11
docker.io/ethereum/solc:0.8.11

Once the download is complete, execute the following command to check the version of solidity:

$ docker run --rm --name eth-solc ethereum/solc:0.8.11 solc --version

The following would be the typical output:

Output.4

solc, the solidity compiler commandline interface
Version: 0.8.11+commit.d7f03943.Linux.g++

The next step is to download and install the Python module Web3, which is the library for interacting with Ethereum. In order to do that, execute the following command:

$ python -m pip install web3

It is now time to launch ganache-cli. Open a new terminal and execute the following command to start a local Ethereum blockchain environment:

$ docker run --rm --name ganache-cli -u $(id -u $USER):$(id -g $USER) -p 8545:8545 -v $ETH_HOME/ganache:/home/ganache trufflesuite/ganache-cli:v6.12.2 --accounts 4 --chainId 2022 --db /home/ganache --deterministic --networkId 2022

The following would be the typical output:

Output.5

Ganache CLI v6.12.2 (ganache-core: 2.13.2)
(node:1) [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead.
(Use `node --trace-deprecation ...` to show where the warning was created)

Available Accounts
==================
(0) 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1 (100 ETH)
(1) 0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0 (100 ETH)
(2) 0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b (100 ETH)
(3) 0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d (100 ETH)

Private Keys
==================
(0) 0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d
(1) 0x6cbed15c793ce57650b9877cf6fa156fbef513c4e6134f022a85b1ffdd59b2a1
(2) 0x6370fd033278c143179d81c5526140625662b8daa446c22ee2d73db3707e620c
(3) 0x646f1ce2fdad0e6deeeb5c7e8e5543bdde65e86029e2fd9fc169899c440a7913

HD Wallet
==================
Mnemonic:      myth like bonus scare over problem client lizard pioneer submit female collect
Base HD Path:  m/44'/60'/0'/0/{account_index}

Gas Price
==================
20000000000

Gas Limit
==================
6721975

Call Gas Limit
==================
9007199254740991

Listening on 0.0.0.0:8545

The following are the explanation of the options used:

This completes the setup and installation of the required components.

Hands-On with Python

Interacting with Ganache

To make API requests on our local blockchain environment, we will issue the following commands at the Python interpreter prompt:

>>> import json

>>> from web3 import Web3

>>> provider = Web3.HTTPProvider('http://127.0.0.1:8545')

>>> w3 = Web3(provider)

To check if we have successfully connected to our development environment, execute the following command at the Python interpreter prompt:

>>> w3.isConnected()

The following would be the typical output:

Output.6

True

We generated 4 account addresses when we started ganache-cli. Let us designate the first address for the bank, the second for the buyer, the third for the dealer, and the fourth for the dmv respectively.

To assign the second address to the buyer and the third to the dealer, execute the following commands at the Python interpreter prompt:

>>> buyer = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0'

>>> dealer = '0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b'

To verify if an Ethereum account address is valid, execute the following command at the Python interpreter prompt:

>>> w3.isAddress(buyer)

The following would be the typical output:

Output.7

True

Ethereum provides built-in support for address checksum as a way to indicate if an address is valid and not. To verify if an Ethereum account address is checksummed address, execute the following command at the Python interpreter prompt:

>>> w3.isChecksumAddress(dealer)

The following would be the typical output:

Output.8

True

To retrieve the details of the genesis block (the very first block with number 0), execute the following commands at the Python interpreter prompt:

>>> genesis_block = json.loads(w3.toJSON(w3.eth.getBlock(0)))

>>> genesis_block

The following would be the typical output:

Output.9

{'number': 0, 'hash': '0xc8d3c9b510a952012ce679b3bf22794ea75c33cbfec7334206ec5591c66171e0', 'parentHash': '0x0000000000000000000000000000000000000000000000000000000000000000', 'mixHash': '0x0000000000000000000000000000000000000000000000000000000000000000', 'nonce': '0x0000000000000000', 'sha3Uncles': '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', 'logsBloom': '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 'transactionsRoot': '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', 'stateRoot': '0x464221261508910eb2d88e3ae1f918f1c74e7f04442e43f99b50625eacf96762', 'receiptsRoot': '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', 'miner': '0x0000000000000000000000000000000000000000', 'difficulty': 0, 'totalDifficulty': 0, 'extraData': '0x', 'size': 1000, 'gasLimit': 6721975, 'gasUsed': 0, 'timestamp': 1641062913, 'transactions': [], 'uncles': []}

To display account balance for the buyer account in ETH, execute the following command at the Python interpreter prompt:

>>> w3.fromWei(w3.eth.getBalance(buyer), 'ether')

The following would be the typical output:

Output.10

Decimal('100')

We will now send a transaction to transfer 1 ETH from the buyer to the dealer. In order to do this we will need to create a transaction message. Enter the following data at the Python interpreter prompt to create the transaction message structure:

>>> buyer_dealer_txn = {

... 'from': buyer,

... 'to': dealer,

... 'value': w3.toWei(1, 'ether'),

... 'gas': 90000,

... 'gasPrice': 18000000000,

... 'nonce': 0,

... 'chainId': 2022

...}

Before sending the transaction message buyer_dealer_txn, it needs to be digitally signed using the private key of the buyer. To do that, execute the following commands at the Python interpreter prompt:

>>> buyer_priv_key = '0x6cbed15c793ce57650b9877cf6fa156fbef513c4e6134f022a85b1ffdd59b2a1'

>>> signed_txn = w3.eth.account.signTransaction(buyer_dealer_txn, buyer_priv_key)

To send the signed transaction to transfer 1 ETH from the buyer to the dealer, execute the following command at the Python interpreter prompt:

>>> txn = w3.eth.sendRawTransaction(signed_txn.rawTransaction)

The executed method will return a transaction hash as a hex string (similar to a transaction id).

To display all the details of the just sent transaction on the local blockchain, execute the following commands at the Python interpreter prompt:

>>> txn_json = json.loads(w3.toJSON(w3.eth.getTransaction(txn)))

>>> txn_json

The following would be the typical output:

Output.11

{'hash': '0xf23d6fcbe7133997ba0227e5c0e2a84ba2dd058fbbfcda04e6d99d9e5ca2b5b2', 'nonce': 0, 'blockHash': '0xe28e0d3c0546400561055713bd4ea1635e6cc85b6a80b77c5f99ee1603e61088', 'blockNumber': 1, 'transactionIndex': 0, 'from': '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0', 'to': '0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b', 'value': 1000000000000000000, 'gas': 90000, 'gasPrice': 18000000000, 'input': '0x', 'v': 4080, 'r': '0x4b14d8984143f45f7f9163c01300cf62822738c53259e7b8a4894790059ae4fd', 's': '0x30bc70b9bb639ee81a8bdfb881027da8a667eb4913d9a9c2f25607d6502ac52d'}

To display the block number of the latest block in our local blockchain, execute the following command at the Python interpreter prompt:

>>> w3.eth.blockNumber

The following would be the typical output:

Output.12

1

To retrieve the details of the latest block from our local blockchain, execute the following commands at the Python interpreter prompt:

>>> latest_block = json.loads(w3.toJSON(w3.eth.getBlock('latest')))

>>> latest_block

The following would be the typical output:

Output.13

{'number': 1, 'hash': '0xe28e0d3c0546400561055713bd4ea1635e6cc85b6a80b77c5f99ee1603e61088', 'parentHash': '0xc8d3c9b510a952012ce679b3bf22794ea75c33cbfec7334206ec5591c66171e0', 'mixHash': '0x0000000000000000000000000000000000000000000000000000000000000000', 'nonce': '0x0000000000000000', 'sha3Uncles': '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', 'logsBloom': '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 'transactionsRoot': '0x74dff4bb9ac27406d5fac268b3d9e9308d71525b43c17727e5e1d96140728f2c', 'stateRoot': '0x9e0c5b5836130734c42601d71c09e883d4e66d41f0dcfc20ea3aebd779c72bfc', 'receiptsRoot': '0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2', 'miner': '0x0000000000000000000000000000000000000000', 'difficulty': 0, 'totalDifficulty': 0, 'extraData': '0x', 'size': 1000, 'gasLimit': 6721975, 'gasUsed': 21000, 'timestamp': 1641072636, 'transactions': ['0xf23d6fcbe7133997ba0227e5c0e2a84ba2dd058fbbfcda04e6d99d9e5ca2b5b2'], 'uncles': []}

To display account balance for the buyer account, execute the following command at the Python interpreter prompt:

>>> w3.fromWei(w3.eth.getBalance(buyer), 'ether')

The following would be the typical output:

Output.14

Decimal('98.999622')

Similarly, to display account balance for the dealer account, execute the following command at the Python interpreter prompt:

>>> w3.fromWei(w3.eth.getBalance(dealer), 'ether')

The following would be the typical output:

Output.15

Decimal('101')

As is evident from the Output.14 and Output.15 above, the account balance for the buyer has decreased, while the account balance for the dealer has increased.

EXCELLENT !!! Our local Ethereum blockchain environment is working as expected.

Moving on to writing, deploying, and testing a simple smart contract using Solidity.

Smart Contracts with Solidity

The following is the code for the simple smart contract located in the directory $ETH_HOME/solidity:


Vehicle.sol
// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.0;

contract Vehicle {
    string _color;
    string _vin;
    uint _cost;
    address _dealer;
    address _owner;

    // Will be initiated by the dealer
    constructor(string memory color, string memory vin, uint cost) {
        _color = color;
        _vin = vin;
        _cost = cost;
        _dealer = msg.sender;
    }
    
    function getColor() public view returns (string memory) {
        return _color;
    }
    
    function getVin() public view returns (string memory) {
        return _vin;
    }
    
    function getCost() public view returns (uint) {
        return _cost;
    }
    
    function getDealer() public view returns (address) {
        return _dealer;
    }
    
    function getOwner() public view returns (address) {
        return _owner;
    }
    
    // Will be initiated by the buyer
    function buyVehicle(uint amount) public payable {
        require(msg.sender != _dealer);
        
        // Equivalent to checking _owner == null
        require(_owner == address(0));
        
        require(amount >= _cost);
        
        _owner = msg.sender;
    }
}

To compile Vehicle.sol using the docker image for Solidity, execute the following command:

$ docker run --rm --name eth-solc -u $(id -u $USER):$(id -g $USER) -v $HOME/Downloads/DATA/solidity:/home/solc ethereum/solc:0.8.11 -o /home/solc/build --abi --bin --gas /home/solc/Vehicle.sol

The following would be the typical output:

Output.16

======= home/solc/Vehicle.sol:Vehicle =======
Gas estimation:
construction:
    infinite + 312000 = infinite
external:
    buyVehicle(uint256):	31096
    getColor():	infinite
    getCost():	2503
    getDealer():	2566
    getOwner():	2522
    getVin():	infinite

On successful compilation, there will be two files generated, namely, Vehicle.abi and Vehicle.bin located in the directory $ETH_HOME/solidity/build.

The option --gas passed to the Solidity compiler enables one to get an estimate on the amount of gas required to create/deploy a contract and to interact with the various functions.

The option --abi passed to the Solidity compiler enables the generation of the Vehicle.abi file, which serves as the standard interface that details all of the publicly accessible contract methods.

The following is a pretty version of the contents of the file Vehicle.abi:


Vehicle.abi
[
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "color",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "vin",
        "type": "string"
      },
      {
        "internalType": "uint256",
        "name": "cost",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "buyVehicle",
    "outputs": [],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "getColor",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "getCost",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "getDealer",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "getOwner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "getVin",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]

The option --bin passed to the Solidity compiler enables the generation of the Vehicle.bin binary file that contains the hex-encoded bytecode for the specified smart contract source file. This bytecode will be executed by the Ethereum Virtual Machine (EVM), which serves as a runtime environment for smart contracts.

The following is a pretty version of the contents of the file Vehicle.bin:


Vehicle.bin
60806040523480156200001157600080fd5b5060405162000a6938038062000a69833981810160405281019062000037919062000342565b82600090805190602001
906200004f929190620000ba565b50816001908051906020019062000068929190620000ba565b508060028190555033600360006101000a81548173ffffffffffff
ffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050505062000441565b828054620000c890620004
0b565b90600052602060002090601f016020900481019282620000ec576000855562000138565b82601f106200010757805160ff191683800117855562000138565b
8280016001018555821562000138579182015b82811115620001375782518255916020019190600101906200011a565b5b5090506200014791906200014b565b5090
565b5b80821115620001665760008160009055506001016200014c565b5090565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b600060
1f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b620001d382
62000188565b810181811067ffffffffffffffff82111715620001f557620001f462000199565b5b80604052505050565b60006200020a6200016a565b9050620002
188282620001c8565b919050565b600067ffffffffffffffff8211156200023b576200023a62000199565b5b620002468262000188565b9050602081019050919050
565b60005b838110156200027357808201518184015260208101905062000256565b8381111562000283576000848401525b50505050565b6000620002a06200029a
846200021d565b620001fe565b905082815260208101848484011115620002bf57620002be62000183565b5b620002cc84828562000253565b509392505050565b60
0082601f830112620002ec57620002eb6200017e565b5b8151620002fe84826020860162000289565b91505092915050565b6000819050919050565b6200031c8162
000307565b81146200032857600080fd5b50565b6000815190506200033c8162000311565b92915050565b6000806000606084860312156200035e576200035d6200
0174565b5b600084015167ffffffffffffffff8111156200037f576200037e62000179565b5b6200038d86828701620002d4565b935050602084015167ffffffffff
ffffff811115620003b157620003b062000179565b5b620003bf86828701620002d4565b9250506040620003d2868287016200032b565b9150509250925092565b7f
4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806200042457607f
821691505b602082108114156200043b576200043a620003dc565b5b50919050565b61061880620004516000396000f3fe6080604052600436106100555760003560
e01c806336ce09201461005a578063893d20e8146100765780639a86139b146100a15780639cf6d1af146100cc578063bd3e19d4146100f7578063bf20f940146101
22575b600080fd5b610074600480360381019061006f9190610413565b61014d565b005b34801561008257600080fd5b5061008b610256565b604051610098919061
0481565b60405180910390f35b3480156100ad57600080fd5b506100b6610280565b6040516100c39190610535565b60405180910390f35b3480156100d857600080
fd5b506100e1610312565b6040516100ee9190610481565b60405180910390f35b34801561010357600080fd5b5061010c61033c565b604051610119919061056656
5b60405180910390f35b34801561012e57600080fd5b50610137610346565b6040516101449190610535565b60405180910390f35b600360009054906101000a9004
73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff
1614156101a857600080fd5b600073ffffffffffffffffffffffffffffffffffffffff16600460009054906101000a900473ffffffffffffffffffffffffffffffff
ffffffff1673ffffffffffffffffffffffffffffffffffffffff161461020357600080fd5b60025481101561021257600080fd5b33600460006101000a81548173ff
ffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600060046000905490610100
0a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60606000805461028f906105b0565b80601f016020809104026020016040519081016040
52809291908181526020018280546102bb906105b0565b80156103085780601f106102dd57610100808354040283529160200191610308565b820191906000526020
600020905b8154815290600101906020018083116102eb57829003601f168201915b5050505050905090565b6000600360009054906101000a900473ffffffffffff
ffffffffffffffffffffffffffff16905090565b6000600254905090565b606060018054610355906105b0565b80601f016020809104026020016040519081016040
5280929190818152602001828054610381906105b0565b80156103ce5780601f106103a3576101008083540402835291602001916103ce565b820191906000526020
600020905b8154815290600101906020018083116103b157829003601f168201915b5050505050905090565b600080fd5b6000819050919050565b6103f0816103dd
565b81146103fb57600080fd5b50565b60008135905061040d816103e7565b92915050565b600060208284031215610429576104286103d8565b5b60006104378482
85016103fe565b91505092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061046b82610440565b9050919050565b
61047b81610460565b82525050565b60006020820190506104966000830184610472565b92915050565b600081519050919050565b60008282526020820190509291
5050565b60005b838110156104d65780820151818401526020810190506104bb565b838111156104e5576000848401525b50505050565b6000601f19601f83011690
50919050565b60006105078261049c565b61051181856104a7565b93506105218185602086016104b8565b61052a816104eb565b840191505092915050565b600060
2082019050818103600083015261054f81846104fc565b905092915050565b610560816103dd565b82525050565b600060208201905061057b600083018461055756
5b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216
806105c857607f821691505b602082108114156105dc576105db610581565b5b5091905056fea2646970667358221220b765b0fc979d526f589335db707032a66ce0
84d9e4e91cc021f14f4cbd97a9fc64736f6c634300080b0033

To load the contents of Vehicle.abi into a variable called abi, execute the following commands at the Python interpreter prompt:

>>> f_abi = open('solidity/build/Vehicle.abi', 'r')

>>> abi = json.loads(f_abi.read())

>>> f_abi.close()

Similarly, load the contents of Vehicle.bin into a variable called bin by executing the following commands at the Python interpreter prompt:

>>> f_bin = open('solidity/build/Vehicle.bin', 'r')

>>> bin = json.loads(f_bin.read())

>>> f_bin.close()

For our use-case, the dealer first deploys the contract of a vehicle. For that, we first need to set the default account to the dealer account. In addition, we will set a value of 5 ETHs as the cost of the vehicle. Execute the following commands at the Python interpreter prompt:

>>> cost = w3.toWei(5, 'ether')

>>> w3.eth.defaultAccount = dealer

The second step is to instantiate the smart contract as an object by executing the following command at the Python interpreter prompt:

>>> Vehicle = w3.eth.contract(abi=abi, bytecode=bin)

The third step is to deploy the smart contract to our local Ethereum blockchain environment by executing the following command at the Python interpreter prompt:

>>> txn_hash = Vehicle.constructor('Blue', 'V12345C0001', cost).transact()

The deployment of the smart contract will return a transaction hash (a handle to the transaction on the blockchain).

The fourth step is to wait for the receipt of the deployment of the smart contract into our local Ethereum blockchain environment. This is typically to wait for the transaction to be processed by a miner. In our local environment, there is no mining involved and all the transactions are processed immediately . In order to retrieve the transaction receipt, execute the following command at the Python interpreter prompt:

>>> txn_receipt = w3.eth.waitForTransactionReceipt(txn_hash)

To display the transaction receipt details for the just deployed smart contract, execute the following commands at the Python interpreter prompt:

>>> txn_receipt_json = json.loads(w3.toJSON(txn_receipt))

>>> txn_receipt_json

The following would be the typical output:

Output.17

{'transactionHash': '0xec3e32026fd4e2953e958d2bebd1a930847388f60730df46d36b998deb81972a', 'transactionIndex': 0, 'blockHash': '0xc9dc2a85857edfd567ba385aa38cc894a8fedfd7fefd75e1be36713e6ae8059e', 'blockNumber': 2, 'from': '0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b', 'to': None, 'gasUsed': 469460, 'cumulativeGasUsed': 469460, 'contractAddress': '0x17e91224c30c5b0B13ba2ef1E84FE880Cb902352', 'logs': [], 'status': 1, 'logsBloom': '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'}

Now that we have successfully deployed the smart contract into our local Ethereum blockchain environment, we need to get the instance of that contract in order to interact with it.

To get the instance of the smart contract from our local blockchain environment into a variable called vehicle_contract, execute the following command at the Python interpreter prompt:

>>> vehicle_contract = w3.eth.contract(address=txn_receipt.contractAddress, abi=abi)

To invoke the method getVin() on the contract instance, execute the following command at the Python interpreter prompt:

>>> vehicle_contract.functions.getVin().call()

The following would be the typical output:

Output.18

'V12345C0001'

Similarly, to invoke the method getOwner() on the contract instance, execute the following command at the Python interpreter prompt:

>>> vehicle_contract.functions.getOwner().call()

The following would be the typical output:

Output.19

'0x0000000000000000000000000000000000000000'

The zero (0) address in Output.19 above indicates the vehicle is not yet purchased by any buyer.

Now for the exciting part - the buyer will transact with the smart contract deployed by the dealer to buy the vehicle. In order to do this we will need to create a transaction message. Enter the following data at the Python interpreter prompt to create the transaction message structure for buying the vehicle:

>>> gas_price = 1000000

>>> buyer_txn = { 'from': buyer, 'value': cost, 'gas': gas_price }

The buyer will use the transaction message buyer_txn to transact with the smart contract to buy the vehicle by executing the following command at the Python interpreter prompt:

>>> buyer_txn_hash = vehicle_contract.functions.buyVehicle(cost).transact(buyer_txn)

The interaction with the smart contract (by the buyer) will return a transaction hash.

To wait for the receipt of the transaction related to the interaction with the smart contract, execute the following command at the Python interpreter prompt:

>>> buyer_txn_receipt = w3.eth.waitForTransactionReceipt(buyer_txn_hash)

To display the transaction receipt details related to the interaction with the smart contract, execute the following commands at the Python interpreter prompt:

>>> buyer_txn_receipt = w3.eth.waitForTransactionReceipt(buyer_txn_hash)

>>> buyer_txn_receipt

The following would be the typical output:

Output.20

{'transactionHash': '0x7699d01a50120c8ca7114c0fa955ccb3ec98eeb7d335619af3ead2e906c945bc', 'transactionIndex': 0, 'blockHash': '0x782646c2d79341f338b85893b4ca46da7be18e940dbd30ecf32abcaf5ef4eded', 'blockNumber': 3, 'from': '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0', 'to': '0x17e91224c30c5b0B13ba2ef1E84FE880Cb902352', 'gasUsed': 45060, 'cumulativeGasUsed': 45060, 'contractAddress': None, 'logs': [], 'status': 1, 'logsBloom': '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'}

Now invoke the method getOwner() on the contract instance, execute the following command at the Python interpreter prompt:

>>> vehicle_contract.functions.getOwner().call()

The following would be the typical output:

Output.21

'0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0'

The presence of the buyer's address in Output.21 above indicates the vehicle has been successfully purchased by the buyer.

To display account balance for the buyer account, execute the following command at the Python interpreter prompt:

>>> w3.fromWei(w3.eth.getBalance(buyer), 'ether')

The following would be the typical output:

Output.22

Decimal('93.9987208')

As is evident from the Output.22 above, the account balance for the buyer has decreased by the cost of the vehicle and some gas.

BINGO !!! We have successfully built, deployed, and transacted with a smart contract in our local Ethereum blockchain environment.

References

Welcome to Ganache CLI

Solidity Documentation

Web3.py Documentation

Introduction to Ethereum - Part 1

Introduction to Docker



© PolarSPARC