From cf1dc4abadfefcdeb2a9a2c25fabd8b8adaa742b Mon Sep 17 00:00:00 2001 From: zouxyan Date: Mon, 7 Dec 2020 17:22:31 +0800 Subject: [PATCH] add NFT support --- .../erc721_template/CrossChainNFTMapping.sol | 26 + .../interface/IEthCrossChainManager.sol | 2 +- .../interface/IEthCrossChainManagerProxy.sol | 2 +- contracts/core/lock_proxy/LockProxy.sol | 14 +- contracts/core/lock_proxy/NFTLockProxy.sol | 170 +++++++ contracts/libs/GSN/Context.sol | 2 +- contracts/libs/common/ZeroCopySink.sol | 28 +- contracts/libs/common/ZeroCopySource.sol | 26 +- contracts/libs/introspection/ERC165.sol | 54 ++ contracts/libs/introspection/IERC165.sol | 24 + contracts/libs/math/SafeMath.sol | 2 +- contracts/libs/ownership/Ownable.sol | 2 +- contracts/libs/token/ERC721/ERC721.sol | 473 ++++++++++++++++++ .../libs/token/ERC721/ERC721Burnable.sol | 25 + contracts/libs/token/ERC721/ERC721Holder.sol | 23 + .../libs/token/ERC721/ERC721Pausable.sol | 28 ++ contracts/libs/token/ERC721/IERC721.sol | 129 +++++ .../libs/token/ERC721/IERC721Enumerable.sol | 29 ++ .../libs/token/ERC721/IERC721Metadata.sol | 27 + .../libs/token/ERC721/IERC721Receiver.sol | 21 + contracts/libs/utils/Address.sol | 141 ++++++ contracts/libs/utils/EnumerableMap.sol | 237 +++++++++ contracts/libs/utils/EnumerableSet.sol | 243 +++++++++ contracts/libs/utils/Strings.sol | 34 ++ contracts/libs/utils/Utils.sol | 2 +- 25 files changed, 1749 insertions(+), 15 deletions(-) create mode 100644 contracts/core/assets/erc721_template/CrossChainNFTMapping.sol create mode 100644 contracts/core/lock_proxy/NFTLockProxy.sol create mode 100644 contracts/libs/introspection/ERC165.sol create mode 100644 contracts/libs/introspection/IERC165.sol create mode 100644 contracts/libs/token/ERC721/ERC721.sol create mode 100644 contracts/libs/token/ERC721/ERC721Burnable.sol create mode 100644 contracts/libs/token/ERC721/ERC721Holder.sol create mode 100644 contracts/libs/token/ERC721/ERC721Pausable.sol create mode 100644 contracts/libs/token/ERC721/IERC721.sol create mode 100644 contracts/libs/token/ERC721/IERC721Enumerable.sol create mode 100644 contracts/libs/token/ERC721/IERC721Metadata.sol create mode 100644 contracts/libs/token/ERC721/IERC721Receiver.sol create mode 100644 contracts/libs/utils/Address.sol create mode 100644 contracts/libs/utils/EnumerableMap.sol create mode 100644 contracts/libs/utils/EnumerableSet.sol create mode 100644 contracts/libs/utils/Strings.sol diff --git a/contracts/core/assets/erc721_template/CrossChainNFTMapping.sol b/contracts/core/assets/erc721_template/CrossChainNFTMapping.sol new file mode 100644 index 0000000..aa90436 --- /dev/null +++ b/contracts/core/assets/erc721_template/CrossChainNFTMapping.sol @@ -0,0 +1,26 @@ +pragma solidity ^0.6.0; + +import "./../../../libs/token/ERC721/ERC721.sol"; +import "./../../../libs/utils/Address.sol"; + +contract CrossChainNFTMapping is ERC721 { + using Address for address; + + address private lpAddr; + + constructor (address _lpAddr, string memory name, string memory symbol) public ERC721(name, symbol) { + require(_lpAddr.isContract(), "lockproxy address must be contract."); + lpAddr = _lpAddr; + } + + modifier onlyProxy() { + require(msg.sender == lpAddr, ""); + _; + } + + function mintWithURI(address to, uint256 tokenId, string memory uri) public onlyProxy { + require(!_exists(tokenId), "token id already exist"); + _safeMint(to, tokenId); + _setTokenURI(tokenId, uri); + } +} \ No newline at end of file diff --git a/contracts/core/cross_chain_manager/interface/IEthCrossChainManager.sol b/contracts/core/cross_chain_manager/interface/IEthCrossChainManager.sol index 852df55..1e20212 100644 --- a/contracts/core/cross_chain_manager/interface/IEthCrossChainManager.sol +++ b/contracts/core/cross_chain_manager/interface/IEthCrossChainManager.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5.0; +pragma solidity >=0.5.0; /** * @dev Interface of the EthCrossChainManager contract for business contract like LockProxy to request cross chain transaction diff --git a/contracts/core/cross_chain_manager/interface/IEthCrossChainManagerProxy.sol b/contracts/core/cross_chain_manager/interface/IEthCrossChainManagerProxy.sol index 73150e1..75b6772 100644 --- a/contracts/core/cross_chain_manager/interface/IEthCrossChainManagerProxy.sol +++ b/contracts/core/cross_chain_manager/interface/IEthCrossChainManagerProxy.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5.0; +pragma solidity >=0.5.0; /** * @dev Interface of the EthCrossChainManagerProxy for business contract like LockProxy to obtain the reliable EthCrossChainManager contract hash. diff --git a/contracts/core/lock_proxy/LockProxy.sol b/contracts/core/lock_proxy/LockProxy.sol index 04da125..4a451c6 100644 --- a/contracts/core/lock_proxy/LockProxy.sol +++ b/contracts/core/lock_proxy/LockProxy.sol @@ -154,18 +154,18 @@ contract LockProxy is Ownable { return true; } - function _transferERC20ToContract(address fromAssetHash, address fromAddress, address toAddress, uint256 amount) internal returns (bool) { - IERC20 erc20Token = IERC20(fromAssetHash); + IERC20 erc20Token = IERC20(fromAssetHash); // require(erc20Token.transferFrom(fromAddress, toAddress, amount), "trasnfer ERC20 Token failed!"); - erc20Token.safeTransferFrom(fromAddress, toAddress, amount); - return true; + erc20Token.safeTransferFrom(fromAddress, toAddress, amount); + return true; } + function _transferERC20FromContract(address toAssetHash, address toAddress, uint256 amount) internal returns (bool) { - IERC20 erc20Token = IERC20(toAssetHash); + IERC20 erc20Token = IERC20(toAssetHash); // require(erc20Token.transfer(toAddress, amount), "trasnfer ERC20 Token failed!"); - erc20Token.safeTransfer(toAddress, amount); - return true; + erc20Token.safeTransfer(toAddress, amount); + return true; } function _serializeTxArgs(TxArgs memory args) internal pure returns (bytes memory) { diff --git a/contracts/core/lock_proxy/NFTLockProxy.sol b/contracts/core/lock_proxy/NFTLockProxy.sol new file mode 100644 index 0000000..19d52d4 --- /dev/null +++ b/contracts/core/lock_proxy/NFTLockProxy.sol @@ -0,0 +1,170 @@ +pragma solidity ^0.6.0; + +import "./../../libs/ownership/Ownable.sol"; +import "./../../libs/common/ZeroCopySink.sol"; +import "./../../libs/common/ZeroCopySource.sol"; +import "./../../libs/utils/Utils.sol"; +import "./../../libs/utils/Address.sol"; +import "./../../libs/token/ERC721/IERC721Metadata.sol"; +import "./../../libs/token/ERC721/IERC721Receiver.sol"; +import "./../../libs/math/SafeMath.sol"; +import "./../cross_chain_manager/interface/IEthCrossChainManager.sol"; +import "./../cross_chain_manager/interface/IEthCrossChainManagerProxy.sol"; + + +contract NFTLockProxy is IERC721Receiver, Ownable { + using SafeMath for uint; + using Address for address; + + struct TxArgs { + bytes toAssetHash; + bytes toAddress; + uint256 tokenId; + bytes tokenURI; + } + + address public managerProxyContract; + mapping(uint64 => bytes) public proxyHashMap; + mapping(address => mapping(uint64 => bytes)) public assetHashMap; + mapping(address => bool) safeTransfer; + + event SetManagerProxyEvent(address manager); + event BindProxyEvent(uint64 toChainId, bytes targetProxyHash); + event BindAssetEvent(address fromAssetHash, uint64 toChainId, bytes targetProxyHash); + event UnlockEvent(address toAssetHash, address toAddress, uint256 tokenId); + event LockEvent(address fromAssetHash, address fromAddress, bytes toAssetHash, bytes toAddress, uint64 toChainId, uint256 tokenId); + + modifier onlyManagerContract() { + IEthCrossChainManagerProxy ieccmp = IEthCrossChainManagerProxy(managerProxyContract); + require(_msgSender() == ieccmp.getEthCrossChainManager(), "msgSender is not EthCrossChainManagerContract"); + _; + } + + function setManagerProxy(address ethCCMProxyAddr) onlyOwner public { + managerProxyContract = ethCCMProxyAddr; + emit SetManagerProxyEvent(managerProxyContract); + } + + function bindProxyHash(uint64 toChainId, bytes memory targetProxyHash) onlyOwner public returns (bool) { + proxyHashMap[toChainId] = targetProxyHash; + emit BindProxyEvent(toChainId, targetProxyHash); + return true; + } + + function bindAssetHash(address fromAssetHash, uint64 toChainId, bytes memory toAssetHash) onlyOwner public returns (bool) { + assetHashMap[fromAssetHash][toChainId] = toAssetHash; + emit BindAssetEvent(fromAssetHash, toChainId, toAssetHash); + return true; + } + + // /* @notice This function is meant to be invoked by the ETH crosschain management contract, + // * then mint a certin amount of tokens to the designated address since a certain amount + // * was burnt from the source chain invoker. + // * @param argsBs The argument bytes recevied by the ethereum lock proxy contract, need to be deserialized. + // * based on the way of serialization in the source chain proxy contract. + // * @param fromContractAddr The source chain contract address + // * @param fromChainId The source chain id + // */ + function unlock(bytes memory argsBs, bytes memory fromContractAddr, uint64 fromChainId) public onlyManagerContract returns (bool) { + TxArgs memory args = _deserializeTxArgs(argsBs); + + require(fromContractAddr.length != 0, "from proxy contract address cannot be empty"); + require(Utils.equalStorage(proxyHashMap[fromChainId], fromContractAddr), "From Proxy contract address error!"); + + require(args.toAssetHash.length != 0, "toAssetHash cannot be empty"); + address toAssetHash = Utils.bytesToAddress(args.toAssetHash); + + require(args.toAddress.length != 0, "toAddress cannot be empty"); + address toAddress = Utils.bytesToAddress(args.toAddress); + + bool success; + bytes memory res; + address owner; + bytes memory raw = abi.encodeWithSignature("ownerOf(uint256)", args.tokenId); + (success, res) = toAssetHash.call(raw); + if (success) { + owner = abi.decode(res, (address)); + require(owner == address(this) || owner == address(0), "your token ID is not hold by lockproxy."); + if (owner == address(this)) { + raw = abi.encodeWithSignature("safeTransferFrom(address,address,uint256)", address(this), toAddress, args.tokenId); + (success, ) = toAssetHash.call(raw); + require(success, "failed to call safeTransferFrom"); + } + } + if (!success || owner == address(0)) { + raw = abi.encodeWithSignature("mintWithURI(address,uint256,string)", toAddress, args.tokenId, string(args.tokenURI)); + (success, ) = toAssetHash.call(raw); + require(success, "failed to call mintWithURI to mint a new mapping NFT"); + } + + emit UnlockEvent(toAssetHash, toAddress, args.tokenId); + return true; + } + + function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) public override returns (bytes4) { + address fromAssetHash = _msgSender(); + require(data.length > 0, "length of toAddress can't be zero. "); + require(fromAssetHash.isContract(), "caller must be a contract. "); + + bytes memory toAddress; + uint64 toChainId; + bytes memory toAssetHash; + { + (toAddress, toChainId) = _deserializeCallData(data); + toAssetHash = assetHashMap[fromAssetHash][toChainId]; + require(toAssetHash.length != 0, "empty illegal toAssetHash"); + + IERC721Metadata nft = IERC721Metadata(fromAssetHash); + require(nft.ownerOf(tokenId) == address(this), "wrong owner for this token ID"); + + string memory uri = nft.tokenURI(tokenId); + TxArgs memory txArgs = TxArgs({ + toAssetHash: toAssetHash, + toAddress: toAddress, + tokenId: tokenId, + tokenURI: bytes(uri) + }); + bytes memory txData = _serializeTxArgs(txArgs); + IEthCrossChainManager eccm = IEthCrossChainManager(IEthCrossChainManagerProxy(managerProxyContract).getEthCrossChainManager()); + + bytes memory toProxyHash = proxyHashMap[toChainId]; + require(toProxyHash.length != 0, "empty illegal toProxyHash"); + require(eccm.crossChain(toChainId, toProxyHash, "unlock", txData), "EthCrossChainManager crossChain executed error!"); + } + { + emit LockEvent(fromAssetHash, from, toAssetHash, toAddress, toChainId, tokenId); + } + + return this.onERC721Received.selector; + } + + function _serializeTxArgs(TxArgs memory args) internal pure returns (bytes memory) { + bytes memory buff; + buff = abi.encodePacked( + ZeroCopySink.WriteVarBytes(args.toAssetHash), + ZeroCopySink.WriteVarBytes(args.toAddress), + ZeroCopySink.WriteUint256(args.tokenId), + ZeroCopySink.WriteVarBytes(args.tokenURI) + ); + return buff; + } + + function _deserializeTxArgs(bytes memory valueBs) internal pure returns (TxArgs memory) { + TxArgs memory args; + uint256 off = 0; + (args.toAssetHash, off) = ZeroCopySource.NextVarBytes(valueBs, off); + (args.toAddress, off) = ZeroCopySource.NextVarBytes(valueBs, off); + (args.tokenId, off) = ZeroCopySource.NextUint256(valueBs, off); + (args.tokenURI, off) = ZeroCopySource.NextVarBytes(valueBs, off); + return args; + } + + function _deserializeCallData(bytes memory valueBs) internal pure returns (bytes memory, uint64) { + bytes memory toAddress; + uint64 chainId; + uint256 off = 0; + (toAddress, off) = ZeroCopySource.NextVarBytes(valueBs, off); + (chainId, off) = ZeroCopySource.NextUint64(valueBs, off); + return (toAddress, chainId); + } +} \ No newline at end of file diff --git a/contracts/libs/GSN/Context.sol b/contracts/libs/GSN/Context.sol index 72b0eeb..e4c4a19 100644 --- a/contracts/libs/GSN/Context.sol +++ b/contracts/libs/GSN/Context.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5.0; +pragma solidity >=0.5.0; /* * @dev Provides information about the current execution context, including the diff --git a/contracts/libs/common/ZeroCopySink.sol b/contracts/libs/common/ZeroCopySink.sol index 7a9b0ae..d9c4779 100644 --- a/contracts/libs/common/ZeroCopySink.sol +++ b/contracts/libs/common/ZeroCopySink.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5.0; +pragma solidity >=0.5.0; /** * @dev Wrappers over encoding and serialization operation into bytes from bassic types in Solidity for PolyNetwork cross chain utility. @@ -162,6 +162,32 @@ library ZeroCopySink { return buff; } + /* @notice Convert limited uint256 value into bytes + * @param v The uint256 value + * @return Converted bytes array + */ + function WriteUint256(uint256 v) internal pure returns (bytes memory) { + require(v <= uint256(-1), "Value exceeds uint256 range"); + bytes memory buff; + + assembly{ + buff := mload(0x40) + let byteLen := 0x20 + mstore(buff, byteLen) + for { + let mindex := 0x00 + let vindex := 0x1f + } lt(mindex, byteLen) { + mindex := add(mindex, 0x01) + vindex := sub(vindex, 0x01) + }{ + mstore8(add(add(buff, 0x20), mindex), byte(vindex, v)) + } + mstore(0x40, add(buff, 0x40)) + } + return buff; + } + /* @notice Encode bytes format data into bytes * @param data The bytes array data * @return Encoded bytes array diff --git a/contracts/libs/common/ZeroCopySource.sol b/contracts/libs/common/ZeroCopySource.sol index c113dd4..00a357b 100644 --- a/contracts/libs/common/ZeroCopySource.sol +++ b/contracts/libs/common/ZeroCopySource.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5.0; +pragma solidity >=0.5.0; /** * @dev Wrappers over decoding and deserialization operation from bytes into bassic types in Solidity for PolyNetwork cross chain utility. @@ -173,6 +173,30 @@ library ZeroCopySource { require(v <= 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, "Value exceeds the range"); return (v, offset + 32); } + + function NextUint256(bytes memory buff, uint256 offset) internal pure returns (uint256, uint256) { + require(offset + 32 <= buff.length && offset < offset + 32, "NextUint256, offset exceeds maximum"); + uint256 v; + assembly { + let tmpbytes := mload(0x40) + let byteLen := 0x20 + for { + let tindex := 0x00 + let bindex := sub(byteLen, 0x01) + let bvalue := mload(add(add(buff, 0x20), offset)) + } lt(tindex, byteLen) { + tindex := add(tindex, 0x01) + bindex := sub(bindex, 0x01) + }{ + mstore8(add(tmpbytes, tindex), byte(bindex, bvalue)) + } + mstore(0x40, add(tmpbytes, byteLen)) + v := mload(tmpbytes) + } + require(v <= uint256(-1), "Value exceeds the range"); + return (v, offset + 32); + } + /* @notice Read next variable bytes starting from offset, the decoding rule coming from multi-chain * @param buff Source bytes array diff --git a/contracts/libs/introspection/ERC165.sol b/contracts/libs/introspection/ERC165.sol new file mode 100644 index 0000000..8f34a88 --- /dev/null +++ b/contracts/libs/introspection/ERC165.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.0; + +import "./IERC165.sol"; + +/** + * @dev Implementation of the {IERC165} interface. + * + * Contracts may inherit from this and call {_registerInterface} to declare + * their support of an interface. + */ +contract ERC165 is IERC165 { + /* + * bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7 + */ + bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7; + + /** + * @dev Mapping of interface ids to whether or not it's supported. + */ + mapping(bytes4 => bool) private _supportedInterfaces; + + constructor () internal { + // Derived contracts need only register support for their own interfaces, + // we register support for ERC165 itself here + _registerInterface(_INTERFACE_ID_ERC165); + } + + /** + * @dev See {IERC165-supportsInterface}. + * + * Time complexity O(1), guaranteed to always use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId) public view override returns (bool) { + return _supportedInterfaces[interfaceId]; + } + + /** + * @dev Registers the contract as an implementer of the interface defined by + * `interfaceId`. Support of the actual ERC165 interface is automatic and + * registering its interface id is not required. + * + * See {IERC165-supportsInterface}. + * + * Requirements: + * + * - `interfaceId` cannot be the ERC165 invalid interface (`0xffffffff`). + */ + function _registerInterface(bytes4 interfaceId) internal virtual { + require(interfaceId != 0xffffffff, "ERC165: invalid interface id"); + _supportedInterfaces[interfaceId] = true; + } +} diff --git a/contracts/libs/introspection/IERC165.sol b/contracts/libs/introspection/IERC165.sol new file mode 100644 index 0000000..425458d --- /dev/null +++ b/contracts/libs/introspection/IERC165.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.0; + +/** + * @dev Interface of the ERC165 standard, as defined in the + * https://eips.ethereum.org/EIPS/eip-165[EIP]. + * + * Implementers can declare support of contract interfaces, which can then be + * queried by others ({ERC165Checker}). + * + * For an implementation, see {ERC165}. + */ +interface IERC165 { + /** + * @dev Returns true if this contract implements the interface defined by + * `interfaceId`. See the corresponding + * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] + * to learn more about how these ids are created. + * + * This function call must use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} diff --git a/contracts/libs/math/SafeMath.sol b/contracts/libs/math/SafeMath.sol index 96b76f0..ba068b8 100644 --- a/contracts/libs/math/SafeMath.sol +++ b/contracts/libs/math/SafeMath.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5.0; +pragma solidity >=0.5.0; /** * @dev Wrappers over Solidity's arithmetic operations with added overflow diff --git a/contracts/libs/ownership/Ownable.sol b/contracts/libs/ownership/Ownable.sol index 0d971e8..9767dab 100644 --- a/contracts/libs/ownership/Ownable.sol +++ b/contracts/libs/ownership/Ownable.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5.0; +pragma solidity >=0.5.0; import "../GSN/Context.sol"; /** diff --git a/contracts/libs/token/ERC721/ERC721.sol b/contracts/libs/token/ERC721/ERC721.sol new file mode 100644 index 0000000..fc04f1d --- /dev/null +++ b/contracts/libs/token/ERC721/ERC721.sol @@ -0,0 +1,473 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.0; + +import "../../GSN/Context.sol"; +import "./IERC721.sol"; +import "./IERC721Metadata.sol"; +import "./IERC721Enumerable.sol"; +import "./IERC721Receiver.sol"; +import "../../introspection/ERC165.sol"; +import "../../math/SafeMath.sol"; +import "../../utils/Address.sol"; +import "../../utils/EnumerableSet.sol"; +import "../../utils/EnumerableMap.sol"; +import "../../utils/Strings.sol"; + +/** + * @title ERC721 Non-Fungible Token Standard basic implementation + * @dev see https://eips.ethereum.org/EIPS/eip-721 + */ +contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable { + using SafeMath for uint256; + using Address for address; + using EnumerableSet for EnumerableSet.UintSet; + using EnumerableMap for EnumerableMap.UintToAddressMap; + using Strings for uint256; + + // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` + // which can be also obtained as `IERC721Receiver(0).onERC721Received.selector` + bytes4 private constant _ERC721_RECEIVED = 0x150b7a02; + + // Mapping from holder address to their (enumerable) set of owned tokens + mapping (address => EnumerableSet.UintSet) private _holderTokens; + + // Enumerable mapping from token ids to their owners + EnumerableMap.UintToAddressMap private _tokenOwners; + + // Mapping from token ID to approved address + mapping (uint256 => address) private _tokenApprovals; + + // Mapping from owner to operator approvals + mapping (address => mapping (address => bool)) private _operatorApprovals; + + // Token name + string private _name; + + // Token symbol + string private _symbol; + + // Optional mapping for token URIs + mapping (uint256 => string) private _tokenURIs; + + // Base URI + string private _baseURI; + + /* + * bytes4(keccak256('balanceOf(address)')) == 0x70a08231 + * bytes4(keccak256('ownerOf(uint256)')) == 0x6352211e + * bytes4(keccak256('approve(address,uint256)')) == 0x095ea7b3 + * bytes4(keccak256('getApproved(uint256)')) == 0x081812fc + * bytes4(keccak256('setApprovalForAll(address,bool)')) == 0xa22cb465 + * bytes4(keccak256('isApprovedForAll(address,address)')) == 0xe985e9c5 + * bytes4(keccak256('transferFrom(address,address,uint256)')) == 0x23b872dd + * bytes4(keccak256('safeTransferFrom(address,address,uint256)')) == 0x42842e0e + * bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)')) == 0xb88d4fde + * + * => 0x70a08231 ^ 0x6352211e ^ 0x095ea7b3 ^ 0x081812fc ^ + * 0xa22cb465 ^ 0xe985e9c5 ^ 0x23b872dd ^ 0x42842e0e ^ 0xb88d4fde == 0x80ac58cd + */ + bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd; + + /* + * bytes4(keccak256('name()')) == 0x06fdde03 + * bytes4(keccak256('symbol()')) == 0x95d89b41 + * bytes4(keccak256('tokenURI(uint256)')) == 0xc87b56dd + * + * => 0x06fdde03 ^ 0x95d89b41 ^ 0xc87b56dd == 0x5b5e139f + */ + bytes4 private constant _INTERFACE_ID_ERC721_METADATA = 0x5b5e139f; + + /* + * bytes4(keccak256('totalSupply()')) == 0x18160ddd + * bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) == 0x2f745c59 + * bytes4(keccak256('tokenByIndex(uint256)')) == 0x4f6ccce7 + * + * => 0x18160ddd ^ 0x2f745c59 ^ 0x4f6ccce7 == 0x780e9d63 + */ + bytes4 private constant _INTERFACE_ID_ERC721_ENUMERABLE = 0x780e9d63; + + /** + * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. + */ + constructor (string memory name, string memory symbol) public { + _name = name; + _symbol = symbol; + + // register the supported interfaces to conform to ERC721 via ERC165 + _registerInterface(_INTERFACE_ID_ERC721); + _registerInterface(_INTERFACE_ID_ERC721_METADATA); + _registerInterface(_INTERFACE_ID_ERC721_ENUMERABLE); + } + + /** + * @dev See {IERC721-balanceOf}. + */ + function balanceOf(address owner) public view override returns (uint256) { + require(owner != address(0), "ERC721: balance query for the zero address"); + + return _holderTokens[owner].length(); + } + + /** + * @dev See {IERC721-ownerOf}. + */ + function ownerOf(uint256 tokenId) public view override returns (address) { + return _tokenOwners.get(tokenId, "ERC721: owner query for nonexistent token"); + } + + /** + * @dev See {IERC721Metadata-name}. + */ + function name() public view override returns (string memory) { + return _name; + } + + /** + * @dev See {IERC721Metadata-symbol}. + */ + function symbol() public view override returns (string memory) { + return _symbol; + } + + /** + * @dev See {IERC721Metadata-tokenURI}. + */ + function tokenURI(uint256 tokenId) public view override returns (string memory) { + require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); + + string memory _tokenURI = _tokenURIs[tokenId]; + + // If there is no base URI, return the token URI. + if (bytes(_baseURI).length == 0) { + return _tokenURI; + } + // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked). + if (bytes(_tokenURI).length > 0) { + return string(abi.encodePacked(_baseURI, _tokenURI)); + } + // If there is a baseURI but no tokenURI, concatenate the tokenID to the baseURI. + return string(abi.encodePacked(_baseURI, tokenId.toString())); + } + + /** + * @dev Returns the base URI set via {_setBaseURI}. This will be + * automatically added as a prefix in {tokenURI} to each token's URI, or + * to the token ID if no specific URI is set for that token ID. + */ + function baseURI() public view returns (string memory) { + return _baseURI; + } + + /** + * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}. + */ + function tokenOfOwnerByIndex(address owner, uint256 index) public view override returns (uint256) { + return _holderTokens[owner].at(index); + } + + /** + * @dev See {IERC721Enumerable-totalSupply}. + */ + function totalSupply() public view override returns (uint256) { + // _tokenOwners are indexed by tokenIds, so .length() returns the number of tokenIds + return _tokenOwners.length(); + } + + /** + * @dev See {IERC721Enumerable-tokenByIndex}. + */ + function tokenByIndex(uint256 index) public view override returns (uint256) { + (uint256 tokenId, ) = _tokenOwners.at(index); + return tokenId; + } + + /** + * @dev See {IERC721-approve}. + */ + function approve(address to, uint256 tokenId) public virtual override { + address owner = ownerOf(tokenId); + require(to != owner, "ERC721: approval to current owner"); + + require(_msgSender() == owner || isApprovedForAll(owner, _msgSender()), + "ERC721: approve caller is not owner nor approved for all" + ); + + _approve(to, tokenId); + } + + /** + * @dev See {IERC721-getApproved}. + */ + function getApproved(uint256 tokenId) public view override returns (address) { + require(_exists(tokenId), "ERC721: approved query for nonexistent token"); + + return _tokenApprovals[tokenId]; + } + + /** + * @dev See {IERC721-setApprovalForAll}. + */ + function setApprovalForAll(address operator, bool approved) public virtual override { + require(operator != _msgSender(), "ERC721: approve to caller"); + + _operatorApprovals[_msgSender()][operator] = approved; + emit ApprovalForAll(_msgSender(), operator, approved); + } + + /** + * @dev See {IERC721-isApprovedForAll}. + */ + function isApprovedForAll(address owner, address operator) public view override returns (bool) { + return _operatorApprovals[owner][operator]; + } + + /** + * @dev See {IERC721-transferFrom}. + */ + function transferFrom(address from, address to, uint256 tokenId) public virtual override { + //solhint-disable-next-line max-line-length + require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); + + _transfer(from, to, tokenId); + } + + /** + * @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override { + safeTransferFrom(from, to, tokenId, ""); + } + + /** + * @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public virtual override { + require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); + _safeTransfer(from, to, tokenId, _data); + } + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients + * are aware of the ERC721 protocol to prevent tokens from being forever locked. + * + * `_data` is additional data, it has no specified format and it is sent in call to `to`. + * + * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g. + * implement alternative mechanisms to perform token transfer, such as signature-based. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function _safeTransfer(address from, address to, uint256 tokenId, bytes memory _data) internal virtual { + _transfer(from, to, tokenId); + require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); + } + + /** + * @dev Returns whether `tokenId` exists. + * + * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}. + * + * Tokens start existing when they are minted (`_mint`), + * and stop existing when they are burned (`_burn`). + */ + function _exists(uint256 tokenId) internal view returns (bool) { + return _tokenOwners.contains(tokenId); + } + + /** + * @dev Returns whether `spender` is allowed to manage `tokenId`. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) { + require(_exists(tokenId), "ERC721: operator query for nonexistent token"); + address owner = ownerOf(tokenId); + return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender)); + } + + /** + * @dev Safely mints `tokenId` and transfers it to `to`. + * + * Requirements: + d* + * - `tokenId` must not exist. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function _safeMint(address to, uint256 tokenId) internal virtual { + _safeMint(to, tokenId, ""); + } + + /** + * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is + * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. + */ + function _safeMint(address to, uint256 tokenId, bytes memory _data) internal virtual { + _mint(to, tokenId); + require(_checkOnERC721Received(address(0), to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); + } + + /** + * @dev Mints `tokenId` and transfers it to `to`. + * + * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible + * + * Requirements: + * + * - `tokenId` must not exist. + * - `to` cannot be the zero address. + * + * Emits a {Transfer} event. + */ + function _mint(address to, uint256 tokenId) internal virtual { + require(to != address(0), "ERC721: mint to the zero address"); + require(!_exists(tokenId), "ERC721: token already minted"); + + _beforeTokenTransfer(address(0), to, tokenId); + + _holderTokens[to].add(tokenId); + + _tokenOwners.set(tokenId, to); + + emit Transfer(address(0), to, tokenId); + } + + /** + * @dev Destroys `tokenId`. + * The approval is cleared when the token is burned. + * + * Requirements: + * + * - `tokenId` must exist. + * + * Emits a {Transfer} event. + */ + function _burn(uint256 tokenId) internal virtual { + address owner = ownerOf(tokenId); + + _beforeTokenTransfer(owner, address(0), tokenId); + + // Clear approvals + _approve(address(0), tokenId); + + // Clear metadata (if any) + if (bytes(_tokenURIs[tokenId]).length != 0) { + delete _tokenURIs[tokenId]; + } + + _holderTokens[owner].remove(tokenId); + + _tokenOwners.remove(tokenId); + + emit Transfer(owner, address(0), tokenId); + } + + /** + * @dev Transfers `tokenId` from `from` to `to`. + * As opposed to {transferFrom}, this imposes no restrictions on msg.sender. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - `tokenId` token must be owned by `from`. + * + * Emits a {Transfer} event. + */ + function _transfer(address from, address to, uint256 tokenId) internal virtual { + require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own"); + require(to != address(0), "ERC721: transfer to the zero address"); + + _beforeTokenTransfer(from, to, tokenId); + + // Clear approvals from the previous owner + _approve(address(0), tokenId); + + _holderTokens[from].remove(tokenId); + _holderTokens[to].add(tokenId); + + _tokenOwners.set(tokenId, to); + + emit Transfer(from, to, tokenId); + } + + /** + * @dev Sets `_tokenURI` as the tokenURI of `tokenId`. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual { + require(_exists(tokenId), "ERC721Metadata: URI set of nonexistent token"); + _tokenURIs[tokenId] = _tokenURI; + } + + /** + * @dev Internal function to set the base URI for all token IDs. It is + * automatically added as a prefix to the value returned in {tokenURI}, + * or to the token ID if {tokenURI} is empty. + */ + function _setBaseURI(string memory baseURI_) internal virtual { + _baseURI = baseURI_; + } + + /** + * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address. + * The call is not executed if the target address is not a contract. + * + * @param from address representing the previous owner of the given token ID + * @param to target address that will receive the tokens + * @param tokenId uint256 ID of the token to be transferred + * @param _data bytes optional data to send along with the call + * @return bool whether the call correctly returned the expected magic value + */ + function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data) + private returns (bool) + { + if (!to.isContract()) { + return true; + } + bytes memory returndata = to.functionCall(abi.encodeWithSelector( + IERC721Receiver(to).onERC721Received.selector, + _msgSender(), + from, + tokenId, + _data + ), "ERC721: transfer to non ERC721Receiver implementer"); + bytes4 retval = abi.decode(returndata, (bytes4)); + return (retval == _ERC721_RECEIVED); + } + + function _approve(address to, uint256 tokenId) private { + _tokenApprovals[tokenId] = to; + emit Approval(ownerOf(tokenId), to, tokenId); + } + + /** + * @dev Hook that is called before any token transfer. This includes minting + * and burning. + * + * Calling conditions: + * + * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be + * transferred to `to`. + * - When `from` is zero, `tokenId` will be minted for `to`. + * - When `to` is zero, ``from``'s `tokenId` will be burned. + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual { } +} diff --git a/contracts/libs/token/ERC721/ERC721Burnable.sol b/contracts/libs/token/ERC721/ERC721Burnable.sol new file mode 100644 index 0000000..6f767d2 --- /dev/null +++ b/contracts/libs/token/ERC721/ERC721Burnable.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.0; + +import "../../GSN/Context.sol"; +import "./ERC721.sol"; + +/** + * @title ERC721 Burnable Token + * @dev ERC721 Token that can be irreversibly burned (destroyed). + */ +abstract contract ERC721Burnable is Context, ERC721 { + /** + * @dev Burns `tokenId`. See {ERC721-_burn}. + * + * Requirements: + * + * - The caller must own `tokenId` or be an approved operator. + */ + function burn(uint256 tokenId) public virtual { + //solhint-disable-next-line max-line-length + require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721Burnable: caller is not owner nor approved"); + _burn(tokenId); + } +} diff --git a/contracts/libs/token/ERC721/ERC721Holder.sol b/contracts/libs/token/ERC721/ERC721Holder.sol new file mode 100644 index 0000000..3684bd1 --- /dev/null +++ b/contracts/libs/token/ERC721/ERC721Holder.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.0; + +import "./IERC721Receiver.sol"; + + /** + * @dev Implementation of the {IERC721Receiver} interface. + * + * Accepts all token transfers. + * Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or {IERC721-setApprovalForAll}. + */ +contract ERC721Holder is IERC721Receiver { + + /** + * @dev See {IERC721Receiver-onERC721Received}. + * + * Always returns `IERC721Receiver.onERC721Received.selector`. + */ + function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) { + return this.onERC721Received.selector; + } +} diff --git a/contracts/libs/token/ERC721/ERC721Pausable.sol b/contracts/libs/token/ERC721/ERC721Pausable.sol new file mode 100644 index 0000000..c590647 --- /dev/null +++ b/contracts/libs/token/ERC721/ERC721Pausable.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.0; + +import "./ERC721.sol"; +import "../../utils/Pausable.sol"; + +/** + * @dev ERC721 token with pausable token transfers, minting and burning. + * + * Useful for scenarios such as preventing trades until the end of an evaluation + * period, or having an emergency switch for freezing all token transfers in the + * event of a large bug. + */ +abstract contract ERC721Pausable is ERC721, Pausable { + /** + * @dev See {ERC721-_beforeTokenTransfer}. + * + * Requirements: + * + * - the contract must not be paused. + */ + function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual override { + super._beforeTokenTransfer(from, to, tokenId); + + require(!paused(), "ERC721Pausable: token transfer while paused"); + } +} diff --git a/contracts/libs/token/ERC721/IERC721.sol b/contracts/libs/token/ERC721/IERC721.sol new file mode 100644 index 0000000..5e7d20e --- /dev/null +++ b/contracts/libs/token/ERC721/IERC721.sol @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.2; + +import "../../introspection/IERC165.sol"; + +/** + * @dev Required interface of an ERC721 compliant contract. + */ +interface IERC721 is IERC165 { + /** + * @dev Emitted when `tokenId` token is transferred from `from` to `to`. + */ + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. + */ + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. + */ + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + /** + * @dev Returns the number of tokens in ``owner``'s account. + */ + function balanceOf(address owner) external view returns (uint256 balance); + + /** + * @dev Returns the owner of the `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function ownerOf(uint256 tokenId) external view returns (address owner); + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients + * are aware of the ERC721 protocol to prevent tokens from being forever locked. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom(address from, address to, uint256 tokenId) external; + + /** + * @dev Transfers `tokenId` token from `from` to `to`. + * + * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * + * Emits a {Transfer} event. + */ + function transferFrom(address from, address to, uint256 tokenId) external; + + /** + * @dev Gives permission to `to` to transfer `tokenId` token to another account. + * The approval is cleared when the token is transferred. + * + * Only a single account can be approved at a time, so approving the zero address clears previous approvals. + * + * Requirements: + * + * - The caller must own the token or be an approved operator. + * - `tokenId` must exist. + * + * Emits an {Approval} event. + */ + function approve(address to, uint256 tokenId) external; + + /** + * @dev Returns the account approved for `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function getApproved(uint256 tokenId) external view returns (address operator); + + /** + * @dev Approve or remove `operator` as an operator for the caller. + * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. + * + * Requirements: + * + * - The `operator` cannot be the caller. + * + * Emits an {ApprovalForAll} event. + */ + function setApprovalForAll(address operator, bool _approved) external; + + /** + * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. + * + * See {setApprovalForAll} + */ + function isApprovedForAll(address owner, address operator) external view returns (bool); + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; +} diff --git a/contracts/libs/token/ERC721/IERC721Enumerable.sol b/contracts/libs/token/ERC721/IERC721Enumerable.sol new file mode 100644 index 0000000..bd3c86d --- /dev/null +++ b/contracts/libs/token/ERC721/IERC721Enumerable.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.2; + +import "./IERC721.sol"; + +/** + * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension + * @dev See https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721Enumerable is IERC721 { + + /** + * @dev Returns the total amount of tokens stored by the contract. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns a token ID owned by `owner` at a given `index` of its token list. + * Use along with {balanceOf} to enumerate all of ``owner``'s tokens. + */ + function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId); + + /** + * @dev Returns a token ID at a given `index` of all the tokens stored by the contract. + * Use along with {totalSupply} to enumerate all tokens. + */ + function tokenByIndex(uint256 index) external view returns (uint256); +} diff --git a/contracts/libs/token/ERC721/IERC721Metadata.sol b/contracts/libs/token/ERC721/IERC721Metadata.sol new file mode 100644 index 0000000..f2ebb49 --- /dev/null +++ b/contracts/libs/token/ERC721/IERC721Metadata.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.2; + +import "./IERC721.sol"; + +/** + * @title ERC-721 Non-Fungible Token Standard, optional metadata extension + * @dev See https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721Metadata is IERC721 { + + /** + * @dev Returns the token collection name. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the token collection symbol. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token. + */ + function tokenURI(uint256 tokenId) external view returns (string memory); +} diff --git a/contracts/libs/token/ERC721/IERC721Receiver.sol b/contracts/libs/token/ERC721/IERC721Receiver.sol new file mode 100644 index 0000000..0d17a07 --- /dev/null +++ b/contracts/libs/token/ERC721/IERC721Receiver.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.0; + +/** + * @title ERC721 token receiver interface + * @dev Interface for any contract that wants to support safeTransfers + * from ERC721 asset contracts. + */ +interface IERC721Receiver { + /** + * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} + * by `operator` from `from`, this function is called. + * + * It must return its Solidity selector to confirm the token transfer. + * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted. + * + * The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`. + */ + function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external returns (bytes4); +} diff --git a/contracts/libs/utils/Address.sol b/contracts/libs/utils/Address.sol new file mode 100644 index 0000000..fe3ba65 --- /dev/null +++ b/contracts/libs/utils/Address.sol @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.2; + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies in extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + // solhint-disable-next-line no-inline-assembly + assembly { size := extcodesize(account) } + return size > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + // solhint-disable-next-line avoid-low-level-calls, avoid-call-value + (bool success, ) = recipient.call{ value: amount }(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain`call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { + return _functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + return _functionCallWithValue(target, data, value, errorMessage); + } + + function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) { + require(isContract(target), "Address: call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.call{ value: weiValue }(data); + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + + // solhint-disable-next-line no-inline-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} diff --git a/contracts/libs/utils/EnumerableMap.sol b/contracts/libs/utils/EnumerableMap.sol new file mode 100644 index 0000000..a62f600 --- /dev/null +++ b/contracts/libs/utils/EnumerableMap.sol @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.5.0; + +/** + * @dev Library for managing an enumerable variant of Solidity's + * https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`] + * type. + * + * Maps have the following properties: + * + * - Entries are added, removed, and checked for existence in constant time + * (O(1)). + * - Entries are enumerated in O(n). No guarantees are made on the ordering. + * + * ``` + * contract Example { + * // Add the library methods + * using EnumerableMap for EnumerableMap.UintToAddressMap; + * + * // Declare a set state variable + * EnumerableMap.UintToAddressMap private myMap; + * } + * ``` + * + * As of v3.0.0, only maps of type `uint256 -> address` (`UintToAddressMap`) are + * supported. + */ +library EnumerableMap { + // To implement this library for multiple types with as little code + // repetition as possible, we write it in terms of a generic Map type with + // bytes32 keys and values. + // The Map implementation uses private functions, and user-facing + // implementations (such as Uint256ToAddressMap) are just wrappers around + // the underlying Map. + // This means that we can only create new EnumerableMaps for types that fit + // in bytes32. + + struct MapEntry { + bytes32 _key; + bytes32 _value; + } + + struct Map { + // Storage of map keys and values + MapEntry[] _entries; + + // Position of the entry defined by a key in the `entries` array, plus 1 + // because index 0 means a key is not in the map. + mapping (bytes32 => uint256) _indexes; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function _set(Map storage map, bytes32 key, bytes32 value) private returns (bool) { + // We read and store the key's index to prevent multiple reads from the same storage slot + uint256 keyIndex = map._indexes[key]; + + if (keyIndex == 0) { // Equivalent to !contains(map, key) + map._entries.push(MapEntry({ _key: key, _value: value })); + // The entry is stored at length-1, but we add 1 to all indexes + // and use 0 as a sentinel value + map._indexes[key] = map._entries.length; + return true; + } else { + map._entries[keyIndex - 1]._value = value; + return false; + } + } + + /** + * @dev Removes a key-value pair from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function _remove(Map storage map, bytes32 key) private returns (bool) { + // We read and store the key's index to prevent multiple reads from the same storage slot + uint256 keyIndex = map._indexes[key]; + + if (keyIndex != 0) { // Equivalent to contains(map, key) + // To delete a key-value pair from the _entries array in O(1), we swap the entry to delete with the last one + // in the array, and then remove the last entry (sometimes called as 'swap and pop'). + // This modifies the order of the array, as noted in {at}. + + uint256 toDeleteIndex = keyIndex - 1; + uint256 lastIndex = map._entries.length - 1; + + // When the entry to delete is the last one, the swap operation is unnecessary. However, since this occurs + // so rarely, we still do the swap anyway to avoid the gas cost of adding an 'if' statement. + + MapEntry storage lastEntry = map._entries[lastIndex]; + + // Move the last entry to the index where the entry to delete is + map._entries[toDeleteIndex] = lastEntry; + // Update the index for the moved entry + map._indexes[lastEntry._key] = toDeleteIndex + 1; // All indexes are 1-based + + // Delete the slot where the moved entry was stored + map._entries.pop(); + + // Delete the index for the deleted slot + delete map._indexes[key]; + + return true; + } else { + return false; + } + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function _contains(Map storage map, bytes32 key) private view returns (bool) { + return map._indexes[key] != 0; + } + + /** + * @dev Returns the number of key-value pairs in the map. O(1). + */ + function _length(Map storage map) private view returns (uint256) { + return map._entries.length; + } + + /** + * @dev Returns the key-value pair stored at position `index` in the map. O(1). + * + * Note that there are no guarantees on the ordering of entries inside the + * array, and it may change when more entries are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function _at(Map storage map, uint256 index) private view returns (bytes32, bytes32) { + require(map._entries.length > index, "EnumerableMap: index out of bounds"); + + MapEntry storage entry = map._entries[index]; + return (entry._key, entry._value); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function _get(Map storage map, bytes32 key) private view returns (bytes32) { + return _get(map, key, "EnumerableMap: nonexistent key"); + } + + /** + * @dev Same as {_get}, with a custom error message when `key` is not in the map. + */ + function _get(Map storage map, bytes32 key, string memory errorMessage) private view returns (bytes32) { + uint256 keyIndex = map._indexes[key]; + require(keyIndex != 0, errorMessage); // Equivalent to contains(map, key) + return map._entries[keyIndex - 1]._value; // All indexes are 1-based + } + + // UintToAddressMap + + struct UintToAddressMap { + Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function set(UintToAddressMap storage map, uint256 key, address value) internal returns (bool) { + return _set(map._inner, bytes32(key), bytes32(uint256(value))); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(UintToAddressMap storage map, uint256 key) internal returns (bool) { + return _remove(map._inner, bytes32(key)); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(UintToAddressMap storage map, uint256 key) internal view returns (bool) { + return _contains(map._inner, bytes32(key)); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(UintToAddressMap storage map) internal view returns (uint256) { + return _length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the set. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) { + (bytes32 key, bytes32 value) = _at(map._inner, index); + return (uint256(key), address(uint256(value))); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(UintToAddressMap storage map, uint256 key) internal view returns (address) { + return address(uint256(_get(map._inner, bytes32(key)))); + } + + /** + * @dev Same as {get}, with a custom error message when `key` is not in the map. + */ + function get(UintToAddressMap storage map, uint256 key, string memory errorMessage) internal view returns (address) { + return address(uint256(_get(map._inner, bytes32(key), errorMessage))); + } +} diff --git a/contracts/libs/utils/EnumerableSet.sol b/contracts/libs/utils/EnumerableSet.sol new file mode 100644 index 0000000..482c479 --- /dev/null +++ b/contracts/libs/utils/EnumerableSet.sol @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.5.0; + +/** + * @dev Library for managing + * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive + * types. + * + * Sets have the following properties: + * + * - Elements are added, removed, and checked for existence in constant time + * (O(1)). + * - Elements are enumerated in O(n). No guarantees are made on the ordering. + * + * ``` + * contract Example { + * // Add the library methods + * using EnumerableSet for EnumerableSet.AddressSet; + * + * // Declare a set state variable + * EnumerableSet.AddressSet private mySet; + * } + * ``` + * + * As of v3.0.0, only sets of type `address` (`AddressSet`) and `uint256` + * (`UintSet`) are supported. + */ +library EnumerableSet { + // To implement this library for multiple types with as little code + // repetition as possible, we write it in terms of a generic Set type with + // bytes32 values. + // The Set implementation uses private functions, and user-facing + // implementations (such as AddressSet) are just wrappers around the + // underlying Set. + // This means that we can only create new EnumerableSets for types that fit + // in bytes32. + + struct Set { + // Storage of set values + bytes32[] _values; + + // Position of the value in the `values` array, plus 1 because index 0 + // means a value is not in the set. + mapping (bytes32 => uint256) _indexes; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function _add(Set storage set, bytes32 value) private returns (bool) { + if (!_contains(set, value)) { + set._values.push(value); + // The value is stored at length-1, but we add 1 to all indexes + // and use 0 as a sentinel value + set._indexes[value] = set._values.length; + return true; + } else { + return false; + } + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function _remove(Set storage set, bytes32 value) private returns (bool) { + // We read and store the value's index to prevent multiple reads from the same storage slot + uint256 valueIndex = set._indexes[value]; + + if (valueIndex != 0) { // Equivalent to contains(set, value) + // To delete an element from the _values array in O(1), we swap the element to delete with the last one in + // the array, and then remove the last element (sometimes called as 'swap and pop'). + // This modifies the order of the array, as noted in {at}. + + uint256 toDeleteIndex = valueIndex - 1; + uint256 lastIndex = set._values.length - 1; + + // When the value to delete is the last one, the swap operation is unnecessary. However, since this occurs + // so rarely, we still do the swap anyway to avoid the gas cost of adding an 'if' statement. + + bytes32 lastvalue = set._values[lastIndex]; + + // Move the last value to the index where the value to delete is + set._values[toDeleteIndex] = lastvalue; + // Update the index for the moved value + set._indexes[lastvalue] = toDeleteIndex + 1; // All indexes are 1-based + + // Delete the slot where the moved value was stored + set._values.pop(); + + // Delete the index for the deleted slot + delete set._indexes[value]; + + return true; + } else { + return false; + } + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function _contains(Set storage set, bytes32 value) private view returns (bool) { + return set._indexes[value] != 0; + } + + /** + * @dev Returns the number of values on the set. O(1). + */ + function _length(Set storage set) private view returns (uint256) { + return set._values.length; + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function _at(Set storage set, uint256 index) private view returns (bytes32) { + require(set._values.length > index, "EnumerableSet: index out of bounds"); + return set._values[index]; + } + + // AddressSet + + struct AddressSet { + Set _inner; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(AddressSet storage set, address value) internal returns (bool) { + return _add(set._inner, bytes32(uint256(value))); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(AddressSet storage set, address value) internal returns (bool) { + return _remove(set._inner, bytes32(uint256(value))); + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(AddressSet storage set, address value) internal view returns (bool) { + return _contains(set._inner, bytes32(uint256(value))); + } + + /** + * @dev Returns the number of values in the set. O(1). + */ + function length(AddressSet storage set) internal view returns (uint256) { + return _length(set._inner); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(AddressSet storage set, uint256 index) internal view returns (address) { + return address(uint256(_at(set._inner, index))); + } + + + // UintSet + + struct UintSet { + Set _inner; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(UintSet storage set, uint256 value) internal returns (bool) { + return _add(set._inner, bytes32(value)); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(UintSet storage set, uint256 value) internal returns (bool) { + return _remove(set._inner, bytes32(value)); + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(UintSet storage set, uint256 value) internal view returns (bool) { + return _contains(set._inner, bytes32(value)); + } + + /** + * @dev Returns the number of values on the set. O(1). + */ + function length(UintSet storage set) internal view returns (uint256) { + return _length(set._inner); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(UintSet storage set, uint256 index) internal view returns (uint256) { + return uint256(_at(set._inner, index)); + } +} diff --git a/contracts/libs/utils/Strings.sol b/contracts/libs/utils/Strings.sol new file mode 100644 index 0000000..c44fa64 --- /dev/null +++ b/contracts/libs/utils/Strings.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.5.0; + +/** + * @dev String operations. + */ +library Strings { + /** + * @dev Converts a `uint256` to its ASCII `string` representation. + */ + function toString(uint256 value) internal pure returns (string memory) { + // Inspired by OraclizeAPI's implementation - MIT licence + // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol + + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + uint256 index = digits - 1; + temp = value; + while (temp != 0) { + buffer[index--] = byte(uint8(48 + temp % 10)); + temp /= 10; + } + return string(buffer); + } +} diff --git a/contracts/libs/utils/Utils.sol b/contracts/libs/utils/Utils.sol index d70406d..8a65160 100644 --- a/contracts/libs/utils/Utils.sol +++ b/contracts/libs/utils/Utils.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5.0; +pragma solidity >=0.5.0; library Utils {