_Bem-vindo a mais um artigo sobre as funções pagáveis e não pagáveis do Solidity! Se você está interessado no desenvolvimento de blockchain e contratos inteligentes, provavelmente já se deparou com esses termos antes. Neste artigo, explicaremos o que são funções pagáveis e não pagáveis, como elas funcionam no Solidity e por que as funções pagáveis são menos dispendiosas (em termos de gás) do que as não pagáveis. Também exploraremos alguns casos de uso para ambos os tipos de funções e destacaremos as compensações que os desenvolvedores precisam considerar ao escolher entre eles.
Ao final deste artigo, você deverá ter uma compreensão clara de como usar as funções pagáveis e não pagáveis efetivamente em seus contratos inteligentes do Solidity.
Vamos começar!_
Funções Pagáveis
No Solidity, uma função pagável é uma função que pode aceitar ether como entrada. Quando um contrato é chamado com uma função pagável, o chamador pode enviar ether com a chamada da função. O ether é então armazenado no saldo do contrato, que pode ser acessado usando a propriedade "address(this).balance".
Como declarar uma função pagável
No Solidity, você usa a função pagável quando deseja permitir que um contrato receba ether. Uma função pagável é declarada usando a palavra-chave 'payable', o que permite receber ether como parte de uma chamada de função. Veja o exemplo de código abaixo;
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Payable{
mapping(address => uint256) balances;
function deposit() external payable {
require(msg.value > 0, "Zero ether not allowed");
balances[msg.sender] = balances[msg.sender] + msg.value;
}
}
O código acima define um contrato que permite aos usuários depositar ether em suas contas chamando a função de depósito.
Função Não Pagável
Por outro lado, uma função não pagável é uma função que não pode aceitar ether como entrada. Se um contrato for chamado com uma função não pagável e o chamador tentar enviar ether com a chamada, isso resultará em um erro. Veja o exemplo de código abaixo;
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract NonPayable{
mapping(address => uint256) public balances;
function deposit(uint256 amount) public {
require(amount > 0, "Zero amount not allowed");
balances[msg.sender] = balances[msg.sender] + amount;
}
}
Usando "msg.value" em funções não pagáveis
No Solidity, o uso de "msg.value" em funções não pagáveis não é permitido por motivos de segurança. "msg.value" representa a quantidade de ether sendo enviada com a chamada da função. Se for usado em uma função não pagável, isso indica que a função está recebendo ether, o que não era pretendido. Isso pode resultar em problemas de segurança e potencial perda financeira.
Para lidar com esse cenário, você precisa tornar a função pagável ou criar uma nova função interna que utilize “msg.value”. Se a função precisa receber ether, você deve torná-la pagável adicionando a palavra-chave "payable" antes da definição da função. Isso permite que a função receba e processe ether.
Funções implicitamente pagáveis
- Função receive: A função receive é automaticamente pagável e é chamada quando os dados da chamada estão vazios. Isso significa que se alguém enviar uma transação para o contrato sem especificar qual função chamar, a função receive será executada. Ela é declarada da seguinte forma;
receive() external payable{}
- Função de fallback: Por outro lado, a função de fallback é acionada quando você chama uma função que não existe ou não corresponde a nenhuma função em um contrato. Se a função receive não existir, a função de fallback lidará com chamadas com dados de chamada vazios. Se uma função de fallback não for pagável, transações ou chamadas de função que não correspondam a nenhuma outra função e também enviem valor (ether) serão revertidas. Ela é declarada da seguinte forma;
fallback() external payable{}
Conversão explícita de endereço
É importante observar que nem todos os endereços são pagáveis e, se você tentar enviar ethers para um endereço não pagável, a transação falhará.
A conversão explícita de endereço para endereço pagável só é possível se o contrato tiver uma função receive ou de fallback pagável. A conversão pode ser realizada usando address(x), onde x deve ser do tipo address.
No entanto, se o tipo de contrato não tiver uma função receive ou de fallback pagável, a conversão para endereço pagável pode ser feita usando payable(address(x)). Isso ocorre porque um contrato sem uma função receive ou de fallback pagável não pode receber ether e, portanto, não é convertível em um endereço pagável.
Essencialmente, a conversão de endereço para endereço pagável é feita para permitir o envio de ether para o endereço. Essa conversão é usada ao chamar uma função de contrato que possui o modificador "payable", o que permite que ela receba ether. A palavra-chave "payable" faz com que a função aceite ether e aumente o saldo do contrato pela quantia de ether recebida. Veja o exemplo de código abaixo;
// O endereço pagável fornece a função de transferência
address payable payableAddress;
Análise de custo de gás para funções pagáveis e não pagáveis
Vamos realizar uma transação e observar o custo de gás para cada caso usando os contratos a seguir.
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Payable{
mapping(address => uint256) public balances;
//Custo de gás 30605
function depositEther() public payable{
require(msg.value > 0, "Zero ether not allowed");
balances[msg.sender] = balances[msg.sender] + msg.value;
}
}
contract NonPayable{
mapping(address => uint256) public balances;
//Custo de gás 31141
function depositAmount(uint256 amount) public {
require(amount > 0, "Zero amount not allowed");
balances[msg.sender] = balances[msg.sender] + amount;
}
}
A transação depositEther no contrato pagável custa 30.605 de gás, enquanto a transação depositAmount no contrato não pagável custa 31.141 de gás. Observe que o custo de gás para a função pagável é mais barato do que o da função não pagável. Vamos desvendar o mistério por trás disso! 📜
Por que funções pagáveis são mais baratas do que funções não pagáveis
Usando o código abaixo, explicaremos por que funções pagáveis são mais econômicas do que funções não pagáveis.
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Payable{
constructor() payable{
}
}
contract NonPayable{
constructor(){
}
}
Em vez de usar o código anterior, no qual o bytecode e os opcodes dos contratos teriam sido excessivamente complexos e extensos, vou utilizar a função de construtor vazia. Fazendo isso, será mais fácil para todos compreenderem e, posteriormente, aplicarem o conhecimento a outros contratos sem confusão.
Código de Inicialização
O código de inicialização, também conhecido como código construtor, é executado apenas uma vez quando o contrato é implantado. Seu objetivo é inicializar o estado do contrato e definir seus valores iniciais. O código de inicialização é usado para criar e armazenar as variáveis do contrato, configurar a lógica do contrato e executar outras tarefas de configuração necessárias para que o contrato funcione corretamente.
Código em Tempo de Execução
O código em tempo de execução é executado sempre que o contrato é chamado ou invocado. Este código define a lógica do contrato, incluindo como ele interage com outros contratos, como armazena e recupera dados e como lida com transações. O código em tempo de execução é o que é executado quando um contrato interage com ele.
Bytecode para o contrato Não Pagável
[6080604052"348015600f57600080fd5b50603f80601d"6000396000f3fe] - Init Code
[6080604052600080fdfea2646970667358fe1220fefefefefe160342fefe35692fec668c4c7705aa8c111a45fe6447aec315841164736f6c63430008110033]- Runtime code
Bytecode para o contrato Pagável
[6080604052"603f806011600039"6000f3fe] - Init Code
[6080604052600080fdfea2646970667358fe1220fe09fefe93fef3f2a4fefefea49cfe11fefe99fe05fe7d05fa3e9cd05f3e143364736f6c63430008110033]- Runtime code
Se você observar, o código de inicialização para a função não pagável é mais longo do que o da função pagável, o mesmo acontece com os opcodes abaixo. O código de inicialização está destacado entre aspas duplas.
Opcode para o contrato Não Pagável
//Configurar o ponteiro de memória livre
[00] PUSH1 80
[02] PUSH1 40
[04] MSTORE // Armazena a palavra na memória
[05] CALLVALUE // é o valor em wei que foi com a transação
[06] DUP1
[07] ISZERO
[08] PUSH1 0f
[0a] JUMPI
[0b] PUSH1 00
[0d] DUP1
[0e] REVERT
[0f] JUMPDEST
[10] POP
[11] PUSH1 3f
[13] DUP1
[14] PUSH1 1d ->
[16] PUSH1 00
[18] CODECOPY
[19] PUSH1 00
[1b] RETURN
[1c] INVALID
[1d] PUSH1 80
[1f] PUSH1 40
[21] MSTORE
[22] PUSH1 00
[24] DUP1
[25] REVERT
[26] INVALID
[27] LOG2
[28] PUSH5 6970667358
[2e] INVALID
[2f] SLT
[30] SHA3
[31] PUSH31 bc71976c3da4aae3dc9fb5312d39cd8267899b40f5cbb457a9ed9feeec97d9
[51] PUSH5 736f6c6343
[57] STOP
[58] ADDMOD
[59] GT
[5a] STOP
[5b] CALLER
Os opcodes comentados executam as seguintes ações: o 'CALLVALUE' verifica a quantia em wei enviada com a transação, duplica-a com o opcode 'DUP' e depois determina se ela é igual a zero usando o opcode 'ISZERO'.
Se o valor for zero, o opcode 'ISZERO' empurra 1 para a pilha, o que significa 'verdade', e continua a execução. Caso contrário, ele empurra 0, o que significa 'falso'.
Neste caso, espera-se que o 'CALLVALUE' seja zero, uma vez que o construtor não foi definido como pagável, o que significa que nenhum valor deveria ser enviado com a transação. Se um valor for enviado por engano, a execução acabará revertendo, como indicado pelo opcode 'INVALID'.
Opcode para o contrato Pagável
//Configurar o ponteiro de memória livre
[00] PUSH1 80
[02] PUSH1 40
[04] MSTORE
[05] PUSH1 3f ->
[07] DUP1
[08] PUSH1 11
[0a] PUSH1 00
[0c] CODECOPY ->
[0d] PUSH1 00
[0f] RETURN
[10] INVALID
[11] PUSH1 80
[13] PUSH1 40
[15] MSTORE
[16] PUSH1 00
[18] DUP1
[19] REVERT
[1a] INVALID
[1b] LOG2
[1c] PUSH5 6970667358
[22] INVALID
[23] SLT
[24] SHA3
[25] INVALID
[26] MULMOD
[27] INVALID
[28] INVALID
[29] SWAP4
[2a] INVALID
[2b] RETURN
[2c] CALLCODE
[2d] LOG4
[2e] INVALID
[2f] INVALID
[30] INVALID
[31] LOG4
[32] SWAP13
[33] INVALID
[34] GT
[35] INVALID
[36] INVALID
[37] SWAP10
[38] INVALID
[39] SDIV
[3a] INVALID
[3b] PUSH30 05fa3e9cd05f3e143364736f6c63430008110033
Para o contrato pagável, a EVM ignora as verificações, pois assume que um valor será enviado com a transação, resultando em um custo de gás mais baixo em comparação com o contrato não pagável.
Conclusão
Para resumir a análise de custo de gás, o uso do 'CALLVALUE' e outros opcodes que o seguem para funções não pagáveis resulta em um aumento nos custos de gás, já que cada opcode incorre em uma taxa de gás.
Obrigado por me acompanhar nesta jornada pelas funções pagáveis e não pagáveis do Solidity! Espero que agora você tenha uma compreensão melhor de como essas funções operam, as distinções entre elas e por que as funções pagáveis são uma alternativa mais eficiente em termos de gás nos contratos inteligentes do Solidity. Se este artigo foi útil para você, certifique-se de curtir, comentar, seguir-me e compartilhar este artigo com outras pessoas para que ele possa alcançar um público mais amplo.
Este artigo foi escrito por Oluwatosin Serah e traduzido por Adriano P. de Araujo. O original em inglês pode ser encontrado aqui.
Oldest comments (0)