Introduction#
In the previous article “Solidity Smart Contract Development - Basics”, we learned the basic syntax of Solidity and understood how to debug using frameworks like Brownie and HardHat. However, before using these encapsulated frameworks, we can interact directly with our local Ganache node through Web3.py to better understand its principles and lay a solid foundation for better use of the frameworks later.
This article takes Web3.py as an example to implement basic contract compilation, deployment to the local Ganache network, and interaction with the contract.
You can click here to access the demo code repository.
Web3.py#
Web3.py is an open-source library for Python that provides a simple API to interact with the Ethereum network through Python programs. Its GitHub address is ethereum/web3.py, and you can access its official documentation for usage.
Installation#
We can install Web3.py using the Python package manager pip as follows:
pip3 install web3
Usage#
Import the required methods using import
to use it.
from web3 import Web3
w3 = Web3(Web3.HTTPProvider("HTTP://127.0.0.1:7545"))
Solidity Contract Compilation#
Contract Source Code#
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract SimpleStorage {
uint256 favoriteNumber;
bool favoriteBool;
struct People {
uint256 favoriteNumber;
string name;
}
People public person = People({favoriteNumber: 2, name: "Arthur"});
People[] public people;
mapping(string => uint256) public nameToFavoriteNumber;
function store(uint256 _favoriteNumber) public returns (uint256) {
favoriteNumber = _favoriteNumber;
return favoriteNumber;
}
function retrieve() public view returns (uint256) {
return favoriteNumber;
}
function addPerson(string memory _name, uint256 _favoriteNumber) public {
people.push(People({favoriteNumber: _favoriteNumber, name: _name}));
nameToFavoriteNumber[_name] = _favoriteNumber;
}
}
This is a simple storage contract that uses a People struct object to store names and their favorite numbers, and an array to store information about multiple people, providing methods for adding and retrieving information.
Reading Contract Source File#
After completing the Solidity contract writing and syntax checking using VSCode or other editors, we need to read the contract source file and store it in a variable for subsequent compilation.
import os
with open("./SimpleStorage.sol", "r") as file:
simple_storage_file = file.read()
The above code reads the content of the SimpleStorage.sol
file into the variable simple_storage_file
.
Compiling the Contract#
Installing solcx
#
The contract compilation requires the pre-installation of the solcx
tool.
pip3 install py-solc-x
Importing solcx
#
Import the required methods using import
to use it.
from solcx import compile_standard, install_solc
Compilation#
install_solc("0.6.0")
compiled_sol = compile_standard(
{
"language": "Solidity",
"sources": {"SimpleStorage.sol": {"content": simple_storage_file}},
"settings": {
"outputSelection": {
"*": {"*": ["abi", "metadata", "evm.bytecode", "evm.sourceMap"]}
}
},
},
solc_version="0.6.0",
)
In the above code, we installed version 0.6.0 of the Solidity compiler and used the compile_standard
method from the solcx
library to compile the previously read contract source file, storing the compilation result in the variable compiled_sol
.
Getting Compilation Results#
After successful compilation, use the following code to write the compiled contract to a file.
import json
with open("compiled_code.json", "w") as file:
json.dump(compiled_sol, file)
Getting Bytecode and ABI#
The deployment and interaction of Solidity contracts require both bytecode and ABI. We can write them into corresponding variables for subsequent operations using the following code.
# get bytecode
bytecode = compiled_sol["contracts"]["SimpleStorage.sol"]["SimpleStorage"]["evm"][
"bytecode"
]["object"]
# get abi
abi = compiled_sol["contracts"]["SimpleStorage.sol"]["SimpleStorage"]["abi"]
Local Ganache Environment#
Debugging smart contracts requires deploying contracts to an actual chain, and deploying to the Ethereum main network or test networks like Rinkeby/Koven is not convenient for debugging. Therefore, we need a local blockchain environment, and Ganache provides such a local debugging environment. Ganache is mainly divided into GUI and CLI installation methods.
Ganache GUI#
In your local environment, such as Mac/Windows systems, you can choose the graphical Ganache client, which is very convenient for installation and use. You can select the corresponding version on the Ganache official website.
After installation, select Quick Start to quickly launch a locally running blockchain network, initializing ten accounts with 100 ETH each, which can be used during development and debugging.
Ganache CLI Installation#
If your system does not support GUI installation, you can use CLI installation, as follows:
npm install --global yarn
yarn global add ganache-cli
Once the installation is complete, you can start the local test network, which is consistent with Ganache GUI and also includes initialized accounts and balances.
Connecting to Local Ganache Environment via Web3#
Web3 provides a library that makes it easy to connect to the local Ganache environment:
w3 = Web3(Web3.HTTPProvider("HTTP://127.0.0.1:7545"))
chain_id = 5777
my_address = "0x2F490e1eA91DF6d3cC856e7AC391a20b1eceD6A5"
private_key = "0fa88bf96b526a955a6126ae4cca0e72c9c82144ae9af37b497eb6afbe8a9711"
Deploying Solidity Contracts#
Creating a Contract#
We can create a contract using the web3 library.
SimpleStorage = w3.eth.contract(abi=abi, bytecode=bytecode)
Deploying the Contract#
Deploying a contract involves three main steps:
- Constructing the transaction
- Signing the transaction
- Sending the transaction
Constructing the Transaction#
nonce = w3.eth.getTransactionCount(my_address)
transaction = SimpleStorage.constructor().buildTransaction(
{
"chainId": chain_id,
"gasPrice": w3.eth.gas_price,
"from": my_address,
"nonce": nonce,
}
)
Signing the Transaction#
signed_txn = w3.eth.account.sign_transaction(transaction, private_key=private_key)
Sending the Transaction#
tx_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction)
tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
Interacting with the Contract#
Interacting with the deployed contract is similar to the deployment steps, and we can also interact with the contract through the web3 library, which involves constructing transactions, signing transactions, and sending transactions.
Constructing the Transaction#
simple_storage = w3.eth.contract(address=tx_receipt.contractAddress, abi=abi)
store_transaction = simple_storage.functions.store(67).buildTransaction(
{
"chainId": chain_id,
"gasPrice": w3.eth.gas_price,
"from": my_address,
"nonce": nonce + 1,
}
)
Signing the Transaction#
signed_store_txn = w3.eth.account.sign_transaction(
store_transaction, private_key=private_key
)
Sending the Transaction#
send_store_tx = w3.eth.send_raw_transaction(signed_store_txn.rawTransaction)
tx_receipt = w3.eth.wait_for_transaction_receipt(send_store_tx)
Conclusion#
The above steps outline how we interact with the local Ganache test network using the Web3.py library. In actual production project development, we generally do not use libraries like Web3.py directly but rather use further encapsulated libraries like Brownie and HardHat. However, understanding how to use libraries like Web3.py or Web3.js is also very important.