dao/contracts/terminus/ERC1155WithTerminusStorage.sol

567 wiersze
16 KiB
Solidity

// SPDX-License-Identifier: Apache-2.0
/**
* Authors: Moonstream Engineering (engineering@moonstream.to)
* GitHub: https://github.com/bugout-dev/dao
*
* An ERC1155 implementation which uses the Moonstream DAO common storage structure for proxies.
* EIP1155: https://eips.ethereum.org/EIPS/eip-1155
*
* The Moonstream contract is used to delegate calls from an EIP2535 Diamond proxy.
*
* This implementation is adapted from the OpenZeppelin ERC1155 implementation:
* https://github.com/OpenZeppelin/openzeppelin-contracts/tree/6bd6b76d1156e20e45d1016f355d154141c7e5b9/contracts/token/ERC1155
*/
pragma solidity ^0.8.9;
import "@openzeppelin-contracts/contracts/token/ERC1155/IERC1155.sol";
import "@openzeppelin-contracts/contracts/token/ERC1155/IERC1155Receiver.sol";
import "@openzeppelin-contracts/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol";
import "@openzeppelin-contracts/contracts/utils/Address.sol";
import "@openzeppelin-contracts/contracts/utils/Context.sol";
import "@openzeppelin-contracts/contracts/utils/introspection/ERC165.sol";
import "./LibTerminus.sol";
contract ERC1155WithTerminusStorage is
Context,
ERC165,
IERC1155,
IERC1155MetadataURI
{
using Address for address;
constructor() {}
/**
* @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 ||
super.supportsInterface(interfaceId);
}
function uri(uint256 poolID)
public
view
virtual
override
returns (string memory)
{
return LibTerminus.terminusStorage().poolURI[poolID];
}
/**
* @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),
"ERC1155: balance query for the zero address"
);
return LibTerminus.terminusStorage().poolBalances[id][account];
}
/**
* @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,
"ERC1155: accounts and ids length 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 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
LibTerminus.terminusStorage().globalOperatorApprovals[account][
operator
];
}
/**
* @dev See {IERC1155-safeTransferFrom}.
*/
function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) public virtual override {
require(
from == _msgSender() || isApprovedForAll(from, _msgSender()),
"ERC1155: caller is not owner nor approved"
);
_safeTransferFrom(from, to, id, amount, data);
}
/**
* @dev See {IERC1155-safeBatchTransferFrom}.
*/
function safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) public virtual override {
require(
from == _msgSender() || isApprovedForAll(from, _msgSender()),
"ERC1155: transfer caller is not owner nor approved"
);
_safeBatchTransferFrom(from, to, ids, amounts, data);
}
/**
* @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
*
* Emits a {TransferSingle} event.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `from` must have a balance of tokens of type `id` of at least `amount`.
* - 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(to != address(0), "ERC1155: transfer to the zero address");
address operator = _msgSender();
_beforeTokenTransfer(
operator,
from,
to,
_asSingletonArray(id),
_asSingletonArray(amount),
data
);
LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
uint256 fromBalance = ts.poolBalances[id][from];
require(
fromBalance >= amount,
"ERC1155: insufficient balance for transfer"
);
unchecked {
ts.poolBalances[id][from] = fromBalance - amount;
}
ts.poolBalances[id][to] += amount;
emit TransferSingle(operator, from, to, id, amount);
_doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data);
}
/**
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_safeTransferFrom}.
*
* Emits a {TransferBatch} event.
*
* Requirements:
*
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
* acceptance magic value.
*/
function _safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual {
require(
ids.length == amounts.length,
"ERC1155: ids and amounts length mismatch"
);
require(to != address(0), "ERC1155: transfer to the zero address");
address operator = _msgSender();
_beforeTokenTransfer(operator, from, to, ids, amounts, data);
LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
for (uint256 i = 0; i < ids.length; ++i) {
uint256 id = ids[i];
uint256 amount = amounts[i];
uint256 fromBalance = ts.poolBalances[id][from];
require(
fromBalance >= amount,
"ERC1155: insufficient balance for transfer"
);
unchecked {
ts.poolBalances[id][from] = fromBalance - amount;
}
ts.poolBalances[id][to] += amount;
}
emit TransferBatch(operator, from, to, ids, amounts);
_doSafeBatchTransferAcceptanceCheck(
operator,
from,
to,
ids,
amounts,
data
);
}
/**
* @dev Creates `amount` tokens of token type `id`, and assigns them to `to`.
*
* Emits a {TransferSingle} event.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
* acceptance magic value.
*/
function _mint(
address to,
uint256 id,
uint256 amount,
bytes memory data
) internal virtual {
require(to != address(0), "ERC1155: mint to the zero address");
LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
require(
ts.poolSupply[id] + amount <= ts.poolCapacity[id],
"ERC1155WithTerminusStorage: _mint -- Minted tokens would exceed pool capacity"
);
address operator = _msgSender();
_beforeTokenTransfer(
operator,
address(0),
to,
_asSingletonArray(id),
_asSingletonArray(amount),
data
);
ts.poolBalances[id][to] += amount;
emit TransferSingle(operator, address(0), to, id, amount);
_doSafeTransferAcceptanceCheck(
operator,
address(0),
to,
id,
amount,
data
);
}
/**
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}.
*
* Requirements:
*
* - `ids` and `amounts` must have the same length.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
* acceptance magic value.
*/
function _mintBatch(
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual {
require(to != address(0), "ERC1155: mint to the zero address");
require(
ids.length == amounts.length,
"ERC1155: ids and amounts length mismatch"
);
LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
for (uint256 i = 0; i < ids.length; i++) {
require(
ts.poolSupply[ids[i]] + amounts[i] <= ts.poolCapacity[ids[i]],
"ERC1155WithTerminusStorage: _mintBatch -- Minted tokens would exceed pool capacity"
);
}
address operator = _msgSender();
_beforeTokenTransfer(operator, address(0), to, ids, amounts, data);
for (uint256 i = 0; i < ids.length; i++) {
ts.poolBalances[ids[i]][to] += amounts[i];
}
emit TransferBatch(operator, address(0), to, ids, amounts);
_doSafeBatchTransferAcceptanceCheck(
operator,
address(0),
to,
ids,
amounts,
data
);
}
/**
* @dev Destroys `amount` tokens of token type `id` from `from`
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `from` must have at least `amount` tokens of token type `id`.
*/
function _burn(
address from,
uint256 id,
uint256 amount
) internal virtual {
require(from != address(0), "ERC1155: burn from the zero address");
address operator = _msgSender();
_beforeTokenTransfer(
operator,
from,
address(0),
_asSingletonArray(id),
_asSingletonArray(amount),
""
);
LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
uint256 fromBalance = ts.poolBalances[id][from];
require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
unchecked {
ts.poolBalances[id][from] = fromBalance - amount;
}
emit TransferSingle(operator, from, address(0), id, amount);
}
/**
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}.
*
* Requirements:
*
* - `ids` and `amounts` must have the same length.
*/
function _burnBatch(
address from,
uint256[] memory ids,
uint256[] memory amounts
) internal virtual {
require(from != address(0), "ERC1155: burn from the zero address");
require(
ids.length == amounts.length,
"ERC1155: ids and amounts length mismatch"
);
address operator = _msgSender();
_beforeTokenTransfer(operator, from, address(0), ids, amounts, "");
LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
for (uint256 i = 0; i < ids.length; i++) {
uint256 id = ids[i];
uint256 amount = amounts[i];
uint256 fromBalance = ts.poolBalances[id][from];
require(
fromBalance >= amount,
"ERC1155: burn amount exceeds balance"
);
unchecked {
ts.poolBalances[id][from] = fromBalance - amount;
}
}
emit TransferBatch(operator, from, 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");
LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
ts.globalOperatorApprovals[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 `id` and `amount` arrays will be 1.
*
* Calling conditions (for each `id` and `amount` pair):
*
* - 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.
* - `ids` and `amounts` have the same, non-zero length.
*
* 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 {}
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");
}
}
}
function _doSafeBatchTransferAcceptanceCheck(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) private {
if (to.isContract()) {
try
IERC1155Receiver(to).onERC1155BatchReceived(
operator,
from,
ids,
amounts,
data
)
returns (bytes4 response) {
if (
response != IERC1155Receiver.onERC1155BatchReceived.selector
) {
revert("ERC1155: ERC1155Receiver rejected tokens");
}
} catch Error(string memory reason) {
revert(reason);
} catch {
revert("ERC1155: transfer to non ERC1155Receiver implementer");
}
}
}
function _asSingletonArray(uint256 element)
private
pure
returns (uint256[] memory)
{
uint256[] memory array = new uint256[](1);
array[0] = element;
return array;
}
}