Alert Source Discuss
🚧 Stagnant Standards Track: ERC

ERC-1616: 属性注册表标准

Authors 0age (@0age), Santiago Palladino (@spalladino), Leo Arias (@elopio), Alejo Salles (@fiiiu), Stephane Gosselin (@thegostep)
Created 2018-11-23
Discussion Link https://github.com/ethereum/EIPs/issues/1616
Requires EIP-165

简单总结

EIP-1616 提供了一个基本接口,用于查询分配给以太坊帐户的属性元数据的注册表。

摘要

本 EIP 包含以下核心思想:

  1. 可以将信任提升到注册表管理者的级别,而不是直接依赖声明发布者的声誉来评估给定声明的真实性。我们称之为“属性注册表”的这个注册表可以降低实现的复杂性,因为需要验证属性的一方现在可以与受信任的声明聚合器合作,而不是依赖于各个声明提供者。
  2. 声明被抽象为标准的“属性”,代表分配给帐户的元数据,声明与发布方分离。属性在每个帐户上注册为扁平的 uint256 -> uint256 键值对,重要的属性是每个属性类型每个地址都有一个规范值。此属性允许属性注册表的可组合性和高级属性形成。
  3. 有一种通用方法可以确定注册表提供的属性键或 ID 的集合。该标准没有规定管理属性及其值的方式,或可能与属性关联的其他元数据的要求或建议。可能会在单独的 EIP 中提出一组标准的属性名称和元数据模式。

属性注册表的潜在高级用途包括:

  • 编码复杂的布尔表达式,将多个属性组合成单个 uint256 键,然后由注册表逻辑解析和评估。
  • 使用与属性关联的值查询其他链上或链下元数据。
  • 通过调用单独的属性注册表或其他合约来解析属性值,从而在不更改注册表接口的情况下委派权限。

动机

本 EIP 的动机是合同和外部帐户需要能够从单个受信任的来源验证有关给定地址的信息,而无需关心如何获得该信息的具体细节,并以尽可能简单的方式进行操作。它的动机还在于促进属性注册表之间的广泛交叉兼容性和可组合性,这种特性因接口的简单性以及所提出的标准提供的唯一性保证而得到放大。

用于将元数据分配给帐户的现有 EIP 包括 EIP-735 和 EIP-780,它们都允许在同一地址上针对任何给定的声明主题发布多个声明。这迫使所述元数据的验证者评估每个声明的真实性,同时考虑到每个声明发布者的相对声誉。它还规定了一种添加和删除声明的方法,这可能并不适用于所有用例。

本 EIP 提出了一个轻量级抽象层,用于一个标准的帐户元数据注册表接口。这个抽象层可以位于 EIP-735 和 EIP-780 等声明注册表之上,或者其他作为属性注册表管理者选择的受信任数据源之上。

规范

属性注册表接口包含四个函数,概述如下:

/**
 * @title EIP-1616 属性注册表标准接口。EIP-165 ID: 0x5f46473f
 */
interface AttributeRegistryInterface {
  function hasAttribute(address account, uint256 attributeTypeID) external view returns (bool);
  function getAttributeValue(address account, uint256 attributeTypeID) external view returns (uint256);
  function countAttributeTypes() external view returns (uint256);
  function getAttributeTypeID(uint256 index) external view returns (uint256);
}

符合属性注册表 EIP 的合约必须实现上述接口。

作为附加要求,必须包含 ERC-165 接口:

/**
 * @title EIP-165 接口。EIP-165 ID: 0x01ffc9a7
 */
interface EIP-165 {
  /**
   * @notice EIP-165 支持. 属性注册表接口 ID 为 0x5f46473f.
   * @param _interfaceID 接口标识符,如 EIP-165 中指定
   * @return 对于 0x01ffc9a7 & 0x5f46473f 返回 True,对于不支持的接口返回 false。
   */
  function supportsInterface(bytes4 _interfaceID) external view returns (bool);
}

该实现必须遵循下述规范。

查看函数

必须实现下面详细说明的查看函数。

hasAttribute 函数

function hasAttribute(address account, uint256 attributeTypeID) external view returns (bool)

检查是否已将属性分配给注册表上的给定帐户,并且当前是否有效。

注意: 此函数必须返回 true 或 false - 即调用此函数不得导致调用者 revert。希望在此函数执行期间调用另一个合约的实现必须捕获任何 revert 并返回 false

注意: 当使用相同的 accountattributeTypeID 参数执行两个直接连续的函数调用时,无论调用者的地址、交易来源或其他带外信息的差异如何,此函数都必须返回两个相等的值。

getAttributeValue 函数

function getAttributeValue(address account, uint256 attributeTypeID) external view returns (uint256)

检索注册表上给定帐户的属性的 uint256 值,假设该属性当前有效。

注意: 如果使用相同的 accountattributeTypeID 参数直接先前或后续调用 hasAttribute 函数将返回 false,则此函数必须 revert。

注意: 当使用相同的 accountattributeTypeID 参数执行两个直接连续的函数调用时,无论调用者的地址、交易来源或其他带外信息的差异如何,此函数都必须返回两个相等的值。

countAttributeTypes 函数

function countAttributeTypes() external view returns (uint256)

检索注册表上定义的有效属性类型的总数。与 getAttributeTypeID 一起使用以确定注册表上可用的所有属性类型。

注意: 此函数必须返回一个正整数值 - 即调用此函数不得导致调用者 revert。

注意: 此函数必须返回一个包含属性类型 ID 的所有索引的值,因此使用给定索引处的属性类型 ID 对某些地址调用 hasAttribute 将返回 true

getAttributeTypeID 函数

function getAttributeTypeID(uint256 index) external view returns (uint256)

按索引检索注册表上定义的属性类型的 ID。与 countAttributeTypes 一起使用以确定注册表上可用的所有属性类型。

注意: 如果提供的 index 值落在直接先前或后续调用 countAttributeTypes 函数返回的值范围之外,则此函数必须 revert。如果提供的 index 值落在上述范围内,则不得 revert。

注意: 如果相同的 attributeTypeID 值会导致给定的 hasAttribute 调用在作为参数传递时返回 true,则此函数必须在某个索引上返回一个 attributeTypeID 值。

理由

本标准将元数据分配的适用性扩展到那些 EIP-735、EIP-780 或类似提案未充分表示的用例。也就是说,它强制执行每个地址每个属性 ID 一个属性值的约束,而不是每个发布者每个地址每个 ID 一个值。

除了规定的属性值之外,标准中有意省略了属性属性。虽然许多属性注册表都需要实例和类级别的属性上的其他元数据,但通过强制执行广泛适用的属性基础层,可以更有效地促进高度可变的注册表扩展之间的可靠且灵活的互操作性。

向后兼容性

不存在向后兼容性问题。

测试用例

可以在 此存储库 中找到具有 100% 代码覆盖率的目标测试用例。有关实现应用程序注册表接口的更复杂的合约的测试,请参阅 此处

实现

以下基本实现可以在 此存储库 中找到(有关更复杂的实现合约的示例,请参阅 此处):

pragma solidity ^0.4.25;

/**
 * @title 属性注册表接口。EIP-165 ID: 0x5f46473f
 */
interface AttributeRegistryInterface {
  /**
   * @notice 检查是否已将 ID 为 `attributeTypeID` 的类型的属性分配给
   *  `account` 的帐户,并且当前是否有效。
   * @param account address 要检查是否存在有效属性的帐户。
   * @param attributeTypeID uint256 要检查的属性类型的 ID。
   * @return 如果属性已分配且有效,则为 True,否则为 false。
   * @dev 此函数必须返回 true 或 false - 即调用此
   * 函数不得导致调用者 revert。
   */
  function hasAttribute(
    address account,
    uint256 attributeTypeID
  ) external view returns (bool);

  /**
   * @notice 检索 ID 为 `attributeTypeID` 的类型的属性在
   * `account` 的帐户上的值,假设它有效。
   * @param account address 要检查给定属性值的帐户。
   * @param attributeTypeID uint256 要检查的属性类型的 ID。
   * @return 如果属性有效,则为属性值,否则 revert。
   * @dev 如果直接先前或后续
   * 使用相同的 `account` 和
   * `attributeTypeID` 参数调用 `hasAttribute` 函数将返回 false,则此函数必须 revert。
   */
  function getAttributeValue(
    address account,
    uint256 attributeTypeID
  ) external view returns (uint256);

  /**
   * @notice 计算注册表定义的属性类型的数量。
   * @return 可用属性类型的数量。
   * @dev 此函数必须返回一个正整数值 - 即调用
   * 此函数不得导致调用者 revert。
   */
  function countAttributeTypes() external view returns (uint256);

  /**
   * @notice 获取索引 `index` 处的属性类型的 ID。
   * @param index uint256 有问题的属性类型的索引。
   * @return 属性类型的 ID。
   * @dev 如果提供的 `index` 值落在直接先前或后续
   * 调用 `countAttributeTypes` 函数返回的值范围之外,则此函数必须 revert。如果提供的
   *  `index` 值落在上述范围内,则不得 revert。
   */
  function getAttributeTypeID(uint256 index) external view returns (uint256);
}


/**
 * @title Attribute Registry 实现的一个简单示例。
 */
contract AttributeRegistry is AttributeRegistryInterface {
  // 此特定实现仅定义两种属性类型。
  enum Affiliation { Whitehat, Blackhat }

  // 有关静态数组中保存的属性类型的顶级信息。
  uint256[2] private _attributeTypeIDs;

  // 静态数组中跟踪的当前颁发的属性数量。
  uint256[2] private _issuedAttributeCounters;

  // 嵌套映射中按帐户和属性类型保存的已颁发属性。
  mapping(address => mapping(uint256 => bool)) private _issuedAttributes;

  // 嵌套映射中按帐户和类型保存的颁发属性值。
  mapping(address => mapping(uint256 => uint256)) private _issuedAttributeValues;

  /**
  * @notice 构造函数,定义此特定注册表上可用的两种属性类型。
  */
  constructor() public {
    // 设置 whitehat (8008) 和 blackhat (1337) 的属性类型 ID。
    _attributeTypeIDs = [8008, 1337];
  }

  /**
   * @notice 将“whitehat”属性类型分配给 `msg.sender`。
   * @dev 该函数可能无法被已分配有“blackhat”属性类型的帐户调用。
   * 这个函数是任意的,不是
   * 属性注册表规范的一部分。
   */
  function joinWhitehats() external {
    // 获取属性注册表上 blackhat 属性类型的索引。
    uint256 blackhatIndex = uint256(Affiliation.Blackhat);

    // 获取 blackhat 属性类型的属性类型 ID。
    uint256 blackhatAttributeTypeID = _attributeTypeIDs[blackhatIndex];

    // 如果已设置 blackhat,则不允许设置 whitehat 属性。
    require(
      !_issuedAttributes[msg.sender][blackhatAttributeTypeID],
      "不允许 blackhat!"
    );

    // 获取属性注册表上 whitehat 属性类型的索引。
    uint256 whitehatIndex = uint256(Affiliation.Whitehat);

    // 获取 whitehat 属性类型的属性类型 ID。
    uint256 whitehatAttributeTypeID = _attributeTypeIDs[whitehatIndex];

    // 将该属性标记为已在给定地址上颁发。
    _issuedAttributes[msg.sender][whitehatAttributeTypeID] = true;

    // 计算 whitehat 属性的总数的新数量。
    uint256 incrementCounter = _issuedAttributeCounters[whitehatIndex] + 1;

    // 将属性值设置为新分配的 whitehat 属性的总数。
    _issuedAttributeValues[msg.sender][whitehatAttributeTypeID] = incrementCounter;

    // 更新 whitehat 属性总数的计数器的值。
    _issuedAttributeCounters[whitehatIndex] = incrementCounter;
  }

  /**
   * @notice 将“blackhat”属性类型分配给 `msg.sender`。
   * @dev 该函数可以被任何帐户调用,但分配的“whitehat”
   * 属性将被删除。这个函数是任意的,不是
   * 属性注册表规范的一部分。
   */
  function joinBlackhats() external {
    // 获取属性注册表上 blackhat 属性类型的索引。
    uint256 blackhatIndex = uint256(Affiliation.Blackhat);

    // 获取 blackhat 属性类型的属性类型 ID。
    uint256 blackhatAttributeTypeID = _attributeTypeIDs[blackhatIndex];

    // 将该属性标记为已在给定地址上颁发。
    _issuedAttributes[msg.sender][blackhatAttributeTypeID] = true;

    // 计算 blackhat 属性的总数的新数量。
    uint256 incrementCounter = _issuedAttributeCounters[blackhatIndex] + 1;

    // 将属性值设置为新分配的 blackhat 属性的总数。
    _issuedAttributeValues[msg.sender][blackhatAttributeTypeID] = incrementCounter;

    // 更新 blackhat 属性总数的计数器的值。
    _issuedAttributeCounters[blackhatIndex] = incrementCounter;

    // 获取属性注册表上 whitehat 属性类型的索引。
    uint256 whitehatIndex = uint256(Affiliation.Whitehat);

    // 获取 whitehat 属性类型的属性类型 ID。
    uint256 whitehatAttributeTypeID = _attributeTypeIDs[whitehatIndex];

    // 确定是否已分配 whitehat 属性类型。
    if (_issuedAttributes[msg.sender][whitehatAttributeTypeID]) {
      // 如果是,则删除该属性。
      delete _issuedAttributes[msg.sender][whitehatAttributeTypeID];

      // 同时也删除属性值。
      delete _issuedAttributeValues[msg.sender][whitehatAttributeTypeID];

      // 将属性值设置为新分配的 whitehat 属性的总数。
      uint256 decrementCounter = _issuedAttributeCounters[whitehatIndex] - 1;

      // 更新 whitehat 属性总数的计数器的值。
      _issuedAttributeCounters[whitehatIndex] = decrementCounter;
    }
  }

  /**
   * @notice 获取分配的 whitehat 和 blackhat 属性的总数。
   * @return 包含分配的 whitehat 和 blackhat 属性计数的数组。
   * @dev 这个函数是任意的,不是属性注册表
   * 规范的一部分。
   */
  function totalHats() external view returns (uint256[2]) {
    // 返回包含计数器值的数组。
    return _issuedAttributeCounters;
  }

  /**
   * @notice 检查是否已将 ID 为 `attributeTypeID` 的类型的属性分配给
   *  `account` 的帐户,并且当前是否有效。
   * @param account address 要检查是否存在有效属性的帐户。
   * @param attributeTypeID uint256 要检查的属性类型的 ID。
   * @return 如果属性已分配且有效,则为 True,否则为 false。
   * @dev 此函数必须返回 true 或 false - 即调用此
   * 函数不得导致调用者 revert。
   */
  function hasAttribute(
    address account,
    uint256 attributeTypeID
  ) external view returns (bool) {
    // 按帐户和属性类型 ID 返回属性的分配状态
    return _issuedAttributes[account][attributeTypeID];
  }

  /**
   * @notice 检索 ID 为 `attributeTypeID` 的类型的属性在
   * `account` 的帐户上的值,假设它有效。
   * @param account address 要检查给定属性值的帐户。
   * @param attributeTypeID uint256 要检查的属性类型的 ID。
   * @return 如果属性有效,则为属性值,否则 revert。
   * @dev 如果直接先前或后续
   * 使用相同的 `account` 和
   * `attributeTypeID` 参数调用 `hasAttribute` 函数将返回 false,则此函数必须 revert。
   */
  function getAttributeValue(
    address account,
    uint256 attributeTypeID
  ) external view returns (uint256 value) {
    // 如果未分配具有给定帐户和属性类型 ID 的属性,则还原
    require(
      _issuedAttributes[account][attributeTypeID],
      "找不到具有提供的帐户和属性类型 ID 的值"
    );

    // 返回属性值。
    return _issuedAttributeValues[account][attributeTypeID];
  }

  /**
   * @notice 计算注册表定义的属性类型的数量。
   * @return 可用属性类型的数量。
   * @dev 此函数必须返回一个正整数值 - 即调用
   * 此函数不得导致调用者 revert。
   */
  function countAttributeTypes() external view returns (uint256) {
    // 返回属性类型 ID 数组的长度。
    return _attributeTypeIDs.length;
  }

  /**
   * @notice 获取索引 `index` 处的属性类型的 ID。
   * @param index uint256 有问题的属性类型的索引。
   * @return 属性类型的 ID。
   * @dev 如果提供的 `index` 值落在直接先前或后续
   * 调用 `countAttributeTypes` 函数返回的值范围之外,则此函数必须 revert。如果提供的
   *  `index` 值落在上述范围内,则不得 revert。
   */
  function getAttributeTypeID(uint256 index) external view returns (uint256) {
    // 如果提供的索引超出范围,则还原。
    require(
      index < _attributeTypeIDs.length,
      "提供的索引超出了定义的属性类型 ID 的范围"
    );

    // 返回数组中给定索引处的属性类型 ID。
    return _attributeTypeIDs[index];
  }
}

版权

版权及相关权利通过 CC0 放弃。

Citation

Please cite this document as:

0age (@0age), Santiago Palladino (@spalladino), Leo Arias (@elopio), Alejo Salles (@fiiiu), Stephane Gosselin (@thegostep), "ERC-1616: 属性注册表标准 [DRAFT]," Ethereum Improvement Proposals, no. 1616, November 2018. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-1616.