WEB3DEV

Cover image for Contratos Timelock e Segurança DeFi: Lições do Compound
Adriano P. Araujo
Adriano P. Araujo

Posted on

Contratos Timelock e Segurança DeFi: Lições do Compound


Foto de Justin Veenema no Unsplash

Olá, pessoal incrível da web! 👋

Preparem-se, porque, neste artigo, vamos dar um passeio pela estrada dos contratos inteligentes do Timelock. Vamos descascar as camadas:

Contratos Timelock - o que exatamente são?
O motivo por trás de sua existência.
Uma visão equilibrada de seus prós e contras.
E, é claro, como o protocolo Compound coloca os Timelocks para funcionar.

Contratos Timelock em Português Simples

Os contratos inteligentes Timelock introduzem um mecanismo para atrasar a execução de funções.

Esses contratos usam modificadores que alteram o funcionamento das funções, garantindo que permaneçam inativas até que um período predefinido tenha decorrido.

Exemplo de um modificador:

modifier onlyBefore(uint time) {

        if (block.timestamp >= time) revert TooLate(time);

        _;

    }

Enter fullscreen mode Exit fullscreen mode

A Motivação

No mundo das blockchains, a governança on-chain é o método preferido para fazer alterações em contratos inteligentes. Mas há um problema - pode ser um pouco arriscado.

Imagine um atacante surgindo com um empréstimo-relâmpago (flash loan) e fazendo uma proposta sorrateira ou um fundador agindo por conta própria e fazendo alterações sem consultar ninguém. Para evitar esses pesadelos, projetos como Uniswap e Compound empregam Timelocks.⏰

Veja como funciona: quando há uma proposta para ajustar as configurações do protocolo, ela precisa passar pelo Timelock. Esse Timelock coloca um período de espera definido (normalmente dois dias) para a execução da proposta.

Isso dá à comunidade tempo para reagir - eles podem cancelar a proposta ou sair do sistema, se necessário. Os Timelocks são como a rede de segurança para a governança on-chain.⛓️

Mas não para por aí. Digamos que você tenha uma plataforma de empréstimo que utiliza um oráculo de preços on-chain para calcular quanto os usuários podem pegar emprestado.

Um atacante poderia manipular esse oráculo para inflar o valor dos ativos usados como garantia. Para evitar isso, você pode impor um pequeno atraso (como um bloco) entre depósitos e empréstimos.

Esse atraso torna mais difícil para o atacante realizar suas artimanhas, mantendo seu sistema seguro e sólido.🤌

Desvantagens dos Timelocks

Atrasos Curtos: os Timelocks são eficazes apenas se o período de atraso for adequado. Um atraso muito curto pode permitir que propostas maliciosas passem despercebidas, como no atraso de um dia do Beanstalk.
Controle Privilegiado: a entidade que implanta o Timelock pode manter o controle. Isso pode levar a abusos, com alguém alterando o atraso em seu benefício.
Dependência de Timestamp: depender de marcas temporais de blocos para execução pode ser problemático. Produtores de blocos podem manipular marcas temporais.

Análise de Código

Vamos dar uma olhada mais de perto no código do Timelock do protocolo Compound. Você pode acessar o código seguindo este link: aqui.

Nossos principais objetivos com este contrato são:

Gerenciar Transações: entender como as transações são enfileiradas, executadas e canceladas.
Ajustar o Atraso: aprender como atualizar o período de atraso para a execução de transações.
Mudar o Admin: ver como o controle do contrato muda.
Código:

contract Timelock {
    using SafeMath for uint;

    event NewAdmin(address indexed newAdmin);
    event NewPendingAdmin(address indexed newPendingAdmin);
    event NewDelay(uint indexed newDelay);
    event CancelTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature,  bytes data, uint eta);
    event ExecuteTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature,  bytes data, uint eta);
    event QueueTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta);
}
Enter fullscreen mode Exit fullscreen mode

Implementamos o SafeMath em nosso código para garantir operações aritméticas seguras e temos eventos listados para os contratos inteligentes.

 uint public constant GRACE_PERIOD = 14 days;
    uint public constant MINIMUM_DELAY = 2 days;
    uint public constant MAXIMUM_DELAY = 30 days;

    address public admin;
    address public pendingAdmin;
    uint public delay;

    mapping (bytes32 => bool) public queuedTransactions;
Enter fullscreen mode Exit fullscreen mode

Este código define constantes para os períodos de carência, atraso mínimo e máximo, define os endereços do administrador e do administrador pendente, armazena o atraso atual e mantém um mapeamento para rastrear as transações enfileiradas por seu hash.

  constructor(address admin_, uint delay_) public {
        require(delay_ >= MINIMUM_DELAY, "Timelock::constructor: Delay must exceed minimum delay.");
        require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay.");

        admin = admin_;
        delay = delay_;
    }

    fallback() external payable { }

    function setDelay(uint delay_) public {
        require(msg.sender == address(this), "Timelock::setDelay: Call must come from Timelock.");
        require(delay_ >= MINIMUM_DELAY, "Timelock::setDelay: Delay must exceed minimum delay.");
        require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay.");
        delay = delay_;

        emit NewDelay(delay);
    }
Enter fullscreen mode Exit fullscreen mode

As funções acima são autoexplicativas.

    function setPendingAdmin(address pendingAdmin_) public {
        require(msg.sender == address(this), "Timelock::setPendingAdmin: Call must come from Timelock.");
        pendingAdmin = pendingAdmin_;

        emit NewPendingAdmin(pendingAdmin);
    }

    function acceptAdmin() public {
        require(msg.sender == pendingAdmin, "Timelock::acceptAdmin: Call must come from pendingAdmin.");
        admin = msg.sender;
        pendingAdmin = address(0);

        emit NewAdmin(admin);
    }
Enter fullscreen mode Exit fullscreen mode

Essas duas funções gerenciam a propriedade do contrato.

Observação: Somente o contrato pode chamar setAdminPendente 🧐?

Sim! Esse controle sobre as mudanças de administrador está alinhado com o conceito de governança on-chain, onde tais ações são tratadas como transações e requerem enfileiramento.

 function queueTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public returns (bytes32) {
        require(msg.sender == admin, "Timelock::queueTransaction: Call must come from admin.");
        require(eta >= getBlockTimestamp().add(delay), "Timelock::queueTransaction: Estimated execution block must satisfy delay.");

        bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
        queuedTransactions[txHash] = true;

        emit QueueTransaction(txHash, target, value, signature, data, eta);
        return txHash;
    }

    function cancelTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public {
        require(msg.sender == admin, "Timelock::cancelTransaction: Call must come from admin.");

        bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
        queuedTransactions[txHash] = false;

        emit CancelTransaction(txHash, target, value, signature, data, eta);
    }

    function executeTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public payable returns (bytes memory) {
        require(msg.sender == admin, "Timelock::executeTransaction: Call must come from admin.");

        bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
        require(queuedTransactions[txHash], "Timelock::executeTransaction: Transaction hasn't been queued.");
        require(getBlockTimestamp() >= eta, "Timelock::executeTransaction: Transaction hasn't surpassed time lock.");
        require(getBlockTimestamp() <= eta.add(GRACE_PERIOD), "Timelock::executeTransaction: Transaction is stale.");

        queuedTransactions[txHash] = false;

        bytes memory callData;

        if (bytes(signature).length == 0) {
            callData = data;
        } else {
            callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
        }

        // solium-desabilitado-na-próxima-linha security/não-chama-valor
        (bool success, bytes memory returnData) = target.call{value: value}(callData);
        require(success, "Timelock::executeTransaction: Transaction execution reverted.");

        emit ExecuteTransaction(txHash, target, value, signature, data, eta);

        return returnData;
    }

    function getBlockTimestamp() internal view returns (uint) {
        // solium-desabilitado-na-próxima-linha //segurança/sem-blocos-membros
        return block.timestamp;
    }
Enter fullscreen mode Exit fullscreen mode

Aqui está a funcionalidade principal:

Enfileirar Transação: o admin pode solicitar o enfileiramento de uma transação especificando o endereço de destino, o valor, a assinatura da função, os dados e o tempo de execução estimado (eta). A solicitação é colocada em hash, marcada como enfileirada, e um evento é emitido para registrar a ação.
Cancelar Transação: o admin pode cancelar uma transação enfileirada fornecendo os mesmos parâmetros usados durante o enfileiramento. Essa ação simplesmente marca a transação como não enfileirada e a registra.
Executar Transação: novamente, apenas o admin pode executar uma transação enfileirada. O contrato verifica se a transação foi enfileirada, se está dentro do prazo definido e se não está atrasada (dentro do período de carência). Se todas as verificações forem bem-sucedidas, a transação é executada e seu resultado é registrado.

É tão simples assim, e agora você tem uma visão de como os contratos inteligentes do Timelock funcionam em vários protocolos. 🕒💡

Espero sinceramente que este post tenha sido informativo e tenha ajudado você a entender o Timelock. Muito obrigado por dedicar seu tempo para lê-lo!

Se você achou este post útil, considere compartilhá-lo com seus amigos e colegas. Seu apoio significa muito para mim 🥳.

Siga-me no Twitter!


Este artigo foi escrito por Srinivas Joshi e traduzido por Adriano P. de Araujo. O original em inglês pode ser encontrado aqui.

Top comments (0)