Contract Name:
ERC721Facet
Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { EnumerableSet } from '../../data/EnumerableSet.sol';
import { AddressUtils } from '../../utils/AddressUtils.sol';
import { UintUtils } from '../../utils/UintUtils.sol';
import { IAccessControlInternal } from './IAccessControlInternal.sol';
import { AccessControlStorage } from './AccessControlStorage.sol';
/**
* @title Role-based access control system
* @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts (MIT license)
*/
abstract contract AccessControlInternal is IAccessControlInternal {
using AddressUtils for address;
using EnumerableSet for EnumerableSet.AddressSet;
using UintUtils for uint256;
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
/*
* @notice query whether role is assigned to account
* @param role role to query
* @param account account to query
* @return whether role is assigned to account
*/
function _hasRole(
bytes32 role,
address account
) internal view virtual returns (bool) {
return
AccessControlStorage.layout().roles[role].members.contains(account);
}
/**
* @notice revert if sender does not have given role
* @param role role to query
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, msg.sender);
}
/**
* @notice revert if given account does not have given role
* @param role role to query
* @param account to query
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!_hasRole(role, account)) {
revert(
string(
abi.encodePacked(
'AccessControl: account ',
account.toString(),
' is missing role ',
uint256(role).toHexString(32)
)
)
);
}
}
/*
* @notice query admin role for given role
* @param role role to query
* @return admin role
*/
function _getRoleAdmin(
bytes32 role
) internal view virtual returns (bytes32) {
return AccessControlStorage.layout().roles[role].adminRole;
}
/**
* @notice set role as admin role
* @param role role to set
* @param adminRole admin role to set
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = _getRoleAdmin(role);
AccessControlStorage.layout().roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/*
* @notice assign role to given account
* @param role role to assign
* @param account recipient of role assignment
*/
function _grantRole(bytes32 role, address account) internal virtual {
AccessControlStorage.layout().roles[role].members.add(account);
emit RoleGranted(role, account, msg.sender);
}
/*
* @notice unassign role from given account
* @param role role to unassign
* @parm account
*/
function _revokeRole(bytes32 role, address account) internal virtual {
AccessControlStorage.layout().roles[role].members.remove(account);
emit RoleRevoked(role, account, msg.sender);
}
/**
* @notice relinquish role
* @param role role to relinquish
*/
function _renounceRole(bytes32 role) internal virtual {
_revokeRole(role, msg.sender);
}
/**
* @notice query role for member at given index
* @param role role to query
* @param index index to query
*/
function _getRoleMember(
bytes32 role,
uint256 index
) internal view virtual returns (address) {
return AccessControlStorage.layout().roles[role].members.at(index);
}
/**
* @notice query role for member count
* @param role role to query
*/
function _getRoleMemberCount(
bytes32 role
) internal view virtual returns (uint256) {
return AccessControlStorage.layout().roles[role].members.length();
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { EnumerableSet } from '../../data/EnumerableSet.sol';
library AccessControlStorage {
struct RoleData {
EnumerableSet.AddressSet members;
bytes32 adminRole;
}
struct Layout {
mapping(bytes32 => RoleData) roles;
}
bytes32 internal constant DEFAULT_ADMIN_ROLE = 0x00;
bytes32 internal constant STORAGE_SLOT =
keccak256('solidstate.contracts.storage.AccessControl');
function layout() internal pure returns (Layout storage l) {
bytes32 slot = STORAGE_SLOT;
assembly {
l.slot := slot
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title Partial AccessControl interface needed by internal functions
*/
interface IAccessControlInternal {
event RoleAdminChanged(
bytes32 indexed role,
bytes32 indexed previousAdminRole,
bytes32 indexed newAdminRole
);
event RoleGranted(
bytes32 indexed role,
address indexed account,
address indexed sender
);
event RoleRevoked(
bytes32 indexed role,
address indexed account,
address indexed sender
);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
/**
* @title Map implementation with enumeration functions
* @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts (MIT license)
*/
library EnumerableMap {
error EnumerableMap__IndexOutOfBounds();
error EnumerableMap__NonExistentKey();
struct MapEntry {
bytes32 _key;
bytes32 _value;
}
struct Map {
MapEntry[] _entries;
// 1-indexed to allow 0 to signify nonexistence
mapping(bytes32 => uint256) _indexes;
}
struct AddressToAddressMap {
Map _inner;
}
struct UintToAddressMap {
Map _inner;
}
function at(
AddressToAddressMap storage map,
uint256 index
) internal view returns (address, address) {
(bytes32 key, bytes32 value) = _at(map._inner, index);
return (
address(uint160(uint256(key))),
address(uint160(uint256(value)))
);
}
function at(
UintToAddressMap storage map,
uint256 index
) internal view returns (uint256, address) {
(bytes32 key, bytes32 value) = _at(map._inner, index);
return (uint256(key), address(uint160(uint256(value))));
}
function contains(
AddressToAddressMap storage map,
address key
) internal view returns (bool) {
return _contains(map._inner, bytes32(uint256(uint160(key))));
}
function contains(
UintToAddressMap storage map,
uint256 key
) internal view returns (bool) {
return _contains(map._inner, bytes32(key));
}
function length(
AddressToAddressMap storage map
) internal view returns (uint256) {
return _length(map._inner);
}
function length(
UintToAddressMap storage map
) internal view returns (uint256) {
return _length(map._inner);
}
function get(
AddressToAddressMap storage map,
address key
) internal view returns (address) {
return
address(
uint160(
uint256(_get(map._inner, bytes32(uint256(uint160(key)))))
)
);
}
function get(
UintToAddressMap storage map,
uint256 key
) internal view returns (address) {
return address(uint160(uint256(_get(map._inner, bytes32(key)))));
}
function set(
AddressToAddressMap storage map,
address key,
address value
) internal returns (bool) {
return
_set(
map._inner,
bytes32(uint256(uint160(key))),
bytes32(uint256(uint160(value)))
);
}
function set(
UintToAddressMap storage map,
uint256 key,
address value
) internal returns (bool) {
return _set(map._inner, bytes32(key), bytes32(uint256(uint160(value))));
}
function remove(
AddressToAddressMap storage map,
address key
) internal returns (bool) {
return _remove(map._inner, bytes32(uint256(uint160(key))));
}
function remove(
UintToAddressMap storage map,
uint256 key
) internal returns (bool) {
return _remove(map._inner, bytes32(key));
}
function toArray(
AddressToAddressMap storage map
)
internal
view
returns (address[] memory keysOut, address[] memory valuesOut)
{
uint256 len = map._inner._entries.length;
keysOut = new address[](len);
valuesOut = new address[](len);
unchecked {
for (uint256 i; i < len; ++i) {
keysOut[i] = address(
uint160(uint256(map._inner._entries[i]._key))
);
valuesOut[i] = address(
uint160(uint256(map._inner._entries[i]._value))
);
}
}
}
function toArray(
UintToAddressMap storage map
)
internal
view
returns (uint256[] memory keysOut, address[] memory valuesOut)
{
uint256 len = map._inner._entries.length;
keysOut = new uint256[](len);
valuesOut = new address[](len);
unchecked {
for (uint256 i; i < len; ++i) {
keysOut[i] = uint256(map._inner._entries[i]._key);
valuesOut[i] = address(
uint160(uint256(map._inner._entries[i]._value))
);
}
}
}
function keys(
AddressToAddressMap storage map
) internal view returns (address[] memory keysOut) {
uint256 len = map._inner._entries.length;
keysOut = new address[](len);
unchecked {
for (uint256 i; i < len; ++i) {
keysOut[i] = address(
uint160(uint256(map._inner._entries[i]._key))
);
}
}
}
function keys(
UintToAddressMap storage map
) internal view returns (uint256[] memory keysOut) {
uint256 len = map._inner._entries.length;
keysOut = new uint256[](len);
unchecked {
for (uint256 i; i < len; ++i) {
keysOut[i] = uint256(map._inner._entries[i]._key);
}
}
}
function values(
AddressToAddressMap storage map
) internal view returns (address[] memory valuesOut) {
uint256 len = map._inner._entries.length;
valuesOut = new address[](len);
unchecked {
for (uint256 i; i < len; ++i) {
valuesOut[i] = address(
uint160(uint256(map._inner._entries[i]._value))
);
}
}
}
function values(
UintToAddressMap storage map
) internal view returns (address[] memory valuesOut) {
uint256 len = map._inner._entries.length;
valuesOut = new address[](len);
unchecked {
for (uint256 i; i < len; ++i) {
valuesOut[i] = address(
uint160(uint256(map._inner._entries[i]._value))
);
}
}
}
function _at(
Map storage map,
uint256 index
) private view returns (bytes32, bytes32) {
if (index >= map._entries.length)
revert EnumerableMap__IndexOutOfBounds();
MapEntry storage entry = map._entries[index];
return (entry._key, entry._value);
}
function _contains(
Map storage map,
bytes32 key
) private view returns (bool) {
return map._indexes[key] != 0;
}
function _length(Map storage map) private view returns (uint256) {
return map._entries.length;
}
function _get(Map storage map, bytes32 key) private view returns (bytes32) {
uint256 keyIndex = map._indexes[key];
if (keyIndex == 0) revert EnumerableMap__NonExistentKey();
unchecked {
return map._entries[keyIndex - 1]._value;
}
}
function _set(
Map storage map,
bytes32 key,
bytes32 value
) private returns (bool) {
uint256 keyIndex = map._indexes[key];
if (keyIndex == 0) {
map._entries.push(MapEntry({ _key: key, _value: value }));
map._indexes[key] = map._entries.length;
return true;
} else {
unchecked {
map._entries[keyIndex - 1]._value = value;
}
return false;
}
}
function _remove(Map storage map, bytes32 key) private returns (bool) {
uint256 keyIndex = map._indexes[key];
if (keyIndex != 0) {
unchecked {
MapEntry storage last = map._entries[map._entries.length - 1];
// move last entry to now-vacant index
map._entries[keyIndex - 1] = last;
map._indexes[last._key] = keyIndex;
}
// clear last index
map._entries.pop();
delete map._indexes[key];
return true;
} else {
return false;
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
/**
* @title Set implementation with enumeration functions
* @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts (MIT license)
*/
library EnumerableSet {
error EnumerableSet__IndexOutOfBounds();
struct Set {
bytes32[] _values;
// 1-indexed to allow 0 to signify nonexistence
mapping(bytes32 => uint256) _indexes;
}
struct Bytes32Set {
Set _inner;
}
struct AddressSet {
Set _inner;
}
struct UintSet {
Set _inner;
}
function at(
Bytes32Set storage set,
uint256 index
) internal view returns (bytes32) {
return _at(set._inner, index);
}
function at(
AddressSet storage set,
uint256 index
) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
function at(
UintSet storage set,
uint256 index
) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
function contains(
Bytes32Set storage set,
bytes32 value
) internal view returns (bool) {
return _contains(set._inner, value);
}
function contains(
AddressSet storage set,
address value
) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
function contains(
UintSet storage set,
uint256 value
) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
function indexOf(
Bytes32Set storage set,
bytes32 value
) internal view returns (uint256) {
return _indexOf(set._inner, value);
}
function indexOf(
AddressSet storage set,
address value
) internal view returns (uint256) {
return _indexOf(set._inner, bytes32(uint256(uint160(value))));
}
function indexOf(
UintSet storage set,
uint256 value
) internal view returns (uint256) {
return _indexOf(set._inner, bytes32(value));
}
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
function add(
Bytes32Set storage set,
bytes32 value
) internal returns (bool) {
return _add(set._inner, value);
}
function add(
AddressSet storage set,
address value
) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
function remove(
Bytes32Set storage set,
bytes32 value
) internal returns (bool) {
return _remove(set._inner, value);
}
function remove(
AddressSet storage set,
address value
) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
function remove(
UintSet storage set,
uint256 value
) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
function toArray(
Bytes32Set storage set
) internal view returns (bytes32[] memory) {
return set._inner._values;
}
function toArray(
AddressSet storage set
) internal view returns (address[] memory) {
bytes32[] storage values = set._inner._values;
address[] storage array;
assembly {
array.slot := values.slot
}
return array;
}
function toArray(
UintSet storage set
) internal view returns (uint256[] memory) {
bytes32[] storage values = set._inner._values;
uint256[] storage array;
assembly {
array.slot := values.slot
}
return array;
}
function _at(
Set storage set,
uint256 index
) private view returns (bytes32) {
if (index >= set._values.length)
revert EnumerableSet__IndexOutOfBounds();
return set._values[index];
}
function _contains(
Set storage set,
bytes32 value
) private view returns (bool) {
return set._indexes[value] != 0;
}
function _indexOf(
Set storage set,
bytes32 value
) private view returns (uint256) {
unchecked {
return set._indexes[value] - 1;
}
}
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
function _add(
Set storage set,
bytes32 value
) private returns (bool status) {
if (!_contains(set, value)) {
set._values.push(value);
set._indexes[value] = set._values.length;
status = true;
}
}
function _remove(
Set storage set,
bytes32 value
) private returns (bool status) {
uint256 valueIndex = set._indexes[value];
if (valueIndex != 0) {
unchecked {
bytes32 last = set._values[set._values.length - 1];
// move last value to now-vacant index
set._values[valueIndex - 1] = last;
set._indexes[last] = valueIndex;
}
// clear last index
set._values.pop();
delete set._indexes[value];
status = true;
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
import { IERC165Internal } from './IERC165Internal.sol';
/**
* @title ERC165 interface registration interface
* @dev see https://eips.ethereum.org/EIPS/eip-165
*/
interface IERC165 is IERC165Internal {
/**
* @notice query whether contract has registered support for given interface
* @param interfaceId interface id
* @return bool whether interface is supported
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
/**
* @title ERC165 interface registration interface
*/
interface IERC165Internal {
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
import { IERC165 } from './IERC165.sol';
import { IERC721Internal } from './IERC721Internal.sol';
/**
* @title ERC721 interface
* @dev see https://eips.ethereum.org/EIPS/eip-721
*/
interface IERC721 is IERC721Internal, IERC165 {
/**
* @notice query the balance of given address
* @return balance quantity of tokens held
*/
function balanceOf(address account) external view returns (uint256 balance);
/**
* @notice query the owner of given token
* @param tokenId token to query
* @return owner token owner
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @notice transfer token between given addresses, checking for ERC721Receiver implementation if applicable
* @param from sender of token
* @param to receiver of token
* @param tokenId token id
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external payable;
/**
* @notice transfer token between given addresses, checking for ERC721Receiver implementation if applicable
* @param from sender of token
* @param to receiver of token
* @param tokenId token id
* @param data data payload
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
) external payable;
/**
* @notice transfer token between given addresses, without checking for ERC721Receiver implementation if applicable
* @param from sender of token
* @param to receiver of token
* @param tokenId token id
*/
function transferFrom(
address from,
address to,
uint256 tokenId
) external payable;
/**
* @notice grant approval to given account to spend token
* @param operator address to be approved
* @param tokenId token to approve
*/
function approve(address operator, uint256 tokenId) external payable;
/**
* @notice get approval status for given token
* @param tokenId token to query
* @return operator address approved to spend token
*/
function getApproved(
uint256 tokenId
) external view returns (address operator);
/**
* @notice grant approval to or revoke approval from given account to spend all tokens held by sender
* @param operator address to be approved
* @param status approval status
*/
function setApprovalForAll(address operator, bool status) external;
/**
* @notice query approval status of given operator with respect to given address
* @param account address to query for approval granted
* @param operator address to query for approval received
* @return status whether operator is approved to spend tokens held by account
*/
function isApprovedForAll(
address account,
address operator
) external view returns (bool status);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
/**
* @title Partial ERC721 interface needed by internal functions
*/
interface IERC721Internal {
event Transfer(
address indexed from,
address indexed to,
uint256 indexed tokenId
);
event Approval(
address indexed owner,
address indexed operator,
uint256 indexed tokenId
);
event ApprovalForAll(
address indexed owner,
address indexed operator,
bool approved
);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
interface IERC721Receiver {
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
import { IERC165BaseInternal } from './IERC165BaseInternal.sol';
import { ERC165BaseStorage } from './ERC165BaseStorage.sol';
/**
* @title ERC165 implementation
*/
abstract contract ERC165BaseInternal is IERC165BaseInternal {
/**
* @notice indicates whether an interface is already supported based on the interfaceId
* @param interfaceId id of interface to check
* @return bool indicating whether interface is supported
*/
function _supportsInterface(
bytes4 interfaceId
) internal view virtual returns (bool) {
return ERC165BaseStorage.layout().supportedInterfaces[interfaceId];
}
/**
* @notice sets status of interface support
* @param interfaceId id of interface to set status for
* @param status boolean indicating whether interface will be set as supported
*/
function _setSupportsInterface(
bytes4 interfaceId,
bool status
) internal virtual {
if (interfaceId == 0xffffffff) revert ERC165Base__InvalidInterfaceId();
ERC165BaseStorage.layout().supportedInterfaces[interfaceId] = status;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
library ERC165BaseStorage {
struct Layout {
mapping(bytes4 => bool) supportedInterfaces;
}
bytes32 internal constant STORAGE_SLOT =
keccak256('solidstate.contracts.storage.ERC165Base');
function layout() internal pure returns (Layout storage l) {
bytes32 slot = STORAGE_SLOT;
assembly {
l.slot := slot
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { IERC165Internal } from '../../../interfaces/IERC165Internal.sol';
interface IERC165BaseInternal is IERC165Internal {
error ERC165Base__InvalidInterfaceId();
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
interface IPartiallyPausableInternal {
error PartiallyPausable__PartiallyPaused();
error PartiallyPausable__NotPartiallyPaused();
event PartiallyPaused(address account, bytes32 key);
event PartiallyUnpaused(address account, bytes32 key);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
import { IPartiallyPausableInternal } from './IPartiallyPausableInternal.sol';
import { PartiallyPausableStorage } from './PartiallyPausableStorage.sol';
/**
* @title Internal functions for PartiallyPausable security control module.
*/
abstract contract PartiallyPausableInternal is IPartiallyPausableInternal {
modifier whenNotPartiallyPaused(bytes32 key) {
if (_partiallyPaused(key)) revert PartiallyPausable__PartiallyPaused();
_;
}
modifier whenPartiallyPaused(bytes32 key) {
if (!_partiallyPaused(key))
revert PartiallyPausable__NotPartiallyPaused();
_;
}
/**
* @notice query whether contract is paused in the scope of the given key
* @return status whether contract is paused in the scope of the given key
*/
function _partiallyPaused(
bytes32 key
) internal view virtual returns (bool status) {
status = PartiallyPausableStorage.layout().partiallyPaused[key];
}
/**
* @notice pause contract in the scope of given key
* @param key key whose scope to pause
*/
function _partiallyPause(
bytes32 key
) internal virtual whenNotPartiallyPaused(key) {
PartiallyPausableStorage.layout().partiallyPaused[key] = true;
emit PartiallyPaused(msg.sender, key);
}
/**
* @notice unpause contract in the scope of given key
* @param key key whose scope to unpause
*/
function _partiallyUnpause(
bytes32 key
) internal virtual whenPartiallyPaused(key) {
delete PartiallyPausableStorage.layout().partiallyPaused[key];
emit PartiallyUnpaused(msg.sender, key);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
library PartiallyPausableStorage {
struct Layout {
mapping(bytes32 => bool) partiallyPaused;
}
bytes32 internal constant STORAGE_SLOT =
keccak256('solidstate.contracts.storage.PartiallyPausable');
function layout() internal pure returns (Layout storage l) {
bytes32 slot = STORAGE_SLOT;
assembly {
l.slot := slot
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
interface IPausableInternal {
error Pausable__Paused();
error Pausable__NotPaused();
event Paused(address account);
event Unpaused(address account);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
import { IPausableInternal } from './IPausableInternal.sol';
import { PausableStorage } from './PausableStorage.sol';
/**
* @title Internal functions for Pausable security control module.
*/
abstract contract PausableInternal is IPausableInternal {
modifier whenNotPaused() {
if (_paused()) revert Pausable__Paused();
_;
}
modifier whenPaused() {
if (!_paused()) revert Pausable__NotPaused();
_;
}
/**
* @notice query whether contract is paused
* @return status whether contract is paused
*/
function _paused() internal view virtual returns (bool status) {
status = PausableStorage.layout().paused;
}
/**
* @notice Triggers paused state, when contract is unpaused.
*/
function _pause() internal virtual whenNotPaused {
PausableStorage.layout().paused = true;
emit Paused(msg.sender);
}
/**
* @notice Triggers unpaused state, when contract is paused.
*/
function _unpause() internal virtual whenPaused {
delete PausableStorage.layout().paused;
emit Unpaused(msg.sender);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
library PausableStorage {
struct Layout {
bool paused;
}
bytes32 internal constant STORAGE_SLOT =
keccak256('solidstate.contracts.storage.Pausable');
function layout() internal pure returns (Layout storage l) {
bytes32 slot = STORAGE_SLOT;
assembly {
l.slot := slot
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
import { IERC721 } from '../../../interfaces/IERC721.sol';
import { IERC721Base } from './IERC721Base.sol';
import { ERC721BaseInternal } from './ERC721BaseInternal.sol';
/**
* @title Base ERC721 implementation, excluding optional extensions
* @dev inheritor must either implement ERC165 supportsInterface or inherit ERC165Base
*/
abstract contract ERC721Base is IERC721Base, ERC721BaseInternal {
/**
* @inheritdoc IERC721
*/
function balanceOf(address account) external view returns (uint256) {
return _balanceOf(account);
}
/**
* @inheritdoc IERC721
*/
function ownerOf(uint256 tokenId) external view returns (address) {
return _ownerOf(tokenId);
}
/**
* @inheritdoc IERC721
*/
function getApproved(uint256 tokenId) external view returns (address) {
return _getApproved(tokenId);
}
/**
* @inheritdoc IERC721
*/
function isApprovedForAll(
address account,
address operator
) external view returns (bool) {
return _isApprovedForAll(account, operator);
}
/**
* @inheritdoc IERC721
*/
function transferFrom(
address from,
address to,
uint256 tokenId
) external payable {
_transferFrom(from, to, tokenId);
}
/**
* @inheritdoc IERC721
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external payable {
_safeTransferFrom(from, to, tokenId);
}
/**
* @inheritdoc IERC721
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes memory data
) external payable {
_safeTransferFrom(from, to, tokenId, data);
}
/**
* @inheritdoc IERC721
*/
function approve(address operator, uint256 tokenId) external payable {
_approve(operator, tokenId);
}
/**
* @inheritdoc IERC721
*/
function setApprovalForAll(address operator, bool status) external {
_setApprovalForAll(operator, status);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
import { IERC721Receiver } from '../../../interfaces/IERC721Receiver.sol';
import { EnumerableMap } from '../../../data/EnumerableMap.sol';
import { EnumerableSet } from '../../../data/EnumerableSet.sol';
import { AddressUtils } from '../../../utils/AddressUtils.sol';
import { IERC721BaseInternal } from './IERC721BaseInternal.sol';
import { ERC721BaseStorage } from './ERC721BaseStorage.sol';
/**
* @title Base ERC721 internal functions
*/
abstract contract ERC721BaseInternal is IERC721BaseInternal {
using AddressUtils for address;
using EnumerableMap for EnumerableMap.UintToAddressMap;
using EnumerableSet for EnumerableSet.UintSet;
function _balanceOf(
address account
) internal view virtual returns (uint256) {
if (account == address(0)) revert ERC721Base__BalanceQueryZeroAddress();
return ERC721BaseStorage.layout().holderTokens[account].length();
}
function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
address owner = ERC721BaseStorage.layout().tokenOwners.get(tokenId);
if (owner == address(0)) revert ERC721Base__InvalidOwner();
return owner;
}
function _exists(uint256 tokenId) internal view virtual returns (bool) {
return ERC721BaseStorage.layout().tokenOwners.contains(tokenId);
}
function _getApproved(
uint256 tokenId
) internal view virtual returns (address) {
if (!_exists(tokenId)) revert ERC721Base__NonExistentToken();
return ERC721BaseStorage.layout().tokenApprovals[tokenId];
}
function _isApprovedForAll(
address account,
address operator
) internal view virtual returns (bool) {
return ERC721BaseStorage.layout().operatorApprovals[account][operator];
}
function _isApprovedOrOwner(
address spender,
uint256 tokenId
) internal view virtual returns (bool) {
if (!_exists(tokenId)) revert ERC721Base__NonExistentToken();
address owner = _ownerOf(tokenId);
return (spender == owner ||
_getApproved(tokenId) == spender ||
_isApprovedForAll(owner, spender));
}
function _mint(address to, uint256 tokenId) internal virtual {
if (to == address(0)) revert ERC721Base__MintToZeroAddress();
if (_exists(tokenId)) revert ERC721Base__TokenAlreadyMinted();
_beforeTokenTransfer(address(0), to, tokenId);
ERC721BaseStorage.Layout storage l = ERC721BaseStorage.layout();
l.holderTokens[to].add(tokenId);
l.tokenOwners.set(tokenId, to);
emit Transfer(address(0), to, tokenId);
}
function _safeMint(address to, uint256 tokenId) internal virtual {
_safeMint(to, tokenId, '');
}
function _safeMint(
address to,
uint256 tokenId,
bytes memory data
) internal virtual {
_mint(to, tokenId);
if (!_checkOnERC721Received(address(0), to, tokenId, data))
revert ERC721Base__ERC721ReceiverNotImplemented();
}
function _burn(uint256 tokenId) internal virtual {
address owner = _ownerOf(tokenId);
_beforeTokenTransfer(owner, address(0), tokenId);
ERC721BaseStorage.Layout storage l = ERC721BaseStorage.layout();
l.holderTokens[owner].remove(tokenId);
l.tokenOwners.remove(tokenId);
l.tokenApprovals[tokenId] = address(0);
emit Approval(owner, address(0), tokenId);
emit Transfer(owner, address(0), tokenId);
}
function _transfer(
address from,
address to,
uint256 tokenId
) internal virtual {
address owner = _ownerOf(tokenId);
if (owner != from) revert ERC721Base__NotTokenOwner();
if (to == address(0)) revert ERC721Base__TransferToZeroAddress();
_beforeTokenTransfer(from, to, tokenId);
ERC721BaseStorage.Layout storage l = ERC721BaseStorage.layout();
l.holderTokens[from].remove(tokenId);
l.holderTokens[to].add(tokenId);
l.tokenOwners.set(tokenId, to);
l.tokenApprovals[tokenId] = address(0);
emit Approval(owner, address(0), tokenId);
emit Transfer(from, to, tokenId);
}
function _transferFrom(
address from,
address to,
uint256 tokenId
) internal virtual {
_handleTransferMessageValue(from, to, tokenId, msg.value);
if (!_isApprovedOrOwner(msg.sender, tokenId))
revert ERC721Base__NotOwnerOrApproved();
_transfer(from, to, tokenId);
}
function _safeTransfer(
address from,
address to,
uint256 tokenId,
bytes memory data
) internal virtual {
_transfer(from, to, tokenId);
if (!_checkOnERC721Received(from, to, tokenId, data))
revert ERC721Base__ERC721ReceiverNotImplemented();
}
function _safeTransferFrom(
address from,
address to,
uint256 tokenId
) internal virtual {
_safeTransferFrom(from, to, tokenId, '');
}
function _safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes memory data
) internal virtual {
_handleTransferMessageValue(from, to, tokenId, msg.value);
if (!_isApprovedOrOwner(msg.sender, tokenId))
revert ERC721Base__NotOwnerOrApproved();
_safeTransfer(from, to, tokenId, data);
}
function _approve(address operator, uint256 tokenId) internal virtual {
_handleApproveMessageValue(operator, tokenId, msg.value);
address owner = _ownerOf(tokenId);
if (operator == owner) revert ERC721Base__SelfApproval();
if (msg.sender != owner && !_isApprovedForAll(owner, msg.sender))
revert ERC721Base__NotOwnerOrApproved();
ERC721BaseStorage.layout().tokenApprovals[tokenId] = operator;
emit Approval(owner, operator, tokenId);
}
function _setApprovalForAll(
address operator,
bool status
) internal virtual {
if (operator == msg.sender) revert ERC721Base__SelfApproval();
ERC721BaseStorage.layout().operatorApprovals[msg.sender][
operator
] = status;
emit ApprovalForAll(msg.sender, operator, status);
}
function _checkOnERC721Received(
address from,
address to,
uint256 tokenId,
bytes memory data
) internal virtual returns (bool) {
if (!to.isContract()) {
return true;
}
bytes memory returnData = to.functionCall(
abi.encodeWithSelector(
IERC721Receiver(to).onERC721Received.selector,
msg.sender,
from,
tokenId,
data
),
'ERC721: transfer to non ERC721Receiver implementer'
);
bytes4 returnValue = abi.decode(returnData, (bytes4));
return returnValue == type(IERC721Receiver).interfaceId;
}
/**
* @notice ERC721 hook, called before externally called approvals for processing of included message value
* @param operator beneficiary of approval
* @param tokenId id of transferred token
* @param value message value
*/
function _handleApproveMessageValue(
address operator,
uint256 tokenId,
uint256 value
) internal virtual {}
/**
* @notice ERC721 hook, called before externally called transfers for processing of included message value
* @param from sender of token
* @param to receiver of token
* @param tokenId id of transferred token
* @param value message value
*/
function _handleTransferMessageValue(
address from,
address to,
uint256 tokenId,
uint256 value
) internal virtual {}
/**
* @notice ERC721 hook, called before all transfers including mint and burn
* @dev function should be overridden and new implementation must call super
* @param from sender of token
* @param to receiver of token
* @param tokenId id of transferred token
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual {}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
import { EnumerableMap } from '../../../data/EnumerableMap.sol';
import { EnumerableSet } from '../../../data/EnumerableSet.sol';
library ERC721BaseStorage {
using EnumerableSet for EnumerableSet.UintSet;
using EnumerableMap for EnumerableMap.UintToAddressMap;
bytes32 internal constant STORAGE_SLOT =
keccak256('solidstate.contracts.storage.ERC721Base');
struct Layout {
EnumerableMap.UintToAddressMap tokenOwners;
mapping(address => EnumerableSet.UintSet) holderTokens;
mapping(uint256 => address) tokenApprovals;
mapping(address => mapping(address => bool)) operatorApprovals;
}
function layout() internal pure returns (Layout storage l) {
bytes32 slot = STORAGE_SLOT;
assembly {
l.slot := slot
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
import { IERC721 } from '../../../interfaces/IERC721.sol';
import { IERC721BaseInternal } from './IERC721BaseInternal.sol';
/**
* @title ERC721 base interface
*/
interface IERC721Base is IERC721BaseInternal, IERC721 {
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
import { IERC721Internal } from '../../../interfaces/IERC721Internal.sol';
/**
* @title ERC721 base interface
*/
interface IERC721BaseInternal is IERC721Internal {
error ERC721Base__NotOwnerOrApproved();
error ERC721Base__SelfApproval();
error ERC721Base__BalanceQueryZeroAddress();
error ERC721Base__ERC721ReceiverNotImplemented();
error ERC721Base__InvalidOwner();
error ERC721Base__MintToZeroAddress();
error ERC721Base__NonExistentToken();
error ERC721Base__NotTokenOwner();
error ERC721Base__TokenAlreadyMinted();
error ERC721Base__TransferToZeroAddress();
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
import { EnumerableMap } from '../../../data/EnumerableMap.sol';
import { EnumerableSet } from '../../../data/EnumerableSet.sol';
import { ERC721BaseStorage } from '../base/ERC721BaseStorage.sol';
import { IERC721Enumerable } from './IERC721Enumerable.sol';
import { ERC721EnumerableInternal } from './ERC721EnumerableInternal.sol';
abstract contract ERC721Enumerable is
IERC721Enumerable,
ERC721EnumerableInternal
{
using EnumerableMap for EnumerableMap.UintToAddressMap;
using EnumerableSet for EnumerableSet.UintSet;
/**
* @inheritdoc IERC721Enumerable
*/
function totalSupply() public view returns (uint256) {
return _totalSupply();
}
/**
* @inheritdoc IERC721Enumerable
*/
function tokenOfOwnerByIndex(
address owner,
uint256 index
) public view returns (uint256) {
return _tokenOfOwnerByIndex(owner, index);
}
/**
* @inheritdoc IERC721Enumerable
*/
function tokenByIndex(uint256 index) public view returns (uint256) {
return _tokenByIndex(index);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
import { EnumerableMap } from '../../../data/EnumerableMap.sol';
import { EnumerableSet } from '../../../data/EnumerableSet.sol';
import { ERC721BaseStorage } from '../base/ERC721BaseStorage.sol';
abstract contract ERC721EnumerableInternal {
using EnumerableMap for EnumerableMap.UintToAddressMap;
using EnumerableSet for EnumerableSet.UintSet;
/**
* @notice TODO
*/
function _totalSupply() internal view returns (uint256) {
return ERC721BaseStorage.layout().tokenOwners.length();
}
/**
* @notice TODO
*/
function _tokenOfOwnerByIndex(
address owner,
uint256 index
) internal view returns (uint256) {
return ERC721BaseStorage.layout().holderTokens[owner].at(index);
}
/**
* @notice TODO
*/
function _tokenByIndex(
uint256 index
) internal view returns (uint256 tokenId) {
(tokenId, ) = ERC721BaseStorage.layout().tokenOwners.at(index);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
interface IERC721Enumerable {
/**
* @notice get total token supply
* @return total supply
*/
function totalSupply() external view returns (uint256);
/**
* @notice get token of given owner at given internal storage index
* @param owner token holder to query
* @param index position in owner's token list to query
* @return tokenId id of retrieved token
*/
function tokenOfOwnerByIndex(
address owner,
uint256 index
) external view returns (uint256 tokenId);
/**
* @notice get token at given internal storage index
* @param index position in global token list to query
* @return tokenId id of retrieved token
*/
function tokenByIndex(
uint256 index
) external view returns (uint256 tokenId);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
import { ERC721MetadataInternal } from './ERC721MetadataInternal.sol';
import { IERC721Metadata } from './IERC721Metadata.sol';
/**
* @title ERC721 metadata extensions
*/
abstract contract ERC721Metadata is IERC721Metadata, ERC721MetadataInternal {
/**
* @notice inheritdoc IERC721Metadata
*/
function name() external view virtual returns (string memory) {
return _name();
}
/**
* @notice inheritdoc IERC721Metadata
*/
function symbol() external view virtual returns (string memory) {
return _symbol();
}
/**
* @notice inheritdoc IERC721Metadata
*/
function tokenURI(
uint256 tokenId
) external view virtual returns (string memory) {
return _tokenURI(tokenId);
}
/**
* @inheritdoc ERC721MetadataInternal
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual override {
super._beforeTokenTransfer(from, to, tokenId);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
import { UintUtils } from '../../../utils/UintUtils.sol';
import { ERC721BaseStorage } from '../base/ERC721BaseStorage.sol';
import { ERC721BaseInternal } from '../base/ERC721Base.sol';
import { IERC721MetadataInternal } from './IERC721MetadataInternal.sol';
import { ERC721MetadataStorage } from './ERC721MetadataStorage.sol';
/**
* @title ERC721Metadata internal functions
*/
abstract contract ERC721MetadataInternal is
IERC721MetadataInternal,
ERC721BaseInternal
{
using UintUtils for uint256;
/**
* @notice get token name
* @return token name
*/
function _name() internal view virtual returns (string memory) {
return ERC721MetadataStorage.layout().name;
}
/**
* @notice get token symbol
* @return token symbol
*/
function _symbol() internal view virtual returns (string memory) {
return ERC721MetadataStorage.layout().symbol;
}
/**
* @notice get generated URI for given token
* @return token URI
*/
function _tokenURI(
uint256 tokenId
) internal view virtual returns (string memory) {
if (!_exists(tokenId)) revert ERC721Metadata__NonExistentToken();
ERC721MetadataStorage.Layout storage l = ERC721MetadataStorage.layout();
string memory tokenIdURI = l.tokenURIs[tokenId];
string memory baseURI = l.baseURI;
if (bytes(baseURI).length == 0) {
return tokenIdURI;
} else if (bytes(tokenIdURI).length > 0) {
return string(abi.encodePacked(baseURI, tokenIdURI));
} else {
return string(abi.encodePacked(baseURI, tokenId.toString()));
}
}
/**
* @notice ERC721 hook: clear per-token URI data on burn
* @inheritdoc ERC721BaseInternal
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual override {
super._beforeTokenTransfer(from, to, tokenId);
if (to == address(0)) {
delete ERC721MetadataStorage.layout().tokenURIs[tokenId];
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
library ERC721MetadataStorage {
bytes32 internal constant STORAGE_SLOT =
keccak256('solidstate.contracts.storage.ERC721Metadata');
struct Layout {
string name;
string symbol;
string baseURI;
mapping(uint256 => string) tokenURIs;
}
function layout() internal pure returns (Layout storage l) {
bytes32 slot = STORAGE_SLOT;
assembly {
l.slot := slot
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
import { IERC721MetadataInternal } from './IERC721MetadataInternal.sol';
/**
* @title ERC721Metadata interface
*/
interface IERC721Metadata is IERC721MetadataInternal {
/**
* @notice get token name
* @return token name
*/
function name() external view returns (string memory);
/**
* @notice get token symbol
* @return token symbol
*/
function symbol() external view returns (string memory);
/**
* @notice get generated URI for given token
* @return token URI
*/
function tokenURI(uint256 tokenId) external view returns (string memory);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
import { IERC721BaseInternal } from '../base/IERC721BaseInternal.sol';
/**
* @title ERC721Metadata internal interface
*/
interface IERC721MetadataInternal is IERC721BaseInternal {
error ERC721Metadata__NonExistentToken();
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
import { UintUtils } from './UintUtils.sol';
library AddressUtils {
using UintUtils for uint256;
error AddressUtils__InsufficientBalance();
error AddressUtils__NotContract();
error AddressUtils__SendValueFailed();
function toString(address account) internal pure returns (string memory) {
return uint256(uint160(account)).toHexString(20);
}
function isContract(address account) internal view returns (bool) {
uint256 size;
assembly {
size := extcodesize(account)
}
return size > 0;
}
function sendValue(address payable account, uint256 amount) internal {
(bool success, ) = account.call{ value: amount }('');
if (!success) revert AddressUtils__SendValueFailed();
}
function functionCall(
address target,
bytes memory data
) internal returns (bytes memory) {
return
functionCall(target, data, 'AddressUtils: failed low-level call');
}
function functionCall(
address target,
bytes memory data,
string memory error
) internal returns (bytes memory) {
return _functionCallWithValue(target, data, 0, error);
}
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return
functionCallWithValue(
target,
data,
value,
'AddressUtils: failed low-level call with value'
);
}
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory error
) internal returns (bytes memory) {
if (value > address(this).balance)
revert AddressUtils__InsufficientBalance();
return _functionCallWithValue(target, data, value, error);
}
/**
* @notice execute arbitrary external call with limited gas usage and amount of copied return data
* @dev derived from https://github.com/nomad-xyz/ExcessivelySafeCall (MIT License)
* @param target recipient of call
* @param gasAmount gas allowance for call
* @param value native token value to include in call
* @param maxCopy maximum number of bytes to copy from return data
* @param data encoded call data
* @return success whether call is successful
* @return returnData copied return data
*/
function excessivelySafeCall(
address target,
uint256 gasAmount,
uint256 value,
uint16 maxCopy,
bytes memory data
) internal returns (bool success, bytes memory returnData) {
returnData = new bytes(maxCopy);
assembly {
// execute external call via assembly to avoid automatic copying of return data
success := call(
gasAmount,
target,
value,
add(data, 0x20),
mload(data),
0,
0
)
// determine whether to limit amount of data to copy
let toCopy := returndatasize()
if gt(toCopy, maxCopy) {
toCopy := maxCopy
}
// store the length of the copied bytes
mstore(returnData, toCopy)
// copy the bytes from returndata[0:toCopy]
returndatacopy(add(returnData, 0x20), 0, toCopy)
}
}
function _functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory error
) private returns (bytes memory) {
if (!isContract(target)) revert AddressUtils__NotContract();
(bool success, bytes memory returnData) = target.call{ value: value }(
data
);
if (success) {
return returnData;
} else if (returnData.length > 0) {
assembly {
let returnData_size := mload(returnData)
revert(add(32, returnData), returnData_size)
}
} else {
revert(error);
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
/**
* @title utility functions for uint256 operations
* @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts/ (MIT license)
*/
library UintUtils {
error UintUtils__InsufficientHexLength();
bytes16 private constant HEX_SYMBOLS = '0123456789abcdef';
function add(uint256 a, int256 b) internal pure returns (uint256) {
return b < 0 ? sub(a, -b) : a + uint256(b);
}
function sub(uint256 a, int256 b) internal pure returns (uint256) {
return b < 0 ? add(a, -b) : a - uint256(b);
}
function toString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return '0';
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
function toHexString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return '0x00';
}
uint256 length = 0;
for (uint256 temp = value; temp != 0; temp >>= 8) {
unchecked {
length++;
}
}
return toHexString(value, length);
}
function toHexString(
uint256 value,
uint256 length
) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = '0';
buffer[1] = 'x';
unchecked {
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = HEX_SYMBOLS[value & 0xf];
value >>= 4;
}
}
if (value != 0) revert UintUtils__InsufficientHexLength();
return string(buffer);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
// Import necessary libraries and contracts
import { AccessControlInternal } from "@solidstate/contracts/access/access_control/AccessControlInternal.sol";
import { ERC165BaseInternal } from "@solidstate/contracts/introspection/ERC165/base/ERC165BaseInternal.sol";
import { ERC721Base } from "@solidstate/contracts/token/ERC721/base/ERC721Base.sol";
import { ERC721BaseInternal } from "@solidstate/contracts/token/ERC721/base/ERC721BaseInternal.sol";
import { ERC721Enumerable } from "@solidstate/contracts/token/ERC721/enumerable/ERC721Enumerable.sol";
import { ERC721Metadata } from "@solidstate/contracts/token/ERC721/metadata/ERC721Metadata.sol";
import { PartiallyPausableInternal } from "@solidstate/contracts/security/partially_pausable/PartiallyPausableInternal.sol";
import { PausableInternal } from "@solidstate/contracts/security/pausable/PausableInternal.sol";
import "../libraries/LibSilksHorseDiamond.sol";
/**
* @title ERC721Facet
* @dev A Solidity smart contract representing the ERC721 facet of the SilksHorseDiamond contract.
* This facet provides functionality for purchasing and airdropping NFTs (horses) and handles pausing functionality.
*/
contract ERC721Facet is
AccessControlInternal,
ERC165BaseInternal,
ERC721Base,
ERC721Enumerable,
ERC721Metadata,
PartiallyPausableInternal,
PausableInternal
{
using EnumerableSet for EnumerableSet.UintSet;
event HorseMinted(
MintMethod indexed method,
uint256 indexed seasonId,
uint256 indexed payoutTier,
address to,
uint256 tokenId
);
/**
* @dev Purchase horses by providing season, payout tier, and quantity.
* @param _seasonId The ID of the season from which to purchase horses.
* @param _payoutTier The ID of the payout tier for the purchase.
* @param _quantity The quantity of horses to purchase.
*/
function purchase(
uint256 _seasonId,
uint256 _payoutTier,
uint256 _quantity
)
public
payable
whenNotPaused
whenNotPartiallyPaused(HORSE_PURCHASING_PAUSED)
{
// Check if the sent Ether amount is valid
PayoutTier storage payoutTier = LibSilksHorseDiamond.layout().payoutTiers[_payoutTier];
uint256 expectedEthTotal = payoutTier.price * _quantity;
if (expectedEthTotal != msg.value){
revert InvalidIntValue(
"INV_ETH_TOTAL",
msg.value,
expectedEthTotal
);
}
// Check if the purchase quantity is within the allowed limit per transaction
if (payoutTier.maxPerTx != 0 && _quantity > payoutTier.maxPerTx){
revert InvalidIntValue(
"PER_TX_ERROR",
_quantity,
payoutTier.maxPerTx
);
}
// Mint the specified quantity of horses to the sender
_mintHorses(_seasonId, _payoutTier, _quantity, MintMethod.PURCHASE, msg.sender);
}
/**
* @dev Airdrop horses by providing season, payout tier, quantity, and the recipient address.
* @param _seasonId The ID of the season for the airdrop.
* @param _payoutTier The ID of the payout tier for the airdrop.
* @param _quantity The quantity of horses to airdrop.
* @param _to The address to receive the airdropped horses.
*/
function airdrop(
uint256 _seasonId,
uint256 _payoutTier,
uint256 _quantity,
address _to
)
public
onlyRole(MINT_ADMIN_ROLE)
{
// Mint the specified quantity of horses and send them to the specified recipient
_mintHorses(_seasonId, _payoutTier, _quantity, MintMethod.AIRDROP, _to);
}
/**
* @dev Allows an external address to mint horses by providing season, payout tier, quantity, and the recipient address.
* Checks if the calling address is in the allowed list of external mint addresses before minting.
* @param _seasonId The ID of the season for the minted horses.
* @param _payoutTier The ID of the payout tier for the minted horses.
* @param _quantity The quantity of horses to mint.
* @param _to The address to receive the minted horses.
*/
function externalMint(
uint256 _seasonId,
uint256 _payoutTier,
uint256 _quantity,
address _to
) external
{
// Check if the calling address is in the list of allowed external mint addresses
bool inAllowedList = LibSilksHorseDiamond.layout().allowedExternalMintAddresses[msg.sender];
if (!inAllowedList){
revert InvalidExternalMintAddress(
msg.sender
);
}
// Call the internal function to mint the horses
_mintHorses(_seasonId, _payoutTier, _quantity, MintMethod.EXTERNAL_MINT, _to);
}
/**
* @dev Mint horses based on season, payout tier, quantity, minting method, and recipient address.
* @param _seasonId The ID of the season for minting horses.
* @param _payoutTier The ID of the payout tier for minting horses.
* @param _quantity The quantity of horses to mint.
* @param _method The minting method used for the horses.
* @param _to The address to receive the minted horses.
* @dev This function is used to mint a specified quantity of horse tokens and associate them with a season and payout tier.
* The minted horse tokens are then assigned to the provided recipient address. The function performs various checks
* to ensure the specified season and payout tier are valid and not paused. It also checks for successful association of
* horse tokens with the season and logs minting events.
* @dev Emits a HorseMinted event upon successful minting.
* @dev Reverts with specific error messages in case of invalid or paused season, invalid or paused payout tier,
* failed association of tokens with the season, or any other minting failure.
*/
function _mintHorses(
uint256 _seasonId,
uint256 _payoutTier,
uint256 _quantity,
MintMethod _method,
address _to
)
private
{
// Check if the _quantity being minted will cause the maximum number of assets in wallet to be exceeded
uint256 maxHorsesPerWallet = LibSilksHorseDiamond.layout().maxHorsesPerWallet;
if (maxHorsesPerWallet > 0 &&( _balanceOf(_to) + _quantity) > maxHorsesPerWallet){
revert MaxHorsesPerWalletExceeded(
_to
);
}
// Check if the specified season and payout tier are valid and not paused
SeasonInfo storage seasonInfo = LibSilksHorseDiamond.layout().seasonInfos[_seasonId];
if (seasonInfo.paused || !seasonInfo.valid){
revert InvalidSeason(
_seasonId
);
}
PayoutTier storage payoutTier = LibSilksHorseDiamond.layout().payoutTiers[_payoutTier];
if (payoutTier.paused || !payoutTier.valid){
revert InvalidPayoutTier(
_payoutTier
);
}
LibSilksHorseDiamond.Layout storage lsh = LibSilksHorseDiamond.layout();
EnumerableSet.UintSet storage payoutTierHorses = lsh.payoutTierHorses[_payoutTier];
if (payoutTier.maxSupply > 0 && (payoutTierHorses.length() + _quantity) > payoutTier.maxSupply){
revert PayoutTierMaxSupplyExceeded(
_payoutTier
);
}
EnumerableSet.UintSet storage seasonHorses = lsh.seasonHorses[_seasonId];
uint256 nextAvailableTokenId = lsh.nextAvailableTokenId;
uint256 i = 0;
// Mint the specified quantity of horses, associate them with the season, and set payout percentages
for (; i < _quantity;) {
uint256 _newTokenId = nextAvailableTokenId + i;
if (!seasonHorses.add(_newTokenId)){
revert MintFailed(
"TOKEN_CROP_ASSOCIATION_ERROR",
_seasonId,
_payoutTier,
_quantity,
_method,
_to
);
}
if (!payoutTierHorses.add(_newTokenId)){
revert MintFailed(
"TOKEN_PAYOUT_TIER_ASSOCIATION_ERROR",
_seasonId,
_payoutTier,
_quantity,
_method,
_to
);
}
lsh.horsePayoutTier[_newTokenId] = payoutTier;
lsh.horseSeasonInfo[_newTokenId] = seasonInfo;
_safeMint(_to, _newTokenId);
emit HorseMinted(_method, _seasonId, _payoutTier, _to, _newTokenId);
unchecked {
i++;
}
}
lsh.nextAvailableTokenId = nextAvailableTokenId + i;
}
/**
* @dev Hook function called before token transfer, inherited from ERC721Metadata.
* @param from The sender of the tokens.
* @param to The recipient of the tokens.
* @param tokenId The ID of the token being transferred.
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
)
internal
virtual
override(
ERC721Metadata,
ERC721BaseInternal
)
{
super._beforeTokenTransfer(from, to, tokenId);
}
/**
* @notice query whether contract has registered support for given interface
* @param interfaceId interface id
* @return bool whether interface is supported
*/
function supportsInterface(
bytes4 interfaceId
)
external
view
returns (
bool
)
{
return super._supportsInterface(interfaceId);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
import {EnumerableSet} from "@solidstate/contracts/data/EnumerableSet.sol";
struct SeasonInfo {
uint256 seasonId;
string description;
bool paused;
bool valid;
}
struct PayoutTier {
uint256 tierId;
string description;
uint256 price;
uint256 maxPerTx;
uint256 payoutPct;
uint256 maxSupply;
bool paused;
bool valid;
}
enum MintMethod {
PURCHASE,
AIRDROP,
EXTERNAL_MINT
}
bytes32 constant CONTRACT_ADMIN_ROLE = keccak256("silks.contracts.roles.ContractAdminRole");
bytes32 constant MINT_ADMIN_ROLE = keccak256("silks.contracts.roles.MintAdminRole");
bytes32 constant HORSE_PURCHASING_PAUSED = keccak256('silks.contracts.paused.HorsePurchasing');
error InvalidAddress(address _address);
error InvalidSeason(uint256 _sent);
error InvalidPayoutTier(uint256 _sent);
error InvalidTokenId(uint256 _sent);
error InvalidIntValue(string _reason, uint256 _sent, uint256 _expected);
error InvalidStringValue(string _reason, string _sent, string _expected);
error InvalidExternalMintAddress(address _sender);
error MaxHorsesPerWalletExceeded(address _walletAddress);
error PayoutTierMaxSupplyExceeded(uint256 _payoutTier);
error MintFailed(string _reason, uint256 _seasonId, uint256 _payoutTier, uint256 _quantity, MintMethod _method, address _to);
library LibSilksHorseDiamond {
bytes32 internal constant STORAGE_SLOT = keccak256('silks.contracts.storage.SilksHorseDiamond');
struct Layout {
mapping(uint256 => EnumerableSet.UintSet) seasonHorses;
mapping(uint256 => SeasonInfo) seasonInfos;
mapping(uint256 => PayoutTier) payoutTiers;
mapping(uint256 => PayoutTier) horsePayoutTier;
mapping(uint256 => EnumerableSet.UintSet) payoutTierHorses;
mapping(uint256 => SeasonInfo) horseSeasonInfo;
mapping(address => bool) allowedExternalMintAddresses;
uint256 maxHorsesPerWallet;
uint256 nextAvailableTokenId;
}
function layout()
internal
pure
returns (
Layout storage l
) {
bytes32 slot = STORAGE_SLOT;
assembly {
l.slot := slot
}
}
}