El estándar de token ERC-6909 es una alternativa simplificada al estándar de token ERC-1155.
El estándar ERC-1155 introdujo una interfaz de múltiples tokens que permite a un único contrato inteligente incorporar tokens fungibles y no fungibles (es decir, ERC20 y ERC721).
El ERC-1155 abordó varios desafíos, como reducir los costos de despliegue, minimizar el bytecode redundante en la blockchain de Ethereum y agilizar las aprobaciones de tokens para el comercio de múltiples tokens.
Sin embargo, introdujo cierta sobrecarga e ineficiencia de gas debido a los callbacks obligatorios para cada transferencia, la inclusión forzada de transferencias por lotes (batch transfer) y la falta de un control preciso en el esquema de aprobaciones de operador único. El ERC-6909 aborda estos inconvenientes eliminando los callbacks a nivel de contrato y las transferencias por lotes, y reemplazando el esquema de permisos de operador único con un esquema de permisos híbrido (allowance-operator) para la gestión granular de tokens.
Nota:
La siguiente sección asume familiaridad con el estándar ERC-1155 y sus conceptos. Si no está familiarizado, por favor revise el artículo antes de continuar.
El contraste entre el estándar ERC-6909 y ERC-1155
El ERC-6909 elimina el requisito de callback para las transferencias
La especificación ERC-1155 requiere que safeTransferFrom y safeBatchTransferFrom verifiquen que la cuenta receptora sea un contrato. Si es así, DEBE llamar a las funciones de la interfaz ERC1155TokenReceiver (onERC1155Received, onERC1155BatchReceived) en la cuenta del contrato receptor para verificar si acepta transferencias.
Estos callbacks son útiles en algunos casos. Sin embargo, son llamadas externas innecesarias para el receptor que desea optar por no participar en este comportamiento. Los callbacks impactan el costo de gas y el tamaño del código de las cuentas de contratos receptores, ya que requieren implementar múltiples callbacks (es decir, a través de onERC1155Received, onERC1155BatchReceived) y devolver valores mágicos de 4 bytes para recibir los tokens. Por el contrario, a los implementadores del ERC-6909 se les permite decidir sobre su arquitectura de callbacks.
El ERC-6909 omite la lógica de transferencias por lotes
Las transferencias por lotes, aunque a veces beneficiosas, se omiten deliberadamente en el estándar ERC-6909 para permitir a los desarrolladores implementar la lógica de transferencia por lotes adaptada a entornos de ejecución específicos. Los desarrolladores pueden implementar transferencias por lotes como mejor les parezca y no tienen que añadir una función adicional de transferencia por lotes simplemente para seguir el estándar.
La función safeBatchTransferFrom, que se muestra a continuación, ejecuta transferencias por lotes en el estándar ERC-1155. Sin embargo, su inclusión forzada añade sobrecarga a las aplicaciones que no las necesitan:
// ERC-1155
function safeBatchTransferFrom(
address _from,
address _to,
uint256[] calldata _ids,
uint256[] calldata _values,
bytes calldata _data
) external;
Aquí está la función transferFrom del ERC-6909. Podemos ver que la característica de lotes y el parámetro _data han sido eliminados.
// ERC-6909
function transferFrom(
address sender,
address receiver,
uint256 id,
uint256 amount
) public returns (bool) {
if (sender != msg.sender && !isOperator[sender][msg.sender]) {
uint256 senderAllowance = allowance[sender][msg.sender][id];
if (senderAllowance < amount) revert InsufficientPermission();
if (senderAllowance != type(uint256).max) {
allowance[sender][msg.sender][id] = senderAllowance - amount;
}
}
if (balanceOf[sender][id] < amount) revert InsufficientBalance();
balanceOf[sender][id] -= amount;
balanceOf[receiver][id] += amount;
emit Transfer(msg.sender, sender, receiver, id, amount);
return true;
}
El ERC-6909 admite tanto aprobaciones globales como allowances granulares
// in ERC-1155 →
function setApprovalForAll(
address _operator,
bool _approved
) external;
La función setApprovalForAll, mostrada arriba, es un modelo de operador global en el ERC-1155 que permite a una cuenta autorizar a otra para gestionar (actuar como un operador) todos los ID de tokens en su nombre. Una vez autorizados, los operadores tienen acceso irrestricto para transferir cualquier cantidad de cualquier ID de token propiedad de la cuenta autorizante.
Si bien este enfoque simplifica la delegación, carece de un control detallado:
- No hay forma de otorgar permisos específicos para cantidades o ID de tokens individuales.
- Este enfoque de todo o nada no se alineará con escenarios que requieran permisos controlados.
Para introducir un control granular, el esquema de permisos de operador híbrido del ERC-6909 incorpora lo siguiente:
- el modelo de operador (operator) del ERC-1155,
- y el modelo de asignación (allowance) inspirado en el ERC-20.
El modelo de operador en el ERC-6909
En la función setOperator del ERC-6909, que se muestra a continuación, la variable spender se establece como un operador y se le otorgan permisos globales para transferir todos los ID de tokens propiedad de la cuenta sin restricciones de allowance.
function setOperator(address spender, bool approved) public returns (bool) {
isOperator[msg.sender][spender] = approved;
emit OperatorSet(msg.sender, spender, approved);
return true;
}
El modelo de allowance en el ERC-6909
El modelo de allowance introduce un sistema de control específico de la cantidad y específico del token donde una cuenta puede establecer un allowance limitado para un ID de token específico.
Por ejemplo, Alice puede permitir que Bob transfiera 100 unidades del ID de token 42 sin otorgarle acceso a otros ID de tokens o a cantidades ilimitadas, utilizando la función approve del ERC-6909 que se muestra a continuación.
function approve(address spender, uint256 id, uint256 amount) public returns (bool) {
allowance[msg.sender][spender][id] = amount;
emit Approval(msg.sender, spender, id, amount);
return true;
}
La variable spender en approve es una cuenta autorizada para transferir cantidades específicas de un ID de token específico en nombre del propietario del token.
Por ejemplo, el propietario de un token puede permitir al spender transferir <= 100 unidades de un ID de token en particular. Alternativamente, también pueden otorgar aprobación infinita para un ID de token específico estableciendo el allowance en type(uint256).max.
El ERC-6909 no especifica si los allowances establecidos en type(uint256).max deben deducirse o no. En su lugar, este comportamiento queda a discreción del implementador, de manera similar al ERC-20.
Estructuras de Datos Principales
Las implementaciones de ERC-6909 utilizan tres mapeos (mappings) para actualizar el estado de los saldos de cuentas y aprobaciones.
balanceOf: Saldo del propietario de un ID
El mapping balanceOf rastrea el saldo de un ID de token específico que posee una dirección (owner). La estructura owner => (id => amount) en el mapping indica que un solo propietario puede tener múltiples tokens y rastrear sus saldos a través de sus respectivos ID.
mapping(address owner => mapping(uint256 id => uint256 amount)) public balanceOf;
allowance: Allowance de un Spender sobre un ID
El mapping allowance define qué cantidad de un token específico (ID) puede transferir un spender en nombre de un propietario. Esto facilita el control detallado sobre el gasto de tokens.
mapping(address owner => mapping(address spender => mapping(uint256 id => uint256 amount))) public allowance;
Por ejemplo, allowance[0xDEF...][0x123...][5] devolverá la cantidad de tokens (con el ID 5) que el propietario 0xDEF... ha permitido que el spender 0x123... transfiera.
isOperator: Estado de aprobación del operador
mapping(address owner => mapping(address operator => bool isOperator)) public isOperator;
Este mapping rastrea si un spender ha sido aprobado como operador para todos los tokens propiedad de una dirección. Por ejemplo, isOperator[0x123...][0xABC...] devuelve true si a la dirección 0xABC... se le permite gastar tokens propiedad de la dirección 0x123...; de lo contrario, devuelve false.
Funciones Principales del ERC-6909 y sus Parámetros de Datos
Las Funciones de Transferencia
Esta especificación no sigue el “mecanismo de transferencia segura” que se ve en el ERC-721 y el ERC-1155, ya que la convención de nomenclatura se consideró engañosa, dado que realizan llamadas externas a contratos arbitrarios. El ERC-6909 utiliza las funciones transfer y transferFrom con los siguientes detalles.
Transfer:
La función transfer del ERC-6909 se comporta de la misma manera que el transfer de ERC-20, excepto que se aplica a un ID de token específico. La función toma la dirección del receptor, el ID del token y la cantidad a transferir como parámetros de entrada y actualiza los saldos usando el mapping balanceOf. Como en la función transfer del ERC-20, es necesario devolver true si la transacción se ejecuta con éxito.
//ERC-20 interface transfer function
function transfer(address _to, uint256 _value) public returns (bool)
// ERC-6909 transfer function Reference Implementation
// @notice Transfers an amount of an id from the caller to a receiver.
// @param receiver The address of the receiver.
// @param id The id of the token.
// @param amount The amount of the token.
function transfer(address receiver, uint256 id, uint256 amount) public returns (bool) {
if (balanceOf[msg.sender][id] < amount) revert InsufficientBalance(msg.sender, id);
balanceOf[msg.sender][id] -= amount;
balanceOf[receiver][id] += amount;
emit Transfer(msg.sender, msg.sender, receiver, id, amount);
return true;
}
transferFrom:
La función transferFrom del ERC-6909 difiere de la del ERC-20 al requerir un ID de token. Adicionalmente, verifica la aprobación del operador además del allowance.
La función primero verifica if (sender != msg.sender && !isOperator[sender][msg.sender]), asegurando que el invocador (msg.sender) sea:
- El propietario (
sender), o - Un operador aprobado (
isOperator[sender][msg.sender] == true).
Si msg.sender no es el propietario ni un operador aprobado, la función verifica si el invocador tiene suficiente allowance para la transferencia. Si existe un allowance pero no está configurado como ilimitado (type(uint256).max), la amount transferida se deduce del allowance.
Además, el estándar especifica que la función NO DEBE restar amount del allowance del id del token del invocador si el invocador es un operador o el sender.
// ERC-6909 transferFrom
function transferFrom(address sender, address receiver, uint256 id, uint256 amount) public returns (bool) {
if (sender != msg.sender && !isOperator[sender][msg.sender]) {
uint256 senderAllowance = allowance[sender][msg.sender][id];
if (senderAllowance < amount) revert InsufficientPermission();
if (senderAllowance != type(uint256).max) {
allowance[sender][msg.sender][id] = senderAllowance - amount;
}
}
if (balanceOf[sender][id] < amount) revert InsufficientBalance();
balanceOf[sender][id] -= amount;
balanceOf[receiver][id] += amount;
emit Transfer(msg.sender, sender, receiver, id, amount);
return true;
}
approve:
La función approve permite al invocador (msg.sender) otorgar un allowance específico de un token (ID) a un spender. Esto actualiza el mapping de allowance para reflejar el nuevo allowance y emite un evento Approval.
function approve(address spender, uint256 id, uint256 amount) public returns (bool) {
allowance[msg.sender][spender][id] = amount;
emit Approval(msg.sender, spender, id, amount);
return true;
}
setOperator:
La función setOperator permite al invocador (msg.sender) otorgar o revocar permisos de operador para una dirección específica (spender) en su nombre estableciendo el parámetro approved en true o false. La función actualiza el mapping isOperator en consecuencia y emite un evento OperatorSet para notificar a los oyentes externos sobre el cambio.
function setOperator(address spender, bool approved) public returns (bool) {
isOperator[msg.sender][spender] = approved;
emit OperatorSet(msg.sender, spender, approved);
return true;
}
Eventos y Registros (Loggings) en el ERC-6909
El ERC-6909 define eventos clave para rastrear transferencias de tokens, aprobaciones y permisos de operador dentro de un contrato multitoken.
1. Evento Transfer:
/// @notice The event emitted when a transfer occurs.
event Transfer(address caller, address indexed sender, address indexed receiver, uint256 indexed id, uint256 amount);
El evento Transfer en el ERC-6909 se usa para rastrear los movimientos de tokens y debe emitirse bajo las siguientes condiciones:
- El evento se emite cuando una
amountde un tokenidse transfiere de una cuenta a otra. Registra elsender,receiver,token IDy laamounttransferida. - Cuando se crean nuevos tokens, el evento debe emitirse con el
sendercomo la dirección cero (0x0). - Cuando se destruyen tokens, el evento debe emitirse con el receptor como la dirección cero (
0x0) para indicar la eliminación del token.
2. Evento OperatorSet:
/// @notice The event emitted when an operator is set.
event OperatorSet(address indexed owner, address indexed spender, bool approved);
El evento OperatorSet se emite cada vez que un propietario asigna o revoca permisos de operador para otra dirección. El evento registra la dirección del propietario, la dirección del spender y el estado de aprobación actualizado (true para otorgado, false para revocado).
3. Evento Approval:
/// @notice The event emitted when an approval occurs.
event Approval(address indexed owner, address indexed spender, uint256 indexed id, uint256 amount);
El evento Approval debe emitirse cuando un propietario establece o actualiza una aprobación para que un spender transfiera una cantidad específica de un ID de token determinado en su nombre. El evento registra el owner, el spender, el id del token y la amount aprobada.
Ahora que hemos explorado las diferencias entre ERC-6909 y ERC-1155, así como los métodos y eventos principales en ERC-6909, examinemos algunos usos en la vida real del estándar.
Cómo implementa ERC-6909 el PoolManager de Uniswap v4.
En Uniswap v3, el modelo factory/pool crea nuevos pares de tokens desplegando un contrato separado para cada pool utilizando el contrato UniswapV3Factory. Este método aumenta los costos de gas ya que cada nuevo pool requiere el despliegue de un nuevo contrato.
En contraste, Uniswap v4 introduce un contrato singleton (PoolManager.sol), que gestiona todos los pools de liquidez como parte de su estado interno en lugar de requerir despliegues de contratos separados. Este diseño reduce significativamente los costos de gas para la creación de pools.
Además, las transacciones que involucran múltiples pools de Uniswap en versiones anteriores requerían transferencias de tokens y actualizaciones de estado redundantes en múltiples contratos. En Uniswap v4, en lugar de transferir tokens ERC-20 dentro y fuera de los pools, el contrato PoolManager puede mantener centralmente las representaciones ERC-6909 de los tokens ERC-20 de los usuarios.
Por ejemplo, si un usuario proporciona liquidez para el token A, más tarde puede optar por retirar su parte y recibir el token A como una transferencia ERC-20 a su billetera. Sin embargo, si optan por dejar sus tokens dentro del protocolo, el PoolManager de Uniswap v4 puede acuñar representaciones ERC-6909 de sus saldos de tokens al LP en lugar de transferir tokens ERC-20 fuera del contrato, ahorrando una llamada entre contratos. Estos saldos ERC-6909 permiten a los usuarios hacer trading o interactuar dentro del protocolo sin necesidad de mover tokens entre billeteras.
Esto significa que cuando el usuario intercambia más tarde el token A por el token B, en lugar de transferir tokens ERC-20 desde su billetera, Uniswap simplemente actualiza su saldo ERC-6909 dentro del pool.
Nota: El ERC-6909 no se utiliza como tokens LP en Uniswap V4.
Consideraciones de metadatos ERC-6909 para arquitecturas defi singleton y colecciones NFT
Aquí está la interfaz IERC6909Metadata que define cómo el estándar ERC-6909 asocia metadatos, como name, symbols y decimals, que pueden estar asociados con tokens individuales.
Tenga en cuenta que las funciones name, symbol y decimals a continuación pueden cambiar según el id, permitiendo que diferentes tokens dentro del ERC-6909 tengan diferentes nombres, símbolos y decimales.
/// @notice Contains metadata about individual tokens.
interface IERC6909Metadata is IERC6909 {
/// @notice Name of a given token.
/// @param id The id of the token.
/// @return name The name of the token.
function name(uint256 id) external view returns (string memory);
/// @notice Symbol of a given token.
/// @param id The id of the token.
/// @return symbol The symbol of the token.
function symbol(uint256 id) external view returns (string memory);
/// @notice Decimals of a given token.
/// @param id The id of the token.
/// @return decimals The decimals of the token.
function decimals(uint256 id) external view returns (uint8);
}
Para un protocolo DeFi, podríamos tener varios tokens LP y es posible que queramos estandarizarlos para que todos tengan la misma cantidad de decimales, como 18. Sin embargo, es posible que queramos que el nombre y el símbolo reflejen diferentes activos mantenidos por el pool.
En contraste, para los NFT, el valor de decimals siempre debe establecerse en 1, ya que los NFT son indivisibles.
En una colección típica de NFT (por ejemplo, ERC-721), todos los tokens comparten el mismo nombre y símbolo para representar la colección en su conjunto (por ejemplo, “CryptoPunks” con el símbolo “PUNK”). El ERC-6909 nos permite seguir la convención ERC-712, donde todos los NFT de una colección comparten los mismos metadatos.
Ejemplo de implementación de un token no fungible ERC-6909.
La especificación ERC-6909 no define explícitamente un enfoque único para soportar tokens no fungibles. Sin embargo, la técnica de división de bits de ID descrita en la especificación ERC-1155 puede usarse para implementar tokens no fungibles en el ERC-6909. Este enfoque codifica el ID de la colección y el ID del artículo en un solo ID de token uint256 utilizando operaciones de suma y desplazamiento de bits.
function getTokenId(uint256 collectionId, uint256 itemId) public pure returns (uint256) {
return (collectionId << 128) + itemId;
}
El contrato ERC6909MultiCollectionNFT a continuación es un ejemplo de una implementación de un token no fungible (NFT) que utiliza getTokenId para generar un ID de token a partir de collectionId e itemId.
La función mintNFT garantiza que cada tokenId solo se pueda acuñar una vez, independientemente de la dirección. Realiza un seguimiento de si un tokenId de NFT ha sido acuñado globalmente mediante el mapping mintedTokens.
Dado que la variable amount se establece en 1 en mintNFT, la llamada _mint(to, tokenId, amount) en la función acuñará solo una copia para un tokenId. En cualquier caso en el que amount > 1, el token se volvería fungible en lugar de no fungible.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "./ERC6909.sol";
contract ERC6909MultiCollectionNFT is ERC6909 {
struct NFT {
string uri;
}
mapping(uint256 => NFT) private _tokens;
mapping(uint256 => string) private _collectionURIs;
mapping(uint256 => bool) public mintedTokens;
event MintedNFT(address indexed to, uint256 indexed collectionId, uint256 indexed itemId, uint256 tokenId, string uri);
// Compute Token ID by concatenating collectionId and itemId
function getTokenId(uint256 collectionId, uint256 itemId) public pure returns (uint256) {
return (collectionId << 128) + itemId;
}
function _mint(address to, uint256 tokenId, uint256 amount) internal {
balanceOf[to][tokenId] += amount;
emit Transfer(msg.sender, address(0), to, tokenId, amount);
}
function mintNFT(address to, uint256 collectionId, uint256 itemId, string memory uri) external {
uint256 amount = 1;
uint256 tokenId = getTokenId(collectionId, itemId);
require(!mintedTokens[tokenId], "ERC6909MultiCollectionNFT: Token already minted");
require(amount == 1, "ERC6909MultiCollectionNFT: Token copies must be 1");
_tokens[tokenId] = NFT(uri);
mintedTokens[tokenId] = true; // Mark as minted
_mint(to, tokenId, amount); // amount is defined as 1.
emit MintedNFT(to, collectionId, itemId, tokenId, uri);
}
function nftBalanceOf(address owner, uint256 tokenId) public view returns (uint256) {
return balanceOf[owner][tokenId];
}
}
Recuerde que la llamada _mint en mintNFT, arriba, actualiza el mapping balanceOf a 1, ya que las acuñaciones son totalmente no fungibles. Por lo tanto, se espera que la función nftBalanceOf devuelva siempre 1 en este contrato si la dirección del owner de hecho acuñó el tokenId.
Para transferir la propiedad del token, la función nftTransfer a continuación garantiza que solo el propietario del NFT pueda iniciar una transferencia verificando su saldo antes de permitir que se transfiera la única unidad existente.
function nftTransfer(address to, uint256 tokenId) external {
require(balanceOf[tokenId][msg.sender] == 1, "ERC6909MultiCollectionNFT: This should be non-fungible.");
require(to != address(0), "ERC6909MultiCollectionNFT: transfer to zero address");
transfer(to, tokenId, 1);
// the amount in this case is equal to 1.
emit Transfer(msg.sender, address(0), to, tokenId, 1);
}
Extensiones URI de contenido de ERC-6909 y esquema JSON de URI de metadatos
Para estandarizar el acceso a metadatos en ERC-6909, la interfaz opcional IERC6909ContentURI define dos funciones URI (contractURI y tokenURI) para recuperar metadatos a nivel de contrato y de token. El estándar ERC-6909 no exige que los tokens tengan metadatos URI asociados a ellos. Sin embargo, si una implementación incluye estas funciones URI, las URI devueltas deben apuntar a archivos JSON que se ajusten al esquema JSON de URI de metadatos del ERC-6909.
pragma solidity ^0.8.19;
import "./IERC6909.sol";
/// @title ERC6909 Content URI Interface
interface IERC6909ContentURI is IERC6909 {
/// @notice Contract level URI
/// @return uri The contract level URI.
function contractURI() external view returns (string memory);
/// @notice Token level URI
/// @param id The id of the token.
/// @return uri The token level URI.
function tokenURI(uint256 id) external view returns (string memory);
}
Como se muestra en el código anterior, la interfaz IERC6909ContentURI del ERC-6909 define dos funciones URI opcionales, a saber, contractURI y tokenURI; cada una con su esquema JSON de URI correspondiente.
La función contractURI (no toma argumentos) devuelve un único URI que apunta a los metadatos a nivel de contrato, mientras que tokenURI() devuelve un URI específico para cada ID de token.
A continuación se muestra un ejemplo de cómo estructurar el Esquema JSON de la URI del Contrato, según lo especificado por el estándar ERC-6909.
{
"title": "Contract Metadata",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the contract."
},
"description": {
"type": "string",
"description": "The description of the contract."
},
"image_url": {
"type": "string",
"format": "uri",
"description": "The URL of the image representing the contract."
},
"banner_image_url": {
"type": "string",
"format": "uri",
"description": "The URL of the banner image of the contract."
},
"external_link": {
"type": "string",
"format": "uri",
"description": "The external link of the contract."
},
"editors": {
"type": "array",
"items": {
"type": "string",
"description": "An Ethereum address representing an authorized editor of the contract."
},
"description": "An array of Ethereum addresses representing editors (authorized editors) of the contract."
},
"animation_url": {
"type": "string",
"description": "An animation URL for the contract."
}
},
"required": ["name"]
}
Por otro lado, la función tokenURI recibe un parámetro id de tipo uint256 de un token y devuelve la URI del token. La función PUEDE revertir si el id del token no existe. Los clientes que interactúan con el contrato DEBEN reemplazar cada aparición de {id} en el URI con el ID del token real para acceder a los metadatos correctos asociados con ese token.
A continuación se muestra la implementación de la función tokenURI, que devuelve una plantilla URI estática que sigue un formato de marcador de posición (placeholder):
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "./ERC6909.sol";
import "./interfaces/IERC6909ContentURI.sol";
contract ERC6909ContentURI is ERC6909, IERC6909ContentURI {
/// @notice The contract level URI.
string public contractURI;
/// @notice The URI for each id.
/// @return The URI of the token.
function tokenURI(uint256) public pure override returns (string memory) {
return "<baseuri>/{id}";
}
}
Aquí hay un ejemplo de cómo estructurar el Esquema JSON de la URI, según lo especificado por el estándar ERC-6909.
{
"title": "Asset Metadata",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Identifies the token"
},
"description": {
"type": "string",
"description": "Describes the token"
},
"image": {
"type": "string",
"description": "A URI pointing to an image resource."
},
"animation_url": {
"type": "string",
"description": "An animation URL for the token."
}
},
"required": ["name", "description", "image"]
}
Ambigüedad entre Allowance y Operator en la especificación de ERC-6909.
Considere un escenario donde una cuenta (A) otorga permisos de operador a otra cuenta (B) y también establece un allowance para que B transfiera una cantidad específica de tokens.
Si B inicia una transferencia en nombre de A, la implementación debe determinar el orden correcto de las comprobaciones y cómo interactúan los allowances con los permisos del operador.
La ambigüedad concierne al orden de las comprobaciones. Debería el contrato:
- Comprobar el allowance primero, revirtiendo si es insuficiente, incluso si B tiene permisos de operador.
- Comprobar los permisos del operador primero, permitiendo que la transferencia proceda independientemente del allowance.
En el contrato allowanceFirst a continuación, si la cuenta B tiene permisos de operador pero un allowance insuficiente, la comprobación de allowance fallará, causando que la transacción se revierta. Esto podría ser contrario a la intuición porque los permisos de operador normalmente implican acceso irrestricto, y los usuarios podrían esperar que la transacción tenga éxito.
A la inversa, en el contrato operatorFirst, si la implementación verifica los permisos del operador primero, omitirá la comprobación del allowance, y la transacción tendrá éxito basándose en el acceso irrestricto del operador.
contract operatorFirst {
function transferFrom(address sender, address receiver, uint256 id, uint256 amount) public {
// check if `isOperator` first
if (msg.sender != sender && !isOperator[sender][msg.sender]) {
require(allowance[sender][msg.sender][id] >= amount, "insufficient allowance");
allowance[sender][msg.sender][id] -= amount;
}
// -- snip --
}
}
contract allowanceFirst{
function transferFrom(address sender, address receiver, uint256 id, uint256 amount) public {
// check if allowance is sufficient first
if (msg.sender != sender && allowance[sender][msg.sender][id] < amount) {
require(isOperator[sender][msg.sender], "insufficient allowance");
}
// ERROR: when allowance is insufficient, this panics due to arithmetic underflow, regardless of
// whether the caller has operator permissions.
allowance[sender][msg.sender][id] -= amount;
// -- snip --
}
}
El estándar deja deliberadamente sin restricciones la decisión sobre las comprobaciones de permisos, dando a los implementadores la flexibilidad de elegir. Cuando una cuenta tiene tanto permisos de operador como un allowance insuficiente, el comportamiento de transferencia depende del orden en que se realicen las comprobaciones.
Conclusión
El estándar ERC-6909 mejora significativamente la eficiencia del ERC-1155 al eliminar el procesamiento por lotes (batching) y los callbacks obligatorios en las funciones de transferencia. Eliminar el procesamiento por lotes permite la optimización caso por caso, particularmente para rollups o entornos sensibles a los costos de gas.
También introdujo un control escalable de la aprobación de tokens a través del esquema híbrido de permisos de operador.
Agradecimientos
Nos gustaría agradecer a vectorized, jtriley y neodaoist (coautores del ERC-6909) por revisar este artículo.