Moonbeam: Monitoring the conviction voting contract
Moonbeam is an EVM-compatible network, however it's not a run-of-the-mill one. Instead, it's pretty interesting. Moonbeam is one the first parachains launched on Polkadot. See the Polkadot blog post from 2021: Parachains are Live!
Moonbeam (the nodes specifically) is built using the Substrate framework and the Polkadot SDK. Being EVM-compatible, it's a path for Ethereum developers to get familiar with the Polkadot ecosystem.
Moonbeam has a few Solidity precompiles to ease the developer life and to cross-operate with Polkadot. Check them out in the Moonbeam docs: Overview of the Precompiled Contracts on Moonbeam.
The precompiled contract that we'll have a look at as part of this tutorial is the conviction voting one. See: Conviction Voting Precompile Contract.
The contract is pretty active in receiving transactions, so it's perfect for our demo. What we are going to do is monitor it for incoming transactions, extract the transaction data, and call the contract to retrieve the voting state based on the transactions data.
We are going to do this in Python and with the Chainstack Moonbeam nodes.
First, a few words on conviction voting and the Moonbeam contract.
Conviction voting is a relatively novel iteration of a voting algorithm that's been out for a while and that's called quadratic voting.
The short version of quadratic voting & conviction voting is:
- Quadratic voting — not everyone is equal in voting and those with the most real currency or artificial currency (token) have the most power in voting.
- Conviction voting — this one expands on quadratic voting and adds a decay of the support of a decision to be made through voting. Conviction voting calculates the conviction of each token holder for a proposal based on the tokens they stake, how long they support it, and how soon the holder switches to voting for another proposal, which starts the conviction decay.
You can check the following resources for more details:
- Moonbeam docs: Governance
- Moonbeam governance dapp
Let's implement a simple monitoring of the conviction voting contract in Python.
Prerequisites
- Chainstack account to deploy a Moonbeam Mainnet node
- web3.py
Step-by-step
Get a Moonbeam node
Log in to your Chainstack account and deploy a node.
Get the ABI
To interact with the contract, you need the contract ABI.
Since it's a precompile, it's not readily available on the Moonbeam explorer. Let's generate one:
- Go to Remix IDE.
- Create the
ConvictionVoting.sol
file and paste the actual Solidity code from the Moonbeam GitHub repository: ConvictionVoting.sol. - In Remix IDE, click Solidity Compiler > Compile.
- When compiled, hit ABI. This will copy the ABI of the contract.
Save the ABI as a ConvictionVoting.abi
file in the same directory where you are going to have the Python script.
Create the script
Now that you have ABI, create the script.
A few details on the implementation:
- The precompile address of the conviction voting contract is
0x0000000000000000000000000000000000000812
. - You can provide the block number of when to start iterating through the blocks to watch for the voting transactions.
- Once the script extracts a voting transaction, it uses the transaction data to call the two publicly readable functions in the contract
votingFor
andclassLocksFor
to retrieve the voting track ID, the index of poll, and the locked amount of the GLMR tokens. You can check what these are in the Moonbeam voting dapp.
import json
from web3 import Web3
# Сonnect to the Moonbeam node
web3 = Web3(Web3.HTTPProvider('NODE_URL'))
# The address of the conviction voting contract
contract_address = '0x0000000000000000000000000000000000000812'
# Load the ABI from the file for both functions
with open('ConvictionVoting.abi', 'r') as abi_file:
abi = json.load(abi_file)
# Create the contract instance
contract = web3.eth.contract(address=contract_address, abi=abi)
# Specify the starting block number
start_block = BLOCK_NUMBER
# Get the latest block number
latest_block = web3.eth.block_number
# Filter transactions to the Conviction Voting contract
for block_number in range(start_block, latest_block + 1):
block = web3.eth.get_block(block_number, full_transactions=True)
for tx in block.transactions:
if tx.to and (tx.to.lower() == contract_address.lower()):
print(f"Transaction from account {tx['from']} in block {block_number}:")
print(f" Transaction Hash: {tx.hash.hex()}")
# Decode the transaction input
if tx.input != '0x':
func_obj, func_params = contract.decode_function_input(tx.input)
print(f" Function called: {func_obj.fn_name}")
for name, value in func_params.items():
print(f" {name}: {value}")
# Call the classLocksFor function
class_locks = contract.functions.classLocksFor(tx['from']).call()
for lock in class_locks:
track_id, amount = lock
amount_in_glmr = web3.from_wei(amount, 'ether')
print(f" Track ID: {track_id}, Locked Amount: {amount_in_glmr} GLMR")
# Call the votingFor function
voting_info = contract.functions.votingFor(tx['from'], track_id).call()
is_casting, is_delegating, casting_details, delegating_details = voting_info
print(" Voting Information:")
print(f" Is Casting: {'Yes' if is_casting else 'No'}")
print(f" Is Delegating: {'Yes' if is_delegating else 'No'}")
# Convert and print voteAmount in GLMR
if is_casting:
for vote in casting_details[0]:
poll_index, vote_details = vote
is_standard, is_split, is_split_abstain, standard_vote, split_vote, split_abstain_vote = vote_details
if is_standard:
aye, conviction = standard_vote[0]
balance = standard_vote[1]
balance_in_glmr = web3.from_wei(balance, 'ether')
print(f" Vote in Favor: {'Yes' if aye else 'No'}, Conviction Level: {conviction}")
print(f" Balance: {balance_in_glmr} GLMR")
# Call the classLocksFor function
class_locks = contract.functions.classLocksFor(tx['from']).call()
for lock in class_locks:
track_id, amount = lock
print(f" Track ID: {track_id}, Locked Amount: {amount_in_glmr}")
# Call the votingFor function
voting_info = contract.functions.votingFor(tx['from'], track_id).call()
is_casting, is_delegating, casting_details, delegating_details = voting_info
print(" Voting Information:")
print(f" Is Casting: {'Yes' if is_casting else 'No'}")
print(f" Is Delegating: {'Yes' if is_delegating else 'No'}")
where
NODE_URL
— your Moonbeam node endpointBLOCK_NUMBER
— the block number from which to start iterating over the blocks for the conviction voting transactions.
Conclusion
This tutorial guided you through setting up the monitoring of the Moonbeam conviction voting system and how retrieve the data off the contract based on the decoded transactions.
About the author
Updated 3 months ago