WEB3DEV

Cover image for Como O Solidity Valida Argumentos de Função - Um Mergulho Profundo Em Yul [Avançado]
Isabela Curado Nehme
Isabela Curado Nehme

Posted on

Como O Solidity Valida Argumentos de Função - Um Mergulho Profundo Em Yul [Avançado]

https://miro.medium.com/v2/resize:fit:720/format:webp/0*ydV3Mb-XbhfmDMOX.png

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:

https://miro.medium.com/v2/resize:fit:720/format:webp/0*N2K5gAVqb5U8foyB.png

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:

https://miro.medium.com/v2/resize:fit:720/format:webp/0*NaridagnHWsUJAId.png

Isso gerará o seguinte código Yul, que nos fornece melhores insights sobre o que está acontecendo:

https://miro.medium.com/v2/resize:fit:720/format:webp/0*fH6hPafK4Z1yLkhZ.png

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.

https://miro.medium.com/v2/resize:fit:720/format:webp/0*Y0IHd97cNUFqxZje.png

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.

https://miro.medium.com/v2/resize:fit:720/format:webp/0*S5EN1a7OVn8F8ULf.png

O próximo bloco de código é uma verificação no calldata (dados de chamada).

https://miro.medium.com/v2/resize:fit:720/format:webp/0*wWPobbWStDGeKFzR.png

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:

https://miro.medium.com/v2/resize:fit:720/format:webp/0*dpO3dpNqkPyN8zcx.png

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 (…)”.

https://miro.medium.com/v2/resize:fit:720/format:webp/0*BnkZzhd0BSfFH1Ys.png

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:

https://miro.medium.com/v2/resize:fit:720/format:webp/0*5KwlBVsziJzcLT7a.png

Trabalhando para fora, not(3) é igual a -4, quando convertido usando o complemento de dois.

https://miro.medium.com/v2/resize:fit:720/format:webp/1*7KnZWLQ6PsanadsruDakQQ.png

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?

https://miro.medium.com/v2/resize:fit:720/format:webp/0*e2rOVBrubxmmmigV.png

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)