WEB3DEV Español

Cover image for Explorando las Diferencias Entre Funciones Pagables y No Pagables en Solidity: Un Análisis en Profundidad
Hector
Hector

Posted on

Explorando las Diferencias Entre Funciones Pagables y No Pagables en Solidity: Un Análisis en Profundidad

Image description

¡Bienvenido a otro artículo de las funciones pagables y no pagables en Solidity! Si estás interesado en el desarrollo de la blockchain y contratos inteligentes probablemente te has encontrado, en algún momento, con estos términos. En este artículo, explicaremos qué son las funciones pagables y no pagables, cómo funcionan en Solidity y por qué las funciones pagables son menos costosas (en términos de gas) que las funciones no pagables. También exploraremos algunos casos de uso para ambos tipos de funciones y destacar las cosas negativas que los desarrolladores deben tomar en cuenta cuando eliges entre ellos. Al final de este artículo, deberías tener un entendimiento claro de cómo usar las funciones pagables y no pagables, efectivamente, en tus contratos inteligentes de Solidity. ¡Sumerjámonos!

Funciones Pagables

En Solidity, una función pagable es una función que puede aceptar ether como un input. Cuando un contrato se llama como función pagable, la persona que llama puede enviar ether junto con la función de llamada. Luego, el ether, es almacenado en el balance de la cuenta, el cual puede accederse usando la propiedad “address(this).balance”.

Cómo declarar una función pagable

En Solidity, puedes usar la función pagable cuando quieres permitir que un contrato reciba ether. Una función pagable es declarada usando el keyword “payable”, lo cual hace posible recibir ether como parte de una función de llamada. Mira el código de ejemplo:

//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;
}
}

Enter fullscreen mode Exit fullscreen mode

El código de arriba define un contrato que permite a los usuarios depositar ether en sus cuentas, llamado a la función de depósito.

Función No Pagable

Una función no pagable, en su contraparte, es una función que no puede aceptar ether como input. Si un contrato se llama con una función no pagable y la persona que llama intenta enviar ether en la llamada, resultará en un error. Mira el código de ejemplo:

//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;
}
}
Enter fullscreen mode Exit fullscreen mode

Usar msg.value en funciones no pagables

En Solidity, usamos “msg.value” en funciones no pagables no es permitido, por razones de seguridad. “Msg.value” representa la cantidad de ether enviado con la función de llamada. Si es usada en una función no pagable, esto quiere decir que la función está recibiendo ether, lo cual no estaba previsto. Esto puede resultar en problemas de seguridad y pérdidas monetarias potenciales.

Para manejar este escenario o necesitas hacer la función pagable o crear una nueva función interna que usa “msg.value”. Si la función necesita recibir ether pero deberías hacerlo pagable, añadiendo el keyword pagable antes de la definición de la función. Esto permite que la función reciba y procese ether.

Funciones que son Implícitamente Pagables

  1. Función de Recibir: la función de recibir es automáticamente pagable y se llama cuando los datos de la llamada están vacíos. Esto significa que si alguien envía una transacción al contrato sin especificar cuál función llamar, la función de recibir será ejecutada. Será declarado así:
receive() external payable{}
Enter fullscreen mode Exit fullscreen mode
  1. Función Fallback: la función fallback, en su contraparte, se desencadena cuando llamas a la función que no existe o coincide con cualquier función en un contrato. Si la función de recibir no existe, la función fallback manejará las llamadas con datos de llamada vacíos. Si una función fallback no es pagable, las transacciones o funciones de llamada que no coincidan con cualquier otra función y que también envíen valor (ether), será revertido. Será declarado así:
fallback() external payable{}
Enter fullscreen mode Exit fullscreen mode

Conversión explícita de la dirección

Es importante tomar en cuenta que no todas las direcciones son pagables, y si intentas enviar ethers a una dirección no pagable, la transacción fallará.

La conversión explícita de dirección a dirección pagable sólo es posible si el contrato tiene una función de recibir o una función fallback pagable. La conversión puede realizarse usando la dirección (x), donde x debe ser el tipo de la dirección.

Sin embargo, si el tipo de contrato no tiene la función de recibir o la función fallback pagable, la conversión a la dirección pagable puede hacerse usando payable(address(x)). Esto es porque un contrato sin una función de recibir o una función fallback pagable no puede recibir ether y, por lo tanto, no puede convertirse a una dirección pagable.

En esencia, la conversión de dirección a dirección pagable se hace para permitir enviar ether a la dirección. Esta conversión es usada cuando se llama a una función del contrato que tiene el modificador “pagable”, lo cual permite recibir ether. El keyword “pagable” hace que la función acepte ether e incremente el balance del contrato por la cantidad de ether recibido. Mira el código de ejemplo:

//la dirección pagable provee la función de transferencia
address payable payableAddress;
Enter fullscreen mode Exit fullscreen mode

Análisis del costo de gas para funciones pagables o no pagables

Vamos a realizar una transacción y observar el costo de gas para cada caso, usando los siguientes contratos

//SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract Payable{
mapping(address => uint256) public balances;

//Gas cost 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;

//Gas cost 31141
function depositAmount(uint256 amount) public {
require(amount > 0, "Zero amount not allowed");
balances[msg.sender] = balances[msg.sender] + amount;
}
}
Enter fullscreen mode Exit fullscreen mode

La transacción depositEther en el contrato pagable cuesta 30605 gas mientras que la transacción depositAmount en el contrato no pagable cuesta 31141 gas. Observa que el costo del gas para la función pagable es más económico que la función no pagable. ¡Desempaquetemos el misterio detrás de esto!📜

Por qué las funciones pagables son más económicas que las funciones no pagables

Usando el código de abajo, esto te mostrará por qué las funciones pagables son más económicas que las funciones no pagables:

//SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract Payable{
constructor() payable{
}
}

contract NonPayable{
constructor(){
}
}
Enter fullscreen mode Exit fullscreen mode

En vez de usar el código anterior, el cual el bytecode y opcode de los contratos habrían sido extremadamente largos y complejos, usaré la función de construcción vacía. Haciendo esto, será fácil para todos comprender y, subsecuentemente, aplicar el conocimiento a otros contratos sin ninguna confusión.

Código Init

El código init, también conocido como el código constructor, es ejecutado solo una vez cuando el contrato es desplegado. Su propósito es inicializar el estado del contrato y establecer sus valores iniciales. El código init es usado para crear y almacenar las variables del contrato, establecen la lógica del contrato y realizan cualquier otra tarea de configuración necesaria para que el contrato funcione correctamente.

Código Runtime

El código runtime es ejecutado cada vez que el contrato se llama o invoca. Este código define la lógica del contrato, incluyendo cómo interactúa con otros contratos, cómo almacena y retira los datos y cómo manejan las transacciones. El código runtime es lo que se ejecuta cuando un contrato es interactuado.

Bytecode para los contratos No Pagables

[6080604052"348015600f57600080fd5b50603f80601d"6000396000f3fe] - Init Code
[6080604052600080fdfea2646970667358fe1220fefefefefe160342fefe35692fec668c4c7705aa8c111a45fe6447aec315841164736f6c63430008110033]- Runtime code
Enter fullscreen mode Exit fullscreen mode

Bytecode para los contratos Pagables

[6080604052"603f806011600039"6000f3fe] - Init Code
[6080604052600080fdfea2646970667358fe1220fe09fefe93fef3f2a4fefefea49cfe11fefe99fe05fe7d05fa3e9cd05f3e143364736f6c63430008110033]- Runtime code
Enter fullscreen mode Exit fullscreen mode

Si te percatas, el código init para la función no pagable es más larga que la de la función pagable, lo mismo sucede para los opcodes de abajo. El init está destacado en doble cita.

Opcode para los contratos No Pagables

//Configura el puntero para liberar la memoria
[00] PUSH1 80
[02] PUSH1 40
[04] MSTORE //Almacena la palabra en la memoria
[05] CALLVALUE -> Is the amount in wei that was with a transaction
[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
Enter fullscreen mode Exit fullscreen mode

Los opcodes comentados realizan las siguientes acciones: “CALLVALUE” revisa la cantidad de wei enviada en la transacción, duplicada en el opcode “DUP” y luego determina si es igual a cero usando el opcode “ISZERO”.

Si el valor es cero, el opcode “ISZERO” empuja 1 a la pila, lo que significa “true” y continua la ejecución. Si no, empuja 0 que significa “falsa”.

En este caso, se espera que “CALLVALUE” sea cero ya que el constructor no fue configurado para que sea pagable, lo que quiere decir que la intención fue enviar ningún valor en la transacción. Si el valor se envía por accidente, la ejecución será eventualmente revertida, como se indica en el opcode “INVALID”.

Opcode para el contrato Pagable

//Configura el puntero para liberar la memoria
[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
Enter fullscreen mode Exit fullscreen mode

Para el contrato pagable, la EVM se salta las revisiones y asume que un valor será enviado con la transacción, resultando en un costo de gas menor, comparado con el contrato no pagable.

Conclusión

En resumen, el análisis del costo de gas, el uso de “CALLVALUE” y otros opcodes que siguen funciones no pagables, que resulta en el costo incrementado de gas, ya que cada opcode incurre en una tarifa de gas.

¡Gracias por unirte en esta travesía a través de las funciones pagables y no pagables de Solidity! Confío en que ahora tienes un mejor entendimiento de cómo estas funciones operan, las distinciones entre ellas y por qué las funciones pagables son una alternativa más eficiente con el gas en los contratos inteligentes en Solidity. Si este artículo te ha ayudado, asegúrate de darle me gusta, comentar, seguirme y compartir este artículo con otros para que puedan llegar a una mayor audiencia.

Este artículo es una traducción de Oluwatosin Serah, hecha por Héctor Botero. Puedes encontrar el artículo original aquí.
Sería genial escucharte en nuestro Discord, puedes contarnos tus ideas, comentarios, sugerencias y dejarnos saber lo que necesitas.
Si prefieres puedes escribirnos a @web3dev_es en Twitter.

Discussion (0)