DCIPs/assets/eip-5516/ERC5516.sol

820 lines
23 KiB
Solidity
Raw Normal View History

//SPDX-License-Identifier: CC0-1.0
/**
* @notice Reference implementation of the eip-5516 interface.
* Note: this implementation only allows for each user to own only 1 token type for each `id`.
* @author Matias Arazi <matiasarazi@gmail.com> , Lucas Martín Grasso Ramos <lucasgrassoramos@gmail.com>
* See https://github.com/ethereum/EIPs/pull/5516
*
*/
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/utils/Context.sol";
import "./IERC5516.sol";
contract ERC5516 is Context, ERC165, IERC1155, IERC1155MetadataURI, IERC5516 {
using Address for address;
// Used for making each token unique, Maintains ID registry and quantity of tokens minted.
uint256 private nonce;
// Used as the URI for all token types by relying on ID substitution, e.g. https://ipfs.io/ipfs/token.data
string private _uri;
// Mapping from token ID to account balances
mapping(address => mapping(uint256 => bool)) private _balances;
// Mapping from address to mapping id bool that states if address has tokens(under id) awaiting to be claimed
mapping(address => mapping(uint256 => bool)) private _pendings;
// Mapping from account to operator approvals
mapping(address => mapping(address => bool)) private _operatorApprovals;
// Mapping from ID to minter address.
mapping(uint256 => address) private _tokenMinters;
// Mapping from ID to URI.
mapping(uint256 => string) private _tokenURIs;
/**
* @dev Sets base uri for tokens. Preferably "https://ipfs.io/ipfs/"
*/
constructor(string memory uri_) {
_uri = uri_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(ERC165, IERC165)
returns (bool)
{
return
interfaceId == type(IERC1155).interfaceId ||
interfaceId == type(IERC1155MetadataURI).interfaceId ||
interfaceId == type(IERC5516).interfaceId ||
super.supportsInterface(interfaceId);
}
/**
* @dev See {IERC1155MetadataURI-uri}.
*/
function uri(uint256 _id)
external
view
virtual
override
returns (string memory)
{
return string(abi.encodePacked(_uri, _tokenURIs[_id]));
}
/**
* @dev See {IERC1155-balanceOf}.
*
* Requirements:
*
* - `account` cannot be the zero address.
*
*/
function balanceOf(address account, uint256 id)
public
view
virtual
override
returns (uint256)
{
require(account != address(0), "EIP5516: Address zero error");
if (_balances[account][id]) {
return 1;
} else {
return 0;
}
}
/**
* @dev See {IERC1155-balanceOfBatch}.
*
* Requirements:
*
* - `accounts` and `ids` must have the same length.
*
*/
function balanceOfBatch(address[] memory accounts, uint256[] memory ids)
public
view
virtual
override
returns (uint256[] memory)
{
require(
accounts.length == ids.length,
"EIP5516: Array lengths mismatch"
);
uint256[] memory batchBalances = new uint256[](accounts.length);
for (uint256 i = 0; i < accounts.length; ++i) {
batchBalances[i] = balanceOf(accounts[i], ids[i]);
}
return batchBalances;
}
/**
* @dev Get tokens owned by a given address
*
* Requirements:
*
* - `account` cannot be the zero address.
*
*/
function tokensFrom(address account)
public
view
virtual
override
returns (uint256[] memory)
{
require(account != address(0), "EIP5516: Address zero error");
uint256 _tokenCount = 0;
for (uint256 i = 1; i <= nonce; ) {
if (_balances[account][i]) {
unchecked {
++_tokenCount;
}
}
unchecked {
++i;
}
}
uint256[] memory _ownedTokens = new uint256[](_tokenCount);
for (uint256 i = 1; i <= nonce; ) {
if (_balances[account][i]) {
_ownedTokens[--_tokenCount] = i;
}
unchecked {
++i;
}
}
return _ownedTokens;
}
/**
* @dev Get tokens marked as _pendings of a given address
*
* Requirements:
*
* - `account` cannot be the zero address.
*
*/
function pendingFrom(address account)
public
view
virtual
override
returns (uint256[] memory)
{
require(account != address(0), "EIP5516: Address zero error");
uint256 _tokenCount = 0;
for (uint256 i = 1; i <= nonce; ) {
if (_pendings[account][i]) {
++_tokenCount;
}
unchecked {
++i;
}
}
uint256[] memory _pendingTokens = new uint256[](_tokenCount);
for (uint256 i = 1; i <= nonce; ) {
if (_pendings[account][i]) {
_pendingTokens[--_tokenCount] = i;
}
unchecked {
++i;
}
}
return _pendingTokens;
}
/**
* @dev See {IERC1155-setApprovalForAll}.
*/
function setApprovalForAll(address operator, bool approved)
public
virtual
override
{
_setApprovalForAll(_msgSender(), operator, approved);
}
/**
* @dev See {IERC1155-isApprovedForAll}.
*/
function isApprovedForAll(address account, address operator)
public
view
virtual
override
returns (bool)
{
return _operatorApprovals[account][operator];
}
/**
* @dev mints(creates) a token
*/
function _mint(address account, string memory data) internal virtual {
unchecked {
++nonce;
}
address operator = _msgSender();
uint256[] memory ids = _asSingletonArray(nonce);
uint256[] memory amounts = _asSingletonArray(1);
bytes memory _bData = bytes(data);
_beforeTokenTransfer(
operator,
address(0),
operator,
ids,
amounts,
_bData
);
_tokenURIs[nonce] = data;
_tokenMinters[nonce] = account;
emit TransferSingle(operator, address(0), operator, nonce, 1);
_afterTokenTransfer(
operator,
address(0),
operator,
ids,
amounts,
_bData
);
}
/**
* @dev See {IERC1155-safeTransferFrom}.
*
* Requirements:
*
* - `from` must be the creator(minter) of `id` or must have allowed _msgSender() as an operator.
*
*/
function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) public virtual override {
require(amount == 1, "EIP5516: Can only transfer one token");
require(
_msgSender() == _tokenMinters[id] ||
isApprovedForAll(_tokenMinters[id], _msgSender()),
"EIP5516: Unauthorized"
);
_safeTransferFrom(from, to, id, amount, data);
}
/**
* @dev See {eip-5516-batchTransfer}
*
* Requirements:
*
* - 'from' must be the creator(minter) of `id` or must have allowed _msgSender() as an operator.
*
*/
function batchTransfer(
address from,
address[] memory to,
uint256 id,
uint256 amount,
bytes memory data
) external virtual override {
require(amount == 1, "EIP5516: Can only transfer one token");
require(
_msgSender() == _tokenMinters[id] ||
isApprovedForAll(_tokenMinters[id], _msgSender()),
"EIP5516: Unauthorized"
);
_batchTransfer(from, to, id, amount, data);
}
/**
* @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
*
* Emits a {TransferSingle} event.
*
* Requirements:
*
* - `from` must be the creator(minter) of the token under `id`.
* - `to` must be non-zero.
* - `to` must have the token `id` marked as _pendings.
* - `to` must not own a token type under `id`.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
* acceptance magic value.
*
*/
function _safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) internal virtual {
require(from != address(0), "EIP5516: Address zero error");
require(
_pendings[to][id] == false && _balances[to][id] == false,
"EIP5516: Already Assignee"
);
address operator = _msgSender();
uint256[] memory ids = _asSingletonArray(id);
uint256[] memory amounts = _asSingletonArray(amount);
_beforeTokenTransfer(operator, from, to, ids, amounts, data);
_pendings[to][id] = true;
emit TransferSingle(operator, from, to, id, amount);
_afterTokenTransfer(operator, from, to, ids, amounts, data);
_doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data);
}
/**
* Transfers `id` token from `from` to every address at `to[]`.
*
* Requirements:
* - See {eip-5516-safeMultiTransfer}.
*
*/
function _batchTransfer(
address from,
address[] memory to,
uint256 id,
uint256 amount,
bytes memory data
) internal virtual {
address operator = _msgSender();
_beforeBatchedTokenTransfer(operator, from, to, id, data);
for (uint256 i = 0; i < to.length; ) {
address _to = to[i];
require(_to != address(0), "EIP5516: Address zero error");
require(
_pendings[_to][id] == false && _balances[_to][id] == false,
"EIP5516: Already Assignee"
);
_pendings[_to][id] = true;
unchecked {
++i;
}
}
emit TransferMulti(operator, from, to, amount, id);
_beforeBatchedTokenTransfer(operator, from, to, id, data);
}
/**
* @dev See {eip-5516-claimOrReject}
*
* If action == true: Claims pending token under `id`.
* Else, rejects pending token under `id`.
*
*/
function claimOrReject(
address account,
uint256 id,
bool action
) external virtual override {
require(_msgSender() == account, "EIP5516: Unauthorized");
_claimOrReject(account, id, action);
}
/**
* @dev See {eip-5516-claimOrReject}
*
* For each `id` - `action` pair:
*
* If action == true: Claims pending token under `id`.
* Else, rejects pending token under `id`.
*
*/
function claimOrRejectBatch(
address account,
uint256[] memory ids,
bool[] memory actions
) external virtual override {
require(
ids.length == actions.length,
"EIP5516: Array lengths mismatch"
);
require(_msgSender() == account, "EIP5516: Unauthorized");
_claimOrRejectBatch(account, ids, actions);
}
/**
* @dev Claims or Reject pending token under `_id` from address `_account`.
*
* Requirements:
*
* - `account` cannot be the zero address.
* - `account` must have a _pendings token under `id` at the moment of call.
* - `account` mUST not own a token under `id` at the moment of call.
*
* Emits a {TokenClaimed} event.
*
*/
function _claimOrReject(
address account,
uint256 id,
bool action
) internal virtual {
require(
_pendings[account][id] == true && _balances[account][id] == false,
"EIP5516: Not claimable"
);
address operator = _msgSender();
bool[] memory actions = new bool[](1);
actions[0] = action;
uint256[] memory ids = _asSingletonArray(id);
_beforeTokenClaim(operator, account, actions, ids);
_balances[account][id] = action;
_pendings[account][id] = false;
delete _pendings[account][id];
emit TokenClaimed(operator, account, actions, ids);
_afterTokenClaim(operator, account, actions, ids);
}
/**
* @dev Claims or Reject _pendings `_id` from address `_account`.
*
* For each `id`-`action` pair:
*
* Requirements:
* - `account` cannot be the zero address.
* - `account` must have a pending token under `id` at the moment of call.
* - `account` must not own a token under `id` at the moment of call.
*
* Emits a {TokenClaimed} event.
*
*/
function _claimOrRejectBatch(
address account,
uint256[] memory ids,
bool[] memory actions
) internal virtual {
uint256 totalIds = ids.length;
address operator = _msgSender();
_beforeTokenClaim(operator, account, actions, ids);
for (uint256 i = 0; i < totalIds; ) {
uint256 id = ids[i];
require(
_pendings[account][id] == true &&
_balances[account][id] == false,
"EIP5516: Not claimable"
);
_balances[account][id] = actions[i];
_pendings[account][id] = false;
delete _pendings[account][id];
unchecked {
++i;
}
}
emit TokenClaimed(operator, account, actions, ids);
_afterTokenClaim(operator, account, actions, ids);
}
/**
* @dev Destroys `id` token from `account`
*
* Emits a {TransferSingle} event with `to` set to the zero address.
*
* Requirements:
*
* - `account` must own a token under `id`.
*
*/
function _burn(address account, uint256 id) internal virtual {
require(_balances[account][id] == true, "EIP5516: Unauthorized");
address operator = _msgSender();
uint256[] memory ids = _asSingletonArray(id);
uint256[] memory amounts = _asSingletonArray(1);
_beforeTokenTransfer(operator, account, address(0), ids, amounts, "");
delete _balances[account][id];
emit TransferSingle(operator, account, address(0), id, 1);
_beforeTokenTransfer(operator, account, address(0), ids, amounts, "");
}
/**
* @dev Destroys all tokens under `ids` from `account`
*
* Emits a {TransferBatch} event with `to` set to the zero address.
*
* Requirements:
*
* - `account` must own all tokens under `ids`.
*
*/
function _burnBatch(address account, uint256[] memory ids)
internal
virtual
{
uint256 totalIds = ids.length;
address operator = _msgSender();
uint256[] memory amounts = _asSingletonArray(totalIds);
uint256[] memory values = _asSingletonArray(0);
_beforeTokenTransfer(operator, account, address(0), ids, amounts, "");
for (uint256 i = 0; i < totalIds; ) {
uint256 id = ids[i];
require(_balances[account][id] == true, "EIP5516: Unauthorized");
delete _balances[account][id];
unchecked {
++i;
}
}
emit TransferBatch(operator, account, address(0), ids, values);
_afterTokenTransfer(operator, account, address(0), ids, amounts, "");
}
/**
* @dev Approve `operator` to operate on all of `owner` tokens
*
* Emits a {ApprovalForAll} event.
*
*/
function _setApprovalForAll(
address owner,
address operator,
bool approved
) internal virtual {
require(owner != operator, "ERC1155: setting approval status for self");
_operatorApprovals[owner][operator] = approved;
emit ApprovalForAll(owner, operator, approved);
}
/**
* @dev Hook that is called before any token transfer. This includes minting
* and burning, as well as batched variants.
*
* The same hook is called on both single and batched variants. For single
* transfers, the length of the `ids` and `amounts` arrays will be 1.
*
* Calling conditions (for each `id` and `amount` pair):
*
* - `amount` will always be and must be equal to 1.
* - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* of token type `id` will be transferred to `to`.
* - When `from` is zero, `amount` tokens of token type `id` will be minted
* for `to`.
* - When `to` is zero, `amount` of ``from``'s tokens of token type `id`
* will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual {}
/**
* @dev Hook that is called after any token transfer. This includes minting
* and burning, as well as batched variants.
*
* The same hook is called on both single and batched variants. For single
* transfers, the length of the `id` and `amount` arrays will be 1.
*
* Calling conditions (for each `id` and `amount` pair):
*
* - `amount` will always be and must be equal to 1.
* - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* of token type `id` will be transferred to `to`.
* - When `from` is zero, `amount` tokens of token type `id` will be minted
* for `to`.
* - When `to` is zero, `amount` of ``from``'s tokens of token type `id`
* will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual {}
/**
* @dev Hook that is called before any batched token transfer. This includes minting
* and burning, as well as batched variants.
*
* The same hook is called on both single and batched variants. For single
* transfers, the length of the `id` and `amount` arrays will be 1.
*
* Calling conditions (for each `id` and `amount` pair):
*
* - `amount` will always be and must be equal to 1.
* - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* of token type `id` will be transferred to `to`.
* - When `from` is zero, `amount` tokens of token type `id` will be minted
* for `to`.
* - When `to` is zero, `amount` of ``from``'s tokens of token type `id`
* will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeBatchedTokenTransfer(
address operator,
address from,
address[] memory to,
uint256 id,
bytes memory data
) internal virtual {}
/**
* @dev Hook that is called after any batched token transfer. This includes minting
* and burning, as well as batched variants.
*
* The same hook is called on both single and batched variants. For single
* transfers, the length of the `id` and `amount` arrays will be 1.
*
* Calling conditions (for each `id` and `amount` pair):
*
* - `amount` will always be and must be equal to 1.
* - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* of token type `id` will be transferred to `to`.
* - When `from` is zero, `amount` tokens of token type `id` will be minted
* for `to`.
* - When `to` is zero, `amount` of ``from``'s tokens of token type `id`
* will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterBatchedTokenTransfer(
address operator,
address from,
address[] memory to,
uint256 id,
bytes memory data
) internal virtual {}
/**
* @dev Hook that is called before any token claim.
+
* Calling conditions (for each `action` and `id` pair):
*
* - A token under `id` must exist.
* - When `action` is non-zero, a token under `id` will now be claimed and owned by`operator`.
* - When `action` is false, a token under `id` will now be rejected.
*
*/
function _beforeTokenClaim(
address operator,
address account,
bool[] memory actions,
uint256[] memory ids
) internal virtual {}
/**
* @dev Hook that is called after any token claim.
+
* Calling conditions (for each `action` and `id` pair):
*
* - A token under `id` must exist.
* - When `action` is non-zero, a token under `id` is now owned by`operator`.
* - When `action` is false, a token under `id` was rejected.
*
*/
function _afterTokenClaim(
address operator,
address account,
bool[] memory actions,
uint256[] memory ids
) internal virtual {}
function _asSingletonArray(uint256 element)
private
pure
returns (uint256[] memory)
{
uint256[] memory array = new uint256[](1);
array[0] = element;
return array;
}
/**
* @dev see {ERC1155-_doSafeTransferAcceptanceCheck, IERC1155Receivable}
*/
function _doSafeTransferAcceptanceCheck(
address operator,
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) private {
if (to.isContract()) {
try
IERC1155Receiver(to).onERC1155Received(
operator,
from,
id,
amount,
data
)
returns (bytes4 response) {
if (response != IERC1155Receiver.onERC1155Received.selector) {
revert("ERC1155: ERC1155Receiver rejected tokens");
}
} catch Error(string memory reason) {
revert(reason);
} catch {
revert("ERC1155: transfer to non-ERC1155Receiver implementer");
}
}
}
/**
* @dev Unused/Deprecated function
* @dev See {IERC1155-safeBatchTransferFrom}.
*/
function safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) public virtual override {}
}