WEB3DEV Español

Cover image for Cartera Multi Firma (Solidity)
Gabriella Alexandra Martinez Viloria
Gabriella Alexandra Martinez Viloria

Posted on

Cartera Multi Firma (Solidity)

Image description

Imagina que una organización se acerca a ti como desarrollador de contratos inteligentes y quieren un contrato donde los ethers (el pago) puede ser recibido, basado en los servicios que prestaron.

Ahora, la organización tiene una junta directiva y, antes que se pueda realizar la retirada, el 70% de los directores necesitan aprobar esa transacción.

En este artículo, te mostraré cómo crear un contrato inteligente que haga esas demandas.

Vamos a ello. Establece una carpeta y añade un archivo MultiSig.Sol

// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

contract MultiSig {
}
Enter fullscreen mode Exit fullscreen mode

Como un desarrollador de contratos inteligentes experimentado, ya sabes cuál es el fragmento. Para los principiantes, la primera línea es un identificador que denota la Licencia de nuestro contrato inteligente. La segunda línea comienza con el keyword pragma y la versión del compilador de Solidity. La última línea es sólo una definición de nuestro contrato.

Vamos a definir los tipos de datos, el manejo de errores y el modificador de nuestro contrato. Pondré comentarios para ayudar en la explicación.

//la estructura mantiene los detalles de cualquiera de nuestros administradores que
//haga un pedido para la retirada
struct Transaction {
       address spender;
       uint amount;
       uint numberOfApproval;
       bool isActive;
   }

   //un array de administradores (junta administrativa)
   address[] Admins;

   //la cantidad mínima de administradores
   uint constant MINIMUM = 3;

   //el identificador de la transacción
   uint transactionId;

   //un mapeo para revisar si la dirección es la de un identificador
   mapping(address => bool) isAdmin;

   //un mapeo de un identificador de transacción a un Mapeo de Transacción
  (uint => Transaction);

   //un mapeo 2d para revisar si una dirección (administrador) ya tiene
   //una dirección aprobada, para evitar gastar mapeo de gas
   (uint => mapping(address => bool)) hasApproved;

   //un error personalizado revisa una dirección inválida, cuando añades administradores
   error InvalidAddress(uint position);

   //un error personalizado revisa para que el mínimo de administradores sea añadido
   error InvalidAdminNumber(uint number);

   //un error personalizado revisa para que el mínimo de administradores sea añadido
   error duplicate(address _addr);

   //un evento que será emitido, cuando el pedido de retirar se haga
   event Create(address who, address spender, uint amount);

   //una revisión para asegurar que sólo un administrador pueda proceder en una acción de modificación
   onlyAdmin() {
       requiere(isAdmin[msg.sender], "Not a Valid Admin");
       _;
   }
Enter fullscreen mode Exit fullscreen mode

Buen trabajo, por ahora hemos creado el diseño de nuestro contrato.

Ahora, queremos que nuestro contrato contenga administradores antes del despliegue y para lograr eso, necesitamos añadir la lógica de nuestro constructor. El código definido dentro del constructor sólo lo ejecutará una vez, en el tiempo que el contrato es creado y desplegado en la red.

constructor(address[] memory _admins) payable {
       //revisa para asegurar que los administradores sean añadidos
       //sean más que 2
       if (_admins.length < MINIMUM) {
           revert InvalidAdminNumber(MINIMUM);
       }

       para (uint i = 0; i < _admins.length; i++) {
           //revisa para asegurarse que ninguna de las direcciones 
           //sea una dirección cero
           if (_admins[i] == address(0)) {
               revert InvalidAddress(i + 1);
           }

           //revisa para asegurarse que no haya duplicados en las direcciones
           if (isAdmin[_admins[i]]) {
               revert duplicate(_admins[i]);
           }

           //maps provided address to true value
           isAdmin[_admins[i]] = true;
       }

       //establece Admins a las direcciones de los administradores proporcionados
       Admins = _admins;
   }
Enter fullscreen mode Exit fullscreen mode

¿Qué estamos haciendo con el fragmento de código? Básicamente, le estamos diciendo al contrato que añada los administradores (direcciones) que proveerá antes del despliegue. Tenemos un revisar que revierte el proceso si la dirección proporcionada está abajo del número mínimo de la variable de estado declarada.

También tenemos un revisor para direcciones duplicadas y direcciones cero. Dentro de nuestro constructor, configuramos los administradores antes del despliegue.

Tendremos cinco (5) funciones como bases en nuestro contrato inteligente (puedes decidir añadir más, basados en los requisitos que intentas implementar).

Vamos a definirlo

function createTransaction(uint amount, address _spender) external onlyOwner {}
function aprroveTransaction(uint id) external {}
function sendtransaction(uint id) internal {}
function calculateMinimumApproval() internal {}
function getTransaction(uint id) external view returns (Transaction memory){}
Enter fullscreen mode Exit fullscreen mode

Así que, empezaremos con la función createTransaction. Básicamente, la función tendrá la cantidad y el gastador como entrada, sólo los administradores puedan crear la transacción (retirar);

function createTransaction(
       uint amount,
       address _spender
   ) external onlyAdmin {

       //Requiere que la cantidad a retirar sea menos que el balance del contrato
       require(amount <= address(this).balance, "Insufficient Funds!!");
       transactionId++;
       Transaction storage _transaction = transaction[transactionId];
       _transaction.amount = amount;
       _transaction.spender = _spender;
       _transaction.isActive = true;

       //emit crea un evento
       emit Create(msg.sender, _spender, amount);

       //el administrador que crea una transacción, es el primero en aprobar tal
       //transacción
       AprroveTransaction(transactionId);
   }
Enter fullscreen mode Exit fullscreen mode

Usando el fragmento de arriba, el contrato toma dos args (gastador y cantidad). Tenemos nuestro modificador sólo para el administrador, también revisamos si la cantidad no sea más que el balance de nuestro contrato. Luego, creamos nuestra transacción usando la estructura Transaction que definimos antes. Emite nuestro evento create.

La siguiente función es la transacción approve, donde los administradores pueden aprobar la transacción.

function AprroveTransaction(uint id) public onlyAdmin {
       //revisa si el administrador no haya aprobado aún
       require(!hasApproved[id][msg.sender], "Already Approved!!");

       Transaction storage _transaction = transaction[id];

       //requiere que la transacción sea activa (exista)
       require(_transaction.isActive, "Not active");

        //incrementa la aprobación de las transacción
       _transaction.numberOfApproval += 1;
       hasApproved[id][msg.sender] = true;
       uint count = _transaction.numberOfApproval;

       //un mini método que calcula si el número de aprobaciones
       //es hasta el 70%, luego la transacción es aprobada finalmente y los ether enviados
       uint MinApp = calculateMinimumApproval();
       if (count >= MinApp) {
           sendtransaction(id);
       }
   }
Enter fullscreen mode Exit fullscreen mode

Vamos a añadir las funciones que envían las transacciones (una vez que el 70% de los administradores estén aprobados) y el que calcula el setenta por ciento. Ambas funciones serán privadas (sólo accesibles dentro del contrato, si es definido).

function sendtransaction(uint id) private {
       Transaction storage _transaction = transaction[id];
       payable(_transaction.spender).transfer(_transaction.amount);
       _transaction.isActive = false;
   }

   function calculateMinimumApproval() private view returns (uint MinApp) {
       uint size = Admins.length;
       MinApp = (size * 70) / 100;
   }
Enter fullscreen mode Exit fullscreen mode

Luego nuestra última, pero no menos importante, función, una función que ayuda a leer el estatus actual en una transacción existente. Esto ayudará a cualquiera de los administradores para que vea el estatus de la transacción. Los detalles como el gastador, el número de aprobaciones actuales, la cantidad pedida, y la transacción del estatus que puede ser visto.

function getTransaction(
       uint id
   ) external view returns (Transaction memory) {
       return transaction[id];
   }
Enter fullscreen mode Exit fullscreen mode

Recuerda, nuestro contrato es un contrato que recibe ethers, así que deberíamos añadir una función de retirar.

La función de recibir es similar a la función fallback, pero está diseñada específicamente para manejar el ether entrante sin la necesidad de la llamada de datos. No es una función requerida por el contrato, pero puede ser útil para manejar ether que es enviado directamente al contrato, sin una función de llamada.

receive() external payable{}
Enter fullscreen mode Exit fullscreen mode

Buen trabajo. Siguiendo los pasos de arriba, haz creado un contrato inteligente que recibe ethers y luego sólo permite retirar, sólo cuando la condición se cumple (la aprobación del setenta por ciento de los administradores). Puedes construir en este contrato, extendiendo sus funcionalidades y la lógica.

Deja un comentario, aplaude y sígueme para más artículos donde doy descripciones detalladas de los contratos inteligentes, conceptos de la web3, solidity, etc.

Este artículo es una traducción de FusionTech, hecha por Gabriella Martínez. Puedes encontrar el artículo original aquí
Sería genial escucharte en nuestro Discord, puedes contarnos tus ideas, comentarios, sugerencias y dejarnos saber lo que necesitas.
Si prefieres puedes escribirnos a @web3dev_es en Twitter.

Discussion (0)