-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
461 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
--- | ||
eip: 888 | ||
title: EXP Token Standard | ||
description: A standard interface for fungible, non-tradable tokens, also known as EXP. | ||
author: Daniel Tedesco (@dtedesco1) | ||
discussions-to: https://ethereum-magicians.org/t/EIP-888-fungible-non-tradable-tokens/8805 | ||
status: Draft | ||
type: Standards Track | ||
category: ERC | ||
created: 2022-04-02 | ||
requires: 165 | ||
--- | ||
|
||
# Simple Summary | ||
A standard interface for fungible, non-tradable tokens, also known as EXP. | ||
|
||
## Abstract | ||
The following describes a standard API for fungible, non-tradable tokens within smart contracts. This standard provides basic functionality for participant addresses to consent to receive tokens and for an operator address to mint, transfer, and burn tokens. | ||
|
||
In general, EXP represents accumulated recognition within a smart contract. Like experience points in video games, citations on an academic paper, or Reddit Karma, EXP is bestowed for useful contributions, accumulates as indistinguishable units, and should only be reallocated or destroyed by a reliable authority so empowered. | ||
|
||
The standard described here allows reputation earned to be codified within a smart contract and recognized by other applications: from a five-member local bicycle club to a million-member green energy DAO. | ||
|
||
## Motivation | ||
How reputation manifests across groups can vary widely. But healthy communities allocate reputation to their members using three key principles: | ||
1. Consent -- No one is forced to be part of the group, but joining requires abiding by the governance structure of the group. | ||
2. Meritocracy -- Reputation is earned by recognition from the group. It cannot be claimed, purchased, or sold. | ||
3. Ethics -- The group can decrease an individual's reputation after bad behavior. | ||
|
||
From the creation of Bitcoin in 2008 through 2021, the vast majority of blockchain applications centered on buying and selling digital assets. While these use cases are substantial, digital assets need not be created with trading in mind. In fact, trading can be detrimental for community-based blockchain projects. This was evident in the pay-to-play dynamics of many EVM-based games and DAOs in 2021. | ||
|
||
A smart contract cannot directly imbue consent, meritocracy, and ethics into a community, but it can encourage those principles. In doing so, the standard set out below will hopefully unlock a diverse array of new use cases for tokens. | ||
|
||
We considered a diverse array of use cases, though this may just be the beginning: | ||
- Voting weight in a DAO | ||
- Experience points in a decentralized game | ||
- Loyalty points for customers of a business | ||
|
||
This standard is influenced by the ERC-20 and ERC-721 token standards, and takes cues from each in terms of its structure, style, and semantics. Neither, however, was created for fungible operator-managed token contracts such as EXP. Nor do existing proposals for non-tradable tokens meet the requirements of EXP use cases. Differences are examined below. | ||
|
||
## 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. | ||
|
||
Every ERC-888 compliant contract MUST implement the ERC888 and ERC165 interfaces: | ||
|
||
``` | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
/// @title ERC-888 EXP Token Standard | ||
/// @dev See https://eips.ethereum.org/EIPS/EIP-888 | ||
/// Note: the ERC-165 identifier for this interface is ###ERC888###. | ||
interface IERC888 /* is ERC165 */ { | ||
/// Emits when operator is changed. | ||
/// @dev MUST emit whenever operator is changed. | ||
event Appointment(address indexed _operator); | ||
/// Emits when an address opts to participate. | ||
/// @dev MUST emit whenever an address begins or ends participation. | ||
/// Transfers SHOULD NOT reset participation. | ||
event Approval(address indexed _participant, bool _participation); | ||
/// @notice Emits when operator transfers EXP to participating address. | ||
/// @dev MUST emit when EXP is created (`from` == 0), | ||
/// destroyed (`to` == 0), or reallocated to another address. | ||
/// Exception: during contract creation, any amount of EXP | ||
/// MAY be created and assigned without emitting Transfer. | ||
event Transfer(address indexed _from, address indexed _to, uint256 _amount); | ||
/// @notice Returns total EXP allocated to a participant. | ||
/// @dev As zero address EXP is invalid, this function | ||
/// MUST throw for queries about the zero address. | ||
/// @param _participant An address for whom to query EXP total | ||
/// @return uint256 The number of EXP allocated to `_participant`, possibly zero. | ||
function balanceOf(address _participant) external view returns (uint256); | ||
/// @notice Transfers EXP from zero address to a participant. | ||
/// @dev MUST throw unless msg.sender is operator. | ||
/// @dev MUST throw unless _to address is participating. | ||
function transfer(address _to, uint256 _amount) external; | ||
/// @notice Transfer EXP from one address to another. | ||
/// @dev MUST throw unless msg.sender is operator. | ||
/// MUST throw unless _to address is participating. | ||
/// MAY throw if _from address is NOT participating. | ||
function transferFrom(address _from, address _to, uint256 _amount) external; | ||
/// @notice Activate or deactivate participation. | ||
/// @dev MUST throw unless msg.sender is _participant. | ||
/// @param _participant Address opting in or out of participation. | ||
/// @param _participation Participation status of _participant. | ||
function approve(address _participant, bool _participation) external; | ||
/// @notice Reassign operator authority. | ||
/// @dev MUST throw unless msg.sender is _operator. | ||
/// @dev MUST throw unless _operator is participating. | ||
/// @param _operator New operator of the smart contract. | ||
function setOperator(address _operator) external; | ||
} | ||
``` | ||
|
||
The *metadata extension* is OPTIONAL for ERC-888 smart contracts. This allows an EXP smart contract to be interrogated for its name and description. | ||
``` | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
import "./IERC888.sol"; | ||
/// @title ERC-888 EXP Standard, optional metadata extension | ||
/// @dev See https://eips.ethereum.org/EIPS/EIP-888 | ||
/// Note: the ERC-165 identifier for this interface is ###ERC888Metadata###. | ||
interface IERC888Metadata is IERC888 { | ||
/// @notice A descriptive name for the EXP in this contract. | ||
function name() external view returns (string memory); | ||
/// @notice A one-line description of the EXP in this contract. | ||
function description() external view returns (string memory); | ||
} | ||
``` | ||
|
||
## Rationale | ||
### Approval | ||
EXP drops SHALL require pre-approval from the delivery address. This ensures the receiver is a consenting participant in the smart contract. | ||
|
||
### Mints & Transfers | ||
EXP mints and transfers SHALL be at the soul discretion of the contract operator. This party may be a sports team coach or a multisig DAO wallet. We decide not to specify how governance occurs, but only *that* governance occurs. This allows for a wider range of potential use cases than optimizing for particular decision-making forms. | ||
|
||
ERC-888 standardizes a control mechanism to allocate community recognition without encouraging financialization of that recognition or easily allowing non-contributors to acquire EXP representing contribution. While it does not ensure meritocracy, it opens the door. | ||
|
||
### Token Destruction | ||
EXP SHOULD allow burning tokens by contract operators. If Bob has contributed greatly to the community, but then is caught stealing from Alice, the community may decide this should lower Bob's standing and influence in the community. Again, while this does not ensure an ethical standard within the community, it opens the door. | ||
|
||
### EXP Word Choice | ||
EXP, or experience points, are common parlance in the video game industry and generally known among modern internet users. Allocated EXP typically confers to strength and accumulates as one progresses in a game. This serves as a fair analogy to what we aim to achieve with ERC-888 by encouraging members of a community to have more strength in that community the more they contribute. | ||
|
||
*Alternatives Considered: Soulbound Tokens, Soulbounds, Fungible Soulbound Tokens, Non-tradable Fungible Tokens, Non-transferrable Fungible Tokens, Karma Points, Reputation Tokens* | ||
|
||
### Participants Word Choice | ||
Participants have agency over their *participation* in an activity, but not over the *outcomes*. Parties to ERC-888 contracts are not owners in the same sense as owners of ERC-20 or ERC-721 tokens. Yes, the EXP sits in their wallet, but they do not directly control any use of those tokens. | ||
|
||
*Alternatives Considered: members, parties, contributors, players, entrants* | ||
|
||
### ERC-165 Interface | ||
We chose Standard Interface Detection (ERC-165) to expose the interfaces that an ERC-888 smart contract supports. | ||
|
||
### Privacy | ||
Users identified in the motivation section have a strong need to identify how much EXP a user has. | ||
|
||
### Metadata Choices | ||
We have required `name` and `description` functions in the metadata extension. Name common among major token standards (namely, ERC-20 and ERC-721). We eschewed `symbol` as we do not wish them to be listed on any tickers that might tempt operators to engage in financial activities with these assets. We included a `description` function that may be helpful for games or other applications with multiple ERC-888 tokens. | ||
|
||
We remind implementation authors that the empty string is a valid response to `name` and `description` if you protest to the usage of this mechanism. We also remind everyone that any smart contract can use the same name and symbol as your contract. How a client may determine which ERC-888 smart contracts are well-known (canonical) is outside the scope of this standard. | ||
|
||
## Backwards Compatibility | ||
We have adopted `Approval`, `Transfer`, `balanceOf`, `totalSupply`, `transfer`, `transferFrom`, and `name` semantics from the ERC-20 and ERC-721 specifications. An implementation may also include a function `decimals` that returns `uint8(0)` if its goal is to be more compatible with ERC-20 while supporting this standard. However, we find it contrived to require all ERC-888 implementations to support the `decimals` function. | ||
|
||
## Test Cases | ||
This EIP does not affect consensus and is therefore exempt from the test case requirement. | ||
|
||
## Reference Implementation | ||
|
||
A reference implementation of this standard can be found at [../assets/EIP-888/](https://github.com/ethereum/EIPs/tree/master/assets/EIP-888). | ||
|
||
## References | ||
|
||
**Standards** | ||
|
||
1. [ERC-20](./eip-20.md) Token Standard. | ||
2. [ERC-165](./eip-165.md) Standard Interface Detection. | ||
3. [ERC-721](./eip-721.md) NFT Standard. | ||
4. [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) Key words for use in RFCs to Indicate Requirement Levels. | ||
|
||
**Issues & Discussions** | ||
1. [EIP-888 discussion thread](https://ethereum-magicians.org/t/EIP-888-fungible-non-tradable-tokens/), Ethereum Magicians, begun April 2022. | ||
2. ["Soulbound"](https://vitalik.ca/general/2022/01/26/soulbound.html), Vitalik Buterin, published January 2022. | ||
3. [EIP-1238](https://github.com/ethereum/EIPs/issues/1238), "Non-transferrable Non-Fungible Tokens", GitHub issue opened July 2018. | ||
4. [EIP-4671](https://github.com/ethereum/EIPs/issues/4671), "Non-Tradable Token Standard", draft status as of April 2022. | ||
5. [EIP-4671 discussion thread](https://ethereum-magicians.org/t/eip-4671-non-tradable-token/7976/35), Ethereum Magicians, begun January 2022. | ||
|
||
## Security Considerations | ||
The `operator` address has total control over the allocation and transfer of tokens. Therefore, ensuring this party is secure and trustworthy is critical for the contract to function. No alternative exists if the operator is corrupted or lost. | ||
|
||
We strongly encourage `operator` to be a smart contract with robust access control features to manage EXP. | ||
|
||
## Copyright | ||
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; | ||
import "@openzeppelin/contracts/utils/Context.sol"; | ||
|
||
import "./IERC888.sol"; | ||
import "./IERC888Metadata.sol"; | ||
|
||
contract ERC888 is Context, IERC888, IERC888Metadata, ERC165 { | ||
mapping(address => uint256) private _balances; | ||
|
||
mapping(address => bool) private _participants; | ||
|
||
uint256 private _totalSupply; | ||
|
||
string private _name; | ||
string private _description; | ||
address private _operator; | ||
|
||
/** | ||
* @notice Sets the values for {name} and {symbol}. | ||
* @dev Both two of these values are immutable: they can only be set once during | ||
* construction. | ||
*/ | ||
constructor(string memory name_, string memory description_, address operator_) { | ||
_name = name_; | ||
_description = description_; | ||
_operator = operator_; | ||
_participants[_operator] = true; | ||
} | ||
|
||
/** | ||
* | ||
* External Functions | ||
* | ||
*/ | ||
|
||
/** | ||
* @notice Returns the name of the EXP token. | ||
* @return string The name of the EXP token. | ||
*/ | ||
function name() public view virtual override returns (string memory) { | ||
return _name; | ||
} | ||
|
||
/** | ||
* @notice Returns the description of the EXP token, | ||
* usually a one-line description. | ||
* @return string The description of the EXP token. | ||
*/ | ||
function description() public view virtual override returns (string memory) { | ||
return _description; | ||
} | ||
|
||
/** | ||
* @notice Returns the current operator of the EXP token, | ||
* @return address The current operator of the EXP token. | ||
*/ | ||
function operator() public view virtual returns (address) { | ||
return _operator; | ||
} | ||
|
||
/** | ||
* @notice Returns the total supply of EXP tokens. | ||
* @dev Result includes inactive accounts, but not destroyed tokens. | ||
* @return uint256 The total supply of EXP tokens. | ||
*/ | ||
function totalSupply() public view virtual returns (uint256) { | ||
return _totalSupply; | ||
} | ||
|
||
/** | ||
* @notice Returns the EXP balance of the account. | ||
* @param account The address to query. | ||
* @return uint256 The EXP balance of the account. | ||
*/ | ||
function balanceOf(address account) public view virtual override returns (uint256) { | ||
return _balances[account]; | ||
} | ||
|
||
/** | ||
* @notice Returns the participation status of the account. | ||
* @param account The address to query. | ||
* @return bool The participation status of the queried account. | ||
*/ | ||
function participationOf(address account) public view virtual returns (bool) { | ||
return _participants[account]; | ||
} | ||
|
||
/** | ||
* @notice Transfer `amount` EXP to an account from zero address. | ||
* Equivalent to minting. | ||
* @dev Emits {Transfer} event if successful. | ||
* Throws if: | ||
* - Sender is not operator | ||
* - `to` address is not participating | ||
* @param to The address of the recipient. | ||
* @param amount The amount to be transferred. | ||
*/ | ||
function transfer(address to, uint256 amount) public virtual override { | ||
_transfer(address(0), to, amount); | ||
} | ||
|
||
/** | ||
* @notice Transfer `amount` EXP to an account. Equivalent to minting. | ||
* @dev Emits {Transfer} event if successful. | ||
* Throws if: | ||
* - Sender is not operator | ||
* - `to` address is not participating | ||
* @param from The address from which to transfer. | ||
* @param to The address of the recipient. | ||
* @param amount The amount to be transferred. | ||
*/ | ||
function transferFrom(address from, address to, uint256 amount) public virtual override { | ||
_transfer(from, to, amount); | ||
} | ||
|
||
/** | ||
* @notice Sets approval of participant EXP acceptance. | ||
* @dev Throws if msg.sender is not the address in question. | ||
*/ | ||
function approve(address participant, bool participation) public virtual override { | ||
require(_msgSender() == participant); | ||
_approve(participant, participation); | ||
} | ||
|
||
/** | ||
* @notice Assigns a new operator address. | ||
* @dev Throws if sender is not operator or `newOperator` equals current `_operator` | ||
* @param newOperator Address to reassign operator role. | ||
*/ | ||
function setOperator(address newOperator) public virtual override { | ||
_setOperator(newOperator); | ||
} | ||
|
||
/** | ||
* | ||
* Internal Functions | ||
* | ||
*/ | ||
|
||
/** | ||
* @notice Moves `amount` of tokens from `from` address to `to` address. | ||
* @dev Throws if sender is not operator. | ||
* Throws if `to` is not participating. | ||
* Emits a {Transfer} event. | ||
* @param from Address from which to transfer. If zero address, then add to totalSupply. | ||
* @param to Address to which to transfer. | ||
* @param amount Number of EXP transfer. | ||
*/ | ||
function _transfer( | ||
address from, | ||
address to, | ||
uint256 amount | ||
) internal virtual { | ||
require(_msgSender() == _operator, "Sender is not the operator."); | ||
require(_participants[to] == true, "`to` address is not an active participant."); | ||
|
||
if (from == address(0)) { | ||
_totalSupply += amount; | ||
} else { | ||
require(_balances[from] >= amount, "{from} address holds less EXP than {amount}."); | ||
_balances[from] -= amount; | ||
} | ||
|
||
_balances[to] += amount; | ||
|
||
emit Transfer(from, to, amount); | ||
} | ||
|
||
/** | ||
* @notice Sets approval status for `participant`. | ||
* @dev Throws if sender is not `participant`. | ||
* Emits a {Approval} event. | ||
* @param participant Address for which to set approval. | ||
* @param participation Requested approval status. | ||
*/ | ||
function _approve(address participant, bool participation) internal virtual { | ||
require(_msgSender() == participant, "Sender is not {participant}."); | ||
require(_msgSender() != _operator, "Operator cannot change participation"); | ||
require(_participants[participant] != participation, "Participant already has {participation} status"); | ||
_participants[participant] = participation; | ||
emit Approval(participant, participation); | ||
} | ||
|
||
/** | ||
* @notice Assign a new operator. | ||
* @dev Throws is sender is not current operator. | ||
* Emits {Appointment} event. | ||
* @param newOperator address to be assigned operator authority. | ||
*/ | ||
function _setOperator(address newOperator) internal virtual { | ||
require(_msgSender() == _operator, "Sender is not operator."); | ||
require(_operator != newOperator, "Address is already assigned as operator"); | ||
_operator = newOperator; | ||
emit Appointment(newOperator); | ||
} | ||
|
||
} |
Oops, something went wrong.