DCIPs/assets/eip-5700/erc721/ERC721Bindable.sol

211 lines
5.8 KiB
Solidity

// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.16;
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import {ERC721} from "./ERC721.sol";
import {IERC721Bindable} from "../interfaces/IERC721Bindable.sol";
import {IERC721Binder} from "../interfaces/IERC721Binder.sol";
/// @title ERC-721 Bindable Reference Implementation.
/// @dev Supports both "legacy" and "delegated" binding modes.
contract ERC721Bindable is ERC721, IERC721Bindable {
/// @notice Encapsulates a bound asset contract address and identifier.
struct Binder {
address bindAddress;
uint256 bindId;
}
/// @notice Tracks the bound balance of a specific asset.
mapping(address => mapping(uint256 => uint256)) public boundBalanceOf;
/// @notice Tracks bound assets of an NFT.
mapping(uint256 => Binder) internal _bound;
/// @dev EIP-165 identifiers for all supported interfaces.
bytes4 private constant _ERC165_INTERFACE_ID = 0x01ffc9a7;
bytes4 private constant _ERC721_BINDER_INTERFACE_ID = 0x2ac2d2bc;
bytes4 private constant _ERC721_BINDABLE_INTERFACE_ID = 0xd92c3ff0;
/// @inheritdoc IERC721Bindable
function binderOf(uint256 tokenId) public returns (address, uint256) {
Binder memory bound = _bound[tokenId];
return (bound.bindAddress, bound.bindId);
}
/// @inheritdoc IERC721Bindable
function bind(
address from,
address to,
uint256 tokenId,
uint256 bindId,
address bindAddress,
bytes calldata data
) public {
if (_bound[tokenId].bindAddress != address(0)) {
revert BindExistent();
}
if (from != _ownerOf[tokenId]) {
revert OwnerInvalid();
}
IERC721Binder binder = IERC721Binder(bindAddress);
address assetOwner = binder.ownerOf(bindId);
if (to != assetOwner && to != bindAddress) {
revert BinderInvalid();
}
if (
msg.sender != from &&
msg.sender != getApproved[tokenId] &&
!_operatorApprovals[from][msg.sender]
) {
revert SenderUnauthorized();
}
delete getApproved[tokenId];
unchecked {
_balanceOf[from]--;
_balanceOf[bindAddress]++;
_balanceOf[assetOwner]++;
boundBalanceOf[bindAddress][bindId]++;
}
_ownerOf[tokenId] = to;
_bound[tokenId] = Binder(bindAddress, bindId);
emit Bind(msg.sender, from, to, tokenId, bindId, bindAddress);
emit Transfer(from, to, tokenId);
if (
binder.onERC721Bind(msg.sender, from, to, tokenId, bindId, "")
!=
IERC721Binder.onERC721Bind.selector
) {
revert BindInvalid();
}
}
/// @inheritdoc IERC721Bindable
function unbind(
address from,
address to,
uint256 tokenId,
uint256 bindId,
address bindAddress,
bytes calldata data
) public {
Binder memory bound = _bound[tokenId];
if (bound.bindAddress != address(0)) {
revert BindNonexistent();
}
IERC721Binder binder = IERC721Binder(bindAddress);
if (
bound.bindAddress != bindAddress ||
bound.bindId != bindId ||
binder.ownerOf(bindId) != from
) {
revert BinderInvalid();
}
if (
msg.sender != from &&
!binder.isApprovedForAll(from, msg.sender)
) {
revert SenderUnauthorized();
}
if (to == address(0)) {
revert ReceiverInvalid();
}
address delegatedOwner = _ownerOf[tokenId];
delete getApproved[tokenId];
unchecked {
_balanceOf[to]++;
_balanceOf[from]--;
_balanceOf[bindAddress]--;
boundBalanceOf[bindAddress][bindId]--;
}
_ownerOf[tokenId] = to;
delete _bound[tokenId];
emit Bind(msg.sender, from, to, tokenId, bindId, bindAddress);
emit Transfer(delegatedOwner, to, tokenId);
if (
binder.onERC721Unbind(msg.sender, from, to, tokenId, bindId, "")
!=
IERC721Binder.onERC721Unbind.selector
) {
revert BindInvalid();
}
if (
to.code.length != 0 &&
IERC721Receiver(to).onERC721Received(msg.sender, delegatedOwner, tokenId, "")
!=
IERC721Receiver.onERC721Received.selector
) {
revert SafeTransferUnsupported();
}
}
/// @inheritdoc IERC721
function transferFrom(
address from,
address to,
uint256 tokenId
) public override(IERC721, ERC721) {
address bindAddress = _bound[tokenId].bindAddress;
uint256 bindId = _bound[tokenId].bindId;
if (bindAddress == address(0)) {
return super.transferFrom(from, to, tokenId);
}
if (msg.sender != bindAddress) {
revert BindExistent();
}
IERC721Binder binder = IERC721Binder(bindAddress);
if (
binder.ownerOf(bindId) != from
) {
revert BinderInvalid();
}
if (to == address(0)) {
revert ReceiverInvalid();
}
delete getApproved[tokenId];
uint256 bindBal = boundBalanceOf[bindAddress][bindId];
unchecked {
_balanceOf[from] -= bindBal;
_balanceOf[to] += bindBal;
}
emit Transfer(from, to, tokenId);
}
function supportsInterface(bytes4 id) public pure override(ERC721, IERC165) returns (bool) {
return super.supportsInterface(id) || id == _ERC721_BINDABLE_INTERFACE_ID;
}
}