Add ERC6909 Implementation along with extensions (#5394)
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com> Co-authored-by: Ernesto García <ernestognw@gmail.com>
This commit is contained in:
27
contracts/token/ERC6909/README.adoc
Normal file
27
contracts/token/ERC6909/README.adoc
Normal file
@ -0,0 +1,27 @@
|
||||
= ERC-6909
|
||||
|
||||
[.readme-notice]
|
||||
NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/token/erc6909
|
||||
|
||||
This set of interfaces and contracts are all related to the https://eips.ethereum.org/EIPS/eip-6909[ERC-6909 Minimal Multi-Token Interface].
|
||||
|
||||
The ERC consists of four interfaces which fulfill different roles--the interfaces are as follows:
|
||||
|
||||
. {IERC6909}: Base interface for a vanilla ERC6909 token.
|
||||
. {IERC6909ContentURI}: Extends the base interface and adds content URI (contract and token level) functionality.
|
||||
. {IERC6909Metadata}: Extends the base interface and adds metadata functionality, which exposes a name, symbol, and decimals for each token id.
|
||||
. {IERC6909TokenSupply}: Extends the base interface and adds total supply functionality for each token id.
|
||||
|
||||
Implementations are provided for each of the 4 interfaces defined in the ERC.
|
||||
|
||||
== Core
|
||||
|
||||
{{ERC6909}}
|
||||
|
||||
== Extensions
|
||||
|
||||
{{ERC6909ContentURI}}
|
||||
|
||||
{{ERC6909Metadata}}
|
||||
|
||||
{{ERC6909TokenSupply}}
|
||||
224
contracts/token/ERC6909/draft-ERC6909.sol
Normal file
224
contracts/token/ERC6909/draft-ERC6909.sol
Normal file
@ -0,0 +1,224 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {IERC6909} from "../../interfaces/draft-IERC6909.sol";
|
||||
import {Context} from "../../utils/Context.sol";
|
||||
import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol";
|
||||
|
||||
/**
|
||||
* @dev Implementation of ERC-6909.
|
||||
* See https://eips.ethereum.org/EIPS/eip-6909
|
||||
*/
|
||||
contract ERC6909 is Context, ERC165, IERC6909 {
|
||||
mapping(address owner => mapping(uint256 id => uint256)) private _balances;
|
||||
|
||||
mapping(address owner => mapping(address operator => bool)) private _operatorApprovals;
|
||||
|
||||
mapping(address owner => mapping(address spender => mapping(uint256 id => uint256))) private _allowances;
|
||||
|
||||
error ERC6909InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 id);
|
||||
error ERC6909InsufficientAllowance(address spender, uint256 allowance, uint256 needed, uint256 id);
|
||||
error ERC6909InvalidApprover(address approver);
|
||||
error ERC6909InvalidReceiver(address receiver);
|
||||
error ERC6909InvalidSender(address sender);
|
||||
error ERC6909InvalidSpender(address spender);
|
||||
|
||||
/// @inheritdoc IERC165
|
||||
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
|
||||
return interfaceId == type(IERC6909).interfaceId || super.supportsInterface(interfaceId);
|
||||
}
|
||||
|
||||
/// @inheritdoc IERC6909
|
||||
function balanceOf(address owner, uint256 id) public view virtual override returns (uint256) {
|
||||
return _balances[owner][id];
|
||||
}
|
||||
|
||||
/// @inheritdoc IERC6909
|
||||
function allowance(address owner, address spender, uint256 id) public view virtual override returns (uint256) {
|
||||
return _allowances[owner][spender][id];
|
||||
}
|
||||
|
||||
/// @inheritdoc IERC6909
|
||||
function isOperator(address owner, address spender) public view virtual override returns (bool) {
|
||||
return _operatorApprovals[owner][spender];
|
||||
}
|
||||
|
||||
/// @inheritdoc IERC6909
|
||||
function approve(address spender, uint256 id, uint256 amount) public virtual override returns (bool) {
|
||||
_approve(_msgSender(), spender, id, amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @inheritdoc IERC6909
|
||||
function setOperator(address spender, bool approved) public virtual override returns (bool) {
|
||||
_setOperator(_msgSender(), spender, approved);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @inheritdoc IERC6909
|
||||
function transfer(address receiver, uint256 id, uint256 amount) public virtual override returns (bool) {
|
||||
_transfer(_msgSender(), receiver, id, amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @inheritdoc IERC6909
|
||||
function transferFrom(
|
||||
address sender,
|
||||
address receiver,
|
||||
uint256 id,
|
||||
uint256 amount
|
||||
) public virtual override returns (bool) {
|
||||
address caller = _msgSender();
|
||||
if (sender != caller && !isOperator(sender, caller)) {
|
||||
_spendAllowance(sender, caller, id, amount);
|
||||
}
|
||||
_transfer(sender, receiver, id, amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Creates `amount` of token `id` and assigns them to `account`, by transferring it from address(0).
|
||||
* Relies on the `_update` mechanism
|
||||
*
|
||||
* Emits a {Transfer} event with `from` set to the zero address.
|
||||
*
|
||||
* NOTE: This function is not virtual, {_update} should be overridden instead.
|
||||
*/
|
||||
function _mint(address to, uint256 id, uint256 amount) internal {
|
||||
if (to == address(0)) {
|
||||
revert ERC6909InvalidReceiver(address(0));
|
||||
}
|
||||
_update(address(0), to, id, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Moves `amount` of token `id` from `from` to `to` without checking for approvals.
|
||||
*
|
||||
* This internal function is equivalent to {transfer}, and can be used to
|
||||
* e.g. implement automatic token fees, slashing mechanisms, etc.
|
||||
*
|
||||
* Emits a {Transfer} event.
|
||||
*
|
||||
* NOTE: This function is not virtual, {_update} should be overridden instead.
|
||||
*/
|
||||
function _transfer(address from, address to, uint256 id, uint256 amount) internal {
|
||||
if (from == address(0)) {
|
||||
revert ERC6909InvalidSender(address(0));
|
||||
}
|
||||
if (to == address(0)) {
|
||||
revert ERC6909InvalidReceiver(address(0));
|
||||
}
|
||||
_update(from, to, id, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Destroys a `amount` of token `id` from `account`.
|
||||
* Relies on the `_update` mechanism.
|
||||
*
|
||||
* Emits a {Transfer} event with `to` set to the zero address.
|
||||
*
|
||||
* NOTE: This function is not virtual, {_update} should be overridden instead
|
||||
*/
|
||||
function _burn(address from, uint256 id, uint256 amount) internal {
|
||||
if (from == address(0)) {
|
||||
revert ERC6909InvalidSender(address(0));
|
||||
}
|
||||
_update(from, address(0), id, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Transfers `amount` of token `id` from `from` to `to`, or alternatively mints (or burns) if `from`
|
||||
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
|
||||
* this function.
|
||||
*
|
||||
* Emits a {Transfer} event.
|
||||
*/
|
||||
function _update(address from, address to, uint256 id, uint256 amount) internal virtual {
|
||||
address caller = _msgSender();
|
||||
|
||||
if (from != address(0)) {
|
||||
uint256 fromBalance = _balances[from][id];
|
||||
if (fromBalance < amount) {
|
||||
revert ERC6909InsufficientBalance(from, fromBalance, amount, id);
|
||||
}
|
||||
unchecked {
|
||||
// Overflow not possible: amount <= fromBalance.
|
||||
_balances[from][id] = fromBalance - amount;
|
||||
}
|
||||
}
|
||||
if (to != address(0)) {
|
||||
_balances[to][id] += amount;
|
||||
}
|
||||
|
||||
emit Transfer(caller, from, to, id, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Sets `amount` as the allowance of `spender` over the `owner`'s `id` tokens.
|
||||
*
|
||||
* This internal function is equivalent to `approve`, and can be used to e.g. set automatic allowances for certain
|
||||
* subsystems, etc.
|
||||
*
|
||||
* Emits an {Approval} event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - `owner` cannot be the zero address.
|
||||
* - `spender` cannot be the zero address.
|
||||
*/
|
||||
function _approve(address owner, address spender, uint256 id, uint256 amount) internal virtual {
|
||||
if (owner == address(0)) {
|
||||
revert ERC6909InvalidApprover(address(0));
|
||||
}
|
||||
if (spender == address(0)) {
|
||||
revert ERC6909InvalidSpender(address(0));
|
||||
}
|
||||
_allowances[owner][spender][id] = amount;
|
||||
emit Approval(owner, spender, id, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Approve `spender` to operate on all of `owner`'s tokens
|
||||
*
|
||||
* This internal function is equivalent to `setOperator`, and can be used to e.g. set automatic allowances for
|
||||
* certain subsystems, etc.
|
||||
*
|
||||
* Emits an {OperatorSet} event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - `owner` cannot be the zero address.
|
||||
* - `spender` cannot be the zero address.
|
||||
*/
|
||||
function _setOperator(address owner, address spender, bool approved) internal virtual {
|
||||
if (owner == address(0)) {
|
||||
revert ERC6909InvalidApprover(address(0));
|
||||
}
|
||||
if (spender == address(0)) {
|
||||
revert ERC6909InvalidSpender(address(0));
|
||||
}
|
||||
_operatorApprovals[owner][spender] = approved;
|
||||
emit OperatorSet(owner, spender, approved);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Updates `owner`'s allowance for `spender` based on spent `amount`.
|
||||
*
|
||||
* Does not update the allowance value in case of infinite allowance.
|
||||
* Revert if not enough allowance is available.
|
||||
*
|
||||
* Does not emit an {Approval} event.
|
||||
*/
|
||||
function _spendAllowance(address owner, address spender, uint256 id, uint256 amount) internal virtual {
|
||||
uint256 currentAllowance = allowance(owner, spender, id);
|
||||
if (currentAllowance < type(uint256).max) {
|
||||
if (currentAllowance < amount) {
|
||||
revert ERC6909InsufficientAllowance(spender, currentAllowance, amount, id);
|
||||
}
|
||||
unchecked {
|
||||
_allowances[owner][spender][id] = currentAllowance - amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {ERC6909} from "../draft-ERC6909.sol";
|
||||
import {IERC6909ContentURI} from "../../../interfaces/draft-IERC6909.sol";
|
||||
|
||||
/**
|
||||
* @dev Implementation of the Content URI extension defined in ERC6909.
|
||||
*/
|
||||
contract ERC6909ContentURI is ERC6909, IERC6909ContentURI {
|
||||
string private _contractURI;
|
||||
mapping(uint256 id => string) private _tokenURIs;
|
||||
|
||||
/// @dev Event emitted when the contract URI is changed. See https://eips.ethereum.org/EIPS/eip-7572[ERC-7572] for details.
|
||||
event ContractURIUpdated();
|
||||
|
||||
/// @dev See {IERC1155-URI}
|
||||
event URI(string value, uint256 indexed id);
|
||||
|
||||
/// @inheritdoc IERC6909ContentURI
|
||||
function contractURI() public view virtual override returns (string memory) {
|
||||
return _contractURI;
|
||||
}
|
||||
|
||||
/// @inheritdoc IERC6909ContentURI
|
||||
function tokenURI(uint256 id) public view virtual override returns (string memory) {
|
||||
return _tokenURIs[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Sets the {contractURI} for the contract.
|
||||
*
|
||||
* Emits a {ContractURIUpdated} event.
|
||||
*/
|
||||
function _setContractURI(string memory newContractURI) internal virtual {
|
||||
_contractURI = newContractURI;
|
||||
|
||||
emit ContractURIUpdated();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Sets the {tokenURI} for a given token of type `id`.
|
||||
*
|
||||
* Emits a {URI} event.
|
||||
*/
|
||||
function _setTokenURI(uint256 id, string memory newTokenURI) internal virtual {
|
||||
_tokenURIs[id] = newTokenURI;
|
||||
|
||||
emit URI(newTokenURI, id);
|
||||
}
|
||||
}
|
||||
76
contracts/token/ERC6909/extensions/draft-ERC6909Metadata.sol
Normal file
76
contracts/token/ERC6909/extensions/draft-ERC6909Metadata.sol
Normal file
@ -0,0 +1,76 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {ERC6909} from "../draft-ERC6909.sol";
|
||||
import {IERC6909Metadata} from "../../../interfaces/draft-IERC6909.sol";
|
||||
|
||||
/**
|
||||
* @dev Implementation of the Metadata extension defined in ERC6909. Exposes the name, symbol, and decimals of each token id.
|
||||
*/
|
||||
contract ERC6909Metadata is ERC6909, IERC6909Metadata {
|
||||
struct TokenMetadata {
|
||||
string name;
|
||||
string symbol;
|
||||
uint8 decimals;
|
||||
}
|
||||
|
||||
mapping(uint256 id => TokenMetadata) private _tokenMetadata;
|
||||
|
||||
/// @dev The name of the token of type `id` was updated to `newName`.
|
||||
event ERC6909NameUpdated(uint256 indexed id, string newName);
|
||||
|
||||
/// @dev The symbol for the token of type `id` was updated to `newSymbol`.
|
||||
event ERC6909SymbolUpdated(uint256 indexed id, string newSymbol);
|
||||
|
||||
/// @dev The decimals value for token of type `id` was updated to `newDecimals`.
|
||||
event ERC6909DecimalsUpdated(uint256 indexed id, uint8 newDecimals);
|
||||
|
||||
/// @inheritdoc IERC6909Metadata
|
||||
function name(uint256 id) public view virtual override returns (string memory) {
|
||||
return _tokenMetadata[id].name;
|
||||
}
|
||||
|
||||
/// @inheritdoc IERC6909Metadata
|
||||
function symbol(uint256 id) public view virtual override returns (string memory) {
|
||||
return _tokenMetadata[id].symbol;
|
||||
}
|
||||
|
||||
/// @inheritdoc IERC6909Metadata
|
||||
function decimals(uint256 id) public view virtual override returns (uint8) {
|
||||
return _tokenMetadata[id].decimals;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Sets the `name` for a given token of type `id`.
|
||||
*
|
||||
* Emits an {ERC6909NameUpdated} event.
|
||||
*/
|
||||
function _setName(uint256 id, string memory newName) internal virtual {
|
||||
_tokenMetadata[id].name = newName;
|
||||
|
||||
emit ERC6909NameUpdated(id, newName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Sets the `symbol` for a given token of type `id`.
|
||||
*
|
||||
* Emits an {ERC6909SymbolUpdated} event.
|
||||
*/
|
||||
function _setSymbol(uint256 id, string memory newSymbol) internal virtual {
|
||||
_tokenMetadata[id].symbol = newSymbol;
|
||||
|
||||
emit ERC6909SymbolUpdated(id, newSymbol);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Sets the `decimals` for a given token of type `id`.
|
||||
*
|
||||
* Emits an {ERC6909DecimalsUpdated} event.
|
||||
*/
|
||||
function _setDecimals(uint256 id, uint8 newDecimals) internal virtual {
|
||||
_tokenMetadata[id].decimals = newDecimals;
|
||||
|
||||
emit ERC6909DecimalsUpdated(id, newDecimals);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {ERC6909} from "../draft-ERC6909.sol";
|
||||
import {IERC6909TokenSupply} from "../../../interfaces/draft-IERC6909.sol";
|
||||
|
||||
/**
|
||||
* @dev Implementation of the Token Supply extension defined in ERC6909.
|
||||
* Tracks the total supply of each token id individually.
|
||||
*/
|
||||
contract ERC6909TokenSupply is ERC6909, IERC6909TokenSupply {
|
||||
mapping(uint256 id => uint256) private _totalSupplies;
|
||||
|
||||
/// @inheritdoc IERC6909TokenSupply
|
||||
function totalSupply(uint256 id) public view virtual override returns (uint256) {
|
||||
return _totalSupplies[id];
|
||||
}
|
||||
|
||||
/// @dev Override the `_update` function to update the total supply of each token id as necessary.
|
||||
function _update(address from, address to, uint256 id, uint256 amount) internal virtual override {
|
||||
super._update(from, to, id, amount);
|
||||
|
||||
if (from == address(0)) {
|
||||
_totalSupplies[id] += amount;
|
||||
}
|
||||
if (to == address(0)) {
|
||||
unchecked {
|
||||
// amount <= _balances[id][from] <= _totalSupplies[id]
|
||||
_totalSupplies[id] -= amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user