forked from DecentralizedClimateFoundation/DCIPs
169 lines
7.7 KiB
Markdown
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).
|