Overview
S Balance
S Value
$0.00More Info
Private Name Tags
ContractCreator
Latest 11 from a total of 11 transactions
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
Whitelist Multip... | 6821580 | 2 days ago | IN | 0 S | 0.0066594 | ||||
Add Multiple Mer... | 6777480 | 2 days ago | IN | 0 S | 0.02507565 | ||||
Close Quest Peri... | 6763288 | 2 days ago | IN | 0 S | 0.03378622 | ||||
Create Ranged Qu... | 6651468 | 3 days ago | IN | 0 S | 0.03260801 | ||||
Create Ranged Qu... | 6641505 | 3 days ago | IN | 0 S | 0.030052 | ||||
Create Ranged Qu... | 6641066 | 3 days ago | IN | 0 S | 0.03121833 | ||||
Create Ranged Qu... | 6635937 | 3 days ago | IN | 0 S | 0.03768979 | ||||
Create Ranged Qu... | 6635603 | 3 days ago | IN | 0 S | 0.03082892 | ||||
Whitelist Multip... | 6631431 | 3 days ago | IN | 0 S | 0.01704252 | ||||
Approve Manager | 6631419 | 3 days ago | IN | 0 S | 0.00264082 | ||||
Init | 6631404 | 3 days ago | IN | 0 S | 0.00259479 |
Loading...
Loading
Contract Name:
QuestBoardRings
Compiler Version
v0.8.20+commit.a1b79de6
Optimization Enabled:
Yes with 9999 runs
Other Settings:
paris 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 Amount of decimals for the veNFT */ uint256 public immutable VOTE_TOKEN_DECIMALS; /** @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, uint256 _veNFTDecimals){ if( _voter == address(0) || _chest == address(0) ) revert Errors.AddressZero(); VOTER = _voter; questChest = _chest; VOTE_TOKEN_DECIMALS = _veNFTDecimals; objectiveMinimalThreshold = 100 * 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); } function _convertVotes(uint256 votes) internal view returns(uint256) { if(VOTE_TOKEN_DECIMALS == 18) return votes; else if(VOTE_TOKEN_DECIMALS > 18) { return votes / (10 ** (VOTE_TOKEN_DECIMALS - 18)); } else { return votes * (10 ** (18 - VOTE_TOKEN_DECIMALS)); } } /** * @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 = _convertVotes(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 = _convertVotes(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, "evmVersion": "paris", "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"},{"internalType":"uint256","name":"_veNFTDecimals","type":"uint256"}],"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":"VOTE_TOKEN_DECIMALS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"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
60c0346200014357601f620053f338819003918201601f19168301916001600160401b03831184841017620001485780849260609460405283398101031262000143576200004d816200015e565b60406200005d602084016200015e565b920151600080546001600160a01b0319808216339081178455604051949691956001600160a01b0395929490938616907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a36001600255610190600f5583821615801562000138575b6200012957506080521690601254161760125560a05268056bc75e2d6310000060115560405161527f90816200017482396080518181816120610152818161382b01528181613c4e01526144be015260a0518181816103550152613a250152f35b639fabe1c160e01b8152600490fd5b5083831615620000c8565b600080fd5b634e487b7160e01b600052604160045260246000fd5b51906001600160a01b0382168203620001435756fe608080604052600436101561001357600080fd5b60003560e01c908163040916a41461354257508063052de43f1461350d578063086146d2146134f25780630881b830146133835780630eb671731461336a57806311195485146130ab578063146bdb7d1461307f57806319ab453c14612fc35780631e96917d14612fa55780632f940c7014612cbb5780633ce455a814612bdc5780633d72387714612b815780633da8a8d114612b4c578063482280d514612b19578063511aa52d14612afb57806357ae1cec14612add5780636eaa04ef14612aa8578063715018a614612a375780637211a52b146128d557806377dd7cf71461289b57806379ba5097146127cb57806379dc77d014612791578063897ec1c7146126f25780638a6ce1cb146120ac5780638da5cb5b146120855780638ebf2fd6146120415780638f4c72d814611f575780638fe8a10114611f345780639655070f14611f13578063975e517314611ee75780639e8c708e14611dde578063a6c93d7d14611db7578063a8a7e38614611ba7578063a8cf56f914611abe578063aa0b598814611a60578063aa412da8146119f6578063ac18de4314611989578063ac4884ac146114c5578063b486d07314610fac578063b5eee72214610f3c578063ba82c89714610e60578063bc30a61814610db9578063bd7ddd0614610d7a578063bfe1092814610d53578063c6c02cac14610c90578063d52f113514610aa9578063daf9c21014610a6a578063dc34a151146109e1578063e085f980146108d6578063e30c3978146108af578063eca5d0ca14610801578063ed8de7191461069f578063f29e9aa7146104fb578063f2fde38b14610416578063f49c1d2414610378578063f8534b011461033d5763f99811401461029257600080fd5b34610338576020806003193601126103385762093a80806004350481810291818304149015171561032257600052600a8152604060002090604051908181845491828152019360005281600020916000905b82821061030b57610307856102fb818903826136a6565b604051918291826135f4565b0390f35b8354865294850194600193840193909101906102e4565b634e487b7160e01b600052601160045260246000fd5b600080fd5b346103385760006003193601126103385760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b34610338576040600319360112610338576103916135a4565b6024359061039d61516a565b600f5482116103ec5760206001600160a01b037f6aade5fcd818524835ddc10e56891b8efefe8fae5d5817ff5e2c630d41fad354921692836000526010825280604060002055604051908152a2005b60046040517f613970e0000000000000000000000000000000000000000000000000000000008152fd5b346103385760206003193601126103385761042f6135a4565b61043761516a565b6001600160a01b038091169081156104d157806000541682146104a757600154827fffffffffffffffffffffffff0000000000000000000000000000000000000000821617600155167fb3d55174552271a4f1aaf36b72f50381e892171636b3fb5447fe00e995e7a37b600080a3005b60046040517fd5e889bf000000000000000000000000000000000000000000000000000000008152fd5b60046040517f9fabe1c1000000000000000000000000000000000000000000000000000000008152fd5b3461033857602080600319360112610338576004356000526005808252816040600020916040519081928391818654948581520190819660005282600020946000915b81600484011061064b575061058495549184828210610633575b828210610618575b8282106105fd575b8282106105e2575b50106105cc575b50905093929303836136a6565b604051928392818401908285525180915260408401929160005b8281106105ad57505050500390f35b835165ffffffffffff168552869550938101939281019260010161059e565b60c01c65ffffffffffff16815286910187610577565b6001919465ffffffffffff8560901c16815201930184610570565b6001919465ffffffffffff8560601c16815201930184610568565b6001919465ffffffffffff8560301c16815201930184610560565b6001919465ffffffffffff8516815201930184610558565b9450925090600160a08592875465ffffffffffff908181168352818160301c168d84015260608282821c166040850152828260901c169084015260c01c16608082015201950191018792869495929561053e565b34610338576106ad3661362f565b80156107d7578260005260046020526001600160a01b038060026040600020015416156107ad578360005260046020526040600020541633036107835782600052600e602052600a6107046040600020548361377f565b116107595760005b81811061073b57837f72a0d025e6eac8ca921e2325e32ff0f17354f246b9024647bf4c1ab67437a859600080a2005b8061075361074c6001938587613bcc565b3586614544565b0161070c565b60046040517f7dce6cb8000000000000000000000000000000000000000000000000000000008152fd5b60046040517f2af07d20000000000000000000000000000000000000000000000000000000008152fd5b60046040517f62d0ece0000000000000000000000000000000000000000000000000000000008152fd5b60046040517f521299a9000000000000000000000000000000000000000000000000000000008152fd5b346103385760206003193601126103385761081a6135a4565b61082261516a565b6001600160a01b0380821680156104d1576108aa7f9e1cbba858790e137077d865d7e4df5017a55524ea53a66b2c20ec665167d8d693601254927fffffffffffffffffffffffff000000000000000000000000000000000000000084161760125560405193849316839060209093929360408301946001600160a01b03809216845216910152565b0390a1005b346103385760006003193601126103385760206001600160a01b0360015416604051908152f35b3461033857602060031936011261033857600435600052600460205260406000206001600160a01b03908181541690826001820154169260028201546003830154600484015460058501549160068601549360078701549561093f600960088a015499016136e7565b99604051998a5260208a01528116604089015265ffffffffffff8160a01c16606089015260d01c608088015260a087015260c086015260e0850152610100840152610120830152610140820152815161099781613663565b610160820152602082015160028110156109cb576101c09260409161018084015201516109c381613663565b6101a0820152f35b634e487b7160e01b600052602160045260246000fd5b34610338576000600319360112610338576109fa61516a565b60175460ff8116610a405760ff196001911617601755426018557ff4a991ad9e7f9711696f7bd41529beb4c470d75788573535d4ca3f0857c79ce86020604051428152a1005b60046040517f7a480252000000000000000000000000000000000000000000000000000000008152fd5b34610338576020600319360112610338576001600160a01b03610a8b6135a4565b166000526015602052602060ff604060002054166040519015158152f35b3461033857610ab73661362f565b610ac29291926151dc565b60ff60175416610c66576001600160a01b03806013541615610c3c5733600052601460205260ff60406000205416159081610c2d575b506107835760009160009162093a808092048281029281840414901517156103225780156107d7578115610c0357610b2e613765565b821015610bd95781600052600a60205260406000205415610baf57906000915b808310610b6a5760408585600160025582519182526020820152f35b909193610b82610b7b868489613bcc565b35846148a4565b15610b9c57610b926001916147f4565b945b019190610b4e565b9392610ba96001916147f4565b93610b94565b60046040517ffbbfe1b6000000000000000000000000000000000000000000000000000000008152fd5b60046040517f9bcf46f4000000000000000000000000000000000000000000000000000000008152fd5b60046040517f17479ac8000000000000000000000000000000000000000000000000000000008152fd5b90506000541633141584610af8565b60046040517f87138d5c000000000000000000000000000000000000000000000000000000008152fd5b60046040517f0f8eeedb000000000000000000000000000000000000000000000000000000008152fd5b3461033857600060031936011261033857610ca961516a565b60175460ff811615610d295760185462127500810180911161032257421015610cff5760ff19166017557f62c91cdd7db0a8e05658629214f78fbbbc0708b9698bf57892027841dc8899c16020604051428152a1005b60046040517fc905adb1000000000000000000000000000000000000000000000000000000008152fd5b60046040517f81e21bc8000000000000000000000000000000000000000000000000000000008152fd5b346103385760006003193601126103385760206001600160a01b0360135416604051908152f35b3461033857610d883661358e565b90600052600660205260406000209060005260205260ff6040600020541660405160048210156109cb576020918152f35b3461033857602060031936011261033857610dd26135a4565b610dda61516a565b6001600160a01b038091169081156104d157601380547fffffffffffffffffffffffff00000000000000000000000000000000000000008116841790915560408051929091166001600160a01b03908116835290921660208201527f111a961d91cf441fe07e7bfddc128b30ab56974d1a76851e969e0642fdb2dd5091819081016108aa565b3461033857604060031936011261033857610e796135a4565b6024359033600052601460205260ff604060002054161580610f27575b610783576001600160a01b03169081600052601560205260ff6040600020541615610efd5780156103ec5760207f0b431c44bb4597805cf43550dbfc7dd3b2a6752c10eef974a53dba1c7e25ffa691836000526016825280604060002055604051908152a2005b60046040517ff84835a0000000000000000000000000000000000000000000000000000000008152fd5b506001600160a01b0360005416331415610e96565b34610338576020600319360112610338576001600160a01b03610f5d6135a4565b610f6561516a565b1680156104d1578060005260146020526040600020600160ff198254161790557f8e6f8d1a4c834c03bc5c8f9dedff078a6fd2904f7a94b17df04efb916aaba72d600080a2005b346103385760c060031936011261033857610fc56151dc565b60ff60175416610c66576001600160a01b036013541615610c3c57610fe8613ab5565b600435600052600460205260406000209060035460043510156107ad576001600160a01b038254163303610783576024351580156114bb575b61149157604435602435116114675760ff600983015460081c1660028110156109cb571580611459575b61142f57600435600052600560205260406000205415611405576004356000526005602052604060002080549060001982019182116103225765ffffffffffff9161109591613b6a565b90549060031b1c166110a5613765565b808210156113e757505060005b808252156113bd57600282015460d01c4211156113935780516110da60843591606435613752565b036113695760a4356127106110f96110f1336147cc565b608435613752565b040361133f57611107613765565b602082015260058201546024351061131557611129606435600484015461377f565b60408201526004356000526004602052600160ff60096040600020015460101c1661115381613663565b146112f1575b604081015191670de0b6b3a76400009283810290808204851490151715610322576111879060243590613732565b926060830193845260ff600983015460081c1660028110156109cb576001036112e4576040830151818102918183041490151715610322576111cc9060443590613732565b60808301525b6080820192835160078301908154116112ba5760209460843561127e575b60038401611201608435825461377f565b9055602435600585015560443560068501555190555160088201556004604083015191015501516040517fdfb634c689d94c27824516a1fa03ef285dae8d316e897e63403182441d82ab656004359180611274606435604435602435846040919493926060820195825260208201520152565b0390a36001600255005b6112b56001600160a01b0360018601541661129d608435303384614e55565b60a435906001600160a01b0360125416903390614e55565b6111f0565b60046040517f29847030000000000000000000000000000000000000000000000000000000008152fd5b50825160808301526111d2565b6004356000526008602052604060002061130e606435825461377f565b9055611159565b60046040517fa810589a000000000000000000000000000000000000000000000000000000008152fd5b60046040517f1fbbed95000000000000000000000000000000000000000000000000000000008152fd5b60046040517f6b5545d7000000000000000000000000000000000000000000000000000000008152fd5b60046040517f59710113000000000000000000000000000000000000000000000000000000008152fd5b60046040517fffbb7602000000000000000000000000000000000000000000000000000000008152fd5b6113f0916137b6565b62093a809081810180911161032257046110b2565b60046040517fb31fbda3000000000000000000000000000000000000000000000000000000008152fd5b60046040517f81387e3e000000000000000000000000000000000000000000000000000000008152fd5b50604435602435141561104b565b60046040517f4b44b921000000000000000000000000000000000000000000000000000000008152fd5b60046040517fe5a74490000000000000000000000000000000000000000000000000000000008152fd5b5060443515611021565b346103385760806003193601126103385760243565ffffffffffff81168103610338576114f06151dc565b60ff60175416610c66576001600160a01b036013541615610c3c57604051610120810181811067ffffffffffffffff821117611973576040526000815260006020820152600060408201526000606082015260006080820152600060a0820152600060c0820152600060e0820152600061010082015260035460043510156107ad5760043560005260046020526001600160a01b0360406000205416330361078357604435158015611969575b6114915765ffffffffffff82161561193f5760043560005260056020526040600020541561140557600435600052600560205260406000208054806000198101116103225765ffffffffffff916000196115f8920190613b6a565b90549060031b1c168152600435600052600460205260026040600020015460d01c42111561139357805161162a613765565b116113bd57600435600052600460205260046040600020015460608201526004356000526004602052600160ff60096040600020015460101c1661166d81613663565b14611924575b60443561168c65ffffffffffff84166060840151613752565b03611369576064356127106116ab6116a3336147cc565b604435613752565b040361133f5760043560005260046020526001600160a01b0360026040600020015416602082015260043560005260046020526117066001600160a01b03600160406000200154168060408401526044359030903390614e55565b61172f6001600160a01b03604083015116606435906001600160a01b0360125416903390614e55565b805162093a8090818101809111610322578190048181029080820483149015171561032257608083015260043560005260046020526003604060002001611779604435825461377f565b90556004356000526004602052600260406000200165ffffffffffff841665ffffffffffff825460a01c160165ffffffffffff81116103225781547fffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffff1660a09190911b79ffffffffffff00000000000000000000000000000000000000001617905560005b65ffffffffffff84168110611853578365ffffffffffff6040519116815260443560208201527f196ca6579b22278c0854d0a67ac2a7fda3dbb66f74a19c2c55fa79c3a2e4bd9f604060043592a26001600255005b6080830151600052600a6020526118706004356040600020613b28565b60043560005260046020526001600160a01b0360026040600020015416600052600b602052604060002060808401516000526020526118b56004356040600020613b28565b60043560005260056020526118db60406000206118d56080860151614dbe565b90613b8c565b6004356000526006602052604060002060808401516000526020526040600020600160ff19825416179055608083015190828201809211610322576001916080850152016117fe565b60043560005260086020526040600020546060820152611673565b60046040517fc82661ee000000000000000000000000000000000000000000000000000000008152fd5b506064351561159d565b634e487b7160e01b600052604160045260246000fd5b34610338576020600319360112610338576001600160a01b036119aa6135a4565b6119b261516a565b1680156104d157806000526014602052604060002060ff1981541690557f0153940c4f400a39a1ca7f4cd7e46be4b7d3793f6142184e505b8c4e1a810378600080a2005b346103385760208060031936011261033857600435600052600e8152604060002090604051908181845491828152019360005281600020916000905b828210611a4957610307856102fb818903826136a6565b835486529485019460019384019390910190611a32565b3461033857602060031936011261033857600435611a7c61516a565b6101f481116103ec5760407ff9d94f1c6388837eee93d61a8020cf4d537953ce27f2a295d60ad96e6a6ec6a391600f549080600f5582519182526020820152a1005b346103385761014060031936011261033857611ad86135a4565b611ae06135ba565b90611ae96135d0565b91611af26135df565b6084359160e4359360038510156103385761010435936003851015610338576101243567ffffffffffffffff811161033857611b3290369060040161355d565b959094611b3d6151dc565b60ff60175416610c66576001600160a01b036013541615610c3c57602098611b9a98849360405191611b6e8361366d565b611b7781613663565b825260008c830152611b8881613663565b604082015260c4359660a43596613bdc565b6001600255604051908152f35b346103385760406003193601126103385760043567ffffffffffffffff811161033857611bd890369060040161355d565b90611be16135ba565b91611bea6151dc565b60ff60175416610c66576001600160a01b0390816013541615610c3c5780156107d757818416156104d15760009260009160005b818110611c5957505050169182151580611c50575b611c3f575b6001600255005b611c4892614dfb565b808080611c38565b50811515611c33565b611c64818385613bcc565b3560035411156107ad57611c79818385613bcc565b356000526020600481528560406000205416330361078357611c9c828486613bcc565b35600052600d9081815260406000205491611cb8848688613bcc565b3560005281526000604081205581611cd5575b5050600101611c1e565b946004829896611ce6858789613bcc565b3560005252888760016040600020015416918881169081841415600014611d9e575080611d7c575b505060019291611d1e919661377f565b965b7f182587d643fe0f1dce1eafe8f758d78a2cfe0f9acdd4653ec0ad97dc94c3102d611d72611d4f848789613bcc565b604080516001600160a01b038e1681526020810195909552903593918291820190565b0390a29088611ccb565b600194935090611d8d929791614dfb565b600094909150869088611d1e611d0e565b979050611db1925060019493915061377f565b96611d20565b346103385760006003193601126103385760206001600160a01b0360125416604051908152f35b346103385760208060031936011261033857611df86135a4565b611e0061516a565b6001600160a01b03809116806000526015835260ff60406000205416611ebd57604051907f70a082310000000000000000000000000000000000000000000000000000000082523060048301528382602481845afa918215611eb157600092611e82575b50811561149157611e79926000541690614dfb565b60405160018152f35b9091508381813d8311611eaa575b611e9a81836136a6565b8101031261033857519084611e64565b503d611e90565b6040513d6000823e3d90fd5b60046040517f80eb2a01000000000000000000000000000000000000000000000000000000008152fd5b346103385760206003193601126103385760043560005260086020526020604060002054604051908152f35b34610338576020611f2c611f263661358e565b906137c3565b604051908152f35b3461033857600060031936011261033857602060ff601754166040519015158152f35b3461033857604060031936011261033857600435611f736135ba565b611f7b6151dc565b60ff60175416610c66576001600160a01b0390816013541615610c3c576003548310156107ad578260005260046020528160406000205416330361078357818116156104d15782600052600d60205260406000209060008254925581611fe2576001600255005b61201d82827f182587d643fe0f1dce1eafe8f758d78a2cfe0f9acdd4653ec0ad97dc94c3102d95600460205260016040600020015416614dfb565b604080516001600160a01b039290921682526020820192909252a280808080611c38565b346103385760006003193601126103385760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b346103385760006003193601126103385760206001600160a01b0360005416604051908152f35b34610338576060600319360112610338576120c56151dc565b60ff60175416610c66576120d761516a565b62093a80600435048062093a8081020462093a8014811517156103225760035460243510156107ad576001600160a01b0360135416156126c85762093a80810215610c0357612124613765565b62093a80820211610c0357602435600052600460205260406000206006602052604060002062093a80830260005260205260ff6040600020541660048110156109cb5760020361269e576024356000526007602052604060002062093a8083026000526020526040600020546004820154906121a081836137b6565b91602435600052600c6020526001600160a01b0360406000205416906044351580612680575b15612350575060206000916024358352600782526040832062093a808802845282528260408120556064604051809481937f657c5e8c000000000000000000000000000000000000000000000000000000008352602435600484015262093a808b0260248401528160448401525af1908115611eb157600091612321575b50156122f75760ff600984015460101c1661225e81613663565b806122dc57505061228861228d92602435600052600d60205260046040600020549101549061377f565b6137b6565b602435600052600d6020526040600020555b60405160443581527f761ac4569e5e4e02e15740915e4281941ef7c47913333b6414da71b1dd8b809a602062093a80602435940292a36001600255005b9091506122f2925062093a808402602435614b6e565b61229f565b60046040517fcc48ad7f000000000000000000000000000000000000000000000000000000008152fd5b612343915060203d602011612349575b61233b81836136a6565b810190613af8565b85612244565b503d612331565b6009859294950190815490600160ff61237961236b87614803565b604435838760081c16614afd565b9360101c1661238781613663565b036126185750506024356000526009602052604060002062093a8087026000526020526123cf60ff604060002054925460081c16826123c585614803565b9160443590614d16565b9462093a808088020162093a8088021161032257602435600052600560205260406000208054806000198101116103225765ffffffffffff91600019612416920190613b6a565b90549060031b1c1662093a8080890201116000146125485761228861245692602435600052600d60205261245088604060002054926137b6565b9061377f565b602435600052600d6020526040600020555b82828511612520575b5050505b6040517f657c5e8c00000000000000000000000000000000000000000000000000000000815260248035600483015262093a808502908201526044810183905290602090829060649082906000905af1908115611eb157600091612501575b50156122f7576024356000526007602052604060002062093a80830260005260205260406000205561229f565b61251a915060203d6020116123495761233b81836136a6565b836124d4565b6001600160a01b03600161253761254095886137b6565b93015416614dfb565b838082612471565b5050818411156126015761256a600482015461256484876137b6565b906137b6565b670de0b6b3a7640000808202908282041482151715610322577ffb23ba90a733f32458900fa34afe764a756eb475000cd4858adc5d5111994ebd906125c26125b6600886015483613732565b91600786015490613732565b908060058601558160068601558360048601556125f960405192839260243596846040919493926060820195825260208201520152565b0390a2612468565b612613600482015461245086856137b6565b61256a565b61263f925061228890602497929735600052600d60205261245088604060002054926137b6565b602435600052600d60205260406000205582828511612661575b505050612475565b6001600160a01b03600161253761267895886137b6565b838082612659565b50600160ff600987015460101c1661269781613663565b14156121c6565b60046040517f226e5a6a000000000000000000000000000000000000000000000000000000008152fd5b60046040517fdc1669a3000000000000000000000000000000000000000000000000000000008152fd5b346103385760406003193601126103385761270b6135a4565b62093a80908160243504828102928184041490151715610322576001600160a01b0316600052602090600b82526040600020906000528152604060002090604051908181845491828152019360005281600020916000905b82821061277a57610307856102fb818903826136a6565b835486529485019460019384019390910190612763565b34610338576020600319360112610338576001600160a01b036127b26135a4565b1660005260166020526020604060002054604051908152f35b34610338576000600319360112610338576001546001600160a01b038082169081330361287157600092828454927fffffffffffffffffffffffff00000000000000000000000000000000000000009382858216178755167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08680a3166001557fb3d55174552271a4f1aaf36b72f50381e892171636b3fb5447fe00e995e7a37b8280a3005b60046040517f2f02da58000000000000000000000000000000000000000000000000000000008152fd5b34610338576020600319360112610338576001600160a01b036128bc6135a4565b1660005260106020526020604060002054604051908152f35b3461033857602080600319360112610338576128ef6151dc565b60ff60175416610c66576001600160a01b03806013541615610c3c57336000526014825260ff60406000205416159081612a28575b506107835760008062093a80928360043504848102948186041490151715610322578315610c0357612954613765565b841015610bd95783600052600a815260406000205415610baf5783600052600a8152604060002093604051808684829854938481520190600052846000209260005b86828210612a12575050506129ad925003866136a6565b84516000915b8183106129ce57604086868660016002558351928352820152f35b9091946129e56129de878961378c565b51836148a4565b156129ff576129f56001916147f4565b955b0191906129b3565b9493612a0c6001916147f4565b946129f7565b855484526001958601958b955093019201612996565b90506000541633141582612924565b3461033857600060031936011261033857612a5061516a565b60006001600160a01b0381547fffffffffffffffffffffffff000000000000000000000000000000000000000081168355167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a3005b3461033857602060031936011261033857612ac1613765565b62093a80810180911161032257611f2c602091600435906137c3565b34610338576000600319360112610338576020600f54604051908152f35b34610338576000600319360112610338576020601854604051908152f35b3461033857612b273661358e565b9060005260076020526040600020906000526020526020604060002054604051908152f35b3461033857602060031936011261033857600435600052600c60205260206001600160a01b0360406000205416604051908152f35b3461033857602060031936011261033857600435612b9d61516a565b80156103ec5760407f3933dd2114106692c792a24b4d8509cf9c5e4a41a71315c9f2b593085ea76e8991601154908060115582519182526020820152a1005b346103385761016060031936011261033857612bf66135a4565b612bfe6135ba565b612c066135d0565b90612c0f6135df565b92610104359260038410156103385761012435936003851015610338576101443567ffffffffffffffff811161033857612c4d90369060040161355d565b949093612c586151dc565b60ff60175416610c66576001600160a01b036013541615610c3c57602097611b9a9760405194612c878661366d565b612c9081613663565b855260018a860152612ca181613663565b604085015260e4359460c4359460a4359460843594613bdc565b3461033857604060031936011261033857600435612cd76135ba565b612cdf6151dc565b60ff806017541615610d2957601854621275008101809111610322574210612f7b576003548310156107ad5782600052602091600483526001600160a01b03928360406000205416330361078357838216156104d15784600052600d815260406000209260008454945560059081835260406000209160405190848294859282825495868152019160005282600020946000915b816004840110612f275750612db695549184828210612f0f575b828210612ef4575b828210612ed9575b828210612ebe575b5010612ea8575b50905003836136a6565b815190876000526004845260406000209260005b838110612e4b57505050506004600091015582612de8576001600255005b82827f947a9dc0c5e62cc9756634ec0a89afea37eb0305933925040b9bda8200440020956004612e2694896000525260016040600020015416614dfb565b604080516001600160a01b039290921682526020820192909252a28080808080611c38565b8960005260068652604060002065ffffffffffff612e69838561378c565b5116600052865282604060002054169060048210156109cb576001809214612e92575b01612dca565b97612ea29060048701549061377f565b97612e8c565b60c01c65ffffffffffff1681528691018b612dac565b6001919465ffffffffffff8560901c16815201930184612da5565b6001919465ffffffffffff8560601c16815201930184612d9d565b6001919465ffffffffffff8560301c16815201930184612d95565b6001919465ffffffffffff8516815201930184612d8d565b9450925090600160a08592875465ffffffffffff908181168352818160301c168d84015260608282821c166040850152828260901c169084015260c01c166080820152019501910187928794959295612d73565b60046040517f24dab61b000000000000000000000000000000000000000000000000000000008152fd5b34610338576000600319360112610338576020600354604051908152f35b3461033857602060031936011261033857612fdc6135a4565b612fe461516a565b601354906001600160a01b039081831661305557169081156104d157817f4f8cfde3439a1a302c21ca51eec26086efbfd940b8c0279889fc6bb6e73ecc66927fffffffffffffffffffffffff00000000000000000000000000000000000000006020931617601355604051908152a1005b60046040517f0dc149f0000000000000000000000000000000000000000000000000000000008152fd5b3461033857602060031936011261033857600435600052600d6020526020604060002054604051908152f35b346103385760806003193601126103385767ffffffffffffffff600435818111610338576130dd90369060040161355d565b9091604435818111610338576130f790369060040161355d565b90916064359081116103385761311190369060040161355d565b93909261311c6151dc565b60ff60175416610c66576001600160a01b036013541615610c3c5733600052601460205260ff604060002054161580613355575b6107835762093a809283602435049485858102048514861517156103225786840361332b5781840361332b5760005b84811061318d576001600255005b61319881868b613bcc565b3560035411156107ad576131ad818984613bcc565b3515613301576131be818486613bcc565b3515611491576131cf81868b613bcc565b356000526006602052604060002086880260005260205260ff6040600020541660048110156109cb5760020361269e578560208860848760008e8e61324e896132468f8e61323f84836132248280968c613bcc565b358b52600c8f526001600160a01b0360408c20541699613bcc565b3598613bcc565b35928c613bcc565b359060405198899788967f75d0d3800000000000000000000000000000000000000000000000000000000088526004880152026024860152604485015260648401525af1908115611eb1576000916132e2575b50156122f757806132b5600192878c613bcc565b35600052600660205260406000208789026000526020526040600020600360ff198254161790550161317f565b6132fb915060203d6020116123495761233b81836136a6565b8a6132a1565b60046040517f85ac2b99000000000000000000000000000000000000000000000000000000008152fd5b60046040517fc155fe21000000000000000000000000000000000000000000000000000000008152fd5b506001600160a01b0360005416331415613150565b346103385761338161337b3661358e565b906145fe565b005b346103385760406003193601126103385767ffffffffffffffff600435818111610338576133b590369060040161355d565b91602435908111610338576133ce90369060040161355d565b919091336000526020916014835260ff6040600020541615806134dd575b6107835784156107d75781850361332b5760005b85811061340957005b6001600160a01b039081613426613421838a87613bcc565b614d02565b16156104d157613437818588613bcc565b35156103ec578161344c613421838a87613bcc565b16600052601585526040600020916001928360ff19825416179055613472828689613bcc565b3581613482613421858c89613bcc565b16600052601687526040600020557fb2fe06f7c1de1ec8e3329909cafefe4d305789d8438c9755943d504772d53730866134c0613421858c89613bcc565b926134cc85898c613bcc565b35936040519485521692a201613400565b506001600160a01b03600054163314156133ec565b34610338576000600319360112610338576020611f2c613765565b3461033857602060031936011261033857600435600052600460205260206001600160a01b0360406000205416604051908152f35b34610338576000600319360112610338576020906011548152f35b9181601f840112156103385782359167ffffffffffffffff8311610338576020808501948460051b01011161033857565b6003196040910112610338576004359060243590565b600435906001600160a01b038216820361033857565b602435906001600160a01b038216820361033857565b60443590811515820361033857565b6064359065ffffffffffff8216820361033857565b6020908160408183019282815285518094520193019160005b82811061361b575050505090565b83518552938101939281019260010161360d565b90604060031983011261033857600435916024359067ffffffffffffffff82116103385761365f9160040161355d565b9091565b600311156109cb57565b6060810190811067ffffffffffffffff82111761197357604052565b610180810190811067ffffffffffffffff82111761197357604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761197357604052565b906040516136f48161366d565b80925460ff811661370481613663565b825260ff8160081c1660028110156109cb57602083015260101c60ff169060409061372e83613663565b0152565b811561373c570490565b634e487b7160e01b600052601260045260246000fd5b8181029291811591840414171561032257565b62093a808042048181029181830414901517156103225790565b9190820180921161032257565b80518210156137a05760209160051b010190565b634e487b7160e01b600052603260045260246000fd5b9190820391821161032257565b906000908082526020600e8152604090818420928251808584829754938481520190885284882092885b868282106139ff57505050613804925003856136a6565b8452600494858252828520956001600160a01b039660ff60098960028401541692015416977f000000000000000000000000000000000000000000000000000000000000000016918796805189915b8183106139505750505061386689613663565b600289146139445785517f734648440000000000000000000000000000000000000000000000000000000081526001600160a01b0390921690820190815260208101939093529291829184918290819060400103915afa92831561393b57508492613906575b50506138d9600191613a23565b936138e381613663565b146138ed57505090565b90918082106138fc5750505b90565b6138f992506137b6565b90809250813d8311613934575b61391d81836136a6565b8101031261393057516138d960016138cc565b8280fd5b503d613913565b513d86823e3d90fd5b50505050505091505090565b90919861395d8a8361378c565b518951907f454640300000000000000000000000000000000000000000000000000000000082528582015285602482015287604482015288816064818a5afa9081156139f5578c916139c3575b506001916124506139ba92613a23565b99019190613853565b90508881813d83116139ee575b6139da81836136a6565b810103126139ea575160016139aa565b8b80fd5b503d6139d0565b8a513d8e823e3d90fd5b855484526001958601958a9550930192016137ed565b604d811161032257600a0a90565b7f000000000000000000000000000000000000000000000000000000000000000060128103613a50575090565b906012821115613a96577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee820191821161032257613a906138f992613a15565b90613732565b90601203906012821161032257613aaf6138f992613a15565b90613752565b6040519060c0820182811067ffffffffffffffff821117611973576040528160a06000918281528260208201528260408201528260608201528260808201520152565b90816020910312610338575180151581036103385790565b80548210156137a05760005260206000200190600090565b90815491680100000000000000008310156119735782613b50916001613b6895018155613b10565b90919060001983549160031b92831b921b1916179055565b565b91909180548310156137a0576000526006600560206000208185040193060290565b80546801000000000000000081101561197357613bae91600182018155613b6a565b819291549060031b9165ffffffffffff809116831b921b1916179055565b91908110156137a05760051b0190565b969a999395909892949194613bef613ab5565b98338a526001600160a01b038916158015614533575b6104d1576040517faa79979b0000000000000000000000000000000000000000000000000000000081526001600160a01b038a1660048201526020816024816001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165afa908115611eb157600091614514575b5080614478575b1561444e576001600160a01b038b16600052601560205260ff6040600020541615610efd5765ffffffffffff881615614424578315801561441c575b8015614414575b801561440c575b611491576001600160a01b038b16600052601660205260406000205484106143e25782841161146757602087015160028110156109cb5715806143d8575b61142f5780612710613d238f613aaf336147cc565b040361133f5760026040880151613d3981613663565b613d4281613663565b146143ae57613d5965ffffffffffff89168e613732565b8060208c015280670de0b6b3a7640000810204670de0b6b3a764000014811517156103225783670de0b6b3a7640000613d929202613732565b8060408c01526011541161438457613de490613dc48e8d6001600160a01b038e51166001600160a01b03309216614e55565b8b6001600160a01b038c51166001600160a01b0380601254169216614e55565b613dec613765565b908160608b0152614368575b506003549a60018c016003556001600160a01b0389511691613e1d60608b0151614dbe565b9360208b015160408c01519381670de0b6b3a7640000810204670de0b6b3a764000014821517156103225765ffffffffffff613e7e978f8e6001600160a01b038092613e7389670de0b6b3a76400008a02613732565b9b6040519d8e613689565b8d521660208c01521660408a0152818d1660608a015216608088015260a087015260c086015260e0850152610100840152610120830152610140820152836101608201528960005260046020526101606040600020916001600160a01b038151167fffffffffffffffffffffffff0000000000000000000000000000000000000000845416178355600183016001600160a01b036020830151167fffffffffffffffffffffffff0000000000000000000000000000000000000000825416179055600283016001600160a01b036040830151167fffffffffffffffffffffffff0000000000000000000000000000000000000000825416178155613fd465ffffffffffff60608401511682907fffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffff79ffffffffffff000000000000000000000000000000000000000083549260a01b169116179055565b608082015179ffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffff000000000000000000000000000000000000000000000000000083549260d01b16911617905560a0810151600384015560c0810151600484015560e081015160058401556101008101516006840155610120810151600784015561014081015160088401550151805161406d81613663565b61407681613663565b60098301549160208101519060028210156109cb576009937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000060ff62ff0000604061ff009501516140c681613663565b6140cf81613663565b60101b1695169116179160081b1617179101556001600160a01b036013541689600052600c6020526040600020907fffffffffffffffffffffffff00000000000000000000000000000000000000008254161790556060860151608087015260005b65ffffffffffff851681106142b157508160a087015281614261575b50506040600191015161415f81613663565b61416881613663565b14614248575b600060206001600160a01b03601354166044604051809481937f58ad40640000000000000000000000000000000000000000000000000000000083528c60048401526001600160a01b038b1660248401525af1908115611eb157600091614229575b50156122f75765ffffffffffff60606001600160a01b0394015191846040519616865216602085015260408401521690837f1a2d9584765fd3a320beb3104d8e3ea37336abaa62152ebff24a0b8135c2823560603393a4565b614242915060203d6020116123495761233b81836136a6565b386141d0565b602083015186600052600860205260406000205561416e565b94919693909592600a87116107595760005b60a084015181101561429d57806142976142906001938b8b613bcc565b358c614544565b01614273565b50929550929560019194506040909161414d565b6080870151600052600a6020526142cc8a6040600020613b28565b6001600160a01b038616600052600b602052604060002060808801516000526020526142fc8a6040600020613b28565b896000526006602052604060002060808801516000526020526040600020600160ff1982541617905589600052600560205261434360406000206118d560808a0151614dbe565b60808701519062093a80820182116103225762093a8060019201608089015201614131565b62093a80810181116103225762093a8001606089015238613df8565b60046040517f85f0de9f000000000000000000000000000000000000000000000000000000008152fd5b60046040517f9135dc13000000000000000000000000000000000000000000000000000000008152fd5b5082841415613d0e565b60046040517f59ffd6f0000000000000000000000000000000000000000000000000000000008152fd5b508015613cd0565b508c15613cc9565b508215613cc2565b60046040517f6a0a01cc000000000000000000000000000000000000000000000000000000008152fd5b60046040517fcbb4b772000000000000000000000000000000000000000000000000000000008152fd5b506040517f1703e5f90000000000000000000000000000000000000000000000000000000081526001600160a01b038a1660048201526020816024816001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165afa908115611eb1576000916144f5575b50613c86565b61450e915060203d6020116123495761233b81836136a6565b386144ef565b61452d915060203d6020116123495761233b81836136a6565b38613c7f565b506001600160a01b038b1615613c05565b9190600092808452602093600e85526040918282208351808289829454938481520190865289862092865b8b8282106145e857505050614586925003826136a6565b805190835b8281106145a657505050613b689495600e9183525220613b28565b866145b1828461378c565b51146145bf5760010161458b565b600486517fa3d582ec000000000000000000000000000000000000000000000000000000008152fd5b855484526001958601958795509301920161456f565b9060009180835260049260208481526001600160a01b03604090806002838620015416156147a4578484528683528184205416330361477c57838352600e808352818420928251808583829754938481520190885283882092885b8582821061476657505050614670925003856136a6565b835193855b8581106146885750505050505050505050565b88614693828461378c565b51146146a157600101614675565b909192939495969750600019999899958681019081116147535780820361472b575b50505085855252822080548015614718577f72a0d025e6eac8ca921e2325e32ff0f17354f246b9024647bf4c1ab67437a859949596508201916147068383613b10565b909182549160031b1b191690555580a2565b602484603189634e487b7160e01b835252fd5b61473b613b509161474b9461378c565b5191898952858552868920613b10565b3880806146c3565b60248860118d634e487b7160e01b835252fd5b855484526001958601958a955093019201614659565b8590517f2af07d20000000000000000000000000000000000000000000000000000000008152fd5b8682517f62d0ece0000000000000000000000000000000000000000000000000000000008152fd5b6001600160a01b031660009081526010602052604090205480156147ed5790565b50600f5490565b60001981146103225760010190565b9060405161481081613689565b61016061489f600983956001600160a01b038082541686528060018301541660208701526002820154908116604087015265ffffffffffff8160a01c16606087015260d01c6080860152600381015460a0860152600481015460c0860152600581015460e0860152600681015461010086015260078101546101208601526008810154610140860152016136e7565b910152565b9060008181526020600681526040808320858452825260ff81842054169160049283811015614aea57600103614ae1578484528281526148e5828520614803565b92858552600682528285208786528252828520600260ff1982541617905562093a808701808811614ace578661491a916137c3565b8061496e57505050816149679161016060c07f8deaacba1e0606cd9e75aec4cb0fefc4c1df8d262d3a05f83107f8c067e8891f96950151925b0151015161496081613663565b8686614b6e565b80a3600190565b82610160869496015101516002811015614abb5761498d918491614afd565b9086865260078552838620888752855281848720556149b08260c08501516137b6565b94878752600c81526001600160a01b03908186892054169284828c8c614a128b5194859384937fc25a4d9e0000000000000000000000000000000000000000000000000000000085528885016040919493926060820195825260208201520152565b03818d895af1908115614ab1578a91614a94575b5015614a6d57506149679593610160938693614a68937f8deaacba1e0606cd9e75aec4cb0fefc4c1df8d262d3a05f83107f8c067e8891f9b9a98015116614dfb565b614953565b86517fcc48ad7f000000000000000000000000000000000000000000000000000000008152fd5b614aab9150833d85116123495761233b81836136a6565b38614a26565b88513d8c823e3d90fd5b602487602185634e487b7160e01b835252fd5b602486601184634e487b7160e01b835252fd5b50505091505090565b602485602186634e487b7160e01b835252fd5b919091610140820151831015614b645760028110156109cb57614b3657670de0b6b3a76400009160e0614b3292015190613752565b0490565b6101208101518211614b5b57670de0b6b3a764000091610100614b3292015190613752565b60c09150015190565b5060c09150015190565b929091600190614b7d81613663565b03614ce75762093a808201808311610322576000928484526020600581526040928386208054906000198201918211614cd35765ffffffffffff91614bc191613b6a565b90549060031b1c1610614cb8578585526004815260088386209160048301938454906009835286892090895282528588205587875252828520549380614ca7575b50670de0b6b3a764000094858502958587041485151715614c93575083927ffb23ba90a733f32458900fa34afe764a756eb475000cd4858adc5d5111994ebd9594614c8e9383614c67614c5b600860069701548a613732565b98600783015490613732565b94818960058894015501555551938493846040919493926060820195825260208201520152565b0390a2565b80634e487b7160e01b602492526011600452fd5b614cb1919461377f565b9238614c02565b600d915084929395614ccf9552522091825461377f565b9055565b602488634e487b7160e01b81526011600452fd5b919050600052600d602052614ccf604060002091825461377f565b356001600160a01b03811681036103385790565b928115614db5576101408101938451831015614dad5760028110156109cb57614d645750670de0b6b3a7640000928383029280840485149015171561032257614b3292613aaf915190613732565b90919250670de0b6b3a7640000918284029184830484148515171561032257610120614d94910192835190613732565b91518111614da757614b32929350613752565b50505090565b505050905090565b50505050600090565b65ffffffffffff90818111614dd1571690565b60046040517ffef46c2d000000000000000000000000000000000000000000000000000000008152fd5b6040517fa9059cbb0000000000000000000000000000000000000000000000000000000060208201526001600160a01b03929092166024830152604480830193909352918152613b6891614e506064836136a6565b614ec0565b9290604051927f23b872dd0000000000000000000000000000000000000000000000000000000060208501526001600160a01b03809216602485015216604483015260648201526064815260a081019181831067ffffffffffffffff84111761197357613b68926040525b6001600160a01b039092919216604051604081019367ffffffffffffffff9482811086821117611973576040526020928383527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564848401526000808386829551910182855af1903d1561503b573d96871161502757614f7e94959660405190614f70887fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601836136a6565b81528093873d92013e615048565b80519081614f8b57505050565b8280614f9b938301019101613af8565b15614fa35750565b608490604051907f08c379a00000000000000000000000000000000000000000000000000000000082526004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f742073756363656564000000000000000000000000000000000000000000006064820152fd5b602483634e487b7160e01b81526041600452fd5b9150614f7e939495506060915b919290156150c3575081511561505c575090565b3b156150655790565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152fd5b8251909150156150d65750805190602001fd5b604051907f08c379a000000000000000000000000000000000000000000000000000000000825281602080600483015282519283602484015260005b848110615153575050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f836000604480968601015201168101030190fd5b818101830151868201604401528593508201615112565b6001600160a01b0360005416330361517e57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602060248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152fd5b60028054146151eb5760028055565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152fdfea2646970667358221220612fba45258d7f2ab0cbcd81c6bf7f1ba352843ee8b06c17623e9b2a7a9d1f7a64736f6c63430008140033000000000000000000000000f365c45b6913be7ab74c970d9227b9d0dff44afb000000000000000000000000e2a7de3c3190afd79c49c8e8f2fa30ca78b97dfd0000000000000000000000000000000000000000000000000000000000000006
Deployed Bytecode
0x608080604052600436101561001357600080fd5b60003560e01c908163040916a41461354257508063052de43f1461350d578063086146d2146134f25780630881b830146133835780630eb671731461336a57806311195485146130ab578063146bdb7d1461307f57806319ab453c14612fc35780631e96917d14612fa55780632f940c7014612cbb5780633ce455a814612bdc5780633d72387714612b815780633da8a8d114612b4c578063482280d514612b19578063511aa52d14612afb57806357ae1cec14612add5780636eaa04ef14612aa8578063715018a614612a375780637211a52b146128d557806377dd7cf71461289b57806379ba5097146127cb57806379dc77d014612791578063897ec1c7146126f25780638a6ce1cb146120ac5780638da5cb5b146120855780638ebf2fd6146120415780638f4c72d814611f575780638fe8a10114611f345780639655070f14611f13578063975e517314611ee75780639e8c708e14611dde578063a6c93d7d14611db7578063a8a7e38614611ba7578063a8cf56f914611abe578063aa0b598814611a60578063aa412da8146119f6578063ac18de4314611989578063ac4884ac146114c5578063b486d07314610fac578063b5eee72214610f3c578063ba82c89714610e60578063bc30a61814610db9578063bd7ddd0614610d7a578063bfe1092814610d53578063c6c02cac14610c90578063d52f113514610aa9578063daf9c21014610a6a578063dc34a151146109e1578063e085f980146108d6578063e30c3978146108af578063eca5d0ca14610801578063ed8de7191461069f578063f29e9aa7146104fb578063f2fde38b14610416578063f49c1d2414610378578063f8534b011461033d5763f99811401461029257600080fd5b34610338576020806003193601126103385762093a80806004350481810291818304149015171561032257600052600a8152604060002090604051908181845491828152019360005281600020916000905b82821061030b57610307856102fb818903826136a6565b604051918291826135f4565b0390f35b8354865294850194600193840193909101906102e4565b634e487b7160e01b600052601160045260246000fd5b600080fd5b346103385760006003193601126103385760206040517f00000000000000000000000000000000000000000000000000000000000000068152f35b34610338576040600319360112610338576103916135a4565b6024359061039d61516a565b600f5482116103ec5760206001600160a01b037f6aade5fcd818524835ddc10e56891b8efefe8fae5d5817ff5e2c630d41fad354921692836000526010825280604060002055604051908152a2005b60046040517f613970e0000000000000000000000000000000000000000000000000000000008152fd5b346103385760206003193601126103385761042f6135a4565b61043761516a565b6001600160a01b038091169081156104d157806000541682146104a757600154827fffffffffffffffffffffffff0000000000000000000000000000000000000000821617600155167fb3d55174552271a4f1aaf36b72f50381e892171636b3fb5447fe00e995e7a37b600080a3005b60046040517fd5e889bf000000000000000000000000000000000000000000000000000000008152fd5b60046040517f9fabe1c1000000000000000000000000000000000000000000000000000000008152fd5b3461033857602080600319360112610338576004356000526005808252816040600020916040519081928391818654948581520190819660005282600020946000915b81600484011061064b575061058495549184828210610633575b828210610618575b8282106105fd575b8282106105e2575b50106105cc575b50905093929303836136a6565b604051928392818401908285525180915260408401929160005b8281106105ad57505050500390f35b835165ffffffffffff168552869550938101939281019260010161059e565b60c01c65ffffffffffff16815286910187610577565b6001919465ffffffffffff8560901c16815201930184610570565b6001919465ffffffffffff8560601c16815201930184610568565b6001919465ffffffffffff8560301c16815201930184610560565b6001919465ffffffffffff8516815201930184610558565b9450925090600160a08592875465ffffffffffff908181168352818160301c168d84015260608282821c166040850152828260901c169084015260c01c16608082015201950191018792869495929561053e565b34610338576106ad3661362f565b80156107d7578260005260046020526001600160a01b038060026040600020015416156107ad578360005260046020526040600020541633036107835782600052600e602052600a6107046040600020548361377f565b116107595760005b81811061073b57837f72a0d025e6eac8ca921e2325e32ff0f17354f246b9024647bf4c1ab67437a859600080a2005b8061075361074c6001938587613bcc565b3586614544565b0161070c565b60046040517f7dce6cb8000000000000000000000000000000000000000000000000000000008152fd5b60046040517f2af07d20000000000000000000000000000000000000000000000000000000008152fd5b60046040517f62d0ece0000000000000000000000000000000000000000000000000000000008152fd5b60046040517f521299a9000000000000000000000000000000000000000000000000000000008152fd5b346103385760206003193601126103385761081a6135a4565b61082261516a565b6001600160a01b0380821680156104d1576108aa7f9e1cbba858790e137077d865d7e4df5017a55524ea53a66b2c20ec665167d8d693601254927fffffffffffffffffffffffff000000000000000000000000000000000000000084161760125560405193849316839060209093929360408301946001600160a01b03809216845216910152565b0390a1005b346103385760006003193601126103385760206001600160a01b0360015416604051908152f35b3461033857602060031936011261033857600435600052600460205260406000206001600160a01b03908181541690826001820154169260028201546003830154600484015460058501549160068601549360078701549561093f600960088a015499016136e7565b99604051998a5260208a01528116604089015265ffffffffffff8160a01c16606089015260d01c608088015260a087015260c086015260e0850152610100840152610120830152610140820152815161099781613663565b610160820152602082015160028110156109cb576101c09260409161018084015201516109c381613663565b6101a0820152f35b634e487b7160e01b600052602160045260246000fd5b34610338576000600319360112610338576109fa61516a565b60175460ff8116610a405760ff196001911617601755426018557ff4a991ad9e7f9711696f7bd41529beb4c470d75788573535d4ca3f0857c79ce86020604051428152a1005b60046040517f7a480252000000000000000000000000000000000000000000000000000000008152fd5b34610338576020600319360112610338576001600160a01b03610a8b6135a4565b166000526015602052602060ff604060002054166040519015158152f35b3461033857610ab73661362f565b610ac29291926151dc565b60ff60175416610c66576001600160a01b03806013541615610c3c5733600052601460205260ff60406000205416159081610c2d575b506107835760009160009162093a808092048281029281840414901517156103225780156107d7578115610c0357610b2e613765565b821015610bd95781600052600a60205260406000205415610baf57906000915b808310610b6a5760408585600160025582519182526020820152f35b909193610b82610b7b868489613bcc565b35846148a4565b15610b9c57610b926001916147f4565b945b019190610b4e565b9392610ba96001916147f4565b93610b94565b60046040517ffbbfe1b6000000000000000000000000000000000000000000000000000000008152fd5b60046040517f9bcf46f4000000000000000000000000000000000000000000000000000000008152fd5b60046040517f17479ac8000000000000000000000000000000000000000000000000000000008152fd5b90506000541633141584610af8565b60046040517f87138d5c000000000000000000000000000000000000000000000000000000008152fd5b60046040517f0f8eeedb000000000000000000000000000000000000000000000000000000008152fd5b3461033857600060031936011261033857610ca961516a565b60175460ff811615610d295760185462127500810180911161032257421015610cff5760ff19166017557f62c91cdd7db0a8e05658629214f78fbbbc0708b9698bf57892027841dc8899c16020604051428152a1005b60046040517fc905adb1000000000000000000000000000000000000000000000000000000008152fd5b60046040517f81e21bc8000000000000000000000000000000000000000000000000000000008152fd5b346103385760006003193601126103385760206001600160a01b0360135416604051908152f35b3461033857610d883661358e565b90600052600660205260406000209060005260205260ff6040600020541660405160048210156109cb576020918152f35b3461033857602060031936011261033857610dd26135a4565b610dda61516a565b6001600160a01b038091169081156104d157601380547fffffffffffffffffffffffff00000000000000000000000000000000000000008116841790915560408051929091166001600160a01b03908116835290921660208201527f111a961d91cf441fe07e7bfddc128b30ab56974d1a76851e969e0642fdb2dd5091819081016108aa565b3461033857604060031936011261033857610e796135a4565b6024359033600052601460205260ff604060002054161580610f27575b610783576001600160a01b03169081600052601560205260ff6040600020541615610efd5780156103ec5760207f0b431c44bb4597805cf43550dbfc7dd3b2a6752c10eef974a53dba1c7e25ffa691836000526016825280604060002055604051908152a2005b60046040517ff84835a0000000000000000000000000000000000000000000000000000000008152fd5b506001600160a01b0360005416331415610e96565b34610338576020600319360112610338576001600160a01b03610f5d6135a4565b610f6561516a565b1680156104d1578060005260146020526040600020600160ff198254161790557f8e6f8d1a4c834c03bc5c8f9dedff078a6fd2904f7a94b17df04efb916aaba72d600080a2005b346103385760c060031936011261033857610fc56151dc565b60ff60175416610c66576001600160a01b036013541615610c3c57610fe8613ab5565b600435600052600460205260406000209060035460043510156107ad576001600160a01b038254163303610783576024351580156114bb575b61149157604435602435116114675760ff600983015460081c1660028110156109cb571580611459575b61142f57600435600052600560205260406000205415611405576004356000526005602052604060002080549060001982019182116103225765ffffffffffff9161109591613b6a565b90549060031b1c166110a5613765565b808210156113e757505060005b808252156113bd57600282015460d01c4211156113935780516110da60843591606435613752565b036113695760a4356127106110f96110f1336147cc565b608435613752565b040361133f57611107613765565b602082015260058201546024351061131557611129606435600484015461377f565b60408201526004356000526004602052600160ff60096040600020015460101c1661115381613663565b146112f1575b604081015191670de0b6b3a76400009283810290808204851490151715610322576111879060243590613732565b926060830193845260ff600983015460081c1660028110156109cb576001036112e4576040830151818102918183041490151715610322576111cc9060443590613732565b60808301525b6080820192835160078301908154116112ba5760209460843561127e575b60038401611201608435825461377f565b9055602435600585015560443560068501555190555160088201556004604083015191015501516040517fdfb634c689d94c27824516a1fa03ef285dae8d316e897e63403182441d82ab656004359180611274606435604435602435846040919493926060820195825260208201520152565b0390a36001600255005b6112b56001600160a01b0360018601541661129d608435303384614e55565b60a435906001600160a01b0360125416903390614e55565b6111f0565b60046040517f29847030000000000000000000000000000000000000000000000000000000008152fd5b50825160808301526111d2565b6004356000526008602052604060002061130e606435825461377f565b9055611159565b60046040517fa810589a000000000000000000000000000000000000000000000000000000008152fd5b60046040517f1fbbed95000000000000000000000000000000000000000000000000000000008152fd5b60046040517f6b5545d7000000000000000000000000000000000000000000000000000000008152fd5b60046040517f59710113000000000000000000000000000000000000000000000000000000008152fd5b60046040517fffbb7602000000000000000000000000000000000000000000000000000000008152fd5b6113f0916137b6565b62093a809081810180911161032257046110b2565b60046040517fb31fbda3000000000000000000000000000000000000000000000000000000008152fd5b60046040517f81387e3e000000000000000000000000000000000000000000000000000000008152fd5b50604435602435141561104b565b60046040517f4b44b921000000000000000000000000000000000000000000000000000000008152fd5b60046040517fe5a74490000000000000000000000000000000000000000000000000000000008152fd5b5060443515611021565b346103385760806003193601126103385760243565ffffffffffff81168103610338576114f06151dc565b60ff60175416610c66576001600160a01b036013541615610c3c57604051610120810181811067ffffffffffffffff821117611973576040526000815260006020820152600060408201526000606082015260006080820152600060a0820152600060c0820152600060e0820152600061010082015260035460043510156107ad5760043560005260046020526001600160a01b0360406000205416330361078357604435158015611969575b6114915765ffffffffffff82161561193f5760043560005260056020526040600020541561140557600435600052600560205260406000208054806000198101116103225765ffffffffffff916000196115f8920190613b6a565b90549060031b1c168152600435600052600460205260026040600020015460d01c42111561139357805161162a613765565b116113bd57600435600052600460205260046040600020015460608201526004356000526004602052600160ff60096040600020015460101c1661166d81613663565b14611924575b60443561168c65ffffffffffff84166060840151613752565b03611369576064356127106116ab6116a3336147cc565b604435613752565b040361133f5760043560005260046020526001600160a01b0360026040600020015416602082015260043560005260046020526117066001600160a01b03600160406000200154168060408401526044359030903390614e55565b61172f6001600160a01b03604083015116606435906001600160a01b0360125416903390614e55565b805162093a8090818101809111610322578190048181029080820483149015171561032257608083015260043560005260046020526003604060002001611779604435825461377f565b90556004356000526004602052600260406000200165ffffffffffff841665ffffffffffff825460a01c160165ffffffffffff81116103225781547fffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffff1660a09190911b79ffffffffffff00000000000000000000000000000000000000001617905560005b65ffffffffffff84168110611853578365ffffffffffff6040519116815260443560208201527f196ca6579b22278c0854d0a67ac2a7fda3dbb66f74a19c2c55fa79c3a2e4bd9f604060043592a26001600255005b6080830151600052600a6020526118706004356040600020613b28565b60043560005260046020526001600160a01b0360026040600020015416600052600b602052604060002060808401516000526020526118b56004356040600020613b28565b60043560005260056020526118db60406000206118d56080860151614dbe565b90613b8c565b6004356000526006602052604060002060808401516000526020526040600020600160ff19825416179055608083015190828201809211610322576001916080850152016117fe565b60043560005260086020526040600020546060820152611673565b60046040517fc82661ee000000000000000000000000000000000000000000000000000000008152fd5b506064351561159d565b634e487b7160e01b600052604160045260246000fd5b34610338576020600319360112610338576001600160a01b036119aa6135a4565b6119b261516a565b1680156104d157806000526014602052604060002060ff1981541690557f0153940c4f400a39a1ca7f4cd7e46be4b7d3793f6142184e505b8c4e1a810378600080a2005b346103385760208060031936011261033857600435600052600e8152604060002090604051908181845491828152019360005281600020916000905b828210611a4957610307856102fb818903826136a6565b835486529485019460019384019390910190611a32565b3461033857602060031936011261033857600435611a7c61516a565b6101f481116103ec5760407ff9d94f1c6388837eee93d61a8020cf4d537953ce27f2a295d60ad96e6a6ec6a391600f549080600f5582519182526020820152a1005b346103385761014060031936011261033857611ad86135a4565b611ae06135ba565b90611ae96135d0565b91611af26135df565b6084359160e4359360038510156103385761010435936003851015610338576101243567ffffffffffffffff811161033857611b3290369060040161355d565b959094611b3d6151dc565b60ff60175416610c66576001600160a01b036013541615610c3c57602098611b9a98849360405191611b6e8361366d565b611b7781613663565b825260008c830152611b8881613663565b604082015260c4359660a43596613bdc565b6001600255604051908152f35b346103385760406003193601126103385760043567ffffffffffffffff811161033857611bd890369060040161355d565b90611be16135ba565b91611bea6151dc565b60ff60175416610c66576001600160a01b0390816013541615610c3c5780156107d757818416156104d15760009260009160005b818110611c5957505050169182151580611c50575b611c3f575b6001600255005b611c4892614dfb565b808080611c38565b50811515611c33565b611c64818385613bcc565b3560035411156107ad57611c79818385613bcc565b356000526020600481528560406000205416330361078357611c9c828486613bcc565b35600052600d9081815260406000205491611cb8848688613bcc565b3560005281526000604081205581611cd5575b5050600101611c1e565b946004829896611ce6858789613bcc565b3560005252888760016040600020015416918881169081841415600014611d9e575080611d7c575b505060019291611d1e919661377f565b965b7f182587d643fe0f1dce1eafe8f758d78a2cfe0f9acdd4653ec0ad97dc94c3102d611d72611d4f848789613bcc565b604080516001600160a01b038e1681526020810195909552903593918291820190565b0390a29088611ccb565b600194935090611d8d929791614dfb565b600094909150869088611d1e611d0e565b979050611db1925060019493915061377f565b96611d20565b346103385760006003193601126103385760206001600160a01b0360125416604051908152f35b346103385760208060031936011261033857611df86135a4565b611e0061516a565b6001600160a01b03809116806000526015835260ff60406000205416611ebd57604051907f70a082310000000000000000000000000000000000000000000000000000000082523060048301528382602481845afa918215611eb157600092611e82575b50811561149157611e79926000541690614dfb565b60405160018152f35b9091508381813d8311611eaa575b611e9a81836136a6565b8101031261033857519084611e64565b503d611e90565b6040513d6000823e3d90fd5b60046040517f80eb2a01000000000000000000000000000000000000000000000000000000008152fd5b346103385760206003193601126103385760043560005260086020526020604060002054604051908152f35b34610338576020611f2c611f263661358e565b906137c3565b604051908152f35b3461033857600060031936011261033857602060ff601754166040519015158152f35b3461033857604060031936011261033857600435611f736135ba565b611f7b6151dc565b60ff60175416610c66576001600160a01b0390816013541615610c3c576003548310156107ad578260005260046020528160406000205416330361078357818116156104d15782600052600d60205260406000209060008254925581611fe2576001600255005b61201d82827f182587d643fe0f1dce1eafe8f758d78a2cfe0f9acdd4653ec0ad97dc94c3102d95600460205260016040600020015416614dfb565b604080516001600160a01b039290921682526020820192909252a280808080611c38565b346103385760006003193601126103385760206040516001600160a01b037f000000000000000000000000f365c45b6913be7ab74c970d9227b9d0dff44afb168152f35b346103385760006003193601126103385760206001600160a01b0360005416604051908152f35b34610338576060600319360112610338576120c56151dc565b60ff60175416610c66576120d761516a565b62093a80600435048062093a8081020462093a8014811517156103225760035460243510156107ad576001600160a01b0360135416156126c85762093a80810215610c0357612124613765565b62093a80820211610c0357602435600052600460205260406000206006602052604060002062093a80830260005260205260ff6040600020541660048110156109cb5760020361269e576024356000526007602052604060002062093a8083026000526020526040600020546004820154906121a081836137b6565b91602435600052600c6020526001600160a01b0360406000205416906044351580612680575b15612350575060206000916024358352600782526040832062093a808802845282528260408120556064604051809481937f657c5e8c000000000000000000000000000000000000000000000000000000008352602435600484015262093a808b0260248401528160448401525af1908115611eb157600091612321575b50156122f75760ff600984015460101c1661225e81613663565b806122dc57505061228861228d92602435600052600d60205260046040600020549101549061377f565b6137b6565b602435600052600d6020526040600020555b60405160443581527f761ac4569e5e4e02e15740915e4281941ef7c47913333b6414da71b1dd8b809a602062093a80602435940292a36001600255005b9091506122f2925062093a808402602435614b6e565b61229f565b60046040517fcc48ad7f000000000000000000000000000000000000000000000000000000008152fd5b612343915060203d602011612349575b61233b81836136a6565b810190613af8565b85612244565b503d612331565b6009859294950190815490600160ff61237961236b87614803565b604435838760081c16614afd565b9360101c1661238781613663565b036126185750506024356000526009602052604060002062093a8087026000526020526123cf60ff604060002054925460081c16826123c585614803565b9160443590614d16565b9462093a808088020162093a8088021161032257602435600052600560205260406000208054806000198101116103225765ffffffffffff91600019612416920190613b6a565b90549060031b1c1662093a8080890201116000146125485761228861245692602435600052600d60205261245088604060002054926137b6565b9061377f565b602435600052600d6020526040600020555b82828511612520575b5050505b6040517f657c5e8c00000000000000000000000000000000000000000000000000000000815260248035600483015262093a808502908201526044810183905290602090829060649082906000905af1908115611eb157600091612501575b50156122f7576024356000526007602052604060002062093a80830260005260205260406000205561229f565b61251a915060203d6020116123495761233b81836136a6565b836124d4565b6001600160a01b03600161253761254095886137b6565b93015416614dfb565b838082612471565b5050818411156126015761256a600482015461256484876137b6565b906137b6565b670de0b6b3a7640000808202908282041482151715610322577ffb23ba90a733f32458900fa34afe764a756eb475000cd4858adc5d5111994ebd906125c26125b6600886015483613732565b91600786015490613732565b908060058601558160068601558360048601556125f960405192839260243596846040919493926060820195825260208201520152565b0390a2612468565b612613600482015461245086856137b6565b61256a565b61263f925061228890602497929735600052600d60205261245088604060002054926137b6565b602435600052600d60205260406000205582828511612661575b505050612475565b6001600160a01b03600161253761267895886137b6565b838082612659565b50600160ff600987015460101c1661269781613663565b14156121c6565b60046040517f226e5a6a000000000000000000000000000000000000000000000000000000008152fd5b60046040517fdc1669a3000000000000000000000000000000000000000000000000000000008152fd5b346103385760406003193601126103385761270b6135a4565b62093a80908160243504828102928184041490151715610322576001600160a01b0316600052602090600b82526040600020906000528152604060002090604051908181845491828152019360005281600020916000905b82821061277a57610307856102fb818903826136a6565b835486529485019460019384019390910190612763565b34610338576020600319360112610338576001600160a01b036127b26135a4565b1660005260166020526020604060002054604051908152f35b34610338576000600319360112610338576001546001600160a01b038082169081330361287157600092828454927fffffffffffffffffffffffff00000000000000000000000000000000000000009382858216178755167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08680a3166001557fb3d55174552271a4f1aaf36b72f50381e892171636b3fb5447fe00e995e7a37b8280a3005b60046040517f2f02da58000000000000000000000000000000000000000000000000000000008152fd5b34610338576020600319360112610338576001600160a01b036128bc6135a4565b1660005260106020526020604060002054604051908152f35b3461033857602080600319360112610338576128ef6151dc565b60ff60175416610c66576001600160a01b03806013541615610c3c57336000526014825260ff60406000205416159081612a28575b506107835760008062093a80928360043504848102948186041490151715610322578315610c0357612954613765565b841015610bd95783600052600a815260406000205415610baf5783600052600a8152604060002093604051808684829854938481520190600052846000209260005b86828210612a12575050506129ad925003866136a6565b84516000915b8183106129ce57604086868660016002558351928352820152f35b9091946129e56129de878961378c565b51836148a4565b156129ff576129f56001916147f4565b955b0191906129b3565b9493612a0c6001916147f4565b946129f7565b855484526001958601958b955093019201612996565b90506000541633141582612924565b3461033857600060031936011261033857612a5061516a565b60006001600160a01b0381547fffffffffffffffffffffffff000000000000000000000000000000000000000081168355167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a3005b3461033857602060031936011261033857612ac1613765565b62093a80810180911161032257611f2c602091600435906137c3565b34610338576000600319360112610338576020600f54604051908152f35b34610338576000600319360112610338576020601854604051908152f35b3461033857612b273661358e565b9060005260076020526040600020906000526020526020604060002054604051908152f35b3461033857602060031936011261033857600435600052600c60205260206001600160a01b0360406000205416604051908152f35b3461033857602060031936011261033857600435612b9d61516a565b80156103ec5760407f3933dd2114106692c792a24b4d8509cf9c5e4a41a71315c9f2b593085ea76e8991601154908060115582519182526020820152a1005b346103385761016060031936011261033857612bf66135a4565b612bfe6135ba565b612c066135d0565b90612c0f6135df565b92610104359260038410156103385761012435936003851015610338576101443567ffffffffffffffff811161033857612c4d90369060040161355d565b949093612c586151dc565b60ff60175416610c66576001600160a01b036013541615610c3c57602097611b9a9760405194612c878661366d565b612c9081613663565b855260018a860152612ca181613663565b604085015260e4359460c4359460a4359460843594613bdc565b3461033857604060031936011261033857600435612cd76135ba565b612cdf6151dc565b60ff806017541615610d2957601854621275008101809111610322574210612f7b576003548310156107ad5782600052602091600483526001600160a01b03928360406000205416330361078357838216156104d15784600052600d815260406000209260008454945560059081835260406000209160405190848294859282825495868152019160005282600020946000915b816004840110612f275750612db695549184828210612f0f575b828210612ef4575b828210612ed9575b828210612ebe575b5010612ea8575b50905003836136a6565b815190876000526004845260406000209260005b838110612e4b57505050506004600091015582612de8576001600255005b82827f947a9dc0c5e62cc9756634ec0a89afea37eb0305933925040b9bda8200440020956004612e2694896000525260016040600020015416614dfb565b604080516001600160a01b039290921682526020820192909252a28080808080611c38565b8960005260068652604060002065ffffffffffff612e69838561378c565b5116600052865282604060002054169060048210156109cb576001809214612e92575b01612dca565b97612ea29060048701549061377f565b97612e8c565b60c01c65ffffffffffff1681528691018b612dac565b6001919465ffffffffffff8560901c16815201930184612da5565b6001919465ffffffffffff8560601c16815201930184612d9d565b6001919465ffffffffffff8560301c16815201930184612d95565b6001919465ffffffffffff8516815201930184612d8d565b9450925090600160a08592875465ffffffffffff908181168352818160301c168d84015260608282821c166040850152828260901c169084015260c01c166080820152019501910187928794959295612d73565b60046040517f24dab61b000000000000000000000000000000000000000000000000000000008152fd5b34610338576000600319360112610338576020600354604051908152f35b3461033857602060031936011261033857612fdc6135a4565b612fe461516a565b601354906001600160a01b039081831661305557169081156104d157817f4f8cfde3439a1a302c21ca51eec26086efbfd940b8c0279889fc6bb6e73ecc66927fffffffffffffffffffffffff00000000000000000000000000000000000000006020931617601355604051908152a1005b60046040517f0dc149f0000000000000000000000000000000000000000000000000000000008152fd5b3461033857602060031936011261033857600435600052600d6020526020604060002054604051908152f35b346103385760806003193601126103385767ffffffffffffffff600435818111610338576130dd90369060040161355d565b9091604435818111610338576130f790369060040161355d565b90916064359081116103385761311190369060040161355d565b93909261311c6151dc565b60ff60175416610c66576001600160a01b036013541615610c3c5733600052601460205260ff604060002054161580613355575b6107835762093a809283602435049485858102048514861517156103225786840361332b5781840361332b5760005b84811061318d576001600255005b61319881868b613bcc565b3560035411156107ad576131ad818984613bcc565b3515613301576131be818486613bcc565b3515611491576131cf81868b613bcc565b356000526006602052604060002086880260005260205260ff6040600020541660048110156109cb5760020361269e578560208860848760008e8e61324e896132468f8e61323f84836132248280968c613bcc565b358b52600c8f526001600160a01b0360408c20541699613bcc565b3598613bcc565b35928c613bcc565b359060405198899788967f75d0d3800000000000000000000000000000000000000000000000000000000088526004880152026024860152604485015260648401525af1908115611eb1576000916132e2575b50156122f757806132b5600192878c613bcc565b35600052600660205260406000208789026000526020526040600020600360ff198254161790550161317f565b6132fb915060203d6020116123495761233b81836136a6565b8a6132a1565b60046040517f85ac2b99000000000000000000000000000000000000000000000000000000008152fd5b60046040517fc155fe21000000000000000000000000000000000000000000000000000000008152fd5b506001600160a01b0360005416331415613150565b346103385761338161337b3661358e565b906145fe565b005b346103385760406003193601126103385767ffffffffffffffff600435818111610338576133b590369060040161355d565b91602435908111610338576133ce90369060040161355d565b919091336000526020916014835260ff6040600020541615806134dd575b6107835784156107d75781850361332b5760005b85811061340957005b6001600160a01b039081613426613421838a87613bcc565b614d02565b16156104d157613437818588613bcc565b35156103ec578161344c613421838a87613bcc565b16600052601585526040600020916001928360ff19825416179055613472828689613bcc565b3581613482613421858c89613bcc565b16600052601687526040600020557fb2fe06f7c1de1ec8e3329909cafefe4d305789d8438c9755943d504772d53730866134c0613421858c89613bcc565b926134cc85898c613bcc565b35936040519485521692a201613400565b506001600160a01b03600054163314156133ec565b34610338576000600319360112610338576020611f2c613765565b3461033857602060031936011261033857600435600052600460205260206001600160a01b0360406000205416604051908152f35b34610338576000600319360112610338576020906011548152f35b9181601f840112156103385782359167ffffffffffffffff8311610338576020808501948460051b01011161033857565b6003196040910112610338576004359060243590565b600435906001600160a01b038216820361033857565b602435906001600160a01b038216820361033857565b60443590811515820361033857565b6064359065ffffffffffff8216820361033857565b6020908160408183019282815285518094520193019160005b82811061361b575050505090565b83518552938101939281019260010161360d565b90604060031983011261033857600435916024359067ffffffffffffffff82116103385761365f9160040161355d565b9091565b600311156109cb57565b6060810190811067ffffffffffffffff82111761197357604052565b610180810190811067ffffffffffffffff82111761197357604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761197357604052565b906040516136f48161366d565b80925460ff811661370481613663565b825260ff8160081c1660028110156109cb57602083015260101c60ff169060409061372e83613663565b0152565b811561373c570490565b634e487b7160e01b600052601260045260246000fd5b8181029291811591840414171561032257565b62093a808042048181029181830414901517156103225790565b9190820180921161032257565b80518210156137a05760209160051b010190565b634e487b7160e01b600052603260045260246000fd5b9190820391821161032257565b906000908082526020600e8152604090818420928251808584829754938481520190885284882092885b868282106139ff57505050613804925003856136a6565b8452600494858252828520956001600160a01b039660ff60098960028401541692015416977f000000000000000000000000f365c45b6913be7ab74c970d9227b9d0dff44afb16918796805189915b8183106139505750505061386689613663565b600289146139445785517f734648440000000000000000000000000000000000000000000000000000000081526001600160a01b0390921690820190815260208101939093529291829184918290819060400103915afa92831561393b57508492613906575b50506138d9600191613a23565b936138e381613663565b146138ed57505090565b90918082106138fc5750505b90565b6138f992506137b6565b90809250813d8311613934575b61391d81836136a6565b8101031261393057516138d960016138cc565b8280fd5b503d613913565b513d86823e3d90fd5b50505050505091505090565b90919861395d8a8361378c565b518951907f454640300000000000000000000000000000000000000000000000000000000082528582015285602482015287604482015288816064818a5afa9081156139f5578c916139c3575b506001916124506139ba92613a23565b99019190613853565b90508881813d83116139ee575b6139da81836136a6565b810103126139ea575160016139aa565b8b80fd5b503d6139d0565b8a513d8e823e3d90fd5b855484526001958601958a9550930192016137ed565b604d811161032257600a0a90565b7f000000000000000000000000000000000000000000000000000000000000000660128103613a50575090565b906012821115613a96577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee820191821161032257613a906138f992613a15565b90613732565b90601203906012821161032257613aaf6138f992613a15565b90613752565b6040519060c0820182811067ffffffffffffffff821117611973576040528160a06000918281528260208201528260408201528260608201528260808201520152565b90816020910312610338575180151581036103385790565b80548210156137a05760005260206000200190600090565b90815491680100000000000000008310156119735782613b50916001613b6895018155613b10565b90919060001983549160031b92831b921b1916179055565b565b91909180548310156137a0576000526006600560206000208185040193060290565b80546801000000000000000081101561197357613bae91600182018155613b6a565b819291549060031b9165ffffffffffff809116831b921b1916179055565b91908110156137a05760051b0190565b969a999395909892949194613bef613ab5565b98338a526001600160a01b038916158015614533575b6104d1576040517faa79979b0000000000000000000000000000000000000000000000000000000081526001600160a01b038a1660048201526020816024816001600160a01b037f000000000000000000000000f365c45b6913be7ab74c970d9227b9d0dff44afb165afa908115611eb157600091614514575b5080614478575b1561444e576001600160a01b038b16600052601560205260ff6040600020541615610efd5765ffffffffffff881615614424578315801561441c575b8015614414575b801561440c575b611491576001600160a01b038b16600052601660205260406000205484106143e25782841161146757602087015160028110156109cb5715806143d8575b61142f5780612710613d238f613aaf336147cc565b040361133f5760026040880151613d3981613663565b613d4281613663565b146143ae57613d5965ffffffffffff89168e613732565b8060208c015280670de0b6b3a7640000810204670de0b6b3a764000014811517156103225783670de0b6b3a7640000613d929202613732565b8060408c01526011541161438457613de490613dc48e8d6001600160a01b038e51166001600160a01b03309216614e55565b8b6001600160a01b038c51166001600160a01b0380601254169216614e55565b613dec613765565b908160608b0152614368575b506003549a60018c016003556001600160a01b0389511691613e1d60608b0151614dbe565b9360208b015160408c01519381670de0b6b3a7640000810204670de0b6b3a764000014821517156103225765ffffffffffff613e7e978f8e6001600160a01b038092613e7389670de0b6b3a76400008a02613732565b9b6040519d8e613689565b8d521660208c01521660408a0152818d1660608a015216608088015260a087015260c086015260e0850152610100840152610120830152610140820152836101608201528960005260046020526101606040600020916001600160a01b038151167fffffffffffffffffffffffff0000000000000000000000000000000000000000845416178355600183016001600160a01b036020830151167fffffffffffffffffffffffff0000000000000000000000000000000000000000825416179055600283016001600160a01b036040830151167fffffffffffffffffffffffff0000000000000000000000000000000000000000825416178155613fd465ffffffffffff60608401511682907fffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffff79ffffffffffff000000000000000000000000000000000000000083549260a01b169116179055565b608082015179ffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffff000000000000000000000000000000000000000000000000000083549260d01b16911617905560a0810151600384015560c0810151600484015560e081015160058401556101008101516006840155610120810151600784015561014081015160088401550151805161406d81613663565b61407681613663565b60098301549160208101519060028210156109cb576009937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000060ff62ff0000604061ff009501516140c681613663565b6140cf81613663565b60101b1695169116179160081b1617179101556001600160a01b036013541689600052600c6020526040600020907fffffffffffffffffffffffff00000000000000000000000000000000000000008254161790556060860151608087015260005b65ffffffffffff851681106142b157508160a087015281614261575b50506040600191015161415f81613663565b61416881613663565b14614248575b600060206001600160a01b03601354166044604051809481937f58ad40640000000000000000000000000000000000000000000000000000000083528c60048401526001600160a01b038b1660248401525af1908115611eb157600091614229575b50156122f75765ffffffffffff60606001600160a01b0394015191846040519616865216602085015260408401521690837f1a2d9584765fd3a320beb3104d8e3ea37336abaa62152ebff24a0b8135c2823560603393a4565b614242915060203d6020116123495761233b81836136a6565b386141d0565b602083015186600052600860205260406000205561416e565b94919693909592600a87116107595760005b60a084015181101561429d57806142976142906001938b8b613bcc565b358c614544565b01614273565b50929550929560019194506040909161414d565b6080870151600052600a6020526142cc8a6040600020613b28565b6001600160a01b038616600052600b602052604060002060808801516000526020526142fc8a6040600020613b28565b896000526006602052604060002060808801516000526020526040600020600160ff1982541617905589600052600560205261434360406000206118d560808a0151614dbe565b60808701519062093a80820182116103225762093a8060019201608089015201614131565b62093a80810181116103225762093a8001606089015238613df8565b60046040517f85f0de9f000000000000000000000000000000000000000000000000000000008152fd5b60046040517f9135dc13000000000000000000000000000000000000000000000000000000008152fd5b5082841415613d0e565b60046040517f59ffd6f0000000000000000000000000000000000000000000000000000000008152fd5b508015613cd0565b508c15613cc9565b508215613cc2565b60046040517f6a0a01cc000000000000000000000000000000000000000000000000000000008152fd5b60046040517fcbb4b772000000000000000000000000000000000000000000000000000000008152fd5b506040517f1703e5f90000000000000000000000000000000000000000000000000000000081526001600160a01b038a1660048201526020816024816001600160a01b037f000000000000000000000000f365c45b6913be7ab74c970d9227b9d0dff44afb165afa908115611eb1576000916144f5575b50613c86565b61450e915060203d6020116123495761233b81836136a6565b386144ef565b61452d915060203d6020116123495761233b81836136a6565b38613c7f565b506001600160a01b038b1615613c05565b9190600092808452602093600e85526040918282208351808289829454938481520190865289862092865b8b8282106145e857505050614586925003826136a6565b805190835b8281106145a657505050613b689495600e9183525220613b28565b866145b1828461378c565b51146145bf5760010161458b565b600486517fa3d582ec000000000000000000000000000000000000000000000000000000008152fd5b855484526001958601958795509301920161456f565b9060009180835260049260208481526001600160a01b03604090806002838620015416156147a4578484528683528184205416330361477c57838352600e808352818420928251808583829754938481520190885283882092885b8582821061476657505050614670925003856136a6565b835193855b8581106146885750505050505050505050565b88614693828461378c565b51146146a157600101614675565b909192939495969750600019999899958681019081116147535780820361472b575b50505085855252822080548015614718577f72a0d025e6eac8ca921e2325e32ff0f17354f246b9024647bf4c1ab67437a859949596508201916147068383613b10565b909182549160031b1b191690555580a2565b602484603189634e487b7160e01b835252fd5b61473b613b509161474b9461378c565b5191898952858552868920613b10565b3880806146c3565b60248860118d634e487b7160e01b835252fd5b855484526001958601958a955093019201614659565b8590517f2af07d20000000000000000000000000000000000000000000000000000000008152fd5b8682517f62d0ece0000000000000000000000000000000000000000000000000000000008152fd5b6001600160a01b031660009081526010602052604090205480156147ed5790565b50600f5490565b60001981146103225760010190565b9060405161481081613689565b61016061489f600983956001600160a01b038082541686528060018301541660208701526002820154908116604087015265ffffffffffff8160a01c16606087015260d01c6080860152600381015460a0860152600481015460c0860152600581015460e0860152600681015461010086015260078101546101208601526008810154610140860152016136e7565b910152565b9060008181526020600681526040808320858452825260ff81842054169160049283811015614aea57600103614ae1578484528281526148e5828520614803565b92858552600682528285208786528252828520600260ff1982541617905562093a808701808811614ace578661491a916137c3565b8061496e57505050816149679161016060c07f8deaacba1e0606cd9e75aec4cb0fefc4c1df8d262d3a05f83107f8c067e8891f96950151925b0151015161496081613663565b8686614b6e565b80a3600190565b82610160869496015101516002811015614abb5761498d918491614afd565b9086865260078552838620888752855281848720556149b08260c08501516137b6565b94878752600c81526001600160a01b03908186892054169284828c8c614a128b5194859384937fc25a4d9e0000000000000000000000000000000000000000000000000000000085528885016040919493926060820195825260208201520152565b03818d895af1908115614ab1578a91614a94575b5015614a6d57506149679593610160938693614a68937f8deaacba1e0606cd9e75aec4cb0fefc4c1df8d262d3a05f83107f8c067e8891f9b9a98015116614dfb565b614953565b86517fcc48ad7f000000000000000000000000000000000000000000000000000000008152fd5b614aab9150833d85116123495761233b81836136a6565b38614a26565b88513d8c823e3d90fd5b602487602185634e487b7160e01b835252fd5b602486601184634e487b7160e01b835252fd5b50505091505090565b602485602186634e487b7160e01b835252fd5b919091610140820151831015614b645760028110156109cb57614b3657670de0b6b3a76400009160e0614b3292015190613752565b0490565b6101208101518211614b5b57670de0b6b3a764000091610100614b3292015190613752565b60c09150015190565b5060c09150015190565b929091600190614b7d81613663565b03614ce75762093a808201808311610322576000928484526020600581526040928386208054906000198201918211614cd35765ffffffffffff91614bc191613b6a565b90549060031b1c1610614cb8578585526004815260088386209160048301938454906009835286892090895282528588205587875252828520549380614ca7575b50670de0b6b3a764000094858502958587041485151715614c93575083927ffb23ba90a733f32458900fa34afe764a756eb475000cd4858adc5d5111994ebd9594614c8e9383614c67614c5b600860069701548a613732565b98600783015490613732565b94818960058894015501555551938493846040919493926060820195825260208201520152565b0390a2565b80634e487b7160e01b602492526011600452fd5b614cb1919461377f565b9238614c02565b600d915084929395614ccf9552522091825461377f565b9055565b602488634e487b7160e01b81526011600452fd5b919050600052600d602052614ccf604060002091825461377f565b356001600160a01b03811681036103385790565b928115614db5576101408101938451831015614dad5760028110156109cb57614d645750670de0b6b3a7640000928383029280840485149015171561032257614b3292613aaf915190613732565b90919250670de0b6b3a7640000918284029184830484148515171561032257610120614d94910192835190613732565b91518111614da757614b32929350613752565b50505090565b505050905090565b50505050600090565b65ffffffffffff90818111614dd1571690565b60046040517ffef46c2d000000000000000000000000000000000000000000000000000000008152fd5b6040517fa9059cbb0000000000000000000000000000000000000000000000000000000060208201526001600160a01b03929092166024830152604480830193909352918152613b6891614e506064836136a6565b614ec0565b9290604051927f23b872dd0000000000000000000000000000000000000000000000000000000060208501526001600160a01b03809216602485015216604483015260648201526064815260a081019181831067ffffffffffffffff84111761197357613b68926040525b6001600160a01b039092919216604051604081019367ffffffffffffffff9482811086821117611973576040526020928383527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564848401526000808386829551910182855af1903d1561503b573d96871161502757614f7e94959660405190614f70887fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601836136a6565b81528093873d92013e615048565b80519081614f8b57505050565b8280614f9b938301019101613af8565b15614fa35750565b608490604051907f08c379a00000000000000000000000000000000000000000000000000000000082526004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f742073756363656564000000000000000000000000000000000000000000006064820152fd5b602483634e487b7160e01b81526041600452fd5b9150614f7e939495506060915b919290156150c3575081511561505c575090565b3b156150655790565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152fd5b8251909150156150d65750805190602001fd5b604051907f08c379a000000000000000000000000000000000000000000000000000000000825281602080600483015282519283602484015260005b848110615153575050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f836000604480968601015201168101030190fd5b818101830151868201604401528593508201615112565b6001600160a01b0360005416330361517e57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602060248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152fd5b60028054146151eb5760028055565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152fdfea2646970667358221220612fba45258d7f2ab0cbcd81c6bf7f1ba352843ee8b06c17623e9b2a7a9d1f7a64736f6c63430008140033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000f365c45b6913be7ab74c970d9227b9d0dff44afb000000000000000000000000e2a7de3c3190afd79c49c8e8f2fa30ca78b97dfd0000000000000000000000000000000000000000000000000000000000000006
-----Decoded View---------------
Arg [0] : _voter (address): 0xF365C45B6913BE7Ab74C970D9227B9D0dfF44aFb
Arg [1] : _chest (address): 0xE2a7De3C3190AFd79C49C8E8f2Fa30Ca78B97DFd
Arg [2] : _veNFTDecimals (uint256): 6
-----Encoded View---------------
3 Constructor Arguments found :
Arg [0] : 000000000000000000000000f365c45b6913be7ab74c970d9227b9d0dff44afb
Arg [1] : 000000000000000000000000e2a7de3c3190afd79c49c8e8f2fa30ca78b97dfd
Arg [2] : 0000000000000000000000000000000000000000000000000000000000000006
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 30 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
[ 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.