DCIPs/EIPS/eip-2135.md

7.7 KiB

eip title description author discussions-to status last-call-deadline type category created requires
2135 Consumable Interface (Tickets, etc) An interface extending EIP-721 and EIP-1155 for consumability, supporting use case such as an event ticket. Zainan Victor Zhou (@xinbenlv) https://ethereum-magicians.org/t/eip-2135-erc-consumable-interface/3439 Last Call 2023-02-01 Standards Track ERC 2019-06-23 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:
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
    );
}
  1. If the compliant contract is an EIP-721 or EIP-1155 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.

  2. supportsInterface(0xdd691946) MUST return true for any compliant contract, as per EIP-165.

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.

  1. The event notifies subscribers whoever are interested to learn an asset is being consumed.

  2. 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.

  3. 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

  4. 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.

  5. We choose to include an extra _data field for future extension, such as adding crypto endorsements.

  6. 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.

  7. 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-777 and Fungible Token of EIP-1155.

Test Cases


  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 and related rights waived via CC0.