はじめに
初めまして。
CryptoGamesというブロックチェーンゲーム企業でエンジニアをしている cardene(かるでね) です!
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。
代表的なゲームはクリプトスペルズというブロックチェーンゲームです。
今回は、コントラクト内でレートを管理する提案をしている規格であるERC4974についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
4974は現在(2023年9月13日)では「Review」段階です。
概要
この規格は、Ethereumブロックチェーン上でレートを管理するための基準を設定したものです。
レートとは、何かのアイテムやプロジェクトに対する数値的な評価やランキングのことを指します。
例えば、製品の評価、アート作品の評価、あるいはプロジェクトの成功度などが評価の対象になります。
この規格によって、レートのデータをブロックチェーン上に保存し、信頼性の高い方法で管理できるようになります。
また、他のアプリケーションも同じデータにアクセスできるため、様々なアプリケーションでこの評価データを利用することができます。
これは、トークンに対して新しい使い道を生み出すことができます。
例えば、特定のレートを持つトークンを発行し、それに基づいて特典を提供するアプリケーションが考えられます。
また、プロジェクトの評価をトークンとして取引する市場も形成されるかもしれません。
動機
これまで、ブロックチェーンアプリケーションはデジタルアセット(例:仮想通貨やトークン)の購入と販売に主に焦点を当ててきました。
しかし、このアセット中心のモデルには欠点があり、特にコミュニティベースのブロックチェーンプロジェクトにとって問題となってきました。
2021年には、EVM(Ethereum Virtual Machine)ベースのゲームやDAO(分散型自治組織)などで、支払いをすることで特典を得るというダイナミクスが問題視されました。
この提案は、レートをスマートコントラクトやウォレットに割り当てることを可能にすることで、この問題に対処しようとしています。
つまり、ブロックチェーンアプリケーションに新しい組み合わせ可能な要素を提供するものです。
これにより、以下のような新しい使い道が考えられます。
DAO内での投票権
この規格を使用して割り当てられたレートをもとに、分散型自治組織(DAO)のメンバーの投票権を決定できます。
例えば、DAOはコミュニティへの貢献が高いメンバーに高いレートを与え、これを利用して各メンバーの意思決定プロセスへの影響力を決めることができます。
分散型ゲームエコシステム内での経験ポイント
レートは、分散型ゲームエコシステム内のプレイヤーの進捗を追跡し、特定の目標を達成した時に報酬を提供するために使用できます。
例えば、ゲームはプレイヤーにレートを与え、これを使って新しいコンテンツや能力をアンロックできるようにすることができます。
ビジネスの顧客向けのロイヤルティポイント
レートは、特定のビジネスやサービスに対する顧客の忠誠心を追跡し、忠誠度に応じた報酬を提供するために使用できます。
例えば、ビジネスは評価を顧客に割り当て、これを特別なオファーや割引と交換できるようにすることができます。
分散型保険会社のアセット評価
レートは、分散型保険会社内のアセットのリスクプロファイルを評価し、保険料やカバレッジを決定するために使用できます。
例えば、分散型保険会社はさまざまなアセットのリスクを評価し、低リスクのアセットに対しては低い保険料と高いカバレッジを提供できるようにレートを利用できます。
この規格は、EIP20やEIP721といったトークン規格を参考にしています。
ERC20は以下を参考にしてください。
ERC721は以下を参考にしてください。
仕様
この規格に準拠コントラクトは、以下のインターフェイスを実装する必要があります。
// SPDX-License-Identifier: CC0
pragma solidity ^0.8.0;
/// @title EIP-4974 Ratings
/// @dev See https://eips.ethereum.org/EIPS/EIP-4974
/// Note: the EIP-165 identifier for this interface is #######.
/// Must initialize contracts with an `operator` address that is not `address(0)`.
interface IERC4974 /* is ERC165 */ {
/// @dev Emits when operator changes.
/// MUST emit when `operator` changes by any mechanism.
/// MUST ONLY emit by `setOperator`.
event NewOperator(address indexed _operator);
/// @dev Emits when operator issues a rating.
/// MUST emit when rating is assigned by any mechanism.
/// MUST ONLY emit by `rate`.
event Rating(address _rated, int8 _rating);
/// @dev Emits when operator removes a rating.
/// MUST emit when rating is removed by any mechanism.
/// MUST ONLY emit by `remove`.
event Removal(address _removed);
/// @notice Appoint operator authority.
/// @dev MUST throw unless `msg.sender` is `operator`.
/// MUST throw if `operator` address is either already current `operator`
/// or is the zero address.
/// MUST emit an `Appointment` event.
/// @param _operator New operator of the smart contract.
function setOperator(address _operator) external;
/// @notice Rate an address.
/// MUST emit a Rating event with each successful call.
/// @param _rated Address to be rated.
/// @param _rating Total EXP tokens to reallocate.
function rate(address _rated, int8 _rating) external;
/// @notice Remove a rating from an address.
/// MUST emit a Remove event with each successful call.
/// @param _removed Address to be removed.
function removeRating(address _removed) external;
/// @notice Return a rated address' rating.
/// @dev MUST register each time `Rating` emits.
/// SHOULD throw for queries about the zero address.
/// @param _rated An address for whom to query rating.
/// @return int8 The rating assigned.
function ratingOf(address _rated) external view returns (int8);
}
interface IERC165 {
/// @notice Query if a contract implements an interface.
/// @dev Interface identification is specified in EIP-165. This function
/// uses less than 30,000 gas.
/// @param interfaceID The interface identifier, as specified in EIP-165.
/// @return bool `true` if the contract implements `interfaceID` and
/// `interfaceID` is not 0xffffffff, `false` otherwise.
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
補足
レートの割り当て
レートはコントラクト運営者の独自の裁量によって行われます。
運営者は、スポーツチームのコーチであるか、マルチシグ(複数の署名が必要な)DAOウォレットなどさまざまです。
具体的なガバナンス方法は指定しませんが、ガバナンスが存在することだけを規定します。
これにより、特定の意思決定形式に最適化するのではなく、より幅広い潜在的な用途が可能になります。
int8の選択
符号つきの整数(int8
)を選択しました。
これは、レビュアーがウォレットやコントラクトに対して中立的でネガティブな評価を行えるようにするためです。
特に悪意のある行為が発生する可能性がある分散型アプリケーションにとって重要です。
8
ビットの整数を使用することで、評価を比較可能な範囲内に保つことが目的です。
長期的には、評価の簡単な集計を促進することができ、ユーザーがさまざまなスケールを使用することが少なくなります。
整数(int
)は、数値データを表現するためのデータ型の一種です。
符号つき整数(int
)は、正負の数を表現できるもので、例えば0
、1
、-1
、2
、-2
などが含まれます。
8ビットの整数(int8
)は、8つのビット(0
または1
のバイナリ桁)で数値を表現することができるものです。
例 1
ウォレットやのコントラクトのレートをint8
型として扱う場合、評価は-128
から127
までの範囲で表現できます。
これは、8
ビットが表現できる範囲です。
例 2
レビュアーが特定の分散型アプリケーションに対して評価を行うと考えましょう。
レートは以下のような数値で表現されるかもしれません。
-
0
- 中立的な評価
-
1
~127
- 肯定的な評価(
1
が最低の肯定的な評価、127
が最高の肯定的な評価)
- 肯定的な評価(
-
-1
~-128
- ネガティブな評価(
-1
が最低のネガティブな評価、-128
が最高のネガティブな評価)
- ネガティブな評価(
例 3
複数のユーザーが同じウォレットに対して評価を行うとします。
それぞれのユーザーは、中立的な評価、肯定的な評価、またはネガティブな評価を0
~127
または-1
~-128
の範囲内で割り当てることができます。
例 4
アプリケーションが複数のユーザーのレートを収集し、集計する場合、int8
型を使用することで、異なるユーザーの評価を容易に合算できます。
例えば、100人のユーザーがウォレットに対して評価を行い、それぞれの評価が0
~127
までの範囲内である場合、これらの評価を単純に合計することで、ウォレットの総合レートを計算できます。
8
ビットの整数は、レートを比較可能な形式で保持し、集計するための効果的な方法を提供します。
ユーザーがさまざまなスケールを使用する必要がないため、レートの統一性を保つのに役立ちます。
レートの変更
レートはコントラクト運営者によって更新できるようにするべきです。
たとえば、Bobがコミュニティに大きな貢献をしたが、後にAliceから盗みを働いたと発覚した場合、コミュニティはBobの地位と影響力を下げるべきかどうかを決定できます。
これはコミュニティ内で倫理的な基準を確保することはできませんが、その可能性を開くものです。
関連して、レートは、評価者が効果的に評価できる自信がない場合にレートを取り消すことを許可するべきです。
例 1: レートの更新
ある分散型コミュニティにおいて、Bobというメンバーがコミュニティに大きな貢献をし、そのレートが高いことで知られています。
しかし、ある日、BobがAliceから盗みを働いたという報告が寄せられました。コミュニティはこの問題を対処する必要があります。
- Bobのレートが高いことは、Bobのコミュニティ内での地位や影響力を示しています。
- コミュニティは、レートを更新することを決定します。
- これにより、Bobのレートが低下し、彼の地位や影響力が減少します。
- この変更は、コミュニティのメンバーや運営者によってコントラクト内で実行され、Bobの行動に応じてレートが変更され、コミュニティは倫理的な基準を確保し、問題のあるメンバーに対処できるようになります。
例 2: レートの取り消し
評価者としての自信を持てないユーザーがいるかもしれません。
このユーザーは、誤ってレートを付けたり、誤ったレートを付けた場合に、レートを取り消す機能が必要です。
- ユーザーAは、特定のコントラクトにレートを付けることを試みますが、レートの正確さに自信がありません。
- ユーザーAは誤って高いレートをつけたことに気付き、そのレートを取り消すことを選択します。
- システムはユーザーAにレートを取り消すオプションを提供し、ユーザーAが誤ったレートを修正できるようにします。
これにより、ユーザーは誤ったレートを調整し、レートの精度を向上させることができます。
この機能は、レートプロセスの信頼性を高め、誤解や不正確なレートを修正できるようにします。
インターフェースの検出
この規格に準拠するスマートコントラクトがサポートするインターフェースを公開するために、標準インターフェース検出(EIP165)を選択しました。
メタデータの選択
この規格では、スマートコントラクトに関する情報を提供するために「名前(name
)」と「説明(description
)」が格納されたメタデータが必要です。
これは、他のブロックチェーン規格と一貫性を持たせるためで、主要なブロックチェーンプリミティブにおいて一般的な要素です。
また、複数のレートシステムが存在する場合、詳細な説明を提供する「description
」機能も提供されています。
実装者がこれらの情報を提供しない場合、空の文字列を使用できますが、その場合でも正当な応答です。
また、同じ名前と説明を使用する他のスマートコントラクトが存在する可能性にも留意する必要があります。
デメリット
この規格を使用する際の潜在的なデメリットは、レートが主観的であるため、コントラクトやウォレットの真の価値や品質を常に正確に反映しない可能性があることです。
しかし、この規格はレートを更新および削除する機能を提供し、柔軟性を持たせています。
ユーザーは他のコントラクトやコミュニティがどのように評価しているかを知る必要があり、評価によって誇りを感じる場合もあれば、不当なネガティブ評価を受ける可能性もあります。
不当な評価は悪意のある行動を引き起こす可能性があるため、レートシステムを作成する実装者には慎重に対処する責任があります。
後方互換性
EIP20およびEIP721仕様のname
の定義を採用しています。
参考実装
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.0;
import "./IERC4974.sol";
/**
* See {IERC4974}
* Implements the ERC4974 Metadata extension.
*/
contract LoyaltyPoints is IERC4974 {
// The address of the operator that can assign ratings
address private _operator;
// Mapping of customer addresses to their ratings
mapping (bytes32 => int8) private _ratings;
// Initializes the contract by setting the operator to msg.sender
constructor () {
_operator = msg.sender;
}
// Set the operator address
// Only the current operator or the contract owner can call this function
function setOperator(address newOperator) public override {
require(_operator == msg.sender || msg.sender == address(this), "Only the current operator or the contract owner can set the operator.");
_operator = newOperator;
emit NewOperator(_operator);
}
// Rate a customer
// Only the operator can call this function
function rate(address customer, int8 rating) public override {
require(_operator == msg.sender, "Only the operator can assign ratings.");
bytes32 hash = keccak256(abi.encodePacked(customer));
_ratings[hash] = rating;
emit Rating(customer, rating);
}
// Remove a rating from a customer
// Only the operator can call this function
function removeRating(address customer) external override {
require(_operator == msg.sender, "Only the operator can remove ratings.");
bytes32 hash = keccak256(abi.encodePacked(customer));
delete _ratings[hash];
emit Removal(customer);
}
// Get the rating for a customer
function getOperator() public view returns (address) {
return _operator;
}
// Check if a customer has been rated
function hasBeenRated(address customer) public view returns (bool) {
// Hash the customer address
bytes32 hash = keccak256(abi.encodePacked(customer));
// Check if the hash exists in the mapping
return _ratings[hash] != 0;
}
function ratingOf(address _rated) public view override returns (int8) {
bytes32 hash = keccak256(abi.encodePacked(_rated));
// Check if the customer has been rated
require(hasBeenRated(_rated), "This customer has not been rated yet.");
// Return the customer's rating
return _ratings[hash];
}
// Award ETH to a customer based on their rating
function awardEth(address payable customer) public payable {
// Calculate the amount of ETH to award based on the customer's rating
int8 rating = ratingOf(customer);
require(rating > 0, "Sorry, this customer has a rating less than 0 and cannot be awarded.");
uint256 award = uint256(int256(rating));
// Transfer the ETH to the customer
require(address(this).balance >= award, "Contract has insufficient balance to award ETH.");
customer.transfer(award);
}
receive () external payable {}
}
_operator
address private _operator;
概要
評価を割り当てることができるオペレーター(運営者)のアドレス。
詳細
このスマートコントラクト内で評価を割り当てる権限を持つアカウントです。
オペレーターは他のユーザーに対してレートを与えることができます。
_ratings
mapping (bytes32 => int8) private _ratings;
概要
アドレスに対応するレートを格納する配列。
詳細
ユーザーごとにレートを記録するために使用されます。
ユーザーのアドレス(bytes32
)とそれに対応するレート(int8
、符号つき整数)が関連付けられます。
ユーザーの評価は、オペレーターによって割り当てられ、必要に応じて更新または削除することができます。
setOperator
function setOperator(address newOperator) public override {
require(_operator == msg.sender || msg.sender == address(this), "Only the current operator or the contract owner can set the operator.");
_operator = newOperator;
emit NewOperator(_operator);
}
概要
オペレーターアドレスを設定する関数。
詳細
オペレーターはレートを割り当てるための権限を持つアカウントであり、この関数を使用して新しいオペレーターアドレスを設定できます。
オペレーターまたはコントラクトの所有者でない場合、この関数を呼び出すことはできません。
新しいオペレーターアドレスが設定されると、NewOperator
イベントが発行されます。
引数
-
newOperator
- 新しいオペレーターのアドレス。
rate
function rate(address customer, int8 rating) public override {
require(_operator == msg.sender, "Only the operator can assign ratings.");
bytes32 hash = keccak256(abi.encodePacked(customer));
_ratings[hash] = rating;
emit Rating(customer, rating);
}
概要
顧客にレートを割り当てる関数。
詳細
レートは特定のアドレスに関連付けられ、対応するハッシュに保存されます。
レートを割り当てる権限を持つオペレーターのみがこの関数を呼び出すことができます。
レートが割り当てられると、Rating
イベントが発行されます。
引数
-
customer
- レートを受けるアドレス。
-
rating
- 特定アドレスに割り当てるレート(
-128
~127
までの範囲内の整数)
- 特定アドレスに割り当てるレート(
removeRating
function removeRating(address customer) external override {
require(_operator == msg.sender, "Only the operator can remove ratings.");
bytes32 hash = keccak256(abi.encodePacked(customer));
delete _ratings[hash];
emit Removal(customer);
}
概要
指定された特定のアドレスのレートを削除する関数。
詳細
レートが削除されると、対応するハッシュが削除され、レートがリセットされます。
レートが削除されると、Removal
イベントが発行されます。
レートを割り当てる権限を持つオペレーターのみがこの関数を呼び出すことができます。
引数
-
customer
- レートを削除する特定のアドレス。
getOperator
function getOperator() public view returns (address) {
return _operator;
}
概要
現在のオペレーターのアドレスを取得する関数。
詳細
他のユーザーが現在のオペレーターを確認できるようにするための関数です。
戻り値
-
address
- 現在のオペレーターのアドレス。
hasBeenRated
function hasBeenRated(address customer) public view returns (bool) {
// Hash the customer address
bytes32 hash = keccak256(abi.encodePacked(customer));
// Check if the hash exists in the mapping
return _ratings[hash] != 0;
}
概要
指定された特定のアドレスが評価を受けたか確認する関数。
詳細
指定された顧客のアドレスをハッシュ化し、それがマッピング内に存在するかどうかを確認します。
存在する場合、該当アドレスは評価を受けたことがあります。
引数
-
customer
- レートを確認する特定のアドレス。
戻り値
-
bool
-
レートが存在する場合は
true
、存在しない場合はfalse
。
-
レートが存在する場合は
ratingOf
function ratingOf(address _rated) public view override returns (int8) {
bytes32 hash = keccak256(abi.encodePacked(_rated));
// Check if the customer has been rated
require(hasBeenRated(_rated), "This customer has not been rated yet.");
// Return the customer's rating
return _ratings[hash];
}
概要
指定された特定のアドレスのレートを取得する関数。
詳細
指定された特定のアドレスをハッシュ化し、レートが存在することを確認します。
レートが存在する場合、顧客のレートを返します。
レートが存在しない場合、エラーメッセージが表示されます。
引数
-
_rated
- レートを取得する特定のアドレス。
戻り値
-
int8
- 特定のアドレスのレート(
-128
~127
までの範囲内の整数)。
- 特定のアドレスのレート(
awardEth
function awardEth(address payable customer) public payable {
// Calculate the amount of ETH to award based on the customer's rating
int8 rating = ratingOf(customer);
require(rating > 0, "Sorry, this customer has a rating less than 0 and cannot be awarded.");
uint256 award = uint256(int256(rating));
// Transfer the ETH to the customer
require(address(this).balance >= award, "Contract has insufficient balance to award ETH.");
customer.transfer(award);
}
概要
指定された特定アドレスに対してETHを付与します。
詳細
特定アドレスのレートに基づいて付与額が計算され、評価が0
より大きい場合にのみ付与が行われ、ETHを特定アドレスに転送します。
また、コントラクト内のETH残高が付与額以上あるかを確認します。
引数
-
customer
- ETHを付与する特定のアドレス。
セキュリティ考慮事項
引用
Daniel Tedesco (@dtedesco1), "ERC-4974: Ratings [DRAFT]," Ethereum Improvement Proposals, no. 4974, April 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-4974.
最後に
今回は「コントラクト内でレートを管理する提案をしている規格であるERC4974」についてまとめてきました!
いかがだったでしょうか?
質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!
他の媒体でも情報発信しているのでぜひ他も見ていってください!