ERC-7578 - Physical Asset Redemption

Created 2023-08-01
Status Final
Category ERC
Type Standards Track
Authors
Requires

Abstract

This proposal is an extension of ERC-721 and implements additional functionality and information pertaining to the NFT’s underlying physical asset by capturing information that enables the holder of physical asset backed NFTs to verify authenticity and facilitate redemption of the underlying physical assets. This proposal is primarily aimed at providing transparency by disclosing details of involved parties and provides opportunity to define and make readily available relevant legal relationship between NFT holder and the owner/holder of the respective underlying physical asset. This proposal makes the token issuer accountable to embed accurate information on a set of standardized information about the underlying physical asset and the involved key parties.

Motivation

The first wave of NFT use cases encompass predominately the representation of ownership of digital assets. In view of the anticipated trend to tokenize any real-world asset, it is to be expected that the use cases of NFTs will rapidly grow and expand around physical assets. The absence of an embedded standardized set of information pertaining to the underlying physical asset together with lack of transparency of involved key parties, creates an unnecessary hurdle for NFT holders and potential users which might, as a result, hinder mass adoption of NFTs that are used as ownership representation of a specific physical asset.

Addressing the lack of readily available information and paving the way for mass adoption for a tokenized economy, this proposal requires that each minted token includes a defined number of predefined variables enabling verification of authenticity and facilitating redemption of the underlying physical asset.

Specification

The keywords "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.

When a token is minted, its properties SHOULD be initialized beforehand, with each field being defined as follows:

The terms parameter SHOULD be an HTTP link to a document that is stored on IPFS. This is to ensure that the document is immutable and can be verified by the NFT holder.

When a token with valid properties is to be burned, the properties MUST be removed.

Contract Interface

pragma solidity ^0.8.21;

/**
 * @notice Struct encapsulating fields required to by the ERC-7578 standard to represent the physical asset
 * @param tokenIssuer The network or entity minting the token
 * @param assetHolder The legal owner of the physical asset
 * @param storageLocation The physical location where the asset is stored
 * @param terms Link to IPFS contract, agreement or terms
 * @param jurisdiction The legal justification set out in the terms
 * @param declaredValue The declared value at time of token minting
 */
struct Properties {
    string tokenIssuer;
    string assetHolder;
    string storageLocation;
    string terms;
    string jurisdiction;
    Amount declaredValue;
}

/**
 * @notice Struct encapsulating fields describing the declared value of the physical asset
 * @param currency The currency of the amount
 * @param value The value of the amount
 */
struct Amount {
    string currency;
    uint256 value;
}

/**
 * @notice Required interface of an ERC-7578 compliant contract
 */
interface IERC7578 {
    /**
     * @notice Emitted when the properties of the `tokenId` token are set
     * @param tokenId The ID of the token
     * @param properties The properties of the token
     */
    event PropertiesSet(uint256 indexed tokenId, Properties properties);

    /**
     * @notice Emitted when the properties of the `tokenId` token are removed
     * @param tokenId The ID of the token
     */
    event PropertiesRemoved(uint256 indexed tokenId);

    /**
     * @notice Retrieves all properties of the `tokenId` token
     * @dev Does NOT revert if token doesn't exist
     * @param tokenId The token ID of the minted token
     */
    function getPropertiesOf(uint256 tokenId) external view returns (Properties memory properties);
}

When properties are set, the PropertiesSet(uint256 indexed tokenId, Properties properties) event is emitted.

When properties are removed, the PropertiesRemoved(uint256 indexed tokenId) event is emitted.

The getPropertiesOf(uint256 tokenId) function MUST return the unique properties of a token. If the ERC-721 token is burned or has no properties set, it SHOULD return an empty Properties struct.

Rationale

By not initializing a token's properties before minting, one risks that the asset's provenance represented by the token cannot be established.

Contract level validation is not used on the properties as we believe the accuracy of the data declared is the responsibility of the token issuer. This builds trust on the token issuer and the token itself.

Backwards Compatibility

This standard is compatible with ERC-721.

Reference Implementation

An example of an ERC-721 that includes this proposal using the OpenZeppelin ERC-721 v5 library:

pragma solidity ^0.8.21;

import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import { IERC7578, Properties, Amount } from "./interfaces/IERC7578.sol";

/**
 * @title ERC7578
 * @author DESAT
 * @notice Implementation of the ERC-7578: Physical Asset Redemption standard
 **/
contract ERC7578 is ERC721, IERC7578 {
    /**
     * @notice Thrown when the properties of a token are not initialized
     */
    error PropertiesUninitialized();

    /**
     * @notice Retrieves the properties of the `tokenId` token
     */
    mapping(uint256 tokenId => Properties) private _properties;

    /**
     * @notice Initializes the name and symbol of the ERC-721 collection
     */
    constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {}

    /**
     * @inheritdoc IERC7578
     */
    function getPropertiesOf(uint256 tokenId) public view override returns (Properties memory properties) {
        properties = _properties[tokenId];
    }

    /**
     * @notice Initializes the ERC-7578 properties of the `tokenId` token
     *
     * WARNING: This method should only be called within a function that has appropriate access control
     * It is recommended to restrict access to trusted Externally Owned Accounts (EOAs),
     * authorized contracts, or implement a Role-Based Access Control (RBAC) mechanism
     * Failure to properly secure this method could lead to unauthorized modification of token properties
     *
     * Emits a {PropertiesSet} event
     */
    function _setPropertiesOf(uint256 tokenId, Properties calldata properties) internal {
        _properties[tokenId] = Properties({
            tokenIssuer: properties.tokenIssuer,
            assetHolder: properties.assetHolder,
            storageLocation: properties.storageLocation,
            terms: properties.terms,
            jurisdiction: properties.jurisdiction,
            declaredValue: Amount({
                currency: properties.declaredValue.currency,
                value: properties.declaredValue.value
            })
        });

        emit PropertiesSet(tokenId, _properties[tokenId]);
    }

    /**
     * @notice Removes the properties of the `tokenId` token
     * @param tokenId The unique identifier of the token whose properties are to be removed
     *
     * Emits a {PropertiesRemoved} event
     */
    function _removePropertiesOf(uint256 tokenId) internal {
        delete _properties[tokenId];
        emit PropertiesRemoved(tokenId);
    }

    /**
     * @notice Override of the {_update} function to remove the properties of the `tokenId` token or
     * to check if they are set before minting
     * @param tokenId The unique identifier of the token being minted or burned
     */
    function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) {
        address from = _ownerOf(tokenId);
        if (to == address(0)) {
            _removePropertiesOf(tokenId);
        } else if (from == address(0)) {
            if (bytes(_properties[tokenId].tokenIssuer).length == 0) revert PropertiesUninitialized();
        }

        return super._update(to, tokenId, auth);
    }
}

Security Considerations

To ensure authenticity, token properties must be set only via a method that is restricted to a trusted Externally Owned Account (EOA) or contract. This trusted entity must verify that the properties accurately reflect the real physical attributes of the represented asset. Additionally, proper access control mechanisms should be implemented to prevent unauthorized modifications of token properties after they are set.

Copyright

Copyright and related rights waived via CC0.