Skip to content

Criação de uma Organização Autônoma Descentralizada (DAO) do zero

Criação de uma Organização Autônoma Descentralizada (DAO) do zero

As DAOs (Decentralized Autonomous Organizations, Organizações Autônomas Descentralizadas) são a base do ecossistema descentralizado. As DAOs são organizações governadas 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 criar uma DAO do zero._

Image

Visão geral da estrutura de uma DAO

Nossa DAO consistirá nos seguintes componentes

  1. Um contrato inteligente que representa a própria DAO.
  2. Um token que representa o poder de voto e a propriedade dentro da DAO.
  3. Um mecanismo de proposta para enviar e votar em propostas.
  4. Uma tesouraria para gerenciar fundos e executar propostas que tenham sido aprovadas.

Pré-requisitos

Para seguir este tutorial, você deve ter um conhecimento básico de Solidity, Ethereum e do ambiente de desenvolvimento Truffle.

Etapa 1: Criação do token da DAO

Primeiro, vamos criar um novo token ERC20 para a nossa DAO. Esse 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);

    }

}

Neste contrato, estamos usando a biblioteca OpenZeppelin para criar um novo token ERC20. O construtor recebe o suprimento inicial como parâmetro e cria os tokens para o implantador.

Etapa 2: Criação do contrato da DAO

Em seguida, vamos criar o contrato principal da DAO. Crie um novo arquivo chamado DAO.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;

    // O contrato do token da DAO

    DAOToken public daoToken;

    // A quantidade mínima de tokens necessária para criar uma proposta

    uint256 public constant MIN_PROPOSAL_THRESHOLD = 1000 * 10**18;

    // A quantidade mínima de tokens necessária para votar em uma proposta

    uint256 public constant MIN_VOTING_THRESHOLD = 100 * 10**18;

    // Estrutura da proposta

    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;

    }

    // Lista de todas as propostas

    Proposal[] public proposals;

    // Mapping para verificar se um endereço tem uma proposta ativa

    mapping(address => bool) public activeProposals;

    // Evento para uma nova proposta

    event NewProposal(uint256 indexed proposalId, address indexed proposer, string description);

    // Evento para a execução de uma proposta

    event ProposalExecuted(uint256 indexed proposalId, address indexed proposer, address indexed recipient, uint256 amount);

    constructor(DAOToken _daoToken) {

        daoToken = _daoToken;

    }

    // Função para criar uma nova proposta

    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);

    }

    

    // Função para votar em uma proposta

    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);

    }

    

    // Função para executar uma proposta

    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);

    }

    

    // Função de retirada de fundos da DAO

    function withdraw(uint256 _amount) external onlyOwner {

        payable(owner()).transfer(_amount);

    }

    

    // Função de fallback para aceitar Ether

    receive() external payable {}

}

Neste contrato, definimos a estrutura principal da DAO

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

Etapa 3: implantando e testando a DAO

Agora que criamos o contrato da 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) {

  // Implantar o contrato DAOToken com um fornecimento inicial de 1.000.000 tokens

  await deployer.deploy(DAOToken, "1000000" + "0".repeat(18));

  const daoTokenInstance = await DAOToken.deployed();

 

  // Implantar o contrato da DAO com uma referência ao contrato DAOToken

  await deployer.deploy(DAO, daoTokenInstance.address);

  const daoInstance = await DAO.deployed();

};

Agora vamos adicionar alguns testes para garantir que nosso contrato da DAO esteja funcionando conforme o esperado.


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ência de tokens para o usuário1

    await this.daoToken.transfer(user1, "1100" + "0".repeat(18), { from: deployer });

    // Usuário1 cria uma proposta

    await this.dao.createProposal("Fund project X", "100" + "0".repeat(18), recipient, {

      from: user1,

    });

    //Verifiqua os detalhes da proposta

    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);

    // Usuário1 vota na proposta

    await this.dao.vote(0, true, { from: user1 });

    // Verifica os resultados da votação

    assert.equal((await this.dao.proposals(0)).yesVotes, "1100" + "0".repeat(18));

    assert.equal((await this.dao.proposals(0)).noVotes, "0");

    

    // Avanço rápido para depois do período de votação

    await time.increase(time.duration.days(8));

    

    // Executa a proposta

    await this.dao.executeProposal(0, { from: user1 });

    

    // Verifica se a proposta foi executada

    assert.equal((await this.dao.proposals(0)).executed, true);

  });

  it("should not allow a user with insufficient tokens to create a proposal", async () => {

    // Tentativa de criar uma proposta com tokens insuficientes

    

    await expectRevert(

      this.dao.createProposal("Fund project X", "100" + "0".repeat(18), recipient, { from: user2 }),

      "Insufficient tokens to create proposal"

    );

  });

  // Adicione mais testes conforme necessário

});

Neste arquivo de teste, abordamos dois testes principais

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

Etapa 4: Interagindo com a DAO

Você pode usar uma interface da Web para interagir com a DAO implantada.

Os principais componentes da interface da Web podem incluir

  • Um painel que exibe o saldo do token da 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 propostas aprovadas.

Conclusão

O potencial da Web 3.0 e dos aplicativos descentralizados é vasto e oferece oportunidades interessantes para os desenvolvedores moldarem o futuro da Internet. Ao aproveitar 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. Eu o incentivo 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.

Artigo escrito por Faucet Consumer. A versão original pode ser encontrada aqui. Traduzido e adaptado por Dimitris Calixto.