Overview
S Balance
0 S
S Value
$0.00More Info
Private Name Tags
ContractCreator
Loading...
Loading
Contract Name:
QuestBoardRings
Compiler Version
v0.8.20+commit.a1b79de6
Optimization Enabled:
Yes with 9999 runs
Other Settings:
default evmVersion
Contract Source Code (Solidity Standard Json-Input format)
//██████╗ █████╗ ██╗ █████╗ ██████╗ ██╗███╗ ██╗ //██╔══██╗██╔══██╗██║ ██╔══██╗██╔══██╗██║████╗ ██║ //██████╔╝███████║██║ ███████║██║ ██║██║██╔██╗ ██║ //██╔═══╝ ██╔══██║██║ ██╔══██║██║ ██║██║██║╚██╗██║ //██║ ██║ ██║███████╗██║ ██║██████╔╝██║██║ ╚████║ //╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═════╝ ╚═╝╚═╝ ╚═══╝ //SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.20; import "./oz/interfaces/IERC20.sol"; import "./oz/libraries/SafeERC20.sol"; import "./oz/utils/ReentrancyGuard.sol"; import "./libraries/QuestDataTypes.sol"; import "./interfaces/IQuestBoard.sol"; import "./interfaces/IRingsVoter.sol"; import "./MultiMerkleDistributor.sol"; import "./utils/Owner.sol"; import "./libraries/Errors.sol"; /** @title Warden Quest Board V2 - Rings protocol version */ /// @author Paladin /* V2 of Quest Board allowing to blacklist or whitelist veNFTs for Rings protocol and chose between fixed or ranged rewards distribution */ contract QuestBoardRings is IQuestBoard, Owner, ReentrancyGuard { using SafeERC20 for IERC20; // Storage /** @notice Address of the Voter contract */ address public immutable VOTER; /** @notice Seconds in a Week */ uint256 private constant WEEK = 604800; /** @notice 1e18 scale */ uint256 private constant UNIT = 1e18; /** @notice Max BPS value (100%) */ uint256 private constant MAX_BPS = 10000; /** @notice Delay where contract can be unkilled */ uint256 private constant KILL_DELAY = 2 * 604800; //2 weeks /** @notice Max VoterList size */ uint256 private constant MAX_VOTERLIST_SIZE = 10; /** @notice ID for the next Quest to be created */ uint256 public nextID; /** @notice List of Quest (indexed by ID) */ // ID => Quest mapping(uint256 => Quest) public quests; /** @notice List of timestamp periods the Quest is active in */ // QuestID => Periods (timestamps) mapping(uint256 => uint48[]) private questPeriods; /** @notice Mapping of all state for each period of each Quest */ // QuestID => period => QuestDataTypes.PeriodState mapping(uint256 => mapping(uint256 => QuestDataTypes.PeriodState)) public periodStateByQuest; /** @notice Mapping of distributed reward amounts for each period of each Quest */ // QuestID => period => uint256 mapping(uint256 => mapping(uint256 => uint256)) public periodAmountDistributedByQuest; /** @notice Original Reward per period for Rollover Quests */ // ID => amount mapping(uint256 => uint256) public originalRewardPerPeriod; /** @notice Used Reward per period for Rollover Quests (when closing a period) */ // ID => period => amount mapping(uint256 => mapping(uint256 => uint256)) private usedRewardPerPeriod; /** @notice All the Quests present in this period */ // period => array of Quest mapping(uint256 => uint256[]) private questsByPeriod; /** @notice All the Quests present in this period for each gauge */ // gauge => period => array of Quest mapping(address => mapping(uint256 => uint256[])) private questsByGaugeByPeriod; /** @notice Mapping of Distributors used by each Quest to send rewards */ // ID => Distributor mapping(uint256 => address) public questDistributors; /** @notice Amount not distributed, for Quest creators to redeem */ mapping(uint256 => uint256) public questWithdrawableAmount; /** @notice Mapping of voter list (blacklist or whitelist) for each Quest, as NFT IDs */ // ID => VoterList mapping(uint256 => uint256[]) private questVoterList; /** @notice Platform fees ratio (in BPS) */ uint256 public platformFeeRatio = 400; /** @notice Mapping of specific fee ratio for some Quest creators */ // Creator => specific fee mapping(address => uint256) public customPlatformFeeRatio; /** @notice Minimum Objective required */ uint256 public objectiveMinimalThreshold; /** @notice Address of the Chest to receive platform fees */ address public questChest; /** @notice Address of the reward Distributor contract */ address public distributor; /** @notice Mapping of addresses allowed to call manager methods */ mapping(address => bool) private approvedManagers; /** @notice Whitelisted tokens that can be used as reward tokens */ mapping(address => bool) public whitelistedTokens; /** @notice Min rewardPerVote per token (to avoid spam creation of useless Quest) */ mapping(address => uint256) public minRewardPerVotePerToken; /** @notice Boolean, true if the cotnract was killed, stopping main user functions */ bool public isKilled; /** @notice Timestamp when the contract was killed */ uint256 public killTs; // Modifiers /** @notice Check the caller is either the admin or an approved manager */ modifier onlyAllowed(){ if(!approvedManagers[msg.sender] && msg.sender != owner()) revert Errors.CallerNotAllowed(); _; } /** @notice Check that contract was not killed */ modifier isAlive(){ if(isKilled) revert Errors.Killed(); _; } /** @notice Check that contract was initialized */ modifier isInitialized(){ if(distributor == address(0)) revert Errors.NotInitialized(); _; } // Constructor constructor(address _voter, address _chest){ if( _voter == address(0) || _chest == address(0) ) revert Errors.AddressZero(); VOTER = _voter; questChest = _chest; objectiveMinimalThreshold = 1000 * UNIT; } /** * @notice Initialize the contract * @param _distributor Address of the Distributor */ function init(address _distributor) external onlyOwner { if(distributor != address(0)) revert Errors.AlreadyInitialized(); if(_distributor == address(0)) revert Errors.AddressZero(); distributor = _distributor; emit Init(_distributor); } // View Functions /** * @notice Returns the current Period for the contract * @dev Returns the current Period for the contract */ function getCurrentPeriod() public view returns(uint256) { return (block.timestamp / WEEK) * WEEK; } /** * @notice Returns the list of all Quest IDs active on a given period * @dev Returns the list of all Quest IDs active on a given period * @param period Timestamp of the period * @return uint256[] : Quest IDs for the period */ function getQuestIdsForPeriodForGauge(address gauge, uint256 period) external view returns(uint256[] memory) { period = (period / WEEK) * WEEK; return questsByGaugeByPeriod[gauge][period]; } /** * @notice Returns the list of all Quest IDs active on a given period * @dev Returns the list of all Quest IDs active on a given period * @param period Timestamp of the period * @return uint256[] : Quest IDs for the period */ function getQuestIdsForPeriod(uint256 period) external view returns(uint256[] memory) { period = (period / WEEK) * WEEK; return questsByPeriod[period]; } /** * @notice Returns all periods for a Quest * @dev Returns all period timestamps for a Quest ID * @param questID ID of the Quest * @return uint256[] : List of period timestamps */ function getAllPeriodsForQuestId(uint256 questID) external view returns(uint48[] memory) { return questPeriods[questID]; } /** * @notice Returns the number of periods to come for a given Quest * @dev Returns the number of periods to come for a given Quest * @param questID ID of the Quest * @return uint : remaining duration (non active periods) */ function _getRemainingDuration(uint256 questID) internal view returns(uint256) { // Since we have the current period, the start period for the Quest, and each period is 1 WEEK // We can find the number of remaining periods in the Quest simply by dividing the remaining time between // currentPeriod and the last QuestPeriod start, plus 1 WEEK, by a WEEK. // If the current period is the last period of the Quest, we want to return 1 if(questPeriods[questID].length == 0) revert Errors.EmptyQuest(); uint256 lastPeriod = questPeriods[questID][questPeriods[questID].length - 1]; uint256 currentPeriod = getCurrentPeriod(); return lastPeriod < currentPeriod ? 0: ((lastPeriod - currentPeriod) + WEEK) / WEEK; } /** * @notice Returns the address of the Quest creator * @dev Returns the address of the Quest creator * @param questID ID of the Quest * @return address : creator of the Quest */ function getQuestCreator(uint256 questID) external view returns(address){ return quests[questID].creator; } /** * @notice Returns the voter list for a given Quest * @param questID ID of the Quest * @return uint256[] : vote list */ function getQuestVoterList(uint256 questID) external view returns(uint256[] memory){ return questVoterList[questID]; } /** * @notice Returns the current reduced bias for a given gauge (based on a Quest's voter list) * @param questID ID of the Quest * @return uint256 : current reduced bias */ function getCurrentReducedBias(uint256 questID) external view returns(uint256) { uint256 nextPeriod = getCurrentPeriod() + WEEK; return getReducedBias(nextPeriod, questID); } /** * @notice Returns the reduced bias for a given gauge for a given period (based on a Quest's voter list) * @param period timestamp of the period * @param questID ID of the Quest * @return uint256 : current reduced bias */ function getReducedBias(uint256 period, uint256 questID) public view returns(uint256) { uint256[] memory voterList = questVoterList[questID]; address gauge = quests[questID].gauge; QuestDataTypes.QuestVoteType questType = quests[questID].types.voteType; IRingsVoter voter = IRingsVoter(VOTER); uint256 voterListSumBias; uint256 voterListLength = voterList.length; for(uint256 i; i < voterListLength;) { uint256 userVotes = voter.getNftVotesOnGaugeAtPeriod(voterList[i], gauge, period); voterListSumBias += userVotes; unchecked { i++; } } // For a WHITELIST type, simply return the sum of voters bias if(questType == QuestDataTypes.QuestVoteType.WHITELIST) return voterListSumBias; // Get the bias of the Gauge for the given period uint256 periodAdjustedBias = voter.getGaugeVotesAtPeriod(gauge, period); // If the Quest is a Blacklist, we need to remove the bias of the voters if(questType == QuestDataTypes.QuestVoteType.BLACKLIST) { periodAdjustedBias = voterListSumBias >= periodAdjustedBias ? 0 : periodAdjustedBias - voterListSumBias; } return periodAdjustedBias; } // Functions /** * @notice Creates a fixed rewards Quest based on the given parameters * @dev Creates a Quest based on the given parameters & the given types with the Fixed Rewards type * @param gauge Address of the gauge * @param rewardToken Address of the reward token * @param startNextPeriod (bool) true to start the Quest the next period * @param duration Duration of the Quest (in weeks) * @param rewardPerVote Amount of reward/vote (in wei) * @param totalRewardAmount Total amount of rewards available for the full Quest duration * @param feeAmount Amount of fees paid at creation * @param voteType Vote type for the Quest * @param closeType Close type for the Quest * @param voterList List of veNFT IDs for the Quest (to be used for Blacklist or Whitelist) * @return uint256 : ID of the newly created Quest */ function createFixedQuest( address gauge, address rewardToken, bool startNextPeriod, uint48 duration, uint256 rewardPerVote, uint256 totalRewardAmount, uint256 feeAmount, QuestDataTypes.QuestVoteType voteType, QuestDataTypes.QuestCloseType closeType, uint256[] calldata voterList ) external nonReentrant isAlive isInitialized returns(uint256) { // Set the Quest Types for the new Quest QuestTypes memory types = QuestTypes({ voteType: voteType, rewardsType: QuestDataTypes.QuestRewardsType.FIXED, closeType: closeType }); return _createQuest( gauge, rewardToken, types, startNextPeriod, duration, rewardPerVote, rewardPerVote, totalRewardAmount, feeAmount, voterList ); } /** * @notice Creates a ranged rewards Quest based on the given parameters * @dev Creates a Quest based on the given parameters & the given types with the Ranged Rewards type * @param gauge Address of the gauge * @param rewardToken Address of the reward token * @param startNextPeriod (bool) true to start the Quest the next period * @param duration Duration of the Quest (in weeks) * @param minRewardPerVote Minimum amount of reward/vote (in wei) * @param maxRewardPerVote Maximum amount of reward/vote (in wei) * @param totalRewardAmount Total amount of rewards available for the full Quest duration * @param feeAmount Amount of fees paid at creation * @param voteType Vote type for the Quest * @param closeType Close type for the Quest * @param voterList List of veNFT IDs for the Quest (to be used for Blacklist or Whitelist) * @return uint256 : ID of the newly created Quest */ function createRangedQuest( address gauge, address rewardToken, bool startNextPeriod, uint48 duration, uint256 minRewardPerVote, uint256 maxRewardPerVote, uint256 totalRewardAmount, uint256 feeAmount, QuestDataTypes.QuestVoteType voteType, QuestDataTypes.QuestCloseType closeType, uint256[] calldata voterList ) external nonReentrant isAlive isInitialized returns(uint256) { // Set the Quest Types for the new Quest QuestTypes memory types = QuestTypes({ voteType: voteType, rewardsType: QuestDataTypes.QuestRewardsType.RANGE, closeType: closeType }); return _createQuest( gauge, rewardToken, types, startNextPeriod, duration, minRewardPerVote, maxRewardPerVote, totalRewardAmount, feeAmount, voterList ); } /** * @notice Creates a Quest based on the given parameters * @dev Creates a Quest based on the given parameters & the given types * @param gauge Address of the gauge * @param rewardToken Address of the reward token * @param types Quest Types (Rewards, Vote & Close) * @param startNextPeriod (bool) true to start the Quest the next period * @param duration Duration of the Quest (in weeks) * @param minRewardPerVote Minimum amount of reward/vote (in wei) * @param maxRewardPerVote Maximum amount of reward/vote (in wei) * @param totalRewardAmount Total amount of rewards available for the full Quest duration * @param feeAmount Amount of fees paid at creation * @param voterList List of veNFT IDs for the Quest (to be used for Blacklist or Whitelist) * @return newQuestID (uint256) : ID of the newly created Quest */ function _createQuest( address gauge, address rewardToken, QuestTypes memory types, bool startNextPeriod, uint48 duration, uint256 minRewardPerVote, uint256 maxRewardPerVote, uint256 totalRewardAmount, uint256 feeAmount, uint256[] calldata voterList ) internal returns(uint256 newQuestID) { // Local memory variables CreateVars memory vars; vars.creator = msg.sender; // Check all parameters if(gauge == address(0) || rewardToken == address(0)) revert Errors.AddressZero(); if(!(IRingsVoter(VOTER).isGauge(gauge) && IRingsVoter(VOTER).isAlive(gauge))) revert Errors.InvalidGauge(); if(!whitelistedTokens[rewardToken]) revert Errors.TokenNotWhitelisted(); if(duration == 0) revert Errors.IncorrectDuration(); if(minRewardPerVote == 0 || maxRewardPerVote == 0 || totalRewardAmount == 0 || feeAmount == 0) revert Errors.NullAmount(); if(minRewardPerVote < minRewardPerVotePerToken[rewardToken]) revert Errors.RewardPerVoteTooLow(); if(minRewardPerVote > maxRewardPerVote) revert Errors.MinValueOverMaxValue(); if(types.rewardsType == QuestDataTypes.QuestRewardsType.FIXED && minRewardPerVote != maxRewardPerVote) revert Errors.InvalidQuestType(); if((totalRewardAmount * _getFeeRatio(msg.sender))/MAX_BPS != feeAmount) revert Errors.IncorrectFeeAmount(); if(types.closeType == QuestDataTypes.QuestCloseType.DISTRIBUTE) revert Errors.QuestTypeForbidden(); // Calculate the reward per period, and the max vote objective per period vars.rewardPerPeriod = totalRewardAmount / duration; vars.minObjective = (vars.rewardPerPeriod * UNIT) / maxRewardPerVote; if(vars.minObjective < objectiveMinimalThreshold) revert Errors.ObjectiveTooLow(); // Pull all the rewards in this contract IERC20(rewardToken).safeTransferFrom(vars.creator, address(this), totalRewardAmount); // And transfer the fees from the Quest creator to the Chest contract IERC20(rewardToken).safeTransferFrom(vars.creator, questChest, feeAmount); // Get the period when the Quest starts (current or next period) vars.startPeriod = getCurrentPeriod(); if(startNextPeriod) vars.startPeriod += WEEK; // Get the ID for that new Quest and increment the nextID counter newQuestID = nextID; unchecked{ ++nextID; } // Fill the Quest struct data quests[newQuestID] = Quest({ creator: vars.creator, rewardToken: rewardToken, gauge: gauge, duration: duration, periodStart: safe48(vars.startPeriod), totalRewardAmount: totalRewardAmount, rewardAmountPerPeriod: vars.rewardPerPeriod, minRewardPerVote: minRewardPerVote, maxRewardPerVote: maxRewardPerVote, minObjectiveVotes: vars.minObjective, maxObjectiveVotes: (vars.rewardPerPeriod * UNIT) / minRewardPerVote, types: types }); //Set the current Distributor as the one to receive the rewards for users for that Quest questDistributors[newQuestID] = distributor; // Iterate on periods based on Quest duration vars.periodIterator = vars.startPeriod; for(uint256 i; i < duration;){ // Add the Quest on the list of Quests active on the period questsByPeriod[vars.periodIterator].push(newQuestID); questsByGaugeByPeriod[gauge][vars.periodIterator].push(newQuestID); // Set the state of the period as ACTIVE periodStateByQuest[newQuestID][vars.periodIterator] = QuestDataTypes.PeriodState.ACTIVE; // And add the period in the list of periods of the Quest questPeriods[newQuestID].push(safe48(vars.periodIterator)); vars.periodIterator += WEEK; unchecked{ ++i; } } vars.voterLength = voterList.length; if(vars.voterLength > 0) { if(vars.voterLength > MAX_VOTERLIST_SIZE) revert Errors.MaxListSize(); for(uint256 i; i < vars.voterLength;){ _addToVoterList(newQuestID, voterList[i]); unchecked { ++i; } } } if(types.closeType == QuestDataTypes.QuestCloseType.ROLLOVER){ originalRewardPerPeriod[newQuestID] = vars.rewardPerPeriod; } // Add that Quest & the reward token in the Distributor if(!MultiMerkleDistributor(distributor).addQuest(newQuestID, rewardToken)) revert Errors.DisitributorFail(); emit NewQuest( newQuestID, msg.sender, gauge, rewardToken, duration, vars.startPeriod ); } /** * @notice Adds a given address to a Quest's voter list * @dev Adds a given address to a Quest's voter list * @param questID ID of the Quest * @param nftID ID of the veNFT */ function _addToVoterList(uint256 questID, uint256 nftID) internal { //We don't want to have 2x the same address in the list uint256[] memory _list = questVoterList[questID]; uint256 length = _list.length; for(uint256 i; i < length;){ if(_list[i] == nftID) revert Errors.AlreadyListed(); unchecked { ++i; } } questVoterList[questID].push(nftID); } /** * @notice Increases the duration of a Quest * @dev Adds more QuestPeriods and extends the duration of a Quest * @param questID ID of the Quest * @param addedDuration Number of period to add * @param addedRewardAmount Amount of reward to add for the new periods (in wei) * @param feeAmount Platform fees amount (in wei) */ function extendQuestDuration( uint256 questID, uint48 addedDuration, uint256 addedRewardAmount, uint256 feeAmount ) external nonReentrant isAlive isInitialized { // Local memory variables ExtendVars memory vars; if(questID >= nextID) revert Errors.InvalidQuestID(); if(msg.sender != quests[questID].creator) revert Errors.CallerNotAllowed(); if(addedRewardAmount == 0 || feeAmount == 0) revert Errors.NullAmount(); if(addedDuration == 0) revert Errors.IncorrectAddDuration(); // We take data from the last period of the Quest to account for any other changes in the Quest parameters if(questPeriods[questID].length == 0) revert Errors.EmptyQuest(); vars.lastPeriod = questPeriods[questID][questPeriods[questID].length - 1]; if(quests[questID].periodStart >= block.timestamp) revert Errors.QuestNotStarted(); if(vars.lastPeriod < getCurrentPeriod()) revert Errors.ExpiredQuest(); // Check that the given amounts are correct vars.rewardPerPeriod = quests[questID].rewardAmountPerPeriod; if(quests[questID].types.closeType == QuestDataTypes.QuestCloseType.ROLLOVER){ vars.rewardPerPeriod = originalRewardPerPeriod[questID]; } if((vars.rewardPerPeriod * addedDuration) != addedRewardAmount) revert Errors.IncorrectAddedRewardAmount(); if((addedRewardAmount * _getFeeRatio(msg.sender))/MAX_BPS != feeAmount) revert Errors.IncorrectFeeAmount(); vars.gauge = quests[questID].gauge; vars.rewardToken = quests[questID].rewardToken; // Pull all the rewards in this contract IERC20(vars.rewardToken).safeTransferFrom(msg.sender, address(this), addedRewardAmount); // And transfer the fees from the Quest creator to the Chest contract IERC20(vars.rewardToken).safeTransferFrom(msg.sender, questChest, feeAmount); vars.periodIterator = ((vars.lastPeriod + WEEK) / WEEK) * WEEK; // Update the Quest struct with added reward admounts & added duration quests[questID].totalRewardAmount += addedRewardAmount; quests[questID].duration += addedDuration; // Add QuestPeriods for the new added duration for(uint256 i; i < addedDuration;){ questsByPeriod[vars.periodIterator].push(questID); questsByGaugeByPeriod[quests[questID].gauge][vars.periodIterator].push(questID); questPeriods[questID].push(safe48(vars.periodIterator)); // Set the state of the period as ACTIVE periodStateByQuest[questID][vars.periodIterator] = QuestDataTypes.PeriodState.ACTIVE; vars.periodIterator += WEEK; unchecked{ ++i; } } emit ExtendQuestDuration(questID, addedDuration, addedRewardAmount); } /** * @notice Updates the parametes of the Quest * @dev Updates the reward/vote parameters, allowing to update the Quest objectives too * @param questID ID of the Quest * @param newMinRewardPerVote New min reward/vote value (in wei) * @param newMaxRewardPerVote New max reward/vote value (in wei) * @param addedPeriodRewardAmount Amount of reward to add for each period (in wei) * @param addedTotalRewardAmount Amount of reward to add for all periods (in wei) * @param feeAmount Platform fees amount (in wei) */ function updateQuestParameters( uint256 questID, uint256 newMinRewardPerVote, uint256 newMaxRewardPerVote, uint256 addedPeriodRewardAmount, uint256 addedTotalRewardAmount, uint256 feeAmount ) external nonReentrant isAlive isInitialized { // Local memory variables UpdateVars memory vars; Quest storage _quest = quests[questID]; if(questID >= nextID) revert Errors.InvalidQuestID(); if(msg.sender != _quest.creator) revert Errors.CallerNotAllowed(); if(newMinRewardPerVote == 0 || newMaxRewardPerVote == 0) revert Errors.NullAmount(); if(newMinRewardPerVote > newMaxRewardPerVote) revert Errors.MinValueOverMaxValue(); if(_quest.types.rewardsType == QuestDataTypes.QuestRewardsType.FIXED && newMinRewardPerVote != newMaxRewardPerVote) revert Errors.InvalidQuestType(); // Check the reamining duration, and that the given reward amounts are correct vars.remainingDuration = _getRemainingDuration(questID); //Also handles the Empty Quest check if(vars.remainingDuration == 0) revert Errors.ExpiredQuest(); if(_quest.periodStart >= block.timestamp) revert Errors.QuestNotStarted(); if((addedPeriodRewardAmount * vars.remainingDuration) != addedTotalRewardAmount) revert Errors.IncorrectAddedRewardAmount(); if((addedTotalRewardAmount * _getFeeRatio(msg.sender))/MAX_BPS != feeAmount) revert Errors.IncorrectFeeAmount(); // The new min reward amount must be higher vars.currentPeriod = getCurrentPeriod(); if(newMinRewardPerVote < _quest.minRewardPerVote) revert Errors.LowerRewardPerVote(); // Get the amount of reward for each period vars.newRewardPerPeriod = _quest.rewardAmountPerPeriod + addedPeriodRewardAmount; if(quests[questID].types.closeType == QuestDataTypes.QuestCloseType.ROLLOVER){ originalRewardPerPeriod[questID] += addedPeriodRewardAmount; } // Calculate the new max vote objective, and the min vote objective based on the Quest Rewards type vars.newMaxObjective = (vars.newRewardPerPeriod * UNIT) / newMinRewardPerVote; vars.newMinObjective; if(_quest.types.rewardsType == QuestDataTypes.QuestRewardsType.RANGE) { vars.newMinObjective = (vars.newRewardPerPeriod * UNIT) / newMaxRewardPerVote; } else { vars.newMinObjective = vars.newMaxObjective; } if(vars.newMinObjective < _quest.minObjectiveVotes) revert Errors.NewObjectiveTooLow(); if(addedTotalRewardAmount > 0) { address rewardToken = _quest.rewardToken; // Pull all the rewards in this contract IERC20(rewardToken).safeTransferFrom(msg.sender, address(this), addedTotalRewardAmount); // And transfer the fees from the Quest creator to the Chest contract IERC20(rewardToken).safeTransferFrom(msg.sender, questChest, feeAmount); } // Update the Quest struct with the added reward amount _quest.totalRewardAmount += addedTotalRewardAmount; _quest.minRewardPerVote = newMinRewardPerVote; _quest.maxRewardPerVote = newMaxRewardPerVote; _quest.minObjectiveVotes = vars.newMinObjective; _quest.maxObjectiveVotes = vars.newMaxObjective; _quest.rewardAmountPerPeriod = vars.newRewardPerPeriod; emit UpdateQuestParameters( questID, vars.currentPeriod, newMinRewardPerVote, newMaxRewardPerVote, addedPeriodRewardAmount ); } /** * @notice Adds a given list of addresses to a Quest's voter list * @param questID ID of the Quest * @param nftIDs list of veNFT IDs */ function addToVoterList(uint256 questID, uint256[] calldata nftIDs) external { uint256 length = nftIDs.length; if(length == 0) revert Errors.EmptyArray(); if(quests[questID].gauge == address(0)) revert Errors.InvalidQuestID(); if(msg.sender != quests[questID].creator) revert Errors.CallerNotAllowed(); if(length + questVoterList[questID].length > MAX_VOTERLIST_SIZE) revert Errors.MaxListSize(); for(uint256 i = 0; i < length;){ _addToVoterList(questID, nftIDs[i]); unchecked { ++i; } } emit VoterListUpdated(questID); } /** * @notice Removes a given address from a Quest's voter list * @param questID ID of the Quest * @param nftID ID of the veNFT */ function removeFromVoterList(uint256 questID, uint256 nftID) external { if(quests[questID].gauge == address(0)) revert Errors.InvalidQuestID(); if(msg.sender != quests[questID].creator) revert Errors.CallerNotAllowed(); uint256[] memory _list = questVoterList[questID]; uint256 length = _list.length; for(uint256 i; i < length;){ if(_list[i] == nftID){ if(i != length - 1){ questVoterList[questID][i] = _list[length - 1]; } questVoterList[questID].pop(); emit VoterListUpdated(questID); return; } unchecked { ++i; } } } /** * @notice Withdraw all undistributed rewards from Closed Quest Periods * @dev Withdraw all undistributed rewards from Closed Quest Periods * @param questID ID of the Quest * @param recipient Address to send the reward tokens to */ function withdrawUnusedRewards(uint256 questID, address recipient) external nonReentrant isAlive isInitialized { if(questID >= nextID) revert Errors.InvalidQuestID(); if(msg.sender != quests[questID].creator) revert Errors.CallerNotAllowed(); if(recipient == address(0)) revert Errors.AddressZero(); // Total amount available to withdraw uint256 withdrawAmount = questWithdrawableAmount[questID]; questWithdrawableAmount[questID] = 0; // If there is a non null amount of token to withdraw, execute a transfer if(withdrawAmount != 0){ address rewardToken = quests[questID].rewardToken; IERC20(rewardToken).safeTransfer(recipient, withdrawAmount); emit WithdrawUnusedRewards(questID, recipient, withdrawAmount); } } function multipleWithdrawUnusedRewards(uint256[] calldata questIDs, address recipient) external nonReentrant isAlive isInitialized { uint256 length = questIDs.length; if(length == 0) revert Errors.EmptyArray(); if(recipient == address(0)) revert Errors.AddressZero(); address lastToken; uint256 transferAmount; for(uint256 i; i < length;){ if(questIDs[i] >= nextID) revert Errors.InvalidQuestID(); if(msg.sender != quests[questIDs[i]].creator) revert Errors.CallerNotAllowed(); // Total amount available to withdraw uint256 withdrawAmount = questWithdrawableAmount[questIDs[i]]; questWithdrawableAmount[questIDs[i]] = 0; // If there is a non null amount of token to withdraw, execute a transfer if(withdrawAmount != 0){ address rewardToken = quests[questIDs[i]].rewardToken; if(rewardToken != lastToken){ if(lastToken != address(0)){ IERC20(lastToken).safeTransfer(recipient, transferAmount); transferAmount = 0; } lastToken = rewardToken; transferAmount += withdrawAmount; } else { transferAmount += withdrawAmount; } emit WithdrawUnusedRewards(questIDs[i], recipient, withdrawAmount); } unchecked{ ++i; } } if(lastToken != address(0) && transferAmount != 0){ IERC20(lastToken).safeTransfer(recipient, transferAmount); } } /** * @notice Emergency withdraws all undistributed rewards from Closed Quest Periods & all rewards for Active Periods * @dev Emergency withdraws all undistributed rewards from Closed Quest Periods & all rewards for Active Periods * @param questID ID of the Quest * @param recipient Address to send the reward tokens to */ function emergencyWithdraw(uint256 questID, address recipient) external nonReentrant { if(!isKilled) revert Errors.NotKilled(); if(block.timestamp < killTs + KILL_DELAY) revert Errors.KillDelayNotExpired(); if(questID >= nextID) revert Errors.InvalidQuestID(); if(msg.sender != quests[questID].creator) revert Errors.CallerNotAllowed(); if(recipient == address(0)) revert Errors.AddressZero(); // Total amount to emergency withdraw uint256 withdrawAmount = questWithdrawableAmount[questID]; questWithdrawableAmount[questID] = 0; uint48[] memory _questPeriods = questPeriods[questID]; uint256 length = _questPeriods.length; Quest storage _questData = quests[questID]; for(uint256 i; i < length;){ // For ACTIVE periods if(periodStateByQuest[questID][_questPeriods[i]] == QuestDataTypes.PeriodState.ACTIVE){ // For the active period, and the next ones, withdraw the total reward amount withdrawAmount += _questData.rewardAmountPerPeriod; } unchecked{ ++i; } } _questData.rewardAmountPerPeriod = 0; // If the total amount to emergency withdraw is non_null, execute a transfer if(withdrawAmount != 0){ IERC20(quests[questID].rewardToken).safeTransfer(recipient, withdrawAmount); emit EmergencyWithdraw(questID, recipient, withdrawAmount); } } /** * @notice Get the fee ratio for a given Quest creator * @dev Returns the custom fee ratio for a Quest creator if set, otherwise returns the general fee ratio * @param questCreator address of the Quest creator * @return uint256 : fee ratio */ function _getFeeRatio(address questCreator) internal view returns(uint256) { return customPlatformFeeRatio[questCreator] != 0 ? customPlatformFeeRatio[questCreator] : platformFeeRatio; } // Manager functions /** * @notice Gets the amount of rewards to be distributed for the period * @dev Gets the amount of rewards to be distributed for the * @param questRewardType Rewards type for the Quest * @param periodBias Bias of the gauge (reduced if nedded) for the given period * @param _questData Data for the Quest * @return uint256 : Amount to be distributed */ function _getDistributionAmount( QuestDataTypes.QuestRewardsType questRewardType, uint256 periodBias, Quest memory _questData ) internal pure returns(uint256) { // Here, if the Gauge Bias is equal or greater than the objective, // set all the period reward to be distributed. // If the bias is less, we take that bias, and calculate the amount of rewards based // on the rewardPerVote & the Gauge bias // If the votes received exceed the max objective of the Quest (for both types) // Distribute all the rewards for the period if(periodBias >= _questData.maxObjectiveVotes) return _questData.rewardAmountPerPeriod; if(questRewardType == QuestDataTypes.QuestRewardsType.FIXED) { return (periodBias * _questData.minRewardPerVote) / UNIT; } else { // For QuestDataTypes.QuestRewardsType.RANGE // If the bias is under the minimum objective, use max reward/vote if(periodBias <= _questData.minObjectiveVotes) return (periodBias * _questData.maxRewardPerVote) / UNIT; else return _questData.rewardAmountPerPeriod; } } /** * @notice Handles the Quest period undistributed rewards * @dev Handles the Quest period undistributed rewards based on the Quest Close type * @param questID ID of the Quest * @param currentPeriod Timestamp of the current period * @param questCloseType Close type for the Quest * @param undistributedAmount Amount of token not distributed for voter rewards */ function _handleUndistributedRewards( uint256 questID, uint256 currentPeriod, QuestDataTypes.QuestCloseType questCloseType, uint256 undistributedAmount ) internal { if(questCloseType == QuestDataTypes.QuestCloseType.ROLLOVER) { // Since this type is only allowed for FIXED Rewards Quests // We simply recalculate the next period reward/vote based on the current Objective uint256 nextPeriod = currentPeriod + WEEK; // If not the last period if(nextPeriod > questPeriods[questID][questPeriods[questID].length - 1]) { // This the Quest last period, no period to rollover to questWithdrawableAmount[questID] += undistributedAmount; return; } Quest storage _questData = quests[questID]; usedRewardPerPeriod[questID][currentPeriod] = _questData.rewardAmountPerPeriod; // Calculate the new period parameters by adding undistributed rewards to the base period reward amount // or reset to the original reward amount if the undistributed amount is 0 // & update the next period parameters based on new calculated parameters uint256 newRewardPerPeriod = originalRewardPerPeriod[questID]; if(undistributedAmount > 0) { newRewardPerPeriod += (undistributedAmount); } uint256 newMinRewardPerVote = (newRewardPerPeriod * UNIT) / _questData.maxObjectiveVotes; uint256 newMaxRewardPerVote = (newRewardPerPeriod * UNIT) / _questData.minObjectiveVotes; _questData.minRewardPerVote = newMinRewardPerVote; _questData.maxRewardPerVote = newMaxRewardPerVote; _questData.rewardAmountPerPeriod = newRewardPerPeriod; emit RewardsRollover(questID, newRewardPerPeriod, newMinRewardPerVote, newMaxRewardPerVote); } else { // For QuestDataTypes.QuestCloseType.NORMAL questWithdrawableAmount[questID] += undistributedAmount; } } /** * @notice Closes the Period, and all QuestPeriods for this period * @dev Closes all QuestPeriod for the given period, calculating rewards to distribute & send them to distributor * @param period Timestamp of the period */ function _closeQuestPeriod(uint256 period, uint256 questID) internal returns(bool) { // We check that this period was not already closed if(periodStateByQuest[questID][period] != QuestDataTypes.PeriodState.ACTIVE) return false; Quest memory _quest = quests[questID]; periodStateByQuest[questID][period] = QuestDataTypes.PeriodState.CLOSED; // Get the bias of the Gauge for the end of the period uint256 periodAdjustedBias = getReducedBias( period + WEEK, questID ); uint256 undistributedAmount; if(periodAdjustedBias == 0) { // Because we don't want to divide by 0 here since the bias is 0, we consider 0% completion // => no rewards to be distributed // We do not change _questPeriod.rewardAmountDistributed since the default value is already 0 undistributedAmount = _quest.rewardAmountPerPeriod; } else{ // Get the amount of rewards to be distributed uint256 distributionAmount = _getDistributionAmount(_quest.types.rewardsType, periodAdjustedBias, _quest); periodAmountDistributedByQuest[questID][period] = distributionAmount; // And the rest is set as withdrawable amount, that the Quest creator can retrieve undistributedAmount = _quest.rewardAmountPerPeriod - distributionAmount; // Send the rewards to be distributed to the Distrubutor address questDistributor = questDistributors[questID]; if(!MultiMerkleDistributor(questDistributor).addQuestPeriod(questID, period, distributionAmount)) revert Errors.DisitributorFail(); IERC20(_quest.rewardToken).safeTransfer(questDistributor, distributionAmount); } // Handle the undistributed rewards based on the Quest Close type _handleUndistributedRewards(questID, period, _quest.types.closeType, undistributedAmount); emit PeriodClosed(questID, period); return true; } /** * @notice Closes the Period, and all QuestPeriods for this period * @dev Closes all QuestPeriod for the given period, calculating rewards to distribute & send them to distributor * @param period Timestamp of the period */ function closeQuestPeriod(uint256 period) external nonReentrant isAlive isInitialized onlyAllowed returns(uint256 closed, uint256 skipped) { period = (period / WEEK) * WEEK; if(period == 0) revert Errors.InvalidPeriod(); if(period >= getCurrentPeriod()) revert Errors.PeriodStillActive(); if(questsByPeriod[period].length == 0) revert Errors.EmptyPeriod(); uint256[] memory questsForPeriod = questsByPeriod[period]; // For each QuestPeriod uint256 length = questsForPeriod.length; for(uint256 i = 0; i < length;){ bool result = _closeQuestPeriod(period, questsForPeriod[i]); if(result) closed++; else skipped++; unchecked{ ++i; } } } /** * @notice Closes the given QuestPeriods for the Period * @dev Closes the given QuestPeriods for the Period, calculating rewards to distribute & send them to distributor * @param period Timestamp of the period * @param questIDs List of the Quest IDs to close */ function closePartOfQuestPeriod(uint256 period, uint256[] calldata questIDs) external nonReentrant isAlive isInitialized onlyAllowed returns(uint256 closed, uint256 skipped) { period = (period / WEEK) * WEEK; uint256 questIDLength = questIDs.length; if(questIDLength == 0) revert Errors.EmptyArray(); if(period == 0) revert Errors.InvalidPeriod(); if(period >= getCurrentPeriod()) revert Errors.PeriodStillActive(); if(questsByPeriod[period].length == 0) revert Errors.EmptyPeriod(); // For each QuestPeriod for the given Quest IDs list for(uint256 i = 0; i < questIDLength;){ bool result = _closeQuestPeriod(period, questIDs[i]); if(result) closed++; else skipped++; unchecked{ ++i; } } } /** * @notice Sets a list of QuestPeriods as disitrbuted, and adds the MerkleRoot to the Distributor contract for each * @dev Loop and internal call to _addMerkleRoot() * @param questIDs List of Quest IDs * @param period Timestamp of the period * @param totalAmounts List of sums of all rewards for the Merkle Tree * @param merkleRoots List of MerkleRoots to add */ function addMultipleMerkleRoot( uint256[] calldata questIDs, uint256 period, uint256[] calldata totalAmounts, bytes32[] calldata merkleRoots ) external nonReentrant isAlive isInitialized onlyAllowed { period = (period / WEEK) * WEEK; uint256 length = questIDs.length; if(length != merkleRoots.length) revert Errors.InequalArraySizes(); if(length != totalAmounts.length) revert Errors.InequalArraySizes(); for(uint256 i = 0; i < length;){ if(questIDs[i] >= nextID) revert Errors.InvalidQuestID(); if(merkleRoots[i] == 0) revert Errors.EmptyMerkleRoot(); if(totalAmounts[i] == 0) revert Errors.NullAmount(); // This also allows to check if the given period is correct => If not, the currentState is never set to CLOSED for the QuestPeriod if(periodStateByQuest[questIDs[i]][period] != QuestDataTypes.PeriodState.CLOSED) revert Errors.PeriodNotClosed(); // Add the MerkleRoot to the Distributor & set the QuestPeriod as DISTRIBUTED if(!MultiMerkleDistributor(questDistributors[questIDs[i]]).updateQuestPeriod(questIDs[i], period, totalAmounts[i], merkleRoots[i])) revert Errors.DisitributorFail(); periodStateByQuest[questIDs[i]][period] = QuestDataTypes.PeriodState.DISTRIBUTED; unchecked{ ++i; } } } /** * @notice Whitelists a list of reward tokens * @dev Whitelists a list of reward tokens * @param newTokens List of reward tokens addresses * @param minRewardPerVotes List of minimal threshold of reward per vote for the reward token */ function whitelistMultipleTokens(address[] calldata newTokens, uint256[] calldata minRewardPerVotes) external onlyAllowed { uint256 length = newTokens.length; if(length == 0) revert Errors.EmptyArray(); if(length != minRewardPerVotes.length) revert Errors.InequalArraySizes(); for(uint256 i = 0; i < length;){ if(newTokens[i] == address(0)) revert Errors.AddressZero(); if(minRewardPerVotes[i] == 0) revert Errors.InvalidParameter(); whitelistedTokens[newTokens[i]] = true; minRewardPerVotePerToken[newTokens[i]] = minRewardPerVotes[i]; emit WhitelistToken(newTokens[i], minRewardPerVotes[i]); unchecked{ ++i; } } } /** * @notice Updates a reward token parameters * @dev Updates a reward token parameters * @param newToken Address of the reward token * @param newMinRewardPerVote New minimal threshold of reward per vote for the reward token */ function updateRewardToken(address newToken, uint256 newMinRewardPerVote) external onlyAllowed { if(!whitelistedTokens[newToken]) revert Errors.TokenNotWhitelisted(); if(newMinRewardPerVote == 0) revert Errors.InvalidParameter(); minRewardPerVotePerToken[newToken] = newMinRewardPerVote; emit UpdateRewardToken(newToken, newMinRewardPerVote); } // Admin functions function _getPastDistributionAmountRollover( QuestDataTypes.QuestRewardsType questRewardType, uint256 periodBias, uint256 pastRewardPerPeriod, Quest memory _questData ) internal pure returns(uint256) { if(periodBias == 0) return 0; if(periodBias >= _questData.maxObjectiveVotes) return pastRewardPerPeriod; if(questRewardType == QuestDataTypes.QuestRewardsType.FIXED) { uint256 pastMinRewardPerVote = (pastRewardPerPeriod * UNIT) / _questData.maxObjectiveVotes; return (periodBias * pastMinRewardPerVote) / UNIT; } else { // For QuestDataTypes.QuestRewardsType.RANGE // If the bias is under the minimum objective, use max reward/vote uint256 pastMaxRewardPerVote = (pastRewardPerPeriod * UNIT) / _questData.minObjectiveVotes; if(periodBias <= _questData.minObjectiveVotes) return (periodBias * pastMaxRewardPerVote) / UNIT; else return pastRewardPerPeriod; } } /** * @notice Approves a new address as manager * @dev Approves a new address as manager * @param period Timestamp fo the period to fix * @param questID ID of the Quest * @param correctReducedBias Currect bias to be used for the Quest period */ function fixQuestPeriodBias(uint256 period, uint256 questID, uint256 correctReducedBias) external nonReentrant isAlive onlyOwner { period = (period / WEEK) * WEEK; if(questID >= nextID) revert Errors.InvalidQuestID(); if(distributor == address(0)) revert Errors.NoDistributorSet(); if(period == 0) revert Errors.InvalidPeriod(); if(period > getCurrentPeriod()) revert Errors.InvalidPeriod(); Quest storage _quest = quests[questID]; // This also allows to check if the given period is correct => If not, the currentState is never set to CLOSED for the QuestPeriod if(periodStateByQuest[questID][period] != QuestDataTypes.PeriodState.CLOSED) revert Errors.PeriodNotClosed(); uint256 previousRewardAmountDistributed = periodAmountDistributedByQuest[questID][period]; uint256 previousWithdrawableAmount = _quest.rewardAmountPerPeriod - previousRewardAmountDistributed; address questDistributor = questDistributors[questID]; if(correctReducedBias == 0 && _quest.types.closeType != QuestDataTypes.QuestCloseType.ROLLOVER) { // Set rewardAmountDistributed back to 0, get all rewards token back to the Board periodAmountDistributedByQuest[questID][period] = 0; if(!MultiMerkleDistributor(questDistributor).fixQuestPeriod(questID, period, 0)) revert Errors.DisitributorFail(); if(_quest.types.closeType == QuestDataTypes.QuestCloseType.NORMAL) { questWithdrawableAmount[questID] = questWithdrawableAmount[questID] + _quest.rewardAmountPerPeriod - previousWithdrawableAmount; } else { _handleUndistributedRewards(questID, period, _quest.types.closeType, previousRewardAmountDistributed); } } else{ uint256 newToDistributeAmount = _getDistributionAmount(_quest.types.rewardsType, correctReducedBias, _quest); if(_quest.types.closeType == QuestDataTypes.QuestCloseType.ROLLOVER) { // Re-calculate the distribution amount for the period based on past data uint256 pastRewardPerPeriod = usedRewardPerPeriod[questID][period]; newToDistributeAmount = _getPastDistributionAmountRollover(_quest.types.rewardsType, correctReducedBias, pastRewardPerPeriod, _quest); // We simply recalculate the next period reward/vote based on the current Objective uint256 nextPeriod = period + WEEK; uint256 lastPeriod = questPeriods[questID][questPeriods[questID].length - 1]; // If not the last period if(nextPeriod > lastPeriod) { // This the Quest last period, no period to rollover to questWithdrawableAmount[questID] = questWithdrawableAmount[questID] + (pastRewardPerPeriod - newToDistributeAmount) - previousWithdrawableAmount; } else { uint256 newRewardPerPeriod = newToDistributeAmount > previousRewardAmountDistributed ? _quest.rewardAmountPerPeriod - ((newToDistributeAmount - previousRewardAmountDistributed)) : _quest.rewardAmountPerPeriod + ((previousRewardAmountDistributed - newToDistributeAmount)); uint256 newMinRewardPerVote = (newRewardPerPeriod * UNIT) / _quest.maxObjectiveVotes; uint256 newMaxRewardPerVote = (newRewardPerPeriod * UNIT) / _quest.minObjectiveVotes; _quest.minRewardPerVote = newMinRewardPerVote; _quest.maxRewardPerVote = newMaxRewardPerVote; _quest.rewardAmountPerPeriod = newRewardPerPeriod; emit RewardsRollover(questID, newRewardPerPeriod, newMinRewardPerVote, newMaxRewardPerVote); } if(newToDistributeAmount > previousRewardAmountDistributed){ uint256 missingAmount = newToDistributeAmount - previousRewardAmountDistributed; IERC20(_quest.rewardToken).safeTransfer(questDistributor, missingAmount); } } else { // For QuestDataTypes.QuestCloseType.NORMAL questWithdrawableAmount[questID] = questWithdrawableAmount[questID] + (_quest.rewardAmountPerPeriod - newToDistributeAmount) - previousWithdrawableAmount; if(newToDistributeAmount > previousRewardAmountDistributed){ uint256 missingAmount = newToDistributeAmount - previousRewardAmountDistributed; IERC20(_quest.rewardToken).safeTransfer(questDistributor, missingAmount); } } // Fix the Period in the Distributor, and retrieve token in case too much was sent if(!MultiMerkleDistributor(questDistributor).fixQuestPeriod(questID, period, newToDistributeAmount)) revert Errors.DisitributorFail(); periodAmountDistributedByQuest[questID][period] = newToDistributeAmount; } emit PeriodBiasFixed(period, questID, correctReducedBias); } /** * @notice Approves a new address as manager * @dev Approves a new address as manager * @param newManager Address to add */ function approveManager(address newManager) external onlyOwner { if(newManager == address(0)) revert Errors.AddressZero(); approvedManagers[newManager] = true; emit ApprovedManager(newManager); } /** * @notice Removes an address from the managers * @dev Removes an address from the managers * @param manager Address to remove */ function removeManager(address manager) external onlyOwner { if(manager == address(0)) revert Errors.AddressZero(); approvedManagers[manager] = false; emit RemovedManager(manager); } /** * @notice Updates the Chest address * @dev Updates the Chest address * @param chest Address of the new Chest */ function updateChest(address chest) external onlyOwner { if(chest == address(0)) revert Errors.AddressZero(); address oldChest = questChest; questChest = chest; emit ChestUpdated(oldChest, chest); } /** * @notice Updates the Distributor address * @dev Updates the Distributor address * @param newDistributor Address of the new Distributor */ function updateDistributor(address newDistributor) external onlyOwner { if(newDistributor == address(0)) revert Errors.AddressZero(); address oldDistributor = distributor; distributor = newDistributor; emit DistributorUpdated(oldDistributor, distributor); } /** * @notice Updates the Platfrom fees BPS ratio * @dev Updates the Platfrom fees BPS ratio * @param newFee New fee ratio */ function updatePlatformFee(uint256 newFee) external onlyOwner { if(newFee > 500) revert Errors.InvalidParameter(); uint256 oldfee = platformFeeRatio; platformFeeRatio = newFee; emit PlatformFeeRatioUpdated(oldfee, newFee); } /** * @notice Updates the min objective value * @dev Updates the min objective value * @param newMinObjective New min objective */ function updateMinObjective(uint256 newMinObjective) external onlyOwner { if(newMinObjective == 0) revert Errors.InvalidParameter(); uint256 oldMinObjective = objectiveMinimalThreshold; objectiveMinimalThreshold = newMinObjective; emit MinObjectiveUpdated(oldMinObjective, newMinObjective); } /** * @notice Sets a custom fee ratio for a given address * @dev Sets a custom fee ratio for a given address * @param user User address * @param customFeeRatio Custom fee ratio */ function setCustomFeeRatio(address user, uint256 customFeeRatio) external onlyOwner { if(customFeeRatio > platformFeeRatio) revert Errors.InvalidParameter(); customPlatformFeeRatio[user] = customFeeRatio; emit SetCustomFeeRatio(user, customFeeRatio); } /** * @notice Recovers ERC2O tokens sent by mistake to the contract * @dev Recovers ERC2O tokens sent by mistake to the contract * @param token Address tof the EC2O token * @return bool: success */ function recoverERC20(address token) external onlyOwner returns(bool) { if(whitelistedTokens[token]) revert Errors.CannotRecoverToken(); uint256 amount = IERC20(token).balanceOf(address(this)); if(amount == 0) revert Errors.NullAmount(); IERC20(token).safeTransfer(owner(), amount); return true; } /** * @notice Kills the contract * @dev Kills the contract */ function killBoard() external onlyOwner { if(isKilled) revert Errors.AlreadyKilled(); isKilled = true; killTs = block.timestamp; emit Killed(killTs); } /** * @notice Unkills the contract * @dev Unkills the contract */ function unkillBoard() external onlyOwner { if(!isKilled) revert Errors.NotKilled(); if(block.timestamp >= killTs + KILL_DELAY) revert Errors.KillDelayExpired(); isKilled = false; emit Unkilled(block.timestamp); } // Utils function safe48(uint n) internal pure returns (uint48) { if(n > type(uint48).max) revert Errors.NumberExceed48Bits(); return uint48(n); } }
//SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.20; import "../libraries/QuestDataTypes.sol"; /** @title Interface fo Quest Board V2 */ /// @author Paladin interface IQuestBoard { // Structs /** @notice Struct holding the parameters of the Quest common for all periods */ struct Quest { // Address of the Quest creator (caller of createQuest() method) address creator; // Address of the ERC20 used for rewards address rewardToken; // Address of the target Gauge address gauge; // Total number of periods for the Quest uint48 duration; // Timestamp where the 1st QuestPeriod starts uint48 periodStart; // Total amount of rewards paid for this Quest // If changes were made to the parameters of this Quest, this will account // any added reward amounts uint256 totalRewardAmount; // Total reward amount that can be distributed for each period uint256 rewardAmountPerPeriod; // Min Amount of reward for each vote (for 1 veToken) uint256 minRewardPerVote; // Max Amount of reward for each vote (for 1 veToken) uint256 maxRewardPerVote; // Min Target Bias for the Gauge uint256 minObjectiveVotes; // Max Target Bias for the Gauge uint256 maxObjectiveVotes; // Quest Types QuestTypes types; } /** @notice Struct with all the Quest types */ struct QuestTypes { QuestDataTypes.QuestVoteType voteType; QuestDataTypes.QuestRewardsType rewardsType; QuestDataTypes.QuestCloseType closeType; } /** @notice Struct for the local variables in _createQuest() method */ struct CreateVars { address creator; uint256 rewardPerPeriod; uint256 minObjective; uint256 startPeriod; uint256 periodIterator; uint256 voterLength; } /** @notice Struct for the local variables in extendQuest() method */ struct ExtendVars { uint256 lastPeriod; address gauge; address rewardToken; uint256 rewardPerPeriod; uint256 periodIterator; uint256 minObjective; uint256 maxObjective; uint256 minRewardPerVote; uint256 maxRewardPerVote; } /** @notice Struct for the local variables in updateQuestParameters() methods */ struct UpdateVars { uint256 remainingDuration; uint256 currentPeriod; uint256 newRewardPerPeriod; uint256 newMaxObjective; uint256 newMinObjective; address creator; } // Events /** @notice Event emitted when the Board is Initialized */ event Init(address distributor); /** @notice Event emitted when a new Quest is created */ event NewQuest( uint256 indexed questID, address indexed creator, address indexed gauge, address rewardToken, uint48 duration, uint256 startPeriod ); /** @notice Event emitted when the Quest duration is extended */ event ExtendQuestDuration(uint256 indexed questID, uint256 addedDuration, uint256 addedRewardAmount); /** @notice Event emitted when a Quest parameters are updated */ event UpdateQuestParameters( uint256 indexed questID, uint256 indexed updatePeriod, uint256 newMinRewardPerVote, uint256 newMaxRewardPerVote, uint256 addedPeriodRewardAmount ); /** @notice Event emitted when Quest creator withdraw undistributed rewards */ event WithdrawUnusedRewards(uint256 indexed questID, address recipient, uint256 amount); /** @notice Event emitted when a Quest Voter list is updated from it's original version */ event VoterListUpdated(uint256 indexed questID); /** @notice Event emitted when a Period is Closed */ event PeriodClosed(uint256 indexed questID, uint256 indexed period); /** @notice Event emitted when a Quest Period rools over the undistributed rewards */ event RewardsRollover(uint256 indexed questID, uint256 newRewardPeriod, uint256 newMinRewardPerVote, uint256 newMaxRewardPerVote); /** @notice Event emitted when a Period Bias is fixed */ event PeriodBiasFixed(uint256 indexed questID, uint256 indexed period, uint256 newBias); /** @notice Event emitted when a new reward token is whitelisted */ event WhitelistToken(address indexed token, uint256 minRewardPerVote); /** @notice Event emitted when a reward token parameter is updated */ event UpdateRewardToken(address indexed token, uint256 newMinRewardPerVote); /** @notice Event emitted when the contract is killed */ event Killed(uint256 killTime); /** @notice Event emitted when the contract is unkilled */ event Unkilled(uint256 unkillTime); /** @notice Event emitted when the Quest creator withdraw all unused funds (if the contract was killed) */ event EmergencyWithdraw(uint256 indexed questID, address recipient, uint256 amount); /** @notice Event emitted when a new manager is approved */ event ApprovedManager(address indexed manager); /** @notice Event emitted when a manager is removed */ event RemovedManager(address indexed manager); /** @notice Event emitted when the Chest address is updated */ event ChestUpdated(address oldChest, address newChest); /** @notice Event emitted when a custom fee ratio is set for a given address */ event SetCustomFeeRatio(address indexed creator, uint256 customFeeRatio); /** @notice Event emitted when the Distributor address is updated */ event DistributorUpdated(address oldDistributor, address newDistributor); /** @notice Event emitted when the fee ratio is updated */ event PlatformFeeRatioUpdated(uint256 oldFeeRatio, uint256 newFeeRatio); /** @notice Event emitted when the minimum objective of votes is updated */ event MinObjectiveUpdated(uint256 oldMinObjective, uint256 newMinObjective); }
pragma solidity 0.8.20; //SPDX-License-Identifier: MIT interface IRingsVoter { function isGauge(address gauge) external view returns (bool); function isAlive(address gauge) external view returns (bool); function getTotalVotes() external view returns (uint256); function getGaugeVotes(address gauge) external view returns (uint256); function getNftVotesOnGauge(uint256 tokenId, address gauge) external view returns (uint256); function getTotalVotesAtPeriod(uint256 ts) external view returns (uint256); function getGaugeVotesAtPeriod(address gauge, uint256 ts) external view returns (uint256); function getNftVotesOnGaugeAtPeriod(uint256 tokenId, address gauge, uint256 ts) external view returns (uint256); function vote(uint256 tokenId, address[] calldata gaugeList, uint256[] calldata weights) external; function reset(uint256 tokenId) external; function recast(uint256 tokenId) external; function voteMultiple(uint256[] calldata tokenIds, address[] calldata gaugeList, uint256[] calldata weights) external; function resetMultiple(uint256[] calldata tokenIds) external; function recastMultiple(uint256[] calldata tokenIds) external; function depositBudget(uint256 amount) external; function addGauge(address gauge, string memory label) external returns (uint256 index); }
pragma solidity ^0.8.16; //SPDX-License-Identifier: MIT library Errors { // Common Errors error AddressZero(); error NullAmount(); error CallerNotAllowed(); error IncorrectRewardToken(); error SameAddress(); error InequalArraySizes(); error EmptyArray(); error EmptyParameters(); error AlreadyInitialized(); error InvalidParameter(); error CannotRecoverToken(); error ForbiddenCall(); error CannotBeOwner(); error CallerNotPendingOwner(); error Killed(); error AlreadyKilled(); error NotKilled(); error KillDelayExpired(); error KillDelayNotExpired(); // Merkle Errors error MerkleRootNotUpdated(); error AlreadyClaimed(); error InvalidProof(); error EmptyMerkleRoot(); error IncorrectRewardAmount(); error MerkleRootFrozen(); error NotFrozen(); error AlreadyFrozen(); // Quest Errors error CallerNotQuestBoard(); error IncorrectQuestID(); error IncorrectPeriod(); error TokenNotWhitelisted(); error QuestAlreadyListed(); error QuestNotListed(); error PeriodAlreadyUpdated(); error PeriodNotClosed(); error PeriodStillActive(); error PeriodNotListed(); error EmptyQuest(); error EmptyPeriod(); error ExpiredQuest(); error QuestNotStarted(); error QuestTypeForbidden(); error NotInitialized(); error NoDistributorSet(); error DisitributorFail(); error InvalidGauge(); error InvalidQuestID(); error InvalidPeriod(); error ObjectiveTooLow(); error NewObjectiveTooLow(); error RewardPerVoteTooLow(); error MinValueOverMaxValue(); error IncorrectDuration(); error IncorrectAddDuration(); error IncorrectTotalRewardAmount(); error IncorrectAddedRewardAmount(); error IncorrectFeeAmount(); error InvalidQuestType(); error QuestTypesIncompatible(); error CalletNotQuestCreator(); error LowerRewardPerVote(); error LowerObjective(); error CreatorNotAllowed(); error AlreadyListed(); error NotListed(); error MaxListSize(); error BoardIsNotAllowedDistributor(); // Proxy error InvalidChainID(); error InvalidCaller(); error PeriodBiasNotUpdated(); error GaugeAlreadyAdded(); error AmountExceedsBalance(); error RecoverFailed(); //Math error NumberExceed48Bits(); }
//SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.16; /** @title Data Types fo Quest Board V2 */ /// @author Paladin library QuestDataTypes { // Enums /** @notice State of each Period for each Quest */ enum PeriodState { ZERO, ACTIVE, CLOSED, DISTRIBUTED } // All Periods are ACTIVE at creation since the voters from past periods are also accounted for the future period /** @notice Types of Vote logic for Quests */ enum QuestVoteType { NORMAL, BLACKLIST, WHITELIST } // NORMAL: basic vote logic // BLACKLIST: remove the blacklisted voters bias from the gauge biases // WHITELIST: only sum up the whitelisted voters biases /** @notice Types of Rewards logic for Quests */ enum QuestRewardsType { FIXED, RANGE } // FIXED: reward per vote is fixed // RANGE: reward per vote is a range between min and max, based on the Quest completion between min objective and max objective /** @notice Types of logic for undistributed rewards when closing Quest periods */ enum QuestCloseType { NORMAL, ROLLOVER, DISTRIBUTE } // NORMAL: undistributed rewards are avaialble to be withdrawn by the creator // ROLLOVER: undistributed rewards are added to the next period, increasing the reward/vote parameter // DISTRIBUTE: undistributed rewards are sent to the gauge for direct distribution }
//██████╗ █████╗ ██╗ █████╗ ██████╗ ██╗███╗ ██╗ //██╔══██╗██╔══██╗██║ ██╔══██╗██╔══██╗██║████╗ ██║ //██████╔╝███████║██║ ███████║██║ ██║██║██╔██╗ ██║ //██╔═══╝ ██╔══██║██║ ██╔══██║██║ ██║██║██║╚██╗██║ //██║ ██║ ██║███████╗██║ ██║██████╔╝██║██║ ╚████║ //╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═════╝ ╚═╝╚═╝ ╚═══╝ //SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.20; import "./oz/interfaces/IERC20.sol"; import "./oz/libraries/SafeERC20.sol"; import "./oz/utils/MerkleProof.sol"; import "./utils/Owner.sol"; import "./oz/utils/ReentrancyGuard.sol"; import "./libraries/Errors.sol"; /** @title Quest Multi Merkle Distributor */ /// @author Paladin /* Contract holds ERC20 rewards from Quests Can handle multiple MerkleRoots */ contract MultiMerkleDistributor is Owner, ReentrancyGuard { using SafeERC20 for IERC20; /** @notice Seconds in a Week */ uint256 private constant WEEK = 604800; /** @notice Mapping listing the reward token associated to each Quest ID */ // QuestID => reward token mapping(uint256 => address) public questRewardToken; /** @notice Mapping of tokens this contract is or was distributing */ // token address => boolean mapping(address => bool) public rewardTokens; // Periods: timestamp => start of a week, used as a voting period // in the Curve GaugeController though the timestamp / WEEK * WEEK logic. // Handled through the QuestManager contract. // Those can be fetched through this contract when they are closed, or through the QuestManager contract. /** @notice List of Closed QuestPeriods by Quest ID */ // QuestID => array of periods mapping(uint256 => uint256[]) public questClosedPeriods; /** @notice Merkle Root for each period of a Quest (indexed by Quest ID) */ // QuestID => period => merkleRoot mapping(uint256 => mapping(uint256 => bytes32)) public questMerkleRootPerPeriod; /** @notice Amount of rewards for each period of a Quest (indexed by Quest ID) */ // QuestID => period => totalRewardsAmount mapping(uint256 => mapping(uint256 => uint256)) public questRewardsPerPeriod; /** @notice BitMap of claims for each period of a Quest */ // QuestID => period => claimedBitMap // This is a packed array of booleans. mapping(uint256 => mapping(uint256 => mapping(uint256 => uint256))) private questPeriodClaimedBitMap; /** @notice Address of the QuestBoard contract */ address public immutable questBoard; // Events /** @notice Event emitted when a user Claims */ event Claimed( uint256 indexed questID, uint256 indexed period, uint256 index, uint256 amount, address rewardToken, address indexed account ); /** @notice Event emitted when a New Quest is added */ event NewQuest(uint256 indexed questID, address rewardToken); /** @notice Event emitted when a Period of a Quest is updated (when the Merkle Root is added) */ event QuestPeriodUpdated(uint256 indexed questID, uint256 indexed period, bytes32 merkleRoot); // Modifier /** @notice Check the caller is either the admin or the QuestBoard contract */ modifier onlyAllowed(){ if(msg.sender != questBoard && msg.sender != owner()) revert Errors.CallerNotAllowed(); _; } // Constructor constructor(address _questBoard){ if(_questBoard == address(0)) revert Errors.AddressZero(); questBoard = _questBoard; } // Functions /** * @notice Checks if the rewards were claimed for a user on a given period * @dev Checks if the rewards were claimed for a user (based on the index) on a given period * @param questID ID of the Quest * @param period Amount of underlying to borrow * @param index Index of the claim * @return bool : true if already claimed */ function isClaimed(uint256 questID, uint256 period, uint256 index) public view returns (bool) { uint256 claimedWordIndex = index >> 8; uint256 claimedBitIndex = index & 0xff; uint256 claimedWord = questPeriodClaimedBitMap[questID][period][claimedWordIndex]; uint256 mask = (1 << claimedBitIndex); return claimedWord & mask != 0; } /** * @dev Sets the rewards as claimed for the index on the given period * @param questID ID of the Quest * @param period Timestamp of the period * @param index Index of the claim */ function _setClaimed(uint256 questID, uint256 period, uint256 index) private { uint256 claimedWordIndex = index >> 8; uint256 claimedBitIndex = index & 0xff; questPeriodClaimedBitMap[questID][period][claimedWordIndex] |= (1 << claimedBitIndex); } //Basic Claim /** * @notice Claims the reward for a user for a given period of a Quest * @dev Claims the reward for a user for a given period of a Quest if the correct proof was given * @param questID ID of the Quest * @param period Timestamp of the period * @param index Index in the Merkle Tree * @param account Address of the user claiming the rewards * @param amount Amount of rewards to claim * @param merkleProof Proof to claim the rewards */ function claim(uint256 questID, uint256 period, uint256 index, address account, uint256 amount, bytes32[] calldata merkleProof) public nonReentrant { if(account == address(0)) revert Errors.AddressZero(); if(questMerkleRootPerPeriod[questID][period] == 0) revert Errors.MerkleRootNotUpdated(); if(isClaimed(questID, period, index)) revert Errors.AlreadyClaimed(); // Check that the given parameters match the given Proof bytes32 node = keccak256(abi.encodePacked(questID, period, index, account, amount)); if(!MerkleProof.verify(merkleProof, questMerkleRootPerPeriod[questID][period], node)) revert Errors.InvalidProof(); // Set the rewards as claimed for that period // And transfer the rewards to the user address rewardToken = questRewardToken[questID]; _setClaimed(questID, period, index); questRewardsPerPeriod[questID][period] -= amount; IERC20(rewardToken).safeTransfer(account, amount); emit Claimed(questID, period, index, amount, rewardToken, account); } //Struct ClaimParams struct ClaimParams { uint256 questID; uint256 period; uint256 index; uint256 amount; bytes32[] merkleProof; } //Multi Claim /** * @notice Claims multiple rewards for a given list * @dev Calls the claim() method for each entry in the claims array * @param account Address of the user claiming the rewards * @param claims List of ClaimParams struct data to claim */ function multiClaim(address account, ClaimParams[] calldata claims) external { uint256 length = claims.length; if(length == 0) revert Errors.EmptyParameters(); for(uint256 i; i < length;){ claim(claims[i].questID, claims[i].period, claims[i].index, account, claims[i].amount, claims[i].merkleProof); unchecked{ ++i; } } } //FullQuest Claim (form of Multi Claim but for only one Quest => only one ERC20 transfer) //Only works for the given periods (in ClaimParams) for the Quest. Any omitted period will be skipped /** * @notice Claims the reward for all the given periods of a Quest, and transfer all the rewards at once * @dev Sums up all the rewards for given periods of a Quest, and executes only one transfer * @param account Address of the user claiming the rewards * @param questID ID of the Quest * @param claims List of ClaimParams struct data to claim */ function claimQuest(address account, uint256 questID, ClaimParams[] calldata claims) external nonReentrant { if(account == address(0)) revert Errors.AddressZero(); uint256 length = claims.length; if(length == 0) revert Errors.EmptyParameters(); // Total amount claimable, to transfer at once uint256 totalClaimAmount; address rewardToken = questRewardToken[questID]; for(uint256 i; i < length;){ if(claims[i].questID != questID) revert Errors.IncorrectQuestID(); if(questMerkleRootPerPeriod[questID][claims[i].period] == 0) revert Errors.MerkleRootNotUpdated(); if(isClaimed(questID, claims[i].period, claims[i].index)) revert Errors.AlreadyClaimed(); // For each period given, if the proof matches the given parameters, // set as claimed and add to the to total to transfer bytes32 node = keccak256(abi.encodePacked(questID, claims[i].period, claims[i].index, account, claims[i].amount)); if(!MerkleProof.verify(claims[i].merkleProof, questMerkleRootPerPeriod[questID][claims[i].period], node)) revert Errors.InvalidProof(); _setClaimed(questID, claims[i].period, claims[i].index); questRewardsPerPeriod[questID][claims[i].period] -= claims[i].amount; totalClaimAmount += claims[i].amount; emit Claimed(questID, claims[i].period, claims[i].index, claims[i].amount, rewardToken, account); unchecked{ ++i; } } // Transfer the total claimed amount IERC20(rewardToken).safeTransfer(account, totalClaimAmount); } /** * @notice Returns all current Closed periods for the given Quest ID * @dev Returns all current Closed periods for the given Quest ID * @param questID ID of the Quest * @return uint256[] : List of closed periods */ function getClosedPeriodsByQuests(uint256 questID) external view returns (uint256[] memory) { return questClosedPeriods[questID]; } // Manager functions /** * @notice Adds a new Quest to the listing * @dev Adds a new Quest ID and the associated reward token * @param questID ID of the Quest * @param token Address of the ERC20 reward token * @return bool : success */ function addQuest(uint256 questID, address token) external returns(bool) { if(msg.sender != questBoard) revert Errors.CallerNotAllowed(); if(questRewardToken[questID] != address(0)) revert Errors.QuestAlreadyListed(); if(token == address(0)) revert Errors.TokenNotWhitelisted(); // Add a new Quest using the QuestID, and list the reward token for that Quest questRewardToken[questID] = token; if(!rewardTokens[token]) rewardTokens[token] = true; emit NewQuest(questID, token); return true; } /** * @notice Adds a new period & the rewards of this period for a Quest * @dev Adds a new period & the rewards of this period for a Quest * @param questID ID of the Quest * @param period Timestamp of the period * @param totalRewardAmount Total amount of rewards to distribute for the period * @return bool : success */ function addQuestPeriod(uint256 questID, uint256 period, uint256 totalRewardAmount) external returns(bool) { period = (period / WEEK) * WEEK; if(msg.sender != questBoard) revert Errors.CallerNotAllowed(); if(questRewardToken[questID] == address(0)) revert Errors.QuestNotListed(); if(questRewardsPerPeriod[questID][period] != 0) revert Errors.PeriodAlreadyUpdated(); if(period == 0) revert Errors.IncorrectPeriod(); if(totalRewardAmount == 0) revert Errors.NullAmount(); questRewardsPerPeriod[questID][period] = totalRewardAmount; return true; } function fixQuestPeriod(uint256 questID, uint256 period, uint256 newTotalRewardAmount) external returns(bool) { if(msg.sender != questBoard) revert Errors.CallerNotAllowed(); period = (period / WEEK) * WEEK; if(questRewardToken[questID] == address(0)) revert Errors.QuestNotListed(); if(period == 0) revert Errors.IncorrectPeriod(); if(questRewardsPerPeriod[questID][period] == 0) revert Errors.PeriodNotListed(); uint256 previousTotalRewardAmount = questRewardsPerPeriod[questID][period]; questRewardsPerPeriod[questID][period] = newTotalRewardAmount; if(previousTotalRewardAmount > newTotalRewardAmount){ // Send back the extra amount of reward token that was incorrectly sent // In the case of missing reward token, the Board will send them to this contract uint256 extraAmount = previousTotalRewardAmount - newTotalRewardAmount; IERC20(questRewardToken[questID]).safeTransfer(questBoard, extraAmount); } return true; } /** * @notice Updates the period of a Quest by adding the Merkle Root * @dev Add the Merkle Root for the eriod of the given Quest * @param questID ID of the Quest * @param period timestamp of the period * @param totalAmount sum of all rewards for the Merkle Tree * @param merkleRoot MerkleRoot to add * @return bool: success */ function updateQuestPeriod(uint256 questID, uint256 period, uint256 totalAmount, bytes32 merkleRoot) external onlyAllowed returns(bool) { period = (period / WEEK) * WEEK; if(questRewardToken[questID] == address(0)) revert Errors.QuestNotListed(); if(period == 0) revert Errors.IncorrectPeriod(); if(questRewardsPerPeriod[questID][period] == 0) revert Errors.PeriodNotListed(); if(questMerkleRootPerPeriod[questID][period] != 0) revert Errors.PeriodAlreadyUpdated(); if(merkleRoot == 0) revert Errors.EmptyMerkleRoot(); // Add a new Closed Period for the Quest questClosedPeriods[questID].push(period); if(totalAmount != questRewardsPerPeriod[questID][period]) revert Errors.IncorrectRewardAmount(); // Add the new MerkleRoot for that Closed Period questMerkleRootPerPeriod[questID][period] = merkleRoot; emit QuestPeriodUpdated(questID, period, merkleRoot); return true; } // Admin functions /** * @notice Recovers ERC2O tokens sent by mistake to the contract * @dev Recovers ERC2O tokens sent by mistake to the contract * @param token Address tof the EC2O token * @return bool: success */ function recoverERC20(address token) external onlyOwner nonReentrant returns(bool) { if(rewardTokens[token]) revert Errors.CannotRecoverToken(); uint256 amount = IERC20(token).balanceOf(address(this)); if(amount == 0) revert Errors.NullAmount(); IERC20(token).safeTransfer(owner(), amount); return true; } // /** * @notice Allows to update the MerkleRoot for a given period of a Quest if the current Root is incorrect * @dev Updates the MerkleRoot for the period of the Quest * @param questID ID of the Quest * @param period Timestamp of the period * @param merkleRoot New MerkleRoot to add * @return bool : success */ function emergencyUpdateQuestPeriod(uint256 questID, uint256 period, uint256 addedRewardAmount, bytes32 merkleRoot) external onlyOwner returns(bool) { // In case the given MerkleRoot was incorrect: // Process: // 1 - block claims for the Quest period by using this method to set an incorrect MerkleRoot, where no proof matches the root // 2 - prepare a new Merkle Tree, taking in account user previous claims on that period, and missing/overpaid rewards // a - for all new claims to be added, set them after the last index of the previous Merkle Tree // b - for users that did not claim, keep the same index, and adjust the amount to claim if needed // c - for indexes that were claimed, place an empty node in the Merkle Tree (with an amount at 0 & the address 0xdead as the account) // 3 - update the Quest period with the correct MerkleRoot // (no need to change the Bitmap, as the new MerkleTree will account for the indexes already claimed) period = (period / WEEK) * WEEK; if(questRewardToken[questID] == address(0)) revert Errors.QuestNotListed(); if(period == 0) revert Errors.IncorrectPeriod(); if(questMerkleRootPerPeriod[questID][period] == 0) revert Errors.PeriodNotClosed(); if(merkleRoot == 0) revert Errors.EmptyMerkleRoot(); questMerkleRootPerPeriod[questID][period] = merkleRoot; questRewardsPerPeriod[questID][period] += addedRewardAmount; emit QuestPeriodUpdated(questID, period, merkleRoot); return true; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Permit.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. * * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't * need to send a transaction, and thus is not required to hold Ether at all. */ interface IERC20Permit { /** * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, * given ``owner``'s signed approval. * * IMPORTANT: The same issues {IERC20-approve} has related to transaction * ordering also apply here. * * Emits an {Approval} event. * * Requirements: * * - `spender` cannot be the zero address. * - `deadline` must be a timestamp in the future. * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` * over the EIP712-formatted function arguments. * - the signature must use ``owner``'s current nonce (see {nonces}). * * For more information on the signature format, see the * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP * section]. */ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external; /** * @dev Returns the current nonce for `owner`. This value must be * included whenever a signature is generated for {permit}. * * Every successful call to {permit} increases ``owner``'s nonce by one. This * prevents a signature from being used multiple times. */ function nonces(address owner) external view returns (uint256); /** * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. */ // solhint-disable-next-line func-name-mixedcase function DOMAIN_SEPARATOR() external view returns (bytes32); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `to`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address to, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `from` to `to` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom(address from, address to, uint256 amount) external returns (bool); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol) pragma solidity ^0.8.0; import "../interfaces/IERC20.sol"; import "../extensions/IERC20Permit.sol"; import "../utils/Address.sol"; /** * @title SafeERC20 * @dev Wrappers around ERC20 operations that throw on failure (when the token * contract returns false). Tokens that return no value (and instead revert or * throw on failure) are also supported, non-reverting calls are assumed to be * successful. * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. */ library SafeERC20 { using Address for address; function safeTransfer(IERC20 token, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); } function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } /** * @dev Deprecated. This function has issues similar to the ones found in * {IERC20-approve}, and its usage is discouraged. * * Whenever possible, use {safeIncreaseAllowance} and * {safeDecreaseAllowance} instead. */ function safeApprove(IERC20 token, address spender, uint256 value) internal { // safeApprove should only be called when setting an initial allowance, // or when resetting it to zero. To increase and decrease it, use // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' require( (value == 0) || (token.allowance(address(this), spender) == 0), "SafeERC20: approve from non-zero to non-zero allowance" ); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); } function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { uint256 newAllowance = token.allowance(address(this), spender) + value; _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { unchecked { uint256 oldAllowance = token.allowance(address(this), spender); require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); uint256 newAllowance = oldAllowance - value; _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } } function safePermit( IERC20Permit token, address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) internal { uint256 nonceBefore = token.nonces(owner); token.permit(owner, spender, value, deadline, v, r, s); uint256 nonceAfter = token.nonces(owner); require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed"); } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). */ function _callOptionalReturn(IERC20 token, bytes memory data) private { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that // the target address contains contract code and also asserts for success in the low-level call. bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); if (returndata.length > 0) { // Return data is optional require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol) pragma solidity ^0.8.1; /** * @dev Collection of functions related to the address type */ library Address { /** * @dev Returns true if `account` is a contract. * * [IMPORTANT] * ==== * It is unsafe to assume that an address for which this function returns * false is an externally-owned account (EOA) and not a contract. * * Among others, `isContract` will return false for the following * types of addresses: * * - an externally-owned account * - a contract in construction * - an address where a contract will be created * - an address where a contract lived, but was destroyed * * Furthermore, `isContract` will also return true if the target contract within * the same transaction is already scheduled for destruction by `SELFDESTRUCT`, * which only has an effect at the end of a transaction. * ==== * * [IMPORTANT] * ==== * You shouldn't rely on `isContract` to protect against flash loan attacks! * * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract * constructor. * ==== */ function isContract(address account) internal view returns (bool) { // This method relies on extcodesize/address.code.length, which returns 0 // for contracts in construction, since the code is only stored at the end // of the constructor execution. return account.code.length > 0; } /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Address: insufficient balance"); (bool success, ) = recipient.call{value: amount}(""); require(success, "Address: unable to send value, recipient may have reverted"); } /** * @dev Performs a Solidity function call using a low level `call`. A * plain `call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason, it is bubbled up by this * function (like regular Solidity function calls). * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - `target` must be a contract. * - calling `target` with `data` must not revert. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, "Address: low-level call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with * `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. * * _Available since v3.1._ */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } /** * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but * with `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCallWithValue( address target, bytes memory data, uint256 value, string memory errorMessage ) internal returns (bytes memory) { require(address(this).balance >= value, "Address: insufficient balance for call"); (bool success, bytes memory returndata) = target.call{value: value}(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { return functionStaticCall(target, data, "Address: low-level static call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall( address target, bytes memory data, string memory errorMessage ) internal view returns (bytes memory) { (bool success, bytes memory returndata) = target.staticcall(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { return functionDelegateCall(target, data, "Address: low-level delegate call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { (bool success, bytes memory returndata) = target.delegatecall(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. * * _Available since v4.8._ */ function verifyCallResultFromTarget( address target, bool success, bytes memory returndata, string memory errorMessage ) internal view returns (bytes memory) { if (success) { if (returndata.length == 0) { // only check isContract if the call was successful and the return data is empty // otherwise we already know that it was a contract require(isContract(target), "Address: call to non-contract"); } return returndata; } else { _revert(returndata, errorMessage); } } /** * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the * revert reason or using the provided one. * * _Available since v4.3._ */ function verifyCallResult( bool success, bytes memory returndata, string memory errorMessage ) internal pure returns (bytes memory) { if (success) { return returndata; } else { _revert(returndata, errorMessage); } } function _revert(bytes memory returndata, string memory errorMessage) private pure { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly /// @solidity memory-safe-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/Context.sol) pragma solidity ^0.8.0; /** * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract Context { function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { return msg.data; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/MerkleProof.sol) pragma solidity ^0.8.0; /** * @dev These functions deal with verification of Merkle Tree proofs. * * The tree and the proofs can be generated using our * https://github.com/OpenZeppelin/merkle-tree[JavaScript library]. * You will find a quickstart guide in the readme. * * WARNING: You should avoid using leaf values that are 64 bytes long prior to * hashing, or use a hash function other than keccak256 for hashing leaves. * This is because the concatenation of a sorted pair of internal nodes in * the merkle tree could be reinterpreted as a leaf value. * OpenZeppelin's JavaScript library generates merkle trees that are safe * against this attack out of the box. */ library MerkleProof { /** * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree * defined by `root`. For this, a `proof` must be provided, containing * sibling hashes on the branch from the leaf to the root of the tree. Each * pair of leaves and each pair of pre-images are assumed to be sorted. */ function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) { return processProof(proof, leaf) == root; } /** * @dev Calldata version of {verify} * * _Available since v4.7._ */ function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) { return processProofCalldata(proof, leaf) == root; } /** * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt * hash matches the root of the tree. When processing the proof, the pairs * of leafs & pre-images are assumed to be sorted. * * _Available since v4.4._ */ function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) { bytes32 computedHash = leaf; for (uint256 i = 0; i < proof.length; i++) { computedHash = _hashPair(computedHash, proof[i]); } return computedHash; } /** * @dev Calldata version of {processProof} * * _Available since v4.7._ */ function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) { bytes32 computedHash = leaf; for (uint256 i = 0; i < proof.length; i++) { computedHash = _hashPair(computedHash, proof[i]); } return computedHash; } /** * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a merkle tree defined by * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}. * * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details. * * _Available since v4.7._ */ function multiProofVerify( bytes32[] memory proof, bool[] memory proofFlags, bytes32 root, bytes32[] memory leaves ) internal pure returns (bool) { return processMultiProof(proof, proofFlags, leaves) == root; } /** * @dev Calldata version of {multiProofVerify} * * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details. * * _Available since v4.7._ */ function multiProofVerifyCalldata( bytes32[] calldata proof, bool[] calldata proofFlags, bytes32 root, bytes32[] memory leaves ) internal pure returns (bool) { return processMultiProofCalldata(proof, proofFlags, leaves) == root; } /** * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false * respectively. * * CAUTION: Not all merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). * * _Available since v4.7._ */ function processMultiProof( bytes32[] memory proof, bool[] memory proofFlags, bytes32[] memory leaves ) internal pure returns (bytes32 merkleRoot) { // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of // the merkle tree. uint256 leavesLen = leaves.length; uint256 totalHashes = proofFlags.length; // Check proof validity. require(leavesLen + proof.length - 1 == totalHashes, "MerkleProof: invalid multiproof"); // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". bytes32[] memory hashes = new bytes32[](totalHashes); uint256 leafPos = 0; uint256 hashPos = 0; uint256 proofPos = 0; // At each step, we compute the next hash using two values: // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we // get the next hash. // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the // `proof` array. for (uint256 i = 0; i < totalHashes; i++) { bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; bytes32 b = proofFlags[i] ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) : proof[proofPos++]; hashes[i] = _hashPair(a, b); } if (totalHashes > 0) { unchecked { return hashes[totalHashes - 1]; } } else if (leavesLen > 0) { return leaves[0]; } else { return proof[0]; } } /** * @dev Calldata version of {processMultiProof}. * * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details. * * _Available since v4.7._ */ function processMultiProofCalldata( bytes32[] calldata proof, bool[] calldata proofFlags, bytes32[] memory leaves ) internal pure returns (bytes32 merkleRoot) { // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of // the merkle tree. uint256 leavesLen = leaves.length; uint256 totalHashes = proofFlags.length; // Check proof validity. require(leavesLen + proof.length - 1 == totalHashes, "MerkleProof: invalid multiproof"); // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". bytes32[] memory hashes = new bytes32[](totalHashes); uint256 leafPos = 0; uint256 hashPos = 0; uint256 proofPos = 0; // At each step, we compute the next hash using two values: // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we // get the next hash. // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the // `proof` array. for (uint256 i = 0; i < totalHashes; i++) { bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; bytes32 b = proofFlags[i] ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) : proof[proofPos++]; hashes[i] = _hashPair(a, b); } if (totalHashes > 0) { unchecked { return hashes[totalHashes - 1]; } } else if (leavesLen > 0) { return leaves[0]; } else { return proof[0]; } } function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) { return a < b ? _efficientHash(a, b) : _efficientHash(b, a); } function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) { /// @solidity memory-safe-assembly assembly { mstore(0x00, a) mstore(0x20, b) value := keccak256(0x00, 0x40) } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol) pragma solidity ^0.8.0; import "./Context.sol"; /** * @dev Contract module which provides a basic access control mechanism, where * there is an account (an owner) that can be granted exclusive access to * specific functions. * * By default, the owner account will be the one that deploys the contract. This * can later be changed with {transferOwnership}. * * This module is used through inheritance. It will make available the modifier * `onlyOwner`, which can be applied to your functions to restrict their use to * the owner. */ abstract contract Ownable is Context { address private _owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the deployer as the initial owner. */ constructor() { _transferOwnership(_msgSender()); } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { _checkOwner(); _; } /** * @dev Returns the address of the current owner. */ function owner() public view virtual returns (address) { return _owner; } /** * @dev Throws if the sender is not the owner. */ function _checkOwner() internal view virtual { require(owner() == _msgSender(), "Ownable: caller is not the owner"); } /** * @dev Leaves the contract without owner. It will not be possible to call * `onlyOwner` functions anymore. Can only be called by the current owner. * * NOTE: Renouncing ownership will leave the contract without an owner, * thereby removing any functionality that is only available to the owner. */ function renounceOwnership() public virtual onlyOwner { _transferOwnership(address(0)); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Can only be called by the current owner. */ function transferOwnership(address newOwner) public virtual onlyOwner { require(newOwner != address(0), "Ownable: new owner is the zero address"); _transferOwnership(newOwner); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Internal function without access restriction. */ function _transferOwnership(address newOwner) internal virtual { address oldOwner = _owner; _owner = newOwner; emit OwnershipTransferred(oldOwner, newOwner); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (security/ReentrancyGuard.sol) pragma solidity ^0.8.0; /** * @dev Contract module that helps prevent reentrant calls to a function. * * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier * available, which can be applied to functions to make sure there are no nested * (reentrant) calls to them. * * Note that because there is a single `nonReentrant` guard, functions marked as * `nonReentrant` may not call one another. This can be worked around by making * those functions `private`, and then adding `external` `nonReentrant` entry * points to them. * * TIP: If you would like to learn more about reentrancy and alternative ways * to protect against it, check out our blog post * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. */ abstract contract ReentrancyGuard { // Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled. // The values being non-zero value makes deployment a bit more expensive, // but in exchange the refund on every call to nonReentrant will be lower in // amount. Since refunds are capped to a percentage of the total // transaction's gas, it is best to keep them low in cases like this one, to // increase the likelihood of the full refund coming into effect. uint256 private constant _NOT_ENTERED = 1; uint256 private constant _ENTERED = 2; uint256 private _status; constructor() { _status = _NOT_ENTERED; } /** * @dev Prevents a contract from calling itself, directly or indirectly. * Calling a `nonReentrant` function from another `nonReentrant` * function is not supported. It is possible to prevent this from happening * by making the `nonReentrant` function external, and making it call a * `private` function that does the actual work. */ modifier nonReentrant() { _nonReentrantBefore(); _; _nonReentrantAfter(); } function _nonReentrantBefore() private { // On the first call to nonReentrant, _status will be _NOT_ENTERED require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); // Any calls to nonReentrant after this point will fail _status = _ENTERED; } function _nonReentrantAfter() private { // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _status = _NOT_ENTERED; } /** * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a * `nonReentrant` function in the call stack. */ function _reentrancyGuardEntered() internal view returns (bool) { return _status == _ENTERED; } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.20; import "../oz/utils/Ownable.sol"; import "../libraries/Errors.sol"; /** @title 2-step Ownership */ /// @author Paladin /* Extends OZ Ownable contract to add 2-step ownership transfer */ contract Owner is Ownable { address public pendingOwner; event NewPendingOwner(address indexed previousPendingOwner, address indexed newPendingOwner); function transferOwnership(address newOwner) public override virtual onlyOwner { if(newOwner == address(0)) revert Errors.AddressZero(); if(newOwner == owner()) revert Errors.CannotBeOwner(); address oldPendingOwner = pendingOwner; pendingOwner = newOwner; emit NewPendingOwner(oldPendingOwner, newOwner); } function acceptOwnership() public virtual { if(msg.sender != pendingOwner) revert Errors.CallerNotPendingOwner(); address newOwner = pendingOwner; _transferOwnership(pendingOwner); pendingOwner = address(0); emit NewPendingOwner(newOwner, address(0)); } }
{ "optimizer": { "enabled": true, "runs": 9999 }, "viaIR": true, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "libraries": {} }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"inputs":[{"internalType":"address","name":"_voter","type":"address"},{"internalType":"address","name":"_chest","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AddressZero","type":"error"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"AlreadyKilled","type":"error"},{"inputs":[],"name":"AlreadyListed","type":"error"},{"inputs":[],"name":"CallerNotAllowed","type":"error"},{"inputs":[],"name":"CallerNotPendingOwner","type":"error"},{"inputs":[],"name":"CannotBeOwner","type":"error"},{"inputs":[],"name":"CannotRecoverToken","type":"error"},{"inputs":[],"name":"DisitributorFail","type":"error"},{"inputs":[],"name":"EmptyArray","type":"error"},{"inputs":[],"name":"EmptyMerkleRoot","type":"error"},{"inputs":[],"name":"EmptyPeriod","type":"error"},{"inputs":[],"name":"EmptyQuest","type":"error"},{"inputs":[],"name":"ExpiredQuest","type":"error"},{"inputs":[],"name":"IncorrectAddDuration","type":"error"},{"inputs":[],"name":"IncorrectAddedRewardAmount","type":"error"},{"inputs":[],"name":"IncorrectDuration","type":"error"},{"inputs":[],"name":"IncorrectFeeAmount","type":"error"},{"inputs":[],"name":"InequalArraySizes","type":"error"},{"inputs":[],"name":"InvalidGauge","type":"error"},{"inputs":[],"name":"InvalidParameter","type":"error"},{"inputs":[],"name":"InvalidPeriod","type":"error"},{"inputs":[],"name":"InvalidQuestID","type":"error"},{"inputs":[],"name":"InvalidQuestType","type":"error"},{"inputs":[],"name":"KillDelayExpired","type":"error"},{"inputs":[],"name":"KillDelayNotExpired","type":"error"},{"inputs":[],"name":"Killed","type":"error"},{"inputs":[],"name":"LowerRewardPerVote","type":"error"},{"inputs":[],"name":"MaxListSize","type":"error"},{"inputs":[],"name":"MinValueOverMaxValue","type":"error"},{"inputs":[],"name":"NewObjectiveTooLow","type":"error"},{"inputs":[],"name":"NoDistributorSet","type":"error"},{"inputs":[],"name":"NotInitialized","type":"error"},{"inputs":[],"name":"NotKilled","type":"error"},{"inputs":[],"name":"NullAmount","type":"error"},{"inputs":[],"name":"NumberExceed48Bits","type":"error"},{"inputs":[],"name":"ObjectiveTooLow","type":"error"},{"inputs":[],"name":"PeriodNotClosed","type":"error"},{"inputs":[],"name":"PeriodStillActive","type":"error"},{"inputs":[],"name":"QuestNotStarted","type":"error"},{"inputs":[],"name":"QuestTypeForbidden","type":"error"},{"inputs":[],"name":"RewardPerVoteTooLow","type":"error"},{"inputs":[],"name":"TokenNotWhitelisted","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"manager","type":"address"}],"name":"ApprovedManager","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldChest","type":"address"},{"indexed":false,"internalType":"address","name":"newChest","type":"address"}],"name":"ChestUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldDistributor","type":"address"},{"indexed":false,"internalType":"address","name":"newDistributor","type":"address"}],"name":"DistributorUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"questID","type":"uint256"},{"indexed":false,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"EmergencyWithdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"questID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"addedDuration","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"addedRewardAmount","type":"uint256"}],"name":"ExtendQuestDuration","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"distributor","type":"address"}],"name":"Init","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"killTime","type":"uint256"}],"name":"Killed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldMinObjective","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newMinObjective","type":"uint256"}],"name":"MinObjectiveUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousPendingOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newPendingOwner","type":"address"}],"name":"NewPendingOwner","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"questID","type":"uint256"},{"indexed":true,"internalType":"address","name":"creator","type":"address"},{"indexed":true,"internalType":"address","name":"gauge","type":"address"},{"indexed":false,"internalType":"address","name":"rewardToken","type":"address"},{"indexed":false,"internalType":"uint48","name":"duration","type":"uint48"},{"indexed":false,"internalType":"uint256","name":"startPeriod","type":"uint256"}],"name":"NewQuest","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"questID","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"period","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newBias","type":"uint256"}],"name":"PeriodBiasFixed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"questID","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"period","type":"uint256"}],"name":"PeriodClosed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldFeeRatio","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newFeeRatio","type":"uint256"}],"name":"PlatformFeeRatioUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"manager","type":"address"}],"name":"RemovedManager","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"questID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newRewardPeriod","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newMinRewardPerVote","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newMaxRewardPerVote","type":"uint256"}],"name":"RewardsRollover","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"creator","type":"address"},{"indexed":false,"internalType":"uint256","name":"customFeeRatio","type":"uint256"}],"name":"SetCustomFeeRatio","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"unkillTime","type":"uint256"}],"name":"Unkilled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"questID","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"updatePeriod","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newMinRewardPerVote","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newMaxRewardPerVote","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"addedPeriodRewardAmount","type":"uint256"}],"name":"UpdateQuestParameters","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"newMinRewardPerVote","type":"uint256"}],"name":"UpdateRewardToken","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"questID","type":"uint256"}],"name":"VoterListUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"minRewardPerVote","type":"uint256"}],"name":"WhitelistToken","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"questID","type":"uint256"},{"indexed":false,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"WithdrawUnusedRewards","type":"event"},{"inputs":[],"name":"VOTER","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"questIDs","type":"uint256[]"},{"internalType":"uint256","name":"period","type":"uint256"},{"internalType":"uint256[]","name":"totalAmounts","type":"uint256[]"},{"internalType":"bytes32[]","name":"merkleRoots","type":"bytes32[]"}],"name":"addMultipleMerkleRoot","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"questID","type":"uint256"},{"internalType":"uint256[]","name":"nftIDs","type":"uint256[]"}],"name":"addToVoterList","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newManager","type":"address"}],"name":"approveManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"period","type":"uint256"},{"internalType":"uint256[]","name":"questIDs","type":"uint256[]"}],"name":"closePartOfQuestPeriod","outputs":[{"internalType":"uint256","name":"closed","type":"uint256"},{"internalType":"uint256","name":"skipped","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"period","type":"uint256"}],"name":"closeQuestPeriod","outputs":[{"internalType":"uint256","name":"closed","type":"uint256"},{"internalType":"uint256","name":"skipped","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"gauge","type":"address"},{"internalType":"address","name":"rewardToken","type":"address"},{"internalType":"bool","name":"startNextPeriod","type":"bool"},{"internalType":"uint48","name":"duration","type":"uint48"},{"internalType":"uint256","name":"rewardPerVote","type":"uint256"},{"internalType":"uint256","name":"totalRewardAmount","type":"uint256"},{"internalType":"uint256","name":"feeAmount","type":"uint256"},{"internalType":"enum QuestDataTypes.QuestVoteType","name":"voteType","type":"uint8"},{"internalType":"enum QuestDataTypes.QuestCloseType","name":"closeType","type":"uint8"},{"internalType":"uint256[]","name":"voterList","type":"uint256[]"}],"name":"createFixedQuest","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"gauge","type":"address"},{"internalType":"address","name":"rewardToken","type":"address"},{"internalType":"bool","name":"startNextPeriod","type":"bool"},{"internalType":"uint48","name":"duration","type":"uint48"},{"internalType":"uint256","name":"minRewardPerVote","type":"uint256"},{"internalType":"uint256","name":"maxRewardPerVote","type":"uint256"},{"internalType":"uint256","name":"totalRewardAmount","type":"uint256"},{"internalType":"uint256","name":"feeAmount","type":"uint256"},{"internalType":"enum QuestDataTypes.QuestVoteType","name":"voteType","type":"uint8"},{"internalType":"enum QuestDataTypes.QuestCloseType","name":"closeType","type":"uint8"},{"internalType":"uint256[]","name":"voterList","type":"uint256[]"}],"name":"createRangedQuest","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"customPlatformFeeRatio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"distributor","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"questID","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"emergencyWithdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"questID","type":"uint256"},{"internalType":"uint48","name":"addedDuration","type":"uint48"},{"internalType":"uint256","name":"addedRewardAmount","type":"uint256"},{"internalType":"uint256","name":"feeAmount","type":"uint256"}],"name":"extendQuestDuration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"period","type":"uint256"},{"internalType":"uint256","name":"questID","type":"uint256"},{"internalType":"uint256","name":"correctReducedBias","type":"uint256"}],"name":"fixQuestPeriodBias","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"questID","type":"uint256"}],"name":"getAllPeriodsForQuestId","outputs":[{"internalType":"uint48[]","name":"","type":"uint48[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentPeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"questID","type":"uint256"}],"name":"getCurrentReducedBias","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"questID","type":"uint256"}],"name":"getQuestCreator","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"period","type":"uint256"}],"name":"getQuestIdsForPeriod","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"gauge","type":"address"},{"internalType":"uint256","name":"period","type":"uint256"}],"name":"getQuestIdsForPeriodForGauge","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"questID","type":"uint256"}],"name":"getQuestVoterList","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"period","type":"uint256"},{"internalType":"uint256","name":"questID","type":"uint256"}],"name":"getReducedBias","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_distributor","type":"address"}],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isKilled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"killBoard","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"killTs","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"minRewardPerVotePerToken","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"questIDs","type":"uint256[]"},{"internalType":"address","name":"recipient","type":"address"}],"name":"multipleWithdrawUnusedRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"nextID","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"objectiveMinimalThreshold","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"originalRewardPerPeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"periodAmountDistributedByQuest","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"periodStateByQuest","outputs":[{"internalType":"enum QuestDataTypes.PeriodState","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"platformFeeRatio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"questChest","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"questDistributors","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"questWithdrawableAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"quests","outputs":[{"internalType":"address","name":"creator","type":"address"},{"internalType":"address","name":"rewardToken","type":"address"},{"internalType":"address","name":"gauge","type":"address"},{"internalType":"uint48","name":"duration","type":"uint48"},{"internalType":"uint48","name":"periodStart","type":"uint48"},{"internalType":"uint256","name":"totalRewardAmount","type":"uint256"},{"internalType":"uint256","name":"rewardAmountPerPeriod","type":"uint256"},{"internalType":"uint256","name":"minRewardPerVote","type":"uint256"},{"internalType":"uint256","name":"maxRewardPerVote","type":"uint256"},{"internalType":"uint256","name":"minObjectiveVotes","type":"uint256"},{"internalType":"uint256","name":"maxObjectiveVotes","type":"uint256"},{"components":[{"internalType":"enum QuestDataTypes.QuestVoteType","name":"voteType","type":"uint8"},{"internalType":"enum QuestDataTypes.QuestRewardsType","name":"rewardsType","type":"uint8"},{"internalType":"enum QuestDataTypes.QuestCloseType","name":"closeType","type":"uint8"}],"internalType":"struct IQuestBoard.QuestTypes","name":"types","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"recoverERC20","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"questID","type":"uint256"},{"internalType":"uint256","name":"nftID","type":"uint256"}],"name":"removeFromVoterList","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"manager","type":"address"}],"name":"removeManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"customFeeRatio","type":"uint256"}],"name":"setCustomFeeRatio","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unkillBoard","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"chest","type":"address"}],"name":"updateChest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newDistributor","type":"address"}],"name":"updateDistributor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newMinObjective","type":"uint256"}],"name":"updateMinObjective","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newFee","type":"uint256"}],"name":"updatePlatformFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"questID","type":"uint256"},{"internalType":"uint256","name":"newMinRewardPerVote","type":"uint256"},{"internalType":"uint256","name":"newMaxRewardPerVote","type":"uint256"},{"internalType":"uint256","name":"addedPeriodRewardAmount","type":"uint256"},{"internalType":"uint256","name":"addedTotalRewardAmount","type":"uint256"},{"internalType":"uint256","name":"feeAmount","type":"uint256"}],"name":"updateQuestParameters","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newToken","type":"address"},{"internalType":"uint256","name":"newMinRewardPerVote","type":"uint256"}],"name":"updateRewardToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"newTokens","type":"address[]"},{"internalType":"uint256[]","name":"minRewardPerVotes","type":"uint256[]"}],"name":"whitelistMultipleTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"whitelistedTokens","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"questID","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"withdrawUnusedRewards","outputs":[],"stateMutability":"nonpayable","type":"function"}]
Contract Creation Code
60a0346200012657601f6200519738819003918201601f19168301916001600160401b038311848410176200012a57808492604094855283398101031262000126576200005a602062000052836200013e565b92016200013e565b5f80546001600160a01b031980821633908117845560405191956001600160a01b03959490938616907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a36001600255610190600f558382161580156200011b575b6200010c575060805216906012541617601255683635c9adc5dea000006011556040516150439081620001548239608051818181611f7a015281816136c101528181613a3d01526142980152f35b639fabe1c160e01b8152600490fd5b5083831615620000be565b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b51906001600160a01b0382168203620001265756fe6080806040526004361015610012575f80fd5b5f3560e01c908163040916a4146133df57508063052de43f146133ac578063086146d2146133925780630881b8301461322b5780630eb67173146132125780631119548514612f61578063146bdb7d14612f3757806319ab453c14612e7b5780631e96917d14612e5e5780632f940c7014612b875780633ce455a814612aa85780633d72387714612a4d5780633da8a8d114612a1a578063482280d5146129eb578063511aa52d146129ce57806357ae1cec146129b15780636eaa04ef1461297c578063715018a61461290d5780637211a52b146127b757806377dd7cf71461277f57806379ba5097146126b157806379dc77d014612679578063897ec1c7146125e15780638a6ce1cb14611fc35780638da5cb5b14611f9e5780638ebf2fd614611f5b5780638f4c72d814611e775780638fe8a10114611e555780639655070f14611e34578063975e517314611e0a5780639e8c708e14611d06578063a6c93d7d14611ce0578063a8a7e38614611add578063a8cf56f9146119f5578063aa0b598814611997578063aa412da814611932578063ac18de43146118c8578063ac4884ac14611436578063b486d07314610f29578063b5eee72214610ebc578063ba82c89714610de7578063bc30a61814610d40578063bd7ddd0614610d05578063bfe1092814610cdf578063c6c02cac14610c1d578063d52f113514610a3e578063daf9c21014610a01578063dc34a15114610979578063e085f98014610872578063e30c39781461084c578063eca5d0ca1461079e578063ed8de71914610644578063f29e9aa7146104a6578063f2fde38b146103c3578063f49c1d24146103275763f998114014610284575f80fd5b34610323576020806003193601126103235762093a80806004350481810291818304149015171561030f575f52600a815260405f209060405190818184549182815201935f52815f20915f905b8282106102f8576102f4856102e881890382613541565b60405191829182613490565b0390f35b8354865294850194600193840193909101906102d1565b634e487b7160e01b5f52601160045260245ffd5b5f80fd5b3461032357604060031936011261032357610340613440565b6024359061034c614f2f565b600f5482116103995760206001600160a01b037f6aade5fcd818524835ddc10e56891b8efefe8fae5d5817ff5e2c630d41fad354921692835f52601082528060405f2055604051908152a2005b60046040517f613970e0000000000000000000000000000000000000000000000000000000008152fd5b34610323576020600319360112610323576103dc613440565b6103e4614f2f565b6001600160a01b0380911690811561047c57805f5416821461045257600154827fffffffffffffffffffffffff0000000000000000000000000000000000000000821617600155167fb3d55174552271a4f1aaf36b72f50381e892171636b3fb5447fe00e995e7a37b5f80a3005b60046040517fd5e889bf000000000000000000000000000000000000000000000000000000008152fd5b60046040517f9fabe1c1000000000000000000000000000000000000000000000000000000008152fd5b3461032357602080600319360112610323576004355f5260058082528160405f2091604051908192839181865494858152019081965f52825f20945f915b8160048401106105f0575061052a955491848282106105d8575b8282106105bd575b8282106105a2575b828210610587575b5010610571575b5090509392930383613541565b60405192839281840190828552518091526040840192915f5b82811061055257505050500390f35b835165ffffffffffff1685528695509381019392810192600101610543565b60c01c65ffffffffffff1681528691018761051d565b6001919465ffffffffffff8560901c16815201930184610516565b6001919465ffffffffffff8560601c1681520193018461050e565b6001919465ffffffffffff8560301c16815201930184610506565b6001919465ffffffffffff85168152019301846104fe565b9450925090600160a08592875465ffffffffffff908181168352818160301c168d84015260608282821c166040850152828260901c169084015260c01c1660808201520195019101879286949592956104e4565b3461032357610652366134ca565b801561077457825f5260046020526001600160a01b0380600260405f200154161561074a57835f52600460205260405f205416330361072057825f52600e602052600a6106a360405f205483613618565b116106f6575f5b8181106106d857837f72a0d025e6eac8ca921e2325e32ff0f17354f246b9024647bf4c1ab67437a8595f80a2005b806106f06106e960019385876139bb565b358661431d565b016106aa565b60046040517f7dce6cb8000000000000000000000000000000000000000000000000000000008152fd5b60046040517f2af07d20000000000000000000000000000000000000000000000000000000008152fd5b60046040517f62d0ece0000000000000000000000000000000000000000000000000000000008152fd5b60046040517f521299a9000000000000000000000000000000000000000000000000000000008152fd5b34610323576020600319360112610323576107b7613440565b6107bf614f2f565b6001600160a01b03808216801561047c576108477f9e1cbba858790e137077d865d7e4df5017a55524ea53a66b2c20ec665167d8d693601254927fffffffffffffffffffffffff000000000000000000000000000000000000000084161760125560405193849316839060209093929360408301946001600160a01b03809216845216910152565b0390a1005b34610323575f6003193601126103235760206001600160a01b0360015416604051908152f35b34610323576020600319360112610323576004355f52600460205260405f206001600160a01b0390818154169082600182015416926002820154600383015460048401546005850154916006860154936007870154956108d9600960088a01549901613582565b99604051998a5260208a01528116604089015265ffffffffffff8160a01c16606089015260d01c608088015260a087015260c086015260e08501526101008401526101208301526101408201528151610931816134fe565b61016082015260208201516002811015610965576101c092604091610180840152015161095d816134fe565b6101a0820152f35b634e487b7160e01b5f52602160045260245ffd5b34610323575f60031936011261032357610991614f2f565b60175460ff81166109d75760ff196001911617601755426018557ff4a991ad9e7f9711696f7bd41529beb4c470d75788573535d4ca3f0857c79ce86020604051428152a1005b60046040517f7a480252000000000000000000000000000000000000000000000000000000008152fd5b34610323576020600319360112610323576001600160a01b03610a22613440565b165f526015602052602060ff60405f2054166040519015158152f35b3461032357610a4c366134ca565b610a57929192614fa0565b60ff60175416610bf3576001600160a01b03806013541615610bc957335f52601460205260ff60405f205416159081610bbb575b50610720575f915f9162093a8080920482810292818404149015171561030f578015610774578115610b9157610abf6135fe565b821015610b6757815f52600a60205260405f205415610b3d57905f915b808310610af85760408585600160025582519182526020820152f35b909193610b10610b098684896139bb565b3584614678565b15610b2a57610b206001916145c9565b945b019190610adc565b9392610b376001916145c9565b93610b22565b60046040517ffbbfe1b6000000000000000000000000000000000000000000000000000000008152fd5b60046040517f9bcf46f4000000000000000000000000000000000000000000000000000000008152fd5b60046040517f17479ac8000000000000000000000000000000000000000000000000000000008152fd5b90505f541633141584610a8b565b60046040517f87138d5c000000000000000000000000000000000000000000000000000000008152fd5b60046040517f0f8eeedb000000000000000000000000000000000000000000000000000000008152fd5b34610323575f60031936011261032357610c35614f2f565b60175460ff811615610cb55760185462127500810180911161030f57421015610c8b5760ff19166017557f62c91cdd7db0a8e05658629214f78fbbbc0708b9698bf57892027841dc8899c16020604051428152a1005b60046040517fc905adb1000000000000000000000000000000000000000000000000000000008152fd5b60046040517f81e21bc8000000000000000000000000000000000000000000000000000000008152fd5b34610323575f6003193601126103235760206001600160a01b0360135416604051908152f35b3461032357610d133661342a565b905f52600660205260405f20905f5260205260ff60405f2054166040516004821015610965576020918152f35b3461032357602060031936011261032357610d59613440565b610d61614f2f565b6001600160a01b0380911690811561047c57601380547fffffffffffffffffffffffff00000000000000000000000000000000000000008116841790915560408051929091166001600160a01b03908116835290921660208201527f111a961d91cf441fe07e7bfddc128b30ab56974d1a76851e969e0642fdb2dd509181908101610847565b3461032357604060031936011261032357610e00613440565b60243590335f52601460205260ff60405f2054161580610ea8575b610720576001600160a01b031690815f52601560205260ff60405f20541615610e7e5780156103995760207f0b431c44bb4597805cf43550dbfc7dd3b2a6752c10eef974a53dba1c7e25ffa691835f52601682528060405f2055604051908152a2005b60046040517ff84835a0000000000000000000000000000000000000000000000000000000008152fd5b506001600160a01b035f5416331415610e1b565b34610323576020600319360112610323576001600160a01b03610edd613440565b610ee5614f2f565b16801561047c57805f52601460205260405f20600160ff198254161790557f8e6f8d1a4c834c03bc5c8f9dedff078a6fd2904f7a94b17df04efb916aaba72d5f80a2005b346103235760c060031936011261032357610f42614fa0565b60ff60175416610bf3576001600160a01b036013541615610bc957610f656138ab565b6004355f52600460205260405f2090600354600435101561074a576001600160a01b0382541633036107205760243515801561142c575b61140257604435602435116113d85760ff600983015460081c1660028110156109655715806113ca575b6113a0576004355f52600560205260405f205415611376576004355f52600560205260405f208054905f19820191821161030f5765ffffffffffff9161100b9161395b565b90549060031b1c1661101b6135fe565b808210156113585750505f5b8082521561132e57600282015460d01c42111561130457805161104f608435916064356135eb565b036112da5760a43561271061106e611066336145a2565b6084356135eb565b04036112b05761107c6135fe565b60208201526005820154602435106112865761109e6064356004840154613618565b60408201526004355f526004602052600160ff600960405f20015460101c166110c6816134fe565b14611264575b604081015191670de0b6b3a7640000928381029080820485149015171561030f576110fa90602435906135cd565b926060830193845260ff600983015460081c1660028110156109655760010361125757604083015181810291818304149015171561030f5761113f90604435906135cd565b60808301525b60808201928351600783019081541161122d576020946084356111f1575b600384016111746084358254613618565b9055602435600585015560443560068501555190555160088201556004604083015191015501516040517fdfb634c689d94c27824516a1fa03ef285dae8d316e897e63403182441d82ab6560043591806111e7606435604435602435846040919493926060820195825260208201520152565b0390a36001600255005b6112286001600160a01b03600186015416611210608435303384614c1d565b60a435906001600160a01b0360125416903390614c1d565b611163565b60046040517f29847030000000000000000000000000000000000000000000000000000000008152fd5b5082516080830152611145565b6004355f52600860205260405f2061127f6064358254613618565b90556110cc565b60046040517fa810589a000000000000000000000000000000000000000000000000000000008152fd5b60046040517f1fbbed95000000000000000000000000000000000000000000000000000000008152fd5b60046040517f6b5545d7000000000000000000000000000000000000000000000000000000008152fd5b60046040517f59710113000000000000000000000000000000000000000000000000000000008152fd5b60046040517fffbb7602000000000000000000000000000000000000000000000000000000008152fd5b6113619161364d565b62093a809081810180911161030f5704611027565b60046040517fb31fbda3000000000000000000000000000000000000000000000000000000008152fd5b60046040517f81387e3e000000000000000000000000000000000000000000000000000000008152fd5b506044356024351415610fc6565b60046040517f4b44b921000000000000000000000000000000000000000000000000000000008152fd5b60046040517fe5a74490000000000000000000000000000000000000000000000000000000008152fd5b5060443515610f9c565b346103235760806003193601126103235760243565ffffffffffff8116810361032357611461614fa0565b60ff60175416610bf3576001600160a01b036013541615610bc957604051610120810181811067ffffffffffffffff8211176118b4576040525f81525f60208201525f60408201525f60608201525f60808201525f60a08201525f60c08201525f60e08201525f610100820152600354600435101561074a576004355f5260046020526001600160a01b0360405f2054163303610720576044351580156118aa575b6114025765ffffffffffff821615611880576004355f52600560205260405f205415611376576004355f52600560205260405f208054805f1981011161030f5765ffffffffffff915f1961155892019061395b565b90549060031b1c1681526004355f526004602052600260405f20015460d01c4211156113045780516115886135fe565b1161132e576004355f526004602052600460405f20015460608201526004355f526004602052600160ff600960405f20015460101c166115c7816134fe565b14611867575b6044356115e665ffffffffffff841660608401516135eb565b036112da576064356127106116056115fd336145a2565b6044356135eb565b04036112b0576004355f5260046020526001600160a01b03600260405f2001541660208201526004355f52600460205261165c6001600160a01b03600160405f200154168060408401526044359030903390614c1d565b6116856001600160a01b03604083015116606435906001600160a01b0360125416903390614c1d565b805162093a809081810180911161030f578190048181029080820483149015171561030f5760808301526004355f526004602052600360405f20016116cd6044358254613618565b90556004355f526004602052600260405f200165ffffffffffff841665ffffffffffff825460a01c160165ffffffffffff811161030f5781547fffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffff1660a09190911b79ffffffffffff0000000000000000000000000000000000000000161790555f5b65ffffffffffff841681106117a4578365ffffffffffff6040519116815260443560208201527f196ca6579b22278c0854d0a67ac2a7fda3dbb66f74a19c2c55fa79c3a2e4bd9f604060043592a26001600255005b60808301515f52600a6020526117bf60043560405f2061391a565b6004355f5260046020526001600160a01b03600260405f200154165f52600b60205260405f2060808401515f526020526117fe60043560405f2061391a565b6004355f52600560205261182260405f2061181c6080860151614b86565b9061397b565b6004355f52600660205260405f2060808401515f5260205260405f20600160ff1982541617905560808301519082820180921161030f5760019160808501520161174f565b6004355f52600860205260405f205460608201526115cd565b60046040517fc82661ee000000000000000000000000000000000000000000000000000000008152fd5b5060643515611503565b634e487b7160e01b5f52604160045260245ffd5b34610323576020600319360112610323576001600160a01b036118e9613440565b6118f1614f2f565b16801561047c57805f52601460205260405f2060ff1981541690557f0153940c4f400a39a1ca7f4cd7e46be4b7d3793f6142184e505b8c4e1a8103785f80a2005b3461032357602080600319360112610323576004355f52600e815260405f209060405190818184549182815201935f52815f20915f905b828210611980576102f4856102e881890382613541565b835486529485019460019384019390910190611969565b34610323576020600319360112610323576004356119b3614f2f565b6101f481116103995760407ff9d94f1c6388837eee93d61a8020cf4d537953ce27f2a295d60ad96e6a6ec6a391600f549080600f5582519182526020820152a1005b346103235761014060031936011261032357611a0f613440565b611a17613456565b90611a2061346c565b91611a2961347b565b6084359160e4359360038510156103235761010435936003851015610323576101243567ffffffffffffffff811161032357611a699036906004016133f9565b959094611a74614fa0565b60ff60175416610bf3576001600160a01b036013541615610bc957602098611ad098849360405191611aa583613508565b611aae816134fe565b82525f8c830152611abe816134fe565b604082015260c4359660a435966139cb565b6001600255604051908152f35b346103235760406003193601126103235760043567ffffffffffffffff811161032357611b0e9036906004016133f9565b90611b17613456565b91611b20614fa0565b60ff60175416610bf3576001600160a01b0390816013541615610bc9578015610774578184161561047c575f925f915f5b818110611b8c57505050169182151580611b83575b611b72575b6001600255005b611b7b92614bc3565b808080611b6b565b50811515611b66565b611b978183856139bb565b35600354111561074a57611bac8183856139bb565b355f526020600481528560405f205416330361072057611bcd8284866139bb565b355f52600d9081815260405f205491611be78486886139bb565b355f5281525f604081205581611c02575b5050600101611b51565b946004829896611c138587896139bb565b355f52528887600160405f200154169188811690818414155f14611cc7575080611ca6575b505060019291611c489196613618565b965b7f182587d643fe0f1dce1eafe8f758d78a2cfe0f9acdd4653ec0ad97dc94c3102d611c9c611c798487896139bb565b604080516001600160a01b038e1681526020810195909552903593918291820190565b0390a29088611bf8565b600194935090611cb7929791614bc3565b5f94909150869088611c48611c38565b979050611cda9250600194939150613618565b96611c4a565b34610323575f6003193601126103235760206001600160a01b0360125416604051908152f35b346103235760208060031936011261032357611d20613440565b611d28614f2f565b6001600160a01b03809116805f526015835260ff60405f205416611de057604051907f70a082310000000000000000000000000000000000000000000000000000000082523060048301528382602481845afa918215611dd5575f92611da6575b50811561140257611d9d925f541690614bc3565b60405160018152f35b9091508381813d8311611dce575b611dbe8183613541565b8101031261032357519084611d89565b503d611db4565b6040513d5f823e3d90fd5b60046040517f80eb2a01000000000000000000000000000000000000000000000000000000008152fd5b34610323576020600319360112610323576004355f526008602052602060405f2054604051908152f35b34610323576020611e4d611e473661342a565b9061365a565b604051908152f35b34610323575f60031936011261032357602060ff601754166040519015158152f35b3461032357604060031936011261032357600435611e93613456565b611e9b614fa0565b60ff60175416610bf3576001600160a01b0390816013541615610bc95760035483101561074a57825f5260046020528160405f2054163303610720578181161561047c57825f52600d60205260405f20905f8254925581611efd576001600255005b611f3782827f182587d643fe0f1dce1eafe8f758d78a2cfe0f9acdd4653ec0ad97dc94c3102d956004602052600160405f20015416614bc3565b604080516001600160a01b039290921682526020820192909252a280808080611b6b565b34610323575f6003193601126103235760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b34610323575f6003193601126103235760206001600160a01b035f5416604051908152f35b3461032357606060031936011261032357611fdc614fa0565b60ff60175416610bf357611fee614f2f565b62093a80600435048062093a8081020462093a80148115171561030f57600354602435101561074a576001600160a01b0360135416156125b75762093a80810215610b915761203b6135fe565b62093a80820211610b91576024355f52600460205260405f20600660205260405f2062093a8083025f5260205260ff60405f20541660048110156109655760020361258d576024355f52600760205260405f2062093a8083025f5260205260405f20546004820154906120ae818361364d565b916024355f52600c6020526001600160a01b0360405f20541690604435158061256f575b15612256575060205f916024358352600782526040832062093a808802845282528260408120556064604051809481937f657c5e8c000000000000000000000000000000000000000000000000000000008352602435600484015262093a808b0260248401528160448401525af1908115611dd5575f91612227575b50156121fd5760ff600984015460101c16612168816134fe565b806121e2575050612190612195926024355f52600d602052600460405f205491015490613618565b61364d565b6024355f52600d60205260405f20555b60405160443581527f761ac4569e5e4e02e15740915e4281941ef7c47913333b6414da71b1dd8b809a602062093a80602435940292a36001600255005b9091506121f8925062093a808402602435614941565b6121a5565b60046040517fcc48ad7f000000000000000000000000000000000000000000000000000000008152fd5b612249915060203d60201161224f575b6122418183613541565b8101906138ed565b8561214e565b503d612237565b6009859294950190815490600160ff61227f612271876145d7565b604435838760081c166148d0565b9360101c1661228d816134fe565b0361250b5750506024355f52600960205260405f2062093a8087025f526020526122d160ff60405f2054925460081c16826122c7856145d7565b9160443590614ae5565b9462093a808088020162093a8088021161030f576024355f52600560205260405f208054805f1981011161030f5765ffffffffffff915f1961231492019061395b565b90549060031b1c1662093a8080890201115f1461243b57612190612351926024355f52600d60205261234b8860405f20549261364d565b90613618565b6024355f52600d60205260405f20555b82828511612413575b5050505b6040517f657c5e8c00000000000000000000000000000000000000000000000000000000815260248035600483015262093a808502908201526044810183905290602090829060649082905f905af1908115611dd5575f916123f4575b50156121fd576024355f52600760205260405f2062093a8083025f5260205260405f20556121a5565b61240d915060203d60201161224f576122418183613541565b836123cb565b6001600160a01b03600161242a612433958861364d565b93015416614bc3565b83808261236a565b5050818411156124f45761245d6004820154612457848761364d565b9061364d565b670de0b6b3a764000080820290828204148215171561030f577ffb23ba90a733f32458900fa34afe764a756eb475000cd4858adc5d5111994ebd906124b56124a96008860154836135cd565b916007860154906135cd565b908060058601558160068601558360048601556124ec60405192839260243596846040919493926060820195825260208201520152565b0390a2612361565b612506600482015461234b868561364d565b61245d565b6125309250612190906024979297355f52600d60205261234b8860405f20549261364d565b6024355f52600d60205260405f205582828511612550575b50505061236e565b6001600160a01b03600161242a612567958861364d565b838082612548565b50600160ff600987015460101c16612586816134fe565b14156120d2565b60046040517f226e5a6a000000000000000000000000000000000000000000000000000000008152fd5b60046040517fdc1669a3000000000000000000000000000000000000000000000000000000008152fd5b34610323576040600319360112610323576125fa613440565b62093a8090816024350482810292818404149015171561030f576001600160a01b03165f52602090600b825260405f20905f52815260405f209060405190818184549182815201935f52815f20915f905b828210612662576102f4856102e881890382613541565b83548652948501946001938401939091019061264b565b34610323576020600319360112610323576001600160a01b0361269a613440565b165f526016602052602060405f2054604051908152f35b34610323575f600319360112610323576001546001600160a01b0380821690813303612755575f92828454927fffffffffffffffffffffffff00000000000000000000000000000000000000009382858216178755167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08680a3166001557fb3d55174552271a4f1aaf36b72f50381e892171636b3fb5447fe00e995e7a37b8280a3005b60046040517f2f02da58000000000000000000000000000000000000000000000000000000008152fd5b34610323576020600319360112610323576001600160a01b036127a0613440565b165f526010602052602060405f2054604051908152f35b3461032357602080600319360112610323576127d1614fa0565b60ff60175416610bf3576001600160a01b03806013541615610bc957335f526014825260ff60405f2054161590816128ff575b50610720575f8062093a8092836004350484810294818604149015171561030f578315610b91576128336135fe565b841015610b6757835f52600a815260405f205415610b3d57835f52600a815260405f20936040518086848298549384815201905f52845f20925f5b868282106128e95750505061288592500386613541565b84515f915b8183106128a557604086868660016002558351928352820152f35b9091946128bc6128b58789613625565b5183614678565b156128d6576128cc6001916145c9565b955b01919061288a565b94936128e36001916145c9565b946128ce565b855484526001958601958b95509301920161286e565b90505f541633141582612804565b34610323575f60031936011261032357612925614f2f565b5f6001600160a01b0381547fffffffffffffffffffffffff000000000000000000000000000000000000000081168355167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a3005b34610323576020600319360112610323576129956135fe565b62093a80810180911161030f57611e4d6020916004359061365a565b34610323575f600319360112610323576020600f54604051908152f35b34610323575f600319360112610323576020601854604051908152f35b34610323576129f93661342a565b905f52600760205260405f20905f52602052602060405f2054604051908152f35b34610323576020600319360112610323576004355f52600c60205260206001600160a01b0360405f205416604051908152f35b3461032357602060031936011261032357600435612a69614f2f565b80156103995760407f3933dd2114106692c792a24b4d8509cf9c5e4a41a71315c9f2b593085ea76e8991601154908060115582519182526020820152a1005b346103235761016060031936011261032357612ac2613440565b612aca613456565b612ad261346c565b90612adb61347b565b92610104359260038410156103235761012435936003851015610323576101443567ffffffffffffffff811161032357612b199036906004016133f9565b949093612b24614fa0565b60ff60175416610bf3576001600160a01b036013541615610bc957602097611ad09760405194612b5386613508565b612b5c816134fe565b855260018a860152612b6d816134fe565b604085015260e4359460c4359460a43594608435946139cb565b3461032357604060031936011261032357600435612ba3613456565b612bab614fa0565b60ff806017541615610cb55760185462127500810180911161030f574210612e345760035483101561074a57825f52602091600483526001600160a01b03928360405f2054163303610720578382161561047c57845f52600d815260405f20925f8454945560059081835260405f20916040519084829485928282549586815201915f52825f20945f915b816004840110612de05750612c7995549184828210612dc8575b828210612dad575b828210612d92575b828210612d77575b5010612d61575b5090500383613541565b815190875f526004845260405f20925f5b838110612d08575050505060045f91015582612ca7576001600255005b82827f947a9dc0c5e62cc9756634ec0a89afea37eb0305933925040b9bda8200440020956004612ce394895f5252600160405f20015416614bc3565b604080516001600160a01b039290921682526020820192909252a28080808080611b6b565b895f526006865260405f2065ffffffffffff612d248385613625565b51165f5286528260405f205416906004821015610965576001809214612d4b575b01612c8a565b97612d5b90600487015490613618565b97612d45565b60c01c65ffffffffffff1681528691018b612c6f565b6001919465ffffffffffff8560901c16815201930184612c68565b6001919465ffffffffffff8560601c16815201930184612c60565b6001919465ffffffffffff8560301c16815201930184612c58565b6001919465ffffffffffff8516815201930184612c50565b9450925090600160a08592875465ffffffffffff908181168352818160301c168d84015260608282821c166040850152828260901c169084015260c01c166080820152019501910187928794959295612c36565b60046040517f24dab61b000000000000000000000000000000000000000000000000000000008152fd5b34610323575f600319360112610323576020600354604051908152f35b3461032357602060031936011261032357612e94613440565b612e9c614f2f565b601354906001600160a01b0390818316612f0d571690811561047c57817f4f8cfde3439a1a302c21ca51eec26086efbfd940b8c0279889fc6bb6e73ecc66927fffffffffffffffffffffffff00000000000000000000000000000000000000006020931617601355604051908152a1005b60046040517f0dc149f0000000000000000000000000000000000000000000000000000000008152fd5b34610323576020600319360112610323576004355f52600d602052602060405f2054604051908152f35b346103235760806003193601126103235767ffffffffffffffff60043581811161032357612f939036906004016133f9565b909160443581811161032357612fad9036906004016133f9565b909160643590811161032357612fc79036906004016133f9565b939092612fd2614fa0565b60ff60175416610bf3576001600160a01b036013541615610bc957335f52601460205260ff60405f20541615806131fe575b6107205762093a8092836024350494858581020485148615171561030f578684036131d4578184036131d4575f5b848110613040576001600255005b61304b81868b6139bb565b35600354111561074a576130608189846139bb565b35156131aa576130718184866139bb565b35156114025761308281868b6139bb565b355f52600660205260405f208688025f5260205260ff60405f20541660048110156109655760020361258d57856020886084875f8e8e6130fc896130f48f8e6130ed84836130d28280968c6139bb565b358b52600c8f526001600160a01b0360408c205416996139bb565b35986139bb565b35928c6139bb565b359060405198899788967f75d0d3800000000000000000000000000000000000000000000000000000000088526004880152026024860152604485015260648401525af1908115611dd5575f9161318b575b50156121fd5780613162600192878c6139bb565b355f52600660205260405f208789025f5260205260405f20600360ff1982541617905501613032565b6131a4915060203d60201161224f576122418183613541565b8a61314e565b60046040517f85ac2b99000000000000000000000000000000000000000000000000000000008152fd5b60046040517fc155fe21000000000000000000000000000000000000000000000000000000008152fd5b506001600160a01b035f5416331415613004565b34610323576132296132233661342a565b906143d6565b005b346103235760406003193601126103235767ffffffffffffffff6004358181116103235761325d9036906004016133f9565b91602435908111610323576132769036906004016133f9565b919091335f526020916014835260ff60405f205416158061337e575b610720578415610774578185036131d4575f5b8581106132ae57005b6001600160a01b0390816132cb6132c6838a876139bb565b614ad1565b161561047c576132dc8185886139bb565b351561039957816132f16132c6838a876139bb565b165f526015855260405f20916001928360ff198254161790556133158286896139bb565b35816133256132c6858c896139bb565b165f526016875260405f20557fb2fe06f7c1de1ec8e3329909cafefe4d305789d8438c9755943d504772d53730866133616132c6858c896139bb565b9261336d85898c6139bb565b35936040519485521692a2016132a5565b506001600160a01b035f5416331415613292565b34610323575f600319360112610323576020611e4d6135fe565b34610323576020600319360112610323576004355f52600460205260206001600160a01b0360405f205416604051908152f35b34610323575f600319360112610323576020906011548152f35b9181601f840112156103235782359167ffffffffffffffff8311610323576020808501948460051b01011161032357565b6003196040910112610323576004359060243590565b600435906001600160a01b038216820361032357565b602435906001600160a01b038216820361032357565b60443590811515820361032357565b6064359065ffffffffffff8216820361032357565b602090816040818301928281528551809452019301915f5b8281106134b6575050505090565b8351855293810193928101926001016134a8565b90604060031983011261032357600435916024359067ffffffffffffffff8211610323576134fa916004016133f9565b9091565b6003111561096557565b6060810190811067ffffffffffffffff8211176118b457604052565b610180810190811067ffffffffffffffff8211176118b457604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176118b457604052565b9060405161358f81613508565b80925460ff811661359f816134fe565b825260ff8160081c16600281101561096557602083015260101c60ff16906040906135c9836134fe565b0152565b81156135d7570490565b634e487b7160e01b5f52601260045260245ffd5b8181029291811591840414171561030f57565b62093a8080420481810291818304149015171561030f5790565b9190820180921161030f57565b80518210156136395760209160051b010190565b634e487b7160e01b5f52603260045260245ffd5b9190820391821161030f57565b905f908082526020600e8152604090818420928251808584829754938481520190885284882092885b868282106138955750505061369a92500385613541565b8452600494858252828520956001600160a01b039660ff60098960028401541692015416977f000000000000000000000000000000000000000000000000000000000000000016918796805189915b8183106137e9575050506136fc896134fe565b600289146137dd5785517f734648440000000000000000000000000000000000000000000000000000000081526001600160a01b0390921690820190815260208101939093529291829184918290819060400103915afa9283156137d4575084926137a2575b505060018194613771816134fe565b1461377d575b50505090565b919250908082106137935750505b5f8080613777565b61379d925061364d565b61378b565b90809250813d83116137cd575b6137b98183613541565b810103126137c957515f80613762565b8280fd5b503d6137af565b513d86823e3d90fd5b50505050505091505090565b9091986137f68a83613625565b518951907f454640300000000000000000000000000000000000000000000000000000000082528582015285602482015287604482015288816064818a5afa90811561388b578c91613859575b5061385090600192613618565b990191906136e9565b90508881813d8311613884575b6138708183613541565b8101031261388057516001613843565b8b80fd5b503d613866565b8a513d8e823e3d90fd5b855484526001958601958a955093019201613683565b6040519060c0820182811067ffffffffffffffff8211176118b4576040528160a05f918281528260208201528260408201528260608201528260808201520152565b90816020910312610323575180151581036103235790565b8054821015613639575f5260205f2001905f90565b90815491680100000000000000008310156118b4578261394291600161395995018155613905565b9091905f1983549160031b92831b921b1916179055565b565b9190918054831015613639575f526006600560205f208185040193060290565b8054680100000000000000008110156118b45761399d9160018201815561395b565b819291549060031b9165ffffffffffff809116831b921b1916179055565b91908110156136395760051b0190565b969a9993959098929491946139de6138ab565b98338a526001600160a01b03891615801561430c575b61047c576040517faa79979b0000000000000000000000000000000000000000000000000000000081526001600160a01b038a1660048201526020816024816001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165afa908115611dd5575f916142ed575b5080614252575b15614228576001600160a01b038b165f52601560205260ff60405f20541615610e7e5765ffffffffffff8816156141fe57831580156141f6575b80156141ee575b80156141e6575b611402576001600160a01b038b165f52601660205260405f205484106141bc578284116113d857602087015160028110156109655715806141b2575b6113a05780612710613b138f613b0d336145a2565b906135eb565b04036112b05760026040880151613b29816134fe565b613b32816134fe565b1461418857613b4965ffffffffffff89168e6135cd565b8060208c015280670de0b6b3a7640000810204670de0b6b3a7640000148115171561030f5783670de0b6b3a7640000613b8292026135cd565b8060408c01526011541161415e57613bd490613bb48e8d6001600160a01b038e51166001600160a01b03309216614c1d565b8b6001600160a01b038c51166001600160a01b0380601254169216614c1d565b613bdc6135fe565b908160608b0152614142575b506003549a60018c016003556001600160a01b0389511691613c0d60608b0151614b86565b9360208b015160408c01519381670de0b6b3a7640000810204670de0b6b3a7640000148215171561030f5765ffffffffffff613c6e978f8e6001600160a01b038092613c6389670de0b6b3a76400008a026135cd565b9b6040519d8e613524565b8d521660208c01521660408a0152818d1660608a015216608088015260a087015260c086015260e085015261010084015261012083015261014082015283610160820152895f52600460205261016060405f20916001600160a01b038151167fffffffffffffffffffffffff0000000000000000000000000000000000000000845416178355600183016001600160a01b036020830151167fffffffffffffffffffffffff0000000000000000000000000000000000000000825416179055600283016001600160a01b036040830151167fffffffffffffffffffffffff0000000000000000000000000000000000000000825416178155613dc265ffffffffffff60608401511682907fffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffff79ffffffffffff000000000000000000000000000000000000000083549260a01b169116179055565b608082015179ffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffff000000000000000000000000000000000000000000000000000083549260d01b16911617905560a0810151600384015560c0810151600484015560e0810151600584015561010081015160068401556101208101516007840155610140810151600884015501518051613e5b816134fe565b613e64816134fe565b6009830154916020810151906002821015610965576009937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000060ff62ff0000604061ff00950151613eb4816134fe565b613ebd816134fe565b60101b1695169116179160081b1617179101556001600160a01b0360135416895f52600c60205260405f20907fffffffffffffffffffffffff0000000000000000000000000000000000000000825416179055606086015160808701525f5b65ffffffffffff8516811061409757508160a087015281614048575b505060406001910151613f4a816134fe565b613f53816134fe565b14614031575b5f60206001600160a01b03601354166044604051809481937f58ad40640000000000000000000000000000000000000000000000000000000083528c60048401526001600160a01b038b1660248401525af1908115611dd5575f91614012575b50156121fd5765ffffffffffff60606001600160a01b0394015191846040519616865216602085015260408401521690837f1a2d9584765fd3a320beb3104d8e3ea37336abaa62152ebff24a0b8135c2823560603393a4565b61402b915060203d60201161224f576122418183613541565b5f613fb9565b6020830151865f52600860205260405f2055613f59565b94919693909592600a87116106f6575f5b60a0840151811015614083578061407d6140766001938b8b6139bb565b358c61431d565b01614059565b509295509295600191945060409091613f38565b60808701515f52600a6020526140b08a60405f2061391a565b6001600160a01b0386165f52600b60205260405f2060808801515f526020526140dc8a60405f2061391a565b895f52600660205260405f2060808801515f5260205260405f20600160ff19825416179055895f52600560205261411d60405f2061181c60808a0151614b86565b60808701519062093a808201821161030f5762093a8060019201608089015201613f1c565b62093a808101811161030f5762093a800160608901525f613be8565b60046040517f85f0de9f000000000000000000000000000000000000000000000000000000008152fd5b60046040517f9135dc13000000000000000000000000000000000000000000000000000000008152fd5b5082841415613af8565b60046040517f59ffd6f0000000000000000000000000000000000000000000000000000000008152fd5b508015613abc565b508c15613ab5565b508215613aae565b60046040517f6a0a01cc000000000000000000000000000000000000000000000000000000008152fd5b60046040517fcbb4b772000000000000000000000000000000000000000000000000000000008152fd5b506040517f1703e5f90000000000000000000000000000000000000000000000000000000081526001600160a01b038a1660048201526020816024816001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165afa908115611dd5575f916142ce575b50613a74565b6142e7915060203d60201161224f576122418183613541565b5f6142c8565b614306915060203d60201161224f576122418183613541565b5f613a6d565b506001600160a01b038b16156139f4565b91905f92808452602093600e85526040918282208351808289829454938481520190865289862092865b8b8282106143c05750505061435e92500382613541565b805190835b82811061437e575050506139599495600e918352522061391a565b866143898284613625565b511461439757600101614363565b600486517fa3d582ec000000000000000000000000000000000000000000000000000000008152fd5b8554845260019586019587955093019201614347565b905f9180835260049260208481526001600160a01b036040908060028386200154161561457a578484528683528184205416330361455257838352600e808352818420928251808583829754938481520190885283882092885b8582821061453c5750505061444792500385613541565b835193855b85811061445f5750505050505050505050565b8861446a8284613625565b51146144785760010161444c565b9091929394959697505f199998999586810190811161452957808203614501575b505050858552528220805480156144ee577f72a0d025e6eac8ca921e2325e32ff0f17354f246b9024647bf4c1ab67437a859949596508201916144dc8383613905565b909182549160031b1b191690555580a2565b602484603189634e487b7160e01b835252fd5b6145116139429161452194613625565b5191898952858552868920613905565b5f8080614499565b60248860118d634e487b7160e01b835252fd5b855484526001958601958a955093019201614430565b8590517f2af07d20000000000000000000000000000000000000000000000000000000008152fd5b8682517f62d0ece0000000000000000000000000000000000000000000000000000000008152fd5b6001600160a01b03165f9081526010602052604090205480156145c25790565b50600f5490565b5f19811461030f5760010190565b906040516145e481613524565b610160614673600983956001600160a01b038082541686528060018301541660208701526002820154908116604087015265ffffffffffff8160a01c16606087015260d01c6080860152600381015460a0860152600481015460c0860152600581015460e086015260068101546101008601526007810154610120860152600881015461014086015201613582565b910152565b905f8181526020600681526040808320858452825260ff818420541691600492838110156148bd576001036148b4578484528281526146b88285206145d7565b92858552600682528285208786528252828520600260ff1982541617905562093a8087018088116148a157866146ed9161365a565b80614741575050508161473a9161016060c07f8deaacba1e0606cd9e75aec4cb0fefc4c1df8d262d3a05f83107f8c067e8891f96950151925b01510151614733816134fe565b8686614941565b80a3600190565b8261016086949601510151600281101561488e576147609184916148d0565b9086865260078552838620888752855281848720556147838260c085015161364d565b94878752600c81526001600160a01b03908186892054169284828c8c6147e58b5194859384937fc25a4d9e0000000000000000000000000000000000000000000000000000000085528885016040919493926060820195825260208201520152565b03818d895af1908115614884578a91614867575b5015614840575061473a959361016093869361483b937f8deaacba1e0606cd9e75aec4cb0fefc4c1df8d262d3a05f83107f8c067e8891f9b9a98015116614bc3565b614726565b86517fcc48ad7f000000000000000000000000000000000000000000000000000000008152fd5b61487e9150833d851161224f576122418183613541565b5f6147f9565b88513d8c823e3d90fd5b602487602185634e487b7160e01b835252fd5b602486601184634e487b7160e01b835252fd5b50505091505090565b602485602186634e487b7160e01b835252fd5b9190916101408201518310156149375760028110156109655761490957670de0b6b3a76400009160e0614905920151906135eb565b0490565b610120810151821161492e57670de0b6b3a764000091610100614905920151906135eb565b60c09150015190565b5060c09150015190565b929091600190614950816134fe565b03614ab85762093a80820180831161030f575f928484526020600581526040928386208054905f198201918211614aa45765ffffffffffff916149929161395b565b90549060031b1c1610614a89578585526004815260088386209160048301938454906009835286892090895282528588205587875252828520549380614a78575b50670de0b6b3a764000094858502958587041485151715614a64575083927ffb23ba90a733f32458900fa34afe764a756eb475000cd4858adc5d5111994ebd9594614a5f9383614a38614a2c600860069701548a6135cd565b986007830154906135cd565b94818960058894015501555551938493846040919493926060820195825260208201520152565b0390a2565b80634e487b7160e01b602492526011600452fd5b614a829194613618565b925f6149d3565b600d915084929395614aa095525220918254613618565b9055565b602488634e487b7160e01b81526011600452fd5b9190505f52600d602052614aa060405f20918254613618565b356001600160a01b03811681036103235790565b928115614b7e576101408101938451831015614b7657600281101561096557614b335750670de0b6b3a7640000928383029280840485149015171561030f5761490592613b0d9151906135cd565b90919250670de0b6b3a7640000918284029184830484148515171561030f57610120614b639101928351906135cd565b91518111613777576149059293506135eb565b505050905090565b505050505f90565b65ffffffffffff90818111614b99571690565b60046040517ffef46c2d000000000000000000000000000000000000000000000000000000008152fd5b6040517fa9059cbb0000000000000000000000000000000000000000000000000000000060208201526001600160a01b0392909216602483015260448083019390935291815261395991614c18606483613541565b614c88565b9290604051927f23b872dd0000000000000000000000000000000000000000000000000000000060208501526001600160a01b03809216602485015216604483015260648201526064815260a081019181831067ffffffffffffffff8411176118b457613959926040525b6001600160a01b039092919216604051604081019367ffffffffffffffff94828110868211176118b4576040526020928383527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564848401525f808386829551910182855af1903d15614e02573d968711614dee57614d4594959660405190614d37887fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160183613541565b81528093873d92013e614e0f565b80519081614d5257505050565b8280614d629383010191016138ed565b15614d6a5750565b608490604051907f08c379a00000000000000000000000000000000000000000000000000000000082526004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f742073756363656564000000000000000000000000000000000000000000006064820152fd5b602483634e487b7160e01b81526041600452fd5b9150614d45939495506060915b91929015614e8a5750815115614e23575090565b3b15614e2c5790565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152fd5b825190915015614e9d5750805190602001fd5b604051907f08c379a00000000000000000000000000000000000000000000000000000000082528160208060048301528251928360248401525f5b848110614f18575050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f835f604480968601015201168101030190fd5b818101830151868201604401528593508201614ed8565b6001600160a01b035f54163303614f4257565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602060248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152fd5b6002805414614faf5760028055565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152fdfea26469706673582212204d1e97b4bc87beaf62c3e428f7bc1c6506c752f4add2cfd3a728a69182b5a6e664736f6c63430008140033000000000000000000000000f365c45b6913be7ab74c970d9227b9d0dff44afb000000000000000000000000e2a7de3c3190afd79c49c8e8f2fa30ca78b97dfd
Deployed Bytecode
0x6080806040526004361015610012575f80fd5b5f3560e01c908163040916a4146133df57508063052de43f146133ac578063086146d2146133925780630881b8301461322b5780630eb67173146132125780631119548514612f61578063146bdb7d14612f3757806319ab453c14612e7b5780631e96917d14612e5e5780632f940c7014612b875780633ce455a814612aa85780633d72387714612a4d5780633da8a8d114612a1a578063482280d5146129eb578063511aa52d146129ce57806357ae1cec146129b15780636eaa04ef1461297c578063715018a61461290d5780637211a52b146127b757806377dd7cf71461277f57806379ba5097146126b157806379dc77d014612679578063897ec1c7146125e15780638a6ce1cb14611fc35780638da5cb5b14611f9e5780638ebf2fd614611f5b5780638f4c72d814611e775780638fe8a10114611e555780639655070f14611e34578063975e517314611e0a5780639e8c708e14611d06578063a6c93d7d14611ce0578063a8a7e38614611add578063a8cf56f9146119f5578063aa0b598814611997578063aa412da814611932578063ac18de43146118c8578063ac4884ac14611436578063b486d07314610f29578063b5eee72214610ebc578063ba82c89714610de7578063bc30a61814610d40578063bd7ddd0614610d05578063bfe1092814610cdf578063c6c02cac14610c1d578063d52f113514610a3e578063daf9c21014610a01578063dc34a15114610979578063e085f98014610872578063e30c39781461084c578063eca5d0ca1461079e578063ed8de71914610644578063f29e9aa7146104a6578063f2fde38b146103c3578063f49c1d24146103275763f998114014610284575f80fd5b34610323576020806003193601126103235762093a80806004350481810291818304149015171561030f575f52600a815260405f209060405190818184549182815201935f52815f20915f905b8282106102f8576102f4856102e881890382613541565b60405191829182613490565b0390f35b8354865294850194600193840193909101906102d1565b634e487b7160e01b5f52601160045260245ffd5b5f80fd5b3461032357604060031936011261032357610340613440565b6024359061034c614f2f565b600f5482116103995760206001600160a01b037f6aade5fcd818524835ddc10e56891b8efefe8fae5d5817ff5e2c630d41fad354921692835f52601082528060405f2055604051908152a2005b60046040517f613970e0000000000000000000000000000000000000000000000000000000008152fd5b34610323576020600319360112610323576103dc613440565b6103e4614f2f565b6001600160a01b0380911690811561047c57805f5416821461045257600154827fffffffffffffffffffffffff0000000000000000000000000000000000000000821617600155167fb3d55174552271a4f1aaf36b72f50381e892171636b3fb5447fe00e995e7a37b5f80a3005b60046040517fd5e889bf000000000000000000000000000000000000000000000000000000008152fd5b60046040517f9fabe1c1000000000000000000000000000000000000000000000000000000008152fd5b3461032357602080600319360112610323576004355f5260058082528160405f2091604051908192839181865494858152019081965f52825f20945f915b8160048401106105f0575061052a955491848282106105d8575b8282106105bd575b8282106105a2575b828210610587575b5010610571575b5090509392930383613541565b60405192839281840190828552518091526040840192915f5b82811061055257505050500390f35b835165ffffffffffff1685528695509381019392810192600101610543565b60c01c65ffffffffffff1681528691018761051d565b6001919465ffffffffffff8560901c16815201930184610516565b6001919465ffffffffffff8560601c1681520193018461050e565b6001919465ffffffffffff8560301c16815201930184610506565b6001919465ffffffffffff85168152019301846104fe565b9450925090600160a08592875465ffffffffffff908181168352818160301c168d84015260608282821c166040850152828260901c169084015260c01c1660808201520195019101879286949592956104e4565b3461032357610652366134ca565b801561077457825f5260046020526001600160a01b0380600260405f200154161561074a57835f52600460205260405f205416330361072057825f52600e602052600a6106a360405f205483613618565b116106f6575f5b8181106106d857837f72a0d025e6eac8ca921e2325e32ff0f17354f246b9024647bf4c1ab67437a8595f80a2005b806106f06106e960019385876139bb565b358661431d565b016106aa565b60046040517f7dce6cb8000000000000000000000000000000000000000000000000000000008152fd5b60046040517f2af07d20000000000000000000000000000000000000000000000000000000008152fd5b60046040517f62d0ece0000000000000000000000000000000000000000000000000000000008152fd5b60046040517f521299a9000000000000000000000000000000000000000000000000000000008152fd5b34610323576020600319360112610323576107b7613440565b6107bf614f2f565b6001600160a01b03808216801561047c576108477f9e1cbba858790e137077d865d7e4df5017a55524ea53a66b2c20ec665167d8d693601254927fffffffffffffffffffffffff000000000000000000000000000000000000000084161760125560405193849316839060209093929360408301946001600160a01b03809216845216910152565b0390a1005b34610323575f6003193601126103235760206001600160a01b0360015416604051908152f35b34610323576020600319360112610323576004355f52600460205260405f206001600160a01b0390818154169082600182015416926002820154600383015460048401546005850154916006860154936007870154956108d9600960088a01549901613582565b99604051998a5260208a01528116604089015265ffffffffffff8160a01c16606089015260d01c608088015260a087015260c086015260e08501526101008401526101208301526101408201528151610931816134fe565b61016082015260208201516002811015610965576101c092604091610180840152015161095d816134fe565b6101a0820152f35b634e487b7160e01b5f52602160045260245ffd5b34610323575f60031936011261032357610991614f2f565b60175460ff81166109d75760ff196001911617601755426018557ff4a991ad9e7f9711696f7bd41529beb4c470d75788573535d4ca3f0857c79ce86020604051428152a1005b60046040517f7a480252000000000000000000000000000000000000000000000000000000008152fd5b34610323576020600319360112610323576001600160a01b03610a22613440565b165f526015602052602060ff60405f2054166040519015158152f35b3461032357610a4c366134ca565b610a57929192614fa0565b60ff60175416610bf3576001600160a01b03806013541615610bc957335f52601460205260ff60405f205416159081610bbb575b50610720575f915f9162093a8080920482810292818404149015171561030f578015610774578115610b9157610abf6135fe565b821015610b6757815f52600a60205260405f205415610b3d57905f915b808310610af85760408585600160025582519182526020820152f35b909193610b10610b098684896139bb565b3584614678565b15610b2a57610b206001916145c9565b945b019190610adc565b9392610b376001916145c9565b93610b22565b60046040517ffbbfe1b6000000000000000000000000000000000000000000000000000000008152fd5b60046040517f9bcf46f4000000000000000000000000000000000000000000000000000000008152fd5b60046040517f17479ac8000000000000000000000000000000000000000000000000000000008152fd5b90505f541633141584610a8b565b60046040517f87138d5c000000000000000000000000000000000000000000000000000000008152fd5b60046040517f0f8eeedb000000000000000000000000000000000000000000000000000000008152fd5b34610323575f60031936011261032357610c35614f2f565b60175460ff811615610cb55760185462127500810180911161030f57421015610c8b5760ff19166017557f62c91cdd7db0a8e05658629214f78fbbbc0708b9698bf57892027841dc8899c16020604051428152a1005b60046040517fc905adb1000000000000000000000000000000000000000000000000000000008152fd5b60046040517f81e21bc8000000000000000000000000000000000000000000000000000000008152fd5b34610323575f6003193601126103235760206001600160a01b0360135416604051908152f35b3461032357610d133661342a565b905f52600660205260405f20905f5260205260ff60405f2054166040516004821015610965576020918152f35b3461032357602060031936011261032357610d59613440565b610d61614f2f565b6001600160a01b0380911690811561047c57601380547fffffffffffffffffffffffff00000000000000000000000000000000000000008116841790915560408051929091166001600160a01b03908116835290921660208201527f111a961d91cf441fe07e7bfddc128b30ab56974d1a76851e969e0642fdb2dd509181908101610847565b3461032357604060031936011261032357610e00613440565b60243590335f52601460205260ff60405f2054161580610ea8575b610720576001600160a01b031690815f52601560205260ff60405f20541615610e7e5780156103995760207f0b431c44bb4597805cf43550dbfc7dd3b2a6752c10eef974a53dba1c7e25ffa691835f52601682528060405f2055604051908152a2005b60046040517ff84835a0000000000000000000000000000000000000000000000000000000008152fd5b506001600160a01b035f5416331415610e1b565b34610323576020600319360112610323576001600160a01b03610edd613440565b610ee5614f2f565b16801561047c57805f52601460205260405f20600160ff198254161790557f8e6f8d1a4c834c03bc5c8f9dedff078a6fd2904f7a94b17df04efb916aaba72d5f80a2005b346103235760c060031936011261032357610f42614fa0565b60ff60175416610bf3576001600160a01b036013541615610bc957610f656138ab565b6004355f52600460205260405f2090600354600435101561074a576001600160a01b0382541633036107205760243515801561142c575b61140257604435602435116113d85760ff600983015460081c1660028110156109655715806113ca575b6113a0576004355f52600560205260405f205415611376576004355f52600560205260405f208054905f19820191821161030f5765ffffffffffff9161100b9161395b565b90549060031b1c1661101b6135fe565b808210156113585750505f5b8082521561132e57600282015460d01c42111561130457805161104f608435916064356135eb565b036112da5760a43561271061106e611066336145a2565b6084356135eb565b04036112b05761107c6135fe565b60208201526005820154602435106112865761109e6064356004840154613618565b60408201526004355f526004602052600160ff600960405f20015460101c166110c6816134fe565b14611264575b604081015191670de0b6b3a7640000928381029080820485149015171561030f576110fa90602435906135cd565b926060830193845260ff600983015460081c1660028110156109655760010361125757604083015181810291818304149015171561030f5761113f90604435906135cd565b60808301525b60808201928351600783019081541161122d576020946084356111f1575b600384016111746084358254613618565b9055602435600585015560443560068501555190555160088201556004604083015191015501516040517fdfb634c689d94c27824516a1fa03ef285dae8d316e897e63403182441d82ab6560043591806111e7606435604435602435846040919493926060820195825260208201520152565b0390a36001600255005b6112286001600160a01b03600186015416611210608435303384614c1d565b60a435906001600160a01b0360125416903390614c1d565b611163565b60046040517f29847030000000000000000000000000000000000000000000000000000000008152fd5b5082516080830152611145565b6004355f52600860205260405f2061127f6064358254613618565b90556110cc565b60046040517fa810589a000000000000000000000000000000000000000000000000000000008152fd5b60046040517f1fbbed95000000000000000000000000000000000000000000000000000000008152fd5b60046040517f6b5545d7000000000000000000000000000000000000000000000000000000008152fd5b60046040517f59710113000000000000000000000000000000000000000000000000000000008152fd5b60046040517fffbb7602000000000000000000000000000000000000000000000000000000008152fd5b6113619161364d565b62093a809081810180911161030f5704611027565b60046040517fb31fbda3000000000000000000000000000000000000000000000000000000008152fd5b60046040517f81387e3e000000000000000000000000000000000000000000000000000000008152fd5b506044356024351415610fc6565b60046040517f4b44b921000000000000000000000000000000000000000000000000000000008152fd5b60046040517fe5a74490000000000000000000000000000000000000000000000000000000008152fd5b5060443515610f9c565b346103235760806003193601126103235760243565ffffffffffff8116810361032357611461614fa0565b60ff60175416610bf3576001600160a01b036013541615610bc957604051610120810181811067ffffffffffffffff8211176118b4576040525f81525f60208201525f60408201525f60608201525f60808201525f60a08201525f60c08201525f60e08201525f610100820152600354600435101561074a576004355f5260046020526001600160a01b0360405f2054163303610720576044351580156118aa575b6114025765ffffffffffff821615611880576004355f52600560205260405f205415611376576004355f52600560205260405f208054805f1981011161030f5765ffffffffffff915f1961155892019061395b565b90549060031b1c1681526004355f526004602052600260405f20015460d01c4211156113045780516115886135fe565b1161132e576004355f526004602052600460405f20015460608201526004355f526004602052600160ff600960405f20015460101c166115c7816134fe565b14611867575b6044356115e665ffffffffffff841660608401516135eb565b036112da576064356127106116056115fd336145a2565b6044356135eb565b04036112b0576004355f5260046020526001600160a01b03600260405f2001541660208201526004355f52600460205261165c6001600160a01b03600160405f200154168060408401526044359030903390614c1d565b6116856001600160a01b03604083015116606435906001600160a01b0360125416903390614c1d565b805162093a809081810180911161030f578190048181029080820483149015171561030f5760808301526004355f526004602052600360405f20016116cd6044358254613618565b90556004355f526004602052600260405f200165ffffffffffff841665ffffffffffff825460a01c160165ffffffffffff811161030f5781547fffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffff1660a09190911b79ffffffffffff0000000000000000000000000000000000000000161790555f5b65ffffffffffff841681106117a4578365ffffffffffff6040519116815260443560208201527f196ca6579b22278c0854d0a67ac2a7fda3dbb66f74a19c2c55fa79c3a2e4bd9f604060043592a26001600255005b60808301515f52600a6020526117bf60043560405f2061391a565b6004355f5260046020526001600160a01b03600260405f200154165f52600b60205260405f2060808401515f526020526117fe60043560405f2061391a565b6004355f52600560205261182260405f2061181c6080860151614b86565b9061397b565b6004355f52600660205260405f2060808401515f5260205260405f20600160ff1982541617905560808301519082820180921161030f5760019160808501520161174f565b6004355f52600860205260405f205460608201526115cd565b60046040517fc82661ee000000000000000000000000000000000000000000000000000000008152fd5b5060643515611503565b634e487b7160e01b5f52604160045260245ffd5b34610323576020600319360112610323576001600160a01b036118e9613440565b6118f1614f2f565b16801561047c57805f52601460205260405f2060ff1981541690557f0153940c4f400a39a1ca7f4cd7e46be4b7d3793f6142184e505b8c4e1a8103785f80a2005b3461032357602080600319360112610323576004355f52600e815260405f209060405190818184549182815201935f52815f20915f905b828210611980576102f4856102e881890382613541565b835486529485019460019384019390910190611969565b34610323576020600319360112610323576004356119b3614f2f565b6101f481116103995760407ff9d94f1c6388837eee93d61a8020cf4d537953ce27f2a295d60ad96e6a6ec6a391600f549080600f5582519182526020820152a1005b346103235761014060031936011261032357611a0f613440565b611a17613456565b90611a2061346c565b91611a2961347b565b6084359160e4359360038510156103235761010435936003851015610323576101243567ffffffffffffffff811161032357611a699036906004016133f9565b959094611a74614fa0565b60ff60175416610bf3576001600160a01b036013541615610bc957602098611ad098849360405191611aa583613508565b611aae816134fe565b82525f8c830152611abe816134fe565b604082015260c4359660a435966139cb565b6001600255604051908152f35b346103235760406003193601126103235760043567ffffffffffffffff811161032357611b0e9036906004016133f9565b90611b17613456565b91611b20614fa0565b60ff60175416610bf3576001600160a01b0390816013541615610bc9578015610774578184161561047c575f925f915f5b818110611b8c57505050169182151580611b83575b611b72575b6001600255005b611b7b92614bc3565b808080611b6b565b50811515611b66565b611b978183856139bb565b35600354111561074a57611bac8183856139bb565b355f526020600481528560405f205416330361072057611bcd8284866139bb565b355f52600d9081815260405f205491611be78486886139bb565b355f5281525f604081205581611c02575b5050600101611b51565b946004829896611c138587896139bb565b355f52528887600160405f200154169188811690818414155f14611cc7575080611ca6575b505060019291611c489196613618565b965b7f182587d643fe0f1dce1eafe8f758d78a2cfe0f9acdd4653ec0ad97dc94c3102d611c9c611c798487896139bb565b604080516001600160a01b038e1681526020810195909552903593918291820190565b0390a29088611bf8565b600194935090611cb7929791614bc3565b5f94909150869088611c48611c38565b979050611cda9250600194939150613618565b96611c4a565b34610323575f6003193601126103235760206001600160a01b0360125416604051908152f35b346103235760208060031936011261032357611d20613440565b611d28614f2f565b6001600160a01b03809116805f526015835260ff60405f205416611de057604051907f70a082310000000000000000000000000000000000000000000000000000000082523060048301528382602481845afa918215611dd5575f92611da6575b50811561140257611d9d925f541690614bc3565b60405160018152f35b9091508381813d8311611dce575b611dbe8183613541565b8101031261032357519084611d89565b503d611db4565b6040513d5f823e3d90fd5b60046040517f80eb2a01000000000000000000000000000000000000000000000000000000008152fd5b34610323576020600319360112610323576004355f526008602052602060405f2054604051908152f35b34610323576020611e4d611e473661342a565b9061365a565b604051908152f35b34610323575f60031936011261032357602060ff601754166040519015158152f35b3461032357604060031936011261032357600435611e93613456565b611e9b614fa0565b60ff60175416610bf3576001600160a01b0390816013541615610bc95760035483101561074a57825f5260046020528160405f2054163303610720578181161561047c57825f52600d60205260405f20905f8254925581611efd576001600255005b611f3782827f182587d643fe0f1dce1eafe8f758d78a2cfe0f9acdd4653ec0ad97dc94c3102d956004602052600160405f20015416614bc3565b604080516001600160a01b039290921682526020820192909252a280808080611b6b565b34610323575f6003193601126103235760206040516001600160a01b037f000000000000000000000000f365c45b6913be7ab74c970d9227b9d0dff44afb168152f35b34610323575f6003193601126103235760206001600160a01b035f5416604051908152f35b3461032357606060031936011261032357611fdc614fa0565b60ff60175416610bf357611fee614f2f565b62093a80600435048062093a8081020462093a80148115171561030f57600354602435101561074a576001600160a01b0360135416156125b75762093a80810215610b915761203b6135fe565b62093a80820211610b91576024355f52600460205260405f20600660205260405f2062093a8083025f5260205260ff60405f20541660048110156109655760020361258d576024355f52600760205260405f2062093a8083025f5260205260405f20546004820154906120ae818361364d565b916024355f52600c6020526001600160a01b0360405f20541690604435158061256f575b15612256575060205f916024358352600782526040832062093a808802845282528260408120556064604051809481937f657c5e8c000000000000000000000000000000000000000000000000000000008352602435600484015262093a808b0260248401528160448401525af1908115611dd5575f91612227575b50156121fd5760ff600984015460101c16612168816134fe565b806121e2575050612190612195926024355f52600d602052600460405f205491015490613618565b61364d565b6024355f52600d60205260405f20555b60405160443581527f761ac4569e5e4e02e15740915e4281941ef7c47913333b6414da71b1dd8b809a602062093a80602435940292a36001600255005b9091506121f8925062093a808402602435614941565b6121a5565b60046040517fcc48ad7f000000000000000000000000000000000000000000000000000000008152fd5b612249915060203d60201161224f575b6122418183613541565b8101906138ed565b8561214e565b503d612237565b6009859294950190815490600160ff61227f612271876145d7565b604435838760081c166148d0565b9360101c1661228d816134fe565b0361250b5750506024355f52600960205260405f2062093a8087025f526020526122d160ff60405f2054925460081c16826122c7856145d7565b9160443590614ae5565b9462093a808088020162093a8088021161030f576024355f52600560205260405f208054805f1981011161030f5765ffffffffffff915f1961231492019061395b565b90549060031b1c1662093a8080890201115f1461243b57612190612351926024355f52600d60205261234b8860405f20549261364d565b90613618565b6024355f52600d60205260405f20555b82828511612413575b5050505b6040517f657c5e8c00000000000000000000000000000000000000000000000000000000815260248035600483015262093a808502908201526044810183905290602090829060649082905f905af1908115611dd5575f916123f4575b50156121fd576024355f52600760205260405f2062093a8083025f5260205260405f20556121a5565b61240d915060203d60201161224f576122418183613541565b836123cb565b6001600160a01b03600161242a612433958861364d565b93015416614bc3565b83808261236a565b5050818411156124f45761245d6004820154612457848761364d565b9061364d565b670de0b6b3a764000080820290828204148215171561030f577ffb23ba90a733f32458900fa34afe764a756eb475000cd4858adc5d5111994ebd906124b56124a96008860154836135cd565b916007860154906135cd565b908060058601558160068601558360048601556124ec60405192839260243596846040919493926060820195825260208201520152565b0390a2612361565b612506600482015461234b868561364d565b61245d565b6125309250612190906024979297355f52600d60205261234b8860405f20549261364d565b6024355f52600d60205260405f205582828511612550575b50505061236e565b6001600160a01b03600161242a612567958861364d565b838082612548565b50600160ff600987015460101c16612586816134fe565b14156120d2565b60046040517f226e5a6a000000000000000000000000000000000000000000000000000000008152fd5b60046040517fdc1669a3000000000000000000000000000000000000000000000000000000008152fd5b34610323576040600319360112610323576125fa613440565b62093a8090816024350482810292818404149015171561030f576001600160a01b03165f52602090600b825260405f20905f52815260405f209060405190818184549182815201935f52815f20915f905b828210612662576102f4856102e881890382613541565b83548652948501946001938401939091019061264b565b34610323576020600319360112610323576001600160a01b0361269a613440565b165f526016602052602060405f2054604051908152f35b34610323575f600319360112610323576001546001600160a01b0380821690813303612755575f92828454927fffffffffffffffffffffffff00000000000000000000000000000000000000009382858216178755167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08680a3166001557fb3d55174552271a4f1aaf36b72f50381e892171636b3fb5447fe00e995e7a37b8280a3005b60046040517f2f02da58000000000000000000000000000000000000000000000000000000008152fd5b34610323576020600319360112610323576001600160a01b036127a0613440565b165f526010602052602060405f2054604051908152f35b3461032357602080600319360112610323576127d1614fa0565b60ff60175416610bf3576001600160a01b03806013541615610bc957335f526014825260ff60405f2054161590816128ff575b50610720575f8062093a8092836004350484810294818604149015171561030f578315610b91576128336135fe565b841015610b6757835f52600a815260405f205415610b3d57835f52600a815260405f20936040518086848298549384815201905f52845f20925f5b868282106128e95750505061288592500386613541565b84515f915b8183106128a557604086868660016002558351928352820152f35b9091946128bc6128b58789613625565b5183614678565b156128d6576128cc6001916145c9565b955b01919061288a565b94936128e36001916145c9565b946128ce565b855484526001958601958b95509301920161286e565b90505f541633141582612804565b34610323575f60031936011261032357612925614f2f565b5f6001600160a01b0381547fffffffffffffffffffffffff000000000000000000000000000000000000000081168355167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a3005b34610323576020600319360112610323576129956135fe565b62093a80810180911161030f57611e4d6020916004359061365a565b34610323575f600319360112610323576020600f54604051908152f35b34610323575f600319360112610323576020601854604051908152f35b34610323576129f93661342a565b905f52600760205260405f20905f52602052602060405f2054604051908152f35b34610323576020600319360112610323576004355f52600c60205260206001600160a01b0360405f205416604051908152f35b3461032357602060031936011261032357600435612a69614f2f565b80156103995760407f3933dd2114106692c792a24b4d8509cf9c5e4a41a71315c9f2b593085ea76e8991601154908060115582519182526020820152a1005b346103235761016060031936011261032357612ac2613440565b612aca613456565b612ad261346c565b90612adb61347b565b92610104359260038410156103235761012435936003851015610323576101443567ffffffffffffffff811161032357612b199036906004016133f9565b949093612b24614fa0565b60ff60175416610bf3576001600160a01b036013541615610bc957602097611ad09760405194612b5386613508565b612b5c816134fe565b855260018a860152612b6d816134fe565b604085015260e4359460c4359460a43594608435946139cb565b3461032357604060031936011261032357600435612ba3613456565b612bab614fa0565b60ff806017541615610cb55760185462127500810180911161030f574210612e345760035483101561074a57825f52602091600483526001600160a01b03928360405f2054163303610720578382161561047c57845f52600d815260405f20925f8454945560059081835260405f20916040519084829485928282549586815201915f52825f20945f915b816004840110612de05750612c7995549184828210612dc8575b828210612dad575b828210612d92575b828210612d77575b5010612d61575b5090500383613541565b815190875f526004845260405f20925f5b838110612d08575050505060045f91015582612ca7576001600255005b82827f947a9dc0c5e62cc9756634ec0a89afea37eb0305933925040b9bda8200440020956004612ce394895f5252600160405f20015416614bc3565b604080516001600160a01b039290921682526020820192909252a28080808080611b6b565b895f526006865260405f2065ffffffffffff612d248385613625565b51165f5286528260405f205416906004821015610965576001809214612d4b575b01612c8a565b97612d5b90600487015490613618565b97612d45565b60c01c65ffffffffffff1681528691018b612c6f565b6001919465ffffffffffff8560901c16815201930184612c68565b6001919465ffffffffffff8560601c16815201930184612c60565b6001919465ffffffffffff8560301c16815201930184612c58565b6001919465ffffffffffff8516815201930184612c50565b9450925090600160a08592875465ffffffffffff908181168352818160301c168d84015260608282821c166040850152828260901c169084015260c01c166080820152019501910187928794959295612c36565b60046040517f24dab61b000000000000000000000000000000000000000000000000000000008152fd5b34610323575f600319360112610323576020600354604051908152f35b3461032357602060031936011261032357612e94613440565b612e9c614f2f565b601354906001600160a01b0390818316612f0d571690811561047c57817f4f8cfde3439a1a302c21ca51eec26086efbfd940b8c0279889fc6bb6e73ecc66927fffffffffffffffffffffffff00000000000000000000000000000000000000006020931617601355604051908152a1005b60046040517f0dc149f0000000000000000000000000000000000000000000000000000000008152fd5b34610323576020600319360112610323576004355f52600d602052602060405f2054604051908152f35b346103235760806003193601126103235767ffffffffffffffff60043581811161032357612f939036906004016133f9565b909160443581811161032357612fad9036906004016133f9565b909160643590811161032357612fc79036906004016133f9565b939092612fd2614fa0565b60ff60175416610bf3576001600160a01b036013541615610bc957335f52601460205260ff60405f20541615806131fe575b6107205762093a8092836024350494858581020485148615171561030f578684036131d4578184036131d4575f5b848110613040576001600255005b61304b81868b6139bb565b35600354111561074a576130608189846139bb565b35156131aa576130718184866139bb565b35156114025761308281868b6139bb565b355f52600660205260405f208688025f5260205260ff60405f20541660048110156109655760020361258d57856020886084875f8e8e6130fc896130f48f8e6130ed84836130d28280968c6139bb565b358b52600c8f526001600160a01b0360408c205416996139bb565b35986139bb565b35928c6139bb565b359060405198899788967f75d0d3800000000000000000000000000000000000000000000000000000000088526004880152026024860152604485015260648401525af1908115611dd5575f9161318b575b50156121fd5780613162600192878c6139bb565b355f52600660205260405f208789025f5260205260405f20600360ff1982541617905501613032565b6131a4915060203d60201161224f576122418183613541565b8a61314e565b60046040517f85ac2b99000000000000000000000000000000000000000000000000000000008152fd5b60046040517fc155fe21000000000000000000000000000000000000000000000000000000008152fd5b506001600160a01b035f5416331415613004565b34610323576132296132233661342a565b906143d6565b005b346103235760406003193601126103235767ffffffffffffffff6004358181116103235761325d9036906004016133f9565b91602435908111610323576132769036906004016133f9565b919091335f526020916014835260ff60405f205416158061337e575b610720578415610774578185036131d4575f5b8581106132ae57005b6001600160a01b0390816132cb6132c6838a876139bb565b614ad1565b161561047c576132dc8185886139bb565b351561039957816132f16132c6838a876139bb565b165f526015855260405f20916001928360ff198254161790556133158286896139bb565b35816133256132c6858c896139bb565b165f526016875260405f20557fb2fe06f7c1de1ec8e3329909cafefe4d305789d8438c9755943d504772d53730866133616132c6858c896139bb565b9261336d85898c6139bb565b35936040519485521692a2016132a5565b506001600160a01b035f5416331415613292565b34610323575f600319360112610323576020611e4d6135fe565b34610323576020600319360112610323576004355f52600460205260206001600160a01b0360405f205416604051908152f35b34610323575f600319360112610323576020906011548152f35b9181601f840112156103235782359167ffffffffffffffff8311610323576020808501948460051b01011161032357565b6003196040910112610323576004359060243590565b600435906001600160a01b038216820361032357565b602435906001600160a01b038216820361032357565b60443590811515820361032357565b6064359065ffffffffffff8216820361032357565b602090816040818301928281528551809452019301915f5b8281106134b6575050505090565b8351855293810193928101926001016134a8565b90604060031983011261032357600435916024359067ffffffffffffffff8211610323576134fa916004016133f9565b9091565b6003111561096557565b6060810190811067ffffffffffffffff8211176118b457604052565b610180810190811067ffffffffffffffff8211176118b457604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176118b457604052565b9060405161358f81613508565b80925460ff811661359f816134fe565b825260ff8160081c16600281101561096557602083015260101c60ff16906040906135c9836134fe565b0152565b81156135d7570490565b634e487b7160e01b5f52601260045260245ffd5b8181029291811591840414171561030f57565b62093a8080420481810291818304149015171561030f5790565b9190820180921161030f57565b80518210156136395760209160051b010190565b634e487b7160e01b5f52603260045260245ffd5b9190820391821161030f57565b905f908082526020600e8152604090818420928251808584829754938481520190885284882092885b868282106138955750505061369a92500385613541565b8452600494858252828520956001600160a01b039660ff60098960028401541692015416977f000000000000000000000000f365c45b6913be7ab74c970d9227b9d0dff44afb16918796805189915b8183106137e9575050506136fc896134fe565b600289146137dd5785517f734648440000000000000000000000000000000000000000000000000000000081526001600160a01b0390921690820190815260208101939093529291829184918290819060400103915afa9283156137d4575084926137a2575b505060018194613771816134fe565b1461377d575b50505090565b919250908082106137935750505b5f8080613777565b61379d925061364d565b61378b565b90809250813d83116137cd575b6137b98183613541565b810103126137c957515f80613762565b8280fd5b503d6137af565b513d86823e3d90fd5b50505050505091505090565b9091986137f68a83613625565b518951907f454640300000000000000000000000000000000000000000000000000000000082528582015285602482015287604482015288816064818a5afa90811561388b578c91613859575b5061385090600192613618565b990191906136e9565b90508881813d8311613884575b6138708183613541565b8101031261388057516001613843565b8b80fd5b503d613866565b8a513d8e823e3d90fd5b855484526001958601958a955093019201613683565b6040519060c0820182811067ffffffffffffffff8211176118b4576040528160a05f918281528260208201528260408201528260608201528260808201520152565b90816020910312610323575180151581036103235790565b8054821015613639575f5260205f2001905f90565b90815491680100000000000000008310156118b4578261394291600161395995018155613905565b9091905f1983549160031b92831b921b1916179055565b565b9190918054831015613639575f526006600560205f208185040193060290565b8054680100000000000000008110156118b45761399d9160018201815561395b565b819291549060031b9165ffffffffffff809116831b921b1916179055565b91908110156136395760051b0190565b969a9993959098929491946139de6138ab565b98338a526001600160a01b03891615801561430c575b61047c576040517faa79979b0000000000000000000000000000000000000000000000000000000081526001600160a01b038a1660048201526020816024816001600160a01b037f000000000000000000000000f365c45b6913be7ab74c970d9227b9d0dff44afb165afa908115611dd5575f916142ed575b5080614252575b15614228576001600160a01b038b165f52601560205260ff60405f20541615610e7e5765ffffffffffff8816156141fe57831580156141f6575b80156141ee575b80156141e6575b611402576001600160a01b038b165f52601660205260405f205484106141bc578284116113d857602087015160028110156109655715806141b2575b6113a05780612710613b138f613b0d336145a2565b906135eb565b04036112b05760026040880151613b29816134fe565b613b32816134fe565b1461418857613b4965ffffffffffff89168e6135cd565b8060208c015280670de0b6b3a7640000810204670de0b6b3a7640000148115171561030f5783670de0b6b3a7640000613b8292026135cd565b8060408c01526011541161415e57613bd490613bb48e8d6001600160a01b038e51166001600160a01b03309216614c1d565b8b6001600160a01b038c51166001600160a01b0380601254169216614c1d565b613bdc6135fe565b908160608b0152614142575b506003549a60018c016003556001600160a01b0389511691613c0d60608b0151614b86565b9360208b015160408c01519381670de0b6b3a7640000810204670de0b6b3a7640000148215171561030f5765ffffffffffff613c6e978f8e6001600160a01b038092613c6389670de0b6b3a76400008a026135cd565b9b6040519d8e613524565b8d521660208c01521660408a0152818d1660608a015216608088015260a087015260c086015260e085015261010084015261012083015261014082015283610160820152895f52600460205261016060405f20916001600160a01b038151167fffffffffffffffffffffffff0000000000000000000000000000000000000000845416178355600183016001600160a01b036020830151167fffffffffffffffffffffffff0000000000000000000000000000000000000000825416179055600283016001600160a01b036040830151167fffffffffffffffffffffffff0000000000000000000000000000000000000000825416178155613dc265ffffffffffff60608401511682907fffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffff79ffffffffffff000000000000000000000000000000000000000083549260a01b169116179055565b608082015179ffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffff000000000000000000000000000000000000000000000000000083549260d01b16911617905560a0810151600384015560c0810151600484015560e0810151600584015561010081015160068401556101208101516007840155610140810151600884015501518051613e5b816134fe565b613e64816134fe565b6009830154916020810151906002821015610965576009937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000060ff62ff0000604061ff00950151613eb4816134fe565b613ebd816134fe565b60101b1695169116179160081b1617179101556001600160a01b0360135416895f52600c60205260405f20907fffffffffffffffffffffffff0000000000000000000000000000000000000000825416179055606086015160808701525f5b65ffffffffffff8516811061409757508160a087015281614048575b505060406001910151613f4a816134fe565b613f53816134fe565b14614031575b5f60206001600160a01b03601354166044604051809481937f58ad40640000000000000000000000000000000000000000000000000000000083528c60048401526001600160a01b038b1660248401525af1908115611dd5575f91614012575b50156121fd5765ffffffffffff60606001600160a01b0394015191846040519616865216602085015260408401521690837f1a2d9584765fd3a320beb3104d8e3ea37336abaa62152ebff24a0b8135c2823560603393a4565b61402b915060203d60201161224f576122418183613541565b5f613fb9565b6020830151865f52600860205260405f2055613f59565b94919693909592600a87116106f6575f5b60a0840151811015614083578061407d6140766001938b8b6139bb565b358c61431d565b01614059565b509295509295600191945060409091613f38565b60808701515f52600a6020526140b08a60405f2061391a565b6001600160a01b0386165f52600b60205260405f2060808801515f526020526140dc8a60405f2061391a565b895f52600660205260405f2060808801515f5260205260405f20600160ff19825416179055895f52600560205261411d60405f2061181c60808a0151614b86565b60808701519062093a808201821161030f5762093a8060019201608089015201613f1c565b62093a808101811161030f5762093a800160608901525f613be8565b60046040517f85f0de9f000000000000000000000000000000000000000000000000000000008152fd5b60046040517f9135dc13000000000000000000000000000000000000000000000000000000008152fd5b5082841415613af8565b60046040517f59ffd6f0000000000000000000000000000000000000000000000000000000008152fd5b508015613abc565b508c15613ab5565b508215613aae565b60046040517f6a0a01cc000000000000000000000000000000000000000000000000000000008152fd5b60046040517fcbb4b772000000000000000000000000000000000000000000000000000000008152fd5b506040517f1703e5f90000000000000000000000000000000000000000000000000000000081526001600160a01b038a1660048201526020816024816001600160a01b037f000000000000000000000000f365c45b6913be7ab74c970d9227b9d0dff44afb165afa908115611dd5575f916142ce575b50613a74565b6142e7915060203d60201161224f576122418183613541565b5f6142c8565b614306915060203d60201161224f576122418183613541565b5f613a6d565b506001600160a01b038b16156139f4565b91905f92808452602093600e85526040918282208351808289829454938481520190865289862092865b8b8282106143c05750505061435e92500382613541565b805190835b82811061437e575050506139599495600e918352522061391a565b866143898284613625565b511461439757600101614363565b600486517fa3d582ec000000000000000000000000000000000000000000000000000000008152fd5b8554845260019586019587955093019201614347565b905f9180835260049260208481526001600160a01b036040908060028386200154161561457a578484528683528184205416330361455257838352600e808352818420928251808583829754938481520190885283882092885b8582821061453c5750505061444792500385613541565b835193855b85811061445f5750505050505050505050565b8861446a8284613625565b51146144785760010161444c565b9091929394959697505f199998999586810190811161452957808203614501575b505050858552528220805480156144ee577f72a0d025e6eac8ca921e2325e32ff0f17354f246b9024647bf4c1ab67437a859949596508201916144dc8383613905565b909182549160031b1b191690555580a2565b602484603189634e487b7160e01b835252fd5b6145116139429161452194613625565b5191898952858552868920613905565b5f8080614499565b60248860118d634e487b7160e01b835252fd5b855484526001958601958a955093019201614430565b8590517f2af07d20000000000000000000000000000000000000000000000000000000008152fd5b8682517f62d0ece0000000000000000000000000000000000000000000000000000000008152fd5b6001600160a01b03165f9081526010602052604090205480156145c25790565b50600f5490565b5f19811461030f5760010190565b906040516145e481613524565b610160614673600983956001600160a01b038082541686528060018301541660208701526002820154908116604087015265ffffffffffff8160a01c16606087015260d01c6080860152600381015460a0860152600481015460c0860152600581015460e086015260068101546101008601526007810154610120860152600881015461014086015201613582565b910152565b905f8181526020600681526040808320858452825260ff818420541691600492838110156148bd576001036148b4578484528281526146b88285206145d7565b92858552600682528285208786528252828520600260ff1982541617905562093a8087018088116148a157866146ed9161365a565b80614741575050508161473a9161016060c07f8deaacba1e0606cd9e75aec4cb0fefc4c1df8d262d3a05f83107f8c067e8891f96950151925b01510151614733816134fe565b8686614941565b80a3600190565b8261016086949601510151600281101561488e576147609184916148d0565b9086865260078552838620888752855281848720556147838260c085015161364d565b94878752600c81526001600160a01b03908186892054169284828c8c6147e58b5194859384937fc25a4d9e0000000000000000000000000000000000000000000000000000000085528885016040919493926060820195825260208201520152565b03818d895af1908115614884578a91614867575b5015614840575061473a959361016093869361483b937f8deaacba1e0606cd9e75aec4cb0fefc4c1df8d262d3a05f83107f8c067e8891f9b9a98015116614bc3565b614726565b86517fcc48ad7f000000000000000000000000000000000000000000000000000000008152fd5b61487e9150833d851161224f576122418183613541565b5f6147f9565b88513d8c823e3d90fd5b602487602185634e487b7160e01b835252fd5b602486601184634e487b7160e01b835252fd5b50505091505090565b602485602186634e487b7160e01b835252fd5b9190916101408201518310156149375760028110156109655761490957670de0b6b3a76400009160e0614905920151906135eb565b0490565b610120810151821161492e57670de0b6b3a764000091610100614905920151906135eb565b60c09150015190565b5060c09150015190565b929091600190614950816134fe565b03614ab85762093a80820180831161030f575f928484526020600581526040928386208054905f198201918211614aa45765ffffffffffff916149929161395b565b90549060031b1c1610614a89578585526004815260088386209160048301938454906009835286892090895282528588205587875252828520549380614a78575b50670de0b6b3a764000094858502958587041485151715614a64575083927ffb23ba90a733f32458900fa34afe764a756eb475000cd4858adc5d5111994ebd9594614a5f9383614a38614a2c600860069701548a6135cd565b986007830154906135cd565b94818960058894015501555551938493846040919493926060820195825260208201520152565b0390a2565b80634e487b7160e01b602492526011600452fd5b614a829194613618565b925f6149d3565b600d915084929395614aa095525220918254613618565b9055565b602488634e487b7160e01b81526011600452fd5b9190505f52600d602052614aa060405f20918254613618565b356001600160a01b03811681036103235790565b928115614b7e576101408101938451831015614b7657600281101561096557614b335750670de0b6b3a7640000928383029280840485149015171561030f5761490592613b0d9151906135cd565b90919250670de0b6b3a7640000918284029184830484148515171561030f57610120614b639101928351906135cd565b91518111613777576149059293506135eb565b505050905090565b505050505f90565b65ffffffffffff90818111614b99571690565b60046040517ffef46c2d000000000000000000000000000000000000000000000000000000008152fd5b6040517fa9059cbb0000000000000000000000000000000000000000000000000000000060208201526001600160a01b0392909216602483015260448083019390935291815261395991614c18606483613541565b614c88565b9290604051927f23b872dd0000000000000000000000000000000000000000000000000000000060208501526001600160a01b03809216602485015216604483015260648201526064815260a081019181831067ffffffffffffffff8411176118b457613959926040525b6001600160a01b039092919216604051604081019367ffffffffffffffff94828110868211176118b4576040526020928383527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564848401525f808386829551910182855af1903d15614e02573d968711614dee57614d4594959660405190614d37887fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160183613541565b81528093873d92013e614e0f565b80519081614d5257505050565b8280614d629383010191016138ed565b15614d6a5750565b608490604051907f08c379a00000000000000000000000000000000000000000000000000000000082526004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f742073756363656564000000000000000000000000000000000000000000006064820152fd5b602483634e487b7160e01b81526041600452fd5b9150614d45939495506060915b91929015614e8a5750815115614e23575090565b3b15614e2c5790565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152fd5b825190915015614e9d5750805190602001fd5b604051907f08c379a00000000000000000000000000000000000000000000000000000000082528160208060048301528251928360248401525f5b848110614f18575050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f835f604480968601015201168101030190fd5b818101830151868201604401528593508201614ed8565b6001600160a01b035f54163303614f4257565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602060248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152fd5b6002805414614faf5760028055565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152fdfea26469706673582212204d1e97b4bc87beaf62c3e428f7bc1c6506c752f4add2cfd3a728a69182b5a6e664736f6c63430008140033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000f365c45b6913be7ab74c970d9227b9d0dff44afb000000000000000000000000e2a7de3c3190afd79c49c8e8f2fa30ca78b97dfd
-----Decoded View---------------
Arg [0] : _voter (address): 0xF365C45B6913BE7Ab74C970D9227B9D0dfF44aFb
Arg [1] : _chest (address): 0xE2a7De3C3190AFd79C49C8E8f2Fa30Ca78B97DFd
-----Encoded View---------------
2 Constructor Arguments found :
Arg [0] : 000000000000000000000000f365c45b6913be7ab74c970d9227b9d0dff44afb
Arg [1] : 000000000000000000000000e2a7de3c3190afd79c49c8e8f2fa30ca78b97dfd
Loading...
Loading
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.