Nonfungible ERC1155 implementation

pull/4684/head
Edward Grech 1 year ago
parent 94697be8a3
commit 954ebe66c3
  1. 112
      contracts/token/ERC1155/extensions/ERC1155Nonfungible.sol

@ -0,0 +1,112 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {ERC1155} from "../ERC1155.sol";
import {Arrays} from "../../../utils/Arrays.sol";
/**
* @dev Indicates that the operator is trying to mint a token that already exists.
* @param id Identifier of token being minted
* @param owner Current token owner
*/
error ERC1155NonfungibleDuplicate(uint256 id, address owner);
/**
* @dev Indicates that the operator is trying mint/transfer/burn an amount > 1 of a token
*/
error ERC1155NonfungibleInvalidAmount(uint256 id, uint256 amount);
/**
* @dev For tokens that are nonfungible but prefer to use {ERC1155} rather than {ERC721}.
*
* {ERC1155Nonfungible} takes advantage of nonfungibility constraint to replace data model
* tracking multiple account balances per token with with a more gas-efficient data model
* tracking unique ownership per token.
*
* Moreover {ERC1155Nonfungible} makes it possible to query the owner of a specific token via {ERC1155Nonfungible-ownerOf},
* similar to {ERC721-ownerOf}, but differs from {ERC721-ownerOf} in that querying the owner of an inexistent token
* will not revert but will return `address(0)`.
*/
abstract contract ERC1155Nonfungible is ERC1155 {
using Arrays for uint256[];
using Arrays for address[];
mapping(uint256 id => address) private _owners;
/**
* @dev Returns the owner of the token `id`. Does NOT revert if token doesn't exist.
*
* {ERC1155Nonfungible-ownerOf} and {ERC1155Nonfungible-_setOwnerOf} may be overridden in tandem,
* e.g. to store more token information along with the owner
*/
function ownerOf(uint256 id) public view virtual returns (address) {
return _owners[id];
}
/**
* @dev {ERC1155Nonfungible-ownerOf} and {ERC1155Nonfungible-_setOwnerOf} may be overridden in tandem,
* e.g. to store more token information along with the owner
*/
function _setOwnerOf(uint256 id, address owner) internal virtual {
_owners[id] = owner;
}
/**
* @dev Replaces {ERC1155-balanceOf} implementation entirely.
*/
function balanceOf(address account, uint256 id) public view virtual override returns (uint256) {
return account != address(0) && ownerOf(id) == account ? 1 : 0;
}
/**
* @dev Replaces {ERC1155-_update} implementation entirely.
*/
function _update(
address from,
address to,
uint256[] memory ids,
uint256[] memory values
) internal virtual override {
if (ids.length != values.length) {
revert ERC1155InvalidArrayLength(ids.length, values.length);
}
address operator = _msgSender();
for (uint256 i = 0; i < ids.length; ++i) {
uint256 id = ids.unsafeMemoryAccess(i);
uint256 value = values.unsafeMemoryAccess(i);
if (value == 1) {
address currOwner = ownerOf(id);
// Could be written more compactly, but clearer if mint tackled separately from transfer/burn
if (from == address(0)) {
if (currOwner == address(0)) {
_setOwnerOf(id, to);
} else {
revert ERC1155NonfungibleDuplicate(id, currOwner);
}
} else {
if (from == currOwner) {
_setOwnerOf(id, to);
} else {
revert ERC1155InsufficientBalance({sender: from, balance: 0, needed: 1, tokenId: id});
}
}
} else if (value == 0) {
// ERC-1155 allows zero-value transfers
} else {
revert ERC1155NonfungibleInvalidAmount(id, value);
}
}
if (ids.length == 1) {
uint256 id = ids.unsafeMemoryAccess(0);
uint256 value = values.unsafeMemoryAccess(0);
emit TransferSingle(operator, from, to, id, value);
} else {
emit TransferBatch(operator, from, to, ids, values);
}
}
}
Loading…
Cancel
Save