DCIPs/assets/eip-5700/erc1155/ERC1155Bindable.sol

240 lines
7.1 KiB
Solidity

// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.16;
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
import {ERC1155} from "./ERC1155.sol";
import {IERC1155Bindable} from "../interfaces/IERC1155Bindable.sol";
import {IERC1155Binder} from "../interfaces/IERC1155Binder.sol";
/// @title ERC-1155 Bindable Reference Implementation.
/// @dev Only supports the "delegated" binding mode.
contract ERC1155Bindable is ERC1155, IERC1155Bindable {
/// @notice Tracks the bound balance of an asset for a specific token type.
mapping(address => mapping(uint256 => mapping(uint256 => uint256))) public boundBalanceOf;
/// @dev EIP-165 identifiers for all supported interfaces.
bytes4 private constant _ERC165_INTERFACE_ID = 0x01ffc9a7;
bytes4 private constant _ERC1155_BINDER_INTERFACE_ID = 0x2ac2d2bc;
bytes4 private constant _ERC1155_BINDABLE_INTERFACE_ID = 0xd92c3ff0;
/// @inheritdoc IERC1155Bindable
function boundBalanceOfBatch(
address bindAddress,
uint256[] calldata bindIds,
uint256[] calldata tokenIds
) public view returns (uint256[] memory balances) {
if (bindIds.length != tokenIds.length) {
revert ArityMismatch();
}
balances = new uint256[](bindIds.length);
unchecked {
for (uint256 i = 0; i < bindIds.length; ++i) {
balances[i] = boundBalanceOf[bindAddress][bindIds[i]][tokenIds[i]];
}
}
}
/// @inheritdoc IERC1155Bindable
function bind(
address from,
address to,
uint256 tokenId,
uint256 amount,
uint256 bindId,
address bindAddress,
bytes calldata data
) public {
if (msg.sender != from && !isApprovedForAll[from][msg.sender]) {
revert SenderUnauthorized();
}
IERC1155Binder binder = IERC1155Binder(bindAddress);
if (to != bindAddress) {
revert BinderInvalid();
}
boundBalanceOf[bindAddress][bindId][tokenId] += amount;
_balanceOf[from][tokenId] -= amount;
_balanceOf[to][tokenId] += amount;
emit Bind(msg.sender, from, bindAddress, tokenId, amount, bindId, bindAddress);
emit TransferSingle(msg.sender, from, bindAddress, tokenId, amount);
if (
binder.onERC1155Bind(msg.sender, from, to, tokenId, amount, bindId, data)
!=
IERC1155Binder.onERC1155Bind.selector
) {
revert BindInvalid();
}
}
/// @inheritdoc IERC1155Bindable
function batchBind(
address from,
address to,
uint256[] calldata tokenIds,
uint256[] calldata amounts,
uint256[] calldata bindIds,
address bindAddress,
bytes calldata data
) public {
if (msg.sender != from && !isApprovedForAll[from][msg.sender]) {
revert SenderUnauthorized();
}
IERC1155Binder binder = IERC1155Binder(bindAddress);
if (to != bindAddress) {
revert BinderInvalid();
}
if (tokenIds.length != amounts.length || tokenIds.length != bindIds.length) {
revert ArityMismatch();
}
for (uint256 i = 0; i < tokenIds.length; i++) {
boundBalanceOf[bindAddress][bindIds[i]][tokenIds[i]] += amounts[i];
_balanceOf[from][tokenIds[i]] -= amounts[i];
_balanceOf[to][tokenIds[i]] += amounts[i];
}
emit BindBatch(msg.sender, from, bindAddress, tokenIds, amounts, bindIds, bindAddress);
emit TransferBatch(msg.sender, from, bindAddress, tokenIds, amounts);
if (
binder.onERC1155BatchBind(msg.sender, from, to, tokenIds, amounts, bindIds, data)
!=
IERC1155Binder.onERC1155Bind.selector
) {
revert BindInvalid();
}
}
/// @inheritdoc IERC1155Bindable
function unbind(
address from,
address to,
uint256 tokenId,
uint256 amount,
uint256 bindId,
address bindAddress,
bytes calldata data
) public {
IERC1155Binder binder = IERC1155Binder(bindAddress);
if (
binder.ownerOf(bindId) != from
) {
revert BinderInvalid();
}
if (
msg.sender != from &&
!binder.isApprovedForAll(from, msg.sender)
) {
revert SenderUnauthorized();
}
if (to == address(0)) {
revert ReceiverInvalid();
}
_balanceOf[to][tokenId] += amount;
_balanceOf[from][tokenId] -= amount;
boundBalanceOf[bindAddress][bindId][tokenId] -= amount;
emit Bind(msg.sender, bindAddress, to, tokenId, amount, bindId, bindAddress);
emit TransferSingle(msg.sender, bindAddress, to, tokenId, amount);
if (
binder.onERC1155Unbind(msg.sender, from, to, tokenId, amount, bindId, data)
!=
IERC1155Binder.onERC1155Unbind.selector
) {
revert BindInvalid();
}
if (
to.code.length != 0 &&
IERC1155Receiver(to).onERC1155Received(msg.sender, from, amount, tokenId, "")
!=
IERC1155Receiver.onERC1155Received.selector
) {
revert SafeTransferUnsupported();
}
}
/// @inheritdoc IERC1155Bindable
function batchUnbind(
address from,
address to,
uint256[] calldata tokenIds,
uint256[] calldata amounts,
uint256[] calldata bindIds,
address bindAddress,
bytes calldata data
) public {
IERC1155Binder binder = IERC1155Binder(bindAddress);
if (
msg.sender != from &&
!binder.isApprovedForAll(from, msg.sender)
) {
revert SenderUnauthorized();
}
if (to == address(0)) {
revert ReceiverInvalid();
}
if (tokenIds.length != amounts.length || tokenIds.length != bindIds.length) {
revert ArityMismatch();
}
for (uint256 i = 0; i < tokenIds.length; i++) {
if (binder.ownerOf(bindIds[i]) != from) {
revert BinderInvalid();
}
_balanceOf[to][tokenIds[i]] += amounts[i];
_balanceOf[from][tokenIds[i]] -= amounts[i];
boundBalanceOf[bindAddress][bindIds[i]][tokenIds[i]] -= amounts[i];
}
emit UnbindBatch(msg.sender, from, bindAddress, tokenIds, amounts, bindIds, bindAddress);
emit TransferBatch(msg.sender, from, bindAddress, tokenIds, amounts);
if (
binder.onERC1155BatchUnbind(msg.sender, from, to, tokenIds, amounts, bindIds, data)
!=
IERC1155Binder.onERC1155BatchUnbind.selector
) {
revert BindInvalid();
}
if (
to.code.length != 0 &&
IERC1155Receiver(to).onERC1155BatchReceived(msg.sender, from, tokenIds, amounts, "")
!=
IERC1155Receiver.onERC1155BatchReceived.selector
) {
revert SafeTransferUnsupported();
}
}
function supportsInterface(bytes4 id) public pure override(ERC1155, IERC165) returns (bool) {
return super.supportsInterface(id) || id == _ERC1155_BINDABLE_INTERFACE_ID;
}
}