WEB3DEV

Cover image for Solidity: Empacotando Uints com Bools
Paulo Gio
Paulo Gio

Posted on

Solidity: Empacotando Uints com Bools

Introdução

À medida que a tecnologia da blockchain e o desenvolvimento de contratos inteligentes continuam a evoluir, está se tornando cada vez mais importante considerar o custo de implantação e execução de contratos inteligentes. Uma maneira de fazer isso é empacotando vários valores booleanos em uma única variável uint256. Neste tutorial, vamos examinar de perto essa técnica e como ela pode ser usada para otimizar o armazenamento e o uso de gás de contratos inteligentes.

Contexto

Antes de mergulhar nos detalhes do empacotamento de variáveis uint256 com valores booleanos, é importante ter uma compreensão básica de Solidity e da blockchain Ethereum. Como a maioria de nós sabe, o Solidity é a linguagem de programação usada para escrever contratos inteligentes para a blockchain Ethereum. É uma linguagem de alto nível, orientada a contratos, semelhante ao JavaScript e projetada para a EVM. E todos nós sabemos que a EVM é o ambiente de tempo de execução para contratos inteligentes na blockchain Ethereum.

Agora, quando se trata de variáveis no Solidity, cada uma tem um tamanho fixo e ocupa um determinado espaço de armazenamento na EVM. Por exemplo, uma variável booleana ocupa 1 byte de armazenamento, enquanto uma variável uint256 ocupa 32 bytes. Como o espaço de armazenamento é muito caro na EVM, pode ser benéfico empacotar vários valores booleanos em uma única variável uint256 para economizar nos custos de armazenamento.

Empacotando variáveis uint256 com valores booleanos

Empacotar variáveis uint256 com valores booleanos é uma técnica que nos permite armazenar múltiplos valores booleanos em uma única variável uint256. Isso pode ser obtido usando operações bit a bit para definir e recuperar bits individuais na variável uint256. Ao utilizar operações bit a bit como “e”, “ou” e “deslocar para a esquerda”, podemos empacotar vários valores booleanos em uma única variável uint256 sem ocupar muito espaço de armazenamento na EVM. Podemos evitar vários booleanos únicos ou, pior ainda, arrays dinâmicos.

Por exemplo, em vez de usar uma variável separada para cada estágio de um processo, podemos empacotar todos os estados dos estágios em uma única variável uint256. Isso permite economizar espaço de armazenamento e também torna mais fácil atualizar e recuperar o estado de cada estágio.

É importante ter em mente que, ao empacotar valores booleanos, a ordem em que eles são empacotados pode afetar a legibilidade do código. Portanto, é uma boa prática documentar a ordem dos valores empacotados e usar nomes significativos de variáveis para melhorar a legibilidade do código. Um exemplo poderia ser uint 0 = QA, 1 = Entrega, 2 = Fornecedor, 3 = distribuição etc…

Neste tutorial, veremos um exemplo concreto de como empacotar valores booleanos em uma única variável, usando o contrato PackedBools como exemplo. Também cobriremos as melhores práticas e as escolhas a serem feitas ao usar esta técnica no desenvolvimento de contratos inteligentes.

Vamos para o código!

Para acompanhar este tutorial, você precisará configurar um ambiente de desenvolvimento para escrever e testar contratos inteligentes.

Instalando o Foundry

Primeiro, precisamos instalar o Foundry, uma ferramenta de linha de comando que facilita o desenvolvimento e o teste de contratos inteligentes. Existem algumas maneiras diferentes de instalar o Foundry, dependendo do seu sistema e preferências.

Opção 1: Usando o curl

curl -L https://foundry.paradigm.xyz | bash
Enter fullscreen mode Exit fullscreen mode

Opção 2: Usando o cargo

cargo install --git https://github.com/foundry-rs/foundry --profile local --locked foundry-cli anvil
Enter fullscreen mode Exit fullscreen mode

Opção 3: Usando o Docker

docker pull ghcr.io/foundry-rs/foundry:latest
Enter fullscreen mode Exit fullscreen mode

Criando um novo projeto

Depois de instalar o Foundry, você pode criar um novo projeto criando um novo diretório e movendo-o a partir de um terminal ou IDE.

mkdir packedBools
cd packedBools
Enter fullscreen mode Exit fullscreen mode

Você pode clonar meu repositório, mas não irá absorver tanto se não escrever o código à mão.

git clone https://github.com/Keyrxng/a-dApp-aDay .
Enter fullscreen mode Exit fullscreen mode

Para inicializar a pasta com um repositório padrão de modelo do Foundry, você pode usar o comando forge init. Este comando criará os arquivos e diretórios necessários para seu projeto, incluindo um diretório src, onde você colocará seu código do contrato inteligente.

forge init
Enter fullscreen mode Exit fullscreen mode

Você pode excluir os arquivos counter.sol e counter.t.sol dos diretórios src/ e test/, pois não os usaremos neste tutorial.

rm ./src/counter.sol
rm ./test/counter.t.sol
Enter fullscreen mode Exit fullscreen mode

Agora você configurou um ambiente de desenvolvimento e está pronto para redigir seu contrato inteligente.

Explicando o Código

O contrato PackedBools é um contrato inteligente que demonstra o conceito de empacotar valores booleanos em uma única variável uint256. O contrato possui diversas funções que permitem definir e recuperar o estado dos diferentes estágios de um processo. O contrato também possui várias variáveis de mapeamento e um array que são usados para armazenar os dados do contrato.

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*scJh6rSsj2W8HGEU15bYlg.png

Variáveis de mapeamento

O contrato possui duas variáveis de mapeamento, stageToErrors e stageToPaid, que são utilizadas para armazenar as informações de erro e pagamento de cada estágio (stage) de um processo. A primeira variável de mapeamento, stageToErrors, é um mapeamento de uint para um mapeamento de uint para bytes. Isso significa que para cada Item Id existe um mapeamento de Stage para Error.

A segunda variável de mapeamento, stageToPaid, também é um mapeamento de uint para um mapeamento de uint para uint. Isso significa que para cada Item Id existe um mapeamento de Stage para Amount Paid (quantidade paga).

mapping(uint=> mapping(uint => bytes)) internal stageToErrors;
mapping(uint=> mapping(uint => uint)) internal stageToPaid;
Enter fullscreen mode Exit fullscreen mode

Ambas variáveis, stageToErrors e stageToPaid, podem ser usadas para armazenar as informações de erro e pagamento para cada estágio de um processo. Esses mapeamentos são acessados assim:

bytes memory error = stageToErrors[itemId][stageIndex].
Enter fullscreen mode Exit fullscreen mode

Struct

O contrato também tem uma struct Agreement (acordo) que tem 3 variáveis, stageConfirms, stagePaid e stageErrors, que são todos do tipo uint.

struct Agreement {
   uint stageConfirms;
   uint stagesPaid;
   uint stagesErrors;
}
Enter fullscreen mode Exit fullscreen mode

Estas variáveis serão usadas para armazenar os estados dos diferentes estágios do processo, como se um estágio foi confirmado, se um pagamento foi feito para um estágio e se houve erros em um estágio.

Array

O contrato também possui um array chamado items do tipo Agreement. Este array será usado para armazenar todas as structs Agreement, que contém informações sobre os diferentes estágios de um processo. Nós só escrevemos neste array como forma de coletar os IDs dos itens. É claro que isso não está pronto para produção.

Agreement[] items;
Enter fullscreen mode Exit fullscreen mode

Visão geral da função

A função injectStagePayment() permite que você defina o estado de pagamento de um estágio específico de um processo específico. Ela leva três parâmetros: _id, _index e _value. _id é o Id do item, _index é o índice do estágio na estrutura do Contrato e _value é o valor pago por esse estágio. Da mesma forma, injectStageError() faz o mesmo, mas essencialmente registra uma mensagem de erro para um determinado estágio.

function injectStagePayment
 (
   uint _id,
   uint _index,
   uint _info,
   uint _value
 ) external {
   stageToPaid[_id][_index] = _value;
   packSingleBool(true, _index, _id, _info);
 }
Enter fullscreen mode Exit fullscreen mode
function injectStageError
 (
   uint _id,
   uint _index,
   uint _info,
   bytes memory _error
 ) external {
     stageToErrors[_id][_index] = _error;
     packSingleBool(true, _index, _id, _info);
 }
Enter fullscreen mode Exit fullscreen mode

setItemStatus

A função setItemStatus permite a definição do estado de um estágio específico de um contrato específico. Leva quatro parâmetros: _status, _index, _itemId e _info. _status é um valor booleano que representa o estado do estágio, _index é o índice do estágio no array, _itemId é o ID do item e _info é a variável da struct que queremos alterar.

function setItemStatus(bool _status, uint _index, uint _itemId, uint _info) external {
   packSingleBool(_status,_index,_itemId,_info);
}
Enter fullscreen mode Exit fullscreen mode

retrieveItemStatus

A função retrieveItemStatus permite recuperar o estado de um estágio específico de um processo específico. Leva três parâmetros: _id, _index e _info. _id é o Id do item, _index é o índice do estágio na struct e _info é a variável da struct que queremos alterar. A função verifica o valor de _info e retorna o estado apropriado do estágio para a variável especificada.

function retrieveItemStatus(uint _id, uint _index, uint _info) external view returns (bool) {
   if(_info == 0) {
       return unpackSingleBool(items[_id].stageConfirms, _index);
   } else if (_info == 1) {
         return unpackSingleBool(items[_id].stagesPaid, _index);
   } else if (_info == 2) {
       return unpackSingleBool(items[_id].stagesErrors, _index);
   } else {
        revert WrongStep();
   }
}
Enter fullscreen mode Exit fullscreen mode

unpackSingleBool

A função unpackSingleBool é uma função auxiliar usada para recuperar um único valor booleano de uma variável uint256 empacotada. Ela usa dois parâmetros: _packed, que é a variável uint256 empacotada, e _index, que é o índice do valor booleano que você deseja recuperar. A função usa operações bit a bit para recuperar o bit apropriado e retorna seu valor como um booleano.

function unpackSingleBool(uint256 _packed, uint _index) internal pure returns (bool) {
   return (_packed & (1 << _index)) != 0;
}
Enter fullscreen mode Exit fullscreen mode

packSingleBool

packSingleBool é uma função auxiliar que é usada para definir um único valor booleano dentro de uma variável empacotada uint256. Ela leva quatro parâmetros: _singleBool, que é o valor booleano que você deseja definir, _index que é o índice do valor booleano dentro da variável empacotada, _id que é o Id do item, _info é a variável da struct que queremos mudar. Ela usa operações bit a bit para definir o bit apropriado dentro da variável empacotada.

function packSingleBool(bool _singleBool, uint _index, uint _id, uint _info) internal {
   if(_info == 0) {
       if (_singleBool) {
           items[_id].stageConfirms |= (1 << _index);
       }
   } else if (_info == 1) {
       if (_singleBool) {
           items[_id].stagesPaid |= (1 << _index);
       }
   } else if (_info == 2) {
       if (_singleBool) {
           items[_id].stagesErrors |= (1 << _index);
       }
   }else {
       revert WrongStep();
   }
}
Enter fullscreen mode Exit fullscreen mode

Verificação completa do código

Incluí algumas funções auxiliares adicionais aqui também.

pragma solidity ^0.8.0;

/**
* @título PackedBool
* @dev Armazenando valores booleanos empacotados em variáveis uint256
* @autor @keyrxng
*/
contract PackedBools {
   struct Agreement {
       uint stageConfirms;
       uint stagesPaid;
       uint stagesErrors;
       bytes itemName;
   }
   Agreement[] items;
   // Item Id => Stage => Error
   mapping(uint=> mapping(uint => bytes)) internal stageToErrors;
   // Item Id => Stage => Amount paid
   mapping(uint=> mapping(uint => uint)) internal stageToPaid;
   /// @dev cria um novo item básico para ser rastreado
   function createNewItem(bytes calldata _name) external returns(uint) {
       Agreement memory item = Agreement(0,0,0, _name);
       items.push(item);
       return items.length - 1;
   }
   /// @param _id do item no array de itens
   /// @param _index - índice de estágio
   /// @param _info, ou seja, 0=stageConfirms, 1=stagePaid, 2=stageErrors etc
   /// @param _value - valor a ser pago em wei
   function injectStagePayment(uint _id, uint _index, uint _info, uint _value) external {
       stageToPaid[_id][_index] = _value;
       packSingleBool(true, _index, _id, _info);
   }
   /// @param _id do item no array de itens
   /// @param _index - índice de estágio
   /// @param _info, ou seja, 0=stageConfirms, 1=stagePaid, 2=stageErrors etc
   /// @param _error - mensagem de erro de bytes
   function injectStageError(uint _id, uint _index, uint _info, bytes memory _error) external {
       stageToErrors[_id][_index] = _error;
       packSingleBool(true, _index, _id, _info);
   }
   /// @param _id do item no array de itens
   /// @param _index - índice de estágio
   function getStagePayment(uint _id, uint _index) external view returns (uint) {
       return stageToPaid[_id][_index];
   }
   /// @param _id do item no array de itens
   /// @param _index - índice de estágio
   function getStageError(uint _id, uint _index) external view returns (bytes memory) {
       return stageToErrors[_id][_index];
   }
   /// @param _id do item no array de itens
   function getItem(uint _id) external view returns (Agreement memory) {
       return items[_id];
   }
   /// @param _status é a conclusão bem-sucedida de uma etapa
   /// @param _index é o índice predefinido de estágios logísticos
   /// @param _itemId é o id no array de itens
   /// @param _info especifica em qual variável escrever
   function setItemStatus(bool _status, uint _index, uint _itemId, uint _info) external {
       packSingleBool(_status,_index,_itemId,_info);
   }
   /// @param _id do item no array de itens
   /// @param _index - índice de confirmação de estágio
   /// @param _info indica de qual variável desempacotar
   function retrieveItemStatus(uint _id, uint _index, uint _info) external view returns (bool) {
       if(_info == 0) {
           return unpackSingleBool(items[_id].stageConfirms, _index);
       } else if (_info == 1) {
           return unpackSingleBool(items[_id].stagesPaid, _index);
       } else if (_info == 2) {
           return unpackSingleBool(items[_id].stagesErrors, _index);
       } else {
           revert WrongStep();
       }
   }
   /// @param _packed - local onde está o uint em que empacotamos os bools
   /// @param _index - fase de confirmação para desempacotar, por exemplo, depósito, etc.
   function unpackSingleBool(uint256 _packed, uint _index) internal pure returns (bool) {
       return (_packed & (1 << _index)) != 0;
   }
   /// @param _singleBool - verdadeiro/falso para armazenar no uint
   /// @param _index - estágio de confirmação
   /// @param _id do array do item
   /// @param _info - em qual variável escrever
   function packSingleBool(bool _singleBool, uint _index, uint _id, uint _info) internal {
       if(_info == 0) {
           if (_singleBool) {
               items[_id].stageConfirms |= (1 << _index);
           }
       } else if (_info == 1) {
           if (_singleBool) {
               items[_id].stagesPaid |= (1 << _index);
           }
       } else if (_info == 2) {
           if (_singleBool) {
               items[_id].stagesErrors |= (1 << _index);
           }
       }else {
           revert WrongStep();
       }
   }
   error WrongStep();
}
Enter fullscreen mode Exit fullscreen mode

Escrevendo testes com o Foundry

O contrato SupplyChainTest é um teste de unidade para o contrato SupplyChain. O objetivo deste teste é garantir que o contrato SupplyChain funcione conforme pretendido, afirmando que os resultados esperados são retornados. Vou apenas mostrar a base de código para os testes de unidade e explicar o que está acontecendo.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "../../src/SupplyChain/SupplyChainAdv.sol";
contract SupplyChainTest is Test {
   SupplyChain public supChain;
   function setUp() public {
       supChain = new SupplyChain();
       supChain.createNewItem("Best Item Ever");
       supChain.setItemStatus(true,0, 0,0); // define stageConfirmations[0] como verdadeiro
   }
   function test_SetItemStatus() external {
       supChain.setItemStatus(true, 1, 0, 0); // define stageConfirmations[1] como verdadeiro
       assertEq(supChain.retrieveItemStatus(0, 0, 0), true); // stageConfirmations[0] é verdadeiro
       assertEq(supChain.retrieveItemStatus(0, 1, 0), true); // stageConfirmations[1] é verdadeiro
       assertEq(supChain.retrieveItemStatus(0, 2, 0), false); // stageConfirmations[2] é falso

       supChain.setItemStatus(false, 0, 0, 1); // define stagePaid para stageConfirmations[0] como falso
       supChain.setItemStatus(true, 1, 0, 1); // define stagePaid para stageConfirmations[1] como verdadeiro
       // Estes são falsos por padrão de qualquer maneira, portanto, é inútil atribuí-los como falsos, e só devem ser chamados se verdadeiro
       supChain.setItemStatus(false, 0, 0, 2); // define stageError para stageConfirmations[0] como falso
       supChain.setItemStatus(false, 1, 0, 2); // define stageError para stageConfirmations[1] como falso
       assertEq(supChain.retrieveItemStatus(0, 0, 0), true); //stageConfirmations[0] é verdadeiro
       assertEq(supChain.retrieveItemStatus(0, 1, 0), true); // stageConfirmations[1] é verdadeiro
       assertEq(supChain.retrieveItemStatus(0, 2, 0), false); // stageConfirmations[2] é falso
       assertEq(supChain.retrieveItemStatus(0, 0, 1), false); // stageConfirmations[0].stagePaid é falso
       assertEq(supChain.retrieveItemStatus(0, 1, 1), true); // stageConfirmations[1].stagePaid é verdadeiro
       assertEq(supChain.retrieveItemStatus(0, 2, 1), false); // stageConfirmations[2].stagePaid é falso
       assertEq(supChain.retrieveItemStatus(0, 0, 2), false); // stageConfirmations[0].stageError é falso
       assertEq(supChain.retrieveItemStatus(0, 1, 2), false); // stageConfirmations[1].stageError é falso
       assertEq(supChain.retrieveItemStatus(0, 2, 2), false); // stageConfirmations[2].stageError é falso
   }
   function test_InjectStagePayment() external {
       supChain.injectStagePayment(0,1,1, 1000); // item 0 estágio 1 é 1000
       assertTrue(supChain.retrieveItemStatus(0,1,1)); // TRUE: stagePaid foi definido como verdadeiro
       assertEq(supChain.getStagePayment(0, 1), 1000); // 1000 wei de pagamento esperado
   }
   function test_InjectStageError() external {
       supChain.injectStageError(0,0,2, "Falha de QA: Unidade Destruida"); // ERROR: stageError definido como verdadeiro
       assertTrue(supChain.retrieveItemStatus(0,0,2)); // TRUE: stageError foi definido como verdadeiro
       assertEq(supChain.getStageError(0,0), "Falha de QA: Unidade Destruida"); // retorno esperado
   }
   function test_GetItem() external {
       supChain.setItemStatus(true, 1, 0, 0); // define stageConfirmations[1] como verdadeiro
       supChain.setItemStatus(false, 0, 0, 1); // define stagePaid para stageConfirmations[0] como falso
       supChain.setItemStatus(true, 1, 0, 1); // define stagePaid para stageConfirmations[1] como verdadeiro
       supChain.injectStagePayment(0,1,1,1000); // item 0 estágio 1 é 1000
       // Estes são falsos por padrão de qualquer maneira, portanto, é inútil atribuí-los como falsos, e só devem ser chamados se verdadeiro
       supChain.setItemStatus(false, 0, 0, 2); // define stageError para stageConfirmations[0] como falso
       supChain.setItemStatus(false, 1, 0, 2); // define stageError para stageConfirmations[1] como falso
       supChain.setItemStatus(true, 2, 0, 2); // define stageError para stageConfirmations[2] como verdadeiro
       supChain.injectStageError(0,2,2, "Falha na Remessa: Carga Perdida"); // ERROR: stageError definido como verdadeiro
       assertEq(supChain.getStageError(0,2), "Falha na Remessa: Carga Perdida"); // retorno esperado
       SupplyChain.Agreement memory agreement = supChain.getItem(0);
   }
}
Enter fullscreen mode Exit fullscreen mode

A função setUp é chamada no início de cada teste e inicializa o contrato supChain, cria um novo item e define stageConfirms para o primeiro estágio como verdadeiro.

A função test_SetItemStatus testa as funções setItemStatus e retrieveItemStatus do contrato SupplyChain. Ela define vários estágios como verdadeiro e falso e afirma que o valor correto é retornado ao chamar retrieveItemStatus.

A função test_InjectStagePayment testa a função injectStagePayment do contrato SupplyChain. Ela define o pagamento para um estágio específico e afirma que o valor correto é retornado ao chamar getStagePayment.

A função test_InjectStageError testa a função injectStageError do contrato SupplyChain. Ela define a mensagem de erro para um estágio específico e afirma que o valor correto é retornado ao chamar getStageError.

A função test_GetItem testa a função getItem do contrato SupplyChain. Ela recupera o item e afirma que ele corresponde aos resultados esperados.

Um leitor perspicaz teria notado que no final do teste final eu chamo SupplyChain.Agreement memory agreement = supChain.getItem(0); sem afirmar ou verificar nada. Isso ocorre porque o compilador Forge, do Foundry, quando fornecido com forge test -vvvv, fornecerá a você o rastreamento completo da chamada, portanto, a chamada final desse teste basicamente registra o contrato no console.

Para executar este teste, você pode usar o comando forge test na raiz do projeto, desde que o contrato de teste esteja no diretório ./test/.

É importante observar que, para executar o teste, você deve usar o framework Foundry e ter todas suas dependências instaladas.

Estas são as melhores práticas para escrever, depurar e testar contratos inteligentes usando o framework Foundry.

Comparação de Gás

O contrato AlternativeApproach é uma alternativa ao contrato SupplyChain, que usa uma nova struct chamada AgreementX, para armazenar o estado de cada acordo. Cada struct AgreementX contém três variáveis: um stageConfirms do tipo Stages, que é um enum de diferentes estágios no processo da cadeia de suprimentos, dois arrays booleanos chamados stagePaid e stageErrors, que armazenam se o pagamento foi feito e se há erros para cada estágio, e uma variável de bytes chamada itemName para armazenar o nome do item. O contrato também inclui um mapeamento, itemtoStage, para armazenar o estágio atual de um item. O contrato possui várias funções para criar, definir e recuperar o estado dos contratos, bem como uma função para definir o estágio atual de um item.

contract AlternativeApproach {
   struct AgreementX {
       Stages stageConfirms;
       bool[] stagesPaid;
       bool[] stagesErrors;
       bytes itemName;
   }

AgreementX[] Xitems;
   enum Stages {Supplier, Manufacturer, Warehouse, FreightForwarder, Carrier, Customs, Distributor, Retailer, Consumer}

   mapping(uint => Stages) public itemtoStage;
   function createNewAgreementX(bytes calldata _name) external returns(uint) {
       AgreementX memory item = AgreementX({
           stageConfirms: Stages.Consumer,
           stagesPaid: new bool[](uint(Stages.Consumer)),
           stagesErrors: new bool[](uint(Stages.Consumer)),
           itemName: _name
       });
       Xitems.push(item);
       return Xitems.length - 1;
   }
   function setItemStatusX(uint _id, bool _status, uint _index, uint _info) external {
       if(_info == 0) {
           Xitems[_id].stageConfirms = Stages(_index);
       } else if (_info == 1) {
           Xitems[_id].stagesPaid[_index] = _status;
       } else if (_info == 2) {
           Xitems[_id].stagesErrors[_index] = _status;
       } else {
           revert WrongStep();
       }
   }
   function getStageStatusX(uint _id, uint _index, uint _info) external view returns (bool) {
       if(_info == 0) {
           return Xitems[_id].stageConfirms == Stages(_index);
       } else if (_info == 1) {
           return Xitems[_id].stagesPaid[_index];
       } else if (_info == 2) {
           return Xitems[_id].stagesErrors[_index];
       } else {
           revert WrongStep();
       }
   }   
   function getItemX(uint _id) external view returns (AgreementX memory) {
       return Xitems[_id];
   }

   function setItemStages(uint _itemId, uint id) external {
       itemtoStage[_itemId] = Stages(id);
   }
}
Enter fullscreen mode Exit fullscreen mode
| Deployment Cost    | Deployment Size |        |        |        |         |
| 986621             | 4960            |        |        |        |         |
| Function Name      | min             | avg    | median | max    | # calls |
| createNewAgreementX| 141219          | 141219 | 141219 | 141219 | 1       |
| createNewItem      | 52275           | 52275  | 52275  | 52275  | 5       |
| getItem            | 4168            | 4168   | 4168   | 4168   | 1       |
| getItemX           | 9122            | 9122   | 9122   | 9122   | 1       |
| getStageError      | 1514            | 1514   | 1514   | 1514   | 2       |
| getStagePayment    | 578             | 578    | 578    | 578    | 1       |
| getStageStatusX    | 1018            | 1048   | 1054   | 1068   | 4       |
| injectStageError   | 23910           | 35860  | 35860  | 47810  | 2       |
| injectStagePayment | 23115           | 35065  | 35065  | 47015  | 2       |
| retrieveItemStatus | 743             | 905    | 766    | 2789   | 14      |
| setItemStatus      | 498             | 11939  | 14255  | 22856  | 16      |
| setItemStatusX     | 724             | 7631   | 1110   | 21060  | 3       |
Enter fullscreen mode Exit fullscreen mode

Conforme mostrado nos resultados do gás, usar operações bit a bit para empacotar valores booleanos em uma única variável uint256 é significativamente mais eficiente em termos de gás do que usar enums e arrays booleanos dinâmicos. A diferença de custo de gás entre criar um acordo com três variáveis uint256 e um com 2 arrays booleanos dinâmicos e um enum é substancial, resultando em uma redução de quase 3x no gasto de gás.

Além disso, as funções getter para o método alternativo também são mais caras em comparação com suas funções equivalentes. No entanto, definir o status é 14x mais barato para o método alternativo. Portanto, criar o item struct é mais caro, assim como lê-lo do armazenamento, mas escrever nele é mais barato do que empacotá-lo.

Vale a pena notar que, embora o método booleano empacotado possa ser mais eficiente em termos de gás em alguns aspectos, também pode ser menos legível e mais difícil de entender. Portanto, é importante colocar tudo na balança e escolher o método que melhor se adapta ao caso de uso específico.

Referindo-se a esta pergunta do stack exchange, a resposta principal dada foi - "A partir desses dados, fica claro que a quantidade máxima de booleanos que você pode armazenar em um slot é 32". O que eles discutem é empacotar uma estrutura com booleanos até que um slot de armazenamento inteiro seja consumido. Ao utilizar meu método, você pode potencialmente armazenar variáveis booleanas uint256.max em um único slot. Eu te desafio a fazer isso.

Conclusão

Para concluir, empacotar variáveis uint256 com valores booleanos é uma técnica poderosa que pode ser usada para otimizar o desenvolvimento de contratos inteligentes. Este tutorial abordou os fundamentos de como usar esta técnica, incluindo uma visão geral dos principais benefícios e compensações. Esta é apenas uma demonstração básica, que ainda precisa de muita evolução.

Mas se você achou este tutorial útil, verifique os seguintes recursos interativos e divertidos para aprender mais:

  • O curso CryptoZombies, que oferece uma introdução divertida e interativa ao Solidity e ao desenvolvimento de contratos inteligentes.
  • O jogo de guerra Ethernaut, que é uma abordagem baseada em exploração para aprender e entender o Solidity e a segurança de contratos inteligentes.
  • Damn Vulnerable DeFi, que é outra abordagem baseada em exploração para aprender e entender o Solidity e segurança de contratos inteligentes.
  • CTFProtocol, um protocolo de captura de bandeira (CTF, capture the flag) dedicado à segurança de contratos inteligentes.
  • QuillCTF, outro formato baseado em desafio CTF, mas com desafios com tempo limitado lançados com frequência.

Sinta-se à vontade para entrar em contato comigo no Twitter, LinkedIn ou confira meu site. Se você tiver mais perguntas ou comentários, eu adoraria ouvir de você e ajudar de qualquer maneira que eu puder.

Artigo original publicado por Keyrxng. Traduzido por Paulinho Giovannini.

Top comments (0)