FRAX Price: $0.88 (-13.53%)

Contract

0x6c21B74eAbF31001dF47a1009B1e6266a0A6C1bA

Overview

FRAX Balance | FXTL Balance

0 FRAX | 3,213 FXTL

FRAX Value

$0.00

Token Holdings

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Block
From
To
Bulk Self Valida...102584812024-09-26 5:14:33486 days ago1727327673IN
0x6c21B74e...6a0A6C1bA
0 FRAX0.000001550.00010025
Add Limited Ques...102351542024-09-25 16:16:59486 days ago1727281019IN
0x6c21B74e...6a0A6C1bA
0 FRAX0.000007610.01666691
Add Limited Ques...102351342024-09-25 16:16:19486 days ago1727280979IN
0x6c21B74e...6a0A6C1bA
0 FRAX0.000009710.03333358
Add Limited Ques...102351162024-09-25 16:15:43486 days ago1727280943IN
0x6c21B74e...6a0A6C1bA
0 FRAX0.000008650.00008358
Add Limited Ques...102350962024-09-25 16:15:03486 days ago1727280903IN
0x6c21B74e...6a0A6C1bA
0 FRAX0.000016830.10000025
Update Base Ques...102350542024-09-25 16:13:39486 days ago1727280819IN
0x6c21B74e...6a0A6C1bA
0 FRAX0.000011080.10000025
Manage Flox Cont...102350342024-09-25 16:12:59486 days ago1727280779IN
0x6c21B74e...6a0A6C1bA
0 FRAX0.000013240.10000025

View more zero value Internal Transactions in Advanced View mode

Advanced mode:

Cross-Chain Transactions
Loading...
Loading

Similar Match Source Code
This contract matches the deployed Bytecode of the Source Code for Contract 0x2CC779f1...62f5FA7e9
The constructor portion of the code might be different and could alter the actual behaviour of the contract

Contract Name:
FNSQuestValidator

Compiler Version
v0.8.23+commit.f704f362

Optimization Enabled:
Yes with 100000 runs

Other Settings:
paris EvmVersion

Contract Source Code (Solidity Standard Json-Input format)

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/**
 * ====================================================================
 * |     ______                   _______                             |
 * |    / _____________ __  __   / ____(_____  ____ _____  ________   |
 * |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
 * |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
 * | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
 * |                                                                  |
 * ====================================================================
 * ====================== FNSQuestValidator ===========================
 * ====================================================================
 * Frax Finance: https://github.com/FraxFinance
 */
import { QuestTracker } from "../QuestTracker.sol";
import { IQuestTrackerEnums } from "../IQuestTrackerEnums.sol";
import { IFNSQuestValidatorErrors } from "./IFNSQuestValidatorErrors.sol";
import { IFNSQuestValidatorEvents } from "./IFNSQuestValidatorEvents.sol";
import { QuestValidatorAccessControl } from "./QuestValidatorAccessControl.sol";
import { IFNS } from "./interfaces/IFNS.sol";

/**
 * @title FNSQuestValidator
 * @author Frax Finance
 * @notice The FNSQuestValidator contract is used to power the quest validation system for Quest track of the Flox
 *  ecosystem.
 */
contract FNSQuestValidator is IFNSQuestValidatorErrors, IFNSQuestValidatorEvents, QuestValidatorAccessControl {
    /// @notice QuestTracker smart contract
    QuestTracker public questTracker;
    /// @notice FNSNameWrapper smart contract
    IFNS public fns;

    /**
     * @notice Used to track the tokens that have already completed a quest.
     * @dev tokenId ID of the token
     * @dev alreadyUsed Whether the token has already been used to complete a quest
     */
    mapping(uint256 tokenId => bool alreadyUsed) public usedTokens;
    /**
     * @notice Used to track the limited quests associated with specific domain lengths.
     * @dev domainLength Length of the domain
     * @dev questId ID of the limited quest associated with the specified domain length
     */
    mapping(uint256 domainLength => uint256 questId) private _lengthToQuestId;

    /// @notice ID of the base quest.
    uint8 public baseQuestId;
    /// @notice Used to keep track of the longest domain length still associated with a dedicated limited quest.
    uint8 public longestLimitedQuestLength;

    /**
     * @notice Used to initialize the smart contract.
     * @param _questTracker Address of the QuestTracker smart contract
     * @param _fns Address of the FNSNameWrapper smart contract
     */
    constructor(address _questTracker, address _fns) {
        questTracker = QuestTracker(_questTracker);
        fns = IFNS(_fns);
    }

    /**
     * @notice Used to get the ID of the limited quest associated with the specified domain length.
     * @dev If the domain length is greater than the longestLimitedQuestLength, the longestLimitedQuestLength will be
     *  truncated to the longest supported limited quest domain length in order to return the correct quest ID.
     * @dev Passing a domain length lower than the shortest supported limited quest domain length will return the 0
     *  value.
     * @param domainLength Length of the domain
     * @return ID of the limited quest associated with the specified domain length
     */
    function lengthToQuestId(uint256 domainLength) public view returns (uint256) {
        domainLength = domainLength >= longestLimitedQuestLength ? longestLimitedQuestLength : domainLength;

        return _lengthToQuestId[domainLength];
    }

    /**
     * @notice Used to validate a quest for oneself.
     * @dev This function can only be called by the owner of the token.
     * @dev This function will check if the token is elegible to complete the base quest and the limited quest
     *  associated with the length of the token's domain.
     * @dev Attempting to use the same token to complete the quest multiple times will result in a reverted call.
     * @param tokenId ID of the token used to complete the quest
     */
    function selfValidateQuest(uint256 tokenId) external {
        _validateQuest(tokenId, msg.sender);
    }

    /**
     * @notice Used to validate multiple quests for oneself.
     * @dev This function can only be called by the owner of the tokens.
     * @dev This function will check if the tokens are elegible to complete the base quest and the limited quest
     *  associated with the length of the tokens' domains.
     * @dev Attempting to use the same token to complete the quest multiple times will result in a reverted call.
     * @param tokenIds Array of token IDs used to complete the quests
     */
    function bulkSelfValidateQuests(uint256[] memory tokenIds) external {
        for (uint256 i; i < tokenIds.length; ) {
            _validateQuest(tokenIds[i], msg.sender);

            unchecked {
                ++i;
            }
        }
    }

    /**
     * @notice Used to validate a quest for a user.
     * @dev This function can only be called by a Flox contributor.
     * @dev This function will check if the token is elegible to complete the base quest and the limited quest
     *  associated with the length of the token's domain.
     * @dev Attempting to use the same token to complete the quest multiple times will result in a reverted call.
     * @dev If a token ID is used to validate a quest for a user, the user has to own it.
     * @param tokenId ID of the token used to complete the quest
     * @param user Address of the user to validate the quest for
     */
    function validateQuestForUser(uint256 tokenId, address user) external {
        _onlyFloxContributor();

        _validateQuest(tokenId, user);
    }

    /**
     * @notice Used to validate multiple quests for a user.
     * @dev This function can only be called by a Flox contributor.
     * @dev This function will check if the tokens are elegible to complete the base quest and the limited quest
     *  associated with the length of the tokens' domains.
     * @dev Attempting to use the same token to complete the quest multiple times will result in a reverted call.
     * @dev If a token ID is used to validate a quest for a user, the user has to own it.
     * @param tokenIds ID of the tokens used to complete the quests
     * @param user Address of the user to validate the quests for
     */
    function validateQuestsForSingleUser(uint256[] memory tokenIds, address user) external {
        _onlyFloxContributor();

        for (uint256 i; i < tokenIds.length; ) {
            _validateQuest(tokenIds[i], user);

            unchecked {
                ++i;
            }
        }
    }

    /**
     * @notice Used to validate multiple quests for multiple users.
     * @dev This function can only be called by a Flox contributor.
     * @dev This function will check if the tokens are elegible to complete the base quest and the limited quest
     *  associated with the length of the tokens' domains.
     * @dev Attempting to use the same token to complete the quest multiple times will result in a reverted call.
     * @dev This function will revert if the arrays are of different lengths.
     * @dev Token IDs correspond to the users in the same index will be used to validate the quests.
     * @dev If a token ID is used to validate a quest for a user, the user has to own it.
     * @param tokenIds Array of token IDs used to complete the quests
     * @param users Array of addresses of the users to validate the quests for
     */
    function validateQuestsForMulitpleUsers(uint256[] memory tokenIds, address[] memory users) external {
        _onlyFloxContributor();

        for (uint256 i; i < tokenIds.length; ) {
            _validateQuest(tokenIds[i], users[i]);

            unchecked {
                ++i;
            }
        }
    }

    /**
     * @notice Used to add a limited quest associated with a specific domain length.
     * @dev This function can only be called by a Flox contributor.
     * @param domainLength Length of the domain associated with the limited quest
     * @param questId ID of the limited quest
     */
    function addLimitedQuest(uint256 domainLength, uint256 questId) external {
        _onlyFloxContributor();

        _lengthToQuestId[domainLength] = questId;

        if (domainLength > longestLimitedQuestLength) {
            longestLimitedQuestLength = uint8(domainLength);
        }

        emit LimitedQuestUpdate(domainLength, questId);
    }

    /**
     * @notice Used to update the base quest ID.
     * @dev This function can only be called by a Flox contributor.
     * @param _baseQuestId ID of the base quest
     */
    function updateBaseQuestId(uint8 _baseQuestId) external {
        _onlyFloxContributor();

        baseQuestId = _baseQuestId;

        emit BaseQuestUpdate(_baseQuestId);
    }

    /**
     * @notice Used to validate an FNS quest.
     * @dev This function will check if the token is elegible to complete the base quest and the limited quest
     *  associated with the length of the token's domain.
     * @dev Attempting to use the same token to complete the quest multiple times will result in a reverted call.
     * @dev Attempting to validate a quest using a token not owned by the user will result in a reverted call.
     * @dev If the user has already completed the quests, the tokens will be marked as used and the function will
     *  proceed without any further action.
     * @param tokenId ID of the token used to complete the quest
     * @param user Address of the user to validate the quest for
     */
    function _validateQuest(uint256 tokenId, address user) internal {
        if (usedTokens[tokenId]) revert TokenAlreadyUsed();
        if (fns.ownerOf(tokenId) != user) revert InvalidTokenOwner();

        bool baseQuestElegibleForCompletion;
        bool limitedQuestElegibleForCompletion;

        bytes memory domainName = _getTokenDomainName(tokenId);
        uint256 domainLength = _getDecodedStringLength(domainName);

        baseQuestElegibleForCompletion = _isQuestElegibleForCompletion(user, uint256(baseQuestId), false);

        domainLength = domainLength >= longestLimitedQuestLength ? longestLimitedQuestLength : domainLength;
        limitedQuestElegibleForCompletion = _isQuestElegibleForCompletion(user, lengthToQuestId(domainLength), true);

        usedTokens[tokenId] = true;

        if (baseQuestElegibleForCompletion) {
            questTracker.updateUserQuestStatus(
                uint256(baseQuestId),
                user,
                IQuestTrackerEnums.UserQuestStatus.PendingAllocation
            );
        }
        if (limitedQuestElegibleForCompletion) {
            questTracker.updateUserQuestStatus(
                lengthToQuestId(domainLength),
                user,
                IQuestTrackerEnums.UserQuestStatus.PendingAllocation
            );
        }
    }

    /**
     * @notice Used to check if a quest is elegible for completion.
     * @param user Address of the user to check for the quest completion elegibility
     * @param questId ID of the quest to check for completion elegibility
     * @param isLimitedQuest Signifies whether the quest is a limited quest or a base quest
     * @return Whether the quest is elegible for completion
     */
    function _isQuestElegibleForCompletion(
        address user,
        uint256 questId,
        bool isLimitedQuest
    ) internal view returns (bool) {
        if (isLimitedQuest) {
            return (questTracker.userQuestStatus(user, questId) == IQuestTrackerEnums.UserQuestStatus.Incomplete &&
                questTracker.remainingLimitedQuestCompletions(questId) > 0);
        } else {
            return questTracker.userQuestStatus(user, questId) == IQuestTrackerEnums.UserQuestStatus.Incomplete;
        }
    }

    /**
     * @notice Used to get the domain name of a token.
     * @param tokenId ID ot the token
     * @return Domain name of the token encoded in bytes
     */
    function _getTokenDomainName(uint256 tokenId) internal view returns (bytes memory) {
        bytes32 hexTokenId = bytes32(tokenId);

        return fns.names(hexTokenId);
    }

    /**
     * @notice Used to process the encoded domain name and get the length of the domain.
     * @dev Since the length of the domain is encoded in the first byte of the domain name, this function will retrieve
     *  it and convert it's hex encoding to an uint256.
     * @param data Data to decode
     * @return Length of the decoded domain name
     */
    function _getDecodedStringLength(bytes memory data) internal pure returns (uint256) {
        uint256 length = uint8(data[0]);

        return length;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/**
 * ====================================================================
 * |     ______                   _______                             |
 * |    / _____________ __  __   / ____(_____  ____ _____  ________   |
 * |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
 * |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
 * | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
 * |                                                                  |
 * ====================================================================
 * ========================= QuestTracker =============================
 * ====================================================================
 * Frax Finance: https://github.com/FraxFinance
 */
import { QuestTrackerAccessControl } from "./QuestTrackerAccessControl.sol";

/**
 * @title QuestTracker
 * @author Frax Finance
 * @notice The QuestTracker contract is used to track quests and their progress for users.
 */
contract QuestTracker is QuestTrackerAccessControl {
    /**
     * @notice This struct represents a quest.
     */
    struct Quest {
        uint256 reward; // The amount of FXTL points awarded for completing the quest - 32 bytes: slot 1
        uint256 startBlock; // The start block of the quest - marks the first block that compleion conditions apply - 32 bytes: slot 2
        uint256 endBlock; // The end block of the quest - marks the last block that completion conditions apply - 32 bytes : slot 3
        string title; // The title of the quest - own slot: slot 4
        string description; // The URI to the quest description - own slot: slot 5
        uint64 experiencePoints; // The amount of experience points earned for completing the quest - 8 bytes: slot 6 (24 bytes remaining)
        QuestStatus status; // The status of the quest (Pending, Upcoming, Active, Expired) - 1 byte: slot 6 (23 bytes remaining)
        QuestType questType; // The type of the quest (Single, Recursive, Limited) - 1 byte: slot 6 (22 bytes remaining)
        uint128 maxCompletions; // The maximum number of users that can complete the quest - 16 bytes: slot 6 (6 bytes remaining)
        uint64 recursiveCooldown; // The cooldown period between completions of the recursive quest - 8 bytes: slot 7 (24 bytes remaining)
        bool selfServeCompletion; // Whether the quest can be completed by the user or not. If true, it needs QuestValidator - 1 byte: slot 7 (23 bytes remaining)
        address questValidator; // The address of the contract that validates the quest completion - 20 bytes: slot 7 (3 bytes remaining)
        uint256[50] __gap; // Reserved storage gap to prevent storage collisions
    }

    /**
     * @notice This struct represents the status of a user's progress on a quest.
     */
    struct UserData {
        uint256 numberOfCompletedQuests; // The number of quests completed by the user - 32 bytes: slot 1
        uint256 lifetimePoints; // The total amount of FXTL points earned by the user - 32 bytes: slot 2
        uint64 lifetimeExperienceEarned; // The total amount of experience points earned by the user - 8 bytes: slot 3 (24 bytes remaining)
        uint64 currentLevelExperiencePoints; // The amount of experience points earned since the last level up - 8 bytes: slot 3 (16 bytes remaining)
        uint64 currentLevel; // The current level of the user - 8 bytes: slot 3 (8 bytes remaining)
        uint256[50] __gap; // Reserved storage gap to prevent storage collisions
    }

    /**
     * @notice This struct represents the status of a user's progress on a recursive quest.
     */
    struct RecursiveQuestCompletions {
        uint128 totalCompletions; // The total number of times the user has completed the quest - 16 bytes: slot 1 (16 bytes remaining)
        uint128 unallocatedCompletions; // The number of completions that have not yet been allocated - 16 bytes: slot 1 (0 bytes remaining)
        uint128 lastCompletionTimestamp; // The timestamp of the last completion of the quest - 16 bytes: slot 2 (16 bytes remaining)
        uint256[50] __gap; // Reserved storage gap to prevent storage collisions
    }

    /**
     * @notice This mapping is used to keep track of the number of times a quest has been completed.
     * @dev questId ID of the quest
     * @dev timesCompleted Number of times the quest has been completed
     */
    mapping(uint256 questId => uint128 timesCompleted) public limitedQuestCompletions;
    /**
     * @notice This mapping is used to keep track of all of the quests.
     * @dev The quests are stored using their unique ID.
     * @dev questId ID of the quest
     * @dev quest The quest being stored
     */
    mapping(uint256 questId => Quest quest) private _quests;
    /**
     * @notice This mapping is used to keep track of users progress on recursive quests.
     * @dev The mapping is used to add the ability to implement a cooldown period between completions, so that the users
     *  can't spam recursive quests.
     * @dev user Address of the user
     * @dev questId ID of the quest
     * @dev questCompletions The struct containing the user's limited progress on the recursive quest
     */
    mapping(address user => mapping(uint256 questId => RecursiveQuestCompletions questCompletions))
        private _recursiveQuestCompletions;
    /**
     * @notice This mapping represents the status of a user's progress on a quest.
     * @dev Tracking the users progress through quest allows for allocation of awards upon completion of the quest.
     * @dev user Address of the user
     * @dev questId ID of the quest
     * @dev status Status of the user's progress on the quest
     */
    mapping(address user => mapping(uint256 questId => UserQuestStatus status)) public userQuestStatus;
    /**
     * @notice This mapping is used to keep track of the users' quest progress.
     * @dev user Address of the user
     * @dev data The UserData struct containing the user's quest progress
     */
    mapping(address user => UserData userData) private _userQuestProgress;
    /**
     * @notice This mapping is used to keep track of the quests completed by the user.
     * @dev This is an unordered list and serves the purpose of tracking the user's progress for off-chain processing.
     * @dev user Address of the user
     * @dev completedQuestId ID of the quest completed by the user
     */
    mapping(address user => mapping(uint256 questIndex => uint256 completedQuestId)) public completedQuestIds;

    /// ID of the next quest to be added as well as the total number of quests.
    uint256 public nextQuestId;
    /// Flag to determine if the contract has been initialized.
    bool public initialized;
    /// Base amount of experience points required to level up from level 1 to level 2.
    uint64 public baseExperiencePoints;
    /// Increment of experience points required to level up.
    uint64 public experiencePointsIncrement;

    /**
     * @notice Initialize the QuestTracker and set the owner.
     * @param owner Address of the owner of the contract
     */
    function initialize(address owner) public override {
        if (initialized) revert AlreadyInitialized();

        baseExperiencePoints = 100;
        experiencePointsIncrement = 50;

        super.initialize(owner);
        initialized = true;
    }

    /**
     * @notice Used to retrieve the number of times the quest can still be completed.
     * @param questId ID of the quest to check for the remaining compleations
     * @return remainingCompletions The number of times the quest can still be completed
     */
    function remainingLimitedQuestCompletions(uint256 questId) public view returns (uint256 remainingCompletions) {
        if (_quests[questId].questType == QuestType.Limited) {
            remainingCompletions = _quests[questId].maxCompletions - limitedQuestCompletions[questId];
        } else {
            remainingCompletions = 2 ** 256 - 1;
        }
    }

    /**
     * @notice Used to retrieve the desired quest.
     * @param questId ID of the quest to retrieve
     * @return quest The quest being retrieved
     */
    function quests(uint256 questId) public view returns (Quest memory quest) {
        return _quests[questId];
    }

    /**
     * @notice Used to retrieve the progress statistics of a user.
     * @param user Address of the user to retrieve the progress statistics for
     * @return userData The UserData struct containing the user's quests progress
     */
    function userQuestProgress(address user) public view returns (UserData memory userData) {
        return _userQuestProgress[user];
    }

    /**
     * @notice Used to retrieve the information about the user's progress on a specific recursive quest.
     * @dev If the quest ID used is for a non-recursive quest, the function will return a struct with all fields set to 0.
     * @param user Address of the user to retrieve the progress statistics for
     * @param questId ID of the quest to retrieve the progress statistics for
     * @return questCompletions The RecursiveQuestCompletions struct containing the user's progress on the recursive quest
     */
    function recursiveQuestCompletions(
        address user,
        uint256 questId
    ) public view returns (RecursiveQuestCompletions memory questCompletions) {
        return _recursiveQuestCompletions[user][questId];
    }

    function getUserLevel(address user) public view returns (uint64) {
        if (_userQuestProgress[user].currentLevel == 0) {
            return 1;
        } else {
            return _userQuestProgress[user].currentLevel;
        }
    }

    function getRequiredExperienceForNextLevel(uint64 currentUserLevel) public view returns (uint64) {
        if (currentUserLevel == 0) revert InvalidLevel();

        uint64 expereincePointsRequired = baseExperiencePoints + ((currentUserLevel - 1) * experiencePointsIncrement);

        return expereincePointsRequired;
    }

    /**
     * @notice Used to add a new quest to the tracker.
     * @dev This can only be called by the Flox contributors.
     * @param reward Amount of FXTL received upon completion of the quest
     * @param startBlock Block number of the block at which the quest starts
     * @param endBlock Block number of the block at which the quest ends
     * @param title The title of the quest
     * @param description The URI to the quest description
     * @param experiencePoints Amount of experience points earned for completing the quest
     * @param questType The type of the quest (Single, Recursive, Limited), defaults to Single
     * @param maxCompletions The maximum number of users that can complete the quest
     * @param recursiveCooldown The cooldown period between completions of the recursive quest
     * @param selfServeCompletion Whether the quest can be completed by the user or not. If true, it needs QuestValidator
     * @param questValidator The address of the contract that validates the quest completion
     */
    function addQuest(
        uint256 reward,
        uint256 startBlock,
        uint256 endBlock,
        string memory title,
        string memory description,
        uint64 experiencePoints,
        QuestType questType,
        uint128 maxCompletions,
        uint64 recursiveCooldown,
        bool selfServeCompletion,
        address questValidator
    ) external {
        _onlyFloxContributor();

        if (startBlock > endBlock) revert InvalidBlockRange();

        QuestStatus status;

        if (block.number < startBlock) {
            status = QuestStatus.Upcoming;
        } else if (block.number < endBlock) {
            status = QuestStatus.Active;
        } else {
            status = QuestStatus.Expired;
        }

        questType = questType == QuestType.Unset ? QuestType.Single : questType;
        maxCompletions = questType == QuestType.Limited ? maxCompletions : 0;
        recursiveCooldown = questType == QuestType.Recursive ? recursiveCooldown : 0;
        questValidator = selfServeCompletion ? questValidator : address(0);

        if (selfServeCompletion && questValidator == address(0)) revert InvalidQuestValidator();

        uint256[50] memory dummy;

        _quests[nextQuestId] = Quest({
            reward: reward,
            startBlock: startBlock,
            endBlock: endBlock,
            experiencePoints: experiencePoints,
            title: title,
            description: description,
            status: status,
            questType: questType,
            maxCompletions: maxCompletions,
            recursiveCooldown: recursiveCooldown,
            selfServeCompletion: selfServeCompletion,
            questValidator: questValidator,
            __gap: dummy
        });
        nextQuestId++;

        emit QuestAdded(nextQuestId - 1, reward, startBlock, endBlock);
        emit QuestValidatorUpdated(nextQuestId - 1, selfServeCompletion, questValidator);
    }

    /**
     * @notice Used to update an existing quest.
     * @dev Passing an empty value for any of the parameters will result in the value not being updated.
     * @dev This can only be called by the Flox contributors.
     * @param questId ID of the quest to update
     * @param updatedReward New amount of FXTL received upon completion of the quest
     * @param updatedStartBlock New block number at which the quest starts
     * @param updatedEndBlock New block number at which the quest ends
     * @param updatedTitle New title of the quest
     * @param updatedDescription New URI to the quest description
     * @param updatedExperiencePoints New amount of experience points earned for completing the quest
     * @param updatedQuestType New type of the quest (Single, Recursive, Limited)
     * @param updatedMaxCompletions New maximum number of users that can complete the quest
     */
    function updateQuest(
        uint256 questId,
        uint256 updatedReward,
        uint256 updatedStartBlock,
        uint256 updatedEndBlock,
        string memory updatedTitle,
        string memory updatedDescription,
        uint64 updatedExperiencePoints,
        QuestType updatedQuestType,
        uint128 updatedMaxCompletions,
        uint64 updatedRecursiveCooldown
    ) external {
        _onlyFloxContributor();

        if (questId >= nextQuestId) revert QuestDoesNotExist();

        Quest memory quest = _quests[questId];

        updatedReward = updatedReward == 0 ? quest.reward : updatedReward;
        updatedStartBlock = updatedStartBlock == 0 ? quest.startBlock : updatedStartBlock;
        updatedEndBlock = updatedEndBlock == 0 ? quest.endBlock : updatedEndBlock;
        updatedExperiencePoints = updatedExperiencePoints == 0 ? quest.experiencePoints : updatedExperiencePoints;
        updatedTitle = bytes(updatedTitle).length == 0 ? quest.title : updatedTitle;
        updatedDescription = bytes(updatedDescription).length == 0 ? quest.description : updatedDescription;
        updatedQuestType = updatedQuestType == QuestType.Unset ? quest.questType : updatedQuestType;

        if (updatedQuestType != QuestType.Limited) {
            updatedMaxCompletions = 0;
        } else if (updatedMaxCompletions == uint128(0)) {
            updatedMaxCompletions = quest.maxCompletions;
        }

        if (updatedQuestType != QuestType.Recursive) {
            updatedRecursiveCooldown = 0;
        } else if (updatedRecursiveCooldown == uint64(0)) {
            updatedRecursiveCooldown = quest.recursiveCooldown;
        }

        if (updatedStartBlock > updatedEndBlock) revert InvalidBlockRange();

        QuestStatus updatedStatus;

        if (block.number < updatedStartBlock) {
            updatedStatus = QuestStatus.Upcoming;
        } else if (block.number < updatedEndBlock) {
            updatedStatus = QuestStatus.Active;
        } else {
            updatedStatus = QuestStatus.Expired;
        }

        uint256[50] memory dummy;

        _quests[questId] = Quest({
            reward: updatedReward,
            startBlock: updatedStartBlock,
            endBlock: updatedEndBlock,
            experiencePoints: updatedExperiencePoints,
            title: updatedTitle,
            description: updatedDescription,
            status: updatedStatus,
            questType: updatedQuestType,
            maxCompletions: updatedMaxCompletions,
            recursiveCooldown: updatedRecursiveCooldown,
            selfServeCompletion: quest.selfServeCompletion,
            questValidator: quest.questValidator,
            __gap: dummy
        });

        emit QuestUpdated(questId, updatedReward, updatedStartBlock, updatedEndBlock, updatedStatus);
    }

    /**
     * @notice Used to update the quest validator settings for a quest.
     * @param questId ID of the quest to update the validator for
     * @param allowSelfServeCompletion Whether the quest can be completed by the user or not. If true, it needs
     *  QuestValidator
     * @param questValidator Address of the quest validator smart contract for the quest
     */
    function manageQuestValidator(uint256 questId, bool allowSelfServeCompletion, address questValidator) external {
        _onlyFloxContributor();

        if (questId >= nextQuestId) revert QuestDoesNotExist();

        Quest storage quest = _quests[questId];

        if (quest.selfServeCompletion == allowSelfServeCompletion && quest.questValidator == questValidator) {
            revert SameValidatorSettings();
        }
        if (allowSelfServeCompletion && questValidator == address(0)) revert InvalidQuestValidator();

        quest.selfServeCompletion = allowSelfServeCompletion;

        if (!allowSelfServeCompletion) {
            quest.questValidator = address(0);
        } else {
            quest.questValidator = questValidator;
        }

        emit QuestValidatorUpdated(questId, quest.selfServeCompletion, quest.questValidator);
    }

    /**
     * @notice Used to update the status of a user's quest.
     * @dev This can only be called by the contributors of the quest of Flox contributors.
     * @dev Setting a `Recursive` quest as `Allocated` will only mark one completed instance as allocated. To mark
     *  multiple instances as allocated, the `Allocated` status must be set multiple times.
     * @param questId ID of the quest being updated
     * @param user Address of the user receiving the quest status update
     * @param status Status of the user's quest progress after the update
     */
    function updateUserQuestStatus(uint256 questId, address user, UserQuestStatus status) external {
        _onlyContributor(questId);

        if (questId >= nextQuestId) revert QuestDoesNotExist();
        if (status <= userQuestStatus[user][questId] && _quests[questId].questType != QuestType.Recursive) {
            revert InvalidUserQuestStatusUpdate(userQuestStatus[user][questId], status);
        }

        _updateUserQuestProgress(questId, user, status);

        emit UserQuestStatusUpdated(user, questId, status);
    }

    /**
     * @notice Used to update the status of a user's quest progress for multiple quests and various progress statuses.
     * @dev Each status will be applied to the corresponding quest ID.
     * @dev This can only be called by the Flox contributors.
     * @dev Setting a `Recursive` quest as `Allocated` will only mark one completed instance as allocated. To mark
     *  multiple instances as allocated, the `Allocated` status must be set multiple times.
     * @param questIds IDs of the quests being updated
     * @param user Address of the user receiving the quest status updates
     * @param statuses Statuses of the user's quest progress after the update
     */
    function bulkUpdateSingleUserQuestStatuses(
        uint256[] memory questIds,
        address user,
        UserQuestStatus[] memory statuses
    ) external {
        _onlyFloxContributor();

        if (questIds.length != statuses.length) revert ArrayLengthMismatch();

        for (uint256 i; i < questIds.length; ) {
            if (questIds[i] >= nextQuestId) revert QuestDoesNotExist();
            if (statuses[i] <= userQuestStatus[user][questIds[i]]) {
                revert InvalidUserQuestStatusUpdate(userQuestStatus[user][questIds[i]], statuses[i]);
            }

            _updateUserQuestProgress(questIds[i], user, statuses[i]);

            emit UserQuestStatusUpdated(user, questIds[i], statuses[i]);

            unchecked {
                ++i;
            }
        }
    }

    /**
     * @notice Used to update the status of multiple users' progresses in a single quest.
     * @dev This can only be called by the contributors of the quest or Flox contributors.
     * @param questId ID of the quest being updated
     * @param users Addresses of the users receiving the quest status updates
     * @param status New status assigned to the users' progress on the quest
     */
    function bulkUpdateMultipleUsersQuestStatus(
        uint256 questId,
        address[] memory users,
        UserQuestStatus status
    ) external {
        _onlyContributor(questId);

        if (questId >= nextQuestId) revert QuestDoesNotExist();

        for (uint256 i; i < users.length; ) {
            if (status <= userQuestStatus[users[i]][questId]) {
                revert InvalidUserQuestStatusUpdate(userQuestStatus[users[i]][questId], status);
            }

            _updateUserQuestProgress(questId, users[i], status);

            emit UserQuestStatusUpdated(users[i], questId, status);

            unchecked {
                ++i;
            }
        }
    }

    /**
     * @notice Used to update the status of multiple users' progresses in multiple quests.
     * @dev The status at each index will be applied to the corresponding user and quest ID.
     * @dev This can only be called by the Flox contributors.
     * @dev Setting a `Recursive` quest as `Allocated` will only mark one completed instance as allocated. To mark
     *  multiple instances as allocated, the `Allocated` status must be set multiple times.
     * @param questIds IDs of the quests being updated
     * @param users Addresses of the users receiving the quest status updates
     * @param statuses Quest progress statuses of the users after the update
     */
    function bulkUpdateMultipleUsersQuestsStatuses(
        uint256[] memory questIds,
        address[] memory users,
        UserQuestStatus[] memory statuses
    ) external {
        _onlyFloxContributor();

        if (users.length != questIds.length) revert ArrayLengthMismatch();
        if (questIds.length != statuses.length) revert ArrayLengthMismatch();

        for (uint256 i; i < users.length; ) {
            if (questIds[i] >= nextQuestId) revert QuestDoesNotExist();
            if (statuses[i] <= userQuestStatus[users[i]][questIds[i]]) {
                revert InvalidUserQuestStatusUpdate(userQuestStatus[users[i]][questIds[i]], statuses[i]);
            }

            _updateUserQuestProgress(questIds[i], users[i], statuses[i]);

            emit UserQuestStatusUpdated(users[i], questIds[i], statuses[i]);

            unchecked {
                ++i;
            }
        }
    }

    /**
     * @notice Used to update the status of a user's progress on a quest.
     * @dev Once the quest progress status transitions from `Incomplete` to anything else, the experience points for it
     *  are allocated to the user.
     * @dev Once the quest progress status transitions to `Allocated`, the reward earned for completing the quest is
     *  credited to the user's data.
     * @dev `Recursive` quests can only be marked as `Allocated` if there are unallocated completions.
     * @param questId ID of the quest being updated
     * @param user Address of the user receiving the quest status update
     * @param status New status assigned to the user's progress on the quest
     */
    function _updateUserQuestProgress(uint256 questId, address user, UserQuestStatus status) internal {
        Quest storage quest = _quests[questId];
        QuestType questType = quest.questType;
        UserQuestStatus currentQuestStatus = userQuestStatus[user][questId];

        if (questType == QuestType.Limited) {
            if (
                limitedQuestCompletions[questId] >= quest.maxCompletions &&
                currentQuestStatus != UserQuestStatus.PendingAllocation
            ) {
                revert MaximumNumberOfCompletionsReached();
            }
        }

        if (questType == QuestType.Recursive) {
            uint64 cooldown = quest.recursiveCooldown;
            if (
                _recursiveQuestCompletions[user][questId].lastCompletionTimestamp + uint256(cooldown) > block.timestamp
            ) {
                revert CooldownStillActive();
            }
            if (
                status == UserQuestStatus.Allocated &&
                _recursiveQuestCompletions[user][questId].unallocatedCompletions == 0
            ) {
                revert NoRecursiveQuestPendingAllocations();
            }
        }

        if (
            currentQuestStatus == UserQuestStatus.Incomplete ||
            (questType == QuestType.Recursive && status == UserQuestStatus.PendingAllocation)
        ) {
            completedQuestIds[user][_userQuestProgress[user].numberOfCompletedQuests] = questId;
            _userQuestProgress[user].numberOfCompletedQuests++;
            _allocateUserExperiencePoints(user, quest.experiencePoints);
            if (questType == QuestType.Recursive) {
                _recursiveQuestCompletions[user][questId].totalCompletions++;
                _recursiveQuestCompletions[user][questId].unallocatedCompletions++;
                _recursiveQuestCompletions[user][questId].lastCompletionTimestamp = uint128(block.timestamp);
            } else if (questType == QuestType.Limited) {
                limitedQuestCompletions[questId]++;

                if (
                    currentQuestStatus == UserQuestStatus.Incomplete &&
                    limitedQuestCompletions[questId] >= quest.maxCompletions
                ) {
                    quest.status = QuestStatus.Expired;
                    quest.endBlock = block.number;

                    emit QuestUpdated(questId, quest.reward, quest.startBlock, block.number, QuestStatus.Expired);
                }
            }
        }

        if (status == UserQuestStatus.Allocated) {
            _userQuestProgress[user].lifetimePoints += quest.reward;
            if (questType == QuestType.Recursive) {
                _recursiveQuestCompletions[user][questId].unallocatedCompletions--;
            }
        }

        userQuestStatus[user][questId] = status;
    }

    function _allocateUserExperiencePoints(address user, uint64 experiencePoints) internal {
        UserData storage userData = _userQuestProgress[user];

        userData.lifetimeExperienceEarned += experiencePoints;
        userData.currentLevelExperiencePoints += experiencePoints;

        if (userData.currentLevel == 0) {
            userData.currentLevel = 1;
        }

        uint64 requiredLevelUpExperience = getRequiredExperienceForNextLevel(userData.currentLevel);

        while (userData.currentLevelExperiencePoints >= requiredLevelUpExperience) {
            userData.currentLevelExperiencePoints -= requiredLevelUpExperience;
            userData.currentLevel++;

            emit UserLeveledUp(user, userData.currentLevel);

            requiredLevelUpExperience = getRequiredExperienceForNextLevel(userData.currentLevel);
        }
    }

    /// Storage gap to prevent storage collisions.
    uint256[50] private __gap;
}

File 3 of 12 : IQuestTrackerEnums.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/**
 * ====================================================================
 * |     ______                   _______                             |
 * |    / _____________ __  __   / ____(_____  ____ _____  ________   |
 * |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
 * |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
 * | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
 * |                                                                  |
 * ====================================================================
 * ===================== IQuestTrackerEnums ===========================
 * ====================================================================
 * Frax Finance: https://github.com/FraxFinance
 */

/**
 * @title IQuestTrackerEnums
 * @author Frax Finance
 * @notice A collection of enums used by the Flox Quest tracker system.
 */
contract IQuestTrackerEnums {
    /**
     * @notice This enum represents the global status of a quest.
     */
    enum QuestStatus {
        Pending, // 0; Quest has not yet been added or fully defined
        Upcoming, // 1; Quest is defined but not yet active (the starting block is in the future)
        Active, // 2; Quest is currently active and can be completed by the users in order to earn rewards
        Expired // 3; Quest is no longer active and can no longer be completed by the users
    }

    /**
     * @notice This enum represents the type of a quest.
     * @dev The Unset quest type is used to power the ability to update the quest type.
     */
    enum QuestType {
        Unset, // 0; Quest type has not yet been set
        Single, // 1; Quest can only be completed once
        Recursive, // 2; Quest can be completed multiple times
        Limited // 3; Quest can be completed by a limited number of users
    }

    /**
     * @notice This enum represents the status of a user's progress on a quest.
     */
    enum UserQuestStatus {
        Incomplete, // 0; User has not yet completed the quest
        PendingAllocation, // 1; User has completed the quest but the reward has not yet been allocated
        Allocated // 2; User has completed the quest and the reward has been allocated
    }
}

File 4 of 12 : IFNSQuestValidatorErrors.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/**
 * ====================================================================
 * |     ______                   _______                             |
 * |    / _____________ __  __   / ____(_____  ____ _____  ________   |
 * |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
 * |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
 * | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
 * |                                                                  |
 * ====================================================================
 * =================== IFNSQuestValidatorErrors =======================
 * ====================================================================
 * Frax Finance: https://github.com/FraxFinance
 */

/**
 * @title IFNSQuestValidatorErrors
 * @author Frax Finance
 * @notice The IFNSQuestValidatorErrors interface is used to provide the errors used by the FNSQuestValidator smart
 *  contract.
 */
interface IFNSQuestValidatorErrors {
    /// @notice Error emitted when trying to validate a quest with a token that is not owned by the user.
    error InvalidTokenOwner();

    /// @notice Error emitted when the token has already been used to complete a quest.
    error TokenAlreadyUsed();
}

File 5 of 12 : IFNSQuestValidatorEvents.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/**
 * ====================================================================
 * |     ______                   _______                             |
 * |    / _____________ __  __   / ____(_____  ____ _____  ________   |
 * |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
 * |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
 * | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
 * |                                                                  |
 * ====================================================================
 * =================== IFNSQuestValidatorEvents =======================
 * ====================================================================
 * Frax Finance: https://github.com/FraxFinance
 */

/**
 * @title IFNSQuestValidatorEvents
 * @author Frax Finance
 * @notice The IFNSQuestValidatorEvents interface is used to provide the events used by the FNSQuestValidator smart
 *  contract.
 */
interface IFNSQuestValidatorEvents {
    /**
     * @notice Emitted when a base quest is updated.
     * @param baseQuestId ID of the base quest
     */
    event BaseQuestUpdate(uint8 baseQuestId);

    /**
     * @notice Emitted when a limited quest is updated.
     * @param domainLength Length of the domain
     * @param questId ID of the limited quest associated with the specified domain length
     */
    event LimitedQuestUpdate(uint256 domainLength, uint256 questId);
}

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/**
 * ====================================================================
 * |     ______                   _______                             |
 * |    / _____________ __  __   / ____(_____  ____ _____  ________   |
 * |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
 * |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
 * | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
 * |                                                                  |
 * ====================================================================
 * ================= QuestValidatorAccessControl ======================
 * ====================================================================
 * Frax Finance: https://github.com/FraxFinance
 */
import { QuestValidatorAccessControlEvents } from "./QuestValidatorAccessControlEvents.sol";
import { QuestValidatorAccessControlErrors } from "./QuestValidatorAccessControlErrors.sol";

/**
 * @title QuestValidatorAccessControl
 * @author Frax Finance
 * @notice The QuestValidatorAccessControl contract is used to power the access control of the quest validator for the
 *  Quest track of the Flox ecosystem.
 */
contract QuestValidatorAccessControl is QuestValidatorAccessControlEvents, QuestValidatorAccessControlErrors {
    /// Address of the owner of the contract.
    address public owner;
    /// Address of the nominated owner of the contract.
    address public nominatedOwner;

    /**
     * @notice Used to track Flox contributors.
     * @dev contributor Address of the contributor
     * @dev isContributor Status of the contributor
     */
    mapping(address contributor => bool isContributor) public floxContributors;

    /**
     * @notice Used to initialize the smart contract and set the owner.
     * @dev Address of the owner of the contract will be set to the deployer.
     */
    constructor() {
        owner = msg.sender;
    }

    /**
     * @notice Used to restrict function execution to calls initiated by the owner.
     */
    modifier onlyOwner() {
        if (msg.sender != owner) revert NotOwner();
        _;
    }

    /**
     * @notice Nominate a new owner for the contract.
     * @dev Only the current owner can nominate a new owner.
     * @param _owner Address of the new owner
     */
    function nominateNewOwner(address _owner) external onlyOwner {
        nominatedOwner = _owner;

        emit OwnerNominated(_owner);
    }

    /**
     * @notice Accept the ownership of the contract.
     * @dev Only the nominated owner can accept the ownership.
     */
    function acceptOwnership() external {
        if (msg.sender != nominatedOwner) revert NotNominatedOwner();

        address oldOwner = owner;

        owner = nominatedOwner;
        nominatedOwner = address(0);

        emit OwnerChanged(oldOwner, owner);
    }

    /**
     * @notice Manage Flox contributors.
     * @dev Flox contributor is allowed to manage all quests as well as their progress status for every user.
     * @param _contributor Address of the contributor to manage
     * @param _isContributor Status to assign the contributor. `false` to remove, `true` to add.
     */
    function manageFloxContributors(address _contributor, bool _isContributor) external onlyOwner {
        if (floxContributors[_contributor] == _isContributor) revert SameContributorStatus();

        floxContributors[_contributor] = _isContributor;

        emit FloxContributorUpdate(_contributor, _isContributor);
    }

    /**
     * @notice Used to restrict function execution to calls initiated by a Flox contributor.
     */
    function _onlyFloxContributor() internal view {
        if (!floxContributors[msg.sender]) revert NotFloxContributor();
    }
}

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

interface IFNS {
    function names(bytes32) external view returns (bytes memory);

    function ownerOf(uint256 id) external view returns (address owner);
}

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/**
 * ====================================================================
 * |     ______                   _______                             |
 * |    / _____________ __  __   / ____(_____  ____ _____  ________   |
 * |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
 * |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
 * | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
 * |                                                                  |
 * ====================================================================
 * =================== QuestTrackerAccessControl ======================
 * ====================================================================
 * Frax Finance: https://github.com/FraxFinance
 */
import { IQuestTrackerEvents } from "./IQuestTrackerEvents.sol";

/**
 * @title QuestTrackerAccessControl
 * @author Frax Finance
 * @notice The QuestTrackerAccessControl contract is used to power the access control of the QuestTracker smart contract.
 */
contract QuestTrackerAccessControl is IQuestTrackerEvents {
    /// Address of the owner of the contract.
    address public owner;
    /// Address of the nominated owner of the contract.
    address public nominatedOwner;

    /**
     * @notice Used to track Flox contributors.
     * @dev contributor Address of the contributor
     * @dev isContributor Status of the contributor
     */
    mapping(address contributor => bool isContributor) public floxContributors;
    /**
     * @notice Used to track contributors for specific quests.
     * @dev The quest contributors are used to manage the statuses of specific quests.
     * @dev questId ID of the quest
     * @dev questContibutor Address of the quest contributor
     * @dev isContributor Status of the quest contributor
     */
    mapping(uint256 questId => mapping(address questContibutor => bool isContributor)) public questContributors;

    /**
     * @notice Used to initialize the smart contract and set the owner.
     * @param _owner Address of the owner of the contract
     */
    function initialize(address _owner) public virtual {
        if (owner != address(0)) revert AlreadyInitialized();

        owner = _owner;
    }

    /**
     * @notice Used to restrict function execution to calls initiated by the owner.
     */
    modifier onlyOwner() {
        if (msg.sender != owner) revert NotOwner();
        _;
    }

    /**
     * @notice Nominate a new owner for the contract.
     * @dev Only the current owner can nominate a new owner.
     * @param _owner Address of the new owner
     */
    function nominateNewOwner(address _owner) external onlyOwner {
        nominatedOwner = _owner;

        emit OwnerNominated(_owner);
    }

    /**
     * @notice Accept the ownership of the contract.
     * @dev Only the nominated owner can accept the ownership.
     */
    function acceptOwnership() external {
        if (msg.sender != nominatedOwner) revert NotNominatedOwner();

        address oldOwner = owner;

        owner = nominatedOwner;
        nominatedOwner = address(0);

        emit OwnerChanged(oldOwner, owner);
    }

    /**
     * @notice Manage Flox contributors.
     * @dev Flox contributor is allowed to manage all quests as well as their progress status for every user.
     * @param _contributor Address of the contributor to manage
     * @param _isContributor Status to assign the contributor. `false` to remove, `true` to add.
     */
    function manageFloxContributors(address _contributor, bool _isContributor) external onlyOwner {
        if (floxContributors[_contributor] == _isContributor) revert SameContributorStatus();

        floxContributors[_contributor] = _isContributor;

        emit FloxContributorUpdate(_contributor, _isContributor);
    }

    /**
     * @notice Update the specific contributor for a quest.
     * @dev Only the owner can call this function.
     * @dev We allow multiple contributors for a single quest, so that we support the possibility of automatic quest
     *  status updates as well as dedicated validators of quest completion to work in synergy.
     * @param _questId ID of the quest we are configuring the specific contibutor for.
     * @param _contributor Address of the quest contributor being managed
     * @param _isContributor Status to assign the contributor. `false` to remove, `true` to add.
     */
    function updateQuestContributor(uint256 _questId, address _contributor, bool _isContributor) external onlyOwner {
        if (questContributors[_questId][_contributor] == _isContributor) revert SameContributorStatus();

        questContributors[_questId][_contributor] = _isContributor;

        emit QuestContributorUpdate(_questId, _contributor, _isContributor);
    }

    /**
     * @notice Used to restrict function execution to calls initiated by a Quest contributor.
     * @param _questId ID of the quest to check the quest contributor status for
     */
    function _onlyQuestContributor(uint256 _questId) internal view {
        if (!questContributors[_questId][msg.sender]) revert NotQuestContributor();
    }

    /**
     * @notice Used to restrict function execution to calls initiated by a Flox contributor.
     */
    function _onlyFloxContributor() internal view {
        if (!floxContributors[msg.sender]) revert NotFloxContributor();
    }

    /**
     * @notice Used to restrict function execution to calls initiated by a Flox or Quest contributor.
     * @param _questId ID of the quest to check the quest contributor status for
     */
    function _onlyContributor(uint256 _questId) internal view {
        if (!floxContributors[msg.sender] && !questContributors[_questId][msg.sender]) {
            revert NotFloxOrQuestContributor();
        }
    }

    /// Storage gap to prevent storage collisions.
    uint256[50] private __gap;
}

File 9 of 12 : QuestValidatorAccessControlEvents.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/**
 * ====================================================================
 * |     ______                   _______                             |
 * |    / _____________ __  __   / ____(_____  ____ _____  ________   |
 * |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
 * |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
 * | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
 * |                                                                  |
 * ====================================================================
 * ============== QuestValidatorAccessControlEvents ===================
 * ====================================================================
 * Frax Finance: https://github.com/FraxFinance
 */

/**
 * @title QuestValidatorAccessControlEvents
 * @author Frax Finance
 * @notice The QuestValidatorAccessControlEvents contract is used to power the quest tracking system for the Flox ecosystem.
 */
contract QuestValidatorAccessControlEvents {
    /**
     * @notice Emitted when the contributor status of an address is updated.
     * @param contributor Adress of the contributor
     * @param isContributor Contributor status; `true` if the address is a contributor, `false` otherwise
     */
    event FloxContributorUpdate(address contributor, bool isContributor);

    /**
     * @notice Emitted when a new address is nominated as the owner of the contract.
     * @param newOwner Address of the account nominated to be the new owner
     */
    event OwnerNominated(address newOwner);

    /**
     * @notice Emitted when the ownership of the contract is transferred.
     * @param oldOwner Address of the previous owner of the smart contract
     * @param newOwner Address of the new owner of the smart contract
     */
    event OwnerChanged(address oldOwner, address newOwner);
}

File 10 of 12 : QuestValidatorAccessControlErrors.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/**
 * ====================================================================
 * |     ______                   _______                             |
 * |    / _____________ __  __   / ____(_____  ____ _____  ________   |
 * |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
 * |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
 * | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
 * |                                                                  |
 * ====================================================================
 * ============== QuestValidatorAccessControlErrors ===================
 * ====================================================================
 * Frax Finance: https://github.com/FraxFinance
 */

/**
 * @title QuestValidatorAccessControlErrors
 * @author Frax Finance
 * @notice The QuestValidatorAccessControlErrors contract is used to provide the errors used by the quest validators of
 *  the Quest track of the Flox ecosystem.
 */
contract QuestValidatorAccessControlErrors {
    /// @notice Error emitted when the caller is not a Flox contributor.
    error NotFloxContributor();

    /// @notice Error emitted when the caller is not the nominated owner.
    error NotNominatedOwner();

    /// @notice Error emitted when the caller is not the owner.
    error NotOwner();

    /// @notice Error emitted when the attempted contibutor status change is the same as the current status.
    error SameContributorStatus();
}

File 11 of 12 : IQuestTrackerEvents.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/**
 * ====================================================================
 * |     ______                   _______                             |
 * |    / _____________ __  __   / ____(_____  ____ _____  ________   |
 * |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
 * |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
 * | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
 * |                                                                  |
 * ====================================================================
 * ======================== IQuestTrackerEvents =======================
 * ====================================================================
 * Frax Finance: https://github.com/FraxFinance
 */
import { IQuestTrackerErrors } from "./IQuestTrackerErrors.sol";

/**
 * @title IQuestTrackerEvents
 * @author Frax Finance
 * @notice A collection of events used by the Flox Quest tracker system.
 */
contract IQuestTrackerEvents is IQuestTrackerErrors {
    /**
     * @notice Emitted when the Flox contributor status is updated.
     * @param contributor Address of the cintributor being updated
     * @param isContributor New status assigned to the contributor
     */
    event FloxContributorUpdate(address contributor, bool isContributor);

    /**
     * @notice Emitted when the ownership of the contract is transferred.
     * @param oldOwner Address of the previous owner
     * @param newOwner Address of the new owner
     */
    event OwnerChanged(address oldOwner, address newOwner);

    /**
     * @notice Emitted when a new owner is nominated.
     * @param newOwner Address of the account nominated to be the new owner
     */
    event OwnerNominated(address newOwner);

    /**
     * @notice Emitted when a user levels up.
     * @param user Address of the user leveling up
     * @param level New level assigned to the user
     */
    event UserLeveledUp(address indexed user, uint256 indexed level);

    /**
     * @notice Emitted when a user's progress on a quest is updated.
     * @param user Address of the user receiving the quest status update
     * @param questId ID of the quest being updated
     * @param status New status assigned to the user's progress on the quest
     */
    event UserQuestStatusUpdated(address indexed user, uint256 indexed questId, UserQuestStatus status);

    /**
     * @notice Emitted when a new quest is added.
     * @param questId ID of the quest being added
     * @param reward Amount of FXTL received upon completion of the quest
     * @param startBlock Block at which the quest starts
     * @param endBlock Block at which the quest ends
     */
    event QuestAdded(uint256 indexed questId, uint256 reward, uint256 startBlock, uint256 endBlock);

    /**
     * @notice Emitted when the quest contributor status is updated.
     * @param questId ID of the quest receiving the contributor update
     * @param contributor Address of the contributor being updated
     * @param isContributor New status assigned to the contributor
     */
    event QuestContributorUpdate(uint256 questId, address contributor, bool isContributor);

    /**
     * @notice Emitted when a quest is updated.
     * @param questId ID of the quest being updated
     * @param reward Amount of FXRTL received upon completion of the quest
     * @param startBlock Block number at which the quest starts
     * @param endBlock BlockNumber at which the quest starts
     * @param status Status of the quest after the update
     */
    event QuestUpdated(
        uint256 indexed questId,
        uint256 reward,
        uint256 startBlock,
        uint256 endBlock,
        QuestStatus status
    );

    /**
     * @notice Emitted when the validator settings of a quest are updated.
     * @param questId ID of the quest that had validator settings updated
     * @param allowsSelfValidation Whether the quest allows self-validation
     * @param validator Address of the validator smart contract for the quest
     */
    event QuestValidatorUpdated(uint256 indexed questId, bool allowsSelfValidation, address indexed validator);
}

File 12 of 12 : IQuestTrackerErrors.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/**
 * ====================================================================
 * |     ______                   _______                             |
 * |    / _____________ __  __   / ____(_____  ____ _____  ________   |
 * |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
 * |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
 * | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
 * |                                                                  |
 * ====================================================================
 * ======================== IQuestTrackerErrors =======================
 * ====================================================================
 * Frax Finance: https://github.com/FraxFinance
 */
import { IQuestTrackerEnums } from "./IQuestTrackerEnums.sol";

/**
 * @title IQuestTrackerErrors
 * @author Frax Finance
 * @notice A collection of events used by the Flox Quest tracker system.
 */
contract IQuestTrackerErrors is IQuestTrackerEnums {
    /// Returned if the smart contract is already initialized
    error AlreadyInitialized();

    /// Returned if the length of the arrays passed to a function do not match.
    error ArrayLengthMismatch();

    /// Returned if the cooldown period of the Recursive quest is still active.
    error CooldownStillActive();

    /// Returned if the start block is greater than the end block.
    error InvalidBlockRange();

    /// Returned if the quest level is invalid.
    error InvalidLevel();

    /// Returned if the quest validator address is set to the zero address if the self validation is permitted.
    error InvalidQuestValidator();

    /**
     * @notice Returned if the user quest status update is invalid.
     * @param currentStatus The current status of the user's progress on the quest
     * @param attemptedStatus The status attempted to be assigned to the user's progress on the quest
     */
    error InvalidUserQuestStatusUpdate(UserQuestStatus currentStatus, UserQuestStatus attemptedStatus);

    /// Signifies that the Limited quest has been completed the maximum number of times.
    error MaximumNumberOfCompletionsReached();

    /// Signifies that the recursive quest has no pending allocations.
    error NoRecursiveQuestPendingAllocations();

    /// Signifies that the caller is not a Flox contributor.
    error NotFloxContributor();

    /// Signifies tht the caller is neiter a Flox or Quest contributor.
    error NotFloxOrQuestContributor();

    /// Signifies that the caller is not the nominated owner.
    error NotNominatedOwner();

    /// Signifies that the caller is not the owner.
    error NotOwner();

    /// Signifies that the caller is not the quest contributor.
    error NotQuestContributor();

    /// Returned if the quest being accessed does not exist.
    error QuestDoesNotExist();

    /// Signifies that the attempted status change is the same as the preexisting status.
    error SameContributorStatus();

    /// Signifies that the attempted quest validator settings updates are the same as the preexisting settings.
    error SameValidatorSettings();
}

Settings
{
  "remappings": [
    "frax-std/=lib/frax-standard-solidity/src/",
    "@eth-optimism/=lib/optimism/packages/",
    "lib/optimism/packages/contracts-bedrock:src/=lib/optimism/packages/contracts-bedrock/src/",
    "src/=src/",
    "@openzeppelin-4/=node_modules/@openzeppelin-4/",
    "@openzeppelin-5/=node_modules/@openzeppelin-5/",
    "@openzeppelin/=node_modules/@openzeppelin/",
    "@rari-capital/=node_modules/@rari-capital/",
    "clones-with-immutable-args/=lib/optimism/packages/contracts-bedrock/lib/clones-with-immutable-args/src/",
    "ds-test/=lib/frax-standard-solidity/lib/forge-std/lib/ds-test/src/",
    "forge-std/=lib/frax-standard-solidity/lib/forge-std/src/",
    "frax-standard-solidity/=lib/frax-standard-solidity/src/",
    "kontrol-cheatcodes/=lib/optimism/packages/contracts-bedrock/lib/kontrol-cheatcodes/src/",
    "lib-keccak/=lib/optimism/packages/contracts-bedrock/lib/lib-keccak/contracts/",
    "openzeppelin-contracts-upgradeable/=lib/optimism/packages/contracts-bedrock/lib/openzeppelin-contracts-upgradeable/",
    "openzeppelin-contracts/=lib/optimism/packages/contracts-bedrock/lib/openzeppelin-contracts/",
    "optimism/=lib/optimism/",
    "safe-contracts/=lib/optimism/packages/contracts-bedrock/lib/safe-contracts/contracts/",
    "solady/=lib/optimism/packages/contracts-bedrock/lib/solady/",
    "solidity-bytes-utils/=lib/frax-standard-solidity/lib/solidity-bytes-utils/",
    "solmate/=lib/optimism/packages/contracts-bedrock/lib/solmate/src/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 100000
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "ipfs",
    "appendCBOR": true
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "paris",
  "viaIR": false,
  "libraries": {}
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"_questTracker","type":"address"},{"internalType":"address","name":"_fns","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"InvalidTokenOwner","type":"error"},{"inputs":[],"name":"NotFloxContributor","type":"error"},{"inputs":[],"name":"NotNominatedOwner","type":"error"},{"inputs":[],"name":"NotOwner","type":"error"},{"inputs":[],"name":"SameContributorStatus","type":"error"},{"inputs":[],"name":"TokenAlreadyUsed","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"baseQuestId","type":"uint8"}],"name":"BaseQuestUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"contributor","type":"address"},{"indexed":false,"internalType":"bool","name":"isContributor","type":"bool"}],"name":"FloxContributorUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"domainLength","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"questId","type":"uint256"}],"name":"LimitedQuestUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":false,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerNominated","type":"event"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"domainLength","type":"uint256"},{"internalType":"uint256","name":"questId","type":"uint256"}],"name":"addLimitedQuest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"baseQuestId","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"bulkSelfValidateQuests","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"contributor","type":"address"}],"name":"floxContributors","outputs":[{"internalType":"bool","name":"isContributor","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fns","outputs":[{"internalType":"contract IFNS","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"domainLength","type":"uint256"}],"name":"lengthToQuestId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"longestLimitedQuestLength","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_contributor","type":"address"},{"internalType":"bool","name":"_isContributor","type":"bool"}],"name":"manageFloxContributors","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"nominateNewOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"nominatedOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"questTracker","outputs":[{"internalType":"contract QuestTracker","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"selfValidateQuest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"_baseQuestId","type":"uint8"}],"name":"updateBaseQuestId","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"usedTokens","outputs":[{"internalType":"bool","name":"alreadyUsed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"user","type":"address"}],"name":"validateQuestForUser","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"address[]","name":"users","type":"address[]"}],"name":"validateQuestsForMulitpleUsers","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"address","name":"user","type":"address"}],"name":"validateQuestsForSingleUser","outputs":[],"stateMutability":"nonpayable","type":"function"}]

0x608060405234801561001057600080fd5b506040516114ed3803806114ed83398101604081905261002f91610087565b600080546001600160a01b03199081163317909155600380546001600160a01b03948516908316179055600480549290931691161790556100ba565b80516001600160a01b038116811461008257600080fd5b919050565b6000806040838503121561009a57600080fd5b6100a38361006b565b91506100b16020840161006b565b90509250929050565b611424806100c96000396000f3fe608060405234801561001057600080fd5b50600436106101515760003560e01c806379ba5097116100cd578063b382e4c511610081578063f501127a11610066578063f501127a14610324578063fc403b7a14610337578063fe5979fb1461034a57600080fd5b8063b382e4c5146102f1578063ee7db1331461031157600080fd5b8063889338d0116100b2578063889338d0146102ab5780638da5cb5b146102be578063af1bee0b146102de57600080fd5b806379ba509714610290578063888d911f1461029857600080fd5b80634fd9d1e0116101245780636024a641116101095780636024a6411461023e5780636b26aafc14610262578063782f67981461028357600080fd5b80634fd9d1e0146101fb57806353a47bb71461021e57600080fd5b80630f7ee879146101565780631627540c1461018e57806328f30c51146101a35780632f2f8c36146101e8575b600080fd5b610179610164366004610eae565b60056020526000908152604090205460ff1681565b60405190151581526020015b60405180910390f35b6101a161019c366004610ee9565b61035d565b005b6003546101c39073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610185565b6101a16101f6366004611017565b610428565b610179610209366004610ee9565b60026020526000908152604090205460ff1681565b6001546101c39073ffffffffffffffffffffffffffffffffffffffff1681565b60075461025090610100900460ff1681565b60405160ff9091168152602001610185565b610275610270366004610eae565b61046c565b604051908152602001610185565b6007546102509060ff1681565b6101a16104a8565b6101a16102a6366004611069565b610584565b6101a16102b936600461112d565b6105dc565b6000546101c39073ffffffffffffffffffffffffffffffffffffffff1681565b6101a16102ec36600461116a565b610617565b6004546101c39073ffffffffffffffffffffffffffffffffffffffff1681565b6101a161031f366004610eae565b61067f565b6101a161033236600461118d565b61068c565b6101a16103453660046111b2565b61069e565b6101a16103583660046111e5565b6107e5565b60005473ffffffffffffffffffffffffffffffffffffffff1633146103ae576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040519081527f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce22906020015b60405180910390a150565b610430610878565b60005b82518110156104675761045f83828151811061045157610451611207565b6020026020010151836108c3565b600101610433565b505050565b600754600090610100900460ff168210156104875781610493565b600754610100900460ff165b60009081526006602052604090205492915050565b60015473ffffffffffffffffffffffffffffffffffffffff1633146104f9576040517fb1f6da5000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600080546001805473ffffffffffffffffffffffffffffffffffffffff8082167fffffffffffffffffffffffff0000000000000000000000000000000000000000808616821790965594909116909155604080519190921680825260208201939093527fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c910161041d565b61058c610878565b60005b8251811015610467576105d48382815181106105ad576105ad611207565b60200260200101518383815181106105c7576105c7611207565b60200260200101516108c3565b60010161058f565b60005b81518110156106135761060b8282815181106105fd576105fd611207565b6020026020010151336108c3565b6001016105df565b5050565b61061f610878565b600780547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff83169081179091556040519081527f031379e7d376148a539f4a337e00ec2654764c77feb4e1b281c0a21b9d21b3669060200161041d565b61068981336108c3565b50565b610694610878565b61061382826108c3565b60005473ffffffffffffffffffffffffffffffffffffffff1633146106ef576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff821660009081526002602052604090205481151560ff909116151503610756576040517fd31fc68300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff821660008181526002602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168515159081179091558251938452908301527f1b6e6916bd76fb0e62ff02acf466a38852d02426c13ba96e69803693ac627d5891015b60405180910390a15050565b6107ed610878565b6000828152600660205260409020819055600754610100900460ff1682111561084257600780547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff1661010060ff8516021790555b60408051838152602081018390527f5c9eca55d6b1f6aece7086203e2ef8884f05d1fcad00cbdbdb16affbd39a518f91016107d9565b3360009081526002602052604090205460ff166108c1576040517f6b697ed100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b60008281526005602052604090205460ff161561090c576040517f883209e000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600480546040517f6352211e00000000000000000000000000000000000000000000000000000000815291820184905273ffffffffffffffffffffffffffffffffffffffff83811692911690636352211e90602401602060405180830381865afa15801561097e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109a29190611236565b73ffffffffffffffffffffffffffffffffffffffff16146109ef576040517f2a7c6b6e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008060006109fd85610bba565b90506000610a0a82610c79565b600754909150610a2090869060ff166000610c9e565b600754909450610100900460ff16811015610a3b5780610a47565b600754610100900460ff165b9050610a5d85610a568361046c565b6001610c9e565b600087815260056020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905592508315610b31576003546007546040517f1030957e00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff90921691631030957e91610afe9160ff909116908990600190600401611282565b600060405180830381600087803b158015610b1857600080fd5b505af1158015610b2c573d6000803e3d6000fd5b505050505b8215610bb25760035473ffffffffffffffffffffffffffffffffffffffff16631030957e610b5e8361046c565b8760016040518463ffffffff1660e01b8152600401610b7f93929190611282565b600060405180830381600087803b158015610b9957600080fd5b505af1158015610bad573d6000803e3d6000fd5b505050505b505050505050565b600480546040517f20c38e2b000000000000000000000000000000000000000000000000000000008152918201839052606091839173ffffffffffffffffffffffffffffffffffffffff16906320c38e2b90602401600060405180830381865afa158015610c2c573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610c7291908101906112ea565b9392505050565b60008082600081518110610c8f57610c8f611207565b016020015160f81c9392505050565b60008115610df8576003546040517fe02140f900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff868116600483015260248201869052600092169063e02140f990604401602060405180830381865afa158015610d1e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d4291906113b4565b6002811115610d5357610d53611253565b148015610df157506003546040517f3dd030420000000000000000000000000000000000000000000000000000000081526004810185905260009173ffffffffffffffffffffffffffffffffffffffff1690633dd0304290602401602060405180830381865afa158015610dcb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610def91906113d5565b115b9050610c72565b6003546040517fe02140f900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff868116600483015260248201869052600092169063e02140f990604401602060405180830381865afa158015610e70573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e9491906113b4565b6002811115610ea557610ea5611253565b14949350505050565b600060208284031215610ec057600080fd5b5035919050565b73ffffffffffffffffffffffffffffffffffffffff8116811461068957600080fd5b600060208284031215610efb57600080fd5b8135610c7281610ec7565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715610f7c57610f7c610f06565b604052919050565b600067ffffffffffffffff821115610f9e57610f9e610f06565b5060051b60200190565b600082601f830112610fb957600080fd5b81356020610fce610fc983610f84565b610f35565b8083825260208201915060208460051b870101935086841115610ff057600080fd5b602086015b8481101561100c5780358352918301918301610ff5565b509695505050505050565b6000806040838503121561102a57600080fd5b823567ffffffffffffffff81111561104157600080fd5b61104d85828601610fa8565b925050602083013561105e81610ec7565b809150509250929050565b6000806040838503121561107c57600080fd5b823567ffffffffffffffff8082111561109457600080fd5b6110a086838701610fa8565b93506020915081850135818111156110b757600080fd5b85019050601f810186136110ca57600080fd5b80356110d8610fc982610f84565b81815260059190911b820183019083810190888311156110f757600080fd5b928401925b8284101561111e57833561110f81610ec7565b825292840192908401906110fc565b80955050505050509250929050565b60006020828403121561113f57600080fd5b813567ffffffffffffffff81111561115657600080fd5b61116284828501610fa8565b949350505050565b60006020828403121561117c57600080fd5b813560ff81168114610c7257600080fd5b600080604083850312156111a057600080fd5b82359150602083013561105e81610ec7565b600080604083850312156111c557600080fd5b82356111d081610ec7565b91506020830135801515811461105e57600080fd5b600080604083850312156111f857600080fd5b50508035926020909101359150565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60006020828403121561124857600080fd5b8151610c7281610ec7565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b83815273ffffffffffffffffffffffffffffffffffffffff8316602082015260608101600383106112dc577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b826040830152949350505050565b600060208083850312156112fd57600080fd5b825167ffffffffffffffff8082111561131557600080fd5b818501915085601f83011261132957600080fd5b81518181111561133b5761133b610f06565b61136b847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601610f35565b9150808252868482850101111561138157600080fd5b60005b8181101561139f578381018501518382018601528401611384565b50600090820190930192909252509392505050565b6000602082840312156113c657600080fd5b815160038110610c7257600080fd5b6000602082840312156113e757600080fd5b505191905056fea264697066735822122050796a59b30cc47c11708b325b1b3db18a238a077844e89a349fb0e4d2d5e78664736f6c6343000817003300000000000000000000000020f98f21ab039c8ceb8f8511aa6cf2e01abbe1bd000000000000000000000000b989e514980dc837fd554f1f85673b0091cf25f3

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106101515760003560e01c806379ba5097116100cd578063b382e4c511610081578063f501127a11610066578063f501127a14610324578063fc403b7a14610337578063fe5979fb1461034a57600080fd5b8063b382e4c5146102f1578063ee7db1331461031157600080fd5b8063889338d0116100b2578063889338d0146102ab5780638da5cb5b146102be578063af1bee0b146102de57600080fd5b806379ba509714610290578063888d911f1461029857600080fd5b80634fd9d1e0116101245780636024a641116101095780636024a6411461023e5780636b26aafc14610262578063782f67981461028357600080fd5b80634fd9d1e0146101fb57806353a47bb71461021e57600080fd5b80630f7ee879146101565780631627540c1461018e57806328f30c51146101a35780632f2f8c36146101e8575b600080fd5b610179610164366004610eae565b60056020526000908152604090205460ff1681565b60405190151581526020015b60405180910390f35b6101a161019c366004610ee9565b61035d565b005b6003546101c39073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610185565b6101a16101f6366004611017565b610428565b610179610209366004610ee9565b60026020526000908152604090205460ff1681565b6001546101c39073ffffffffffffffffffffffffffffffffffffffff1681565b60075461025090610100900460ff1681565b60405160ff9091168152602001610185565b610275610270366004610eae565b61046c565b604051908152602001610185565b6007546102509060ff1681565b6101a16104a8565b6101a16102a6366004611069565b610584565b6101a16102b936600461112d565b6105dc565b6000546101c39073ffffffffffffffffffffffffffffffffffffffff1681565b6101a16102ec36600461116a565b610617565b6004546101c39073ffffffffffffffffffffffffffffffffffffffff1681565b6101a161031f366004610eae565b61067f565b6101a161033236600461118d565b61068c565b6101a16103453660046111b2565b61069e565b6101a16103583660046111e5565b6107e5565b60005473ffffffffffffffffffffffffffffffffffffffff1633146103ae576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040519081527f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce22906020015b60405180910390a150565b610430610878565b60005b82518110156104675761045f83828151811061045157610451611207565b6020026020010151836108c3565b600101610433565b505050565b600754600090610100900460ff168210156104875781610493565b600754610100900460ff165b60009081526006602052604090205492915050565b60015473ffffffffffffffffffffffffffffffffffffffff1633146104f9576040517fb1f6da5000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600080546001805473ffffffffffffffffffffffffffffffffffffffff8082167fffffffffffffffffffffffff0000000000000000000000000000000000000000808616821790965594909116909155604080519190921680825260208201939093527fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c910161041d565b61058c610878565b60005b8251811015610467576105d48382815181106105ad576105ad611207565b60200260200101518383815181106105c7576105c7611207565b60200260200101516108c3565b60010161058f565b60005b81518110156106135761060b8282815181106105fd576105fd611207565b6020026020010151336108c3565b6001016105df565b5050565b61061f610878565b600780547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff83169081179091556040519081527f031379e7d376148a539f4a337e00ec2654764c77feb4e1b281c0a21b9d21b3669060200161041d565b61068981336108c3565b50565b610694610878565b61061382826108c3565b60005473ffffffffffffffffffffffffffffffffffffffff1633146106ef576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff821660009081526002602052604090205481151560ff909116151503610756576040517fd31fc68300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff821660008181526002602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168515159081179091558251938452908301527f1b6e6916bd76fb0e62ff02acf466a38852d02426c13ba96e69803693ac627d5891015b60405180910390a15050565b6107ed610878565b6000828152600660205260409020819055600754610100900460ff1682111561084257600780547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff1661010060ff8516021790555b60408051838152602081018390527f5c9eca55d6b1f6aece7086203e2ef8884f05d1fcad00cbdbdb16affbd39a518f91016107d9565b3360009081526002602052604090205460ff166108c1576040517f6b697ed100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b60008281526005602052604090205460ff161561090c576040517f883209e000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600480546040517f6352211e00000000000000000000000000000000000000000000000000000000815291820184905273ffffffffffffffffffffffffffffffffffffffff83811692911690636352211e90602401602060405180830381865afa15801561097e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109a29190611236565b73ffffffffffffffffffffffffffffffffffffffff16146109ef576040517f2a7c6b6e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008060006109fd85610bba565b90506000610a0a82610c79565b600754909150610a2090869060ff166000610c9e565b600754909450610100900460ff16811015610a3b5780610a47565b600754610100900460ff165b9050610a5d85610a568361046c565b6001610c9e565b600087815260056020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905592508315610b31576003546007546040517f1030957e00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff90921691631030957e91610afe9160ff909116908990600190600401611282565b600060405180830381600087803b158015610b1857600080fd5b505af1158015610b2c573d6000803e3d6000fd5b505050505b8215610bb25760035473ffffffffffffffffffffffffffffffffffffffff16631030957e610b5e8361046c565b8760016040518463ffffffff1660e01b8152600401610b7f93929190611282565b600060405180830381600087803b158015610b9957600080fd5b505af1158015610bad573d6000803e3d6000fd5b505050505b505050505050565b600480546040517f20c38e2b000000000000000000000000000000000000000000000000000000008152918201839052606091839173ffffffffffffffffffffffffffffffffffffffff16906320c38e2b90602401600060405180830381865afa158015610c2c573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610c7291908101906112ea565b9392505050565b60008082600081518110610c8f57610c8f611207565b016020015160f81c9392505050565b60008115610df8576003546040517fe02140f900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff868116600483015260248201869052600092169063e02140f990604401602060405180830381865afa158015610d1e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d4291906113b4565b6002811115610d5357610d53611253565b148015610df157506003546040517f3dd030420000000000000000000000000000000000000000000000000000000081526004810185905260009173ffffffffffffffffffffffffffffffffffffffff1690633dd0304290602401602060405180830381865afa158015610dcb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610def91906113d5565b115b9050610c72565b6003546040517fe02140f900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff868116600483015260248201869052600092169063e02140f990604401602060405180830381865afa158015610e70573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e9491906113b4565b6002811115610ea557610ea5611253565b14949350505050565b600060208284031215610ec057600080fd5b5035919050565b73ffffffffffffffffffffffffffffffffffffffff8116811461068957600080fd5b600060208284031215610efb57600080fd5b8135610c7281610ec7565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715610f7c57610f7c610f06565b604052919050565b600067ffffffffffffffff821115610f9e57610f9e610f06565b5060051b60200190565b600082601f830112610fb957600080fd5b81356020610fce610fc983610f84565b610f35565b8083825260208201915060208460051b870101935086841115610ff057600080fd5b602086015b8481101561100c5780358352918301918301610ff5565b509695505050505050565b6000806040838503121561102a57600080fd5b823567ffffffffffffffff81111561104157600080fd5b61104d85828601610fa8565b925050602083013561105e81610ec7565b809150509250929050565b6000806040838503121561107c57600080fd5b823567ffffffffffffffff8082111561109457600080fd5b6110a086838701610fa8565b93506020915081850135818111156110b757600080fd5b85019050601f810186136110ca57600080fd5b80356110d8610fc982610f84565b81815260059190911b820183019083810190888311156110f757600080fd5b928401925b8284101561111e57833561110f81610ec7565b825292840192908401906110fc565b80955050505050509250929050565b60006020828403121561113f57600080fd5b813567ffffffffffffffff81111561115657600080fd5b61116284828501610fa8565b949350505050565b60006020828403121561117c57600080fd5b813560ff81168114610c7257600080fd5b600080604083850312156111a057600080fd5b82359150602083013561105e81610ec7565b600080604083850312156111c557600080fd5b82356111d081610ec7565b91506020830135801515811461105e57600080fd5b600080604083850312156111f857600080fd5b50508035926020909101359150565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60006020828403121561124857600080fd5b8151610c7281610ec7565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b83815273ffffffffffffffffffffffffffffffffffffffff8316602082015260608101600383106112dc577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b826040830152949350505050565b600060208083850312156112fd57600080fd5b825167ffffffffffffffff8082111561131557600080fd5b818501915085601f83011261132957600080fd5b81518181111561133b5761133b610f06565b61136b847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601610f35565b9150808252868482850101111561138157600080fd5b60005b8181101561139f578381018501518382018601528401611384565b50600090820190930192909252509392505050565b6000602082840312156113c657600080fd5b815160038110610c7257600080fd5b6000602082840312156113e757600080fd5b505191905056fea264697066735822122050796a59b30cc47c11708b325b1b3db18a238a077844e89a349fb0e4d2d5e78664736f6c63430008170033

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.