WEB3DEV

Cover image for Fundamentos da Implantação de Contratos Inteligentes
Panegali
Panegali

Posted on

Fundamentos da Implantação de Contratos Inteligentes

As transações de implantação de contrato são únicas em vários aspectos. Neste artigo, dissecamos tais transações, examinando de perto o bytecode que dá origem a um novo contrato.

Com quem estamos conversando?

As transações são uma troca entre duas partes.

Ao transacionar a criptomoeda nativa, os endereços de/para identificam os proprietários iniciais e finais da moeda. Ao executar um contrato inteligente, o remetente da transação solicita que um contrato inteligente execute uma função. Assim, o contrato torna-se o destinatário da transação.

Da mesma forma, na implantação do contrato, o remetente da transação é quem cria o novo contrato na cadeia. Mas quem é o destinatário? É a própria blockchain! A blockchain designa um endereço especial para qualquer pessoa solicitar tal serviço. Este endereço especial é o endereço null (nulo).

Aqui está o código, implante-o

Tendo uma contraparte de implantação, poderíamos simplesmente enviar o código do contrato inteligente para o endereço null, certo? Não exatamente! A carga útil dos dados da transação de implantação é um pouco mais complicada e por um bom motivo.

O motivo é o construtor, um trecho de código encarregado da validação da implantação e da inicialização do contrato. Um construtor pode…

  • …abortar a implantação se a validação falhar.
  • …fornecer inicialização única para variáveis ​​de estado.

Além disso, o construtor só é executado na implantação. Portanto, não há sentido em salvá-lo na cadeia.

Agora estamos prontos para definir a carga de dados da transação de implantação, que claramente deve integrar a lógica do construtor.

De fato, a carga de implantação é uma versão ligeiramente modificada do código do construtor. Ela inclui toda a lógica do construtor, mas quando executada com sucesso, retorna a parte do contrato inteligente a ser escrita na cadeia.

Falo de uma parte do contrato inteligente, pois como já foi dito, o construtor não é escrito na cadeia. A figura mostra a blockchain recebendo uma transação de implantação e executando o construtor. Por fim, isso retorna com sucesso o código do contrato ou reverte a transação, cancelando a implantação.

Implantação de contrato inteligente

Implantando um contrato

Agora é hora de investigar a carga de implantação da transação. Eu preparei um projeto do Hardhat com um contrato simples.

Clonar e instalar dependências

Para prosseguir, clone e instale as dependências do projeto:

git clone [email protected]:kaxxa123/BlockchainThings.git
cd ./BlockchainThings/ContractBytecode

npm install
Enter fullscreen mode Exit fullscreen mode

Configurar e compilar

Em seguida, precisamos personalizar um pouco o projeto:

  1. Este código será executado em qualquer cadeia compatível com a EVM. Aqui o Hardhat foi configurado para rodar na rede de testes Fuji, da Avalanche. A Fuji foi escolhida por causa de sua torneira fácil de usar, que nos fornece 2 AVAX sem nenhum problema. Portanto, comece solicitando alguns AVAX de teste.
  2. Na pasta ContractBytecode, renomeie o arquivo:
    De: ./BlockchainThings/ContractBytecode/.env_template
    Para: ./BlockchainThings/ContractBytecode/.env

  3. Edite o .env para definir a chave privada da conta para a qual o AVAX foi enviado. Uma vez pronto, o conteúdo deve ter a seguinte aparência:
    PRIVATE_KEY_1=”0x1234567890abcd…”

Agora estamos prontos para compilar o projeto:

npx hardhat compile
Enter fullscreen mode Exit fullscreen mode

O código

Em seguida, verifique o código do contrato com o qual estaremos trabalhando.

pragma solidity 0.8.18;

contract Demo {
address public owner;
uint public counter;

constructor(uint start) payable {
require (start > 100, "Too small");
owner = msg.sender;
counter = start;
}

function increase() external {
++counter;
}
}
Enter fullscreen mode Exit fullscreen mode

O construtor:

  1. Leva um parâmetro de entrada.
  2. Inclui uma cláusula require que pode abortar a implantação.
  3. Inicializa duas variáveis ​​de estado.

Para simplificar nosso exemplo, o construtor é marcado como payable (pagável). Caso contrário, o compilador injetaria uma segunda cláusula de exigência, garantindo que nenhuma quantia de criptomoedas seja incluída na transação de implantação. Isso tornaria o bytecode mais difícil de ser compreendido.

Implantar

Antes de nos aprofundarmos no bytecode, vamos ver quais dados são necessários ao implantá-lo usando sendTransaction.

Anteriormente, compilamos o contrato inteligente e procuramos a saída resultante em ./artifacts/contracts/Demo.sol/Demo.json.

Estamos especialmente interessados ​​em:
bytecode - o código de contrato completo, incluindo o construtor.
deployedBytecode - a parte do código do contrato que exclui o construtor.

Em seguida, iniciamos um console do node.js conectado à Avalanche Fuji:

npx hardhat console --network fuji
Enter fullscreen mode Exit fullscreen mode

Verifique se o arquivo .env foi configurado corretamente recuperando o endereço da sua conta:

accounts = await ethers.getSigners()
accounts[0].address
Enter fullscreen mode Exit fullscreen mode

Carregue o arquivo de saída da compilação:

fs = require("fs")
fs.readFile('./artifacts/contracts/Demo.sol/Demo.json', 'utf8',
(err, data) => compile = JSON.parse(data))
Enter fullscreen mode Exit fullscreen mode

Isso incluirá os valores bytecode e deployBytecode:

compile.bytecode
compile.deployedBytecode
Enter fullscreen mode Exit fullscreen mode

Esses valores são formatados da seguinte maneira:

compile.bytecode =
Initialization Code (aka constructor) | Contract On-Chain Portion

compile.deployedBytecode =
Contract On-Chain Portion
Enter fullscreen mode Exit fullscreen mode

Se o construtor não exigisse nenhum parâmetro de entrada, poderíamos enviar uma transação com compile.bytecode como carga útil. Como temos um parâmetro, ele deve ser anexado ao bytecode. Vou deixar o ethers.js fazer isso.

paramIn = 200
DemoFactory = await ethers.getContractFactory("Demo")
complete = await DemoFactory.getDeployTransaction(paramIn)
complete.data
Enter fullscreen mode Exit fullscreen mode

O valor em complete.data tem tudo o que precisamos e está formatado da seguinte forma:

complete.data =
Initialization Code | Contract On-Chain Portion | Constructor Parameters
Enter fullscreen mode Exit fullscreen mode

Vamos confirmar que os valores que acabamos de discutir estão realmente formatados conforme descrito…

//Remova o "0x" inicial das strings
bytecode = compile.bytecode.slice(2)
deployedBytecode = compile.deployedBytecode.slice(2)
bytecodeEx = complete.data.slice(2)

//Confirme se o bytecode termina com o deployBytecode
assert(bytecode.length - bytecode.indexOf(deployedBytecode) ==
deployedBytecode.length)

//Confirme se bytecodeEx começa com bytecode
assert(bytecodeEx.indexOf(bytecode) == 0)

//Confirme se o parâmetro de entrada do construtor corresponde à nossa entrada
//'00000000000000000000000000000000000000000000000000000000000000c8'
param = bytecodeEx.slice(bytecode.length)
assert(parseInt(param, 16) == paramIn)
Enter fullscreen mode Exit fullscreen mode

Ok, agora estamos prontos para implantar o contrato usando sendTransaction com a carga útil complete.data:

trn = await accounts[0].sendTransaction({to: null, data: complete.data})
receipt = await trn.provider.getTransactionReceipt(trn.hash)

receipt.contractAddress
Enter fullscreen mode Exit fullscreen mode

E verificamos a implantação, executando suas funções:

abi = DemoFactory.interface.fragments
addr = receipt.contractAddress
demo = new ethers.Contract(addr, abi, accounts[0])

await demo.owner()
await demo.counter()
await demo.increase()
await demo.counter()
Enter fullscreen mode Exit fullscreen mode

O bytecode

A melhor maneira de ver como o código de inicialização inclui a lógica do construtor é percorrendo o bytecode (é um formato de código intermediário entre o código fonte, o texto que o programador consegue manipular, e o código de máquina, que o computador consegue executar). O bytecode não é fácil de ler, mas se nos prepararmos com alguns valores de referência chave, fica mais fácil. Aqui está uma tabela de valores que veremos aparecendo ao percorrer o código.

  Description                       Computation                    Hex Value

bytecode length             (bytecodeEx.length/2).toString(16)         21f

bytecode length excluding   (bytecode.length/2).toString(16)           1ff
constructor parameters 

constructor parameter       ((bytecodeEx.length -                       20
length                      bytecode.length)/2).toString(16)         

contract code length        (deployedBytecode.length/2)                15b
                                               .toString(16)

contract code offset        ((bytecode.length - 
within bytecode stream      deployedBytecode.length)/2).toString(16)    a4

constructor parameter       (200).toString(16)                          c8

condition value in          (100).toString(16)                          64
require (start > 100, ...)       

owner state variable                                                     0
storage key   

counter state variable                                                   1
storage key
Enter fullscreen mode Exit fullscreen mode

Em seguida, percorreremos os bytes individuais, converteremos cada código de operação (opcode) usando uma tabela como esta e, para cada código de operação, calcularemos o estado da pilha (stack). Não mostro os valores mantidos na memória, mas o código é simples o suficiente para não exigir isso.

A ordem do bytecode também foi ajustada para que este possa ser lido sequencialmente. Basicamente, meu registro mostra o índice de bytecode 7c logo após o salto no índice 21 e volta para o índice 22 quando o código volta.

idx  bytecode   opcodes     stack               description

00   60 80      PUSH1 80    [80]
02   60 40      PUSH1 40    [40, 80]            Save 80 to mem location 40
04   52         MSTORE      []
05   60 40      PUSH1 40    [40]
07   51         MLOAD       [80]                Load memory location 40
08   61 01ff    PUSH2 01ff  [1ff, 80]           1ff - bytecode length 
                                                    except ctr parameters
0b   38         CODESIZE    [21f, 1ff, 80]      push bytecode length 21f
0c   03         SUB         [ 20, 80]           Subtracting gives the ctr 
                                                parameters length
0d   80         DUP1        [20, 20, 80]
0e   61 01ff    PUSH2 01ff  [1ff, 20, 20, 80]
11   83         DUP4        [80, 1ff, 20, 20, 
                                    80]
12   39         CODECOPY    [20, 80]            Copy ctr param of size 20
                                                @ 1ff to memory location 80
Enter fullscreen mode Exit fullscreen mode

Este código apenas copiou o parâmetro de entrada do construtor para a memória.

Valores da pilha:
20 - tamanho do parâmetro ctr.
80 — localização da memória do parâmetro ctr.

idx   bytecode    opcodes     stack               description

13    81          DUP2        [80, 20, 80]
14    01          ADD         [a0, 80]            Get memory pointer 
                                                  following ctr parameter
15    60 40       PUSH1 40    [40, a0, 80]
17    81          DUP2        [a0, 40, a0, 80]
18    90          SWAP1       [40, a0, a0, 80]        
19    52          MSTORE      [a0, 80]            Store memory pointer a0
                                                  to memory location 40

1a    61 0022     PUSH2 0022  [22, a0, 80]
1d    91          SWAP2       [80, a0, 22]
1e    61 007c     PUSH2 007c  [7c, 80, a0, 22]
21    56          JUMP        [80, a0, 22]        Jump to 7c
Enter fullscreen mode Exit fullscreen mode

Valores da pilha:
a0 — próximo local de memória após o parâmetro ctr.
22 — local de “jump back” (retornar a uma posição anterior), para continuar de onde o código parou.
80 — local de memória do parâmetro ctr.

idx   bytecode    opcodes     stack               description

7c    5b          JUMPDEST    [80, a0, 22]                    
7d    60 00       PUSH1 00    [00, 80, a0, 22]
7f    60 20       PUSH1 20    [20, 00, 80, a0, 
                               22]
81    82          DUP3        [80, 20, 00, 80, 
                               a0, 22]
82    84          DUP5        [a0, 80, 20, 00, 
                               80, a0, 22]
83    03          SUB         [20, 20, 00, 80,    Get ctr parameter size
                               a0, 22]
84    12          SLT         [00, 00, 80, a0,    Is (top < top-1)?
                               22]
85    15          ISZERO      [01, 00, 80, a0,    Is (top == 00)?
                               22]
86    61 008e     PUSH2 008e  [8e, 01, 00, 80, 
                               a0, 22]
89    57          JUMPI       [00, 80, a0, 22]    If (top-1!=0) Jump to 8e
8a    60 00       PUSH1 00
8c    80          DUP1 
8d    fd          REVERT
Enter fullscreen mode Exit fullscreen mode

Este código verificou o tamanho esperado dos parâmetros ctr.

Valores da pilha:
80 — localização da memória do parâmetro ctr.
a0 — localização da memória após o parâmetro ctr.
22 — localização do “jump back”.

idx   bytecode    opcodes     stack               description

8e    5b          JUMPDEST    [00, 80, a0, 22]
8f    50          POP         [80, a0, 22]
90    51          MLOAD       [c8, a0, 22]        Load ctr parameter from  
                                                  memory location 80
91    91          SWAP2       [22, a0, c8]
92    90          SWAP1       [a0, 22, c8]
93    50          POP         [22, c8]
94    56          JUMP        [c8]                Jump back to location 22
Enter fullscreen mode Exit fullscreen mode

Valores da pilha:
c8 — valor do parâmetro de entrada ctr.

idx   bytecode    opcodes     stack             description

22    5b          JUMPDEST    [c8]
23    60 64       PUSH1 64    [64, c8]          0x64 = 100, processing...
                                                require (start > 100, ...)
25    81          DUP2        [c8, 64, c8]
26    11          GT          [01, c8]          Is (c8 > 64)?
27    61 0062     PUSH2 0062  [62, 01, c8]
2a    57          JUMPI       [c8]              If (top-1 != 0) Jump to 62
Enter fullscreen mode Exit fullscreen mode

Este código verificou a condição exigida em:
require (start > 100, “Too small”)

Se a verificação falhar, o código não retornará a uma posição anterior e a sequência de código a seguir será revertida.

Valores da pilha:
c8 — valor do parâmetro de entrada ctr.

idx     bytecode    opcodes                   description

2b      60 40       PUSH1 40                  Following
2d      51          MLOAD                     this
2e      62 461bcd   PUSH3 461bcd              code
32      60 e5       PUSH1 e5                  sequence
34      1b          SHL                       it ultimately
35      81          DUP2                      reverts
36      52          MSTORE                    as expected 
37      60 20       PUSH1 20                  from a 
39      60 04       PUSH1 04                  failed 
3b      82          DUP3                      require
3c      01          ADD                       clause
3d      52          MSTORE 
3e      60 09       PUSH109
40      60 24       PUSH1 24
42      82          DUP3 
43      01          ADD 
44      52          MSTORE 
45      68 151bdb   PUSH9 151bdb
           c81cdb         c81cdb
           585b1b         585b1b
4f      60 ba       PUSH1 ba
51      1b          SHL 
52      60 44       PUSH1 44
54      82          DUP3 
55      01          ADD 
56      52          MSTORE 
57      60 64       PUSH1 64
59      01          ADD 
5a      60 40       PUSH1 40
5c      51          MLOAD 
5d      80          DUP1 
5e      91          SWAP2 
5f      03          SUB 
60      90          SWAP1 
61      fd          REVERT                   If require failed revert here.
Enter fullscreen mode Exit fullscreen mode

Quando a condição require for satisfeita, o código continua a partir daqui...

idx   bytecode    opcodes      stack               description

62    5b          JUMPDEST     [c8]
63    60 00       PUSH1 00     [00, c8]
65    80          DUP1         [00, 00, c8]
66    54          SLOAD        [00, 00, c8]        Load state value key 00
                                                   i.e. the owner address
67    60 01       PUSH1 01     [01, 00, 00, c8] 
69    60 01       PUSH1 01     [01, 01, 00, 00, 
                                c8]
6b    60 a0       PUSH1 a0     [a0, 01, 01, 00, 
                                00, c8] 
6d    1b          SHL          [10000000000...,    Shift (top-1) left by
                                01, 00, 00, c8]    a0=20*8=address length

6e    03          SUB          [fffffffffff...,    Created a 20 byte mask!
                                00, 00, c8]            

6f    19          NOT          [fff...00000000,    Invert the top value
                                00, 00, c8]        !(top)

70    16          AND          [00, 00, c8]        top AND (top-1)
71    33          CALLER       [addr, 00, 00, c8]  get caller address
72    17          OR           [addr, 00, c8]
73    90          SWAP1        [00, addr, c8]
74    55          SSTORE       [c8]                Store address at slot 0
                                                   owner = msg.sender
75    60 01       PUSH1 01     [01, c8]
77    55          SSTORE       []                  Store ctr parameter 
                                                   counter = start
78    61 0095     PUSH2 0095   [95]
7b    56          JUMP         []                  Jump to location 95
Enter fullscreen mode Exit fullscreen mode

Instruções de armazenamento:
owner = msg.sender
counter = start

idx   bytecode    opcodes      stack               description

95    5b          JUMPDEST    [] 
96    61 015b     PUSH2 015b  [15b]               Push contract length
99    80          DUP1        [15b, 15b]
9a    61 00a4     PUSH2 00a4  [ a4, 15b, 15b]     Push contract offset 
                                                  within bytecode stream

9d    60 00       PUSH1 00    [00, a4, 15b, 15b]  Push memory offset where 
                                                  the code is to be copied

9f    39          CODECOPY    [15b]               Copy code length 15b
                                                  to memory offset 00 from
                                                  stream at offset a4 

a0    60 00       PUSH1 00    [00, 15b]           Return code to deploy
a2    f3          RETURN      []                  from memory offset 00 
                                                  with length 15b. 

a3    fe          INVALID                         INVALID marks end of
                                                  initialization code. 
                                                  contract code next
Enter fullscreen mode Exit fullscreen mode

Este código lida com o caso de uma execução bem-sucedida do construtor, retornando o código do contrato inteligente para ser escrito na cadeia.


Artigo escrito por Alexander Zammit. Traduzido por Marcelo Panegali

Oldest comments (0)