Estándar de tokens ERC-223
Última edición: @eugenia__(opens in a new tab), 11 de junio de 2024
Introducción
¿Qué es ERC-223?
ERC-223 es un estándar para tokens fungibles, similar al estándar ERC-20. La principal diferencia es que el estándar ERC-223 no solamente define la API del token, sino también la lógica para transferir tokens del remitente al destinatario. Este estándar introduce un modelo de comunicación que permite que las transferencias de tokens sean manejadas desde el lado del destinatario.
Diferencias con ERC-20
ERC-223 resuelve algunas de las limitaciones de ERC-20 e introduce un nuevo método de interacción entre el contrato de token y otro contrato que pueda recibir los tokens. Hay algunas cosas que son posibles con ERC-223, pero no lo son con ERC-20:
- El manejo de transferencias de tokens del lado del destinatario: Los destinatarios pueden detectar que un token ERC-223 está siendo depositado.
- El rechazo de tokens enviados incorrectamente: Si un usuario envía tokens ERC-223 a un contrato que no debe recibir tokens, el contrato puede rechazar la transaccion, evitando la pérdida de tokens.
- Metadatos en las transferencias: Los tokens ERC-223 pueden incluir metadatos, permitiendo que se adjunte información arbitraria a las transacciones de tokens.
Requisitos previos
Cuerpo
ERC-223 es un estándar de token que implementa una API para tokens dentro de contratos inteligentes. También declara una API para contratos que deben recibir tokens ERC-223. Los contratos que no son compatibles con la Receiver API de ERC-223 no pueden recibir tokens ERC-223, impidiendo errores del usuario.
Si un contrato inteligente implementa los métodos y eventos mencionados a continuación, se puede decir que es un contrato compatible con tokens ERC-223. Una vez implementado, será responsable de mantener un registro de los tokens creados en Ethereum.
El contrato inteligente no está obligado a tener solo estas funciones, y el desarrollador puede agregar al contrato cualquier otra característica correspondiente a otro estándar de token. Por ejemplo, las funciones approve
y transferFrom
no son parte del estándar ERC-223, pero estas funciones podrían ser implementadas si fueran necesarias.
De EIP-223(opens in a new tab):
Métodos
Los tokens ERC-223 deben implementar los siguientes métodos:
1function name() public view returns (string)2function symbol() public view returns (string)3function decimals() public view returns (uint8)4function totalSupply() public view returns (uint256)5function balanceOf(address _owner) public view returns (uint256 balance)6function transfer(address _to, uint256 _value) public returns (bool success)7function transfer(address _to, uint256 _value, bytes calldata _data) public returns (bool success)Copiar
Un contrato que debe recibir tokens del estándar ERC-223 tiene que implementar el método siguiente:
1function tokenReceived(address _from, uint _value, bytes calldata _data)Copiar
Si los tokens ERC-223 son enviados a un contrato que no implementa la función tokenReceived(..)
, la transferencia debe fallar y los tokens tienen que permanecer en el saldo del emisor.
Eventos
1event Transfer(address indexed _from, address indexed _to, uint256 _value, bytes calldata _data)Copiar
Ejemplos
La API del token ERC-223 es similar a la API de ERC-20, por lo tanto, desde el punto de vista del desarrollo de la UI, no hay diferencia. La única excepción aquí es que los tokens ERC-223 pueden no incluir las funciones approve
+ transferFrom
porque estas son opcionales para este estándar.
Ejemplos de Solidity
El ejemplo a continuación ilustra cómo opera un contrato de token ERC-223 básico:
1pragma solidity ^0.8.19;2abstract contract IERC223Recipient {3 function tokenReceived(address _from, uint _value, bytes memory _data) public virtual;4}5contract VeryBasicERC223Token {6 event Transfer(address indexed from, address indexed to, uint value, bytes data);7 string private _name;8 string private _symbol;9 uint8 private _decimals;10 uint256 private _totalSupply;11 mapping(address => uint256) private balances;12 function name() public view returns (string memory) { return _name; }13 function symbol() public view returns (string memory) {return _symbol; }14 function decimals() public view returns (uint8) { return _decimals; }15 function totalSupply() public view returns (uint256) { return _totalSupply; }16 function balanceOf(address _owner) public view returns (uint256) { return balances[_owner]; }17 function isContract(address account) internal view returns (bool) {18 uint256 size;19 assembly { size := extcodesize(account) }20 return size > 0;21 }22 function transfer(address _to, uint _value, bytes calldata _data) public returns (bool success){23 balances[msg.sender] = balances[msg.sender] - _value;24 balances[_to] = balances[_to] + _value;25 if(isContract(_to)) {26 IERC223Recipient(_to).tokenReceived(msg.sender, _value, _data);27 }28 emit Transfer(msg.sender, _to, _value, _data);29 return true;30 }31 function transfer(address _to, uint _value) public returns (bool success){32 bytes memory _empty = hex"00000000";33 balances[msg.sender] = balances[msg.sender] - _value;34 balances[_to] = balances[_to] + _value;35 if(isContract(_to)) {36 IERC223Recipient(_to).tokenReceived(msg.sender, _value, _empty);37 }38 emit Transfer(msg.sender, _to, _value, _empty);39 return true;40 }41}Mostrar todoCopiar
Ahora queremos que otro contrato acepte depósitos del tokenA
, asumiendo que dicho token es un token ERC-223. Los contratos deben aceptar únicamente tokenA y rechazar otros tipos de tokens. Cuando el contrato recibe un tokenA debe emitir un evento Deposit()
y aumentar el valor de la variable interna deposits
.
Este es el código:
1contract RecipientContract is IERC223Recipient {2 event Deposit(address whoSentTheTokens);3 uint256 deposits = 0;4 address tokenA; // La única token que queremos aceptar.5 function tokenReceived(address _from, uint _value, bytes memory _data) public override6 {7 // Es importante entender que dentro de esta función8 // msg.sender es la dirección de una token que está siendo recibida9 // msg.value es siempre 0 por lo que la token del contrato normalmente no tiene o envía Ether10 // _from es el remitente de la transferencia.11 // _value es la cantidad de tokens que fue depositada.12 require(msg.sender == tokenA);13 deposits += _value;14 emit Deposit(_from);15 }16}Mostrar todoCopiar
Preguntas frecuentes
¿Qué pasaría si enviamos tokenB al contrato?
La transacción va a fallar y la transferencia de tokens no va a suceder. Los tokens serán regresados a la dirección del remitente.
¿Cómo podríamos realizar un depósito a este contrato?
Invocamos la función transfer(address,uint256)
o a la función transfer(address,uint256,bytes)
del token ERC-223 especificando la dirección del RecipientContract
.
¿Qué ocurriría si transfiriéramos un token ERC-20 a este contrato?
Si se envía un token ERC-20 al RecipientContract
, los tokens se transferirán, pero la transferencia no se reconocerá (no se disparará un evento Deposit()
, y el valor de los depósitos no cambiará). Los depósitos ERC-20 no reconocidos no pueden filtrarse ni evitarse.
¿Qué ocurre si queremos ejecutar una función luego de que el depósito haya sido completado?
Existen varias formas de lograrlo. En este ejemplo, seguiremos el método que hace que las transferencias ERC-223 sean identicas a transferencias de Ether:
1contract RecipientContract is IERC223Recipient {2 event Foo();3 event Bar(uint256 someNumber);4 address tokenA; // La única token que queremos aceptar.5 function tokenReceived(address _from, uint _value, bytes memory _data) public override6 {7 require(msg.sender == tokenA);8 address(this).call(_data); // Maneja la transacción entrante y realiza una llamada subsecuente.9 }10 function foo() public11 {12 emit Foo();13 }14 function bar(uint256 _someNumber) public15 {16 emit Bar(_someNumber);17 }18}Mostrar todoCopiar
Cuando el RecipientContract
recibe un token ERC-223, el contrato ejecutará una función codificada con el parámetro _data
que corresponde a la transacción del token. Esto es idéntico al modo en que las transacciones de Ether codifican las llamadas a funciones como data
de transacción. Lea el campo de datos(opens in a new tab) para obtener más información.
En el ejemplo anterior, se debe transferir un token ERC-223 a la dirección del RecipientContract
con la función transfer(address,uin256,bytes calldata _data)
. Si el parámetro de los datos será 0xc2985578
(la firma de una función foo()
), la función foo() será invocada luego de que se reciba el depósito y se disparará el evento Foo().
Los parámetros pueden codificarse en los datos (data
) de la transferencia de tokens, por ejemplo, podemos llamar a la función bar() con el valor 12345 para _someNumber
. En este caso data
debe ser 0x0423a13200000000000000000000000000000000000000000000000000000000000004d2
, donde 0x0423a132
es la firma de la función bar(uint256)
y 00000000000000000000000000000000000000000000000000000000000004d2
es 12345 como uint256.
Limitaciones
Si bien ERC-20 aborda varios problemas encontrados en el estándar ERC-223, no dejan de existir otras limitaciones propias:
- Adopción y compatibilidad: El estándar ERC-223 no se encuentra ampliamente adoptado, lo cual limita su compatibilidad con herramientas y plataformas existentes.
- Compatibilidad hacia atrás: El estándar ERC-223 no tiene retrocompatibilidad con ERC-223. Esto significa que los contratos y las herramientas existentes no funcionarán con tokens ERC-223, sin previamente haber realizado modificaciones.
- Costos de gas: Los chequeos y funcionalidades adicionales brindadas por las transferencias del estándar-223 pueden resultar en costos de gas más elevados, en comparación con aquellos de transacciones ERC-20.