WEB3DEV

Cover image for Série Raio X Solidity: Dominando o Armazenamento - Parte Final
Paulo Gio
Paulo Gio

Posted on

Série Raio X Solidity: Dominando o Armazenamento - Parte Final

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

https://blog.angle.money/playing-with-yul-cd4785e456d8

Vamos entender os conceitos avançados do Armazenamento de Contratos usando Yul com a ajuda do seguinte código de exemplo.

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract StroageYul {

   uint256 a = 1;
   uint128 b = 2;
   uint120 c = 3;
   uint8 d = 4;

   function readStorageSlot() public view
   returns(
   bytes32 valueAtSlotZero,
   bytes32 valueAtSlotOne,
   bytes32 valueAtSlotTwo,
   uint256 slotB,
   uint256 slotC,
   uint256 slotD,
   uint256 offsetB,
   uint256 offsetC,
   uint256 offsetD) {


       assembly {
            valueAtSlotZero := sload(0)  // valor real do slot em bytes <- sload(NúmeroDoSlot)
            valueAtSlotOne := sload(1)   // sload(var_name.slot)
            valueAtSlotTwo := sload(2)
            slotB := b.slot                 // uint256 <- nome_var.slot (não o valor real)
            slotC := c.slot
            slotD := d.slot
            offsetB := b.offset             // uint256 <- nome_var.offset (não o valor real)
           offsetC := c.offset
           offsetD := d.offset
       }       
   }

   function getC_ValueBySlotAndOffset() external view returns (uint256 cValue) {
       assembly {
           let cSlotPackedBytes32:= sload(c.slot)
           let cValueShiftR_Bits128 := shr(128, cSlotPackedBytes32)
           cValue := and(0xff, cValueShiftR_Bits128)
       }    
   }
Enter fullscreen mode Exit fullscreen mode

Seção # 1 - .slot e .offset

Saída da função readStorageSlot():

https://miro.medium.com/v2/resize:fit:750/format:webp/1*ml-3UoMuEl5N04RoJmQYpA.png

As variáveis de estado b = 2, c = 3, d = 4 foram compactadas pelo Solidity no mesmo slot juntas (marcadas em vermelho).

Você pode ler mais sobre compactar variáveis de estado aqui.

.slotB := b.slot
Enter fullscreen mode Exit fullscreen mode

O operador .slot é sufixado com o var_name como b para retornar o número de slot de armazenamento do contrato onde o valor real (que a variável b se refere) é armazenado.

Neste caso, todas as 3 variáveis são armazenadas no slot # 1 apenas.

https://miro.medium.com/v2/resize:fit:282/format:webp/1*B9TbN-EWWn0f38TAZh-IMg.png

MAS... o offset para todas as 3 varia.

Offset é retornado pelo operador .offset (consulte o código acima).

offsetB := b.offset
Enter fullscreen mode Exit fullscreen mode

Mas, o que é o *offset *(deslocamento)?

Offset = número de bytes começando da esquerda em uma palavra de 32 bytes.

O Offset começa com o número 0, e não 1, e vai até 31, que é o último byte de 32.

b = 2 tem um offset = 0, pois está bem no início de nossa palavra de valor bytes32 no slot # 1.

c = 3 tem um offset = 16, pois o valor de b = 2 ocupa os primeiros 16 bytes (0–15) de espaço, sendo do tipo uint128 (128 bits = 16 bytes).

d = 4 tem um offset = 31 (último byte de 32) pois o valor de c = 3 ocupa 15 bytes de espaço, sendo do tipo uint120 (120 bits = 15 bytes).

Observe que tipos definidos pelo usuário / complexos / não elementares, como structs, mapeamentos, arrays, etc. que pegam ponteiros de armazenamento/referências de armazenamento dentro do corpo da função têm offset sempre zero, pois tais tipos não podem ser compactados com outras variáveis de armazenamento.

Seção # 2 - Deslocamento e Máscara Bitwise

Uma limitação ao usar _sload(slot#) _é que ele fornece um valor compactado em bytes para as variáveis de estado compactadas com diferentes valores de deslocamento no mesmo slot.

Não se pode obter o valor de uma única variável usando sload(slot#).

Se tentarmos executar o seguinte trecho de código para obter o valor de uma variável específica c dentro do slot, receberemos um erro.

function getCValueInSlot() external view returns (uint256 cValue) {
       assembly {
           cValue := sload(c)
       }
   }
Enter fullscreen mode Exit fullscreen mode

https://miro.medium.com/v2/resize:fit:640/format:webp/1*4G124un8CMPWG8AkKmHHbQ.png

No entanto, isso é possível usando Deslocamento e Máscara Bitwise.

function getC_ValueBySlotAndOffset() external view returns (uint256 cValue) {
       assembly {
           let cSlotPackedBytes32 := sload(c.slot)
           let cValueShiftR_Bits128 := shr(128, cSlotPackedBytes32)
           cValue := and(0xff, cValueShiftR_Bits128)
       }    
   }
Enter fullscreen mode Exit fullscreen mode

Aqui, estamos tentando obter o valor da variável c dentro do slot # 1.

Vou explicar cada passo.

(1). sload(c.slot)

cSlotPackedBytes32 = 0x04
                    000000000000000000000000000003
                    00000000000000000000000000000002
Enter fullscreen mode Exit fullscreen mode

(2). shr(128, cSlotPackedBytes32)

Ele (bitwise) desloca o valor dos bytes em cSlotPackedBytes32 para o lado direito por 128 bits (16 bytes).

cValueShiftR_Bits128 =  0x0000000000000000000000000000000004
                        000000000000000000000000000003
Enter fullscreen mode Exit fullscreen mode

Fiz uma pequena edição no código de exemplo acima para obter esse valor intermediário para mostrar a saída.

Por que deslocamos para a direita por 16 bytes?

Fizemos isso usando shr().

b = 2 ocupa os primeiros 16 bytes da esquerda para o início e depois vem c = 3.

Ao deslocar (primeiro) 16 bytes para a direita, trouxemos c = 3 exatamente no início à esquerda.

Além disso, observe como zeros extras foram preenchidos no lado direito de d = 4 e permanecerá assim, e isso é bom para nosso caso.

(3). and(0xff, cValueShiftR_Bits128)

Aqui vem o (bitwise) and.

Multiplicamos o valor deslocado em cValueShiftR_Bits128 com 0xff.

0xff & 0x0000000000000000000000000000000004
    000000000000000000000000000003

= 0x000000000000000000000000000000000000000000000000000000000000003
Enter fullscreen mode Exit fullscreen mode

Ele retorna 0x03 (observe atentamente, não há 4 no resultado).

É assim que mascaramos todos os valores, exceto o que estamos procurando, ou seja, 0x03.

Uma maneira mais fácil de entender a máscara é pensar em 0xff como 11 (Não onze), apenas UmUm.

Qualquer coisa que você multiplique por 1 será a mesma.

Usei 0xff para manter as coisas simples quando, idealmente, seria

0x0x0000000000000000000000000000000000000000000000000000000000000ff
Enter fullscreen mode Exit fullscreen mode

Portanto, todos os dígitos, exceto os 2 mais à esquerda (03), são multiplicados por 0 e se tornam 0 e nos restam apenas:

0x000000000000000000000000000000000000000000000000000000000000003
Enter fullscreen mode Exit fullscreen mode

Ufa...

Parece muito para assimilar.

Mas, não se apresse, releia OU, o melhor, execute o código no RemixIDE e veja você mesmo as saídas ajustando os valores de entrada.

Você certamente pegará o jeito.

Aprendizados diversos:

(1). Não é possível atribuir valores explicitamente ao slot e offset de uma Variável de Estado em Assembly.

function assignValues() external {
       assembly {
           c.slot := 1
           c.offset := 16
       }
   }
Enter fullscreen mode Exit fullscreen mode

https://miro.medium.com/v2/resize:fit:640/format:webp/1*FI0TH4KyHO8nGHb_ZtIrxw.png

(2). Uma advertência ao usar sstore():

function setValueBySlotNumberAndVal(uint256 slotNumber, uint256 value) external {
       assembly {
           sstore(slotNumber, value)
       }
   }
Enter fullscreen mode Exit fullscreen mode

Esse trecho de código pode alterar os valores das Variáveis de Estado em todos os slots que se deseja ao interagir com a função setValueBySlotNumberAndVal().

Tenha MUITO cuidado ao usar tal código em seu Contrato Inteligente, pois isso pode levar a mudanças indesejadas nos valores das Variáveis de Estado e é por isso que geralmente não é recomendado, pois pode adicionar uma vulnerabilidade.

No entanto, ainda estou aprendendo mais sobre os casos de uso úteis de sstore() e continuarei atualizando esta seção ao longo do caminho.

Se você encontrar quaisquer fontes relevantes para aprender mais sobre isso, sinta-se à vontade para compartilhar.

Obrigado :)

Isso conclui a parte final.

E, como sempre, ficarei grato se você puder deixar um feedback honesto / reação e/ou feedback construtivo sobre este artigo para que eu possa melhorá-lo.

Até a próxima vez, boa codificação (em Solidity) :)

Bibliografia

https://docs.soliditylang.org/en/v0.8.17/yul.html#evm-dialect

Artigo original publicado por Manu Kapoor. Traduzido por Paulinho Giovannini.

Oldest comments (0)