WEB3DEV

Cover image for Carteira multi-assinaturas (Solidity)
Panegali
Panegali

Posted on

Carteira multi-assinaturas (Solidity)

Imagine que uma organização entre em contato com você como desenvolvedor de contratos inteligentes. Eles desejam um contrato no qual ethers (pagamento) possam ser recebidos com base nos serviços prestados.

Agora, a organização possui um conselho de diretores e, antes que um saque possa ser feito do contrato, 70% do número total de diretores precisará aprovar tal transação.

Neste artigo, vou mostrar a você como criar um contrato inteligente que lida com essas demandas.

Vamos entrar em ação. Configure uma pasta e adicione um arquivo MultiSig.sol.


// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

contract MultiSig {
}

Enter fullscreen mode Exit fullscreen mode

Como um desenvolvedor experiente de contratos inteligentes, você sabe o que é esse trecho. Para iniciantes, a primeira linha é um identificador para indicar a licença do nosso contrato inteligente. A segunda linha começa com a palavra-chave pragma e a versão do compilador Solidity. A última linha é apenas uma definição do nosso contrato.

Vamos definir os tipos de dados, tratamento de erros e modificador para o nosso contrato. Vou colocar comentários para ajudar na explicação.


// O struct da transação contém os detalhes de qualquer um de nossos administradores 
// que faz uma solicitação de saque

struct Transaction {
     address spender;
        uint amount;
        uint numberOfApproval;
        bool isActive;
    }

// Um array de administradores (conselho de diretores)
address[] Admins;

// Número mínimo para administradores
uint constant MINIMUM = 3;

// Identificador da transação
uint transactionId;

// Um mapeamento para verificar se um endereço é o de um identificador
mapping(address => bool) isAdmin;

// Um mapeamento de um identificador de transação para uma Transação
mapping(uint => Transaction) transaction;

// Um mapeamento 2D para verificar se um endereço (administrador) já
// aprovou uma transação, para evitar o desperdício de gás
mapping(uint => mapping(address => bool)) hasApproved;

// Uma verificação de erro personalizada para endereço inválido ao adicionar administradores
error InvalidAddress(uint position);

// Uma verificação de erro personalizada para o número mínimo de administradores a serem adicionados
error InvalidAdminNumber(uint number);

// Uma verificação de erro personalizada para o número mínimo de administradores a serem adicionados
error duplicate(address _addr);

// Um evento a ser emitido quando uma solicitação de saque é feita
event Create(address who, address spender, uint amount);

// Uma verificação para garantir que apenas um administrador possa prosseguir com uma ação
modifier onlyAdmin() {
    require(isAdmin[msg.sender], "Não é um administrador válido");
    _;
}

Enter fullscreen mode Exit fullscreen mode

Até agora, ótimo trabalho! Criamos o layout do nosso contrato.

Agora, queremos que nosso contrato contenha administradores antes da implantação e, para conseguir isso, precisaremos adicionar a lógica ao nosso construtor. O código definido dentro do construtor será executado apenas uma vez, no momento em que o contrato for criado e implantado na rede.


constructor(address[] memory _admins) payable {
    // Verifica se os administradores a serem adicionados
    // são mais do que 2
    if (_admins.length < MINIMUM) {
        revert InvalidAdminNumber(MINIMUM);
    }

    for (uint i = 0; i < _admins.length; i++) {
        // Verifica se nenhum dos endereços
        // é um endereço zero
        if (_admins[i] == address(0)) {
            revert InvalidAddress(i + 1);
        }

        // Verifica se não há duplicatas nos endereços
        if (isAdmin[_admins[i]]) {
            revert duplicate(_admins[i]);
        }

        // Mapeia o endereço fornecido para “valor verdadeiro”
        isAdmin[_admins[i]] = true;
    }

    // Define Admins para os endereços dos administradores fornecidos
    Admins = _admins;
}

Enter fullscreen mode Exit fullscreen mode

O que estamos fazendo com o trecho de código? Basicamente, estamos instruindo o contrato a adicionar os administradores (endereços) que forneceremos antes da implantação. Temos uma verificação que reverte o processo se o endereço fornecido estiver abaixo do nosso número mínimo declarado na variável de estado.

Também temos uma verificação para endereços duplicados e endereço zero. Dentro do nosso construtor, definimos os administradores antes da implantação.

Teremos cinco funções como bases em nosso contrato inteligente (você pode decidir adicionar mais com base nos requisitos que pretende implementar).

Vamos defini-las;


function createTransaction(uint amount, address _spender) external onlyOwner {}
function approveTransaction(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

Então, começaremos com a função createTransaction. Basicamente, a função terá o montante e o beneficiário como entrada, apenas os administradores podem criar uma transação (saque).


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

    // Exigir que o montante a ser sacado seja menor que o saldo do contrato
    require(amount <= address(this).balance, "Saldo insuficiente!!");
    transactionId++;
    Transaction storage _transaction = transaction[transactionId];
    _transaction.amount = amount;
    _transaction.spender = _spender;
    _transaction.isActive = true;

    // Emitir evento de criação
    emit Create(msg.sender, _spender, amount);

    // O administrador que cria uma transação é o primeiro a aprovar tal transação
    approveTransaction(transactionId);
}

Enter fullscreen mode Exit fullscreen mode

Usando o trecho de código acima, o contrato recebe dois argumentos (beneficiário e montante). Temos nosso modificador apenas para administradores e também verificamos se o montante não é maior que o saldo do nosso contrato. Em seguida, criamos nossa transação usando a struct Transaction que definimos anteriormente. Emitimos nosso evento de criação.

A próxima função é a approveTransaction, onde os administradores podem aprovar a transação.


function approveTransaction(uint id) public onlyAdmin {
    // Verificar se o administrador ainda não aprovou
    require(!hasApproved[id][msg.sender], "Já aprovado!!");

    Transaction storage _transaction = transaction[id];

    // Exigir que a transação esteja ativa (existente)
    require(_transaction.isActive, "Não está ativa");

    // Aumentar as aprovações da transação
    _transaction.numberOfApproval += 1;
    hasApproved[id][msg.sender] = true;
    uint count = _transaction.numberOfApproval;

    // Um método auxiliar que calcula se o número de aprovações
    // é superior a 70%, então a transação é aprovada finalmente e ethers são enviados
    uint MinApp = calculateMinimumApproval();
    if (count >= MinApp) {
        sendTransaction(id);
    }
}

Enter fullscreen mode Exit fullscreen mode

Vamos adicionar funções que enviam transações (quando 70% dos administradores tiverem aprovado) e aquela que calcula os setenta por cento. Ambas as funções serão privadas (apenas acessíveis dentro do contrato em que são definidas).


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

Então, nossa última função, mas não menos importante, é uma função para ajudar a ler o estado atual de uma transação existente. Isso permitirá que qualquer um dos administradores visualize o estado de uma transação. Detalhes como o gastador, número de aprovações atuais, valor solicitado e status da transação podem ser visualizados.


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

Enter fullscreen mode Exit fullscreen mode

Lembre-se, nosso contrato é um contrato que recebe ethers, então devemos adicionar uma função de recebimento.

A função de recebimento é semelhante à função de fallback, mas é projetada especificamente para lidar com ether recebido sem a necessidade de uma chamada de dados. Não é uma função obrigatória para um contrato, mas pode ser útil para lidar com ether enviado diretamente para o contrato sem uma chamada de função.


receive() external payable {}

Enter fullscreen mode Exit fullscreen mode

Ótimo trabalho. Seguindo os passos acima, você criou um contrato inteligente que recebe ethers e permite retiradas apenas quando uma condição é atendida (aprovação de setenta por cento dos administradores). Você pode expandir este contrato, estendendo suas funcionalidades e lógica.

Deixe um comentário, aplauda e siga para mais artigos que ofereçam descrições detalhadas de contratos inteligentes, conceitos Web3, Solidity, etc.

Artigo escrito por FusionTech | Academy. Traduzido por Marcelo Panegali

Latest comments (0)