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
Opção 2: Usando o cargo
cargo install --git https://github.com/foundry-rs/foundry --profile local --locked foundry-cli anvil
Opção 3: Usando o Docker
docker pull ghcr.io/foundry-rs/foundry:latest
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
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 .
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
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
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.
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;
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].
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;
}
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;
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);
}
function injectStageError
(
uint _id,
uint _index,
uint _info,
bytes memory _error
) external {
stageToErrors[_id][_index] = _error;
packSingleBool(true, _index, _id, _info);
}
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);
}
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();
}
}
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;
}
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();
}
}
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();
}
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);
}
}
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);
}
}
| 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 |
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)