DCIPs/EIPS/eip-2135.md

169 lines
7.7 KiB
Markdown

---
eip: 2135
title: Consumable Interface (Tickets, etc)
description: An interface extending EIP-721 and EIP-1155 for consumability, supporting use case such as an event ticket.
author: Zainan Victor Zhou (@xinbenlv)
discussions-to: https://ethereum-magicians.org/t/eip-2135-erc-consumable-interface/3439
status: Last Call
last-call-deadline: 2023-02-01
type: Standards Track
category: ERC
created: 2019-06-23
requires: 165, 721, 1155
---
## Abstract
This EIP defines an interface to mark a digital asset as "consumable" and to react to its "consumption."
## Motivation
Digital assets sometimes need to be consumaed. One of the most common examples is a concert ticket.
It is "consumed" when the ticket-holder enters the concert hall.
Having a standard interface enables interoperability for services, clients, UI, and inter-contract functionalities on top of this use-case.
## Specification
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
1. Any compliant contract **MUST** implement the following interface:
```solidity
pragma solidity >=0.7.0 <0.9.0;
/// The EIP-165 identifier of this interface is 0xdd691946
interface IERC2135 {
/// @notice The consume function consumes a token every time it succeeds.
/// @param _consumer the address of consumer of this token. It doesn't have
/// to be the EOA or contract Account that initiates the TX.
/// @param _assetId the NFT asset being consumed
/// @param _data extra data passed in for consume for extra message
/// or future extension.
function consume(
address _consumer,
uint256 _assetId,
uint256 _amount,
bytes calldata _data
) external returns (bool _success);
/// @notice The interface to check whether an asset is consumable.
/// @param _consumer the address of consumer of this token. It doesn't have
/// to be the EOA or contract Account that initiates the TX.
/// @param _assetId the NFT asset being consumed.
/// @param _amount the amount of the asset being consumed.
function isConsumableBy(
address _consumer,
uint256 _assetId,
uint256 _amount
) external view returns (bool _consumable);
/// @notice The event emitted when there is a successful consumption.
/// @param consumer the address of consumer of this token. It doesn't have
/// to be the EOA or contract Account that initiates the TX.
/// @param assetId the NFT asset being consumed
/// @param amount the amount of the asset being consumed.
/// @param data extra data passed in for consume for extra message
/// or future extension.
event OnConsumption(
address indexed consumer,
uint256 indexed assetId,
uint256 amount,
bytes data
);
}
```
2. If the compliant contract is an [EIP-721](./eip-721.md) or [EIP-1155](./eip-1155.md) token, in addition to `OnConsumption`, it **MUST** also emit the `Transfer` / `TransferSingle` event (as applicable) as if a token has been transferred from the current holder to the zero address if the call to `consume` method succeeds.
3. `supportsInterface(0xdd691946)` **MUST** return `true` for any compliant contract, as per [EIP-165](./eip-165.md).
## Rationale
1. The function `consume` performs the consume action. This EIP does not assume:
- who has the power to perform consumption
- under what condition consumption can occur
It does, however, assume the asset can be identified in a `uint256` asset id as in the parameter. A design convention and compatibility consideration is put in place to follow the EIP-721 pattern.
2. The event notifies subscribers whoever are interested to learn an asset is being consumed.
3. To keep it simple, this standard *intentionally* contains no functions or events related to the creation of a consumable asset. This is because the creation of a consumable asset will need to make assumptions about the nature of an actual use-case. If there are common use-cases for creation, another follow up standard can be created.
4. Metadata associated to the consumables is not included the standard. If necessary, related metadata can be created with a separate metadata extension interface like `ERC721Metadata` from [EIP-721](./eip-721.md)
5. We choose to include an `address consumer` for `consume` function and `isConsumableBy` so that an NFT MAY be consumed for someone other than the transaction initiator.
6. We choose to include an extra `_data` field for future extension, such as
adding crypto endorsements.
7. We explicitly stay opinion-less about whether EIP-721 or EIP-1155 shall be required because
while we design this EIP with EIP-721 and EIP-1155 in mind mostly, we don't want to rule out
the potential future case someone use a different token standard or use it in different use cases.
8. The boolean view function of `isConsumableBy` can be used to check whether an asset is
consumable by the `_consumer`.
## Backwards Compatibility
This interface is designed to be compatible with EIP-721 and NFT of EIP-1155. It can be tweaked to used for [EIP-20](./eip-20.md), [EIP-777](./eip-777.md) and Fungible Token of EIP-1155.
## Test Cases
```ts
describe("Consumption", function () {
it("Should consume when minted", async function () {
const fakeTokenId = "0x1234";
const { contract, addr1 } = await loadFixture(deployFixture);
await contract.safeMint(addr1.address, fakeTokenId);
expect(await contract.balanceOf(addr1.address)).to.equal(1);
expect(await contract.ownerOf(fakeTokenId)).to.equal(addr1.address);
expect(await contract.isConsumableBy(addr1.address, fakeTokenId, 1)).to.be.true;
const tx = await contract.consume(addr1.address, fakeTokenId, 1, []);
const receipt = await tx.wait();
const events = receipt.events.filter((x: any) => { return x.event == "OnConsumption" });
expect(events.length).to.equal(1);
expect(events[0].args.consumer).to.equal(addr1.address);
expect(events[0].args.assetId).to.equal(fakeTokenId);
expect(events[0].args.amount).to.equal(1);
expect(await contract.balanceOf(addr1.address)).to.equal(0);
await expect(contract.ownerOf(fakeTokenId))
.to.be.rejectedWith('ERC721: invalid token ID');
await expect(contract.isConsumableBy(addr1.address, fakeTokenId, 1))
.to.be.rejectedWith('ERC721: invalid token ID');
});
});
describe("EIP-165 Identifier", function () {
it("Should match", async function () {
const { contract } = await loadFixture(deployFixture);
expect(await contract.get165()).to.equal("0xdd691946");
expect(await contract.supportsInterface("0xdd691946")).to.be.true;
});
});
```
## Reference Implementation
A deployment of version 0x1002 has been deployed onto `goerli` testnet at address `0x3682bcD67b8A5c0257Ab163a226fBe07BF46379B`.
Find the reference contract verified source code on Etherscan's
`goerli` site for the address above.
## Security Considerations
Compliant contracts should pay attention to the balance change when a token is consumed.
When the contract is being paused, or the user is being restricted from transferring a token,
the consumeability should be consistent with the transferral restriction.
Compliant contracts should also carefully define access control, particularlly whether any EOA or contract account may or may not initiate a `consume` method in their own use case.
Security audits and tests should be used to verify that the access control to the `consume`
function behaves as expected.
## Copyright
Copyright and related rights waived via [CC0](../LICENSE.md).