WEB3DEV Español

Cover image for Construyendo una Organización Autónoma Descentralizada (DAO) desde Cero
Paulo Gio
Paulo Gio

Posted on

Construyendo una Organización Autónoma Descentralizada (DAO) desde Cero

Las Organizaciones Autónomas Descentralizadas (DAOs) son una piedra angular del ecosistema descentralizado. Las DAOs son organizaciones gobernadas por contratos inteligentes y cuyos procesos de toma de decisiones se ejecutan de manera descentralizada. En este artículo, profundizaremos en conceptos avanzados de Solidity para construir una DAO desde cero.

https://miro.medium.com/v2/resize:fit:1400/format:webp/1*CpPWX5zbgcuJYlZWkuKfpg.jpeg

Crédito | Kevin Laminto

Visión General de la Estructura de la DAO

Nuestra DAO consistirá de los siguientes componentes:

  1. Un contrato inteligente que representa a la propia DAO.
  2. Un token que representa el poder de voto y la propiedad dentro de la DAO.
  3. Un mecanismo de propuesta para presentar y votar sobre propuestas.
  4. Un tesoro para gestionar fondos y ejecutar propuestas que han sido aprobadas.

Prerrequisitos

Para seguir este tutorial, debes tener un conocimiento básico de Solidity, Ethereum y el entorno de desarrollo de Truffle.

Paso 1: Creación del Token de la DAO

Primero, vamos a crear un nuevo token ERC20 para nuestra DAO. Este token se utilizará para representar el poder de voto dentro de la organización.

// 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);
   }
}
Enter fullscreen mode Exit fullscreen mode

En este contrato, estamos utilizando la biblioteca OpenZeppelin para crear un nuevo token ERC20. El constructor toma el suministro inicial como un parámetro y acuña los tokens para el implementador.

Paso 2: Construyendo el Contrato DAO

A continuación, construyamos el contrato DAO principal. Crea un nuevo archivo llamado DAO.sol.

// SPDX-License-Identifier: MIT
pragma 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;

    // El contrato del token de la DAO
    DAOToken public daoToken;

    // Cantidad mínima de tokens requeridos para crear una propuesta
    uint256 public constant MIN_PROPOSAL_THRESHOLD = 1000 * 10**18;

    // Cantidad mínima de tokens requeridos para votar en una propuesta
    uint256 public constant MIN_VOTING_THRESHOLD = 100 * 10**18;

    // Estructura de Propuesta
    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 las propuestas
    Proposal[] public proposals;

    // Mapeo para verificar si una dirección tiene una propuesta activa
    mapping(address => bool) public activeProposals;

    // Evento para una nueva propuesta
    event NewProposal(uint256 indexed proposalId, address indexed proposer, string description);

    // Evento para la ejecución de una propuesta
    event ProposalExecuted(uint256 indexed proposalId, address indexed proposer, address indexed recipient, uint256 amount);

    constructor(DAOToken _daoToken) {
        daoToken = _daoToken;
    }

    // Función para crear una nueva propuesta
    function createProposal(string memory _description, uint256 _amount, address payable _recipient) external {
        require(daoToken.balanceOf(msg.sender) >= MIN_PROPOSAL_THRESHOLD, "Tokens insuficientes para crear propuesta");
        require(!activeProposals[msg.sender], "Ya tienes una propuesta activa");

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

    // Función para votar en una propuesta
    function vote(uint256 _proposalId, bool _support) external {
        require(daoToken.balanceOf(msg.sender) >= MIN_VOTING_THRESHOLD, "Tokens insuficientes para votar");
        Proposal storage proposal = proposals[_proposalId];
        require(block.timestamp >= proposal.startTime && block.timestamp <= proposal.endTime, "Periodo de votacion invalido");
        require(!proposal.voters.contains(msg.sender), "Ya has votado en esta propuesta");

        uint256 voterWeight = daoToken.balanceOf(msg.sender);
        if (_support) {
            proposal.yesVotes += voterWeight;
        } else {
            proposal.noVotes += voterWeight;
        }

        proposal.voters.add(msg.sender);
    }

    // Función para ejecutar una propuesta
    function executeProposal(uint256 _proposalId) external {
        Proposal storage proposal = proposals[_proposalId];
        require(!proposal.executed, "La propuesta ya ha sido ejecutada");
        require(block.timestamp > proposal.endTime, "El periodo de votacion aun esta en curso");
        require(proposal.yesVotes > proposal.noVotes, "La propuesta no ha alcanzado el apoyo de la mayoria");

        proposal.recipient.transfer(proposal.amount);
        proposal.executed = true;
        activeProposals[proposal.proposer] = false;
        emit ProposalExecuted(_proposalId, proposal.proposer, proposal.recipient, proposal.amount);
    }

    // Función para retirar fondos de la DAO
    function withdraw(uint256 _amount) external onlyOwner {
        payable(owner()).transfer(_amount);
    }

    // Función de fallback para aceptar Ether
    receive() external payable {}
}
Enter fullscreen mode Exit fullscreen mode

En este contrato, definimos la estructura principal de la DAO:

  • Se importa el contrato del token de la DAO y se guarda como una variable.
  • Se define una estructura de propuesta con campos necesarios como el id de la propuesta, el proponente, la descripción, la cantidad, el destinatario y los detalles de la votación.
  • Un array almacena todas las propuestas, y un mapeo lleva un registro de las propuestas activas.
  • Se crean funciones para manejar la creación de propuestas, la votación y la ejecución.

Paso 3: Desplegar y Probar la DAO

Ahora que hemos construido el contrato de la DAO, vamos a desplegarlo y probar su funcionalidad. Primero, creemos un archivo de migración para nuestros contratos.

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

module.exports = async function (deployer) {
  // Despliega el contrato DAOToken con un suministro inicial de 1,000,000 de tokens
  await deployer.deploy(DAOToken, "1000000" + "0".repeat(18));
  const daoTokenInstance = await DAOToken.deployed();

  // Despliega el contrato DAO con una referencia al contrato DAOToken
  await deployer.deploy(DAO, daoTokenInstance.address);
  const daoInstance = await DAO.deployed();
};
Enter fullscreen mode Exit fullscreen mode

Ahora agregamos algunas pruebas para asegurarnos de que nuestro contrato DAO funcione como se esperaba.

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 () => {
    // Crear nuevos tokens y contratos DAO
    this.daoToken = await DAOToken.new("1000000" + "0".repeat(18), { from: deployer });
    this.dao = await DAO.new(this.daoToken.address, { from: deployer });
  });

  it("debería crear una propuesta y votar en ella", async () => {
    // Transferir tokens a user1
    await this.daoToken.transfer(user1, "1100" + "0".repeat(18), { from: deployer });

    // User1 crea una propuesta
    await this.dao.createProposal("Financiar el proyecto X", "100" + "0".repeat(18), recipient, {
    from: user1,
    });

    // Verificar los detalles de la propuesta
    const proposal = await this.dao.proposals(0);
    assert.equal(proposal.id, "0");
    assert.equal(proposal.proposer, user1);
    assert.equal(proposal.description, "Financiar el proyecto X");
    assert.equal(proposal.amount, "100" + "0".repeat(18));
    assert.equal(proposal.recipient, recipient);

    // User1 vota en la propuesta
    await this.dao.vote(0, true, { from: user1 });

    // Verificar los resultados de la votación
    assert.equal((await this.dao.proposals(0)).yesVotes, "1100" + "0".repeat(18));
    assert.equal((await this.dao.proposals(0)).noVotes, "0");

    // Avanzar el tiempo hasta después del período de votación
    await time.increase(time.duration.days(8));

    // Ejecutar la propuesta
    await this.dao.executeProposal(0, { from: user1 });

    // Verificar que la propuesta ha sido ejecutada
    assert.equal((await this.dao.proposals(0)).executed, true);
  });

  it("no debería permitir que un usuario con tokens insuficientes cree una propuesta", async () => {
    // Intentar crear una propuesta con tokens insuficientes
    await expectRevert(
    this.dao.createProposal("Financiar el proyecto X", "100" + "0".repeat(18), recipient, { from: user2 }),
    "Tokens insuficientes para crear la propuesta"
    );
  });
  // Añadir más pruebas según sea necesario
});
Enter fullscreen mode Exit fullscreen mode

En este archivo de prueba, cubrimos dos pruebas clave:

  1. Crear una propuesta y votar sobre ella.

  2. Asegurar que un usuario con tokens insuficientes no pueda crear una propuesta.

Paso 4: Interactuando con la DAO

Puedes usar una interfaz web para interactuar con tu DAO desplegada.

Los componentes clave de la interfaz web podrían incluir:

  • Un tablero que muestra el saldo de tokens de la DAO y la lista de propuestas.
  • Un formulario para crear una nueva propuesta.
  • Una interfaz de votación para votar sobre propuestas existentes.
  • Un botón para ejecutar propuestas aprobadas.

Conclusión

El potencial de la web 3.0 y las aplicaciones descentralizadas es vasto y ofrece emocionantes oportunidades para que los desarrolladores den forma al futuro de internet. Al aprovechar el poder de la tecnología blockchain, podemos crear aplicaciones más seguras, transparentes y resilientes que empoderan a los usuarios y promueven la descentralización.

Como desarrollador, tienes la oportunidad de contribuir al crecimiento del ecosistema descentralizado y construir soluciones innovadoras para abordar problemas del mundo real. Te animo a explorar las numerosas posibilidades que ofrecen las tecnologías web3, desde finanzas descentralizadas (DeFi) y tokens no fungibles (NFTs) hasta almacenamiento descentralizado y gestión de identidad.

Artículo original publicado por Faucet Consumer. Traducción de Paulinho Giovannini.

Discussion (0)