240 lines
7.1 KiB
Solidity
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;
|
||
|
}
|
||
|
|
||
|
}
|