WEB3DEV

Cover image for Dominando Bibliotecas no Solidity
Fatima Lima
Fatima Lima

Posted on

Dominando Bibliotecas no Solidity

Bem-vindo de volta! Neste artigo, exploraremos o fascinante mundo das bibliotecas no Solidity. Descobriremos as vantagens de utilizar bibliotecas, nos aprofundaremos em sua implementação e descobriremos outros aspectos fascinantes. Prepare-se para uma jornada esclarecedora ao mergulharmos no reino das bibliotecas do Solidity.

Pré-requisitos

Antes de se aprofundar no assunto, é fundamental ter um conhecimento básico da linguagem de programação Solidity. O Solidity é usado para escrever contratos inteligentes em cadeias compatíveis com EVM. Se você já tem experiência ou familiaridade com o Solidity e com o desenvolvimento de contratos inteligentes, está bem preparado para explorar mais o assunto. No entanto, se você for novo no Solidity, recomendo consultar a documentação e os recursos disponíveis aqui para se familiarizar com a linguagem antes de prosseguir. Esse conhecimento básico aumentará sua compreensão dos conceitos discutidos neste artigo.

O que são bibliotecas?

CENÁRIO 1

Digamos que você seja um chef habilidoso preparando um delicioso banquete em uma cozinha movimentada. Ao preparar vários pratos de dar água na boca, você percebe que muitas receitas pedem um molho específico ou uma mistura de temperos. Em vez de medir e misturar repetidamente os mesmos ingredientes para cada receita, você decide criar uma "mistura principal" que possa ser usada em vários pratos. Essa mistura principal serve como uma biblioteca de sabores que você pode adicionar facilmente a diferentes receitas, conforme necessário.

Agora, imagine que você esteja criando um aplicativo descentralizado (DApp), como um comércio on-line, na blockchain da Ethereum. Você se depara com vários cálculos matemáticos exigidos pelo seu contrato inteligente. Em vez de escrever o mesmo código aritmético várias vezes, você pode criar uma biblioteca. Essa biblioteca funciona como sua "mistura principal" de funções matemáticas que podem ser usadas em diferentes partes do seu contrato inteligente. Ao fazer isso, você economiza tempo e esforço, assim como faria na cozinha, mantendo a consistência e a eficiência em todo o seu código.

CENÁRIO 2

Imagine que você esteja trabalhando em um projeto de blockchain de grande escala que envolva vários contratos inteligentes interagindo entre si. Um dos requisitos do seu projeto é implementar um mecanismo de autenticação complexo que envolva criptografia, descriptografia e autenticação de usuário, por exemplo.

Para conseguir isso, você pode criar uma biblioteca dedicada especificamente a lidar com a lógica de autenticação. Essa biblioteca pode conter funções para criptografar e descriptografar os dados do usuário, verificar as credenciais do usuário e gerenciar o controle de acesso. Ao criar uma biblioteca para essa lógica de autenticação, você garante que ela seja aplicada de forma consistente em todos os contratos inteligentes do seu projeto.

Agora, vamos considerar um cenário em que você tenha vários contratos inteligentes que exijam autenticação. Por exemplo, você pode ter um contrato para registro de usuário, um contrato para realizar transações e um contrato para acessar informações confidenciais. Em vez de duplicar o código de autenticação em cada um desses contratos, você pode simplesmente importar e utilizar a biblioteca de autenticação.

Ao usar a biblioteca, você obtém reutilização de código, pois a lógica de autenticação é centralizada em um único local. Todas as atualizações ou aprimoramentos do mecanismo de autenticação podem ser feitos na biblioteca, e todos os contratos inteligentes que usam a biblioteca se beneficiarão automaticamente dessas alterações. Essa abordagem não apenas reduz a duplicação de código, mas também melhora a capacidade de manutenção do código e reduz as chances de introdução de erros entre vários contratos inteligentes.

Definição de uma Biblioteca

De acordo com os cenários mencionados acima, podemos inferir que as bibliotecas no Solidity são um tipo de contrato inteligente que contém partes reutilizáveis de código. Essa reutilização permite que vários contratos aproveitem com eficiência a funcionalidade fornecida pela biblioteca sem duplicar o código em cada contrato. Elas são semelhantes para os contratos, mas diferem na forma como são implantadas e usadas.

Vantagens do uso de Bibliotecas

As bibliotecas permitem a reutilização de trechos de código comuns em vários contratos inteligentes, reduzindo a redundância, promovendo o desenvolvimento de código modular e também:

  • Eficiência/Otimização de Gas: Ao usar bibliotecas, você pode otimizar os custos de gas e o tamanho do contrato, evitando a duplicação de código. Isso resulta em contratos inteligentes mais eficientes e econômicos.
  • Organização do Código: As bibliotecas ajudam a organizar o código, separando a lógica reutilizável em entidades autônomas. Isso melhora a legibilidade, a capacidade de manutenção e a estrutura geral do código.
  • Segurança e Consistência: Ao usar bibliotecas, você pode garantir que as funcionalidades essenciais, como autenticação ou cálculos aritméticos, sejam implementadas de forma consistente e segura em vários contratos.
  • Desenvolvimento Simplificado: A alavancagem das bibliotecas simplifica o processo de desenvolvimento, fornecendo componentes de código predefinidos e testados que podem ser facilmente integrados aos contratos inteligentes.
  • Colaboração e Padronização: As bibliotecas fomentam a colaboração e a padronização na comunidade de desenvolvedores, oferecendo uma coleção compartilhada de funcionalidades que podem ser utilizadas em vários projetos. Um ótimo exemplo disso é o uso disseminado da biblioteca SafeMath do OpenZeppelin. Os desenvolvedores frequentemente integram essa biblioteca em seus projetos, inclusive eu, para garantir cálculos precisos e seguros em contratos inteligentes. Ao confiar em bibliotecas amplamente adotadas, como a SafeMath, os desenvolvedores podem evitar erros e algumas vulnerabilidades em seu código, ao mesmo tempo em que se beneficiam da expertise e das práticas recomendadas implementadas nessas bibliotecas confiáveis. Isso promove consistência, confiabilidade e eficiência no desenvolvimento de contratos inteligentes em diferentes projetos do ecossistema de blockchain.

Restrições das Bibliotecas

As bibliotecas possuem restrições das seguintes maneiras:

  • As bibliotecas não podem herdar nem ser herdadas.
  • As bibliotecas não podem receber ether, portanto, as funções receive e fallback não podem estar em uma biblioteca.
  • As bibliotecas não podem ser destruídas.

Há uma concepção errônea de que as bibliotecas não podem ter variáveis de estado. Para esclarecer melhor isso, dê uma olhada no artigo de Nick Mudgen.

Como definir e importar bibliotecas no Solidity

Defina um novo contrato usando a palavra-chave library .

pragma solidity ^0.8.9;

library MyLibrary {
   // Código e funções aqui
}
Enter fullscreen mode Exit fullscreen mode

Implemente as funções reutilizáveis desejadas no contrato da biblioteca da seguinte forma:

library MyLibrary {
   function add(uint256 a, uint256 b) internal pure returns (uint256) {
       return a + b;
   }
}
Enter fullscreen mode Exit fullscreen mode

Certifique-se de que as funções da biblioteca estejam marcadas como internal para indicar que elas só podem ser acessadas por contratos que usam a biblioteca.

Salve o contrato da biblioteca em um arquivo separado .sol, semelhante aos contratos comuns.

Nos contratos que precisam usar a biblioteca, importe o arquivo da biblioteca usando a declaração import.

import "./MyLibrary.sol";
Enter fullscreen mode Exit fullscreen mode

Em um caso em que o arquivo contenha várias bibliotecas, você pode usar chaves {}na instrução de importação, especificando qual biblioteca gostaria de importar. Por exemplo, digamos que temos 3 bibliotecas diferentes em um arquivo como este:

pragma solidity ^0.8.9;

library Library1 {
   // Código e funções aqui
}

library Library2 {
   // Código e funções aqui
}

library Library3 {
   // Código e funções aqui
}
Enter fullscreen mode Exit fullscreen mode

E você deseja usar duas das bibliotecas em seu código de contrato. Basta especificar qual biblioteca você gostaria de importar da seguinte forma:

pragma solidity ^0.8.9;

import {Library1, Library3} from "./LibraryFolder.sol";

contract ThisContract{
  //código do contrato
}
Enter fullscreen mode Exit fullscreen mode

Uso de bibliotecas em seu contrato inteligente

No contrato principal, declare o uso da biblioteca usando a palavra-chave using ou ‘*’ seguida do nome da biblioteca.

“using LibraryName for Type”

Essa diretiva permite anexar funções de biblioteca (da biblioteca LibraryName) a um tipo específico (Type) dentro do contrato, o que permite acessar as funções da biblioteca e usá-las dentro do contrato.

Por exemplo:

// Usar a biblioteca para números inteiros sem sinal
using MathLib for uint;
// Usar a biblioteca para qualquer tipo de dados
using MathLib for *;
Enter fullscreen mode Exit fullscreen mode

Em ambos os casos mencionados acima, todas as funções da biblioteca são anexadas ao contrato, mesmo que o tipo do primeiro parâmetro não corresponda diretamente ao tipo do objeto. O tipo é verificado quando a função é chamada e a resolução de excesso de função é realizada adequadamente.

Ao incluir uma biblioteca dessa forma, seus tipos de dados e funções de biblioteca se tornam acessíveis sem a necessidade de código adicional. Quando você chamar uma função de biblioteca, essas funções receberão o objeto em que são chamadas como primeiro parâmetro. Exemplo:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

library MathLib {

   function add(uint256 a, uint256 b) internal pure returns (uint256) {
       return a + b;
   }






}
Enter fullscreen mode Exit fullscreen mode

Usando a MathLibrary no seu contrato:

import {MathLib} from  './MathLibrary.sol';

contract MyContract{

  using MathLib for uint;
    uint a = 10;
    uint b= 10;

    uint c = a.add(b);
}
Enter fullscreen mode Exit fullscreen mode

Bibliotecas com Funções Internas e Bibliotecas com Funções Externas

Ao usar bibliotecas Solidity com funções externas, elas são implantadas como contratos separados e independentes na blockchain. Depois que a biblioteca é implantada na blockchain, é atribuído a ela um endereço específico. Esse endereço torna-se, então, um ponto de referência para que outros contratos na rede Ethereum utilizem as funcionalidades da biblioteca. Os contratos que utilizam essas bibliotecas usam DELEGATECALL para chamar as funções externas nessas bibliotecas.

As bibliotecas Solidity que contêm exclusivamente funções internas não são implantadas como contratos separados e independentes na blockchain. Em vez disso, as funções internas dessas bibliotecas são adicionadas diretamente aos contratos que as utilizam. Essas funções internas tornam-se parte do contrato que é implantado. Diferentemente das funções externas, as funções internas são executadas usando uma instrução JUMP em vez do mecanismo DELEGATECALL.

Delegatecall é uma função de nível baixo que permite que um contrato delegue sua chamada a outro contrato (tomando emprestada a funcionalidade de outro contrato) e, ao mesmo tempo, preserve seu próprio armazenamento e contexto. Para saber mais sobre delegatecall, leia meu artigo aqui.

O código de operação JUMP desempenha um papel fundamental na alteração do contador do programa, permitindo que a execução salte para um ponto diferente dentro do código implantado. A instrução JUMP facilita a navegação e a execução eficientes de diferentes partes do código em um contrato inteligente.

Exemplo de Biblioteca Solidity com função interna

Digamos que temos o "MyContract" que chama a função interna "doSomething()" da biblioteca do Solidity ‘Library1’.

// Library1.sol
library Library1 {
   function doSomething() internal pure returns (uint256) {
       // Lógica da função Interna
       return 42;
   }
}

// MyContract.sol
pragma solidity ^0.8.9;

import "./Library1.sol";

contract MyContract {
   function execute() public pure returns (uint256) {
       uint256 result = Library1.doSomething();
       return result;
   }
}
Enter fullscreen mode Exit fullscreen mode

Nesse exemplo acima, a Library1 contém a função interna doSomething(). O contrato MyContract importa a Library1 e chama a função doSomething(). Eis o que acontece durante a compilação e implantação:

  • Os contratos MyContract e Library1 são compilados em bytecodes separados.
  • O bytecode da função doSomething() da Library1 é adicionado ao bytecode do MyContract.
  • O bytecode combinado, que inclui a função interna, é implantado num contrato inteligente, que é o MyContract.

Assim, somente o MyContract é implantado na blockchain e a função doSomething() torna-se parte do bytecode do MyContract.

Exemplo de Biblioteca Solidity com função Externa

Consideremos um cenário em que o MyContract2 chama uma função externa stake() da biblioteca Library2do Solidity.

// Library2.sol
pragma solidity ^0.8.9;

library Library2 {
   function stake() external pure returns (uint256) {
       // lógica da função externa 
       return 10;
   }
}

// MyContract2.sol
pragma solidity ^0.8.9;

import "./Library2.sol";

contract MyContract2 {
   address public libraryAddress;

   constructor(address _libraryAddress) {
       libraryAddress = _libraryAddress;
   }

   function executeStake() public view returns (uint256) {
       // Faz DELEGATECALL para a função stake () da Library2
       (bool success, bytes memory result) = libraryAddress.delegatecall(abi.encodeWithSignature("stake()"));
       require(success, "DELEGATECALL to Library2 failed");
       uint256 stakeResult = abi.decode(result, (uint256));
       return stakeResult;
   }
}
Enter fullscreen mode Exit fullscreen mode

Nesse exemplo acima, o contrato MyContract2 importa a Library2 e possui um construtor que aceita o endereço do contrato Library2 implantado. O contrato MyContract2 inclui a função executeStake() que faz um DELEGATECALL para a função stake() na Library2. Eis o que acontece durante a compilação e implantação:

  • MyContract2 é compilado em bytecode.
  • Library2 é compilado em bytecode.
  • Library2 é implantado como seu próprio contrato na blockchain.
  • O bytecode do MyContract2 é atualizado com o endereço Ethereum do contrato Library2 implantado. Isso permite ao MyContract2 fazer um DELEGATECALL para o contrato Library2.
  • MyContract2 é implantado para a blockchain.

Nesse cenário, dois contratos são implantados: o MyContract2 e o Library2. MyContract2 inclui o endereço Ethereum do contrato Library2 implantado em seu bytecode, permitindo que ele faça DELEGATECALLs para executar a função externa stake() da Library2.

Custo do gas ao usar a biblioteca com função interna e função externa

O custo de gas do uso de uma biblioteca Solidity com funções internas é normalmente menor em comparação com o uso de uma biblioteca Solidity com funções externas.

Quando usar uma biblioteca com funções internas:

  • As funções internas são adicionadas ao bytecode do contrato durante a compilação e a implantação.
  • As chamadas de função são executadas diretamente no contrato, de forma semelhante às chamadas de função comuns. Isso evita a sobrecarga de DELEGATECALL e o custo de gas associado.

Por outro lado, ao usar uma biblioteca com funções externas:

  • A biblioteca é implantada como um contrato separado na blockchain.
  • O contrato de chamada precisa fazer DELEGATECALL para invocar as funções externas na biblioteca. E o DELEGATECALL incorre em um custo adicional de gas devido à despesa geral de passagem de mensagens e execução em um contexto diferente.

Em geral, a biblioteca Solidity com funções internas costuma ser mais eficiente do que usar uma biblioteca com funções externas. No entanto, o custo real do gas pode variar dependendo da implementação específica e dos padrões de uso.

Segurança: Implementação da biblioteca SafeMath para Evitar Overflow/Underflow de Números Inteiros

A função de transferência no contrato fornecido abaixo carece de proteção contra possíveis erros de overflow/underflow de número inteiro. Especificamente, ao subtrair o '_value' de 'balances[msg.sender]' e adicioná-lo a 'balances[_to]', há o risco de encontrar underflow, o que pode ter efeitos prejudiciais sobre a funcionalidade e a segurança do contrato.

O overflow de números inteiros ocorre quando o resultado de uma operação aritmética excede o valor máximo que pode ser armazenado no tipo de dados, enquanto o underflow ocorre quando o resultado está abaixo do valor mínimo. Esses problemas podem levar a comportamentos inesperados e vulnerabilidades de segurança em contratos inteligentes.

Para minimizar esses riscos, é fundamental implementar medidas preventivas, como a biblioteca SafeMath do Openzeppelin. A SafeMath oferece operações aritméticas seguras, realizando verificações abrangentes nos cálculos para evitar overflow/underflow. Ao incorporar a SafeMath ou bibliotecas semelhantes ao contrato, você pode garantir a integridade e a confiabilidade de suas operações, protegendo-se contra possíveis vulnerabilidades e aprimorando a robustez geral do contrato inteligente.

Bibliotecas Populares

OpenZeppelin

Rede Modular

Conclusão

As bibliotecas são uma ferramenta poderosa para os desenvolvedores, que permitem criar códigos modulares e reutilizáveis que podem simplificar significativamente o processo de desenvolvimento e melhorar a qualidade do código. Ao aproveitar as bibliotecas de forma eficaz, os desenvolvedores podem criar contratos inteligentes mais eficientes, dimensionáveis e passíveis de manutenção em plataformas de blockchain.

Referência

Solidity Documentation on Libraries

Esse artigo foi escrito por Oluwatosin Serah e traduzido por Fátima Lima. O original pode ser lido aqui.

Top comments (0)