WEB3DEV

Cover image for Melhorias na Segurança do Solidity: Overflows e Underflows
Panegali
Panegali

Posted on • Atualizado em

Melhorias na Segurança do Solidity: Overflows e Underflows

Vamos analisar uma característica importante de segurança que você deve estar ciente ao escrever contratos inteligentes: Prevenção de transbordos e subfluxos.


Sumário
1 - O que é Overflow?

2 - Usando o SafeMath
..... . Vamos dar uma olhada no código por trás da SafeMath

3 - Usando a SafeMath em nosso código


O Que é Overflow?

Digamos que temos um uint8, que só pode ter 8 bits. Isso significa que o maior número que podemos armazenar é o binário 11111111 (ou em decimal, 2^8 - 1 = 255).

Dê uma olhada no seguinte código. O que é igual ao number no final?

uint8 number = 255;
number++;
Enter fullscreen mode Exit fullscreen mode

Neste caso, fizemos um overflow - então number agora é contraintuitivamente igual a 0, embora o tenhamos aumentado. (Se você acrescentar 1 ao binário 11111111, ele volta a 00000000, como um relógio que vai das 23:59 às 00:00).

Um underflow é semelhante, onde se você subtrair 1 de um uint8 que é igual a 0, ele agora será igual a 255 (porque os uints não são assinados, e não podem ser negativos).

Embora não estejamos usando uint8 aqui, e parece improvável que um uint256 overflow ao incrementar por 1 cada vez (2^256 é um número realmente grande), ainda é bom colocar proteções em nosso contrato para que nosso DApp nunca tenha um comportamento inesperado no futuro.


Usando o SafeMath

Para evitar isto, o OpenZeppelin criou uma biblioteca chamada SafeMath que evita estes problemas por padrão.

Mas antes de entrarmos nisso... O que é uma biblioteca?

Uma biblioteca é um tipo específico de contrato em Solidity. Uma das coisas para as quais ela é útil é anexar funções a tipos de dados nativos.

Por exemplo, com a biblioteca SafeMath, usaremos a sintaxe using SafeMath para uint256. A biblioteca SafeMath tem 4 funções - add, sub, mul, e div. E agora podemos acessar estas funções a partir do uint256 da seguinte forma:

using SafeMath for uint256;

uint256 a = 5;
uint256 b = a.add(3); // 5 + 3 = 8
uint256 c = a.mul(2); // 5 * 2 = 10
Enter fullscreen mode Exit fullscreen mode

Em breve veremos o que essas funções fazem.

Vamos dar uma olhada no código por trás da SafeMath:

library SafeMath {

 function mul(uint256 a, uint256 b) internal pure returns (uint256) {
   if (a == 0) {
     return 0;
   }
   uint256 c = a * b;
   assert(c / a == b);
   return c;
 }

 function div(uint256 a, uint256 b) internal pure returns (uint256) {
   // assert(b > 0); // Solidity automatically throws when dividing by 0
   uint256 c = a / b;
   // assert(a == b * c + a % b); // There is no case in which this doesn't hold
   return c;
 }

 function sub(uint256 a, uint256 b) internal pure returns (uint256) {
   assert(b <= a);
   return a - b;
 }

 function add(uint256 a, uint256 b) internal pure returns (uint256) {
   uint256 c = a + b;
   assert(c >= a);
   return c;
 }
}
Enter fullscreen mode Exit fullscreen mode

Primeiro temos a palavra-chave library - bibliotecas são semelhantes aos contracts, mas com algumas diferenças. Para os nossos propósitos, as bibliotecas nos permitem usar a palavra-chave using, que automaticamente se aplica a todos os métodos da biblioteca para outro tipo de dados:

using SafeMath for uint;
// now we can use these methods on any uint
uint test = 2;
test = test.mul(3); // test now equals 6
test = test.add(5); // test now equals 11
Enter fullscreen mode Exit fullscreen mode

Observe que as funções mul e add requerem 2 argumentos cada, mas quando declaramos using SafeMath for uint, o uint que chamamos da função em (test) é automaticamente passado como o primeiro argumento.

Vamos olhar para o código por trás de add para ver o que a SafeMath faz:

function add(uint256 a, uint256 b) internal pure returns (uint256) {
 uint256 c = a + b;
 assert(c >= a);
 return c;
}
Enter fullscreen mode Exit fullscreen mode

Basicamente, add adiciona apenas 2 uints como +, mas também contém uma declaração assert para garantir que a soma seja maior que a. Isto nos protege de overflows.

assert é semelhante à require, onde ela lançará um erro se for falsa. A diferença entre assert e require é que require restituirá ao usuário o resto de seu gas quando uma função falhar, enquanto que assert não o fará. Portanto, na maioria das vezes você quer usar require em seu código; assert é tipicamente usada quando algo mau correu com o código (como um overflow de uint).

Portanto, simplesmente coloque as funções da SafeMath, add, sub, mul e div, que são funções que fazem as 4 operações básicas da matemática, mas que mostram um erro se um overflow ou um underflow ocorrer.

Usando a SafeMath em nosso código.

Para evitar overflows e underflows, podemos procurar lugares em nosso código onde usamos +, -, *, ou /, e substituí-los por add, sub, mul, div.


Este artigo foi escrito por Alexandr Kumancev e traduzido por Marcelo Panegali. O artigo original pode ser encontrado aqui.

Latest comments (0)