Construindo uma Organização Autônoma Descentralizada (DAO) a partir do zero

Apr 30 2023
Organizações Autônomas Descentralizadas (DAOs) são uma pedra angular do ecossistema descentralizado. DAOs são organizações regidas por contratos inteligentes e cujos processos de tomada de decisão são executados de forma descentralizada.

Organizações Autônomas Descentralizadas (DAOs) são uma pedra angular do ecossistema descentralizado. DAOs são organizações regidas por contratos inteligentes e cujos processos de tomada de decisão são executados de forma descentralizada. Neste artigo, vamos nos aprofundar nos conceitos avançados do Solidity para construir um DAO do zero.

Crédito | Kevin Laminto

Visão geral da estrutura DAO

Nosso DAO consistirá nos seguintes componentes

  1. Um contrato inteligente que representa o próprio DAO.
  2. Um token que representa poder de votação e propriedade dentro do DAO.
  3. Um mecanismo de proposta para enviar e votar propostas.
  4. Uma tesouraria para gerir os fundos e executar as propostas aprovadas.

Para seguir este tutorial, você deve ter uma compreensão básica do ambiente de desenvolvimento Solidity, Ethereum e Truffle.

Passo 1: Criando o token DAO

Primeiro, vamos criar um novo token ERC20 para nosso DAO. Este token será usado para representar o poder de voto dentro da organização.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract DAOToken is ERC20 {
    constructor(uint256 initialSupply) ERC20("DAO Token", "DAO") {
        _mint(msg.sender, initialSupply);
    }
}

Passo 2: Construindo o Contrato DAO

Em seguida, vamos criar o contrato DAO principal. Crie um novo arquivo chamadoDAO.sol

// SPDX-License-Identifier: MIT
prasgma solidity ^0.8.0;

import "./DAOToken.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract DAO is Ownable {
    using EnumerableSet for EnumerableSet.AddressSet;

    // The DAO token contract
    DAOToken public daoToken;

    // The minimum amount of tokens required to create a proposal
    uint256 public constant MIN_PROPOSAL_THRESHOLD = 1000 * 10**18;

    // The minimum amount of tokens required to vote on a proposal
    uint256 public constant MIN_VOTING_THRESHOLD = 100 * 10**18;

    // Proposal struct
    struct Proposal {
        uint256 id;
        address proposer;
        string description;
        uint256 amount;
        address payable recipient;
        uint256 startTime;
        uint256 endTime;
        uint256 yesVotes;
        uint256 noVotes;
        EnumerableSet.AddressSet voters;
        bool executed;
    }

    // Array of all proposals
    Proposal[] public proposals;

    // Mapping to check if an address has an active proposal
    mapping(address => bool) public activeProposals;

    // Event for a new proposal
    event NewProposal(uint256 indexed proposalId, address indexed proposer, string description);

    // Event for a proposal execution
    event ProposalExecuted(uint256 indexed proposalId, address indexed proposer, address indexed recipient, uint256 amount);

    constructor(DAOToken _daoToken) {
        daoToken = _daoToken;
    }

    // Function to create a new proposal
    function createProposal(string memory _description, uint256 _amount, address payable _recipient) external {
        require(daoToken.balanceOf(msg.sender) >= MIN_PROPOSAL_THRESHOLD, "Insufficient tokens to create proposal");
        require(!activeProposals[msg.sender], "You already have an active proposal");
    
        Proposal memory newProposal = Proposal({
            id: proposals.length,
            proposer: msg.sender,
            description: _description,
            amount: _amount,
            recipient: _recipient,
            startTime: block.timestamp,
            endTime: block.timestamp + 7 days,
            yesVotes: 0,
            noVotes: 0,
            voters: new EnumerableSet.AddressSet(),
            executed: false
        });
    
        proposals.push(newProposal);
        activeProposals[msg.sender] = true;
        emit NewProposal(newProposal.id, msg.sender, _description);
    }
    
    // Function to vote on a proposal
    function vote(uint256 _proposalId, bool _support) external {
        require(daoToken.balanceOf(msg.sender) >= MIN_VOTING_THRESHOLD, "Insufficient tokens to vote");
        Proposal storage proposal = proposals[_proposalId];
        require(block.timestamp >= proposal.startTime && block.timestamp <= proposal.endTime, "Invalid voting period");
        require(!proposal.voters.contains(msg.sender), "You have already voted on this proposal");
    
        uint256 voterWeight = daoToken.balanceOf(msg.sender);
        if (_support) {
            proposal.yesVotes += voterWeight;
        } else {
            proposal.noVotes += voterWeight;
        }
    
        proposal.voters.add(msg.sender);
    }
    
    // Function to execute a proposal
    function executeProposal(uint256 _proposalId) external {
        Proposal storage proposal = proposals[_proposalId];
        require(!proposal.executed, "Proposal has already been executed");
        require(block.timestamp > proposal.endTime, "Voting period is still ongoing");
        require(proposal.yesVotes > proposal.noVotes, "Proposal has not reached majority support");
    
        proposal.recipient.transfer(proposal.amount);
        proposal.executed = true;
        activeProposals[proposal.proposer] = false;
        emit ProposalExecuted(_proposalId, proposal.proposer, proposal.recipient, proposal.amount);
    }
    
    // Function to withdraw funds from the DAO
    function withdraw(uint256 _amount) external onlyOwner {
        payable(owner()).transfer(_amount);
    }
    
    // Fallback function to accept Ether
    receive() external payable {}
}

  • O contrato de token DAO é importado e armazenado como uma variável.
  • Uma estrutura de proposta é definida com os campos necessários, como ID da proposta, proponente, descrição, valor, destinatário e detalhes de votação.
  • Uma matriz armazena todas as propostas e um mapeamento acompanha as propostas ativas.
  • As funções são criadas para lidar com a criação, votação e execução da proposta.

Agora que criamos o contrato DAO, vamos implantá-lo e testar sua funcionalidade. Primeiro, vamos criar um arquivo de migração para nossos contratos

const DAOToken = artifacts.require("DAOToken"); const DAO = artifacts.require("DAO");

module.exports = async function (deployer) {
  // Deploy the DAOToken contract with an initial supply of 1,000,000 tokens
  await deployer.deploy(DAOToken, "1000000" + "0".repeat(18));
  const daoTokenInstance = await DAOToken.deployed();
  
  // Deploy the DAO contract with a reference to the DAOToken contract
  await deployer.deploy(DAO, daoTokenInstance.address);
  const daoInstance = await DAO.deployed();
};


const { assert } = require("chai");
const { expectRevert, time } = require("@openzeppelin/test-helpers");
const DAOToken = artifacts.require("DAOToken");
const DAO = artifacts.require("DAO");

contract("DAO", ([deployer, user1, user2, recipient]) => {
  beforeEach(async () => {
    this.daoToken = await DAOToken.new("1000000" + "0".repeat(18), { from: deployer });
    this.dao = await DAO.new(this.daoToken.address, { from: deployer });
  });

  it("should create a proposal and vote on it", async () => {
    // Transfer tokens to user1
    await this.daoToken.transfer(user1, "1100" + "0".repeat(18), { from: deployer });

    // User1 creates a proposal
    await this.dao.createProposal("Fund project X", "100" + "0".repeat(18), recipient, {
      from: user1,
    });

    // Check the proposal details
    const proposal = await this.dao.proposals(0);
    assert.equal(proposal.id, "0");
    assert.equal(proposal.proposer, user1);
    assert.equal(proposal.description, "Fund project X");
    assert.equal(proposal.amount, "100" + "0".repeat(18));
    assert.equal(proposal.recipient, recipient);

    // User1 votes on the proposal
    await this.dao.vote(0, true, { from: user1 });

    // Check the voting results
    assert.equal((await this.dao.proposals(0)).yesVotes, "1100" + "0".repeat(18));
    assert.equal((await this.dao.proposals(0)).noVotes, "0");
    
    // Fast-forward time to after the voting period
    await time.increase(time.duration.days(8));
    
    // Execute the proposal
    await this.dao.executeProposal(0, { from: user1 });
    
    // Check that the proposal has been executed
    assert.equal((await this.dao.proposals(0)).executed, true);
  });

  it("should not allow a user with insufficient tokens to create a proposal", async () => {
    // Try to create a proposal with insufficient tokens
    
    await expectRevert(
      this.dao.createProposal("Fund project X", "100" + "0".repeat(18), recipient, { from: user2 }),
      "Insufficient tokens to create proposal"
    );
  });
  // Add more tests as necessary
});

  • Criando uma proposta e votando nela.
  • Garantir que um usuário com tokens insuficientes não possa criar uma proposta. Você pode executar os testes usando o seguinte comando:

Você pode usar uma interface da Web para interagir com seu DAO implantado.

Os principais componentes da interface da web podem incluir

  • Um painel exibindo o saldo do token DAO e a lista de propostas.
  • Um formulário para criar uma nova proposta.
  • Uma interface de votação para votar em propostas existentes.
  • Um botão para executar as propostas aprovadas.

O potencial da Web 3.0 e dos aplicativos descentralizados é vasto e oferece oportunidades empolgantes para os desenvolvedores moldarem o futuro da Internet. Aproveitando o poder da tecnologia blockchain, podemos criar aplicativos mais seguros, transparentes e resilientes que capacitam os usuários e promovem a descentralização.

Como desenvolvedor, você tem a chance de contribuir para o crescimento do ecossistema descentralizado e criar soluções inovadoras para resolver problemas do mundo real. Encorajo você a explorar as inúmeras possibilidades que as tecnologias web3 oferecem, desde finanças descentralizadas (DeFi) e tokens não fungíveis (NFTs) até armazenamento descentralizado e gerenciamento de identidade.