136 lines
4.2 KiB
Solidity
136 lines
4.2 KiB
Solidity
|
// SPDX-License-Identifier: CC0-1.0
|
||
|
pragma solidity ^0.8.16;
|
||
|
|
||
|
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
||
|
|
||
|
import {ERC721} from "./ERC721.sol";
|
||
|
import {IERC721Bindable} from "../interfaces/IERC721Bindable.sol";
|
||
|
import {IERC721Binder} from "../interfaces/IERC721Binder.sol";
|
||
|
|
||
|
/// @title ERC-721 Binder Reference Implementation
|
||
|
contract ERC721Binder is IERC721Binder {
|
||
|
|
||
|
struct Bindable {
|
||
|
address tokenAddress;
|
||
|
uint256 tokenId;
|
||
|
}
|
||
|
|
||
|
/// @notice Checks for an owner if an address is an authorized operator.
|
||
|
mapping(address => mapping(address => bool)) public _isApprovedForAll;
|
||
|
|
||
|
/// @notice Tracks ownership of bound assets.
|
||
|
mapping(uint256 => address) _ownerOf;
|
||
|
|
||
|
/// @notice Maps an asset to a list of all bound bindables.
|
||
|
mapping(uint256 => Bindable[]) _boundTokens;
|
||
|
|
||
|
/// @notice Maps a token address and identifier to the bound tokens index.
|
||
|
mapping(address => mapping(uint256 => uint256)) _boundIndexes;
|
||
|
|
||
|
/// @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 IERC721Binder
|
||
|
function isApprovedForAll(address owner, address operator) external view override returns (bool) {
|
||
|
return _isApprovedForAll[owner][operator];
|
||
|
}
|
||
|
|
||
|
/// @inheritdoc IERC721Binder
|
||
|
function ownerOf(uint256 id) public view returns (address) {
|
||
|
return _ownerOf[id];
|
||
|
}
|
||
|
|
||
|
/// @inheritdoc IERC721Binder
|
||
|
function onERC721Bind(
|
||
|
address operator,
|
||
|
address from,
|
||
|
address to,
|
||
|
uint256 tokenId,
|
||
|
uint256 bindId,
|
||
|
bytes calldata data
|
||
|
) public returns (bytes4) {
|
||
|
if (_ownerOf[bindId] != to) {
|
||
|
revert OwnerInvalid();
|
||
|
}
|
||
|
|
||
|
if (_boundIndexes[msg.sender][tokenId] != 0) {
|
||
|
revert BindExistent();
|
||
|
}
|
||
|
|
||
|
if (!IERC721Bindable(msg.sender).supportsInterface(_ERC721_BINDABLE_INTERFACE_ID)) {
|
||
|
revert BindInvalid();
|
||
|
}
|
||
|
|
||
|
_boundIndexes[msg.sender][tokenId] = _boundTokens[bindId].length;
|
||
|
_boundTokens[bindId].push(Bindable(msg.sender, tokenId));
|
||
|
|
||
|
return IERC721Binder.onERC721Bind.selector;
|
||
|
}
|
||
|
|
||
|
/// @inheritdoc IERC721Binder
|
||
|
function onERC721Unbind(
|
||
|
address operator,
|
||
|
address from,
|
||
|
address to,
|
||
|
uint256 tokenId,
|
||
|
uint256 bindId,
|
||
|
bytes calldata data
|
||
|
) public returns (bytes4) {
|
||
|
if (_ownerOf[bindId] != from) {
|
||
|
revert OwnerInvalid();
|
||
|
}
|
||
|
|
||
|
if (_boundIndexes[msg.sender][tokenId] == 0) {
|
||
|
revert BindNonexistent();
|
||
|
}
|
||
|
|
||
|
uint256 boundLastIndex = _boundTokens[bindId].length - 1;
|
||
|
uint256 boundIndex = _boundIndexes[msg.sender][tokenId];
|
||
|
|
||
|
if (boundIndex != boundLastIndex) {
|
||
|
Bindable memory bindable = _boundTokens[bindId][boundLastIndex];
|
||
|
_boundTokens[bindId][boundIndex] = bindable;
|
||
|
_boundIndexes[bindable.tokenAddress][bindable.tokenId] = boundIndex;
|
||
|
}
|
||
|
|
||
|
delete _boundIndexes[msg.sender][tokenId];
|
||
|
delete _boundTokens[bindId][boundLastIndex];
|
||
|
|
||
|
return IERC721Binder.onERC721Unbind.selector;
|
||
|
}
|
||
|
|
||
|
/// @notice Transfers an asset from address `from` to address `to`.
|
||
|
function transfer(
|
||
|
address from,
|
||
|
address to,
|
||
|
uint256 bindId
|
||
|
) public {
|
||
|
if (msg.sender != from && !_isApprovedForAll[from][msg.sender]) {
|
||
|
revert SenderUnauthorized();
|
||
|
}
|
||
|
|
||
|
if (from != _ownerOf[bindId]) {
|
||
|
revert OwnerInvalid();
|
||
|
}
|
||
|
|
||
|
if (to == address(0)) {
|
||
|
revert ReceiverInvalid();
|
||
|
}
|
||
|
|
||
|
_ownerOf[bindId] = to;
|
||
|
|
||
|
Bindable[] memory bindables = _boundTokens[bindId];
|
||
|
for (uint256 i = 0; i < bindables.length; ++i) {
|
||
|
IERC721Bindable(bindables[i].tokenAddress).transferFrom(from, to, bindables[i].tokenId);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
function supportsInterface(bytes4 id) external pure returns (bool) {
|
||
|
return id == _ERC165_INTERFACE_ID || id == _ERC721_BINDER_INTERFACE_ID;
|
||
|
}
|
||
|
|
||
|
}
|