Imagem do banner
Publicado originalmente em https://zorz.substack.com.
Se você não conseguir responder à pergunta a seguir com segurança e explicar o porquê, aprenderá algo com este artigo.
Para o seguinte contrato Solidity, o que name()
retorna depois de setName()
é chamado no teste abaixo do contrato:
Os usuários podem olhar para esse teste e pensar, “espere um segundo, dois argumentos são fornecidos para uma função que deve receber apenas 1 argumento. Isso deveria falhar!” Bem, não falha. Vamos descobrir o porquê.
O Solidity, como uma linguagem, faz muitos ajustes sintáticos para nós, então precisamos descobrir o que realmente está acontecendo nos bastidores. Lembre-se de que, em última análise, a Máquina Virtual Ethereum (Ethereum Virtual Machine - EVM) se preocupa apenas com várias permutações de opcodes e pré-compilações. A melhor maneira de descobrir o que está sendo interpretado pela EVM sem passar pelo bytecode é observar a representação Yul do contrato inteligente. No Foundry, podemos fazer isso da seguinte maneira:
Isso gerará o seguinte código Yul, que nos fornece melhores insights sobre o que está acontecendo:
No primeiro bloco de código, temos o código init (inicializador). Em resumo, ele garante que o construtor não é pagável, verificando se não há callvalue()
(valor de chamada). Em seguida, ele retorna o código de tempo de execução que contém a lógica principal.
O código de tempo de execução é o loop da lógica principal que será executado sempre que alguém tentar fazer uma call
(chamada) para este contrato inteligente. No código acima, ele é rotulado como o objeto MockPresetA_22875_deployed
.
Vamos analisar os principais componentes do código de tempo de execução.
O Memoryguard é usado pelo otimizador Yul, informando ao otimizador que este aplicativo promete usar apenas o intervalo de memória começando em 0x80.
0x80 é escolhido porque os primeiros quatro slots de 32 bytes são reservados pelo Solidity.
O próximo bloco de código é uma verificação no calldata (dados de chamada).
Este bloco de código será revertido se o tamanho do calldata for menor que quatro bytes.
Por que quatro? O Solidity deriva o seletor de função dos primeiros quatro bytes do hash keccak da assinatura da função.
Para o nosso contrato inteligente original, isso pode ser derivado explicitamente da seguinte forma:
Se tivéssemos uma função de fallback em nosso contrato inteligente original, não verificaríamos imediatamente se o tamanho do calldata era de pelo menos 4 bytes, pois existiria uma função no contrato inteligente que não exige pelo menos 4 bytes de calldata. No entanto, em nosso contrato inteligente MockPresetA, toda chamada de função possível requer um seletor de função, e é por isso que revertemos se ele não existir.
A seguir, vamos mergulhar no bloco “if (…)
”.
Rastreamos o valor 0 na variável _2
como uma otimização, uma vez que o valor 0 é usado várias vezes no bloco de código.
Em seguida, extraímos o seletor de função de 4 bytes do calldata. O calldataload(0)
nos dará uma carga útil de 32 bytes a partir do índice 0 do calldata.
Ao deslocar essa carga útil 28 bytes para a direita (224 em decimal), obtemos os primeiros 4 bytes da carga útil de 32 bytes, que é, obviamente, o seletor de função.
Agora que temos o seletor de função, o comparamos com as possíveis funções que o usuário pode estar tentando chamar. Lembre-se do cálculo do seletor de função que fizemos anteriormente. Se o usuário estiver tentando chamar name()
, o seletor de função será 0x06fdde03
. Se ele estiver tentando chamar setName(bytes32)
, o seletor de função será 0x5ac801fe
.
Vamos nos concentrar em 0x5ac801fe
, ou seja, setName(bytes32)
.
Se o valor da chamada for diferente de zero, revertemos. Isso ocorre porque a função não está marcada como pagável. Temos então esta linha complicada:
Trabalhando para fora, not(3) é igual a -4, quando convertido usando o complemento de dois.
Então, na seção add(calldatasize(), -4)
, estamos reduzindo o tamanho do calldata em 4 e verificando se o resultado é menor que 32. Se for menor que 32, revertemos.
Esta é essencialmente uma verificação para garantir que temos PELO MENOS um argumento de 32 bytes. Essa é a razão pela qual nós reverteríamos se não fornecêssemos nenhum argumento para chamada da função setName
.
Finalmente, pegamos a carga útil de 32 bytes, começando após o 4º byte (para excluir o seletor de função), e salvamos no slot de armazenamento 0 usando sstore
, antes de retornar.
O interessante a notar é que quaisquer bytes adicionais após os primeiros 36 bytes (seletor de função + argumento de 32 bytes) são completamente ignorados.
Olhando para a pergunta original, vamos ver se podemos estar mais confiantes com a nossa resposta. O que name()
retorna em nosso teste?
Claro, ele retornará “Olá, Mundo! (Hello World!)”. O segundo argumento é completamente ignorado. Os mesmos princípios se aplicam aos construtores de contratos.
Obrigado pela leitura, siga-me no Twitter e assine a newsletter.
https://zorz.substack.com/p/how-the-solidity-validates-function-23-08-26
Publicado originalmente em https://zorz.substack.com.
Este artigo foi escrito por Zoraiz Mahmood e traduzido por Isabela Curado Nehme. Seu original pode ser lido aqui.
Top comments (0)