Alert Source Discuss
⚠️ Review Standards Track: SRC

SRC-5269: SRC Detection and Discovery

An interface to identify if major behavior or optional behavior specified in an SRC is supported for a given caller.

Authors Zainan Victor Zhou (@xinbenlv)
Created 2022-07-15
Requires SIP-5750

Abstract

An interface for better identification and detection of SRCs by number. It designates a field called majorSRCIdentifier which is normally known or referred to as “SRC number”. For example, SRC-721 has a majorSRCIdentifier = 721. This SRC has a majorSRCIdentifier = 5269.

Calling it a majorSRCIdentifier instead of ERCNumber makes it future-proof: anticipating there is a possibility where future SRCs are not numbered or if we want to incorporate other types of standards.

It also proposes a new concept of minorSRCIdentifier which is left for authors of individual SRC to define. For example, SRC-721’s author may define SRC721Metadata interface as minorSRCIdentifier= keccak256("SRC721Metadata").

It also proposes an event to allow smart contracts to optionally declare the SRCs they support.

Motivation

This SRC is created as a competing standard for SRC-165.

Here are the major differences between this SRC and SRC-165.

  1. SRC-165 uses the hash of a method’s signature which declares the existence of one method or multiple methods, therefore it requires at least one method to exist in the first place. In some cases, some SRC interfaces do not have a method, such as some SRCs related to data format and signature schemes or the “Soul-Bound-ness” aka SBT which could just revert a transfer call without needing any specific method.
  2. SRC-165 doesn’t provide query ability based on the caller. The compliant contract of this SRC will respond to whether it supports certain SRC based on a given caller.

Here is the motivation for this SRC given SRC-165 already exists:

  1. Using SRC numbers improves human readability as well as make it easier to work with named contracts such as ENS.

  2. Instead of using an SRC-165 identifier, we have seen an increasing interest to use SRC numbers as the way to identify or specify an SRC. For example

  • SRC-5267 specifies extensions to be a list of SRC numbers.
  • SRC-600, and SRC-601 specify an SRC number in the m / purpose' / subpurpose' / SRC' / wallet' path.
  • SRC-5568 specifies The instruction_id of an instruction defined by an SRC MUST be its SRC number unless there are exceptional circumstances (be reasonable)
  • SRC-6120 specifies struct Token { uint sip; ..., } where uint sip is an SRC number to identify SRCs.
  • SRC-867 (Stagnant) proposes creating an erpId, described as a string identifier for that ERP, likely based on the associated SRC number.
  1. Having an SRC/SRC number detection interface reduces the need for a lookup table in smart contract to convert a function method or whole interface in any SRC in the bytes4 SRC-165 identifier into its respective SRC number and massively simplifies the way to specify SRC for behavior expansion.

  2. We also recognize a smart contract might have different behavior given different caller accounts. One of the most notable use cases is that when using Transparent Upgradable Pattern, a proxy contract gives an Admin account and Non-Admin account different treatment when they call.

Specification

In the following description, we use SRC and SRC interchangeably. This was because while most of the time the description applies to an SRC category of the Standards Track of SRC, the SRC number space is a subspace of SRC number space and we might sometimes encounter SRCs that aren’t recognized as SRCs but has behavior that’s worthy of a query.

  1. Any compliant smart contract MUST implement the following interface
// DRAFTv1
pragma solidity ^0.8.9;

interface ISRC5269 {
  event OnSupportERC(
      address indexed caller, // when emitted with `address(0x0)` means all callers.
      uint256 indexed majorSRCIdentifier,
      bytes32 indexed minorSRCIdentifier, // 0 means the entire SRC
      bytes32 ercStatus,
      bytes extraData
  );

  /// @dev The core method of SRC Interface Detection
  /// @param caller, a `address` value of the address of a caller being queried whether the given SRC is supported.
  /// @param majorSRCIdentifier, a `uint256` value and SHOULD BE the SRC number being queried. Unless superseded by future SRC, such SRC number SHOULD BE less or equal to (0, 2^32-1]. For a function call to `supportERC`, any value outside of this range is deemed unspecified and open to implementation's choice or for future SRCs to specify.
  /// @param minorSRCIdentifier, a `bytes32` value reserved for authors of individual SRC to specify. For example the author of [SRC-721](/SRCS/sip-721) MAY specify `keccak256("SRC721Metadata")` or `keccak256("SRC721Metadata.tokenURI")` as `minorSRCIdentifier` to be queried for support. Author could also use this minorSRCIdentifier to specify different versions, such as SRC-712 has its V1-V4 with different behavior.
  /// @param extraData, a `bytes` for [SRC-5750](/SRCS/sip-5750) for future extensions.
  /// @return ercStatus, a `bytes32` indicating the status of SRC the contract supports.
  ///                    - For FINAL SRCs, it MUST return `keccak256("FINAL")`.
  ///                    - For non-FINAL SRCs, it SHOULD return `keccak256("DRAFT")`.
  ///                      During SRC procedure, SRC authors are allowed to specify their own
  ///                      ercStatus other than `FINAL` or `DRAFT` at their discretion such as `keccak256("DRAFTv1")`
  ///                      or `keccak256("DRAFT-option1")`and such value of ercStatus MUST be documented in the SRC body
  function supportERC(
    address caller,
    uint256 majorSRCIdentifier,
    bytes32 minorSRCIdentifier,
    bytes calldata extraData)
  external view returns (bytes32 ercStatus);
}

In the following description, SRC_5269_STATUS is set to be keccak256("DRAFTv1").

In addition to the behavior specified in the comments of ISRC5269:

  1. Any minorSRCIdentifier=0 is reserved to be referring to the main behavior of the SRC being queried.
  2. The Author of compliant SRC is RECOMMENDED to declare a list of minorSRCIdentifier for their optional interfaces, behaviors and value range for future extension.
  3. When this SRC is FINAL, any compliant contract MUST return an SRC_5269_STATUS for the call of supportERC((any caller), 5269, 0, [])

Note: at the current snapshot, the supportERC((any caller), 5269, 0, []) MUST return SRC_5269_STATUS.

  1. Any complying contract SHOULD emit an OnSupportERC(address(0), 5269, 0, SRC_5269_STATUS, []) event upon construction or upgrade.
  2. Any complying contract MAY declare for easy discovery any SRC main behavior or sub-behaviors by emitting an event of OnSupportERC with relevant values and when the compliant contract changes whether the support an SRC or certain behavior for a certain caller or all callers.
  3. For any SRC-XXX that is NOT in Final status, when querying the supportERC((any caller), xxx, (any minor identifier), []), it MUST NOT return keccak256("FINAL"). It is RECOMMENDED to return 0 in this case but other values of ercStatus is allowed. Caller MUST treat any returned value other than keccak256("FINAL") as non-final, and MUST treat 0 as strictly “not supported”.
  4. The function supportERC MUST be mutability view, i.e. it MUST NOT mutate any global state of SVM.

Rationale

  1. When data type uint256 majorSRCIdentifier, there are other alternative options such as:
  • (1) using a hashed version of the SRC number,
  • (2) use a raw number, or
  • (3) use an SRC-165 identifier.

The pros for (1) are that it automatically supports any evolvement of future SRC numbering/naming conventions. But the cons are it’s not backward readable: seeing a hash(SRC-number) one usually can’t easily guess what their SRC number is.

We choose the (2) in the rationale laid out in motivation.

  1. We have a bytes32 minorSRCIdentifier in our design decision. Alternatively, it could be (1) a number, forcing all SRC authors to define its numbering for sub-behaviors so we go with a bytes32 and ask the SRC authors to use a hash for a string name for their sub-behaviors which they are already doing by coming up with interface name or method name in their specification.

  2. Alternatively, it’s possible we add extra data as a return value or an array of all SRC being supported but we are unsure how much value this complexity brings and whether the extra overhead is justified.

  3. Compared to SRC-165, we also add an additional input of address caller, given the increasing popularity of proxy patterns such as those enabled by SRC-1967. One may ask: why not simply use msg.sender? This is because we want to allow query them without transaction or a proxy contract to query whether interface SRC-number will be available to that particular sender.

  4. We reserve the input majorSRCIdentifier greater than or equals 2^32 in case we need to support other collections of standards which is not an SRC/SRC.

Test Cases


describe("SRC5269", function () {
  async function deployFixture() {
    // ...
  }

  describe("Deployment", function () {
    // ...
    it("Should emit proper OnSupportERC events", async function () {
      let { txDeployErc721 } = await loadFixture(deployFixture);
      let events = txDeployErc721.events?.filter(event => event.event === 'OnSupportERC');
      expect(events).to.have.lengthOf(4);

      let ev5269 = events!.filter(
        (event) => event.args!.majorSRCIdentifier.eq(5269));
      expect(ev5269).to.have.lengthOf(1);
      expect(ev5269[0].args!.caller).to.equal(BigNumber.from(0));
      expect(ev5269[0].args!.minorSRCIdentifier).to.equal(BigNumber.from(0));
      expect(ev5269[0].args!.ercStatus).to.equal(ethers.utils.id("DRAFTv1"));

      let ev721 = events!.filter(
        (event) => event.args!.majorSRCIdentifier.eq(721));
      expect(ev721).to.have.lengthOf(3);
      expect(ev721[0].args!.caller).to.equal(BigNumber.from(0));
      expect(ev721[0].args!.minorSRCIdentifier).to.equal(BigNumber.from(0));
      expect(ev721[0].args!.ercStatus).to.equal(ethers.utils.id("FINAL"));

      expect(ev721[1].args!.caller).to.equal(BigNumber.from(0));
      expect(ev721[1].args!.minorSRCIdentifier).to.equal(ethers.utils.id("SRC721Metadata"));
      expect(ev721[1].args!.ercStatus).to.equal(ethers.utils.id("FINAL"));

      // ...
    });

    it("Should return proper ercStatus value when called supportERC() for declared supported SRC/features", async function () {
      let { src721ForTesting, owner } = await loadFixture(deployFixture);
      expect(await src721ForTesting.supportERC(owner.address, 5269, ethers.utils.hexZeroPad("0x00", 32), [])).to.equal(ethers.utils.id("DRAFTv1"));
      expect(await src721ForTesting.supportERC(owner.address, 721, ethers.utils.hexZeroPad("0x00", 32), [])).to.equal(ethers.utils.id("FINAL"));
      expect(await src721ForTesting.supportERC(owner.address, 721, ethers.utils.id("SRC721Metadata"), [])).to.equal(ethers.utils.id("FINAL"));
      // ...

      expect(await src721ForTesting.supportERC(owner.address, 721, ethers.utils.id("WRONG FEATURE"), [])).to.equal(BigNumber.from(0));
      expect(await src721ForTesting.supportERC(owner.address, 9999, ethers.utils.hexZeroPad("0x00", 32), [])).to.equal(BigNumber.from(0));
    });

    it("Should return zero as ercStatus value when called supportERC() for non declared SRC/features", async function () {
      let { src721ForTesting, owner } = await loadFixture(deployFixture);
      expect(await src721ForTesting.supportERC(owner.address, 721, ethers.utils.id("WRONG FEATURE"), [])).to.equal(BigNumber.from(0));
      expect(await src721ForTesting.supportERC(owner.address, 9999, ethers.utils.hexZeroPad("0x00", 32), [])).to.equal(BigNumber.from(0));
    });
  });
});

See TestSRC5269.ts.

Reference Implementation

Here is a reference implementation for this SRC:

contract SRC5269 is ISRC5269 {
    bytes32 constant public SRC_STATUS = keccak256("DRAFTv1");
    constructor () {
        emit OnSupportERC(address(0x0), 5269, bytes32(0), SRC_STATUS, "");
    }

    function _supportERC(
        address /*caller*/,
        uint256 majorSRCIdentifier,
        bytes32 minorSRCIdentifier,
        bytes calldata /*extraData*/)
    internal virtual view returns (bytes32 ercStatus) {
        if (majorSRCIdentifier == 5269) {
            if (minorSRCIdentifier == bytes32(0)) {
                return SRC_STATUS;
            }
        }
        return bytes32(0);
    }

    function supportERC(
        address caller,
        uint256 majorSRCIdentifier,
        bytes32 minorSRCIdentifier,
        bytes calldata extraData)
    external virtual view returns (bytes32 ercStatus) {
        return _supportERC(caller, majorSRCIdentifier, minorSRCIdentifier, extraData);
    }
}

See SRC5269.sol.

Here is an example where a contract of SRC-721 also implements this SRC to make it easier to detect and discover:

import "@openzeppelin/contracts/token/SRC721/SRC721.sol";
import "../SRC5269.sol";
contract SRC721ForTesting is SRC721, SRC5269 {

    bytes32 constant public SRC_FINAL = keccak256("FINAL");
    constructor() SRC721("SRC721ForTesting", "E721FT") SRC5269() {
        _mint(msg.sender, 0);
        emit OnSupportERC(address(0x0), 721, bytes32(0), SRC_FINAL, "");
        emit OnSupportERC(address(0x0), 721, keccak256("SRC721Metadata"), SRC_FINAL, "");
        emit OnSupportERC(address(0x0), 721, keccak256("SRC721Enumerable"), SRC_FINAL, "");
    }

  function supportERC(
    address caller,
    uint256 majorSRCIdentifier,
    bytes32 minorSRCIdentifier,
    bytes calldata extraData)
  external
  override
  view
  returns (bytes32 ercStatus) {
    if (majorSRCIdentifier == 721) {
      if (minorSRCIdentifier == 0) {
        return keccak256("FINAL");
      } else if (minorSRCIdentifier == keccak256("SRC721Metadata")) {
        return keccak256("FINAL");
      } else if (minorSRCIdentifier == keccak256("SRC721Enumerable")) {
        return keccak256("FINAL");
      }
    }
    return super._supportERC(caller, majorSRCIdentifier, minorSRCIdentifier, extraData);
  }
}

See SRC721ForTesting.sol.

Security Considerations

Similar to SRC-165 callers of the interface MUST assume the smart contract declaring they support such SRC interfaces doesn’t necessarily correctly support them.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Zainan Victor Zhou (@xinbenlv), "SRC-5269: SRC Detection and Discovery [REVIEW]," Sila Improvement Proposals, no. 5269, July 2022. Available: https://sips.sila.org/SIPS/sip-5269.