ndujaLabs / MCIPs

Multi-chain Improvement Proposals
0 stars 1 forks source link

mcip-1 Cross-player On-chain NFT Attributes #3

Open sullof opened 3 years ago

sullof commented 3 years ago

A proposal for a standard to handle cross-player on-chain NFT attributes

Abstract

The following standard allows for the implementation of a standard protocol for gaming NFTs and other NFTs that must be managed on pure decentralized application, i.e., at a smart contract level.

Motivation

The standard ERC721 works very well for collectibles, despite being introduced by a game. Games on chain may need attributes and other information on chain to be able to play with it. For example, the original EverDragons factory was implementing the following

    struct Dragon {
        bytes32 name;
        uint24 attributes;
        uint32 experience;
        uint32 prestige;
        uint16 state;
        mapping(bytes32 => uint32) items;
    }

to manage mutable and immutable properties of the single dragon. The limit of this model is that the properties were predefined and a different game could not reuse the same approach.

We need a generic solution that establish basic rules and is flexible enough to manage most scenarios and make NFT really movable among games.

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.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @title IERC721Playable Cross-player On-chain Attributes
///  Version: 0.1.0
interface IERC721Playable /* is IERC165 */ {
  /// @dev Emitted when the attributes for a token id and a player is set.
  event AttributesSet(uint256 indexed _tokenId, address indexed _player, Attributes _attributes);

  /// @dev This struct saves info about the token.
  struct Attributes {
    // A player can change the way it manages the data, updating the version
    // If 8 bits are not enough, one attribute can be used to form a uint16
    // Alternatively, we could use the first attribute as a version, but it
    // would use the same space, making the concept less clear
    uint8 version;
    // Attributes can be immutable (for example because taken from the attributes.json)
    // or mutable, because they depends only on the player itself.
    // If a field requires more than 256 possible value, two bytes can be used for it.
    uint8[31] attributes;
  }

  /// @dev It returns the on-chain attributes of a specific token
  /// @param _tokenId The id of the token for whom to query the on-chain attributes
  /// @param _player The address of the player's contract
  /// @return The attributes of the token
  function attributesOf(uint256 _tokenId, address _player) external view returns (Attributes memory);

  /// @notice Initialize the attributes of a token
  /// @dev It must be called by the nft's owner to approve the player.
  /// To avoid that nft owners give themselves arbitrary values, they can
  /// only set an empty uint8[], with version = 1
  /// Later, the player will be able to update array and version, with the right values
  /// @param _tokenId The id of the token for whom to change the attributes
  /// @param _player The version of the attributes
  /// @return true if the initialization is successful
  function initAttributes(uint256 _tokenId, address _player) external returns (bool);

  /// @notice Sets the attributes of a token after first set up
  /// @dev It modifies attributes by id for a specific player. It must
  /// be called by the player's contract, after an NFT has been initialized.
  /// @param _tokenId The id of the token for whom to change the attributes
  /// @param _newVersion It should be changed only when introducing a new set
  /// of attributes. If set to zero, the update should be ignored.
  /// @param _indexes The indexes of the attributes to be changed
  /// @param _attributes The values of the attributes to be changed
  /// @return true if the change is successful
  function updateAttributes(
    uint256 _tokenId,
    uint8 _newVersion,
    uint256[] memory _indexes,
    uint8[] memory _attributes
  ) external returns (bool);
}

Rationale

The reason why ERC721 metadata are off-chain makes perfect sense in general, in particular for immutable collectibles, but it does not allow pure on-chain games to interact with the NFT because they cannot access the metadata. This proposal adds a relatively inexpensive solution to it.

Imagine that there are two Bored Ape with the same rarity, but the two apes are used in a game. If one of the two gets very good attributes, whoever will by the ape will also buy levels in the game where the ape played. So, the value on the market of the good ape will be higher that the value of the ape who is a bad player.

Of course, to work as expected, OpenSea and other marketplace should have a list of games and be able to retrieve the attributes on game of any NFT.

NFT Owner inits and Player updates

If NFT owners are allowed to update the attributes of their tokens, they could set whichever value the want, so the only the Player must be able to update the attributes. It is important that the owner cannot revoke the approval because if not, owners could play a game and, when they realized they are decreasing their score, before that the game updates the attributes, they revoke the approval.

On the other hand, if the Player initializes the token, spam will florish. For example, a bad game could initialize tokens pretending that their owner played the game. Think of a porn game that sets your NFT and pretends that you have high scores. That can be a problem, right? The solution is that the Owner only can initialize the attribute for a specific game.

Recommendation

The on-chain Metadata must be used only for important informations, like, for example, level ups. If used for frequent changing values, too many changes will congestionate the blockchain.

Backwards Compatibility

This is totally compatible with the ERC721 standard, except for the interfaceId.

Implementations

https://github.com/ndujaLabs/erc721playable

tckpwr commented 3 years ago

Great concept, thanks for the work! I am a big fan of more on-chain data for NFTs.

Here are two suggestions:

sullof commented 3 years ago

@tckpwr Thanks for the comment.

  1. I like the idea of renaming version to type. The goal there was to allow different types of tokens on the same contract. For example, in a contract, there could be accessories and weapons. In this case, the set of attributes would be different. In general, though, it would be more common to have one type and consistent attributes.

  2. I thought the burnable status was more like a temporary feature. Since the approved operator can burn a token if it is set to burnable (if the contract has implemented the function), it makes sense that a game burns a weapon NFT to mint a more powerful weapon. Still, when I leave the game, I would like to reset the burnable to 0 so that OpenSea cannot burn it by mistake. I agree that having a separate field gives more flexibility, allowing, for example, to specify who can burn the token.

I will make some change asap.

sullof commented 3 years ago

My primary doubt is if we should allow generic fields or if it would be better to refer to standard agreements. One way to solve it would be to deploy read-only contracts that provide information about a specific set of attributes and their rules.

Take the role-playing attributes as defined in https://en.wikipedia.org/wiki/Attribute_(role-playing_games) It would be nice to have a read-only contract for any of these sets so that when specifying the type of metadata in the ERC721, you refer to the smart contract you are using for the definition. It would allow a compatible game to use a token created by someone else right away.

sullof commented 3 years ago

I updated the issue and the proposal at https://github.com/ndujaLabs/MCIPs/blob/main/MCIPs/mcip-1.md The change comes after conversations with people at Superpower Labs, Syn City and Amber Studio.

The change makes the NFT totally agnostic about the game where it is playing.

sullof commented 3 years ago

I was wondering if it is better to not extend ERC-165, since marketplace can have difficulty to manage the token.