Decentralized Autonomous Organizations (DAOs) on Celo with Python

Decentralized Autonomous Organizations (DAOs) on Celo with Python https://celo.academy/uploads/default/optimized/2X/6/648dbf22eb5a69274d8923c0b82af0338ba38fdd_2_1024x576.jpeg
none 0.0 0

Introduction

In the blockchain ecosystem, Decentralized Autonomous Organizations, or DAOs, have been gaining popularity and attention. DAOs run autonomously using smart contracts on a blockchain because they are governed by code rather than humans, in contrast to traditional organizations. The democratic decision-making procedures are typically based on the agreement of token holders.

A mobile-first strategy is used by the open-source blockchain platform Celo to promote greater financial inclusion. The Celo blockchain is a great choice for deploying DAOs that can have a real-world impact because of its emphasis on usability and accessibility. Additionally, developers who are familiar with Ethereum but are seeking for alternative blockchains to deploy their apps find Celo to be an appealing option because it is compatible with the Ethereum Virtual Machine (EVM).

Python is a fantastic tool for interfacing with blockchains, like Celo, as it is among the most flexible and user-friendly programming languages. Python makes it simple to create smart contracts and offers a wide range of libraries for developers to use.

In this tutorial, we’ll use Python and Solidity to establish a straightforward DAO on the Celo blockchain. The DAO will have fundamental capabilities including generating proposals, voting, and carrying out judgments determined by member votes. We’ll cover the following along the way:

  • Understanding DAOs: To start, let’s explore what DAOs are, how they came to be, and why people think they’re a revolutionary paradigm of organizational governance.

  • Celo’s introduction: We’ll give a brief introduction to Celo, explain how it works, and explain why it’s a potential platform for implementing DAOs.

  • Develop your Solidity writing skills by becoming familiar with smart contracts, which will form the basis of our DAO.

  • Python-based interaction with Celo: We’ll look at how to deploy our DAO’s smart contract to the Celo network using Python and how to communicate with it by submitting proposals, casting votes, and carrying out decisions.

You will have a working DAO running on the Celo blockchain by the end of this course, and you will be well-equipped with the knowledge and abilities to create more complex DAOs and blockchain applications. This lesson contains something for everyone, whether you are a seasoned developer trying to improve your blockchain programming abilities or a novice just starting started with smart contracts.

Let’s jump in and get to work!

Prerequisites

You should have the following prerequisites in order to follow this tutorial:

  • Python programming language fundamentals

  • Basic familiarity with the Celo Blockchain Platform

  • Basic understanding of the creation of smart contracts

  • Basic familiarity with Web3 Library

Requirements

  • Your machine must have Python 3.7 or greater installed.

  • A code editor or IDE (such as PyCharm or Visual Studio Code)

  • A wallet that works with Celo, such as MetaMask or Valora

  • A test account on the Celo network with some test cash (available through the Celo Faucet)

Step 1: Setting up a development environment

First, we need to set up a development environment for our DAO.

On your terminal, use the following commands to create a new folder for your project:


  mkdir celo-dao
    
  cd celo-dao

In your new directory, Let’s now establish and activate a virtual Python environment.


  python3 -m venv venv
  
  source venv/bin/activate

Next, Install the following libraries using pip, the Python package manager:

  
  pip install py-solcx web3 python-dotenv

Step 2: Write the subscription service smart contract

Next, let’s create a new file named Dao.sol in the project’s root directory and paste the following code in it to construct our smart contract:

Dao.sol


// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract Dao {
    struct Proposal {
        string description;
        uint256 voteCount;
        bool executed;
        address payable recipient;
        uint256 amount;
    }

    address public owner;

    mapping(address => uint256) public tokens;

    mapping(uint256 => Proposal) public proposals;

    mapping(address => mapping(uint256 => bool)) public votes;

    uint256 public proposalCount;

    event ProposalCreated(uint256 proposalId, string description);

    event Voted(uint256 proposalId, address voter);

    event ProposalExecuted(uint256 proposalId);

    modifier onlyOwner() {
        require(msg.sender == owner, "Not the contract owner");

        _;
    }

    modifier hasNotVoted(uint256 proposalId) {
        require(
            !votes[msg.sender][proposalId],
            "Already voted on this proposal"
        );

        _;
    }

    modifier proposalExists(uint256 proposalId) {
        require(proposalId < proposalCount, "Proposal does not exist");

        _;
    }

    constructor() {
        owner = msg.sender;
    }

    function addTokens(address to, uint256 amount) external onlyOwner {
        tokens[to] += amount;
    }

    function createProposal(
        string calldata description,
        address payable recipient,
        uint256 amount
    ) external {
        proposals[proposalCount] = Proposal(
            description,
            0,
            false,
            recipient,
            amount
        );

        emit ProposalCreated(proposalCount, description);

        proposalCount++;
    }

    function vote(
        uint256 proposalId
    ) external hasNotVoted(proposalId) proposalExists(proposalId) {
        require(tokens[msg.sender] > 0, "No tokens to vote");

        proposals[proposalId].voteCount += tokens[msg.sender];

        votes[msg.sender][proposalId] = true;

        emit Voted(proposalId, msg.sender);
    }

    function executeProposal(
        uint256 proposalId
    ) external proposalExists(proposalId) {
        Proposal storage proposal = proposals[proposalId];

        require(!proposal.executed, "Proposal already executed");

        require(
            proposal.voteCount > (address(this).balance / 2),
            "Not enough votes"
        );

        proposal.executed = true;

        // Executing the proposal's action

        proposal.recipient.transfer(proposal.amount);

        emit ProposalExecuted(proposalId);
    }
}


Let’s go through the smart contract code above to understand what’s going on:


    struct Proposal {
        string description;
        uint256 voteCount;
        bool executed;
        address payable recipient;
        uint256 amount;
    }

Each proposal in the DAO will be represented by a Proposal struct, which is defined by the structure. A proposal has a description, a vote total, a flag indicating whether it will be carried out, the recipient address, and the money at stake.

    address public owner;

    mapping(address => uint256) public tokens;

    mapping(uint256 => Proposal) public proposals;

    mapping(address => mapping(uint256 => bool)) public votes;

    uint256 public proposalCount;

These are variables that store the contract’s current state. owner maintains ownership records for the contract. Each address’s token holdings are recorded in the tokens database. Proposal structs are mapped to proposal IDs in proposals. Votes is a mapping that shows if an address has cast a ballot for a proposition. The quantity of proposals is tracked by proposalCount.


    event ProposalCreated(uint256 proposalId, string description);
    
    event Voted(uint256 proposalId, address voter);
    
    event ProposalExecuted(uint256 proposalId);

These are the events that are released when a proposal is made, considered for voting, or carried out.


    modifier onlyOwner() {
        require(msg.sender == owner, "Not the contract owner");

        _;
    }

    modifier hasNotVoted(uint256 proposalId) {
        require(
            !votes[msg.sender][proposalId],
            "Already voted on this proposal"
        );

        _;
    }

Functions can only run if these prerequisites are met. Only the contract’s owner can carry out some operations thanks to onlyOwner. An address cannot vote twice on the same proposal thanks to hasNotVoted. proposalExists Checks to see if the proposal already exists.


    constructor() {
        owner = msg.sender;
    }

When the contract is deployed, this function is only ever called once. The owner of the contract is changed to the address where the contract was deployed.


function addTokens(address to, uint256 amount) external onlyOwner {
        tokens[to] += amount;
    }

    function createProposal(
        string calldata description,
        address payable recipient,
        uint256 amount
    ) external {
        proposals[proposalCount] = Proposal(
            description,
            0,
            false,
            recipient,
            amount
        );

        emit ProposalCreated(proposalCount, description);

        proposalCount++;
    }

    function vote(
        uint256 proposalId
    ) external hasNotVoted(proposalId) proposalExists(proposalId) {
        require(tokens[msg.sender] > 0, "No tokens to vote");

        proposals[proposalId].voteCount += tokens[msg.sender];

        votes[msg.sender][proposalId] = true;

        emit Voted(proposalId, msg.sender);
    }

    function executeProposal(
        uint256 proposalId
    ) external proposalExists(proposalId) {
        Proposal storage proposal = proposals[proposalId];

        require(!proposal.executed, "Proposal already executed");

        require(
            proposal.voteCount > (address(this).balance / 2),
            "Not enough votes"
        );

        proposal.executed = true;

        // Executing the proposal's action

        proposal.recipient.transfer(proposal.amount);

        emit ProposalExecuted(proposalId);
    }


The contract has four primary functions:

  • AddTokens - This function enables the owner to add tokens to an address.

  • createProposal - Enables the creation of new proposals by anyone.

  • Voting - enables token holders to decide whether to support a proposition.

  • executeProposal - Executes a proposal if it receives more than half of the total votes, taking into account the remaining terms of the contract.

Step 3: Compile and deploy the smart contract on Celo

Next, let’s compile the DAO using web3.py and deploy on the celo blockhain. Create a new file called deploy.py in the root directory of your project and paste the follwing code:

deploy.py


import json
import os
from web3 import Web3
from dotenv import load_dotenv
from solcx import compile_standard, install_solc

load_dotenv()

# Install specific Solidity compiler version
install_solc("0.8.0")


# Set up web3 connection
provider_url = os.environ.get("CELO_PROVIDER_URL")
w3 = Web3(Web3.HTTPProvider(provider_url))
assert w3.is_connected(), "Not connected to a Celo node"

# Set deployer account and private key
deployer = os.environ.get("CELO_DEPLOYER_ADDRESS")
private_key = os.getenv("CELO_DEPLOYER_PRIVATE_KEY")


with open("Dao.sol", "r") as file:
    contract_source_code = file.read()


# Compile the contract
compiled_sol = compile_standard({
    "language": "Solidity",
    "sources": {
        "Dao.sol": {
            "content": contract_source_code
        }
    },
    "settings": {
        "outputSelection": {
            "*": {
                "*": ["metadata", "evm.bytecode", "evm.sourceMap"]
            }
        }
    }
})

# Extract the contract data
contract_data = compiled_sol['contracts']['Dao.sol']['Dao']
bytecode = contract_data['evm']['bytecode']['object']
abi = json.loads(contract_data['metadata'])['output']['abi']

# Deploy the contract
nonce = w3.eth.get_transaction_count(deployer)
transaction = {
    'nonce': nonce,
    'gas': 2000000,
    'gasPrice': w3.eth.gas_price,
    'data': bytecode,
}
signed_txn = w3.eth.account.sign_transaction(transaction, private_key)
transaction_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction)
transaction_receipt = w3.eth.wait_for_transaction_receipt(transaction_hash)

# Get the contract address
contract_address = transaction_receipt['contractAddress']
print(f"DAO Contract deployed at address: {contract_address}")


This Python script automates the construction and deployment of Solidity smart contracts onto the Celo blockchain. Among other necessary libraries, the script initially imports json, os, web3, dotenv, and solcx. Environment variables are loaded, and after that, the Solidity compiler is installed.

The script then establishes a provider URL-based connection to the Celo blockchain using the Web3 library. It confirms the connection and then sets the deployer’s account and private key from the environment variables.

The smart contract source code is then read from the Dao.sol file and built with the Solidity compiler. The script takes from the built contract the necessary deployment bytecode and ABI (Application Binary Interface).

Run the following code on your terminal to deploy the contract:


python deploy.py

Step 4: Interacting with the DAO

Lastly, lets create a python clientb that would interact with our DAO functions. Create a new file called client.py and paste the following code:

client.py


import os
from web3 import Web3, HTTPProvider
from web3.middleware import geth_poa_middleware
from dotenv import load_dotenv
import time

import deploy

# Load environment variables
load_dotenv()

# Set up web3 connection
provider_url = os.getenv("CELO_PROVIDER_URL")
w3 = Web3(HTTPProvider(provider_url))
assert w3.is_connected(), "Not connected to a Celo node"

# Add PoA middleware to web3.py instance
w3.middleware_onion.inject(geth_poa_middleware, layer=0)

# Get contract ABI and address
abi = deploy.abi
contract_address = deploy.contract_address
contract = w3.eth.contract(address=contract_address, abi=abi)

# Initialize account
account_address = os.getenv("CELO_DEPLOYER_ADDRESS")
private_key = os.getenv("CELO_DEPLOYER_PRIVATE_KEY")



# Function to create a proposal
def create_proposal(description, recipient, amount, private_key):
    
     # Ensure chainId matches the network you are using (e.g., 44787 for Alfajores Testnet)
    chain_id = 44787
    
    # Fetch the current gas price from the network
    current_gas_price = w3.eth.gas_price

    # Ensure that the nonce is correctly set
    nonce = w3.eth.get_transaction_count(account_address, 'pending')

    # Build the transaction
    txn = contract.functions.createProposal(description, recipient, amount).build_transaction({
        'chainId': chain_id,
        'gas': 2000000,
        'gasPrice': current_gas_price,
        'nonce': nonce
    })

    signed_txn = w3.eth.account.sign_transaction(txn, private_key)
    txn_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction)
    
    print(f"Proposal Created: {txn_hash.hex()}")


# Function to vote for a proposal
def vote(proposal_id, private_key):
     # Fetch the current gas price from the network and increase it to ensure quick mining
    current_gas_price = w3.eth.gas_price
    increased_gas_price = int(current_gas_price * 1.2)
    
    # Ensure that the nonce is correctly set
    nonce = w3.eth.get_transaction_count(account_address, 'pending')

    txn = contract.functions.vote(proposal_id).build_transaction({
        'chainId': 44787,
        'gas': 200000,
        'gasPrice': increased_gas_price,
        'nonce': nonce,
    })

    signed_txn = w3.eth.account.sign_transaction(txn, private_key)
    txn_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction)

    print(f"Voted: {txn_hash.hex()}")

# Function to execute a proposal
def execute_proposal(proposal_id, private_key):
     # Fetch the current gas price from the network and increase it to ensure quick mining
    current_gas_price = w3.eth.gas_price
    increased_gas_price = int(current_gas_price * 1.5)
    
    # Ensure that the nonce is correctly set
    nonce = w3.eth.get_transaction_count(account_address, 'pending')

    txn = contract.functions.executeProposal(proposal_id).build_transaction({
        'chainId': 44787,
        'gas': 200000,
        'gasPrice': increased_gas_price,
        'nonce': nonce,
    })

    signed_txn = w3.eth.account.sign_transaction(txn, private_key)
    txn_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction)

    print(f"Proposal Executed: {txn_hash.hex()}")

# Example Usage
description = ""
recipient =  "" # "RECIPIENT_ADDRESS"
amount = int(w3.to_wei(0.00001, 'ether'))  # 1 Ether

# Create a new proposal
create_proposal(description, recipient, amount, private_key)

# Vote for the proposal with id 0
vote(0, private_key)

# Execute the proposal with id 0
execute_proposal(0, private_key)

To communicate with a deployed smart contract on the Celo blockchain, use this Python script. It provides procedures for making, voting on, and carrying out proposals in a decentralized autonomous organization (DAO). The code is broken down as follows:

  • import the relevant modules and libraries.

Utilize load_dotenv() to load environment variables from a.env file.

  • Using the Web3 library and the provider URL learned from environment variables, establish a connection to the Celo node. It verifies the viability of the link.

  • Web3 instance should be updated with Proof of Authority (PoA) middleware. This is necessary to communicate with specific Ethereum-based networks that employ the PoA consensus algorithm.

  • The deploy module is presuming that the contract’s ABI (Application Binary Interface) and address are defined elsewhere (presumably containing deployment details).

  • Initialize the user’s account by getting the deployer’s address and private key from environment variables.

Functions:

  1. create_proposal: This function, create_proposal, adds a new proposal to the contract.
  • accepts as parameters the description, recipient, amount, and private key.

  • sets the chain ID, retrieves the account nonce, and finds the gas price at the moment.

  • uses the contract function to build and sign the transaction in order to produce a proposal.

  • prints the transaction hash and sends the transaction to the network.

  1. Vote: This feature enables a user to cast their ballot for a proposal.
  • accepts the private key and proposal ID as inputs.

  • Similar to create_proposal, but uses the proposal ID to call the Vote method in the contract.

  • A higher gas price has been implemented to ensure faster mining.

  1. execute_proposal: If a proposal receives enough votes,
  • the execute_proposal function puts it into action.

  • accepts the private key and proposal ID as inputs comparable to vote to ensure faster mining.

Example of Use:

  • The script creates a proposal, votes on it, and then tries to carry it out to show how to use these functions. The proposal must include details on the recipient, amount, and description.

Run the following code on your terminal to see how our Python client interacted with the functions in our DAO:


python client.py

Conclusion

This concludes our tutorial on using Python to create a Decentralized Autonomous Organization (DAO) on the Celo blockchain. We’ve talked about the fundamental ideas behind DAOs, the Celo platform, and how to use Python to create and work with smart contracts. You ought to have a working DAO installed on Celo by this point and have learned useful information about creating blockchain-based applications.

Next Steps

  • Improve the DAO: The DAO we made is simple. By including capabilities like delegation, more intricate proposal kinds, or the integration of a DAO treasury, the functionality can be increased.

  • While Celo was the subject of this tutorial, there are many more blockchains to investigate. If you want to create DAOs on other platforms like Ethereum, Polkadot, or Binance Smart Chain, you might want to adapt the knowledge you learned here.

  • Dive Into Security: When creating smart contracts, security is of the utmost importance. Learn as much as you can about smart contract security best practices and common weaknesses. Finding security flaws in smart contracts can be made much easier with the help of programs like Mythril, Slither, and Manticore.

About the Author

I am a Software engineer with an interest in Blockchain technology. I love picking up new technologies and sharing my knowledge as I learn as a way of giving back to the tech community. You can find one on GitHub and Twitter.

References

  1. Solidity Documentation

  2. Web3.py Documentation

  3. Celo SDK Documentation

  4. ConsenSys Smart Contract Best Practices

  5. Github repo

2 Likes

Congratulations on your proposal being chosen as a standout this week at Celo Academy! As you prepare your tutorial, please ensure you follow our guidelines found here: Celo Sage Guidelines and Best Practices. Thank you for helping improve the experience of the developers learning at Celo Academy.

3 Likes

I will do a review for you

2 Likes

Alright, Thank you. I’ll be waiting

1 Like

@yafiabiyyu you can check it now. I have made the changes

1 Like

Good job @divinef837, I really didn’t know I can use solcx programmatically. I am so used to CLI :smile:

11 Likes

oh, i use solx for most of my python web3 projects but i want to try out Foundry

2 Likes