// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/AccessControl.sol)
pragma solidity ^0.8.20;
import {IAccessControl} from "./IAccessControl.sol";
import {Context} from "../utils/Context.sol";
import {ERC165} from "../utils/introspection/ERC165.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```solidity
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```solidity
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
* to enforce additional security measures for this role.
*/
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address account => bool) hasRole;
bytes32 adminRole;
}
mapping(bytes32 role => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with an {AccessControlUnauthorizedAccount} error including the required role.
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view virtual returns (bool) {
return _roles[role].hasRole[account];
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
* is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
* is missing `role`.
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert AccessControlUnauthorizedAccount(account, role);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
return _roles[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleGranted} event.
*/
function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleRevoked} event.
*/
function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been revoked `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `callerConfirmation`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address callerConfirmation) public virtual {
if (callerConfirmation != _msgSender()) {
revert AccessControlBadConfirmation();
}
_revokeRole(role, callerConfirmation);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
if (!hasRole(role, account)) {
_roles[role].hasRole[account] = true;
emit RoleGranted(role, account, _msgSender());
return true;
} else {
return false;
}
}
/**
* @dev Attempts to revoke `role` to `account` and returns a boolean indicating if `role` was revoked.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
if (hasRole(role, account)) {
_roles[role].hasRole[account] = false;
emit RoleRevoked(role, account, _msgSender());
return true;
} else {
return false;
}
}
}
// contracts/BECAccessControl.sol
// SPDX-License-Identifier: MIT
/**
/3333333 /33333333 /333333
| 33__ 33 | 33_____/ /33__ 33
| 33 \ 33 | 33 | 33 \__/
| 3333333 | 33333 | 33
| 33__ 33 | 33__/ | 33
| 33 \ 33 | 33 | 33 33
| 3333333/ | 33333333 | 333333/
|_______/ |________/ \______/
# https://blackeyedcreatures.com
*/
pragma solidity ^0.8.25;
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
/**
* @title Black Eyed Creatures Access Control
* @author https://42i.co
*/
abstract contract BECAccessControl is AccessControl {
/// @dev Role identifier for the GENETICIST role.
bytes32 public constant GENETICIST = keccak256("GENETICIST");
/// @dev Constructor grants the GENETICIST role to the address deploying the contract.
constructor() {
_setRoleAdmin(GENETICIST, GENETICIST);
_grantRole(GENETICIST, msg.sender);
}
}
// contracts/BECCore.sol
// SPDX-License-Identifier: MIT
/**
/3333333 /33333333 /333333
| 33__ 33 | 33_____/ /33__ 33
| 33 \ 33 | 33 | 33 \__/
| 3333333 | 33333 | 33
| 33__ 33 | 33__/ | 33
| 33 \ 33 | 33 | 33 33
| 3333333/ | 33333333 | 333333/
|_______/ |________/ \______/
# https://blackeyedcreatures.com
*/
pragma solidity ^0.8.25;
import {ERC404B} from "./erc404/ERC404B.sol";
import {BECURIStorage} from "./BECURIStorage.sol";
/**
* @title Black Eyed Creatures Core Contract
* @dev This is the base contract for Black Eyed Creatures (BEC).
* @author https://42i.co
*/
contract BECCore is BECURIStorage {
/// @dev Role constants for managing access to specific methods.
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
/// @dev Mapping between creature ID and the seed that generates its DNA.
/// 0: Type (8, 3bit), Strategy (32, 5bit), Generation (4, 2bit), Seed (212bit)
mapping(uint256 creatureId => bytes32 seed) internal creatureSeed;
/// @dev Constructor initializes the ERC404 token with name "Black Eyed Creatures" and symbol "BEC".
constructor() ERC404B("Black Eyed Creatures", "BEC", 333) {
_setRoleAdmin(MINTER_ROLE, GENETICIST);
}
/**
* @notice Mints new creatures with given DNA seeds for a specified address.
* @dev Only addresses with MINTER_ROLE can call this function.
* @param _creatureOwner Address of the new owner of the minted creatures.
* @param _seed A seed value used to generate creature DNAs.
* @param _quantity Quantity of minted creatures.
* @return creatureIds Array of IDs for the newly minted creatures.
*/
function mintWithSeed(
address _creatureOwner,
bytes32 _seed,
uint256 _quantity
) public payable onlyRole(MINTER_ROLE) returns (uint256[] memory) {
if (_quantity == 0) revert BECZeroQuantityMinting();
uint256 baseCreatureId = nextTokenId();
if (baseCreatureId + _quantity > 133333) revert BECCapLimitReached();
uint256[] memory mintedCreatureIds = new uint256[](_quantity);
_mint(_creatureOwner, _quantity);
creatureSeed[baseCreatureId] = _seed;
for (uint256 i = 0; i < _quantity; i++) {
mintedCreatureIds[i] = baseCreatureId + i;
}
return mintedCreatureIds;
}
/**
* @notice Retrieves the next available token ID for minting new creatures.
* @dev This function returns the token ID that will be assigned to the next minted creature.
* @return uint256 Next available token ID.
*/
function nextTokenId() public view virtual returns (uint256) {
return _nextTokenId;
}
/**
* @notice Retrieves the seed of a creature by ID.
* @param _creatureId ID of the creature.
* @return bytes32 Seed of the creatures.
*/
function getSeed(uint256 _creatureId) public view returns (bytes32) {
bytes32 seed = 0;
for (uint256 i = 0; seed == 0; i++) {
seed = creatureSeed[_creatureId - i];
}
return seed;
}
/**
* @notice Retrieves the seeds of a set creatures by ID.
* @param _creatureIds ID of the creatures.
* @return bytes32[] Seeds of the creatures.
*/
function getSeeds(uint256[] calldata _creatureIds) public view returns (bytes32[] memory) {
bytes32[] memory seeds = new bytes32[](_creatureIds.length);
for (uint256 j = 0; j < _creatureIds.length; j++) {
seeds[j] = getSeed(_creatureIds[j]);
}
return seeds;
}
/**
* @notice Retrieves the total number of tokens currently in circulation.
* @dev This function returns the total count of tokens that have been minted and are currently in existence.
* @return uint256 Total number of tokens in circulation.
*/
function totalTokens() public view returns (uint256) {
return _totalTokens();
}
/**
* @notice Burns a creature by ID.
* @dev Only owner or approved senders can call this function.
* @param _creatureId ID of the creature to be burned.
*/
function burn(uint256 _creatureId) public {
if (!_isApprovedOrOwner(msg.sender, _creatureId)) revert ERC721InsufficientApproval(msg.sender, _creatureId);
_burn(_creatureId);
}
/**
* @notice Retrieves the number of creatures that have been burned.
* @return uint256 Number of burned creatures.
*/
function burned() public view returns (uint256) {
return _burned();
}
/**
* @dev Forces the exemption status for ERC-721 transfers for the caller.
* This function can only be called by the owner, and is created for setting as exempt DEXs & LPs.
* An address that is not a contract, cannot be forced.
*
* Emits:
* - {Transfer} event for each target_ ERC721 token from/to the stash.
*
* @param _target The address to be setted as exempt.
* @param _state The new exemption state to set (true for exempt, false for non-exempt).
*/
function forceERC721TransferExempt(address _target, bool _state) external onlyOwner {
if (_target.code.length == 0) revert BECInvalidAddress(_target);
_setERC721TransferExempt(_target, _state);
}
}
// contracts/BECEnabler.sol
// SPDX-License-Identifier: MIT
/**
/3333333 /33333333 /333333
| 33__ 33 | 33_____/ /33__ 33
| 33 \ 33 | 33 | 33 \__/
| 3333333 | 33333 | 33
| 33__ 33 | 33__/ | 33
| 33 \ 33 | 33 | 33 33
| 3333333/ | 33333333 | 333333/
|_______/ |________/ \______/
# https://blackeyedcreatures.com
*/
pragma solidity ^0.8.25;
import {BECWithdrawable} from "./BECWithdrawable.sol";
/**
* @title Black Eyed Creatures Enabler
* @author https://42i.co
*/
contract BECEnabler is BECWithdrawable {
event BECEnabled(address contractAddress);
/// @dev Internal state variable to track whether the BEC ecosystem is enabled.
bool internal enabled = false;
/**
* @notice Checks whether the contract enabled.
* @return bool True if the contract is enabled, false otherwise.
*/
function isEnabled() external view returns (bool) {
return enabled;
}
/**
* @notice Enables the BEC ecosystem functionalities.
* @dev Sets the internal `enabled` state variable to true, activating the functionalities of the BEC ecosystem that depend on this state.
* This action can typically control the operational state of various contract features, such as minting or token transfers.
*/
function enable() external onlyOwner {
enabled = true;
emit BECEnabled(address(this));
}
}
// contracts/BECGenesisEngine.sol
// SPDX-License-Identifier: MIT
/**
/3333333 /33333333 /333333
| 33__ 33 | 33_____/ /33__ 33
| 33 \ 33 | 33 | 33 \__/
| 3333333 | 33333 | 33
| 33__ 33 | 33__/ | 33
| 33 \ 33 | 33 | 33 33
| 3333333/ | 33333333 | 333333/
|_______/ |________/ \______/
# http://blackeyedcreatures.com
*/
pragma solidity ^0.8.25;
import {BECCore} from "./BECCore.sol";
import {BECLab} from "./BECLab.sol";
import {BECWithdrawable} from "./BECWithdrawable.sol";
import {BECAccessControl} from "./BECAccessControl.sol";
import {IBECDnaStrategy} from "./strategies/IBECDnaStrategy.sol";
import {IBECMetaExtension} from "./meta-extensions/IBECMetaExtension.sol";
import {IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
/**
* @title Black Eyed Creatures Genesis Engine Contract
* @notice Orchestrates the unveiling of creature DNA within the Black Eyed Creatures universe.
* @dev Manages the revelation of creature DNA based on different logics.
* @author https://42i.co
*/
contract BECGenesisEngine is BECWithdrawable, BECAccessControl, IERC721Errors {
/// @dev A constant identifier for the role that is permitted to update the genesis storage.
bytes32 public constant GENESIS_STORAGE_UPDATER = keccak256("GENESIS_STORAGE_UPDATER");
/// @dev Core contract address.
address private immutable coreContractAddress;
/// @dev Nested mapping structure to store the probability information of each variation for each phenotype and family, represented bitwise.
/// The outermost mapping categorizes by [distribution].
/// The intermediate mapping categorizes by [phenotype].
/// The innermost mapping categorizes by [family].
/// The uint256 value associated with each [distribution][phenotype][family] contains:
/// - familyTickets (32b): Represents the quantity of tickets allocated to this specific family, influencing the probability of selection.
/// - variationsCount (8b): Indicates the total number of variations available within the family.
/// - variationExponent (3b): Specifies the bit length used to represent the rarity of each family and variation. This value acts as an exponent for calculating their respective ticket quantities.
mapping(uint256 distribution => mapping(uint256 phenotype => mapping(uint256 family => uint256 tickets))) public tickets;
/// @dev A mapping storing uint256s, each containing 32-bit segments representing the total tickets for each phenotype in a specific distribution.
mapping(uint256 distribution => uint256 tickets) public phenotypeTickets;
/// @dev A mapping to store specific data associated with each creature, indexed by the creature's ID.
mapping(uint256 creatureId => uint256 value) public genesisStorage;
/// @dev A mapping that associates each dna strategy with a strategy contract address, indexed by the strategy ID.
mapping(uint256 strategyId => address contractAddress) public dnaStrategies;
/// @dev An array of metadata extensions.
IBECMetaExtension[] public extensions;
/**
* @notice Initializes the Genesis Engine.
* @dev Sets the coreContractAddress to the provided BEC core contract address.
* @param _becCore The address of the BEC core contract.
*/
constructor(address _becCore) {
coreContractAddress = _becCore;
_setRoleAdmin(GENESIS_STORAGE_UPDATER, GENETICIST);
}
/**
* @notice Sets the genesis storage value for a specific creature.
* @dev The function requires that the storage value for the creature has not been set previously. It can only be called by an address with the GENESIS_STORAGE_UPDATER role.
* @param _creatureId The ID of the creature for which the storage value is to be set.
* @param _value The value to be stored.
*/
function setGenesisStorage(uint256 _creatureId, uint256 _value) public onlyRole(GENESIS_STORAGE_UPDATER) {
if (genesisStorage[_creatureId] != 0) revert BECGenesisStoreAlreadySet(_creatureId);
genesisStorage[_creatureId] = _value;
}
/**
* @notice Retrieves the DNA of a specific creature.
* @dev The function fetches the seed associated with the creature from the core contract,
* determines the strategy using the seed, and then retrieves the DNA using the strategy.
* It requires that the creature with the given ID exists.
* @param _creatureId The ID of the creature whose DNA is to be retrieved.
* @return uint256 The DNA of the specified creature.
*/
function getDna(uint256 _creatureId) public view returns (uint256) {
BECCore coreContract = BECCore(payable(coreContractAddress));
if (!coreContract.exists(_creatureId)) revert ERC721NonexistentToken(_creatureId);
bytes32 seed = coreContract.getSeed(_creatureId);
IBECDnaStrategy strategy = IBECDnaStrategy(dnaStrategies[uint256(uint8(seed[0]) >> 3) & 0x1F]);
return strategy.getDna(_creatureId);
}
/**
* @notice Retrieves the token URI of a specific creature.
* @dev The function fetches the seed associated with the creature from the core contract, determines the strategy using the seed, and then retrieves the token URI using the strategy.
* @param _creatureId The ID of the creature whose token URI is to be retrieved.
* @return string The token URI of the specified creature.
*/
function tokenURI(uint256 _creatureId) public view returns (string memory) {
BECCore coreContract = BECCore(payable(coreContractAddress));
bytes32 seed = coreContract.getSeed(_creatureId);
IBECDnaStrategy strategy = IBECDnaStrategy(dnaStrategies[uint256(uint8(seed[0]) >> 3) & 0x1F]);
return strategy.tokenURI(_creatureId);
}
/**
* @notice Retrieves the genesis storage value for a specific creature.
* @dev Returns the stored value associated with the given creature ID from the genesisStorage mapping.
* @param _creatureId The ID of the creature whose genesis storage value is to be retrieved.
* @return uint256 The genesis storage value of the specified creature.
*/
function getGenesisStorage(uint256 _creatureId) public view returns (uint256) {
return genesisStorage[_creatureId];
}
/**
* @notice Retrieves the amount of tickets of a family for a specific phenotype within a given distribution ID.
* @dev Accesses the tickets mapping with the provided _distribution, _phenotype, and _family to return the associated family ticket information.
* @param _distribution The distribution identifier.
* @param _phenotype The phenotype identifier.
* @param _family The family identifier within the phenotype.
* @return uint256 The family ticket information of the specified phenotype and family.
*/
function getFamilyTickets(uint256 _distribution, uint256 _phenotype, uint256 _family) public view returns (uint256) {
return tickets[_distribution][_phenotype][_family] & 0xFFFFFFFF;
}
/**
* @notice Retrieves the amount of tickets of a variation for a specific phenotype and family within a given distribution ID.
* @dev Calculates the variation ticket information based on the stored ticket information and the provided _variation.
* @param _distribution The distribution identifier.
* @param _phenotype The phenotype identifier.
* @param _family The family identifier within the phenotype.
* @param _variation The variation identifier within the family.
* @return uint256 The variation ticket information of the specified phenotype, family, and variation.
*/
function getVariationTickets(uint256 _distribution, uint256 _phenotype, uint256 _family, uint256 _variation) public view returns (uint256) {
return 1 << ((tickets[_distribution][_phenotype][_family] >> (40 + (_variation * 3))) & 0x7);
}
/**
* @notice Retrieves the count of variations for a specific phenotype and family within a given distribution ID.
* @dev Calculates the variations count based on the stored ticket information.
* @param _distribution The distribution identifier.
* @param _phenotype The phenotype identifier.
* @param _family The family identifier within the phenotype.
* @return uint256 The count of variations for the specified phenotype and family.
*/
function getVariationsCount(uint256 _distribution, uint256 _phenotype, uint256 _family) public view returns (uint256) {
return (tickets[_distribution][_phenotype][_family] >> 32) & 0xFF;
}
/**
* @notice Retrieves the total number of tickets for each phenotype within a given distribution.
* @dev Accesses the ticketsPerPhenotype mapping with the provided _distribution ID to return the associated ticket count.
* @param _distribution The ID of the distribution.
* @return uint256 The total number of tickets for each phenotype in the specified distribution.
*/
function getPhenotypeTickets(uint256 _distribution) public view returns (uint256) {
return phenotypeTickets[_distribution];
}
/**
* @notice Adds ticket information for a specific distribution.
* @dev Requires that the distribution has not been previously set. Iterates through the _tickets parameter to populate the tickets mapping for the given distribution. Can only be called by an address with the GENETICIST role.
* @param _distribution The ID of the distribution to which the tickets are to be added.
* @param _phenotypeTickets The total number of tickets for each phenotype within the distribution.
* @param _tickets A two-dimensional array representing the ticket information to be added for each phenotype and family.
*/
function addTickets(uint256 _distribution, uint256 _phenotypeTickets, uint256[][] memory _tickets) public onlyRole(GENETICIST) {
if (phenotypeTickets[_distribution] != 0) revert BECDistributionAlreadySet(_distribution);
phenotypeTickets[_distribution] = _phenotypeTickets;
for (uint256 i = 0; i < _tickets.length; i++) {
for (uint256 j = 0; j < _tickets[i].length; j++) {
tickets[_distribution][i][j] = _tickets[i][j];
}
}
}
/**
* @notice Registers a DNA strategy for a distribution.
* @dev Can only be called by an address with the GENETICIST role.
* @param _dnaStrategy The address of the DNA strategy contract to be registered.
*/
function registerStrategy(address _dnaStrategy) public onlyRole(GENETICIST) {
IBECDnaStrategy dnaStrategy = IBECDnaStrategy(_dnaStrategy);
uint256 distributionId = dnaStrategy.getDistributionId();
if (tickets[distributionId][0][0] == 0) revert BECWrongDistributionId(distributionId);
uint256 strategyId = dnaStrategy.getStrategyId();
dnaStrategies[strategyId] = _dnaStrategy;
}
/**
* @notice Adds a new metadata extension to the Genesis Engine.
* @dev Appends a new extension contract address to the `extensions` array.
* This function can only be invoked by an address with the GENETICIST role.
* Each extension should implement the IBECMetaExtension interface.
* @param _extensionAddress The address of the metadata extension contract to add.
*/
function addExtension(address _extensionAddress) public onlyRole(GENETICIST) {
IBECMetaExtension extension = IBECMetaExtension(_extensionAddress);
extensions.push(extension);
}
/**
* @notice Clears all registered metadata extensions from the Genesis Engine.
* @dev Deletes the `extensions` array, effectively removing all metadata extension contracts.
* This action cannot be undone. This function can only be invoked by an address with the GENETICIST role.
*/
function clearExtensions() public onlyRole(GENETICIST) {
delete extensions;
}
/**
* @notice Retrieves the addresses of all registered metadata extensions.
* @dev Returns an array of addresses for each metadata extension contract currently registered in the `extensions` array.
* @return An array of IBECMetaExtension contract addresses currently registered.
*/
function getExtensions() external view returns (IBECMetaExtension[] memory) {
return extensions;
}
}
// contracts/BECLab.sol
// SPDX-License-Identifier: MIT
/**
/3333333 /33333333 /333333
| 33__ 33 | 33_____/ /33__ 33
| 33 \ 33 | 33 | 33 \__/
| 3333333 | 33333 | 33
| 33__ 33 | 33__/ | 33
| 33 \ 33 | 33 | 33 33
| 3333333/ | 33333333 | 333333/
|_______/ |________/ \______/
# https://blackeyedcreatures.com
*/
pragma solidity ^0.8.25;
import {BECCore} from "./BECCore.sol";
import {BECEnabler} from "./BECEnabler.sol";
import {BECAccessControl} from "./BECAccessControl.sol";
import {BECGenesisEngine} from "./BECGenesisEngine.sol";
import {EIP712} from "./eip712/EIP712.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
/**
* @title Black Eyed Creatures Lab Contract
* @author https://42i.co
*/
contract BECLab is BECEnabler, BECAccessControl, EIP712, IERC721Errors {
using ECDSA for bytes32;
event FamilyNameSet(uint256 indexed phenotype, uint256 indexed family, uint256[] ambassadors, string name);
event VariationNameSet(uint256 indexed phenotype, uint256 indexed family, uint256 indexed variation, uint256[] ambassadors, string name);
event CreatureNameSet(uint256 indexed creatureId, string name);
/// @dev Core contract address.
address private immutable coreContractAddress;
/// @dev Genesis engine contract address.
address private immutable genesisEngineAddress;
/// @dev Blcksphr contract address.
address private immutable blcksphrsAddress;
/// @dev Mapping of the lab registry
/// Structure:
/// [bucket] each bucket stores 8 creature registries, of 4 bytes each
/// - isVariationAmbassador (1bit)
/// - isFamilyAmbassador (1bit)
/// - ambassadorPhenotype (3bit)
/// - ambassadorFamily (5bit)
/// - ambassadorVariation (6bit)
/// - omegas (16bit)
mapping(uint256 bucket => uint256 registries) internal labRegistry;
/// @dev Mapping that keeps track of each family name.
mapping(uint256 phenotype => mapping(uint256 family => string name)) public familyNames;
/// @dev Mapping that keeps track of each variation name.
mapping(uint256 phenotype => mapping(uint256 family => mapping(uint256 variation => string name))) public variationNames;
/// @dev Names of the creatures.
mapping(uint256 creatureId => string name) public names;
/// @dev Hash of names, and if they are used or not (default not).
mapping(bytes32 nameHash => bool isUsed) internal nameUsed;
/// @dev Mapping storing the amount of families and variations set by the address.
mapping(address caller => uint256 count) public familyVariationAndNamesSet;
/// @dev A constant identifier for the role that is permitted to update the lab register.
bytes32 public constant LAB_REGISTER_UPDATER = keccak256("LAB_REGISTER_UPDATER");
/// @dev The typehash for the data type specified in the family structured data.
/// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md#rationale-for-typehash
bytes32 public constant VERIFY_FAMILY_TYPEHASH = keccak256("VerifyFamilyName(uint256 phenotype,uint256 family,uint256[] ambassadors,string name,address wallet)");
/// @dev The typehash for the data type specified in the variation structured data.
/// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md#rationale-for-typehash
bytes32 public constant VERIFY_VARIATION_TYPEHASH = keccak256("VerifyVariationName(uint256 phenotype,uint256 family,uint256 variation,uint256[] ambassadors,string name,address wallet)");
/**
* @dev Constructor for the Lab contract.
* @param _becCore The core contract address.
* @param _becGenesisEngine The genesis engine contract address.
* @param _blcksphrsAddress The blcksphr contract address.
*/
constructor(address _becCore, address _becGenesisEngine, address _blcksphrsAddress) {
coreContractAddress = _becCore;
genesisEngineAddress = _becGenesisEngine;
blcksphrsAddress = _blcksphrsAddress;
_setRoleAdmin(LAB_REGISTER_UPDATER, GENETICIST);
}
/**
* @notice Checks if a variation name proposal meets the necessary criteria for a given phenotype, family, and variation.
* @dev This function examines the validity of a name proposal for a variation by assessing the criteria regarding
* the phenotype, family, variation, and the three proposed ambassador creatures.
* A successful execution of this function indicates the name proposal meets all established conditions.
* @param _phenotype The phenotype within which the family resides.
* @param _family The family within which the variation exists.
* @param _variation The specific variation for which the name validity is being verified.
* @param _ambassadorIds The 3 ambassador ids.
* @param _name The proposed name for the variation.
*
* Requirements:
* - Phenotype number must be less than 6.
* - Name length should be up to 8 characters.
* - Name must be alphanumeric.
* - The proposed ambassador creatures must be distinct.
* - The creatures cannot already be variation ambassadors.
* - Each proposed ambassador must be associated with the specified phenotype, family, and variation.
*/
function verifyVariationName(
uint256 _phenotype,
uint256 _family,
uint256 _variation,
uint256[] memory _ambassadorIds,
string memory _name
) public view {
// Verify basics
if (_phenotype > 5) revert BECInvalidPhenotype(_phenotype);
if (bytes(_name).length > 8 || bytes(_name).length == 0) revert BECInvalidName(_name);
if (!isAZ(bytes(_name))) revert BECInvalidName(_name);
if (nameUsed[keccak256(abi.encodePacked(_name))]) revert BECDuplicatedName(_name);
if (_ambassadorIds.length != 3) revert BECInvalidAmbassadorsLength(_ambassadorIds.length);
if (bytes(variationNames[_phenotype][_family][_variation]).length != 0)
revert BECVariationNameAlreadySet(_phenotype, _family, _variation);
// Verify creatures
if (
_ambassadorIds[0] == _ambassadorIds[1] ||
_ambassadorIds[0] == _ambassadorIds[2] ||
_ambassadorIds[1] == _ambassadorIds[2]
) revert BECInvalidAmbassadors(_ambassadorIds[0], _ambassadorIds[1], _ambassadorIds[2]);
// Verifying ambassadors and family & variation for each creature
for (uint256 i = 0; i < 3; i++) {
uint256 registry = (labRegistry[_ambassadorIds[i] / 8] >> ((_ambassadorIds[i] % 8) * 32)) & 0xFFFFFFFF;
if (registry & 0x1 != 0) revert BECAlreadyVariationAmbassador(_ambassadorIds[i]);
verifyFamilyAndVariation(_phenotype, _family, _variation, _ambassadorIds[i]);
}
}
/**
* @notice Verifies if a creature's family and variation match the provided family and variation within a specified phenotype.
* @dev Extracts the creature's DNA related to the provided phenotype. It then verifies if the extracted DNA matches the provided family and variation.
* A successful execution indicates that the creature has the expected family and variation within the specified phenotype.
* @param _phenotype The phenotype within which the family and variation reside.
* @param _family The expected family of the creature within the given phenotype.
* @param _variation The expected variation of the creature within the provided family.
* @param _creatureId The ID of the creature (creature) to be verified.
*
* Requirements:
* - The creature's family and variation, as determined from its DNA for the given phenotype, must match `_family` and `_variation` respectively.
*/
function verifyFamilyAndVariation(
uint256 _phenotype,
uint256 _family,
uint256 _variation,
uint256 _creatureId
) internal view {
unchecked {
uint256 phenotypeTraits = BECGenesisEngine(payable(genesisEngineAddress)).getDna(_creatureId) >> ((_phenotype * 16));
uint256 dnaVariation = (phenotypeTraits >> 8) & 0xFF;
uint256 dnaFamily = (phenotypeTraits) & 0xFF;
if (dnaFamily != _family) revert BECInvalidFamily(_creatureId);
if (dnaVariation != _variation) revert BECInvalidVariation(_creatureId);
}
}
/**
* @notice Allows a user to propose a name for a variation by specifying the phenotype, family, variation, ambassador creatures,
* and the name itself.
* @dev This function allows users to suggest a name for a specific variation by proposing three creatures as ambassadors.
* Before calling this function, users should call verifyVariationNameProposal() with the same parameters
* to ensure that the proposal is valid.
* The proposal will be checked by a validator backend after submission.
* The proposal is validated through a signature. If the proposal meets all criteria, the name is set for the variation.
* @param _phenotype The phenotype within which the family resides.
* @param _family The family within which the variation exists.
* @param _variation The specific variation for which a name is being proposed.
* @param _ambassadorIds An array of ambassador creature IDs proposed for the naming.
* @param _name The proposed name for the variation.
*
* Requirements:
* - Phenotype number must be less than 6.
* - Name length should be up to 8 characters.
* - Name must be alphanumeric.
* - The proposal price must be met.
* - The caller must own all three proposed ambassador creatures.
* - The proposed ambassador creatures must be distinct.
* - The creatures cannot already be variation ambassadors.
* - There shouldn't be a pre-existing name or proposal for the specified variation.
*/
function setVariationName(
uint256 _phenotype,
uint256 _family,
uint256 _variation,
uint256[] memory _ambassadorIds,
string memory _name,
bytes calldata _signature
) public {
if (!enabled) revert BECNotEnabled();
// Verify basics
if (!validateVariationSignature(_phenotype, _family, _variation, _ambassadorIds, _name, _signature)) revert EIP712InvalidSignature(_signature);
if (bytes(_name).length > 8 || bytes(_name).length == 0) revert BECInvalidName(_name);
if (nameUsed[keccak256(abi.encodePacked(_name))]) revert BECDuplicatedName(_name);
if (bytes(variationNames[_phenotype][_family][_variation]).length != 0)
revert BECVariationNameAlreadySet(_phenotype, _family, _variation);
BECCore coreContract = BECCore(payable(coreContractAddress));
for (uint256 i = 0; i < 3; i++) {
// Verify ownership
if (msg.sender != coreContract.ownerOf(_ambassadorIds[i])) revert BECInvalidOwner(msg.sender, _ambassadorIds[i]);
// Verifying creature was not used for being ambassador
uint256 registry = (labRegistry[_ambassadorIds[i] / 8] >> ((_ambassadorIds[i] % 8) * 32)) & 0xFFFFFFFF;
if (registry & 0x1 != 0) revert BECAlreadyVariationAmbassador(_ambassadorIds[i]);
// Setting creature as ambassador in the registry
setVariationAmbassadorToRegistry(_ambassadorIds[i], _phenotype, _family, _variation);
}
// Set variation name
variationNames[_phenotype][_family][_variation] = _name;
nameUsed[keccak256(abi.encodePacked(_name))] = true;
familyVariationAndNamesSet[msg.sender] += 1;
emit VariationNameSet(_phenotype, _family, _variation, _ambassadorIds, _name);
}
/**
* @notice Validates the signature for a variation name proposal.
* @dev Checks if the provided signature is valid for the given variation name proposal details.
* @param _phenotype The phenotype ID involved in the proposal.
* @param _family The family ID involved in the proposal.
* @param _variation The variation ID for which the name is proposed.
* @param _ambassadorIds The IDs of the ambassador creatures proposed.
* @param _name The proposed name for the variation.
* @param _signature The signature to validate.
* @return bool Returns true if the signature is valid, false otherwise.
*/
function validateVariationSignature(
uint256 _phenotype,
uint256 _family,
uint256 _variation,
uint256[] memory _ambassadorIds,
string memory _name,
bytes calldata _signature
) internal view returns (bool) {
if (allowlistSigningKey == address(0)) revert EIP712BECSigningKeyNotSet();
bytes32 digest = keccak256(
abi.encodePacked(
"\x19\x01",
EIP712_DOMAIN_SEPARATOR,
keccak256(
abi.encode(
VERIFY_VARIATION_TYPEHASH,
_phenotype,
_family,
_variation,
keccak256(abi.encodePacked(_ambassadorIds)),
keccak256(bytes(_name)),
msg.sender
)
)
)
);
address recoveredAddress = digest.recover(_signature);
return recoveredAddress == allowlistSigningKey;
}
/**
* @notice Updates the registry entry to set the creature as a variation ambassador.
* @dev This function performs bitwise operations on the provided registry entry to indicate
* that the creature is now an ambassador for a specified variation. It updates and returns
* the modified registry entry by setting the appropriate bits based on the phenotype, family,
* and variation passed as arguments.
* @param _creatureId The creature Id.
* @param _variationAmbassadorPhenotype The phenotype associated with the variation ambassador.
* @param _variationAmbassadorFamily The family associated with the variation ambassador.
* @param _variationAmbassadorVariation The specific variation the creature is an ambassador for.
*/
function setVariationAmbassadorToRegistry(
uint256 _creatureId,
uint256 _variationAmbassadorPhenotype,
uint256 _variationAmbassadorFamily,
uint256 _variationAmbassadorVariation
) private {
unchecked {
labRegistry[_creatureId / 8] |=
(1 |
(_variationAmbassadorPhenotype << 2) |
(_variationAmbassadorFamily << 5) |
(_variationAmbassadorVariation << 10)) <<
((_creatureId % 8) * 32);
}
}
/**
* @notice Retrieves the name of a specified variation. If the variation hasn't been approved by ambassadors,
* it returns a default name based on the family and variation numbers.
* @dev Checks the approval status of the variation using the variationAmbassadorsProposal mapping. If approved,
* it fetches the name from the variationNames mapping. Otherwise, it constructs a default name.
* @param _phenotype The phenotype of the variation.
* @param _family The family of the variation.
* @param _variation The specific variation number.
* @return variationName The name of the variation or a default name if not approved.
*/
function getVariationName(
uint256 _phenotype,
uint256 _family,
uint256 _variation
) public view returns (string memory) {
if (bytes(variationNames[_phenotype][_family][_variation]).length != 0) {
return capitalized(variationNames[_phenotype][_family][_variation]);
}
return string(abi.encodePacked("#(", Strings.toString(_family), ", ", Strings.toString(_variation), ")"));
}
/**
* @notice Checks if a family name proposal meets the necessary criteria for a given phenotype and family.
* @dev This function examines the validity of a name proposal for a family by assessing the criteria regarding
* the phenotype, family, and the three proposed ambassador creatures.
* A successful execution of this function indicates the name proposal meets all established conditions.
* The checks include validations for phenotype number, name length, name format,
* ambassador uniqueness, and ambassador association with the specific phenotype and family.
* It also ensures all variations within the family have been named before a family name is proposed.
* @param _phenotype The phenotype within which the family resides.
* @param _family The family for which the name validity is being verified.
* @param _ambassadorIds An array of ambassador creature IDs proposed for the naming.
* @param _name The proposed name for the family.
*
* Requirements:
* - Phenotype number must be less than 6.
* - Name length should be up to 8 characters.
* - Name must be alphanumeric.
* - The proposed ambassador creatures must be distinct.
* - Each proposed ambassador must be associated with the specified phenotype and family.
* - All variations within the family must have been named.
*
* @return Returns `true` if the name proposal is valid; reverts otherwise.
*/
function verifyFamilyName(
uint256 _phenotype,
uint256 _family,
uint256[] memory _ambassadorIds,
string memory _name
) public view returns (bool) {
unchecked {
// Verify basics
if (_phenotype > 5) revert BECInvalidPhenotype(_phenotype);
if (bytes(_name).length > 8 || bytes(_name).length == 0) revert BECInvalidName(_name);
if (!isAZ(bytes(_name))) revert BECInvalidName(_name);
if (nameUsed[keccak256(abi.encodePacked(_name))]) revert BECDuplicatedName(_name);
if (_ambassadorIds.length != 3) revert BECInvalidAmbassadorsLength(_ambassadorIds.length);
if (bytes(familyNames[_phenotype][_family]).length != 0) revert BECFamilyNameAlreadySet(_phenotype, _family);
BECGenesisEngine genesisEngineContract = BECGenesisEngine(payable(genesisEngineAddress));
// Verify all variations of the family set up
uint256 variationsCount = genesisEngineContract.getVariationsCount(0, _phenotype, _family);
for (uint256 v = 0; v < variationsCount; v++) {
if (bytes(variationNames[_phenotype][_family][v]).length == 0) revert BECNotAllVariationsSet(_phenotype, _family, v);
}
// Verify creatures are different
if (
_ambassadorIds[0] == _ambassadorIds[1] ||
_ambassadorIds[0] == _ambassadorIds[2] ||
_ambassadorIds[1] == _ambassadorIds[2]
) revert BECInvalidAmbassadors(_ambassadorIds[0], _ambassadorIds[1], _ambassadorIds[2]);
// Verifying ambassadors and family & variation for each creature
for (uint256 i = 0; i < 3; i++) {
uint256 registry = (labRegistry[_ambassadorIds[i] / 8] >> ((_ambassadorIds[i] % 8) * 32)) & 0xFFFFFFFF;
verifyFamilyAmbassador(_ambassadorIds[i], registry);
verifyFamily(_phenotype, _family, _ambassadorIds[i]);
}
return true;
}
}
/**
* @notice Validates if a creature can be designated as a family ambassador.
* @dev Evaluates the provided creature's registry to determine if the creature can be designated as a family ambassador.
* A successful execution indicates that the creature meets the criteria to become a family ambassador,
* i.e., it is not already a family ambassador and it is a variation ambassador.
* @param _creatureId The creatureId to be verified.
* @param _registry The registry entry of the creature (creature) to be verified.
*
* Requirements:
* - The creature should not already be a family ambassador.
* - The creature must be a variation ambassador.
*/
function verifyFamilyAmbassador(uint256 _creatureId, uint256 _registry) internal pure {
unchecked {
if ((_registry & 2) != 0) revert BECAlreadyFamilyAmbassador(_creatureId); // Checks that the second bit (isFamilyAmbassador) is 0
if ((_registry & 1) == 0) revert BECNotVariationAmbassador(_creatureId); // Checks that the first bit (isVariationAmbassador) is 1
}
}
/**
* @notice Verifies if the family of a creature corresponds to the specified family within a given phenotype.
* @dev Extracts the creature's DNA related to the given phenotype and checks if its family matches the provided `_family`.
* A successful execution indicates the creature belongs to the expected family within the specified phenotype.
* @param _phenotype The phenotype within which the family resides.
* @param _family The expected family of the creature within the given phenotype.
* @param _creatureId The ID of the creature (creature) to be verified.
*
* Requirements:
* - The creature's family, as determined from its DNA for the given phenotype, must match `_family`.
*/
function verifyFamily(uint256 _phenotype, uint256 _family, uint256 _creatureId) internal view {
unchecked {
uint256 phenotypeTraits = BECGenesisEngine(payable(genesisEngineAddress)).getDna(_creatureId) >>
((_phenotype * 16));
uint256 dnaFamily = (phenotypeTraits) & 0xFF;
if (dnaFamily != _family) revert BECInvalidFamily(_creatureId);
}
}
/**
* @notice Proposes a name for a family within a specific phenotype.
* @dev This function allows users to suggest a name for a specific family by proposing three creatures as ambassadors.
* The proposal will be checked by a validator backend after submission. If valid, the name will be approved.
* The function includes basic validation for phenotype number, name length, name format, proposal price,
* creature ownership, ambassador uniqueness, and previous proposals for the same family.
* @param _phenotype The phenotype within which the family resides.
* @param _family The family for which a name is being proposed.
* @param _ambassadorIds An array of ambassador creature IDs proposed for the naming.
* @param _name The proposed name for the family.
*
* Requirements:
* - Phenotype number must be less than 6.
* - Name length should be up to 8 characters.
* - Name must be alphanumeric.
* - The proposal price must be met.
* - The caller must own all three proposed ambassador creatures.
* - The proposed ambassador creatures must be distinct.
* - All family variations should have their names set.
* - The creatures cannot already be family ambassadors.
* - There shouldn't be a pre-existing name or proposal for the specified family.
*/
function setFamilyName(
uint256 _phenotype,
uint256 _family,
uint256[] memory _ambassadorIds,
string memory _name,
bytes calldata _signature
) public {
if (!enabled) revert BECNotEnabled();
// Verify not already set
if (!validateFamilySignature(_phenotype, _family, _ambassadorIds, _name, _signature)) revert EIP712InvalidSignature(_signature);
if (bytes(_name).length > 8 || bytes(_name).length == 0) revert BECInvalidName(_name);
if (nameUsed[keccak256(abi.encodePacked(_name))]) revert BECDuplicatedName(_name);
if (bytes(familyNames[_phenotype][_family]).length != 0) revert BECFamilyNameAlreadySet(_phenotype, _family);
BECCore coreContract = BECCore(payable(coreContractAddress));
for (uint256 i = 0; i < 3; i++) {
if (msg.sender != coreContract.ownerOf(_ambassadorIds[i])) revert BECInvalidOwner(msg.sender, _ambassadorIds[i]);
uint256 registry = (labRegistry[_ambassadorIds[i] / 8] >> ((_ambassadorIds[i] % 8) * 32)) & 0xFFFFFFFF;
verifyFamilyAmbassador(_ambassadorIds[i], registry);
setFamilyAmbassadorToRegistry(_ambassadorIds[i], _phenotype, _family);
}
familyNames[_phenotype][_family] = _name;
nameUsed[keccak256(abi.encodePacked(_name))] = true;
familyVariationAndNamesSet[msg.sender] += 1 << 64;
emit FamilyNameSet(_phenotype, _family, _ambassadorIds, _name);
}
/**
* @notice Validates the signature for a family name proposal.
* @dev Checks if the provided signature is valid for the given family name proposal details.
* @param _phenotype The phenotype ID involved in the proposal.
* @param _family The family ID for which the name is proposed.
* @param _ambassadorIds The IDs of the ambassador creatures proposed.
* @param _name The proposed name for the family.
* @param _signature The signature to validate.
* @return bool Returns true if the signature is valid, false otherwise.
*/
function validateFamilySignature(
uint256 _phenotype,
uint256 _family,
uint256[] memory _ambassadorIds,
string memory _name,
bytes calldata _signature
) internal view returns (bool) {
if (allowlistSigningKey == address(0)) revert EIP712BECSigningKeyNotSet();
bytes32 digest = keccak256(
abi.encodePacked(
"\x19\x01",
EIP712_DOMAIN_SEPARATOR,
keccak256(
abi.encode(
VERIFY_FAMILY_TYPEHASH,
_phenotype,
_family,
keccak256(abi.encodePacked(_ambassadorIds)),
keccak256(bytes(_name)),
msg.sender
)
)
)
);
address recoveredAddress = digest.recover(_signature);
return recoveredAddress == allowlistSigningKey;
}
/**
* @notice Retrieves the name of a family within a specific phenotype.
* @dev This function returns the name of a family based on the given phenotype and family parameters.
* If the ambassador proposal for the family name has been approved, it returns the assigned family name.
* Otherwise, it returns a default placeholder name constructed with the family number.
* @param _phenotype The phenotype within which the family resides.
* @param _family The specific family for which the name is being queried.
* @return A string representing either the assigned name of the family (if the name proposal was approved)
* or a default placeholder name indicating the family number.
*/
function getFamilyName(uint256 _phenotype, uint256 _family) public view returns (string memory) {
if (bytes(familyNames[_phenotype][_family]).length != 0) {
return capitalized(familyNames[_phenotype][_family]);
}
return string(abi.encodePacked("#(", Strings.toString(_family), ")"));
}
/**
* @notice Updates the registry entry to set the creature as a family ambassador.
* @dev This function performs bitwise operations on the provided registry entry to indicate
* that the creature is now an ambassador for a specified family. It updates and returns
* the modified registry entry by setting the appropriate bits based on the laphenotypeyer and family
* passed as arguments.
* @param _creatureId The creature Id.
* @param _phenotype The phenotype associated with the family ambassador.
* @param _family The specific family the creature is an ambassador for.
*/
function setFamilyAmbassadorToRegistry(uint256 _creatureId, uint256 _phenotype, uint256 _family) private {
unchecked {
labRegistry[_creatureId / 8] |= (2 | (_phenotype << 2) | (_family << 5)) << ((_creatureId % 8) * 32);
}
}
/**
* @notice Determines whether the provided string consists only of A-Z characters.
* @dev The function checks each character of the input string to ensure it's a letter (A-Z).
* Non-alphabetic characters will result in a return value of false.
* ^[A-Z]{0,6}$
* @param _string The input string, given as a byte array, to be verified.
* @return bool Returns true if all characters in the string are A-Z, otherwise returns false.
*/
function isAZ(bytes memory _string) internal pure returns (bool) {
unchecked {
if (_string.length < 3 || _string.length > 7) {
return false;
}
for (uint256 i = 0; i < _string.length; i++) {
bytes1 char = _string[i];
if (char < 0x41 || char > 0x5A) { // not A-Z
return false;
}
}
return true;
}
}
/**
* @notice Removes the capitalization of all characters besides the first one.
* @dev This function iterates over all the elements of the string array and transforms it to lowercase.
* @param _caps the string all in caps.
* @return A string capitalized. (First leter in uppercase, the rest in lowercase).
*/
function capitalized(string memory _caps) internal pure returns (string memory) {
bytes memory b = bytes(_caps);
for (uint i = 1; i < b.length; i++) {
if (uint8(b[i]) >= 65 && uint8(b[i]) <= 90) {
b[i] = bytes1(uint8(b[i]) + 32);
}
}
return string(b);
}
/**
* @notice Sets the allowlist signing key for validating signatures.
* @dev Updates the allowlist signing key used for signature validation in name proposals.
* @param newSigningKey The new signing key to be used for validation.
*/
function setAllowlistSigningAddress(address newSigningKey) public onlyRole(GENETICIST) {
_setAllowlistSigningAddress(newSigningKey);
}
/**
* @notice Retrieves the lab registry record for a specific creature.
* @dev Fetches the registry record for a creature based on its ID.
* The record contains information about the creature's ambassador status and other traits.
* @param _creatureId The ID of the creature.
* @return uint256 The registry record of the specified creature.
*/
function getLabRegistryRecord(uint256 _creatureId) external view returns (uint256) {
return (labRegistry[_creatureId / 8] >> ((_creatureId % 8) * 32)) & 0xFFFFFFFF;
}
/**
* @notice Retrieves the number of Omegas for a specific creature.
* @dev Fetches the Omega count for a creature from the lab registry based on its ID.
* @param _creatureId The ID of the creature.
* @return uint256 The number of Omegas associated with the creature.
*/
function getOmegas(uint256 _creatureId) external view returns (uint256) {
return (labRegistry[_creatureId / 8] >> ((((_creatureId % 8) * 32)) + 16)) & 0xFFFF;
}
/**
* @notice Increments the Omega count for a specific creature.
* @dev Adds a specified quantity to the Omega count of a creature in the lab registry.
* @param _creatureId The ID of the creature.
* @param _quantity The quantity to add to the creature's Omega count.
*/
function incrementOmegas(uint256 _creatureId, uint256 _quantity) external onlyRole(LAB_REGISTER_UPDATER) {
labRegistry[_creatureId / 8] += _quantity << (16 + ((_creatureId % 8) * 32));
}
/**
* @notice Grants the LAB_REGISTER_UPDATER role to a specified address.
* @dev Assigns the role responsible for updating lab registry entries to a given address.
* @param _address The address to be granted the LAB_REGISTER_UPDATER role.
*/
function grantLabRegisterUpdaterRole(address _address) external onlyRole(GENETICIST) {
grantRole(LAB_REGISTER_UPDATER, _address);
}
/**
* @notice Sets a name for a specific creature.
* @dev Assigns a name to a creature based on its ID. The caller must be the owner of the creature.
* @param _creatureId The ID of the creature.
* @param _name The name to assign to the creature.
*/
function setCreatureName(uint256 _creatureId, string memory _name) public {
BECCore coreContract = BECCore(payable(coreContractAddress));
if (msg.sender != coreContract.ownerOf(_creatureId)) revert BECInvalidOwner(msg.sender, _creatureId);
if (bytes(_name).length > 8 || bytes(_name).length == 0) revert BECInvalidName(_name);
if (nameUsed[keccak256(abi.encodePacked(_name))]) revert BECDuplicatedName(_name);
if (!isAZ(bytes(_name))) revert BECInvalidName(_name);
names[_creatureId] = _name;
nameUsed[keccak256(abi.encodePacked(_name))] = true;
familyVariationAndNamesSet[msg.sender] += 1 << 128;
emit CreatureNameSet(_creatureId, _name);
}
/**
* @notice Retrieves the name of a specific creature.
* @dev Returns the name of a creature based on its ID. If the creature does not have a custom name, a default name is generated.
* @param _creatureId The ID of the creature.
* @return string The name of the creature or a default name if no custom name is set.
*/
function getCreatureName(uint256 _creatureId) external view virtual returns (string memory) {
if (bytes(names[_creatureId]).length > 0) {
return capitalized(names[_creatureId]);
}
return string(abi.encodePacked("BEC #", Strings.toString(_creatureId)));
}
}
// contracts/BECURIStorage.sol
// SPDX-License-Identifier: MIT
/**
/3333333 /33333333 /333333
| 33__ 33 | 33_____/ /33__ 33
| 33 \ 33 | 33 | 33 \__/
| 3333333 | 33333 | 33
| 33__ 33 | 33__/ | 33
| 33 \ 33 | 33 | 33 33
| 3333333/ | 33333333 | 333333/
|_______/ |________/ \______/
# http://blackeyedcreatures.com
*/
pragma solidity ^0.8.25;
import {ERC404B} from "./erc404/ERC404B.sol";
import {BECAccessControl} from "./BECAccessControl.sol";
import {BECGenesisEngine} from "./BECGenesisEngine.sol";
import {BECWithdrawable} from "./BECWithdrawable.sol";
import {EIP712} from "./eip712/EIP712.sol";
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
/**
* @title Black Eyed Creatures URI Storage
* @dev Contract for managing URIs and IPFS-related functionality for Black Eyed Creatures (BEC) tokens.
* @author https://42i.co
*/
abstract contract BECURIStorage is ERC404B, BECAccessControl, BECWithdrawable, EIP712 {
/// @dev Using Elliptic Curve Digital Signature Algorithm (ECDSA).
using ECDSA for bytes32;
/// @dev Base URI for retrieve creature images.
string _tokenBaseURI;
/// @dev Base URL for the site.
string internal _siteBaseUrl;
/// @dev Genesis engine contract address.
address genesisEngineContractAddress;
/// @dev Mapping containing the IPFS uri of each token.
mapping(uint256 tokenId => string ipfsUri) public ipfsUris;
// The typehash for the data type specified in the structured data.
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md#rationale-for-typehash
bytes32 public constant IPFS_URI_TYPEHASH = keccak256("SetIPFSUri(uint256 creatureId,string uri)");
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 _interfaceId) public view virtual override(ERC404B, AccessControl) returns (bool) {
return super.supportsInterface(_interfaceId);
}
/**
* @notice Sets the Base Token URI.
* @param _uri the URI to set.
*/
function setBaseURI(string memory _uri) public onlyOwner {
_tokenBaseURI = _uri;
}
/**
* @notice Sets the Site Base Token URI.
* @dev Allows the owner to set the base URL for the site.
* @param _uri The new site base URI to set.
*/
function setSiteBaseURI(string memory _uri) public onlyOwner {
_siteBaseUrl = _uri;
}
/**
* @notice Sets the IPFS-specific Token URI.
* @dev Allows setting a specific IPFS URI for a creature token.
* @param _creatureId The ID of the creature token.
* @param _uri The IPFS URI to set for the token.
* @param _signature The signature to validate the operation.
*/
function setIPFSTokenURI(uint256 _creatureId, string calldata _uri, bytes calldata _signature) public {
if (!validateIPFSUriSignature(_creatureId, _uri, _signature)) revert EIP712InvalidSignature(_signature);
ipfsUris[_creatureId] = _uri;
}
/**
* @notice Gets the Site Base Token URI.
* @dev Returns the current site base URI.
* @return uri The current site base URI.
*/
function getSiteBaseURI() public view returns (string memory uri) {
return _siteBaseUrl;
}
/**
* @notice Gets the image URI for a creature token.
* @dev Generates and returns the image URI for a given creature token ID.
* @param _creatureId The ID of the creature token.
* @return uri The image URI for the creature token.
*/
function getImageURI(uint256 _creatureId) public view returns (string memory uri) {
if (bytes(ipfsUris[_creatureId]).length > 0) {
return ipfsUris[_creatureId];
}
else {
return string(
abi.encodePacked(
_tokenBaseURI,
Strings.toHexString(uint256(uint160(address(this))), 20),
"/",
Strings.toString(_creatureId),
".png"
)
);
}
}
/**
* @dev See {IERC721Metadata-tokenURI}.
*/
function tokenURI(uint256 _creatureId) public view virtual override returns (string memory) {
BECGenesisEngine genesisEngineContract = BECGenesisEngine(payable(genesisEngineContractAddress));
return genesisEngineContract.tokenURI(_creatureId);
}
/**
* @notice Sets the address of the Genesis Engine contract.
* @dev Allows the contract to interact with the Genesis Engine, enabling dynamic token URI generation based on creature DNA.
* @param _genesisEngineContractAddress The address of the Genesis Engine contract.
*/
function setGenesisEngineContractAddress(address _genesisEngineContractAddress) public onlyRole(GENETICIST) {
genesisEngineContractAddress = _genesisEngineContractAddress;
}
/**
* @notice Sets a new signing key for the allowlist.
* @dev Updates the signing key used to validate signatures for certain privileged operations.
* @param _signingKey The new public address to be used as the signing key.
*/
function setAllowlistSigningAddress(address _signingKey) public onlyOwner {
_setAllowlistSigningAddress(_signingKey);
}
/**
* @notice Validates the signature for setting IPFS image creature uri.
* @dev Uses EIP-712 signing standard to verify the signature against the expected message format and content,
* including the creatureId, and the IPFS URI of the creature.
* @param _creatureId The creature.
* @param _uri The IPFS uri for the creature.
* @param _signature The signature to validate.
* @return bool True if the signature is valid, false otherwise.
*/
function validateIPFSUriSignature(
uint256 _creatureId,
string calldata _uri,
bytes calldata _signature
) internal view returns (bool) {
if (allowlistSigningKey == address(0)) revert EIP712BECSigningKeyNotSet();
bytes32 digest = keccak256(
abi.encodePacked(
"\x19\x01",
EIP712_DOMAIN_SEPARATOR,
keccak256(abi.encode(IPFS_URI_TYPEHASH, _creatureId, keccak256(bytes(_uri)), msg.sender))
)
);
address recoveredAddress = digest.recover(_signature);
return recoveredAddress == allowlistSigningKey;
}
}
// contracts/BECWithdrawable.sol
// SPDX-License-Identifier: MIT
/**
/3333333 /33333333 /333333
| 33__ 33 | 33_____/ /33__ 33
| 33 \ 33 | 33 | 33 \__/
| 3333333 | 33333 | 33
| 33__ 33 | 33__/ | 33
| 33 \ 33 | 33 | 33 33
| 3333333/ | 33333333 | 333333/
|_______/ |________/ \______/
# http://blackeyedcreatures.com
*/
pragma solidity ^0.8.25;
import {IBECErrors} from "./IBECErrors.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
/**
* @title Black Eyed Creatures Widthdrawable Contract
* @dev This contract adds the availablitily to extract ether and erc-20 token balance from the contract that
* implements it.
* @author https://42i.co
*/
abstract contract BECWithdrawable is Ownable, IBECErrors {
/// @dev Event emitted when the contract receives ether from an address.
event Received(address from, uint256 amount);
/// @dev Constructor for Withdrawable.
constructor () Ownable(msg.sender) {}
/**
* @dev Withdraws specified amount of royalties from contract to address.
* @param _amount amount of royalties to withdraw.
* @param _address address to withdraw the balance to.
*/
function withdraw(uint256 _amount, address payable _address) public payable onlyOwner {
if (_address == address(0)) revert BECInvalidAddress(_address);
if (_amount > address(this).balance) revert BECInsufficientFunds(_amount, address(this).balance);
_address.transfer(_amount);
}
/**
* @dev Withdraws token specified ERC20 amount to the given address.
* @param _tokenAddress Address of the token to withdraw.
* @param _amount Amount of tokens to withdraw.
* @param _address Address to withdraw the tokens to.
*/
function withdrawERC20Token(
address _tokenAddress,
uint256 _amount,
address payable _address
) public payable onlyOwner {
IERC20 tokenContract = IERC20(_tokenAddress);
if (_address == address(0)) revert BECInvalidAddress(_address);
if (_amount > tokenContract.balanceOf(address(this))) revert BECInsufficientFunds(_amount, tokenContract.balanceOf(address(this)));
tokenContract.transfer(_address, _amount);
}
/**
* @dev Withdraws token specified ERC721 tokenId to the given address.
* @param _tokenAddress Address of the token to withdraw.
* @param _tokenId The id of the token to withdraw.
* @param _address Address to withdraw the tokens to.
*/
function withdrawERC721Token(
address _tokenAddress,
uint256 _tokenId,
address _address
) public payable onlyOwner {
IERC721 tokenContract = IERC721(_tokenAddress);
if (_address == address(0)) revert BECInvalidAddress(_address);
if (address(this) != tokenContract.ownerOf(_tokenId)) revert BECInvalidOwner(address(this), _tokenId);
tokenContract.safeTransferFrom(address(this), _address, _tokenId);
}
/**
* @dev Function to receive Ether. msg.data must be empty.
*/
receive() external payable {
emit Received(msg.sender, msg.value);
}
/**
* @dev Fallback function is called when msg.data is not empty.
*/
fallback() external payable {}
}
// SPDX-License-Identifier: MIT
/**
_____ ___ ___ __ ____ _ __
/ ___/____ / (_)___/ (_) /___ __ / __ )(_) /______
\__ \/ __ \/ / / __ / / __/ / / / / __ / / __/ ___/
___/ / /_/ / / / /_/ / / /_/ /_/ / / /_/ / / /_(__ )
/____/\____/_/_/\__,_/_/\__/\__, / /_____/_/\__/____/
/____/
- npm: https://www.npmjs.com/package/solidity-bits
- github: https://github.com/estarriolvetch/solidity-bits
*/
pragma solidity ^0.8.0;
import "./BitScan.sol";
import "./Popcount.sol";
/**
* @dev This Library is a modified version of Openzeppelin's BitMaps library with extra features.
*
* 1. Functions of finding the index of the closest set bit from a given index are added.
* The indexing of each bucket is modifed to count from the MSB to the LSB instead of from the LSB to the MSB.
* The modification of indexing makes finding the closest previous set bit more efficient in gas usage.
* 2. Setting and unsetting the bitmap consecutively.
* 3. Accounting number of set bits within a given range.
*
*/
/**
* @dev Library for managing uint256 to bool mapping in a compact and efficient way, providing the keys are sequential.
* Largelly inspired by Uniswap's https://github.com/Uniswap/merkle-distributor/blob/master/contracts/MerkleDistributor.sol[merkle-distributor].
*/
library BitMaps {
using BitScan for uint256;
uint256 private constant MASK_INDEX_ZERO = (1 << 255);
uint256 private constant MASK_FULL = type(uint256).max;
struct BitMap {
mapping(uint256 => uint256) _data;
}
/**
* @dev Returns whether the bit at `index` is set.
*/
function get(BitMap storage bitmap, uint256 index) internal view returns (bool) {
uint256 bucket = index >> 8;
uint256 mask = MASK_INDEX_ZERO >> (index & 0xff);
return bitmap._data[bucket] & mask != 0;
}
/**
* @dev Sets the bit at `index` to the boolean `value`.
*/
function setTo(
BitMap storage bitmap,
uint256 index,
bool value
) internal {
if (value) {
set(bitmap, index);
} else {
unset(bitmap, index);
}
}
/**
* @dev Sets the bit at `index`.
*/
function set(BitMap storage bitmap, uint256 index) internal {
uint256 bucket = index >> 8;
uint256 mask = MASK_INDEX_ZERO >> (index & 0xff);
bitmap._data[bucket] |= mask;
}
/**
* @dev Unsets the bit at `index`.
*/
function unset(BitMap storage bitmap, uint256 index) internal {
uint256 bucket = index >> 8;
uint256 mask = MASK_INDEX_ZERO >> (index & 0xff);
bitmap._data[bucket] &= ~mask;
}
/**
* @dev Consecutively sets `amount` of bits starting from the bit at `startIndex`.
*/
function setBatch(BitMap storage bitmap, uint256 startIndex, uint256 amount) internal {
uint256 bucket = startIndex >> 8;
uint256 bucketStartIndex = (startIndex & 0xff);
unchecked {
if(bucketStartIndex + amount < 256) {
bitmap._data[bucket] |= MASK_FULL << (256 - amount) >> bucketStartIndex;
} else {
bitmap._data[bucket] |= MASK_FULL >> bucketStartIndex;
amount -= (256 - bucketStartIndex);
bucket++;
while(amount > 256) {
bitmap._data[bucket] = MASK_FULL;
amount -= 256;
bucket++;
}
bitmap._data[bucket] |= MASK_FULL << (256 - amount);
}
}
}
/**
* @dev Consecutively unsets `amount` of bits starting from the bit at `startIndex`.
*/
function unsetBatch(BitMap storage bitmap, uint256 startIndex, uint256 amount) internal {
uint256 bucket = startIndex >> 8;
uint256 bucketStartIndex = (startIndex & 0xff);
unchecked {
if(bucketStartIndex + amount < 256) {
bitmap._data[bucket] &= ~(MASK_FULL << (256 - amount) >> bucketStartIndex);
} else {
bitmap._data[bucket] &= ~(MASK_FULL >> bucketStartIndex);
amount -= (256 - bucketStartIndex);
bucket++;
while(amount > 256) {
bitmap._data[bucket] = 0;
amount -= 256;
bucket++;
}
bitmap._data[bucket] &= ~(MASK_FULL << (256 - amount));
}
}
}
/**
* @dev Returns number of set bits within a range.
*/
function popcountA(BitMap storage bitmap, uint256 startIndex, uint256 amount) internal view returns(uint256 count) {
uint256 bucket = startIndex >> 8;
uint256 bucketStartIndex = (startIndex & 0xff);
unchecked {
if(bucketStartIndex + amount < 256) {
count += Popcount.popcount256A(
bitmap._data[bucket] & (MASK_FULL << (256 - amount) >> bucketStartIndex)
);
} else {
count += Popcount.popcount256A(
bitmap._data[bucket] & (MASK_FULL >> bucketStartIndex)
);
amount -= (256 - bucketStartIndex);
bucket++;
while(amount > 256) {
count += Popcount.popcount256A(bitmap._data[bucket]);
amount -= 256;
bucket++;
}
count += Popcount.popcount256A(
bitmap._data[bucket] & (MASK_FULL << (256 - amount))
);
}
}
}
/**
* @dev Returns number of set bits within a range.
*/
function popcountB(BitMap storage bitmap, uint256 startIndex, uint256 amount) internal view returns(uint256 count) {
uint256 bucket = startIndex >> 8;
uint256 bucketStartIndex = (startIndex & 0xff);
unchecked {
if(bucketStartIndex + amount < 256) {
count += Popcount.popcount256B(
bitmap._data[bucket] & (MASK_FULL << (256 - amount) >> bucketStartIndex)
);
} else {
count += Popcount.popcount256B(
bitmap._data[bucket] & (MASK_FULL >> bucketStartIndex)
);
amount -= (256 - bucketStartIndex);
bucket++;
while(amount > 256) {
count += Popcount.popcount256B(bitmap._data[bucket]);
amount -= 256;
bucket++;
}
count += Popcount.popcount256B(
bitmap._data[bucket] & (MASK_FULL << (256 - amount))
);
}
}
}
/**
* @dev Find the closest index of the set bit before `index`.
*/
function scanForward(BitMap storage bitmap, uint256 index) internal view returns (uint256 setBitIndex) {
uint256 bucket = index >> 8;
// index within the bucket
uint256 bucketIndex = (index & 0xff);
// load a bitboard from the bitmap.
uint256 bb = bitmap._data[bucket];
// offset the bitboard to scan from `bucketIndex`.
bb = bb >> (0xff ^ bucketIndex); // bb >> (255 - bucketIndex)
if(bb > 0) {
unchecked {
setBitIndex = (bucket << 8) | (bucketIndex - bb.bitScanForward256());
}
} else {
while(true) {
require(bucket > 0, "BitMaps: The set bit before the index doesn't exist.");
unchecked {
bucket--;
}
// No offset. Always scan from the least significiant bit now.
bb = bitmap._data[bucket];
if(bb > 0) {
unchecked {
setBitIndex = (bucket << 8) | (255 - bb.bitScanForward256());
break;
}
}
}
}
}
function getBucket(BitMap storage bitmap, uint256 bucket) internal view returns (uint256) {
return bitmap._data[bucket];
}
}
// SPDX-License-Identifier: MIT
/**
_____ ___ ___ __ ____ _ __
/ ___/____ / (_)___/ (_) /___ __ / __ )(_) /______
\__ \/ __ \/ / / __ / / __/ / / / / __ / / __/ ___/
___/ / /_/ / / / /_/ / / /_/ /_/ / / /_/ / / /_(__ )
/____/\____/_/_/\__,_/_/\__/\__, / /_____/_/\__/____/
/____/
- npm: https://www.npmjs.com/package/solidity-bits
- github: https://github.com/estarriolvetch/solidity-bits
*/
pragma solidity ^0.8.0;
library BitScan {
uint256 constant private DEBRUIJN_256 = 0x818283848586878898a8b8c8d8e8f929395969799a9b9d9e9faaeb6bedeeff;
bytes constant private LOOKUP_TABLE_256 = hex"0001020903110a19042112290b311a3905412245134d2a550c5d32651b6d3a7506264262237d468514804e8d2b95569d0d495ea533a966b11c886eb93bc176c9071727374353637324837e9b47af86c7155181ad4fd18ed32c9096db57d59ee30e2e4a6a5f92a6be3498aae067ddb2eb1d5989b56fd7baf33ca0c2ee77e5caf7ff0810182028303840444c545c646c7425617c847f8c949c48a4a8b087b8c0c816365272829aaec650acd0d28fdad4e22d6991bd97dfdcea58b4d6f29fede4f6fe0f1f2f3f4b5b6b607b8b93a3a7b7bf357199c5abcfd9e168bcdee9b3f1ecf5fd1e3e5a7a8aa2b670c4ced8bbe8f0f4fc3d79a1c3cde7effb78cce6facbf9f8";
/**
@dev Isolate the least significant set bit.
*/
function isolateLS1B256(uint256 bb) pure internal returns (uint256) {
require(bb > 0);
unchecked {
return bb & (0 - bb);
}
}
/**
@dev Isolate the most significant set bit.
*/
function isolateMS1B256(uint256 bb) pure internal returns (uint256) {
require(bb > 0);
unchecked {
bb |= bb >> 128;
bb |= bb >> 64;
bb |= bb >> 32;
bb |= bb >> 16;
bb |= bb >> 8;
bb |= bb >> 4;
bb |= bb >> 2;
bb |= bb >> 1;
return (bb >> 1) + 1;
}
}
/**
@dev Find the index of the lest significant set bit. (trailing zero count)
*/
function bitScanForward256(uint256 bb) pure internal returns (uint8) {
unchecked {
return uint8(LOOKUP_TABLE_256[(isolateLS1B256(bb) * DEBRUIJN_256) >> 248]);
}
}
/**
@dev Find the index of the most significant set bit.
*/
function bitScanReverse256(uint256 bb) pure internal returns (uint8) {
unchecked {
return 255 - uint8(LOOKUP_TABLE_256[((isolateMS1B256(bb) * DEBRUIJN_256) >> 248)]);
}
}
function log2(uint256 bb) pure internal returns (uint8) {
unchecked {
return uint8(LOOKUP_TABLE_256[(isolateMS1B256(bb) * DEBRUIJN_256) >> 248]);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/DoubleEndedQueue.sol)
// Modified by Pandora Labs to support native uint256 operations
// Modified by 42i.co to retriveQueueItems && setIndexValue
pragma solidity ^0.8.25;
/**
* @dev A sequence of items with the ability to efficiently push and pop items (i.e. insert and remove) on both ends of
* the sequence (called front and back). Among other access patterns, it can be used to implement efficient LIFO and
* FIFO queues. Storage use is optimized, and all operations are O(1) constant time. This includes {clear}, given that
* the existing queue contents are left in storage.
*
* The struct is called `Uint256Deque`. This data structure can only be used in storage, and not in memory.
*
* ```solidity
* DoubleEndedQueue.Uint256Deque queue;
* ```
*/
library DoubleEndedQueue {
/**
* @dev An operation (e.g. {front}) couldn't be completed due to the queue being empty.
*/
error QueueEmpty();
/**
* @dev A push operation couldn't be completed due to the queue being full.
*/
error QueueFull();
/**
* @dev An operation (e.g. {at}) couldn't be completed due to an index being out of bounds.
*/
error QueueOutOfBounds();
/**
* @dev Indices are 128 bits so begin and end are packed in a single storage slot for efficient access.
*
* Struct members have an underscore prefix indicating that they are "private" and should not be read or written to
* directly. Use the functions provided below instead. Modifying the struct manually may violate assumptions and
* lead to unexpected behavior.
*
* The first item is at data[begin] and the last item is at data[end - 1]. This range can wrap around.
*/
struct Uint256Deque {
uint128 _begin;
uint128 _end;
mapping(uint128 index => uint256) _data;
}
/**
* @dev Retrieves all items from the queue as an array of uint256 values.
* This function does not modify the state of the queue.
*/
function retriveQueueItems(Uint256Deque storage deque) internal view returns (uint256[] memory dequeTokens) {
unchecked {
uint128 beginIndex = deque._begin;
uint128 queueLenght = deque._end - beginIndex;
dequeTokens = new uint256[](queueLenght);
for (uint128 i = 0; i < queueLenght; ++i) {
dequeTokens[i] = deque._data[beginIndex+i];
}
}
}
/**
* @dev Sets the value at a specific index in the queue. The index is relative to the current `begin` of the queue.
* This function allows for direct manipulation of queue items at a given index, and should be used with caution as
* improper usage can lead to logical errors in the queue state.
*
* Reverts with `QueueOutOfBounds` if the index is out of bounds.
*/
function setIndexValue(Uint256Deque storage deque, uint128 index, uint256 value) internal {
if (index >= length(deque)) revert QueueOutOfBounds();
deque._data[index+deque._begin] = value;
}
/**
* @dev Inserts an item at the end of the queue.
*
* Reverts with {QueueFull} if the queue is full.
*/
function pushBack(Uint256Deque storage deque, uint256 value) internal {
unchecked {
uint128 backIndex = deque._end;
if (backIndex + 1 == deque._begin) revert QueueFull();
deque._data[backIndex] = value;
deque._end = backIndex + 1;
}
}
/**
* @dev Removes the item at the end of the queue and returns it.
*
* Reverts with {QueueEmpty} if the queue is empty.
*/
function popBack(
Uint256Deque storage deque
) internal returns (uint256 value) {
unchecked {
uint128 backIndex = deque._end;
if (backIndex == deque._begin) revert QueueEmpty();
--backIndex;
value = deque._data[backIndex];
delete deque._data[backIndex];
deque._end = backIndex;
}
}
/**
* @dev Inserts an item at the beginning of the queue.
*
* Reverts with {QueueFull} if the queue is full.
*/
function pushFront(Uint256Deque storage deque, uint256 value) internal {
unchecked {
uint128 frontIndex = deque._begin - 1;
if (frontIndex == deque._end) revert QueueFull();
deque._data[frontIndex] = value;
deque._begin = frontIndex;
}
}
/**
* @dev Removes the item at the beginning of the queue and returns it.
*
* Reverts with `QueueEmpty` if the queue is empty.
*/
function popFront(
Uint256Deque storage deque
) internal returns (uint256 value) {
unchecked {
uint128 frontIndex = deque._begin;
if (frontIndex == deque._end) revert QueueEmpty();
value = deque._data[frontIndex];
delete deque._data[frontIndex];
deque._begin = frontIndex + 1;
}
}
/**
* @dev Returns the item at the beginning of the queue.
*
* Reverts with `QueueEmpty` if the queue is empty.
*/
function front(
Uint256Deque storage deque
) internal view returns (uint256 value) {
if (empty(deque)) revert QueueEmpty();
return deque._data[deque._begin];
}
/**
* @dev Returns the item at the end of the queue.
*
* Reverts with `QueueEmpty` if the queue is empty.
*/
function back(
Uint256Deque storage deque
) internal view returns (uint256 value) {
if (empty(deque)) revert QueueEmpty();
unchecked {
return deque._data[deque._end - 1];
}
}
/**
* @dev Return the item at a position in the queue given by `index`, with the first item at 0 and last item at
* `length(deque) - 1`.
*
* Reverts with `QueueOutOfBounds` if the index is out of bounds.
*/
function at(
Uint256Deque storage deque,
uint256 index
) internal view returns (uint256 value) {
if (index >= length(deque)) revert QueueOutOfBounds();
// By construction, length is a uint128, so the check above ensures that index can be safely downcast to uint128
unchecked {
return deque._data[deque._begin + uint128(index)];
}
}
/**
* @dev Resets the queue back to being empty.
*
* NOTE: The current items are left behind in storage. This does not affect the functioning of the queue, but misses
* out on potential gas refunds.
*/
function clear(Uint256Deque storage deque) internal {
deque._begin = 0;
deque._end = 0;
}
/**
* @dev Returns the number of items in the queue.
*/
function length(Uint256Deque storage deque) internal view returns (uint256) {
unchecked {
return uint256(deque._end - deque._begin);
}
}
/**
* @dev Returns true if the queue is empty.
*/
function empty(Uint256Deque storage deque) internal view returns (bool) {
return deque._end == deque._begin;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.20;
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS
}
/**
* @dev The signature derives the `address(0)`.
*/
error ECDSAInvalidSignature();
/**
* @dev The signature has an invalid length.
*/
error ECDSAInvalidSignatureLength(uint256 length);
/**
* @dev The signature has an S value that is in the upper half order.
*/
error ECDSAInvalidSignatureS(bytes32 s);
/**
* @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not
* return address(0) without also returning an error description. Errors are documented using an enum (error type)
* and a bytes32 providing additional information about the error.
*
* If no error is returned, then the address can be used for verification purposes.
*
* The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*/
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError, bytes32) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
/// @solidity memory-safe-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
*/
function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError, bytes32) {
unchecked {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
// We do not check for an overflow here since the shift operation results in 0 or 1.
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*/
function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address, RecoverError, bytes32) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS, s);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature, bytes32(0));
}
return (signer, RecoverError.NoError, bytes32(0));
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.
*/
function _throwError(RecoverError error, bytes32 errorArg) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert ECDSAInvalidSignature();
} else if (error == RecoverError.InvalidSignatureLength) {
revert ECDSAInvalidSignatureLength(uint256(errorArg));
} else if (error == RecoverError.InvalidSignatureS) {
revert ECDSAInvalidSignatureS(errorArg);
}
}
}
// contracts/eip712/EIP712.sol
// SPDX-License-Identifier: MIT
/**
/3333333 /33333333 /333333
| 33__ 33 | 33_____/ /33__ 33
| 33 \ 33 | 33 | 33 \__/
| 3333333 | 33333 | 33
| 33__ 33 | 33__/ | 33
| 33 \ 33 | 33 | 33 33
| 3333333/ | 33333333 | 333333/
|_______/ |________/ \______/
# http://blackeyedcreatures.com
*/
pragma solidity ^0.8.25;
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
/**
* @title EIP712 Base Contract for Typed Structured Data Hashing and Signing
* @dev Implements EIP-712 standard providing a secure method for generating off-chain signatures
* that can be verified on-chain. Utilizes OpenZeppelin's ECDSA library for cryptographic operations,
* ensuring robust and secure handling of digital signatures.
* @author https://42i.co
*/
contract EIP712 {
/**
* @dev Indicates that the signature was invalid.
* @param signature The used signature.
*/
error EIP712InvalidSignature(bytes signature);
/**
* @dev Indicates that the signing key was not set.
*/
error EIP712BECSigningKeyNotSet();
/// @dev Using Elliptic Curve Digital Signature Algorithm (ECDSA)
using ECDSA for bytes32;
/// @dev The key used to sign allowlist signatures.
address allowlistSigningKey = address(0);
/// @dev EIP-712 Domain Separator, providing protection against replay attacks across different contracts and networks.
/// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md#definition-of-domainseparator
bytes32 public immutable EIP712_DOMAIN_SEPARATOR;
/**
* @dev Initializes the contract by setting up the DOMAIN_SEPARATOR to include the contract's details
* and the current chain ID, ensuring signatures are valid for specific interactions on the intended network.
*/
constructor() {
// This should match whats in the client side allowlist signing code
EIP712_DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes("BEC")),
keccak256(bytes("3")),
block.chainid,
address(this)
)
);
}
/**
* @notice Sets a new signing key for the allowlist, updating the key used for signature validation.
* @dev Internal function to update the allowlist signing key, ensuring only authorized signatures are considered valid.
* @param _signingKey The new signing key address to be used for validating signatures.
*/
function _setAllowlistSigningAddress(address _signingKey) internal {
allowlistSigningKey = _signingKey;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
// contracts/erc404/ERC404B.sol
// SPDX-License-Identifier: MIT
/**
/3333333 /33333333 /333333
| 33__ 33 | 33_____/ /33__ 33
| 33 \ 33 | 33 | 33 \__/
| 3333333 | 33333 | 33
| 33__ 33 | 33__/ | 33
| 33 \ 33 | 33 | 33 33
| 3333333/ | 33333333 | 333333/
|_______/ |________/ \______/
# https://blackeyedcreatures.com
*/
pragma solidity ^0.8.25;
import {IERC404B} from "./IERC404B.sol";
import {IERC404BErrors} from "./IERC404BErrors.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import {IERC721Metadata} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
import {IERC20Errors, IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
import {DoubleEndedQueue} from "./lib/DoubleEndedQueue.sol";
import {BitMaps} from "solidity-bits/contracts/BitMaps.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
/**
* @title Stash Contract
* @dev This empty contract serves as the designated address for ERC721 tokens that have been minted but not yet burned and currently have no owner.
*/
contract Stash {}
/**
* @title ERC404B Contract
* @dev Implements the IERC404 interface to provide a hybrid token mechanism supporting both ERC-20 and ERC-721 functionalities.
* This contract diverts tokens without current ownership to a "stash" address instead of sending them to the zero address (0x00) as is common with burns.
* It also enables the actual burning of tokens, sending them irreversibly to the zero address.
* Features include batch minting for ERC-721 tokens to optimize minting operations, gas savings optimizations, and support for EIP-2612 permit-based transactions.
* @author https://42i.co
*/
contract ERC404B is IERC404B, IERC20Errors, IERC721Errors, IERC404BErrors {
using Strings for uint256;
using BitMaps for BitMaps.BitMap;
using DoubleEndedQueue for DoubleEndedQueue.Uint256Deque; // DoubleEndedQueue.Uint32Deque would be great
/// @dev Token Name.
string private _name;
/// @dev Token Symbol.
string private _symbol;
/// @dev Token decimal digits.
uint8 private immutable _DECIMALS;
/// @dev The number of ERC20 tokens required for one ERC721 token.
uint256 private immutable _UNIT;
/// @dev The number of tokens held by each address.
mapping(address holder => uint256 balance) private _balances;
/// @dev Identifier for the next token to mint.
uint256 internal _nextTokenId = _startTokenId();
/// @dev ERC721 token approvals.
mapping(uint256 tokenId => address approvedAddress) private _tokenApprovals;
/// @dev ERC721 operator approvals.
mapping(address holder => mapping(address operator => bool approval)) private _operatorApprovals;
/// @dev ERC20 token allowances.
mapping(address holder => mapping(address operator => uint256 allowance)) private _allowances;
/// @dev Exempt addresses from ERC-721 transfers (e.g., pairs, routers) for gas savings.
mapping(address holder => bool exempt) private _erc721TransferExempt;
/// @dev Bitmask to extract lower 160 bits from a uint256.
uint256 private constant _BITMASK_LOWER160BITS = (1 << 160) - 1;
/// @dev Bitmask to extract upper 96 bits from a uint256.
uint256 private constant _BITMASK_UPPER96BITS = ((1 << 96) - 1) << 160;
/// @dev Array of owned ERC-721 token IDs, each position stores a batch of tokens with the initial ID (_BITMASK_LOWER160BITS) and quantity (_BITMASK_UPPER96BITS).
mapping(address holder => uint256[] batchArray) private _owned;
/// @dev Mapping storing in each position the owner address (_BITMASK_LOWER160BITS) and index of this batch in _owned (_BITMASK_UPPER96BITS).
mapping(uint256 tokenId => uint256 addressAndPosition) private _ownedData;
/// @dev Bitmap storing the head of the batches, indicating where the _ownedData is located.
BitMaps.BitMap private _batchHead;
/// @dev Bitmap representing burned tokens.
BitMaps.BitMap private _burnedTokens;
/// @dev Amount of burned tokens.
uint256 private _burnedCount = 0;
/// @dev Queue of ERC-721 tokens in the stash.
DoubleEndedQueue.Uint256Deque private _stashQueue;
/// @dev Transfer(address,address,uint256) hash signature.
bytes32 private constant _TRANSFER_EVENT_SIGNATURE = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
/// @dev Approval(address,address,uint256) hash signature.
bytes32 private constant _APPROVAL_EVENT_SIGNATURE = 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925;
/// @dev ApprovalForAll(address,address,bool) hash signature.
bytes32 private constant _APPROVALFORALL_EVENT_SIGNATURE = 0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31;
/// @dev Stash address for this token.
address private immutable _STASH_ADDRESS;
/// @dev EIP-2612 nonces.
mapping(address => uint256) public nonces;
/// @dev Initial chain id for EIP-2612 support.
uint256 internal immutable _INITIAL_CHAIN_ID;
/// @dev Initial domain separator for EIP-2612 support.
bytes32 internal immutable _INITIAL_DOMAIN_SEPARATOR;
/**
* @dev Initializes the contract with token details and necessary parameters.
*
* @param name_ The name of the token.
* @param symbol_ The symbol of the token.
* @param unit_ The equivalence between 1 ERC721 token and ERC20 needed for that token.
*/
constructor(string memory name_, string memory symbol_, uint256 unit_) {
_name = name_;
_symbol = symbol_;
_DECIMALS = 18;
_UNIT = unit_ * 10 ** _DECIMALS;
_STASH_ADDRESS = address(new Stash());
// EIP-2612 initialization
_INITIAL_CHAIN_ID = block.chainid;
_INITIAL_DOMAIN_SEPARATOR = _computeDomainSeparator();
}
/**
* @dev Returns the starting token ID.
*
* @return The starting token ID, which is set to 1 by default.
* To change the starting token ID, please override this function.
*/
function _startTokenId() internal pure returns (uint256) {
return 1;
}
/**
* @dev Returns the total number of tokens minted in the contract.
*
* @return The total number of tokens minted.
*/
function _totalMinted() internal view virtual returns (uint256) {
return _nextTokenId - _startTokenId();
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId_) public view virtual returns (bool) {
return interfaceId_ == type(IERC404B).interfaceId ||
interfaceId_ == type(IERC165).interfaceId ||
interfaceId_ == type(IERC20).interfaceId ||
interfaceId_ == type(IERC721).interfaceId ||
interfaceId_ == type(IERC721Receiver).interfaceId ||
interfaceId_ == type(IERC721Metadata).interfaceId;
}
/// IERC20 + ERC721 Metadata Methods ///
/**
* @dev See {IERC404-name}.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @dev See {IERC404-symbol}.
*/
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
/**
* @dev See {IERC404-decimals}.
*/
function decimals() public view virtual override returns (uint8) {
return _DECIMALS;
}
/**
* @dev See {IERC404-unit}.
*/
function unit() public view virtual returns (uint256) {
return _UNIT;
}
/**
* @dev See {IERC404-tokenURI}.
*/
function tokenURI(uint256 tokenId_) public view virtual override returns (string memory) {
if (!_exists(tokenId_)) revert ERC721NonexistentToken(tokenId_);
string memory baseURI = _baseURI();
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId_.toString())) : "";
}
/**
* @dev Base URI for computing the {tokenURI}. If set, the resulting URI for each
* token will be the concatenation of the `baseURI` and the `tokenId`.
*
* It is empty by default and can be overridden in child contracts.
* @return The base URI string.
*/
function _baseURI() internal view virtual returns (string memory) {
return "";
}
/**
* @dev Checks whether the specified `tokenId` exists in the contract.
* Tokens start existing when they are minted using the {_mint} function. Burned tokens are not considered to exist.
*
* @param tokenId_ The ID of the token to check.
* @return A boolean indicating whether the token exists.
*/
function _exists(uint256 tokenId_) internal view virtual returns (bool) {
if (_burnedTokens.get(tokenId_)) {
return false;
}
return tokenId_ < _nextTokenId && _startTokenId() <= tokenId_;
}
/**
* @dev See {IERC404-exists}.
*/
function exists(uint256 tokenId_) external view virtual returns (bool) {
return _exists(tokenId_);
}
/// ERC721 Methods ///
/**
* @dev See {IERC404-ownerOf}.
*/
function ownerOf(uint256 tokenId_) public view virtual override returns (address owner) {
if (!_exists(tokenId_)) revert ERC721NonexistentToken(tokenId_);
uint256 data = _ownedData[_batchHead.scanForward(tokenId_)];
assembly {
owner := and(data, _BITMASK_LOWER160BITS)
}
if (owner == address(0)) {
owner = _STASH_ADDRESS;
}
}
/**
* @dev See {IERC404-safeTransferFrom}.
*/
function safeTransferFrom(address from_, address to_, uint256 tokenId_) public virtual override {
safeTransferFrom(from_, to_, tokenId_, "");
}
/**
* @dev See {IERC404-safeTransferFrom}.
*/
function safeTransferFrom(address from_, address to_, uint256 tokenId_, bytes memory data_) public virtual override {
if (!_exists(tokenId_)) revert ERC721NonexistentToken(tokenId_);
if (!_isApprovedOrOwner(msg.sender, tokenId_)) revert ERC721InsufficientApproval(msg.sender, tokenId_);
_safeTransferERC721(from_, to_, tokenId_, data_);
}
/**
* @dev Safely transfers `tokenId_` token from `from_` to `to_`, ensuring the recipient contract
* implements the ERC721Receiver interface to prevent tokens from being forever locked.
*
* `data_` is additional data without a specified format, included in the call to `to_`.
*
* This internal function is equivalent to {safeTransferFrom}, and can be used to implement alternative
* token transfer mechanisms, such as signature-based ones.
*
* Requirements:
* - `to_` cannot be the zero nor stash address.
* - `tokenId_` must exist and be owned by `from_`.
* - If `to_` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}.
*
* Emits a {Transfer} event.
*
* @param from_ The address sending the token.
* @param to_ The address receiving the token.
* @param tokenId_ The ID of the token being transferred.
* @param data_ Additional data sent during the token transfer.
*/
function _safeTransferERC721(address from_, address to_, uint256 tokenId_, bytes memory data_) internal virtual {
if (!_checkOnERC721Received(from_, to_, tokenId_, 1, data_))
revert ERC721ReceiverNotImplemented(to_, tokenId_, 1, data_);
if (to_ == address(0) || to_ == _STASH_ADDRESS || to_ == from_) revert ERC721InvalidReceiver(to_);
_transferERC721(from_, to_, tokenId_);
}
/**
* @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 startTokenId_ The first ID of the tokens to be transferred.
* @param quantity_ Amount of tokens to be transferred.
* @param data_ Optional data to send along with the call.
* @return r Boolean indicating whether the call returned the expected magic value.
*/
function _checkOnERC721Received(address from_, address to_, uint256 startTokenId_, uint256 quantity_, bytes memory data_) private returns (bool r) {
if (to_.code.length > 0) {
r = true;
for (uint256 tokenId = startTokenId_; tokenId < startTokenId_ + quantity_; tokenId++) {
try IERC721Receiver(to_).onERC721Received(msg.sender, from_, tokenId, data_) returns (bytes4 retval) {
r = r && retval == IERC721Receiver.onERC721Received.selector;
} catch (bytes memory reason) {
if (reason.length == 0) {
revert ERC721ReceiverNotImplemented(to_, startTokenId_, quantity_, data_);
} else {
assembly {
revert(add(32, reason), mload(reason))
}
}
}
}
return r;
} else {
return true;
}
}
/**
* @dev Returns whether `spender` is allowed to manage `tokenId`.
*
* Requirements:
* - `tokenId` must exist.
*
* @param spender_ The address being checked for approval.
* @param tokenId_ The ID of the token being checked.
* @return A boolean indicating whether `spender` is allowed to manage `tokenId`.
*/
function _isApprovedOrOwner(address spender_, uint256 tokenId_) internal view virtual returns (bool) {
address owner = ownerOf(tokenId_);
return (spender_ == owner || getApproved(tokenId_) == spender_ || isApprovedForAll(owner, spender_));
}
/**
* @dev Adds a batch of tokens to the `_owned` mapping for the given owner.
*
* @param batchInitialId_ The initial ID of the batch of tokens.
* @param owner_ The address of the owner.
* @param quantity_ The quantity of tokens in the batch.
*/
function _pushOwned(uint256 batchInitialId_, address owner_, uint256 quantity_) internal virtual {
uint256 data;
assembly {
data := add(and(batchInitialId_, _BITMASK_LOWER160BITS), and(shl(160, quantity_), _BITMASK_UPPER96BITS))
}
_owned[owner_].push(data);
}
/**
* @dev Sets the data for a specific batch of tokens in the `_owned` mapping for the given owner and index.
*
* @param batchInitialId_ The initial ID of the batch of tokens.
* @param owner_ The address of the owner.
* @param index_ The index of the batch in the `_owned` array.
* @param quantity_ The quantity of tokens in the batch.
*/
function _setOwned(uint256 batchInitialId_, address owner_, uint256 index_, uint256 quantity_) internal virtual {
uint256 data;
assembly {
data := add(and(batchInitialId_, _BITMASK_LOWER160BITS), and(shl(160, quantity_), _BITMASK_UPPER96BITS))
}
_owned[owner_][index_] = data;
}
/**
* @dev Retrieves the initial ID and quantity of tokens in a batch owned by a specific address.
*
* @param owner_ The address of the token owner.
* @param index_ The index of the batch in the owner's collection.
* @return batchInitialId The initial ID of the tokens in the batch.
* @return batchQuantity The quantity of tokens in the batch.
*/
function _getOwnedBatchInitialIdAndQuantity(address owner_, uint256 index_) internal view virtual returns (uint256 batchInitialId, uint256 batchQuantity) {
uint256 data = _owned[owner_][index_];
assembly {
batchInitialId := and(data, _BITMASK_LOWER160BITS)
batchQuantity := shr(160, data)
}
}
/**
* @dev Sets the data for a batch of owned tokens at a specific index in the _ownedData mapping.
* This function is used to update the data associated with a batch of tokens owned by an address.
* It ensures that the index does not exceed the upper limit defined by _BITMASK_UPPER96BITS >> 160.
*
* @param batchInitialId_ The initial ID of the tokens in the batch.
* @param ownerOf_ The address of the owner of the tokens in the batch.
* @param index_ The index of the batch within the _owned[ownerOf_] mapping.
*/
function _setOwnedData(uint256 batchInitialId_, address ownerOf_, uint256 index_) internal virtual {
if (index_ > _BITMASK_UPPER96BITS >> 160) {
revert ERC404OwnedIndexOverflow(index_);
}
uint256 data;
assembly {
data := add(and(ownerOf_, _BITMASK_LOWER160BITS), and(shl(160, index_), _BITMASK_UPPER96BITS))
}
_ownedData[batchInitialId_] = data;
}
/**
* @dev Retrieves the owner, index within the owner's batch, and token ID at the head of the batch for the given token ID.
*
* Requirements:
* - The token ID must exist.
*
* @param tokenId_ The ID of the token for which to retrieve information.
* @return owner The address of the token's owner.
* @return index The index of the token within the owner's batch.
* @return tokenIdBatchHead The token ID at the head of the batch.
*/
function _getOwnerOwnedIndexAndBatchHeadId(uint256 tokenId_) internal view returns (address owner, uint256 index, uint256 tokenIdBatchHead) {
tokenIdBatchHead = _batchHead.scanForward(tokenId_);
uint256 data = _ownedData[tokenIdBatchHead];
assembly {
index := shr(160, data)
owner := and(data, _BITMASK_LOWER160BITS)
}
}
/**
* @dev Transfers an ERC721 token from one address to another.
*
* Requirements:
* - `from_` must be the owner of the token.
* - Token ID must exist and be owned by `from_`.
* - If `to_` is a smart contract, it must implement {IERC721Receiver-onERC721Received}.
* - If `to_` is exempt from ERC721 transfer, the token is moved to the stash.
*
* Emits an {IERC721-Transfer} event.
* Emits an {IERC20-Transfer} event.
*
* @param from_ The address transferring the token.
* @param to_ The address receiving the token.
* @param tokenId_ The ID of the token being transferred.
*/
function _transferERC721(address from_, address to_, uint256 tokenId_) internal virtual {
(address owner, uint256 index, uint256 tokenIdBatchHead) = _getOwnerOwnedIndexAndBatchHeadId(tokenId_);
// _beforeTokenTransfers(from_, to_, tokenId_, 1);
delete _tokenApprovals[tokenId_]; // On transfer, any previous approval is reset.
uint256 batchQuantity;
uint256 data = _owned[owner][index];
assembly {
batchQuantity := shr(160, data)
}
_removeTokenFrom(from_, index, tokenId_, tokenIdBatchHead, batchQuantity);
if (_erc721TransferExempt[to_]) {
// Is exempt: move to stash
_batchHead.set(tokenId_);
_stashQueue.pushFront(tokenId_);
address mutableStashAddress = _STASH_ADDRESS;
assembly {
// Emit ERC721.Transfer(from_, _STASH_ADDRESS, tokenId_)
log4(0x00, 0x00, _TRANSFER_EVENT_SIGNATURE, and(from_, _BITMASK_LOWER160BITS), and(mutableStashAddress, _BITMASK_LOWER160BITS), tokenId_)
}
}
else {
// Add ownership to "to_"
assembly {
data := add(and(tokenId_, _BITMASK_LOWER160BITS), and(shl(160, 1), _BITMASK_UPPER96BITS))
}
_owned[to_].push(data);
uint256 index_ = _owned[to_].length - 1;
assembly {
data := add(and(to_, _BITMASK_LOWER160BITS), and(shl(160, index_), _BITMASK_UPPER96BITS))
}
_ownedData[tokenId_] = data;
assembly {
// emit IERC721.Transfer(from_, to_, tokenId_);
log4(0x00, 0x00, _TRANSFER_EVENT_SIGNATURE, and(from_, _BITMASK_LOWER160BITS), and(to_, _BITMASK_LOWER160BITS), tokenId_)
}
}
unchecked {
// `_balances[from]` cannot overflow for the same reason as described in `_burn`:
// `from`'s balance is the number of token held, which is at least one before the current
// transfer.
// `_balances[to]` could overflow in the conditions described in `_mint`. That would require
// all 2**256 token ids to be minted, which in practice is impossible.
_balances[from_] -= _UNIT;
_balances[to_] += _UNIT;
}
data = _UNIT;
assembly {
// emit IERC20.Transfer(from_, to_, _UNIT);
mstore(0x00, data)
log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, and(from_, _BITMASK_LOWER160BITS), and(to_, _BITMASK_LOWER160BITS))
}
// _afterTokenTransfers(from_, to_, tokenId_, 1);
}
/**
* @dev Removes a token from an address, adjusting the internal data structures accordingly.
*
* Requirements:
* - `from_` must be the owner of the token.
*
* @param from_ The address from which the token is being removed.
* @param index_ The index of the token within the owner's batch.
* @param tokenId_ The ID of the token being removed.
* @param batchInitialId_ The initial ID of the batch.
* @param batchQuantity_ The quantity of tokens in the batch.
*/
function _removeTokenFrom(address from_, uint256 index_, uint256 tokenId_, uint256 batchInitialId_, uint256 batchQuantity_) internal {
unchecked {
// If Is Batch Head == No tokens before in the batch.
if (batchInitialId_ == tokenId_) {
if (batchQuantity_ == 1) {
_removeSingleToken(from_, index_);
} else {
_removeBatchHeadToken(from_, index_, tokenId_, batchQuantity_);
}
} else {
// Is not batch head == There is tokens before for the
_removeNonHeadToken(from_, index_, tokenId_, batchInitialId_, batchQuantity_);
}
}
}
/**
* @dev Removes a single token from the owner's collection.
*
* This internal function is used during ERC721 token transfers to remove a single token from the owner's collection.
* It shifts the token data in the owner's collection to fill the gap left by the removed token, ensuring continuous indexing.
* If the removed token is not the last token in the collection, it updates the metadata and batch information accordingly.
*
* @param from_ The address of the owner from which the token is being removed.
* @param index_ The index of the token in the owner's collection to be removed.
*/
function _removeSingleToken(address from_, uint256 index_) internal {
unchecked {
uint256[] storage ownedFrom = _owned[from_];
uint256 fromStackLastIndex = ownedFrom.length - 1;
if (fromStackLastIndex != index_) {
uint256 data = ownedFrom[fromStackLastIndex];
ownedFrom[index_] = data;
uint256 lastBatchInitialId;
assembly {
lastBatchInitialId := and(data, _BITMASK_LOWER160BITS)
data := add(and(from_, _BITMASK_LOWER160BITS), and(shl(160, index_), _BITMASK_UPPER96BITS))
}
_ownedData[lastBatchInitialId] = data;
}
ownedFrom.pop();
}
}
/**
* @dev Removes the batch head token from the owner's collection.
*
* This internal function is used during ERC721 token transfers to remove the batch head token from the owner's collection.
* It sets the subsequent token ID as the new batch head, updates the batch information, and shifts the remaining tokens accordingly.
*
* @param from_ The address of the owner from which the batch head token is being removed.
* @param index_ The index of the batch head token in the owner's collection to be removed.
* @param tokenId_ The ID of the batch head token being removed.
* @param batchQuantity_ The quantity of tokens in the batch (including the batch head).
*/
function _removeBatchHeadToken(address from_, uint256 index_, uint256 tokenId_, uint256 batchQuantity_) internal {
unchecked {
uint256 subsequentTokenId = tokenId_ + 1;
_batchHead.set(subsequentTokenId);
uint256 data;
assembly {
data := add(and(from_, _BITMASK_LOWER160BITS), and(shl(160, index_), _BITMASK_UPPER96BITS))
}
_ownedData[subsequentTokenId] = data;
assembly {
data := add(
and(subsequentTokenId, _BITMASK_LOWER160BITS),
and(shl(160, sub(batchQuantity_, 1)), _BITMASK_UPPER96BITS)
)
}
_owned[from_][index_] = data;
}
}
/**
* @dev Removes a non-head token from the owner's collection within a batch.
*
* This internal function is used during ERC721 token transfers to remove a token that is not the batch head from the owner's collection within a batch.
* It updates the batch information, shifts the remaining tokens accordingly, and creates a new batch if necessary.
*
* @param from_ The address of the owner from which the token is being removed.
* @param index_ The index of the token in the owner's collection to be removed.
* @param tokenId_ The ID of the token being removed.
* @param batchInitialId_ The ID of the first token in the batch.
* @param batchQuantity_ The quantity of tokens in the batch.
*/
function _removeNonHeadToken(address from_, uint256 index_, uint256 tokenId_, uint256 batchInitialId_, uint256 batchQuantity_) internal {
unchecked {
_batchHead.set(tokenId_);
uint256 batchSizeAndIndex = tokenId_ - batchInitialId_;
uint256 data;
assembly {
data := add(and(batchInitialId_, _BITMASK_LOWER160BITS), and(shl(160, batchSizeAndIndex), _BITMASK_UPPER96BITS))
}
_owned[from_][index_] = data;
if (batchSizeAndIndex < batchQuantity_ - 1) {
// It means that the batch continues
uint256 subsequentTokenId = tokenId_ + 1;
_batchHead.set(subsequentTokenId);
batchSizeAndIndex = (batchQuantity_ - 1) - (tokenId_ - batchInitialId_);
assembly {
data := add(and(subsequentTokenId, _BITMASK_LOWER160BITS), and(shl(160, batchSizeAndIndex), _BITMASK_UPPER96BITS))
}
_owned[from_].push(data);
batchSizeAndIndex = _owned[from_].length - 1;
assembly {
data := add(and(from_, _BITMASK_LOWER160BITS), and(shl(160, batchSizeAndIndex), _BITMASK_UPPER96BITS))
}
_ownedData[subsequentTokenId] = data;
}
}
}
/**
* @dev See {IERC404-getApproved}.
*/
function getApproved(uint256 tokenId_) public view virtual override returns (address) {
if (!_exists(tokenId_)) revert ERC721NonexistentToken(tokenId_);
return _tokenApprovals[tokenId_];
}
/**
* @dev See {IERC404-setApprovalForAll}.
*/
function setApprovalForAll(address operator_, bool approved_) public virtual override {
address owner = msg.sender;
if(operator_ == owner) revert ERC721InvalidOperator(operator_);
_operatorApprovals[owner][operator_] = approved_;
assembly {
// emit IERC721.ApprovalForAll(owner, operator, approved);
mstore(0x00, approved_)
log3(0x00, 0x20, _APPROVALFORALL_EVENT_SIGNATURE, and(owner, _BITMASK_LOWER160BITS), and(operator_, _BITMASK_LOWER160BITS))
}
}
/**
* @dev See {IERC404-isApprovedForAll}.
*/
function isApprovedForAll(address owner_, address operator_) public view virtual override returns (bool) {
return _operatorApprovals[owner_][operator_];
}
/**
* @dev Safely mints a specified quantity of ERC721 tokens and assigns them to the specified address.
*
* This internal function is equivalent to calling `_safeMint(to, quantity, "")`, providing an empty data parameter.
* It ensures that the minted tokens are safely transferred to the recipient by calling the ERC721 safe transfer function.
*
* @param to_ The address to which the minted tokens will be assigned.
* @param quantity_ The number of tokens to mint.
*/
function _safeMint(address to_, uint256 quantity_) internal virtual {
_safeMint(to_, quantity_, "");
}
/**
* @dev Safely mints a specified quantity of ERC721 tokens and assigns them to the specified address.
*
* This internal function is equivalent to calling `_mint(to_, quantity_)` followed by a check to ensure that the recipient contract implements ERC721Receiver.
* It requires the recipient contract to implement the ERC721Receiver interface's `onERC721Received` function to receive the tokens safely.
*
* @param to_ The address to which the minted tokens will be assigned.
* @param quantity_ The number of tokens to mint.
* @param data_ Additional data sent along with the token transfer, if any.
*/
function _safeMint(address to_, uint256 quantity_, bytes memory data_) internal virtual {
if (!_checkOnERC721Received(address(0), to_, _nextTokenId, quantity_, data_)) {
revert ERC721ReceiverNotImplemented(to_, _nextTokenId, quantity_, data_);
}
_mint(to_, quantity_);
}
/**
* @dev Mints a specified quantity of ERC721 tokens and assigns them to the specified address.
*
* This internal function performs the minting of ERC721 tokens and updates the balances accordingly.
* It also emits one ERC20 Transfer event and a ERC721 Transfer event for each minted token.
* If the recipient address is exempt from ERC721 transfer fees, the method sets the batch head accordingly;
* otherwise, it adds the minted tokens to the recipient's ownership.
*
* Requirements:
* - `quantity_` must be greater than 0.
* - `to_` address must not be the zero nor stash address.
*
* Emits an IERC20 {Transfer} event.
* Emits an IERC721 {Transfer} event for each minted token.
*
* @param to_ The address to which the minted tokens will be assigned.
* @param quantity_ The number of tokens to mint.
*/
function _mint(address to_, uint256 quantity_) internal virtual {
if (quantity_ == 0) revert ERC721InvalidMintQuantity();
if (to_ == address(0) || to_ == _STASH_ADDRESS) revert ERC20InvalidReceiver(to_);
// _beforeTokenTransfers(address(0), to_, _nextTokenId, quantity_);
uint256 value;
uint256 toMasked;
uint256 end;
uint256 batchTokenId = _nextTokenId;
unchecked {
value = quantity_ * _UNIT;
_balances[to_] += value; // May overflow in big quantities if total balance not controlled.
end = batchTokenId + quantity_;
}
assembly {
// emit IERC20.Transfer(0x00, to_, _UNIT);
toMasked := and(to_, _BITMASK_LOWER160BITS)
mstore(0x00, value)
log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, 0x00, toMasked)
}
if (_erc721TransferExempt[to_]) {
_batchHead.setBatch(batchTokenId, quantity_);
} else {
_batchHead.set(batchTokenId);
_pushOwned(batchTokenId, to_, quantity_);
_setOwnedData(batchTokenId, to_, _owned[to_].length - 1);
// Use assembly to loop and emit the `Transfer` event for gas savings.
// The duplicated `log4` removes an extra check and reduces stack juggling.
assembly {
// emit IERC721.Transfer(0x00, to_, batchTokenId);
log4(0x00, 0x00, _TRANSFER_EVENT_SIGNATURE, 0x00, toMasked, batchTokenId)
// The `iszero(eq(,))` check ensures that large values of `quantity`
// that overflows uint256 will make the loop run out of gas.
// The compiler will optimize the `iszero` away for performance.
for {
let tokenId := add(batchTokenId, 1)
} iszero(eq(tokenId, end)) {
tokenId := add(tokenId, 1)
} {
// emit IERC721.Transfer(0x00, to_, tokenId);
log4(0x00, 0x00, _TRANSFER_EVENT_SIGNATURE, 0x00, toMasked, tokenId)
}
}
}
// _afterTokenTransfers(address(0), to_, _nextTokenId, quantity_);
unchecked {
_nextTokenId += quantity_;
}
}
/// ERC20 Methods ///
/**
* @dev See {IERC404-totalSupply}.
*/
function totalSupply() public view virtual override returns (uint256) {
return _totalTokens() * _UNIT;
}
/**
* @dev Calculates the total number of tokens that have been minted and not burned.
*
* This internal function returns the difference between the total minted tokens and the burned tokens, representing the total number of existing tokens in circulation.
*
* @return The total number of existing tokens in circulation.
*/
function _totalTokens() internal view virtual returns (uint256) {
return _totalMinted() - _burned();
}
/**
* @dev Retrieves the total number of tokens that have been burned.
*
* This internal function returns the count of tokens that have been burned within the contract.
*
* @return The total number of tokens that have been burned.
*/
function _burned() internal view virtual returns (uint256) {
return _burnedCount;
}
/**
* @dev See {IERC404-transfer}.
*/
function transfer(address to_, uint256 valueOrId_) public virtual override returns (bool) {
address owner = msg.sender;
transferFrom(owner, to_, valueOrId_);
return true;
}
/**
* @dev See {IERC404-allowance}.
*/
function allowance(address owner_, address spender_) public view virtual override returns (uint256) {
return _allowances[owner_][spender_];
}
/// ERC404 Combined (Methods with similar interfaces and behavior in ERC20 & ERC721) ///
/**
* @dev See {IERC404-balanceOf}.
*/
function balanceOf(address account_) public view virtual override returns (uint256) {
return _balances[account_];
}
/**
* @dev See {IERC404-transferFrom}.
*/
function transferFrom(address from_, address to_, uint256 valueOrId_) public virtual returns (bool) {
if (_exists(valueOrId_)) {
safeTransferFrom(from_, to_, valueOrId_, "");
} else {
return _transferFromERC20(from_, to_, valueOrId_);
}
return true;
}
/**
* @dev Transfers ERC20 tokens from one address to another, handling ERC721 exemptions internally.
*
* This function is used to transfer ERC20 tokens directly between addresses, handling exemptions for ERC721 transfers
* internally. It checks for valid recipients, allowances, and handles ERC721 exemptions if the recipient address is
* exempt from ERC721 transfers.
*
* Requirements:
* - `to_` cannot be the zero address or the stash address.
*
* @param from_ The address sending the ERC20 tokens.
* @param to_ The address receiving the ERC20 tokens.
* @param value_ The amount of ERC20 tokens to transfer.
* @return A boolean indicating whether the transfer was successful.
*/
function _transferFromERC20(address from_, address to_, uint256 value_) public virtual returns (bool) {
if (to_ == address(0) || to_ == _STASH_ADDRESS || to_ == from_) revert ERC20InvalidReceiver(to_);
if (from_ != msg.sender) {
uint256 allowed = _allowances[from_][msg.sender];
// Check that the operator has sufficient allowance.
if (allowed != type(uint256).max) {
if(value_ > allowed) revert ERC20InsufficientAllowance(from_, allowed, value_);
_allowances[from_][msg.sender] = allowed - value_;
}
}
// Transferring ERC-20s directly requires the _transferERC20WithERC721 function.
return _transferERC20WithERC721(from_, to_, value_);
}
/**
* @dev This function handles the transfer of ERC-20 tokens and optionally adjusts the ERC-721 token balances based on the transfer exemptions.
*
* Requirements:
* - The sender (`from_`) must have a balance of at least `value_`.
*
* Emits:
* - {IERC20.Transfer} event for ERC-20 and possibly a {IERC721.Transfer} for each ERC-721 Token.
*
* @param from_ Address sending the tokens.
* @param to_ Address receiving the tokens.
* @param value_ Amount of ERC-20 tokens to transfer.
* @return bool True if the transfer is successful.
*/
function _transferERC20WithERC721(address from_, address to_, uint256 value_) internal virtual returns (bool) {
uint256 erc20BalanceOfSenderBefore = _balances[from_];
uint256 erc20BalanceOfReceiverBefore = _balances[to_];
if (erc20BalanceOfSenderBefore < value_) revert ERC20InsufficientBalance(from_, erc20BalanceOfSenderBefore, value_);
unchecked {
_balances[from_] -= value_;
_balances[to_] += value_;
}
assembly {
// emit IERC20.Transfer(from_, to_, value_);
mstore(0x00, value_)
log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, and(from_, _BITMASK_LOWER160BITS), and(to_, _BITMASK_LOWER160BITS))
}
// Skip ERC-721 transfer to exempt addresses to save gas
bool isFromERC721TransferExempt = _erc721TransferExempt[from_];
bool isToERC721TransferExempt = _erc721TransferExempt[to_];
if (isFromERC721TransferExempt && isToERC721TransferExempt) {
// Case 1) Both sender and recipient are ERC-721 transfer exempt. No ERC-721s need to be transferred.
} else if (isFromERC721TransferExempt) {
// Case 2) The sender is ERC-721 transfer exempt, but the recipient is not.
unchecked {
uint256 tokensToRetrieveFromStash = (_balances[to_] / _UNIT) - (erc20BalanceOfReceiverBefore / _UNIT);
_retrieveFromStash(to_, tokensToRetrieveFromStash);
}
} else if (isToERC721TransferExempt) {
// Case 3) The sender is not ERC-721 transfer exempt, but the recipient is.
unchecked {
uint256 tokensToStash = (erc20BalanceOfSenderBefore / _UNIT) - (_balances[from_] / _UNIT);
_stash(from_, tokensToStash);
}
} else {
// Case 4) Neither the sender nor the recipient are ERC-721 transfer exempt.
_batchTransferERC721WithBalanceAdjustment(from_, to_, erc20BalanceOfSenderBefore, erc20BalanceOfReceiverBefore, value_);
}
return true;
}
/**
* @dev Internal function to batch transfer ERC721 tokens with balance adjustment.
*
* Emits a {IERC721.Transfer} event for each token transferred, including the initial token ID and all subsequent IDs in the batch.
*
* @param from_ The address from which to transfer tokens.
* @param to_ The address to which to transfer tokens.
* @param fromPreviousBalance_ The previous balance of tokens for the sender.
* @param toPreviousBalance_ The previous balance of tokens for the recipient.
* @param transferedValue_ The value of tokens to be transferred.
*/
function _batchTransferERC721WithBalanceAdjustment(address from_, address to_, uint256 fromPreviousBalance_, uint256 toPreviousBalance_, uint256 transferedValue_) internal {
uint256 tokenId;
uint256 end;
uint256 nftsToTransfer = transferedValue_ / _UNIT;
for (uint256 i = 0; i < nftsToTransfer; ) {
// Transfers the whole batch
uint256 lastOwnedIndex = _owned[from_].length - 1;
(uint256 batchInitialId_, uint256 batchQuantity_) = _getOwnedBatchInitialIdAndQuantity(from_, lastOwnedIndex);
if (batchQuantity_ + i <= nftsToTransfer) {
// Transfer whole batch
_owned[to_].push(_owned[from_][lastOwnedIndex]);
_owned[from_].pop();
uint256 lastToIndex = _owned[to_].length - 1;
_setOwnedData(batchInitialId_, to_, lastToIndex);
unchecked {
tokenId = batchInitialId_;
end = batchInitialId_ + batchQuantity_;
i += batchQuantity_;
}
} else {
// Transfers part of the batch
unchecked {
uint256 tokensToTransfer = nftsToTransfer - i;
uint256 tokensInPreviousBatch = batchQuantity_ - tokensToTransfer;
_setOwned(batchInitialId_, from_, lastOwnedIndex, tokensInPreviousBatch);
uint256 newBatchInitialId = batchInitialId_ + tokensInPreviousBatch;
_batchHead.set(newBatchInitialId);
_pushOwned(newBatchInitialId, to_, tokensToTransfer);
_setOwnedData(newBatchInitialId, to_, _owned[to_].length - 1);
tokenId = newBatchInitialId;
end = newBatchInitialId + tokensToTransfer;
i = nftsToTransfer;
}
}
unchecked {
for (uint256 j = tokenId; j < end; ++j) {
delete _tokenApprovals[j]; // On transfer, any previous approval is reset.
assembly {
// emit IERC721.Transfer(from_, to_, emitInitialId);
log4(0x00, 0x00, _TRANSFER_EVENT_SIGNATURE, and(from_, _BITMASK_LOWER160BITS), and(to_, _BITMASK_LOWER160BITS), j)
}
}
}
}
// If the transfer changes either the sender or the recipient's holdings from a fractional to a non-fractional
// amount (or vice versa), adjust ERC-721s.
unchecked {
// First check if the send causes the sender to lose a whole token that was represented by an ERC-721
// due to a fractional part being transferred.
if (fromPreviousBalance_ / _UNIT - _balances[from_] / _UNIT > nftsToTransfer) {
_stash(from_, 1);
}
// Then, check if the transfer causes the receiver to gain a whole new token which requires gaining
// an additional ERC-721.
if (_balances[to_] / _UNIT - toPreviousBalance_ / _UNIT > nftsToTransfer) {
_retrieveFromStash(to_);
}
}
}
/**
* @dev Internal virtual function to stash ERC721 tokens.
*
* Emits a {IERC721.Transfer} event for each token stashed.
*
* @param from_ The address from which to stash tokens.
* @param quantity_ The quantity of tokens to be stashed.
*/
function _stash(address from_, uint256 quantity_) internal virtual {
unchecked {
uint256 batchInitialId_;
uint256 batchQuantity_;
uint256 data;
// Stash loop variables
uint256 begin;
uint256 end;
for (uint256 stashed = 0; stashed < quantity_; ) {
data = _owned[from_][_owned[from_].length - 1];
assembly {
batchInitialId_ := and(data, _BITMASK_LOWER160BITS)
batchQuantity_ := shr(160, data)
}
if (stashed + batchQuantity_ <= quantity_) {
// Transfer the whole batch
delete _ownedData[batchInitialId_];
_batchHead.setBatch(batchInitialId_, batchQuantity_); // Set batchead in a massive way to all tokens.
_owned[from_].pop(); // Remove batch from owned ids
stashed += batchQuantity_; // Increment the stashed items
begin = batchInitialId_;
end = begin + batchQuantity_;
} else {
// Only transfer the amount needed, maintain the batch
uint256 tokensToStash = quantity_ - stashed;
uint256 nonStashedBatchSize = batchQuantity_ - tokensToStash;
begin = batchInitialId_ + nonStashedBatchSize;
end = begin + tokensToStash;
_batchHead.setBatch(begin, tokensToStash); // Set batchead in a massive way to all tokens to be stashed
_setOwned(batchInitialId_, from_, _owned[from_].length - 1, nonStashedBatchSize); // Update the batch size
stashed = quantity_; // Update the stashed items
}
address mutableStashAddress = _STASH_ADDRESS;
for (uint256 i=begin; i<end; ++i) {
_stashQueue.pushFront(i);
delete _tokenApprovals[i]; // On stash of a token, any previous approval is reset.
assembly {
// emit IERC721.Transfer(from_, _STASH_ADDRESS, tokenId);
log4(0x00, 0x00, _TRANSFER_EVENT_SIGNATURE, and(from_, _BITMASK_LOWER160BITS), and(mutableStashAddress, _BITMASK_LOWER160BITS), i)
}
}
}
}
}
/**
* @dev Internal virtual function to retrieve ERC721 tokens from the stash.
*
* Emits a {IERC721.Transfer} event for each token retrieved.
*
* @param to_ The address to which retrieved tokens will be transferred.
*/
function _retrieveFromStash(address to_) internal virtual {
uint256 id = _stashQueue.popBack();
_pushOwned(id, to_, 1);
unchecked {
_setOwnedData(id, to_, _owned[to_].length - 1);
}
address mutableStashAddress = _STASH_ADDRESS;
assembly {
// emit IERC721.Transfer(_STASH_ADDRESS, to_, tokenId);
log4(0x00, 0x00, _TRANSFER_EVENT_SIGNATURE, and(mutableStashAddress, _BITMASK_LOWER160BITS), and(to_, _BITMASK_LOWER160BITS), id)
}
}
/**
* @dev Internal function to retrieve multiple ERC721 tokens from the stash and transfer them to a recipient.
*
* Emits a {IERC721.Transfer} event for each token retrieved, including the token ID transferred to the recipient.
*
* @param to_ The address to which retrieved tokens will be transferred.
* @param amount_ The number of tokens to retrieve from the stash.
*/
function _retrieveFromStash(address to_, uint256 amount_) internal {
for (uint256 i = 0; i < amount_; ) {
_retrieveFromStash(to_);
unchecked {
++i;
}
}
}
/**
* @dev See {IERC404-approve}.
*/
function approve(address spender_, uint256 valueOrId_) public virtual returns (bool) {
if (_exists(valueOrId_)) {
_erc721Approve(spender_, valueOrId_);
} else {
return _erc20Approve(spender_, valueOrId_);
}
return true;
}
/**
* @dev Approves a specific address to transfer the specified ERC-721 token.
*
* Requirements:
* - The caller must be the owner of the ERC-721 token or have been approved by the owner.
*
* Emits:
* - {IERC721.Approval} event for ERC-721 Tokens.
*
* @param spender_ Address to be approved for the specified ERC-721 token.
* @param id_ ID of the ERC-721 token to be approved.
*/
function _erc721Approve(address spender_, uint256 id_) public virtual {
address erc721Owner = ownerOf(id_);
if (msg.sender != erc721Owner && !isApprovedForAll(erc721Owner, msg.sender)) {
revert ERC721InvalidApprover(msg.sender);
}
_tokenApprovals[id_] = spender_;
assembly {
// emit IERC721.Approval(erc721Owner, spender_, id_);
log4(0x00, 0x00, _APPROVAL_EVENT_SIGNATURE, and(erc721Owner, _BITMASK_LOWER160BITS), and(spender_, _BITMASK_LOWER160BITS), id_)
}
}
/**
* @dev Approves a specific address to spend a specified amount of ERC-20 tokens on behalf of the caller.
*
* Requirements:
* - The spender address must not be the zero address.
*
* Emits:
* - {IERC20.Approval} event for ERC-20 Tokens.
*
* @param spender_ Address to be approved for spending the specified ERC-20 tokens.
* @param value_ Amount of ERC-20 tokens to be approved for spending.
* @return bool True if the approval is successful.
*/
function _erc20Approve(address spender_, uint256 value_) public virtual returns (bool) {
address owner = msg.sender;
if (spender_ == address(0) || spender_ == _STASH_ADDRESS) {
revert ERC20InvalidSpender(spender_);
}
_allowances[owner][spender_] = value_;
assembly {
// emit IERC20.Approval(msg.sender, spender_, value_);
let ownerMasked := and(owner, _BITMASK_LOWER160BITS)
let approvedMasked := and(spender_, _BITMASK_LOWER160BITS)
mstore(0x00, value_)
log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, ownerMasked, approvedMasked)
}
return true;
}
/**
* @dev See {IERC20Permit-permit}.
*/
function permit(address owner_, address spender_, uint256 value_, uint256 deadline_, uint8 v_, bytes32 r_, bytes32 s_) public virtual {
if (deadline_ < block.timestamp) {
revert EIP2612PermitDeadlineExpired(owner_, spender_, value_, deadline_, v_, r_, s_);
}
// permit cannot be used for ERC-721 token approvals, so ensure
// the value does not fall within the valid range of ERC-721 token ids.
if (_exists(value_)) {
revert ERC404InvalidTransferValue(value_);
}
if (spender_ == address(0) || spender_ == _STASH_ADDRESS) {
revert ERC20InvalidSpender(spender_);
}
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"),
owner_,
spender_,
value_,
nonces[owner_]++,
deadline_
)
)
)
),
v_,
r_,
s_
);
if (recoveredAddress == address(0) || recoveredAddress != owner_) {
revert EIP2612InvalidSigner(recoveredAddress, owner_, spender_, value_, deadline_, v_, r_, s_);
}
_allowances[recoveredAddress][spender_] = value_;
}
assembly {
// emit IERC20.Approval(owner_, spender_, value_);
let ownerMasked := and(owner_, _BITMASK_LOWER160BITS)
let approvedMasked := and(spender_, _BITMASK_LOWER160BITS)
mstore(0x00, value_)
log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, ownerMasked, approvedMasked)
}
}
/**
* @dev See {IERC20Permit-DOMAIN_SEPARATOR}.
*/
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == _INITIAL_CHAIN_ID ? _INITIAL_DOMAIN_SEPARATOR : _computeDomainSeparator();
}
/**
* @notice Internal function to compute the domain separator for EIP-2612 permits.
* @dev This function computes the domain separator based on the contract's name, version, chain ID, and address.
*
* @return bytes32 The computed domain separator value.
*/
function _computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(_name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/**
* @dev See {IERC404-isERC721TransferExempt}.
*/
function isERC721TransferExempt(address target_) external view override returns (bool isExempt) {
isExempt = _erc721TransferExempt[target_];
}
/**
* @dev See {IERC404-setERC721TransferExempt}.
*/
function setERC721TransferExempt(bool state_) external override {
_setERC721TransferExempt(msg.sender, state_);
}
/**
* @dev Internal function to set the exemption status for ERC-721 transfers.
*
* Requirements:
* - `target_` address cannot be the zero address or the stash address.
*
* @param target_ The address for which to set the exemption status.
* @param state_ The new exemption state to set (true for exempt, false for non-exempt).
*/
function _setERC721TransferExempt(address target_, bool state_) internal virtual {
if (target_ == address(0) || target_ == _STASH_ADDRESS) {
revert ERC404InvalidERC721Exemption(target_);
}
// Adjust the ERC721 balances of the target to respect exemption rules.
if (state_) {
_stashAll(target_);
} else {
_retrieveAllFromStash(target_);
}
_erc721TransferExempt[target_] = state_;
}
/**
* @dev Internal function to stash all ERC-721 tokens owned by the target address.
*
* Emits:
* - {IERC721.Transfer} event for ERC-721 tokens being transferred to the stash.
*
* @param target_ The address whose tokens are to be stashed.
*/
function _stashAll(address target_) private {
uint256[] memory ownedTarget = _owned[target_];
for (uint256 i = 0; i < ownedTarget.length; ) {
(uint256 batchInitialId_, uint256 batchQuantity_) = _getOwnedBatchInitialIdAndQuantity(target_, i);
delete _ownedData[batchInitialId_]; // Resets _ownedData
_batchHead.setBatch(batchInitialId_, batchQuantity_); // Set batchead in a massive way to all tokens.
// add all tokens to the stash
unchecked {
uint256 end = batchInitialId_ + batchQuantity_;
address mutableStashAddress = _STASH_ADDRESS;
for (uint256 b = batchInitialId_; b < end; ++b) {
delete _tokenApprovals[b]; // On stash of a token, any previous approval is reset.
_stashQueue.pushFront(b);
assembly {
// emit IERC721.Transfer(target_, _STASH_ADDRESS, batchInitialId_);
log4(0x00, 0x00, _TRANSFER_EVENT_SIGNATURE, and(target_, _BITMASK_LOWER160BITS), and(mutableStashAddress, _BITMASK_LOWER160BITS), b)
}
}
++i;
}
}
delete _owned[target_];
}
/**
* @dev Internal function to retrieve all ERC-721 tokens from the stash for the target address.
*
* Emits:
* - {IERC721.Transfer} event for ERC-721 tokens being transferred from the stash.
*
* @param target_ The address to retrieve ERC-721 tokens for.
*/
function _retrieveAllFromStash(address target_) private {
uint256 expectedERC721Balance = _balances[target_] / _UNIT;
uint256 actualERC721Balance = 0;
uint256[] memory ownedTarget = _owned[target_];
for (uint256 i = 0; i < ownedTarget.length; ) {
uint256 data = ownedTarget[i];
assembly {
actualERC721Balance := add(actualERC721Balance, shr(160, data))
i := add(i, 1) // Avoiding an unchecked block after this one
}
}
unchecked {
expectedERC721Balance -= actualERC721Balance;
for (uint256 i = 0; i < expectedERC721Balance; ++i) {
// Transfer ERC721 balance in from pool
_retrieveFromStash(target_);
}
}
}
/**
* @dev See {IERC404-owned}.
*/
function owned(address owner_) public view virtual returns (uint256[] memory ownedCreatureIds) {
if (owner_ == _STASH_ADDRESS) return tokensInStash();
uint256 size = 0;
uint256 data;
uint256[] memory ownedOwner = _owned[owner_];
for (uint256 i = 0; i < ownedOwner.length; ) {
data = ownedOwner[i];
assembly {
size := add(size, shr(160, data))
i := add(i, 1)
}
}
ownedCreatureIds = new uint256[](size);
unchecked {
uint256 ix = 0;
uint256 batchInitialId_;
uint256 batchQuantity_;
for (uint256 i = 0; i < ownedOwner.length; ++i) {
data = ownedOwner[i];
assembly {
batchInitialId_ := and(data, _BITMASK_LOWER160BITS)
batchQuantity_ := shr(160, data)
}
for (uint256 j = 0; j < batchQuantity_; ++j) {
ownedCreatureIds[ix] = batchInitialId_ + j;
++ix;
}
}
}
}
/**
* @dev Internal function to burn (destroy) an ERC-721 token.
*
* Emits:
* - {IERC721.Transfer} event from `from` to `address(0)` (burning the token).
* - {IERC20.Transfer} event from `from` to `address(0)` with `_UNIT` value (The ERC-20 side of the token).
*
* @param tokenId_ ID of the ERC-721 token to be burned.
*/
function _burn(uint256 tokenId_) internal virtual {
if (!_exists(tokenId_)) revert ERC721NonexistentToken(tokenId_);
(address from, uint256 index, uint256 tokenIdBatchHead) = _getOwnerOwnedIndexAndBatchHeadId(tokenId_);
// _beforeTokenTransfers(from, to, tokenId, 1);
delete _tokenApprovals[tokenId_]; // On transfer, any previous approval is reset.
uint256 batchQuantity_;
uint256 data = _owned[from][index];
assembly {
batchQuantity_ := shr(160, data)
}
_removeTokenFrom(from, index, tokenId_, tokenIdBatchHead, batchQuantity_);
delete _ownedData[tokenId_];
_batchHead.set(tokenId_);
_burnedTokens.set(tokenId_);
unchecked {
// Cannot overflow, as that would require more tokens to be burned/transferred
// out than the owner initially received through minting and transferring in.
_balances[from] -= _UNIT;
++_burnedCount;
}
data = _UNIT;
assembly {
let fromMasked := and(from, _BITMASK_LOWER160BITS)
// emit IERC20.Transfer(from_, to_, _UNIT);
mstore(0x00, data)
log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, fromMasked, 0x00)
// emit IERC721.Transfer(from, address(0), tokenId_);
log4(0x00, 0x00, _TRANSFER_EVENT_SIGNATURE, fromMasked, 0x00, tokenId_)
}
// _afterTokenTransfers(from, address(0), tokenId, 1);
}
/**
* @dev See {IERC404-stashAddress}.
*/
function stashAddress() public view returns (address) {
return _STASH_ADDRESS;
}
/**
* @dev See {IERC404-stashLength}.
*/
function stashLength() public view returns (uint256) {
return _stashQueue.length();
}
/**
* @dev See {IERC404-tokensInStash}.
*/
function tokensInStash() public view returns (uint256[] memory) {
return _stashQueue.retriveQueueItems();
}
/**
* @dev See {IERC404-exchangeWithStash}.
*/
function exchangeWithStash(uint256 tokenId_, uint128 index_) public {
uint256 stashTokenId = _stashQueue.at(index_);
if (!_exists(tokenId_)) revert ERC721NonexistentToken(tokenId_);
if (!_isApprovedOrOwner(msg.sender, tokenId_)) revert ERC721InsufficientApproval(msg.sender, tokenId_);
(address owner, uint256 index, uint256 tokenIdBatchHead) = _getOwnerOwnedIndexAndBatchHeadId(tokenId_);
// _beforeTokenTransfers(msg.sender, _STASH_ADDRESS, tokenId_, 1);
// _beforeTokenTransfers(_STASH_ADDRESS, msg.sender, stashTokenId, 1);
delete _tokenApprovals[tokenId_]; // On transfer, any previous approval is reset.
uint256 batchQuantity_;
uint256 data = _owned[owner][index];
assembly {
batchQuantity_ := shr(160, data)
}
_removeTokenFrom(owner, index, tokenId_, tokenIdBatchHead, batchQuantity_);
delete _ownedData[tokenId_];
_batchHead.set(tokenId_);
_stashQueue.setIndexValue(index_, tokenId_); // Sets the token to exchange into the stash
// Now, sets the Stash token to the tokenId owner
assembly {
data := add(and(stashTokenId, _BITMASK_LOWER160BITS), and(shl(160, 1), _BITMASK_UPPER96BITS))
}
_owned[owner].push(data);
unchecked {
uint256 ownedIndex = _owned[owner].length - 1;
assembly {
data := add(and(owner, _BITMASK_LOWER160BITS), and(shl(160, ownedIndex), _BITMASK_UPPER96BITS))
}
}
_ownedData[stashTokenId] = data;
address stashMasked = _STASH_ADDRESS;
assembly {
stashMasked := and(stashMasked, _BITMASK_LOWER160BITS)
let ownerMasked := and(owner, _BITMASK_LOWER160BITS)
// emit IERC721.Transfer(_STASH_ADDRESS, owner, stashTokenId);
log4(0x00, 0x00, _TRANSFER_EVENT_SIGNATURE, stashMasked, ownerMasked, stashTokenId)
// emit IERC721.Transfer(owner, _STASH_ADDRESS, tokenId_);
log4(0x00, 0x00, _TRANSFER_EVENT_SIGNATURE, ownerMasked, stashMasked, tokenId_)
}
// _afterTokenTransfers(msg.sender, _STASH_ADDRESS, tokenId_, 1);
// _afterTokenTransfers(_STASH_ADDRESS, msg.sender, stashTokenId, 1);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/IAccessControl.sol)
pragma solidity ^0.8.20;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControl {
/**
* @dev The `account` is missing a role.
*/
error AccessControlUnauthorizedAccount(address account, bytes32 neededRole);
/**
* @dev The caller of a function is not the expected one.
*
* NOTE: Don't confuse with {AccessControlUnauthorizedAccount}.
*/
error AccessControlBadConfirmation();
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `callerConfirmation`.
*/
function renounceRole(bytes32 role, address callerConfirmation) external;
}
// contracts/strategies/IBECDnaStrategy.sol
// SPDX-License-Identifier: MIT
/**
/3333333 /33333333 /333333
| 33__ 33 | 33_____/ /33__ 33
| 33 \ 33 | 33 | 33 \__/
| 3333333 | 33333 | 33
| 33__ 33 | 33__/ | 33
| 33 \ 33 | 33 | 33 33
| 3333333/ | 33333333 | 333333/
|_______/ |________/ \______/
# https://blackeyedcreatures.com
*/
pragma solidity ^0.8.25;
/**
* @title Black Eyed Creatures DNA Strategy
* @notice The strategies are the contracts that mints and generates the metada for the BECs.
* This interface helps with the information the genesis engine needs for each creature type.
* @dev The genesis engine (through different strategies) creates DNA based in different logis,
* but always creating DNA that are compliant with the following order (8b each):
* 00: body_family
* 01: body_variation
* 02: head_family
* 03: head_variation
* 04: ears_family
* 05: ears_variation
* 06: face_family
* 07: face_variation
* 08: hair_family
* 09: hair_variation
* 10: eyes_family
* 11: eyes_variation
* 12: bg_color_family
* 13: bg_color_variation
* 14: skin_color_family
* 15: skin_color_variation
* 16: secondary_color_family
* 17: secondary_color_variation
* 18-30: unused
* 31: type: normal|golden|ghost|others
*
* @author https://42i.co
*/
interface IBECDnaStrategy {
/**
* @notice Retrieves the DNA of a creature based on its id.
* @param _creatureId The id of the creature.
* @return uint256 The DNA of the specified creature.
*/
function getDna(uint256 _creatureId) external view returns (uint256);
/**
* @notice Gets the metadata tokenURI for this creature.
* @param _creatureId The id of the creature.
* @return string memory The token uri.
*/
function tokenURI(uint256 _creatureId) external view returns (string memory);
/**
* @notice Identifies the strategy ID.
* @return uint256 The ID of the strategy.
*/
function getStrategyId() external view returns (uint256);
/**
* @notice Identifies the distribution ID associated with this strategy.
* @return uint256 The distribution ID.
*/
function getDistributionId() external view returns (uint256);
}
// contracts/IBECErrors.sol
// SPDX-License-Identifier: MIT
/**
/3333333 /33333333 /333333
| 33__ 33 | 33_____/ /33__ 33
| 33 \ 33 | 33 | 33 \__/
| 3333333 | 33333 | 33
| 33__ 33 | 33__/ | 33
| 33 \ 33 | 33 | 33 33
| 3333333/ | 33333333 | 333333/
|_______/ |________/ \______/
# https://blackeyedcreatures.com
*/
pragma solidity ^0.8.25;
/**
* @title IBECErrors
* @dev Interface for Black Eyed Creatures (BEC) custom errors.
* @author https://42i.co
*/
interface IBECErrors {
/**
* @dev Indicates that a minting operation attempted to mint zero creatures.
*/
error BECZeroQuantityMinting();
/**
* @dev Indicates that a minting operation has exceeded the cap limit for creatures.
*/
error BECCapLimitReached();
/**
* @dev Indicates that invalid ambassadors were provided.
* @param alpha ID of the first ambassador.
* @param beta ID of the second ambassador.
* @param gamma ID of the third ambassador.
*/
error BECInvalidAmbassadors(uint256 alpha, uint256 beta, uint256 gamma);
/**
* @dev Indicates that an incorrect number of ambassadors was provided.
* @param length The number of ambassadors provided.
*/
error BECInvalidAmbassadorsLength(uint256 length);
/**
* @dev Indicates that a creature is already set as a variation ambassador.
* @param creatureId The ID of the creature.
*/
error BECAlreadyVariationAmbassador(uint256 creatureId);
/**
* @dev Indicates that a creature is already set as a family ambassador.
* @param creatureId The ID of the creature.
*/
error BECAlreadyFamilyAmbassador(uint256 creatureId);
/**
* @dev Indicates that a creature is not a variation ambassador when required.
* @param creatureId The ID of the creature.
*/
error BECNotVariationAmbassador(uint256 creatureId);
/**
* @dev Indicates that an invalid phenotype was provided.
* @param phenotype The invalid phenotype identifier.
*/
error BECInvalidPhenotype(uint256 phenotype);
/**
* @dev Indicates that an invalid family was provided for a creature.
* @param creatureId The ID of the creature.
*/
error BECInvalidFamily(uint256 creatureId);
/**
* @dev Indicates that an invalid variation was provided for a creature.
* @param creatureId The ID of the creature.
*/
error BECInvalidVariation(uint256 creatureId);
/**
* @dev Indicates that an invalid name was provided, either empty or too long.
* @param name The invalid name submitted.
*/
error BECInvalidName(string name);
/**
* @dev Indicates that a duplicated name was provided.
* @param name The duplicate name submitted.
*/
error BECDuplicatedName(string name);
/**
* @dev Indicates that a variation name has already been set for a given phenotype, family, and variation.
* @param phenotype The phenotype identifier.
* @param family The family identifier.
* @param variation The variation identifier.
*/
error BECVariationNameAlreadySet(uint256 phenotype, uint256 family, uint256 variation);
/**
* @dev Indicates that a family name has already been set for a given phenotype and family.
* @param phenotype The phenotype identifier.
* @param family The family identifier.
*/
error BECFamilyNameAlreadySet(uint256 phenotype, uint256 family);
/**
* @dev Indicates that not all variations have been set for a given phenotype and family.
* @param phenotype The phenotype identifier.
* @param family The family identifier.
* @param variation The variation identifier that is missing.
*/
error BECNotAllVariationsSet(uint256 phenotype, uint256 family, uint256 variation);
/**
* @dev Indicates that not all required variations were present.
*/
error BECNotAllVariationsPresent();
/**
* @dev Indicates that a hunt has already finished.
*/
error BECHuntFinished();
/**
* @dev Indicates that the ritual price was not fully paid.
* @param price The expected price.
* @param paid The amount that was actually paid.
*/
error BECRitualPriceNotPaid(uint256 price, uint256 paid);
/**
* @dev Indicates that the maximum number of hunts allowed was exceeded.
* @param allowed The allowed number of hunts.
* @param required The required number of hunts attempted.
*/
error BECMaxHuntsAllowed(uint256 allowed, uint256 required);
/**
* @dev Indicates that the maximum number of reproductions allowed was exceeded.
* @param allowed The allowed number of reproductions.
* @param required The required number of reproductions attempted.
*/
error BECMaxReproductionsAllowed(uint256 allowed, uint256 required);
/**
* @dev Indicates that the maximum number of reproductions for a specific creature was exceeded.
* @param creatureId The ID of the creature.
* @param allowed The allowed number of reproductions for this creature.
* @param required The required number of reproductions attempted.
*/
error BECMaxCreatureReproductionsAllowed(uint256 creatureId, uint256 allowed, uint256 required);
/**
* @dev Indicates that the creature is not of type 0, which is required for the operation.
* @param creatureId The ID of the creature.
*/
error BECNotType0Creature(uint256 creatureId);
/**
* @dev Indicates that the creature is fruitless, meaning it cannot reproduce.
* @param creatureId The ID of the creature.
*/
error BECFruitlessCreature(uint256 creatureId);
/**
* @dev Indicates that a parent creature was attempted to be used as payment, which is not allowed.
* @param creatureId The ID of the creature.
*/
error BECParentCreatureAsPayment(uint256 creatureId);
/**
* @dev Indicates that an invalid owner was provided for a token.
* @param operator The address of the operator trying to act on the token.
* @param tokenId The token ID being acted upon.
*/
error BECInvalidOwner(address operator, uint256 tokenId);
/**
* @dev Indicates that there are insufficient funds for the requested operation.
* @param requested The amount of funds requested.
* @param available The amount of funds available.
*/
error BECInsufficientFunds(uint256 requested, uint256 available);
/**
* @dev Indicates that an invalid owner was provided for a Black Sphere.
* @param owner The incorrect owner address.
* @param blacksphere The Black Sphere ID involved.
*/
error BlackSphereInvalidOwner(address owner, uint256 blacksphere);
/**
* @dev Indicates that the function or feature called is not currently enabled.
*/
error BECNotEnabled();
/**
* @dev Indicates that the genesis store for a creature has already been set.
* @param creatureId The ID of the creature.
*/
error BECGenesisStoreAlreadySet(uint256 creatureId);
/**
* @dev Indicates that the distribution for a creature has already been set.
* @param distribution The distribution ID.
*/
error BECDistributionAlreadySet(uint256 distribution);
/**
* @dev Indicates that the wrong distribution ID was provided.
* @param distributionId The incorrect distribution ID provided.
*/
error BECWrongDistributionId(uint256 distributionId);
/**
* @dev Indicates that the domain for a creature has already been set.
* @param domain The domain ID.
*/
error BECDomainAlreadySet(uint256 domain);
/**
* @dev Indicates that an empty array was provided for golden data.
*/
error BECEmptyGoldenDataArray();
/**
* @dev Indicates that a non-empty golden deltas array was provided when expected to be empty.
*/
error BECNotEmptyGoldenDeltasArray();
/**
* @dev Indicates that more creatures were provided than there are phenotypes available.
*/
error BECMoreCreaturesThanPhenotypes();
/**
* @dev Indicates that an awakening operation has already been performed.
*/
error BECAlreadyAwaken();
/**
* @dev Indicates that IDs were not provided in order.
*/
error BECIdsNotInOrder();
/**
* @dev Indicates that an invalid address was provided.
* @param target The target address.
*/
error BECInvalidAddress(address target);
/**
* @dev Indicates that an invalid protocol bit was used.
*/
error BECInvalidProtocolBit();
/**
* @dev Indicates that an index was out of bounds.
*/
error BECIndexOutOfBounds();
}
// contracts/meta-extensions/IBECMetaExtension.sol
// SPDX-License-Identifier: MIT
/**
/3333333 /33333333 /333333
| 33__ 33 | 33_____/ /33__ 33
| 33 \ 33 | 33 | 33 \__/
| 3333333 | 33333 | 33
| 33__ 33 | 33__/ | 33
| 33 \ 33 | 33 | 33 33
| 3333333/ | 33333333 | 333333/
|_______/ |________/ \______/
# https://blackeyedcreatures.com
*/
pragma solidity ^0.8.25;
/**
* @title Black Eyed Creatures Meta Extension Interface
* @dev Defines the interface for meta extensions within the Black Eyed Creatures ecosystem.
* Meta extensions are modular contracts that provide additional token metadata for creatures, enhancing their attributes,
* appearance, or interactions within the ecosystem.
* @notice Implementations of this interface should ensure that extensions are applied in a manner that is consistent with
* the ecosystem's rules and enhances the user experience.
* @author https://42i.co
*/
interface IBECMetaExtension {
/**
* @notice Applies the meta extension to a creature and returns the additional metadata.
* @dev When implemented, this function should handle the logic for appending the creature's metadata based on the extension's purpose.
* @param _creatureId The unique identifier of the creature to which the extension is applied.
* @return string A string representing the modified or additional metadata for the creature.
*/
function applyExtension(uint256 _creatureId) external view returns (string memory);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @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);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
// contracts/erc404/IERC404B.sol
// SPDX-License-Identifier: MIT
/**
/3333333 /33333333 /333333
| 33__ 33 | 33_____/ /33__ 33
| 33 \ 33 | 33 | 33 \__/
| 3333333 | 33333 | 33
| 33__ 33 | 33__/ | 33
| 33 \ 33 | 33 | 33 33
| 3333333/ | 33333333 | 333333/
|_______/ |________/ \______/
# https://blackeyedcreatures.com
*/
pragma solidity ^0.8.25;
import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol";
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
/**
* @title IERC404B Interface
* @dev Interface for a hybrid token contract combining ERC20 and ERC721 functionalities with a unique "stash" feature.
* The stash is a holding area for tokens that are currently unowned but not burned, allowing for controlled management and re-distribution.
* Supports ERC165 for interface detection and ERC20Permit for token allowance via signatures.
*/
interface IERC404B is IERC165, IERC20Permit {
/// IERC20 + ERC721 Metadata Methods ///
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() external view returns (uint8);
/**
* @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
*/
function tokenURI(uint256 tokenId) external view returns (string memory);
/// ERC721 Methods ///
/**
* @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`.
*
* 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;
/**
* @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 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 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 the account approved for `tokenId` token.
*
* Requirements:
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @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);
/// ERC20 Methods ///
/**
* @dev Returns the amount of ERC20 tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Moves `amountOrId` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits {Transfer} events.
*/
function transfer(address to, uint256 amountOrId) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/// ERC404 Combined (Methods with similar interfaces and behavior in ERC20 & ERC721) ///
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256 balance);
/**
* @dev Moves `amountOrId` tokens from `from` to `to` using the
* allowance mechanism. `amountOrId` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* WARNING: In case of Id, note that the caller is responsible to confirm that the recipient is capable of
* receiving ERC721 or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though
* the caller must understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `amountOrId` amount should be less or equal than balance OR tokenId 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 {Transfer} events.
*/
function transferFrom(address from, address to, uint256 amountOrId) external returns (bool);
/**
* @dev Sets `amountOrId` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amountOrId) external returns (bool);
/// ERC404 Specific ///
/**
* @notice Retrieves the equivalence between 1 ERC721 token and ERC20 needed for that token.
* @dev This function returns the unit value used in conversions between ERC721 and ERC20 tokens.
* @return The unit value representing the equivalence between 1 ERC721 token and ERC20.
*/
function unit() external view returns (uint256);
/**
* @notice Checks if the specified token exists.
*
* A token is considered to exist if it has been minted using {_mint} and is not in the set of burned tokens.
*
* @param tokenId_ The ID of the token to check.
* @return True if the token exists, false otherwise.
*/
function exists(uint256 tokenId_) external view returns (bool);
/**
* @dev Checks if the specified address is exempt from ERC-721 transfers.
* This function retrieves the exemption status for ERC-721 transfers for the given address.
*
* @param target_ The address to check for ERC-721 transfer exemption.
* @return isExempt True if the address is exempt from ERC-721 transfers, false otherwise.
*/
function isERC721TransferExempt(address target_) external view returns (bool isExempt);
/**
* @dev Sets the exemption status for ERC-721 transfers for the caller.
*
* Emits:
* - {Transfer} event for each target_ ERC721 token from/to the stash.
*
* @param state_ The new exemption state to set (true for exempt, false for non-exempt).
*/
function setERC721TransferExempt(bool state_) external;
/**
* @dev Retrieves the IDs of ERC-721 tokens owned by a specific address.
*
* @param owner_ The address for which ERC-721 token IDs are being retrieved.
* @return ownedCreatureIds An array of uint256 representing the ERC-721 token IDs owned by the specified address.
*/
function owned(address owner_) external view returns (uint256[] memory);
/**
* @dev External function to get the current stash address.
*
* @return address Current stash address.
*/
function stashAddress() external view returns (address);
/**
* @dev External function to get the current length of the stash queue.
*
* @return uint256 Current length of the stash queue.
*/
function stashLength() external view returns (uint256);
/**
* @dev External function to retrieve all tokens currently in the stash queue.
*
* @return uint256[] An array containing all tokens currently in the stash queue.
*/
function tokensInStash() external view returns (uint256[] memory);
/**
* @dev Public function to exchange an ERC-721 token with a token in the stash.
*
* Requirements:
* - The caller must be the owner or have approval to transfer the tokenId_.
* - The stashTokenId_ must belong to the stash.
*
* Emits:
* - {Transfer} event for the token exchanged from the stash to the caller.
* - {Transfer} event for the token exchanged from the caller to the stash.
*
* @param tokenId_ The ID of the ERC-721 token to exchange.
* @param index_ The index of the token at the stash to exchange with.
*/
function exchangeWithStash(uint256 tokenId_, uint128 index_) external;
}
// contracts/erc404/IERC404BErrors.sol
// SPDX-License-Identifier: MIT
/**
/3333333 /33333333 /333333
| 33__ 33 | 33_____/ /33__ 33
| 33 \ 33 | 33 | 33 \__/
| 3333333 | 33333 | 33
| 33__ 33 | 33__/ | 33
| 33 \ 33 | 33 | 33 33
| 3333333/ | 33333333 | 333333/
|_______/ |________/ \______/
# https://blackeyedcreatures.com
*/
pragma solidity ^0.8.25;
/**
* @dev ERC404B3Errors interface defines custom error messages for the ERC404B3 contract.
*/
interface IERC404BErrors {
/**
* @dev Indicates that the specified token is not found in the stash.
* @param stashTokenId The ID of the token in the stash.
*/
error ERC404TokenNotInStash(uint256 stashTokenId);
/**
* @dev Indicates that the index value provided is not valid in the stash queue.
* @param indexInQueue The index value in the queue.
* @param stashTokenId The ID of the token in the stash.
*/
error ERC404NotValidIndexValueInStash(uint128 indexInQueue, uint256 stashTokenId);
/**
* @dev Indicates that the transfer value is invalid.
* @param value The invalid transfer value.
*/
error ERC404InvalidTransferValue(uint256 value);
/**
* @dev Indicates that the target address is not eligible for ERC721 exemption.
* @param target The address that is not eligible for exemption.
*/
error ERC404InvalidERC721Exemption(address target);
/**
* @dev Indicates an overflow in the owned index.
* @param index The index value causing the overflow.
*/
error ERC404OwnedIndexOverflow(uint256 index);
/**
* @dev Indicates an invalid mint quantity for ERC721.
*/
error ERC721InvalidMintQuantity();
/**
* @dev Indicates that the recipient address has not implemented ERC721Receiver.
* @param to The recipient address.
* @param tokenId The ID of the token being transferred.
* @param quantity The quantity of tokens being transferred.
* @param data Additional data for the transfer.
*/
error ERC721ReceiverNotImplemented(address to, uint256 tokenId, uint256 quantity, bytes data);
/**
* @dev Indicates that the permit deadline has expired for EIP-2612 permit.
* @param owner The owner of the tokens.
* @param spender The spender of the tokens.
* @param value The token value.
* @param deadline The expired deadline.
* @param v Signature parameter v.
* @param r Signature parameter r.
* @param s Signature parameter s.
*/
error EIP2612PermitDeadlineExpired(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s);
/**
* @dev Indicates that the signer of the permit is invalid for EIP-2612 permit.
* @param recoveredAddress The recovered signer address.
* @param owner The owner of the tokens.
* @param spender The spender of the tokens.
* @param value The token value.
* @param deadline The permit deadline.
* @param v Signature parameter v.
* @param r Signature parameter r.
* @param s Signature parameter s.
*/
error EIP2612InvalidSigner(address recoveredAddress, address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../../utils/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`.
*
* 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;
/**
* @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 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: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* 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 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 address zero.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @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);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/IERC721Metadata.sol)
pragma solidity ^0.8.20;
import {IERC721} from "../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);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721Receiver.sol)
pragma solidity ^0.8.20;
/**
* @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 `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
/**
* @dev Muldiv operation overflow.
*/
error MathOverflowedMulDiv();
enum Rounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with an overflow flag.
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds towards infinity instead
* of rounding towards zero.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
if (b == 0) {
// Guarantee the same behavior as in a regular Solidity division.
return a / b;
}
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
* denominator == 0.
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
* Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0 = x * y; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
if (denominator <= prod1) {
revert MathOverflowedMulDiv();
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator.
// Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
uint256 twos = denominator & (0 - denominator);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
// works in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
* towards zero.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256 of a positive value rounded towards zero.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
}
}
/**
* @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
*/
function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
return uint8(rounding) % 2 == 1;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
/**
_____ ___ ___ __ ____ _ __
/ ___/____ / (_)___/ (_) /___ __ / __ )(_) /______
\__ \/ __ \/ / / __ / / __/ / / / / __ / / __/ ___/
___/ / /_/ / / / /_/ / / /_/ /_/ / / /_/ / / /_(__ )
/____/\____/_/_/\__,_/_/\__/\__, / /_____/_/\__/____/
/____/
- npm: https://www.npmjs.com/package/solidity-bits
- github: https://github.com/estarriolvetch/solidity-bits
*/
pragma solidity ^0.8.0;
library Popcount {
uint256 private constant m1 = 0x5555555555555555555555555555555555555555555555555555555555555555;
uint256 private constant m2 = 0x3333333333333333333333333333333333333333333333333333333333333333;
uint256 private constant m4 = 0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f;
uint256 private constant h01 = 0x0101010101010101010101010101010101010101010101010101010101010101;
function popcount256A(uint256 x) internal pure returns (uint256 count) {
unchecked{
for (count=0; x!=0; count++)
x &= x - 1;
}
}
function popcount256B(uint256 x) internal pure returns (uint256) {
if (x == type(uint256).max) {
return 256;
}
unchecked {
x -= (x >> 1) & m1; //put count of each 2 bits into those 2 bits
x = (x & m2) + ((x >> 2) & m2); //put count of each 4 bits into those 4 bits
x = (x + (x >> 4)) & m4; //put count of each 8 bits into those 8 bits
x = (x * h01) >> 248; //returns left 8 bits of x + (x<<8) + (x<<16) + (x<<24) + ...
}
return x;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard signed math utilities missing in the Solidity language.
*/
library SignedMath {
/**
* @dev Returns the largest of two signed numbers.
*/
function max(int256 a, int256 b) internal pure returns (int256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two signed numbers.
*/
function min(int256 a, int256 b) internal pure returns (int256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/
function average(int256 a, int256 b) internal pure returns (int256) {
// Formula from the book "Hacker's Delight"
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// must be unchecked in order to support `n = type(int256).min`
return uint256(n >= 0 ? n : -n);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol)
pragma solidity ^0.8.20;
import {Math} from "./math/Math.sol";
import {SignedMath} from "./math/SignedMath.sol";
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant HEX_DIGITS = "0123456789abcdef";
uint8 private constant ADDRESS_LENGTH = 20;
/**
* @dev The `value` string doesn't fit in the specified `length`.
*/
error StringsInsufficientHexLength(uint256 value, uint256 length);
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toStringSigned(int256 value) internal pure returns (string memory) {
return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
uint256 localValue = value;
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = HEX_DIGITS[localValue & 0xf];
localValue >>= 4;
}
if (localValue != 0) {
revert StringsInsufficientHexLength(value, length);
}
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
* representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard ERC20 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens.
*/
interface IERC20Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC20InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC20InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
* @param spender Address that may be allowed to operate on tokens without being their owner.
* @param allowance Amount of tokens a `spender` is allowed to operate with.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC20InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `spender` to be approved. Used in approvals.
* @param spender Address that may be allowed to operate on tokens without being their owner.
*/
error ERC20InvalidSpender(address spender);
}
/**
* @dev Standard ERC721 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens.
*/
interface IERC721Errors {
/**
* @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20.
* Used in balance queries.
* @param owner Address of the current owner of a token.
*/
error ERC721InvalidOwner(address owner);
/**
* @dev Indicates a `tokenId` whose `owner` is the zero address.
* @param tokenId Identifier number of a token.
*/
error ERC721NonexistentToken(uint256 tokenId);
/**
* @dev Indicates an error related to the ownership over a particular token. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param tokenId Identifier number of a token.
* @param owner Address of the current owner of a token.
*/
error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC721InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC721InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param tokenId Identifier number of a token.
*/
error ERC721InsufficientApproval(address operator, uint256 tokenId);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC721InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC721InvalidOperator(address operator);
}
/**
* @dev Standard ERC1155 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens.
*/
interface IERC1155Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
* @param tokenId Identifier number of a token.
*/
error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC1155InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC1155InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param owner Address of the current owner of a token.
*/
error ERC1155MissingApprovalForAll(address operator, address owner);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC1155InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC1155InvalidOperator(address operator);
/**
* @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
* Used in batch transfers.
* @param idsLength Length of the array of token identifiers
* @param valuesLength Length of the array of token amounts
*/
error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}
{
"compilationTarget": {
"contracts/BECCore.sol": "BECCore"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs",
"useLiteralContent": true
},
"optimizer": {
"details": {
"constantOptimizer": true,
"cse": true,
"deduplicate": true,
"inliner": true,
"jumpdestRemover": true,
"orderLiterals": true,
"peephole": true,
"simpleCounterForLoopUncheckedIncrement": true,
"yul": true,
"yulDetails": {
"optimizerSteps": "u:fDnTOcmu",
"stackAllocation": true
}
},
"runs": 200
},
"remappings": [],
"viaIR": true
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AccessControlBadConfirmation","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"bytes32","name":"neededRole","type":"bytes32"}],"name":"AccessControlUnauthorizedAccount","type":"error"},{"inputs":[],"name":"BECAlreadyAwaken","type":"error"},{"inputs":[{"internalType":"uint256","name":"creatureId","type":"uint256"}],"name":"BECAlreadyFamilyAmbassador","type":"error"},{"inputs":[{"internalType":"uint256","name":"creatureId","type":"uint256"}],"name":"BECAlreadyVariationAmbassador","type":"error"},{"inputs":[],"name":"BECCapLimitReached","type":"error"},{"inputs":[{"internalType":"uint256","name":"distribution","type":"uint256"}],"name":"BECDistributionAlreadySet","type":"error"},{"inputs":[{"internalType":"uint256","name":"domain","type":"uint256"}],"name":"BECDomainAlreadySet","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"BECDuplicatedName","type":"error"},{"inputs":[],"name":"BECEmptyGoldenDataArray","type":"error"},{"inputs":[{"internalType":"uint256","name":"phenotype","type":"uint256"},{"internalType":"uint256","name":"family","type":"uint256"}],"name":"BECFamilyNameAlreadySet","type":"error"},{"inputs":[{"internalType":"uint256","name":"creatureId","type":"uint256"}],"name":"BECFruitlessCreature","type":"error"},{"inputs":[{"internalType":"uint256","name":"creatureId","type":"uint256"}],"name":"BECGenesisStoreAlreadySet","type":"error"},{"inputs":[],"name":"BECHuntFinished","type":"error"},{"inputs":[],"name":"BECIdsNotInOrder","type":"error"},{"inputs":[],"name":"BECIndexOutOfBounds","type":"error"},{"inputs":[{"internalType":"uint256","name":"requested","type":"uint256"},{"internalType":"uint256","name":"available","type":"uint256"}],"name":"BECInsufficientFunds","type":"error"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"BECInvalidAddress","type":"error"},{"inputs":[{"internalType":"uint256","name":"alpha","type":"uint256"},{"internalType":"uint256","name":"beta","type":"uint256"},{"internalType":"uint256","name":"gamma","type":"uint256"}],"name":"BECInvalidAmbassadors","type":"error"},{"inputs":[{"internalType":"uint256","name":"length","type":"uint256"}],"name":"BECInvalidAmbassadorsLength","type":"error"},{"inputs":[{"internalType":"uint256","name":"creatureId","type":"uint256"}],"name":"BECInvalidFamily","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"BECInvalidName","type":"error"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"BECInvalidOwner","type":"error"},{"inputs":[{"internalType":"uint256","name":"phenotype","type":"uint256"}],"name":"BECInvalidPhenotype","type":"error"},{"inputs":[],"name":"BECInvalidProtocolBit","type":"error"},{"inputs":[{"internalType":"uint256","name":"creatureId","type":"uint256"}],"name":"BECInvalidVariation","type":"error"},{"inputs":[{"internalType":"uint256","name":"creatureId","type":"uint256"},{"internalType":"uint256","name":"allowed","type":"uint256"},{"internalType":"uint256","name":"required","type":"uint256"}],"name":"BECMaxCreatureReproductionsAllowed","type":"error"},{"inputs":[{"internalType":"uint256","name":"allowed","type":"uint256"},{"internalType":"uint256","name":"required","type":"uint256"}],"name":"BECMaxHuntsAllowed","type":"error"},{"inputs":[{"internalType":"uint256","name":"allowed","type":"uint256"},{"internalType":"uint256","name":"required","type":"uint256"}],"name":"BECMaxReproductionsAllowed","type":"error"},{"inputs":[],"name":"BECMoreCreaturesThanPhenotypes","type":"error"},{"inputs":[],"name":"BECNotAllVariationsPresent","type":"error"},{"inputs":[{"internalType":"uint256","name":"phenotype","type":"uint256"},{"internalType":"uint256","name":"family","type":"uint256"},{"internalType":"uint256","name":"variation","type":"uint256"}],"name":"BECNotAllVariationsSet","type":"error"},{"inputs":[],"name":"BECNotEmptyGoldenDeltasArray","type":"error"},{"inputs":[],"name":"BECNotEnabled","type":"error"},{"inputs":[{"internalType":"uint256","name":"creatureId","type":"uint256"}],"name":"BECNotType0Creature","type":"error"},{"inputs":[{"internalType":"uint256","name":"creatureId","type":"uint256"}],"name":"BECNotVariationAmbassador","type":"error"},{"inputs":[{"internalType":"uint256","name":"creatureId","type":"uint256"}],"name":"BECParentCreatureAsPayment","type":"error"},{"inputs":[{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"uint256","name":"paid","type":"uint256"}],"name":"BECRitualPriceNotPaid","type":"error"},{"inputs":[{"internalType":"uint256","name":"phenotype","type":"uint256"},{"internalType":"uint256","name":"family","type":"uint256"},{"internalType":"uint256","name":"variation","type":"uint256"}],"name":"BECVariationNameAlreadySet","type":"error"},{"inputs":[{"internalType":"uint256","name":"distributionId","type":"uint256"}],"name":"BECWrongDistributionId","type":"error"},{"inputs":[],"name":"BECZeroQuantityMinting","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"blacksphere","type":"uint256"}],"name":"BlackSphereInvalidOwner","type":"error"},{"inputs":[],"name":"ECDSAInvalidSignature","type":"error"},{"inputs":[{"internalType":"uint256","name":"length","type":"uint256"}],"name":"ECDSAInvalidSignatureLength","type":"error"},{"inputs":[{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"ECDSAInvalidSignatureS","type":"error"},{"inputs":[{"internalType":"address","name":"recoveredAddress","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"EIP2612InvalidSigner","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"EIP2612PermitDeadlineExpired","type":"error"},{"inputs":[],"name":"EIP712BECSigningKeyNotSet","type":"error"},{"inputs":[{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"EIP712InvalidSignature","type":"error"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"allowance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"ERC20InsufficientAllowance","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"ERC20InsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"approver","type":"address"}],"name":"ERC20InvalidApprover","type":"error"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"}],"name":"ERC20InvalidReceiver","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"ERC20InvalidSender","type":"error"},{"inputs":[{"internalType":"address","name":"spender","type":"address"}],"name":"ERC20InvalidSpender","type":"error"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"ERC404InvalidERC721Exemption","type":"error"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"ERC404InvalidTransferValue","type":"error"},{"inputs":[{"internalType":"uint128","name":"indexInQueue","type":"uint128"},{"internalType":"uint256","name":"stashTokenId","type":"uint256"}],"name":"ERC404NotValidIndexValueInStash","type":"error"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"ERC404OwnedIndexOverflow","type":"error"},{"inputs":[{"internalType":"uint256","name":"stashTokenId","type":"uint256"}],"name":"ERC404TokenNotInStash","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"owner","type":"address"}],"name":"ERC721IncorrectOwner","type":"error"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721InsufficientApproval","type":"error"},{"inputs":[{"internalType":"address","name":"approver","type":"address"}],"name":"ERC721InvalidApprover","type":"error"},{"inputs":[],"name":"ERC721InvalidMintQuantity","type":"error"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"ERC721InvalidOperator","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"ERC721InvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"}],"name":"ERC721InvalidReceiver","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"ERC721InvalidSender","type":"error"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721NonexistentToken","type":"error"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"quantity","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"ERC721ReceiverNotImplemented","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"QueueEmpty","type":"error"},{"inputs":[],"name":"QueueFull","type":"error"},{"inputs":[],"name":"QueueOutOfBounds","type":"error"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"length","type":"uint256"}],"name":"StringsInsufficientHexLength","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Received","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EIP712_DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GENETICIST","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"IPFS_URI_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender_","type":"address"},{"internalType":"uint256","name":"value_","type":"uint256"}],"name":"_erc20Approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender_","type":"address"},{"internalType":"uint256","name":"id_","type":"uint256"}],"name":"_erc721Approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from_","type":"address"},{"internalType":"address","name":"to_","type":"address"},{"internalType":"uint256","name":"value_","type":"uint256"}],"name":"_transferFromERC20","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner_","type":"address"},{"internalType":"address","name":"spender_","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender_","type":"address"},{"internalType":"uint256","name":"valueOrId_","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account_","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_creatureId","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"burned","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId_","type":"uint256"},{"internalType":"uint128","name":"index_","type":"uint128"}],"name":"exchangeWithStash","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId_","type":"uint256"}],"name":"exists","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_target","type":"address"},{"internalType":"bool","name":"_state","type":"bool"}],"name":"forceERC721TransferExempt","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId_","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_creatureId","type":"uint256"}],"name":"getImageURI","outputs":[{"internalType":"string","name":"uri","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_creatureId","type":"uint256"}],"name":"getSeed","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_creatureIds","type":"uint256[]"}],"name":"getSeeds","outputs":[{"internalType":"bytes32[]","name":"","type":"bytes32[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getSiteBaseURI","outputs":[{"internalType":"string","name":"uri","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ipfsUris","outputs":[{"internalType":"string","name":"ipfsUri","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner_","type":"address"},{"internalType":"address","name":"operator_","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target_","type":"address"}],"name":"isERC721TransferExempt","outputs":[{"internalType":"bool","name":"isExempt","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_creatureOwner","type":"address"},{"internalType":"bytes32","name":"_seed","type":"bytes32"},{"internalType":"uint256","name":"_quantity","type":"uint256"}],"name":"mintWithSeed","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextTokenId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner_","type":"address"}],"name":"owned","outputs":[{"internalType":"uint256[]","name":"ownedCreatureIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId_","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"owner","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner_","type":"address"},{"internalType":"address","name":"spender_","type":"address"},{"internalType":"uint256","name":"value_","type":"uint256"},{"internalType":"uint256","name":"deadline_","type":"uint256"},{"internalType":"uint8","name":"v_","type":"uint8"},{"internalType":"bytes32","name":"r_","type":"bytes32"},{"internalType":"bytes32","name":"s_","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"callerConfirmation","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from_","type":"address"},{"internalType":"address","name":"to_","type":"address"},{"internalType":"uint256","name":"tokenId_","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from_","type":"address"},{"internalType":"address","name":"to_","type":"address"},{"internalType":"uint256","name":"tokenId_","type":"uint256"},{"internalType":"bytes","name":"data_","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_signingKey","type":"address"}],"name":"setAllowlistSigningAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator_","type":"address"},{"internalType":"bool","name":"approved_","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_uri","type":"string"}],"name":"setBaseURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"state_","type":"bool"}],"name":"setERC721TransferExempt","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_genesisEngineContractAddress","type":"address"}],"name":"setGenesisEngineContractAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_creatureId","type":"uint256"},{"internalType":"string","name":"_uri","type":"string"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"setIPFSTokenURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_uri","type":"string"}],"name":"setSiteBaseURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stashAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stashLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"_interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_creatureId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokensInStash","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalTokens","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to_","type":"address"},{"internalType":"uint256","name":"valueOrId_","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from_","type":"address"},{"internalType":"address","name":"to_","type":"address"},{"internalType":"uint256","name":"valueOrId_","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address payable","name":"_address","type":"address"}],"name":"withdraw","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_tokenAddress","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address payable","name":"_address","type":"address"}],"name":"withdrawERC20Token","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_tokenAddress","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"address","name":"_address","type":"address"}],"name":"withdrawERC721Token","outputs":[],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}]